#region References using System; using System.Collections; using System.Collections.Generic; using System.Linq; using Server.Accounting; using Server.ContextMenus; using Server.Engines.BulkOrders; using Server.Factions; using Server.Items; using Server.Misc; using Server.Mobiles; using Server.Network; using Server.Regions; using Server.Services.Virtues; using Server.Targeting; #endregion namespace Server.Mobiles { public enum VendorShoeType { None, Shoes, Boots, Sandals, ThighBoots } public abstract class BaseVendor : BaseCreature, IVendor { public static bool UseVendorEconomy = Core.AOS && !Siege.SiegeShard; public static int BuyItemChange = Config.Get("Vendors.BuyItemChange", 1000); public static int SellItemChange = Config.Get("Vendors.SellItemChange", 1000); public static int EconomyStockAmount = Config.Get("Vendors.EconomyStockAmount", 500); public static TimeSpan DelayRestock = TimeSpan.FromMinutes(Config.Get("Vendors.RestockDelay", 60)); public static int MaxSell = Config.Get("Vendors.MaxSell", 500); public static List AllVendors { get; private set; } static BaseVendor() { AllVendors = new List(0x4000); } protected abstract List SBInfos { get; } private readonly ArrayList m_ArmorBuyInfo = new ArrayList(); private readonly ArrayList m_ArmorSellInfo = new ArrayList(); private DateTime m_LastRestock; public override bool CanTeach { get { return true; } } public override bool BardImmune { get { return true; } } public override bool PlayerRangeSensitive { get { return true; } } public override bool UseSmartAI { get { return true; } } public virtual bool IsActiveVendor { get { return true; } } public virtual bool IsActiveBuyer { get { return IsActiveVendor && !Siege.SiegeShard; } } // response to vendor SELL public virtual bool IsActiveSeller { get { return IsActiveVendor; } } // repsonse to vendor BUY public virtual bool HasHonestyDiscount { get { return true; } } public virtual NpcGuild NpcGuild { get { return NpcGuild.None; } } public virtual bool ChangeRace { get { return true; } } public override bool IsInvulnerable { get { return true; } } public virtual DateTime NextTrickOrTreat { get; set; } public virtual double GetMoveDelay { get { return (double)Utility.RandomMinMax(30, 120); } } public override bool ShowFameTitle { get { return false; } } public virtual bool IsValidBulkOrder(Item item) { return false; } public virtual Item CreateBulkOrder(Mobile from, bool fromContextMenu) { return null; } public virtual bool SupportsBulkOrders(Mobile from) { return false; } public virtual TimeSpan GetNextBulkOrder(Mobile from) { return TimeSpan.Zero; } public virtual void OnSuccessfulBulkOrderReceive(Mobile from) { } public virtual BODType BODType { get { return BODType.Smith; } } #region Faction public virtual int GetPriceScalar() { Town town = Town.FromRegion(Region); if (town != null) { return (100 + town.Tax); } return 100; } public void UpdateBuyInfo() { int priceScalar = GetPriceScalar(); var buyinfo = (IBuyItemInfo[])m_ArmorBuyInfo.ToArray(typeof(IBuyItemInfo)); if (buyinfo != null) { foreach (IBuyItemInfo info in buyinfo) { info.PriceScalar = priceScalar; } } } #endregion private class BulkOrderInfoEntry : ContextMenuEntry { private readonly Mobile m_From; private readonly BaseVendor m_Vendor; public BulkOrderInfoEntry(Mobile from, BaseVendor vendor) : base(6152, 10) { Enabled = vendor.CheckVendorAccess(from); m_From = from; m_Vendor = vendor; } public override void OnClick() { if (!m_From.InRange(m_Vendor.Location, 10)) return; EventSink.InvokeBODOffered(new BODOfferEventArgs(m_From, m_Vendor)); if (m_Vendor.SupportsBulkOrders(m_From) && m_From is PlayerMobile) { if (BulkOrderSystem.NewSystemEnabled) { if (BulkOrderSystem.CanGetBulkOrder(m_From, m_Vendor.BODType) || m_From.AccessLevel > AccessLevel.Player) { Item bulkOrder = BulkOrderSystem.CreateBulkOrder(m_From, m_Vendor.BODType, true); if (bulkOrder is LargeBOD) { m_From.CloseGump(typeof (LargeBODAcceptGump)); m_From.SendGump(new LargeBODAcceptGump(m_From, (LargeBOD)bulkOrder)); } else if (bulkOrder is SmallBOD) { m_From.CloseGump(typeof (SmallBODAcceptGump)); m_From.SendGump(new SmallBODAcceptGump(m_From, (SmallBOD)bulkOrder)); } } else { TimeSpan ts = BulkOrderSystem.GetNextBulkOrder(m_Vendor.BODType, (PlayerMobile)m_From); int totalSeconds = (int)ts.TotalSeconds; int totalHours = (totalSeconds + 3599) / 3600; int totalMinutes = (totalSeconds + 59) / 60; m_Vendor.SayTo(m_From, 1072058, totalMinutes.ToString(), 0x3B2); // An offer may be available in about ~1_minutes~ minutes. } } else { TimeSpan ts = m_Vendor.GetNextBulkOrder(m_From); int totalSeconds = (int)ts.TotalSeconds; int totalHours = (totalSeconds + 3599) / 3600; int totalMinutes = (totalSeconds + 59) / 60; if (((Core.SE) ? totalMinutes == 0 : totalHours == 0)) { m_From.SendLocalizedMessage(1049038); // You can get an order now. if (Core.AOS) { Item bulkOrder = m_Vendor.CreateBulkOrder(m_From, true); if (bulkOrder is LargeBOD) { m_From.CloseGump(typeof (LargeBODAcceptGump)); m_From.SendGump(new LargeBODAcceptGump(m_From, (LargeBOD)bulkOrder)); } else if (bulkOrder is SmallBOD) { m_From.CloseGump(typeof (SmallBODAcceptGump)); m_From.SendGump(new SmallBODAcceptGump(m_From, (SmallBOD)bulkOrder)); } } } else { int oldSpeechHue = m_Vendor.SpeechHue; m_Vendor.SpeechHue = 0x3B2; if (Core.SE) { m_Vendor.SayTo(m_From, 1072058, totalMinutes.ToString(), 0x3B2); // An offer may be available in about ~1_minutes~ minutes. } else { m_Vendor.SayTo(m_From, 1049039, totalHours.ToString(), 0x3B2); // An offer may be available in about ~1_hours~ hours. } m_Vendor.SpeechHue = oldSpeechHue; } } } } } private class BribeEntry : ContextMenuEntry { private Mobile m_From; private BaseVendor m_Vendor; public BribeEntry(Mobile from, BaseVendor vendor) : base(1152294, 2) { Enabled = vendor.CheckVendorAccess(from); m_From = from; m_Vendor = vendor; } public override void OnClick() { if (!m_From.InRange(m_Vendor.Location, 2) || !(m_From is PlayerMobile)) return; if (m_Vendor.SupportsBulkOrders(m_From) && m_From is PlayerMobile) { if (m_From.NetState != null && m_From.NetState.IsEnhancedClient) { Timer.DelayCall(TimeSpan.FromMilliseconds(100), m_Vendor.TryBribe, m_From); } else { m_Vendor.TryBribe(m_From); } } } } private class ClaimRewardsEntry : ContextMenuEntry { private readonly Mobile m_From; private readonly BaseVendor m_Vendor; public ClaimRewardsEntry(Mobile from, BaseVendor vendor) : base(1155593, 3) { Enabled = vendor.CheckVendorAccess(from); m_From = from; m_Vendor = vendor; } public override void OnClick() { if (!m_From.InRange(m_Vendor.Location, 3) || !(m_From is PlayerMobile)) return; var context = BulkOrderSystem.GetContext(m_From); int pending = context.GetPendingRewardFor(m_Vendor.BODType); if (pending > 0) { if (context.PointsMode == PointsMode.Enabled) { m_From.SendGump(new ConfirmBankPointsGump((PlayerMobile)m_From, m_Vendor, m_Vendor.BODType, pending, (double)pending * 0.02)); } else { m_From.SendGump(new RewardsGump(m_Vendor, (PlayerMobile)m_From, m_Vendor.BODType, pending)); } } else if (!BulkOrderSystem.CanClaimRewards(m_From)) { m_Vendor.SayTo(m_From, 1157083, 0x3B2); // You must claim your last turn-in reward in order for us to continue doing business. } else { m_From.SendGump(new RewardsGump(m_Vendor, (PlayerMobile)m_From, m_Vendor.BODType)); } } } public BaseVendor(string title) : base(AIType.AI_Vendor, FightMode.None, 2, 1, 0.5, 5) { AllVendors.Add(this); LoadSBInfo(); Title = title; InitBody(); InitOutfit(); Container pack; //these packs MUST exist, or the client will crash when the packets are sent pack = new Backpack(); pack.Layer = Layer.ShopBuy; pack.Movable = false; pack.Visible = false; AddItem(pack); pack = new Backpack(); pack.Layer = Layer.ShopResale; pack.Movable = false; pack.Visible = false; AddItem(pack); BribeMultiplier = Utility.Random(10); m_LastRestock = DateTime.UtcNow; } public BaseVendor(Serial serial) : base(serial) { AllVendors.Add(this); } public override void OnDelete() { base.OnDelete(); AllVendors.Remove(this); } public override void OnAfterDelete() { base.OnAfterDelete(); AllVendors.Remove(this); } public DateTime LastRestock { get { return m_LastRestock; } set { m_LastRestock = value; } } public virtual TimeSpan RestockDelay { get { return DelayRestock; } } public Container BuyPack { get { Container pack = FindItemOnLayer(Layer.ShopBuy) as Container; if (pack == null) { pack = new Backpack(); pack.Layer = Layer.ShopBuy; pack.Visible = false; AddItem(pack); } return pack; } } public abstract void InitSBInfo(); public virtual bool IsTokunoVendor { get { return (Map == Map.Tokuno); } } public virtual bool IsStygianVendor { get { return (Map == Map.TerMur); } } protected void LoadSBInfo() { m_LastRestock = DateTime.UtcNow; for (int i = 0; i < m_ArmorBuyInfo.Count; ++i) { GenericBuyInfo buy = m_ArmorBuyInfo[i] as GenericBuyInfo; if (buy != null) { buy.DeleteDisplayEntity(); } } SBInfos.Clear(); InitSBInfo(); m_ArmorBuyInfo.Clear(); m_ArmorSellInfo.Clear(); for (int i = 0; i < SBInfos.Count; i++) { SBInfo sbInfo = SBInfos[i]; m_ArmorBuyInfo.AddRange(sbInfo.BuyInfo); m_ArmorSellInfo.Add(sbInfo.SellInfo); } } public virtual bool GetGender() { return Utility.RandomBool(); } public virtual void InitBody() { InitStats(100, 100, 25); SpeechHue = Utility.RandomDyedHue(); Hue = Utility.RandomSkinHue(); Female = GetGender(); if (Female) { Body = 0x191; Name = NameList.RandomName("female"); } else { Body = 0x190; Name = NameList.RandomName("male"); } } public virtual int GetRandomHue() { switch (Utility.Random(5)) { default: case 0: return Utility.RandomBlueHue(); case 1: return Utility.RandomGreenHue(); case 2: return Utility.RandomRedHue(); case 3: return Utility.RandomYellowHue(); case 4: return Utility.RandomNeutralHue(); } } public virtual int GetShoeHue() { if (0.1 > Utility.RandomDouble()) { return 0; } return Utility.RandomNeutralHue(); } public virtual VendorShoeType ShoeType { get { return VendorShoeType.Shoes; } } public virtual void CheckMorph() { if (!ChangeRace) return; if (CheckGargoyle()) { return; } #region SA else if (CheckTerMur()) { return; } #endregion else if (CheckNecromancer()) { return; } else if (CheckTokuno()) { return; } } public virtual bool CheckTokuno() { if (Map != Map.Tokuno) { return false; } NameList n; if (Female) { n = NameList.GetNameList("tokuno female"); } else { n = NameList.GetNameList("tokuno male"); } if (!n.ContainsName(Name)) { TurnToTokuno(); } return true; } public virtual void TurnToTokuno() { if (Female) { Name = NameList.RandomName("tokuno female"); } else { Name = NameList.RandomName("tokuno male"); } } public virtual bool CheckGargoyle() { Map map = Map; if (map != Map.Ilshenar) { return false; } if (!Region.IsPartOf("Gargoyle City")) { return false; } if (Body != 0x2F6 || (Hue & 0x8000) == 0) { TurnToGargoyle(); } return true; } #region SA Change public virtual bool CheckTerMur() { Map map = Map; if (map != Map.TerMur || Server.Spells.SpellHelper.IsEodon(map, Location)) return false; if (Body != 0x29A || Body != 0x29B) TurnToGargRace(); return true; } #endregion public virtual bool CheckNecromancer() { Map map = Map; if (map != Map.Malas) { return false; } if (!Region.IsPartOf("Umbra")) { return false; } if (Hue != 0x83E8) { TurnToNecromancer(); } return true; } public override void OnAfterSpawn() { CheckMorph(); } protected override void OnMapChange(Map oldMap) { base.OnMapChange(oldMap); CheckMorph(); LoadSBInfo(); } public virtual int GetRandomNecromancerHue() { switch (Utility.Random(20)) { case 0: return 0; case 1: return 0x4E9; default: return Utility.RandomList(0x485, 0x497); } } public virtual void TurnToNecromancer() { for (int i = 0; i < Items.Count; ++i) { Item item = Items[i]; if (item is Hair || item is Beard) { item.Hue = 0; } else if (item is BaseClothing || item is BaseWeapon || item is BaseArmor || item is BaseTool) { item.Hue = GetRandomNecromancerHue(); } } HairHue = 0; FacialHairHue = 0; Hue = 0x83E8; } public virtual void TurnToGargoyle() { for (int i = 0; i < Items.Count; ++i) { Item item = Items[i]; if (item is BaseClothing || item is Hair || item is Beard) { item.Delete(); } } HairItemID = 0; FacialHairItemID = 0; Body = 0x2F6; Hue = Utility.RandomBrightHue() | 0x8000; Name = NameList.RandomName("gargoyle vendor"); CapitalizeTitle(); } #region SA public virtual void TurnToGargRace() { for (int i = 0; i < Items.Count; ++i) { Item item = Items[i]; if (item is BaseClothing) { item.Delete(); } } Race = Race.Gargoyle; Hue = Race.RandomSkinHue(); HairItemID = Race.RandomHair(Female); HairHue = Race.RandomHairHue(); FacialHairItemID = Race.RandomFacialHair(Female); if (FacialHairItemID != 0) { FacialHairHue = Race.RandomHairHue(); } else { FacialHairHue = 0; } InitGargOutfit(); if (Female = GetGender()) { Body = 0x29B; Name = NameList.RandomName("gargoyle female"); } else { Body = 0x29A; Name = NameList.RandomName("gargoyle male"); } CapitalizeTitle(); } #endregion public virtual void CapitalizeTitle() { string title = Title; if (title == null) { return; } var split = title.Split(' '); for (int i = 0; i < split.Length; ++i) { if (Insensitive.Equals(split[i], "the")) { continue; } if (split[i].Length > 1) { split[i] = Char.ToUpper(split[i][0]) + split[i].Substring(1); } else if (split[i].Length > 0) { split[i] = Char.ToUpper(split[i][0]).ToString(); } } Title = String.Join(" ", split); } public virtual int GetHairHue() { return Utility.RandomHairHue(); } public virtual void InitOutfit() { switch (Utility.Random(3)) { case 0: SetWearable(new FancyShirt(GetRandomHue())); break; case 1: SetWearable(new Doublet(GetRandomHue())); break; case 2: SetWearable(new Shirt(GetRandomHue())); break; } switch (ShoeType) { case VendorShoeType.Shoes: SetWearable(new Shoes(GetShoeHue())); break; case VendorShoeType.Boots: SetWearable(new Boots(GetShoeHue())); break; case VendorShoeType.Sandals: SetWearable(new Sandals(GetShoeHue())); break; case VendorShoeType.ThighBoots: SetWearable(new ThighBoots(GetShoeHue())); break; } int hairHue = GetHairHue(); Utility.AssignRandomHair(this, hairHue); Utility.AssignRandomFacialHair(this, hairHue); if (Body == 0x191) { FacialHairItemID = 0; } if (Body == 0x191) { switch (Utility.Random(6)) { case 0: SetWearable(new ShortPants(GetRandomHue())); break; case 1: case 2: SetWearable(new Kilt(GetRandomHue())); break; case 3: case 4: case 5: SetWearable(new Skirt(GetRandomHue())); break; } } else { switch (Utility.Random(2)) { case 0: SetWearable(new LongPants(GetRandomHue())); break; case 1: SetWearable(new ShortPants(GetRandomHue())); break; } } if(!Siege.SiegeShard) PackGold(100, 200); } #region SA public virtual void InitGargOutfit() { for (int i = 0; i < Items.Count; ++i) { Item item = Items[i]; if (item is BaseClothing) { item.Delete(); } } if (Female) { switch (Utility.Random(2)) { case 0: SetWearable(new FemaleGargishClothLegs(GetRandomHue())); SetWearable(new FemaleGargishClothKilt(GetRandomHue())); SetWearable(new FemaleGargishClothChest(GetRandomHue())); break; case 1: SetWearable(new FemaleGargishClothKilt(GetRandomHue())); SetWearable(new FemaleGargishClothChest(GetRandomHue())); break; } } else { switch (Utility.Random(2)) { case 0: SetWearable(new MaleGargishClothLegs(GetRandomHue())); SetWearable(new MaleGargishClothKilt(GetRandomHue())); SetWearable(new MaleGargishClothChest(GetRandomHue())); break; case 1: SetWearable(new MaleGargishClothKilt(GetRandomHue())); SetWearable(new MaleGargishClothChest(GetRandomHue())); break; } } if(!Siege.SiegeShard) PackGold(100, 200); } #endregion [CommandProperty(AccessLevel.GameMaster)] public bool ForceRestock { get { return false; } set { if (value) { Restock(); Say("Restocked!"); } } } public virtual void Restock() { m_LastRestock = DateTime.UtcNow; var buyInfo = GetBuyInfo(); foreach (IBuyItemInfo bii in buyInfo) { bii.OnRestock(); } } private static readonly TimeSpan InventoryDecayTime = TimeSpan.FromHours(1.0); public virtual void VendorBuy(Mobile from) { if (!IsActiveSeller) { return; } if (!from.CheckAlive()) { return; } if (!CheckVendorAccess(from)) { Say(501522); // I shall not treat with scum like thee! return; } if (DateTime.UtcNow - m_LastRestock > RestockDelay) { Restock(); } UpdateBuyInfo(); int count = 0; List list; var buyInfo = GetBuyInfo(); var sellInfo = GetSellInfo(); list = new List(buyInfo.Length); Container cont = BuyPack; List opls = null; for (int idx = 0; idx < buyInfo.Length; idx++) { IBuyItemInfo buyItem = buyInfo[idx]; if (buyItem.Amount <= 0 || list.Count >= 250) { continue; } // NOTE: Only GBI supported; if you use another implementation of IBuyItemInfo, this will crash GenericBuyInfo gbi = (GenericBuyInfo)buyItem; IEntity disp = gbi.GetDisplayEntity(); if (Siege.SiegeShard && !Siege.VendorCanSell(gbi.Type)) { continue; } list.Add( new BuyItemState( buyItem.Name, cont.Serial, disp == null ? (Serial)0x7FC0FFEE : disp.Serial, buyItem.Price, buyItem.Amount, buyItem.ItemID, buyItem.Hue)); count++; if (opls == null) { opls = new List(); } if (disp is Item) { opls.Add(((Item)disp).PropertyList); } else if (disp is Mobile) { opls.Add(((Mobile)disp).PropertyList); } } var playerItems = cont.Items; for (int i = playerItems.Count - 1; i >= 0; --i) { if (i >= playerItems.Count) { continue; } Item item = playerItems[i]; if ((item.LastMoved + InventoryDecayTime) <= DateTime.UtcNow) { item.Delete(); } } for (int i = 0; i < playerItems.Count; ++i) { Item item = playerItems[i]; if (Siege.SiegeShard && !Siege.VendorCanSell(item.GetType())) { continue; } int price = 0; string name = null; foreach (IShopSellInfo ssi in sellInfo) { if (ssi.IsSellable(item)) { price = ssi.GetBuyPriceFor(item, this); name = ssi.GetNameFor(item); break; } } if (name != null && list.Count < 250) { list.Add(new BuyItemState(name, cont.Serial, item.Serial, price, item.Amount, item.ItemID, item.Hue)); count++; if (opls == null) { opls = new List(); } opls.Add(item.PropertyList); } } if (list.Count > 0) { list.Sort(new BuyItemStateComparer()); SendPacksTo(from); NetState ns = from.NetState; if (ns == null) { return; } if (ns.ContainerGridLines) { from.Send(new VendorBuyContent6017(list)); } else { from.Send(new VendorBuyContent(list)); } from.Send(new VendorBuyList(this, list)); if (ns.HighSeas) { from.Send(new DisplayBuyListHS(this)); } else { from.Send(new DisplayBuyList(this)); } from.Send(new MobileStatusExtended(from)); //make sure their gold amount is sent if (opls != null) { for (int i = 0; i < opls.Count; ++i) { from.Send(opls[i]); } } SayTo(from, 500186, 0x3B2); // Greetings. Have a look around. } } public virtual void SendPacksTo(Mobile from) { Item pack = FindItemOnLayer(Layer.ShopBuy); if (pack == null) { pack = new Backpack(); pack.Layer = Layer.ShopBuy; pack.Movable = false; pack.Visible = false; SetWearable(pack); } from.Send(new EquipUpdate(pack)); pack = FindItemOnLayer(Layer.ShopSell); if (pack != null) { from.Send(new EquipUpdate(pack)); } pack = FindItemOnLayer(Layer.ShopResale); if (pack == null) { pack = new Backpack(); pack.Layer = Layer.ShopResale; pack.Movable = false; pack.Visible = false; SetWearable(pack); } from.Send(new EquipUpdate(pack)); } public virtual void VendorSell(Mobile from) { if (!IsActiveBuyer) { return; } if (!from.CheckAlive()) { return; } if (!CheckVendorAccess(from)) { Say(501522); // I shall not treat with scum like thee! return; } Container pack = from.Backpack; if (pack != null) { var info = GetSellInfo(); Dictionary table = new Dictionary(); foreach (IShopSellInfo ssi in info) { var items = pack.FindItemsByType(ssi.Types); foreach (Item item in items) { if (item is Container && (item).Items.Count != 0) { continue; } if (item.IsStandardLoot() && item.Movable && ssi.IsSellable(item)) { table[item] = new SellItemState(item, ssi.GetSellPriceFor(item, this), ssi.GetNameFor(item)); } } } if (table.Count > 0) { SendPacksTo(from); from.Send(new VendorSellList(this, table.Values)); } else { Say(true, "You have nothing I would be interested in."); } } } public override bool OnDragDrop(Mobile from, Item dropped) { if (ConvertsMageArmor && dropped is BaseArmor && CheckConvertArmor(from, (BaseArmor)dropped)) { return false; } if (dropped is SmallBOD || dropped is LargeBOD) { PlayerMobile pm = from as PlayerMobile; IBOD bod = dropped as IBOD; if (bod != null && BulkOrderSystem.NewSystemEnabled && Bribes != null && Bribes.ContainsKey(from) && Bribes[from].BOD == bod) { if (BulkOrderSystem.CanExchangeBOD(from, this, bod, Bribes[from].Amount)) { DoBribe(from, bod); return false; } } if (Core.ML && pm != null && pm.NextBODTurnInTime > DateTime.UtcNow) { SayTo(from, 1079976, 0x3B2); // You'll have to wait a few seconds while I inspect the last order. return false; } else if (!IsValidBulkOrder(dropped) || !SupportsBulkOrders(from)) { SayTo(from, 1045130, 0x3B2); // That order is for some other shopkeeper. return false; } else if (!BulkOrderSystem.CanClaimRewards(from)) { SayTo(from, 1157083, 0x3B2); // You must claim your last turn-in reward in order for us to continue doing business. return false; } else if (bod == null || !bod.Complete) { SayTo(from, 1045131, 0x3B2); // You have not completed the order yet. return false; } Item reward; int gold, fame; if (dropped is SmallBOD) { ((SmallBOD)dropped).GetRewards(out reward, out gold, out fame); } else { ((LargeBOD)dropped).GetRewards(out reward, out gold, out fame); } from.SendSound(0x3D); if (BulkOrderSystem.NewSystemEnabled && from is PlayerMobile) { SayTo(from, 1157204, from.Name, 0x3B2); // Ho! Ho! Thank ye ~1_PLAYER~ for giving me a Bulk Order Deed! BODContext context = BulkOrderSystem.GetContext(from); int points = 0; double banked = 0.0; if(dropped is SmallBOD) BulkOrderSystem.ComputePoints((SmallBOD)dropped, out points, out banked); else BulkOrderSystem.ComputePoints((LargeBOD)dropped, out points, out banked); switch (context.PointsMode) { case PointsMode.Enabled: context.AddPending(BODType, points); from.SendGump(new ConfirmBankPointsGump((PlayerMobile)from, this, this.BODType, points, banked)); break; case PointsMode.Disabled: context.AddPending(BODType, points); from.SendGump(new RewardsGump(this, (PlayerMobile)from, this.BODType, points)); break; case PointsMode.Automatic: BulkOrderSystem.SetPoints(from, this.BODType, banked); from.SendGump(new RewardsGump(this, (PlayerMobile)from, this.BODType)); break; } // On EA, you have to choose the reward before you get the gold/fame reward. IF you right click the gump, you lose // the gold/fame for that bod. Banker.Deposit(from, gold, true); } else { SayTo(from, 1045132, 0x3B2); // Thank you so much! Here is a reward for your effort. if (reward != null) { from.AddToBackpack(reward); } Banker.Deposit(from, gold, true); } Titles.AwardFame(from, fame, true); OnSuccessfulBulkOrderReceive(from); Server.Engines.CityLoyalty.CityLoyaltySystem.OnBODTurnIn(from, gold); if (Core.ML && pm != null) { pm.NextBODTurnInTime = DateTime.UtcNow + TimeSpan.FromSeconds(2.0); } dropped.Delete(); return true; } else if (AcceptsGift(from, dropped)) { dropped.Delete(); } return base.OnDragDrop(from, dropped); } public bool AcceptsGift(Mobile from, Item dropped) { string name; if (dropped.Name != null) { if (dropped.Amount > 0) { name = String.Format("{0} {1}", dropped.Amount, dropped.Name); } else { name = dropped.Name; } } else { name = Server.Engines.VendorSearching.VendorSearch.GetItemName(dropped); } if (!String.IsNullOrEmpty(name)) { PrivateOverheadMessage(MessageType.Regular, 0x3B2, true, String.Format("Thou art giving me {0}.", name), from.NetState); } else { this.SayTo(from, 1071971, String.Format("#{0}", dropped.LabelNumber.ToString()), 0x3B2); // Thou art giving me ~1_VAL~? } if (dropped is Gold) { this.SayTo(from, 501548, 0x3B2); // I thank thee. Titles.AwardFame(from, dropped.Amount / 100, true); return true; } var info = GetSellInfo(); foreach (IShopSellInfo ssi in info) { if (ssi.IsSellable(dropped)) { this.SayTo(from, 501548, 0x3B2); // I thank thee. Titles.AwardFame(from, ssi.GetSellPriceFor(dropped, this) * dropped.Amount, true); return true; } } this.SayTo(from, 501550, 0x3B2); // I am not interested in this. return false; } #region BOD Bribing [CommandProperty(AccessLevel.GameMaster)] public int BribeMultiplier { get; set; } [CommandProperty(AccessLevel.GameMaster)] public DateTime NextMultiplierDecay { get; set; } [CommandProperty(AccessLevel.GameMaster)] public DateTime WatchEnds { get; set; } [CommandProperty(AccessLevel.GameMaster)] public int RecentBribes { get; set; } [CommandProperty(AccessLevel.GameMaster)] public bool UnderWatch { get { return WatchEnds > DateTime.MinValue; } } public Dictionary Bribes { get; set; } private void CheckNextMultiplierDecay(bool force = true) { int minDays = Config.Get("Vendors.BribeDecayMinTime", 25); int maxDays = Config.Get("Vendors.BribeDecayMaxTime", 30); if (force || (NextMultiplierDecay > DateTime.UtcNow + TimeSpan.FromDays(maxDays))) NextMultiplierDecay = DateTime.UtcNow + TimeSpan.FromDays(Utility.RandomMinMax(minDays, maxDays)); } public void TryBribe(Mobile m) { if (UnderWatch) { if (WatchEnds < DateTime.UtcNow) { WatchEnds = DateTime.MinValue; RecentBribes = 0; } else { SayTo(m, 1152293, 0x3B2); // My business is being watched by the Guild, so I can't be messing with bulk orders right now. Come back when there's less heat on me! return; } } SayTo(m, 1152295, 0x3B2); // So you want to do a little business under the table? m.SendLocalizedMessage(1152296); // Target a bulk order deed to show to the shopkeeper. m.BeginTarget(-1, false, Server.Targeting.TargetFlags.None, (from, targeted) => { IBOD bod = targeted as IBOD; if (bod is Item && ((Item)bod).IsChildOf(from.Backpack)) { if (BulkOrderSystem.CanExchangeBOD(from, this, bod, -1)) { int amount = BulkOrderSystem.GetBribe(bod); amount *= BribeMultiplier; if (Bribes == null) Bribes = new Dictionary(); // Per EA, new bribe replaced old pending bribe if (!Bribes.ContainsKey(m)) { Bribes[m] = new PendingBribe(bod, amount); } else { Bribes[m].BOD = bod; Bribes[m].Amount = amount; } SayTo(from, 1152292, amount.ToString("N0", System.Globalization.CultureInfo.GetCultureInfo("en-US")), 0x3B2); // If you help me out, I'll help you out. I can replace that bulk order with a better one, but it's gonna cost you ~1_amt~ gold coin. Payment is due immediately. Just hand me the order and I'll pull the old switcheroo. } } else if (bod == null) { SayTo(from, 1152297, 0x3B2); // That is not a bulk order deed. } }); } public void DoBribe(Mobile m, IBOD bod) { BulkOrderSystem.MutateBOD(bod); RecentBribes++; if (RecentBribes >= 3 && Utility.Random(6) < RecentBribes) { WatchEnds = DateTime.UtcNow + TimeSpan.FromMinutes(Utility.RandomMinMax(120, 180)); } SayTo(m, 1152303, 0x3B2); // You'll find this one much more to your liking. It's been a pleasure, and I look forward to you greasing my palm again very soon. if (Bribes.ContainsKey(m)) { Bribes.Remove(m); } BribeMultiplier++; CheckNextMultiplierDecay(); } #endregion private GenericBuyInfo LookupDisplayObject(object obj) { var buyInfo = GetBuyInfo(); for (int i = 0; i < buyInfo.Length; ++i) { GenericBuyInfo gbi = (GenericBuyInfo)buyInfo[i]; if (gbi.GetDisplayEntity() == obj) { return gbi; } } return null; } private void ProcessSinglePurchase( BuyItemResponse buy, IBuyItemInfo bii, List validBuy, ref int controlSlots, ref bool fullPurchase, ref double cost) { int amount = buy.Amount; if (amount > bii.Amount) { amount = bii.Amount; } if (amount <= 0) { return; } int slots = bii.ControlSlots * amount; if (controlSlots >= slots) { controlSlots -= slots; } else { fullPurchase = false; return; } cost = (double)bii.Price * amount; validBuy.Add(buy); } private void ProcessValidPurchase(int amount, IBuyItemInfo bii, Mobile buyer, Container cont) { if (amount > bii.Amount) { amount = bii.Amount; } if (amount < 1) { return; } bii.Amount -= amount; IEntity o = bii.GetEntity(); if (o is Item) { Item item = (Item)o; if (item.Stackable) { item.Amount = amount; if (cont == null || !cont.TryDropItem(buyer, item, false)) { item.MoveToWorld(buyer.Location, buyer.Map); } } else { item.Amount = 1; if (cont == null || !cont.TryDropItem(buyer, item, false)) { item.MoveToWorld(buyer.Location, buyer.Map); } for (int i = 1; i < amount; i++) { item = bii.GetEntity() as Item; if (item != null) { item.Amount = 1; if (cont == null || !cont.TryDropItem(buyer, item, false)) { item.MoveToWorld(buyer.Location, buyer.Map); } } } } bii.OnBought(buyer, this, item, amount); } else if (o is Mobile) { Mobile m = (Mobile)o; bii.OnBought(buyer, this, m, amount); m.Direction = (Direction)Utility.Random(8); m.MoveToWorld(buyer.Location, buyer.Map); m.PlaySound(m.GetIdleSound()); if (m is BaseCreature) { ((BaseCreature)m).SetControlMaster(buyer); } for (int i = 1; i < amount; ++i) { m = bii.GetEntity() as Mobile; if (m != null) { m.Direction = (Direction)Utility.Random(8); m.MoveToWorld(buyer.Location, buyer.Map); if (m is BaseCreature) { ((BaseCreature)m).SetControlMaster(buyer); } } } } } public virtual bool OnBuyItems(Mobile buyer, List list) { if (!IsActiveSeller) { return false; } if (!buyer.CheckAlive()) { return false; } if (!CheckVendorAccess(buyer)) { Say(501522); // I shall not treat with scum like thee! return false; } UpdateBuyInfo(); //var buyInfo = GetBuyInfo(); var info = GetSellInfo(); var totalCost = 0.0; var validBuy = new List(list.Count); Container cont; bool bought = false; bool fromBank = false; bool fullPurchase = true; int controlSlots = buyer.FollowersMax - buyer.Followers; foreach (BuyItemResponse buy in list) { Serial ser = buy.Serial; int amount = buy.Amount; double cost = 0; if (ser.IsItem) { Item item = World.FindItem(ser); if (item == null) { continue; } GenericBuyInfo gbi = LookupDisplayObject(item); if (gbi != null) { ProcessSinglePurchase(buy, gbi, validBuy, ref controlSlots, ref fullPurchase, ref cost); } else if (item != BuyPack && item.IsChildOf(BuyPack)) { if (amount > item.Amount) { amount = item.Amount; } if (amount <= 0) { continue; } foreach (IShopSellInfo ssi in info) { if (ssi.IsSellable(item)) { if (ssi.IsResellable(item)) { cost = (double)ssi.GetBuyPriceFor(item, this) * amount; validBuy.Add(buy); break; } } } } if (validBuy.Contains(buy)) { if (ValidateBought(buyer, item)) { totalCost += cost; } else { validBuy.Remove(buy); } } } else if (ser.IsMobile) { Mobile mob = World.FindMobile(ser); if (mob == null) { continue; } GenericBuyInfo gbi = LookupDisplayObject(mob); if (gbi != null) { ProcessSinglePurchase(buy, gbi, validBuy, ref controlSlots, ref fullPurchase, ref cost); } if (validBuy.Contains(buy)) { if (ValidateBought(buyer, mob)) { totalCost += cost; } else { validBuy.Remove(buy); } } } } //foreach if (fullPurchase && validBuy.Count == 0) { SayTo(buyer, 500190, 0x3B2); // Thou hast bought nothing! } else if (validBuy.Count == 0) { SayTo(buyer, 500187, 0x3B2); // Your order cannot be fulfilled, please try again. } if (validBuy.Count == 0) { return false; } bought = buyer.AccessLevel >= AccessLevel.GameMaster; cont = buyer.Backpack; var discount = 0.0; if (Core.SA && HasHonestyDiscount) { double discountPc = 0; switch (VirtueHelper.GetLevel(buyer, VirtueName.Honesty)) { case VirtueLevel.Seeker: discountPc = .1; break; case VirtueLevel.Follower: discountPc = .2; break; case VirtueLevel.Knight: discountPc = .3; break; default: discountPc = 0; break; } discount = totalCost - (totalCost * (1.0 - discountPc)); totalCost -= discount; } if (!bought && cont != null && ConsumeGold(cont, totalCost)) { bought = true; } if (!bought) { if (totalCost <= Int32.MaxValue) { if (Banker.Withdraw(buyer, (int)totalCost)) { bought = true; fromBank = true; } } else if (buyer.Account != null && AccountGold.Enabled) { if (buyer.Account.WithdrawCurrency(totalCost / AccountGold.CurrencyThreshold)) { bought = true; fromBank = true; } } } if (!bought) { cont = buyer.FindBankNoCreate(); if (cont != null && ConsumeGold(cont, totalCost)) { bought = true; fromBank = true; } } if (!bought) { // ? Begging thy pardon, but thy bank account lacks these funds. // : Begging thy pardon, but thou casnt afford that. SayTo(buyer, totalCost >= 2000 ? 500191 : 500192, 0x3B2); return false; } buyer.PlaySound(0x32); cont = buyer.Backpack ?? buyer.BankBox; foreach (BuyItemResponse buy in validBuy) { Serial ser = buy.Serial; int amount = buy.Amount; if (amount < 1) { continue; } if (ser.IsItem) { Item item = World.FindItem(ser); if (item == null) { continue; } GenericBuyInfo gbi = LookupDisplayObject(item); if (gbi != null) { ProcessValidPurchase(amount, gbi, buyer, cont); } else { if (amount > item.Amount) { amount = item.Amount; } foreach (IShopSellInfo ssi in info) { if (ssi.IsSellable(item)) { if (ssi.IsResellable(item)) { Item buyItem; if (amount >= item.Amount) { buyItem = item; } else { buyItem = LiftItemDupe(item, item.Amount - amount); if (buyItem == null) { buyItem = item; } } if (cont == null || !cont.TryDropItem(buyer, buyItem, false)) { buyItem.MoveToWorld(buyer.Location, buyer.Map); } break; } } } } } else if (ser.IsMobile) { Mobile mob = World.FindMobile(ser); if (mob == null) { continue; } GenericBuyInfo gbi = LookupDisplayObject(mob); if (gbi != null) { ProcessValidPurchase(amount, gbi, buyer, cont); } } } //foreach if (discount > 0) { SayTo(buyer, 1151517, discount.ToString(), 0x3B2); } if (fullPurchase) { if (buyer.AccessLevel >= AccessLevel.GameMaster) { SayTo( buyer, 0x3B2, "I would not presume to charge thee anything. Here are the goods you requested.", null, !Core.AOS); } else if (fromBank) { SayTo( buyer, 0x3B2, "The total of thy purchase is {0} gold, which has been withdrawn from your bank account. My thanks for the patronage.", totalCost.ToString(), !Core.AOS); } else { SayTo(buyer, String.Format("The total of thy purchase is {0} gold. My thanks for the patronage.", totalCost), 0x3B2, true); } } else { if (buyer.AccessLevel >= AccessLevel.GameMaster) { SayTo( buyer, 0x3B2, "I would not presume to charge thee anything. Unfortunately, I could not sell you all the goods you requested.", null, !Core.AOS); } else if (fromBank) { SayTo( buyer, 0x3B2, "The total of thy purchase is {0} gold, which has been withdrawn from your bank account. My thanks for the patronage. Unfortunately, I could not sell you all the goods you requested.", totalCost.ToString(), !Core.AOS); } else { SayTo( buyer, 0x3B2, "The total of thy purchase is {0} gold. My thanks for the patronage. Unfortunately, I could not sell you all the goods you requested.", totalCost.ToString(), !Core.AOS); } } return true; } public virtual bool ValidateBought(Mobile buyer, Item item) { return true; } public virtual bool ValidateBought(Mobile buyer, Mobile m) { return true; } public static bool ConsumeGold(Container cont, double amount) { return ConsumeGold(cont, amount, true); } public static bool ConsumeGold(Container cont, double amount, bool recurse) { var gold = new Queue(FindGold(cont, recurse)); var total = gold.Aggregate(0.0, (c, g) => c + g.Amount); if (total < amount) { gold.Clear(); return false; } var consume = amount; while (consume > 0) { var g = gold.Dequeue(); if (g.Amount > consume) { g.Consume((int)consume); consume = 0; } else { consume -= g.Amount; g.Delete(); } } gold.Clear(); return true; } private static IEnumerable FindGold(Container cont, bool recurse) { if (cont == null || cont.Items.Count == 0) { yield break; } if (cont is ILockable && ((ILockable)cont).Locked) { yield break; } if (cont is TrapableContainer && ((TrapableContainer)cont).TrapType != TrapType.None) { yield break; } var count = cont.Items.Count; while(--count >= 0) { if (count >= cont.Items.Count) { continue; } var item = cont.Items[count]; if (item is Container) { if (!recurse) { continue; } foreach (var gold in FindGold((Container)item, true)) { yield return gold; } } else if (item is Gold) { yield return (Gold)item; } } } public virtual bool CheckVendorAccess(Mobile from) { GuardedRegion reg = (GuardedRegion)Region.GetRegion(typeof(GuardedRegion)); if (reg != null && !reg.CheckVendorAccess(this, from)) { return false; } if (Region != from.Region) { reg = (GuardedRegion)from.Region.GetRegion(typeof(GuardedRegion)); if (reg != null && !reg.CheckVendorAccess(this, from)) { return false; } } return true; } public virtual bool OnSellItems(Mobile seller, List list) { if (!IsActiveBuyer) { return false; } if (!seller.CheckAlive()) { return false; } if (!CheckVendorAccess(seller)) { Say(501522); // I shall not treat with scum like thee! return false; } seller.PlaySound(0x32); var info = GetSellInfo(); var buyInfo = GetBuyInfo(); int GiveGold = 0; int Sold = 0; Container cont; foreach (SellItemResponse resp in list) { if (resp.Item.RootParent != seller || resp.Amount <= 0 || !resp.Item.IsStandardLoot() || !resp.Item.Movable || (resp.Item is Container && (resp.Item).Items.Count != 0)) { continue; } foreach (IShopSellInfo ssi in info) { if (ssi.IsSellable(resp.Item)) { Sold++; break; } } } if (Sold > MaxSell) { SayTo(seller, "You may only sell {0} items at a time!", MaxSell, 0x3B2, true); return false; } else if (Sold == 0) { return true; } foreach (SellItemResponse resp in list) { if (resp.Item.RootParent != seller || resp.Amount <= 0 || !resp.Item.IsStandardLoot() || !resp.Item.Movable || (resp.Item is Container && (resp.Item).Items.Count != 0)) { continue; } foreach (IShopSellInfo ssi in info) { if (ssi.IsSellable(resp.Item)) { int amount = resp.Amount; if (amount > resp.Item.Amount) { amount = resp.Item.Amount; } if (ssi.IsResellable(resp.Item)) { bool found = false; foreach (var bii in buyInfo) { if (bii.Restock(resp.Item, amount)) { bii.OnSold(this, amount); resp.Item.Consume(amount); found = true; break; } } if (!found) { cont = BuyPack; if (amount < resp.Item.Amount) { Item item = LiftItemDupe(resp.Item, resp.Item.Amount - amount); if (item != null) { item.SetLastMoved(); cont.DropItem(item); } else { resp.Item.SetLastMoved(); cont.DropItem(resp.Item); } } else { resp.Item.SetLastMoved(); cont.DropItem(resp.Item); } } } else { if (amount < resp.Item.Amount) { resp.Item.Amount -= amount; } else { resp.Item.Delete(); } } var singlePrice = ssi.GetSellPriceFor(resp.Item, this); GiveGold += singlePrice * amount; EventSink.InvokeValidVendorSell(new ValidVendorSellEventArgs(seller, this, resp.Item, singlePrice)); break; } } } if (GiveGold > 0) { while (GiveGold > 60000) { seller.AddToBackpack(new Gold(60000)); GiveGold -= 60000; } seller.AddToBackpack(new Gold(GiveGold)); seller.PlaySound(0x0037); //Gold dropping sound if (SupportsBulkOrders(seller)) { Item bulkOrder = CreateBulkOrder(seller, false); if (bulkOrder is LargeBOD) { seller.SendGump(new LargeBODAcceptGump(seller, (LargeBOD)bulkOrder)); } else if (bulkOrder is SmallBOD) { seller.SendGump(new SmallBODAcceptGump(seller, (SmallBOD)bulkOrder)); } } } //no cliloc for this? //SayTo( seller, true, "Thank you! I bought {0} item{1}. Here is your {2}gp.", Sold, (Sold > 1 ? "s" : ""), GiveGold ); return true; } public override void Serialize(GenericWriter writer) { base.Serialize(writer); writer.Write(3); // version writer.Write(BribeMultiplier); writer.Write(NextMultiplierDecay); writer.Write(RecentBribes); var sbInfos = SBInfos; for (int i = 0; sbInfos != null && i < sbInfos.Count; ++i) { SBInfo sbInfo = sbInfos[i]; var buyInfo = sbInfo.BuyInfo; for (int j = 0; buyInfo != null && j < buyInfo.Count; ++j) { GenericBuyInfo gbi = buyInfo[j]; int maxAmount = gbi.MaxAmount; int doubled = 0; int bought = gbi.TotalBought; int sold = gbi.TotalSold; switch (maxAmount) { case 40: doubled = 1; break; case 80: doubled = 2; break; case 160: doubled = 3; break; case 320: doubled = 4; break; case 640: doubled = 5; break; case 999: doubled = 6; break; } if (doubled > 0 || bought > 0 || sold > 0) { writer.WriteEncodedInt(1 + ((j * sbInfos.Count) + i)); writer.WriteEncodedInt(doubled); writer.WriteEncodedInt(bought); writer.WriteEncodedInt(sold); } } } writer.WriteEncodedInt(0); if (NextMultiplierDecay != DateTime.MinValue && NextMultiplierDecay < DateTime.UtcNow) { Timer.DelayCall(TimeSpan.FromSeconds(10), () => { if (BribeMultiplier > 0) BribeMultiplier /= 2; CheckNextMultiplierDecay(); }); } } public override void Deserialize(GenericReader reader) { base.Deserialize(reader); int version = reader.ReadInt(); LoadSBInfo(); var sbInfos = SBInfos; switch (version) { case 3: case 2: BribeMultiplier = reader.ReadInt(); NextMultiplierDecay = reader.ReadDateTime(); CheckNextMultiplierDecay(false); // Reset NextMultiplierDecay if it is out of range of the config RecentBribes = reader.ReadInt(); goto case 1; case 1: { int index; while ((index = reader.ReadEncodedInt()) > 0) { int doubled = reader.ReadEncodedInt(); int bought = 0; int sold = 0; if (version >= 3) { bought = reader.ReadEncodedInt(); sold = reader.ReadEncodedInt(); } if (sbInfos != null) { index -= 1; int sbInfoIndex = index % sbInfos.Count; int buyInfoIndex = index / sbInfos.Count; if (sbInfoIndex >= 0 && sbInfoIndex < sbInfos.Count) { SBInfo sbInfo = sbInfos[sbInfoIndex]; var buyInfo = sbInfo.BuyInfo; if (buyInfo != null && buyInfoIndex >= 0 && buyInfoIndex < buyInfo.Count) { GenericBuyInfo gbi = buyInfo[buyInfoIndex]; int amount = 20; switch (doubled) { case 0: break; case 1: amount = 40; break; case 2: amount = 80; break; case 3: amount = 160; break; case 4: amount = 320; break; case 5: amount = 640; break; case 6: amount = 999; break; } if (version == 2 && gbi.Stackable) { gbi.Amount = gbi.MaxAmount = BaseVendor.EconomyStockAmount; } else { gbi.Amount = gbi.MaxAmount = amount; } gbi.TotalBought = bought; gbi.TotalSold = sold; } } } } break; } } if (IsParagon) { IsParagon = false; } if (version == 1) { BribeMultiplier = Utility.Random(10); } Timer.DelayCall(TimeSpan.Zero, CheckMorph); } public override void AddCustomContextEntries(Mobile from, List list) { if (ConvertsMageArmor) { list.Add(new UpgradeMageArmor(from, this)); } if (from.Alive && IsActiveVendor) { if (SupportsBulkOrders(from)) { list.Add(new BulkOrderInfoEntry(from, this)); if (BulkOrderSystem.NewSystemEnabled) { list.Add(new BribeEntry(from, this)); list.Add(new ClaimRewardsEntry(from, this)); } } if (IsActiveSeller) { list.Add(new VendorBuyEntry(from, this)); } if (IsActiveBuyer) { list.Add(new VendorSellEntry(from, this)); } } base.AddCustomContextEntries(from, list); } public virtual IShopSellInfo[] GetSellInfo() { return (IShopSellInfo[])m_ArmorSellInfo.ToArray(typeof(IShopSellInfo)); } public virtual IBuyItemInfo[] GetBuyInfo() { return (IBuyItemInfo[])m_ArmorBuyInfo.ToArray(typeof(IBuyItemInfo)); } #region Mage Armor Conversion public virtual bool ConvertsMageArmor { get { return false; } } private List _PendingConvertEntries = new List(); private bool CheckConvertArmor(Mobile from, BaseArmor armor) { var convert = GetConvert(from, armor); if (convert == null || !(from is PlayerMobile)) return false; object state = convert.Armor; RemoveConvertEntry(convert); from.CloseGump(typeof(Server.Gumps.ConfirmCallbackGump)); from.SendGump(new Server.Gumps.ConfirmCallbackGump((PlayerMobile)from, 1049004, 1154115, state, null, (m, obj) => { BaseArmor ar = obj as BaseArmor; if (!Deleted && ar != null && armor.IsChildOf(m.Backpack) && CanConvertArmor(m, ar)) { if (!InRange(m.Location, 3)) { m.SendLocalizedMessage(1149654); // You are too far away. } else if (!Banker.Withdraw(m, 250000, true)) { m.SendLocalizedMessage(1019022); // You do not have enough gold. } else { ConvertMageArmor(m, ar); } } }, (m, obj) => { var con = GetConvert(m, armor); if (con != null) { RemoveConvertEntry(con); } })); return true; } protected virtual bool CanConvertArmor(Mobile from, BaseArmor armor) { if (armor == null || armor is BaseShield/*|| armor.ArtifactRarity != 0 || armor.IsArtifact*/) { from.SendLocalizedMessage(1113044); // You can't convert that. return false; } if (armor.ArmorAttributes.MageArmor == 0 && Server.SkillHandlers.Imbuing.GetTotalMods(armor) > 4) { from.SendLocalizedMessage(1154119); // This action would exceed a stat cap return false; } return true; } public void TryConvertArmor(Mobile from, BaseArmor armor) { if (CanConvertArmor(from, armor)) { from.SendLocalizedMessage(1154117); // Ah yes, I will convert this piece of armor but it's gonna cost you 250,000 gold coin. Payment is due immediately. Just hand me the armor. var convert = GetConvert(from, armor); if (convert != null) { convert.ResetTimer(); } else { _PendingConvertEntries.Add(new PendingConvert(from, armor, this)); } } } public virtual void ConvertMageArmor(Mobile from, BaseArmor armor) { if (armor.ArmorAttributes.MageArmor > 0) armor.ArmorAttributes.MageArmor = 0; else armor.ArmorAttributes.MageArmor = 1; from.SendLocalizedMessage(1154118); // Your armor has been converted. } private void RemoveConvertEntry(PendingConvert convert) { _PendingConvertEntries.Remove(convert); if (convert.Timer != null) { convert.Timer.Stop(); } } private PendingConvert GetConvert(Mobile from, BaseArmor armor) { return _PendingConvertEntries.FirstOrDefault(c => c.From == from && c.Armor == armor); } protected class PendingConvert { public Mobile From { get; set; } public BaseArmor Armor { get; set; } public BaseVendor Vendor { get; set; } public Timer Timer { get; set; } public DateTime Expires { get; set; } public bool Expired { get { return DateTime.UtcNow > Expires; } } public PendingConvert(Mobile from, BaseArmor armor, BaseVendor vendor) { From = from; Armor = armor; Vendor = vendor; ResetTimer(); } public void ResetTimer() { if (Timer != null) { Timer.Stop(); Timer = null; } Expires = DateTime.UtcNow + TimeSpan.FromSeconds(120); Timer = Timer.DelayCall(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1), OnTick); Timer.Start(); } public void OnTick() { if (Expired) { Vendor.RemoveConvertEntry(this); } } } #endregion } } namespace Server.ContextMenus { public class VendorBuyEntry : ContextMenuEntry { private readonly BaseVendor m_Vendor; public VendorBuyEntry(Mobile from, BaseVendor vendor) : base(6103, 8) { m_Vendor = vendor; Enabled = vendor.CheckVendorAccess(from); } public override void OnClick() { m_Vendor.VendorBuy(Owner.From); } } public class VendorSellEntry : ContextMenuEntry { private readonly BaseVendor m_Vendor; public VendorSellEntry(Mobile from, BaseVendor vendor) : base(6104, 8) { m_Vendor = vendor; Enabled = vendor.CheckVendorAccess(from); } public override void OnClick() { m_Vendor.VendorSell(Owner.From); } } public class UpgradeMageArmor : ContextMenuEntry { public Mobile From { get; set; } public BaseVendor Vendor { get; set; } public UpgradeMageArmor(Mobile from, BaseVendor vendor) : base(1154114) // Convert Mage Armor { Enabled = vendor.CheckVendorAccess(from); From = from; Vendor = vendor; } public override void OnClick() { From.Target = new InternalTarget(From, Vendor); From.SendLocalizedMessage(1154116); // Target a piece of armor to show to the guild master. } private class InternalTarget : Target { public Mobile From { get; set; } public BaseVendor Vendor { get; set; } public InternalTarget(Mobile from, BaseVendor vendor) : base(1, false, TargetFlags.None) { From = from; Vendor = vendor; } protected override void OnTarget(Mobile from, object targeted) { if (targeted is BaseArmor) { BaseArmor armor = (BaseArmor)targeted; Vendor.TryConvertArmor(from, armor); } } } } } namespace Server { public interface IShopSellInfo { //get display name for an item string GetNameFor(Item item); //get price for an item which the player is selling int GetSellPriceFor(Item item); int GetSellPriceFor(Item item, BaseVendor vendor); //get price for an item which the player is buying int GetBuyPriceFor(Item item); int GetBuyPriceFor(Item item, BaseVendor vendor); //can we sell this item to this vendor? bool IsSellable(Item item); //What do we sell? Type[] Types { get; } //does the vendor resell this item? bool IsResellable(Item item); } public interface IBuyItemInfo { //get a new instance of an object (we just bought it) IEntity GetEntity(); int ControlSlots { get; } int PriceScalar { get; set; } bool Stackable { get; set; } int TotalBought { get; set; } int TotalSold { get; set; } void OnBought(Mobile buyer, BaseVendor vendor, IEntity entity, int amount); void OnSold(BaseVendor vendor, int amount); //display price of the item int Price { get; } //display name of the item string Name { get; } //display hue int Hue { get; } //display id int ItemID { get; } //amount in stock int Amount { get; set; } //max amount in stock int MaxAmount { get; } //Attempt to restock with item, (return true if restock sucessful) bool Restock(Item item, int amount); //called when its time for the whole shop to restock void OnRestock(); } }