public GetProductionSuccessChance ( Skill skill, ProductionCategory category, int baseChance, int rainBonus ) : float | ||
skill | Skill | |
category | ProductionCategory | |
baseChance | int | |
rainBonus | int | |
return | float |
/// <summary> /// Completes production. /// </summary> /// <param name="creature"></param> /// <param name="skill"></param> /// <param name="packet"></param> public void Complete(Creature creature, Skill skill, Packet packet) { var unkByte = packet.GetByte(); var propEntityId = 0L; var unkInt = 0; if (packet.Peek() == PacketElementType.Long) // Rule unknown { propEntityId = packet.GetLong(); unkInt = packet.GetInt(); } var productId = packet.GetInt(); var unkShort = packet.GetShort(); var category = (ProductionCategory)packet.GetShort(); var amountToProduce = packet.GetShort(); var count = packet.GetByte(); var materials = new List<ProductionMaterial>(count); for (int i = 0; i < count; ++i) { var entityId = packet.GetLong(); var amount = packet.GetShort(); // Check item var item = creature.Inventory.GetItem(entityId); if (item == null) { Log.Warning("ProductionSkill.Prepare: Creature '{0:X16}' tried to use non-existent item as material.", creature.EntityId); return; } materials.Add(new ProductionMaterial(item, amount)); } // Check prop if (!this.CheckProp(creature, propEntityId)) goto L_Fail; // Check category if (!this.CheckCategory(creature, category)) { Log.Warning("ProductionSkill.Complete: Creature '{0:X16}' tried to use category '{1}' with skill '{2}'.", creature.EntityId, category, this.GetType().Name); goto L_Fail; } // Get potential products // Some productions can produce items of varying quality (cheap, // common, fine, finest) var potentialProducts = AuraData.ProductionDb.Find(category, productId); if (potentialProducts.Length == 0) { Send.ServerMessage(creature, "Unknown product."); goto L_Fail; } // Get reference product for checks and mats var productData = potentialProducts[0]; // Check tools if (!this.CheckTools(creature, skill, productData)) goto L_Fail; // Check mana if (!this.CheckMana(creature, productData)) goto L_Fail; if (productData.Mana > 0) { creature.Mana -= productData.Mana; Send.StatUpdate(creature, StatUpdateType.Private, Stat.Mana); } // Check materials var requiredMaterials = productData.GetMaterialList(); var toReduce = new List<ProductionMaterial>(); var inUse = new HashSet<long>(); foreach (var reqMat in requiredMaterials) { // Check all selected items for tag matches foreach (var material in materials) { // Check item and stack item for tag, pouches can be put // into the window, reducing the contained items. var match = material.Item.HasTag(reqMat.Tag) || (material.Item.IsGatheringPouch && material.Item.Data.StackItem != null && material.Item.Data.StackItem.HasTag(reqMat.Tag)); // Satisfy requirement with item, up to the max amount // needed or available if (match) { // Cancel if one item matches multiple materials. // It's unknown how this would be handled, can it even // happen? Can one item maybe only be used as one material? if (inUse.Contains(material.Item.EntityId)) { Send.ServerMessage(creature, Localization.Get("Unable to handle request, please report, with this information: ({0}/{1})."), material.Item.Info.Id, productData.Id); Log.Warning("ProductionSkill.Complete: Item '{0}' matches multiple materials for product '{1}'.", material.Item.Info.Id, productData.Id); goto L_Fail; } var reduce = Math.Min(reqMat.Amount, material.Item.Amount); reqMat.Amount -= reduce; toReduce.Add(new ProductionMaterial(material.Item, reduce)); inUse.Add(material.Item.EntityId); } // Break once we got what we need if (reqMat.Amount == 0) break; } } if (requiredMaterials.Any(a => a.Amount != 0)) { // Unofficial, the client should normally prevent this. Send.ServerMessage(creature, Localization.Get("Insufficient materials.")); goto L_Fail; } // Check success var rank = skill.Info.Rank <= SkillRank.R1 ? skill.Info.Rank : SkillRank.R1; var baseChance = potentialProducts.Sum(a => a.SuccessRates[rank]); var rainBonus = productData.RainBonus; var chance = creature.GetProductionSuccessChance(skill, category, baseChance, rainBonus); var rnd = RandomProvider.Get(); var success = (rnd.Next(100) < chance); // Debug if (creature.Titles.SelectedTitle == TitleId.devCAT) Send.ServerMessage(creature, "Debug: Chance {0}%", chance); // Select random product // Do this here, so we have the data for skill training, // no matter the outcome. if (potentialProducts.Length > 1) { var itemId = 0; var num = rnd.NextDouble() * baseChance; var n = 0.0; foreach (var potentialProduct in potentialProducts) { n += potentialProduct.SuccessRates[rank]; if (num <= n) { itemId = potentialProduct.ItemId; productData = potentialProduct; break; } } // Sanity check if (itemId == 0) { Log.Error("ProductionSkill.Complete: Failed to select random product item for {0}/{1}, num: {2}.", category, productId, num); Send.ServerMessage(creature, "Failed to generate product."); goto L_Fail; } } // Update tool's durability and proficiency this.UpdateTool(creature, productData); // Skill training this.SkillTraining(creature, skill, productData, success); // Reduce mats foreach (var material in toReduce) { // On fail of non-queued productions you lose 1~amount of // materials randomly var reduce = success ? material.Amount : rnd.Next(1, material.Amount + 1); if (reduce > 0) creature.Inventory.Decrement(material.Item, (ushort)reduce); } if (success) { // Check item var productItemData = AuraData.ItemDb.Find(productData.ItemId); if (productItemData == null) { Log.Error("ProductionSkill.Complete: Unknown product item '{0}'.", productData.ItemId); Send.ServerMessage(creature, "Unknown product item."); goto L_Fail; } // Create product var productItem = new Item(productData.ItemId); productItem.Amount = productData.Amount; // Add product to inventory creature.Inventory.Insert(productItem, true); // Material creation event ChannelServer.Instance.Events.OnCreatureProducedItem(new ProductionEventArgs(creature, productData, true, productItem)); // Success Send.UseMotion(creature, 14, 0); // Success motion Send.Notice(creature, Localization.Get("{0} created successfully!"), productItem.Data.Name); Send.Echo(creature, Op.SkillComplete, packet); return; } // Material creation event ChannelServer.Instance.Events.OnCreatureProducedItem(new ProductionEventArgs(creature, productData, false, null)); L_Fail: // Unofficial Send.UseMotion(creature, 14, 3); // Fail motion Send.Echo(creature, Op.SkillComplete, packet); }