/// <summary> /// Fired from the client / client is sending us a Buy transaction to vendor /// </summary> /// <param name="vendorId"></param> /// <param name="items"></param> public void BuyFromVendor(ObjectGuid vendorId, List <ItemProfile> items) { Vendor vendor = (CurrentLandblock.GetObject(vendorId) as Vendor); vendor.BuyValidateTransaction(vendorId, items, this); }
/// <summary> /// Sends updated network packets to client / vendor item list. /// </summary> public void ApproachVendor(Vendor vendor, List <WorldObject> itemsForSale) { Session.Network.EnqueueSend(new GameEventApproachVendor(Session, vendor, itemsForSale)); SendUseDoneEvent(); }
/// <summary> /// Vendor has validated the transactions and sent a list of items for processing. /// </summary> public void FinalizeBuyTransaction(Vendor vendor, List <WorldObject> uqlist, List <WorldObject> genlist, uint goldcost, uint altcost) { // vendor accepted the transaction var valid = ValidateBuyTransaction(vendor, goldcost, altcost); if (valid) { if (altcost > 0) { var altCurrencyWCID = vendor.AlternateCurrency ?? 0; SpendCurrency(altCurrencyWCID, altcost, true); } else { SpendCurrency(Vendor.CoinStackWCID, goldcost, true); } foreach (WorldObject wo in uqlist) { wo.RemoveProperty(PropertyFloat.SoldTimestamp); TryCreateInInventoryWithNetworking(wo); } foreach (var gen in genlist) { var service = gen.GetProperty(PropertyBool.VendorService) ?? false; if (!service) { TryCreateInInventoryWithNetworking(gen); } else { var spell = new Spell(gen.SpellDID ?? 0); if (!spell.NotFound) { var preCastTime = vendor.PreCastMotion(this); vendor.IsBusy = true; var castChain = new ActionChain(); castChain.AddDelaySeconds(preCastTime); castChain.AddAction(vendor, () => { vendor.TryCastSpell(spell, this, vendor); vendor.PostCastMotion(); }); var postCastTime = vendor.GetPostCastTime(); castChain.AddDelaySeconds(postCastTime); castChain.AddAction(vendor, () => vendor.IsBusy = false); castChain.EnqueueChain(); } else if (gen.GetProperty(PropertyInt.HomeRealm).HasValue) { var realmId = gen.GetProperty(PropertyInt.HomeRealm).Value; RealmManager.SetHomeRealm(this, realmId); } } } Session.Network.EnqueueSend(new GameMessageSound(Guid, Sound.PickUpItem)); if (PropertyManager.GetBool("player_receive_immediate_save").Item) { RushNextPlayerSave(5); } } vendor.BuyItems_FinalTransaction(this, uqlist, valid, altcost); }
/// <summary> /// Vendor has validated the transactions and sent a list of items for processing. /// </summary> public void FinalizeBuyTransaction(Vendor vendor, List <WorldObject> genericItems, List <WorldObject> uniqueItems, uint cost) { // transaction has been validated by this point var currencyWcid = vendor.AlternateCurrency ?? coinStackWcid; SpendCurrency(currencyWcid, cost, true); vendor.MoneyIncome += (int)cost; foreach (var item in genericItems) { var service = item.GetProperty(PropertyBool.VendorService) ?? false; if (!service) { // errors shouldn't be possible here, since the items were pre-validated, but just in case... if (!TryCreateInInventoryWithNetworking(item)) { log.Error($"[VENDOR] {Name}.FinalizeBuyTransaction({vendor.Name}) - couldn't add {item.Name} ({item.Guid}) to player inventory after validation, this shouldn't happen!"); item.Destroy(); // cleanup for guid manager } vendor.NumItemsSold++; } else { vendor.ApplyService(item, this); } } foreach (var item in uniqueItems) { if (TryCreateInInventoryWithNetworking(item)) { vendor.UniqueItemsForSale.Remove(item.Guid); // this was only for when the unique item was sold to the vendor, // to determine when the item should rot on the vendor. it gets removed now item.SoldTimestamp = null; vendor.NumItemsSold++; } else { log.Error($"[VENDOR] {Name}.FinalizeBuyTransaction({vendor.Name}) - couldn't add {item.Name} ({item.Guid}) to player inventory after validation, this shouldn't happen!"); } } Session.Network.EnqueueSend(new GameMessageSound(Guid, Sound.PickUpItem)); if (PropertyManager.GetBool("player_receive_immediate_save").Item) { RushNextPlayerSave(5); } var altCurrencySpent = vendor.AlternateCurrency != null ? cost : 0; vendor.ApproachVendor(this, VendorType.Buy, altCurrencySpent); }
/// <summary> /// Filters the list of ItemProfiles the player is attempting to sell to the vendor /// to the list of verified WorldObjects in the player's inventory w/ validations /// </summary> private Dictionary <uint, WorldObject> VerifySellItems(List <ItemProfile> sellItems, Vendor vendor) { var allPossessions = GetAllPossessions().ToDictionary(i => i.Guid.Full, i => i); var acceptedItemTypes = (ItemType)(vendor.MerchandiseItemTypes ?? 0); var verified = new Dictionary <uint, WorldObject>(); foreach (var sellItem in sellItems) { if (!allPossessions.TryGetValue(sellItem.ObjectGuid, out var wo)) { log.Warn($"[VENDOR] {Name} tried to sell item {sellItem.ObjectGuid:X8} not in their inventory to {vendor.Name}"); continue; } // verify item profile (unique guids, amount) if (verified.ContainsKey(wo.Guid.Full)) { log.Warn($"[VENDOR] {Name} tried to sell duplicate item {wo.Name} ({wo.Guid}) to {vendor.Name}"); continue; } if (!sellItem.IsValidAmount) { log.Warn($"[VENDOR] {Name} tried to sell {sellItem.Amount}x {wo.Name} ({wo.Guid}) to {vendor.Name}"); continue; } if (sellItem.Amount > (wo.StackSize ?? 1)) { log.Warn($"[VENDOR] {Name} tried to sell {sellItem.Amount}x {wo.Name} ({wo.Guid}) to {vendor.Name}, but they only have {wo.StackSize ?? 1}x"); continue; } // verify wo / vendor / player properties if ((acceptedItemTypes & wo.ItemType) == 0 || !wo.IsSellable || wo.Retained) { var itemName = (wo.StackSize ?? 1) > 1 ? wo.GetPluralName() : wo.Name; Session.Network.EnqueueSend(new GameEventCommunicationTransientString(Session, $"The {itemName} is unsellable.")); // retail message did not include item name, leaving in that for now. continue; } if (wo.Value < 1) { var itemName = (wo.StackSize ?? 1) > 1 ? wo.GetPluralName() : wo.Name; Session.Network.EnqueueSend(new GameEventCommunicationTransientString(Session, $"The {itemName} has no value and cannot be sold.")); // retail message did not include item name, leaving in that for now. continue; } if (IsTrading && wo.IsBeingTradedOrContainsItemBeingTraded(ItemsInTradeWindow)) { var itemName = (wo.StackSize ?? 1) > 1 ? wo.GetPluralName() : wo.Name; Session.Network.EnqueueSend(new GameEventCommunicationTransientString(Session, $"You cannot sell that! The {itemName} is currently being traded.")); // custom message? continue; } if (wo is Container container && container.Inventory.Count > 0) { var itemName = (wo.StackSize ?? 1) > 1 ? wo.GetPluralName() : wo.Name; Session.Network.EnqueueSend(new GameEventCommunicationTransientString(Session, $"You cannot sell that! The {itemName} must be empty.")); // custom message? continue; } verified.Add(wo.Guid.Full, wo); } return(verified); }
/// <summary> /// Vendor has validated the transactions and sent a list of items for processing. /// </summary> public void FinalizeBuyTransaction(Vendor vendor, List <WorldObject> uqlist, List <WorldObject> genlist, uint goldcost, uint altcost) { // todo research packets more for both buy and sell. ripley thinks buy is update.. // vendor accepted the transaction var valid = ValidateBuyTransaction(vendor, goldcost, altcost); if (valid) { SpendCurrency(goldcost, WeenieType.Coin); foreach (WorldObject wo in uqlist) { wo.RemoveProperty(PropertyFloat.SoldTimestamp); TryCreateInInventoryWithNetworking(wo); } foreach (var gen in genlist) { var service = gen.GetProperty(PropertyBool.VendorService) ?? false; if (!service) { TryCreateInInventoryWithNetworking(gen); } else { var spell = new Spell(gen.SpellDID ?? 0); if (!spell.NotFound) { var preCastTime = vendor.PreCastMotion(this); var castChain = new ActionChain(); castChain.AddDelaySeconds(preCastTime); castChain.AddAction(vendor, () => { vendor.TryCastSpell(spell, this, vendor); vendor.PostCastMotion(); }); castChain.EnqueueChain(); } } } if (altcost > 0) { var altCurrency = vendor.AlternateCurrency ?? 0; TryConsumeFromInventoryWithNetworking(altCurrency, (int)altcost); } Session.Network.EnqueueSend(new GameMessageSound(Guid, Sound.PickUpItem)); if (PropertyManager.GetBool("player_receive_immediate_save").Item) { RushNextPlayerSave(5); } } vendor.BuyItems_FinalTransaction(this, uqlist, valid); }