/// <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);
}