ModernUO/Projects/UOContent/Engines/Craft/Core/CraftItem.cs

1418 lines
44 KiB
C#

using System;
using System.Collections.Generic;
using Server.Commands;
using Server.Factions;
using Server.Items;
using Server.Logging;
using Server.Mobiles;
using Server.Utilities;
namespace Server.Engines.Craft
{
public enum ConsumeType
{
All,
Half,
None
}
public interface ICraftable
{
int OnCraft(
int quality, bool makersMark, Mobile from, CraftSystem craftSystem, Type typeRes, BaseTool tool,
CraftItem craftItem, int resHue
);
}
public class CraftItem
{
private static readonly ILogger logger = LogFactory.GetLogger(typeof(CraftItem));
private static readonly Dictionary<Type, int> _itemIds = new();
private static readonly int[] m_HeatSources =
{
0x461, 0x48E, // Sandstone oven/fireplace
0x92B, 0x96C, // Stone oven/fireplace
0xDE3, 0xDE9, // Campfire
0xFAC, 0xFAC, // Firepit
0x184A, 0x184C, // Heating stand (left)
0x184E, 0x1850, // Heating stand (right)
0x398C, 0x399F, // Fire field
0x2DDB, 0x2DDC, // Elven stove
0x19AA, 0x19BB, // Veteran Reward Brazier
0x197A, 0x19A9, // Large Forge
0x0FB1, 0x0FB1, // Small Forge
0x2DD8, 0x2DD8 // Elven Forge
};
private static readonly int[] m_Ovens =
{
0x461, 0x46F, // Sandstone oven
0x92B, 0x93F, // Stone oven
0x2DDB, 0x2DDC // Elven stove
};
private static readonly int[] m_Mills =
{
0x1920, 0x1921, 0x1922, 0x1923, 0x1924, 0x1295, 0x1926, 0x1928,
0x192C, 0x192D, 0x192E, 0x129F, 0x1930, 0x1931, 0x1932, 0x1934
};
private static readonly Type[][] m_TypesTable =
{
new[] { typeof(Log), typeof(Board) },
new[] { typeof(HeartwoodLog), typeof(HeartwoodBoard) },
new[] { typeof(BloodwoodLog), typeof(BloodwoodBoard) },
new[] { typeof(FrostwoodLog), typeof(FrostwoodBoard) },
new[] { typeof(OakLog), typeof(OakBoard) },
new[] { typeof(AshLog), typeof(AshBoard) },
new[] { typeof(YewLog), typeof(YewBoard) },
new[] { typeof(Leather), typeof(Hides) },
new[] { typeof(SpinedLeather), typeof(SpinedHides) },
new[] { typeof(HornedLeather), typeof(HornedHides) },
new[] { typeof(BarbedLeather), typeof(BarbedHides) },
new[] { typeof(BlankMap), typeof(BlankScroll) },
new[] { typeof(Cloth), typeof(UncutCloth) },
new[] { typeof(CheeseWheel), typeof(CheeseWedge) },
new[] { typeof(Pumpkin), typeof(SmallPumpkin) },
new[] { typeof(WoodenBowlOfPeas), typeof(PewterBowlOfPeas) }
};
private static readonly Type[] m_ColoredItemTable =
{
typeof(BaseWeapon), typeof(BaseArmor), typeof(BaseClothing),
typeof(BaseJewel), typeof(DragonBardingDeed)
};
private static readonly Type[] m_ColoredResourceTable =
{
typeof(BaseIngot), typeof(BaseOre),
typeof(BaseLeather), typeof(BaseHides),
typeof(UncutCloth), typeof(Cloth),
typeof(BaseGranite), typeof(BaseScales)
};
private static readonly Type[] m_MarkableTable =
{
typeof(BaseArmor),
typeof(BaseWeapon),
typeof(BaseClothing),
typeof(BaseInstrument),
typeof(DragonBardingDeed),
typeof(BaseTool),
typeof(BaseHarvestTool),
typeof(FukiyaDarts), typeof(Shuriken),
typeof(Spellbook), typeof(Runebook),
typeof(BaseQuiver)
};
private static readonly Type[] m_NeverColorTable =
{
typeof(OrcHelm)
};
private int m_ResAmount;
private int m_ResHue;
private CraftSystem m_System;
private int _itemId;
public CraftItem(Type type, TextDefinition groupName, TextDefinition name) : this(type, groupName, name, -1)
{
}
public CraftItem(Type type, TextDefinition groupName, TextDefinition name, int itemId)
{
Resources = new List<CraftRes>();
Skills = new List<CraftSkill>();
ItemType = type;
GroupNameString = groupName;
NameString = name;
GroupNameNumber = groupName;
NameNumber = name;
RequiredBeverage = BeverageType.Water;
_itemId = itemId;
}
public bool ForceNonExceptional { get; set; }
public Expansion RequiredExpansion { get; set; }
public Recipe Recipe { get; private set; }
public BeverageType RequiredBeverage { get; set; }
public int Mana { get; set; }
public int Hits { get; set; }
public int Stam { get; set; }
public bool UseSubRes2 { get; set; }
public bool UseAllRes { get; set; }
public bool NeedHeat { get; set; }
public bool NeedOven { get; set; }
public bool NeedMill { get; set; }
public Type ItemType { get; }
public int ItemHue { get; set; }
public string GroupNameString { get; }
public int GroupNameNumber { get; }
public string NameString { get; }
public int NameNumber { get; }
public int ItemId => _itemId == -1 ? _itemId = ItemIDOf(ItemType) : _itemId;
public List<CraftRes> Resources { get; }
public List<CraftSkill> Skills { get; }
public void AddRecipe(int id, CraftSystem system)
{
if (Recipe != null)
{
logger.Warning(
"Attempted add of recipe #{Id} to the crafting of {ItemTypeName} in CraftSystem {CraftSystem}.",
id,
ItemType.Name,
system
);
return;
}
Recipe = new Recipe(id, system, this);
}
public static int LabelNumber(Type type)
{
var number = ItemIDOf(type);
return number + (number >= 0x4000 ? 1078872 : 1020000);
}
public static int ItemIDOf(Type type)
{
if (_itemIds.TryGetValue(type, out var itemId))
{
return itemId;
}
if (type == typeof(FactionExplosionTrap))
{
itemId = 14034;
}
else if (type == typeof(FactionGasTrap))
{
itemId = 4523;
}
else if (type == typeof(FactionSawTrap))
{
itemId = 4359;
}
else if (type == typeof(FactionSpikeTrap))
{
itemId = 4517;
}
if (itemId == 0)
{
var attrs = type.GetCustomAttributes(typeof(CraftItemIDAttribute), false);
if (attrs.Length > 0)
{
var craftItemID = (CraftItemIDAttribute)attrs[0];
itemId = craftItemID.ItemID;
}
}
if (itemId == 0)
{
Item item = null;
try
{
item = type.CreateInstance<Item>();
}
catch
{
// ignored
}
if (item != null)
{
itemId = item.ItemID;
item.Delete();
}
}
_itemIds[type] = itemId;
return itemId;
}
public void AddRes(Type type, TextDefinition name, int amount, TextDefinition message)
{
var craftRes = new CraftRes(type, name, amount, message);
Resources.Add(craftRes);
}
public void AddSkill(SkillName skillToMake, double minSkill, double maxSkill)
{
var craftSkill = new CraftSkill(skillToMake, minSkill, maxSkill);
Skills.Add(craftSkill);
}
public bool ConsumeAttributes(Mobile from, ref TextDefinition message, bool consume)
{
if (Hits > 0 && from.Hits < Hits)
{
message = "You lack the required hit points to make that.";
return false;
}
if (Mana > 0 && from.Mana < Mana)
{
message = "You lack the required mana to make that.";
return false;
}
if (Stam > 0 && from.Stam < Stam)
{
message = "You lack the required stamina to make that.";
return false;
}
if (consume)
{
from.Mana -= Mana;
from.Hits -= Hits;
from.Stam -= Stam;
}
return true;
}
public bool IsMarkable(Type type)
{
if (ForceNonExceptional) // Don't even display the stuff for marking if it can't ever be exceptional.
{
return false;
}
for (var i = 0; i < m_MarkableTable.Length; ++i)
{
if (type == m_MarkableTable[i] || type.IsSubclassOf(m_MarkableTable[i]))
{
return true;
}
}
return false;
}
public static bool RetainsColor(Type type)
{
var neverColor = false;
for (var i = 0; !neverColor && i < m_NeverColorTable.Length; ++i)
{
neverColor = type == m_NeverColorTable[i] || type.IsSubclassOf(m_NeverColorTable[i]);
}
if (neverColor)
{
return false;
}
var inItemTable = false;
for (var i = 0; !inItemTable && i < m_ColoredItemTable.Length; ++i)
{
inItemTable = type == m_ColoredItemTable[i] || type.IsSubclassOf(m_ColoredItemTable[i]);
}
return inItemTable;
}
public bool RetainsColorFrom(CraftSystem system, Type type)
{
if (system.RetainsColorFrom(this, type))
{
return true;
}
var inItemTable = RetainsColor(ItemType);
if (!inItemTable)
{
return false;
}
var inResourceTable = false;
for (var i = 0; !inResourceTable && i < m_ColoredResourceTable.Length; ++i)
{
inResourceTable = type == m_ColoredResourceTable[i] || type.IsSubclassOf(m_ColoredResourceTable[i]);
}
return inResourceTable;
}
public static bool Find(Mobile from, int[] itemIDs)
{
var map = from.Map;
if (map == null)
{
return false;
}
var eable = map.GetItemsInRange(from.Location, 2);
foreach (var item in eable)
{
if (item.Z + 16 > item.Z && item.Z + 16 > item.Z && Find(item.ItemID, itemIDs))
{
eable.Free();
return true;
}
}
for (var x = -2; x <= 2; ++x)
{
for (var y = -2; y <= 2; ++y)
{
var vx = from.X + x;
var vy = from.Y + y;
var tiles = map.Tiles.GetStaticTiles(vx, vy, true);
for (var i = 0; i < tiles.Length; ++i)
{
var z = tiles[i].Z;
var id = tiles[i].ID;
if (z + 16 > from.Z && from.Z + 16 > z && Find(id, itemIDs))
{
return true;
}
}
}
}
return false;
}
public static bool Find(int itemID, int[] itemIDs)
{
var contains = false;
for (var i = 0; !contains && i < itemIDs.Length; i += 2)
{
contains = itemID >= itemIDs[i] && itemID <= itemIDs[i + 1];
}
return contains;
}
public static bool IsQuantityType(Type[][] types)
{
for (int i = 0; i < types.Length; ++i)
{
Type[] check = types[i];
for (int j = 0; j < check.Length; ++j)
{
if (typeof(IHasQuantity).IsAssignableFrom(check[j]))
{
return true;
}
}
}
return false;
}
public int ConsumeQuantity(Container cont, Type[][] types, int[] amounts)
{
if (types.Length != amounts.Length)
{
throw new ArgumentOutOfRangeException(nameof(types));
}
// TODO: Optimize allocation
var items = new List<Item>[types.Length];
var totals = new int[types.Length];
for (var i = 0; i < types.Length; ++i)
{
items[i] = cont.FindItemsByType(types[i]);
for (var j = 0; j < items[i].Count; ++j)
{
if (items[i][j] is not IHasQuantity hq)
{
totals[i] += items[i][j].Amount;
}
else if (hq is not BaseBeverage beverage || beverage.Content == RequiredBeverage)
{
totals[i] += hq.Quantity;
}
}
if (totals[i] < amounts[i])
{
return i;
}
}
for (var i = 0; i < types.Length; ++i)
{
var need = amounts[i];
for (var j = 0; j < items[i].Count; ++j)
{
var item = items[i][j];
if (item is not IHasQuantity hq)
{
var theirAmount = item.Amount;
if (theirAmount < need)
{
item.Delete();
need -= theirAmount;
}
else
{
item.Consume(need);
break;
}
}
else
{
if (hq is BaseBeverage beverage && beverage.Content != RequiredBeverage)
{
continue;
}
var theirAmount = hq.Quantity;
if (theirAmount < need)
{
hq.Quantity -= theirAmount;
need -= theirAmount;
}
else
{
hq.Quantity -= need;
break;
}
}
}
}
return -1;
}
public int GetQuantity(Container cont, Type[] types)
{
var items = cont.FindItemsByType(types);
var amount = 0;
for (var i = 0; i < items.Count; ++i)
{
if (items[i] is not IHasQuantity hq)
{
amount += items[i].Amount;
}
else
{
if ((hq as BaseBeverage)?.Content != RequiredBeverage)
{
continue;
}
amount += hq.Quantity;
}
}
return amount;
}
public bool ConsumeRes(
Mobile from, Type typeRes, CraftSystem craftSystem, ref int resHue, ref int maxAmount,
ConsumeType consumeType, ref TextDefinition message
) =>
ConsumeRes(from, typeRes, craftSystem, ref resHue, ref maxAmount, consumeType, ref message, false);
public bool ConsumeRes(
Mobile from, Type typeRes, CraftSystem craftSystem, ref int resHue, ref int maxAmount,
ConsumeType consumeType, ref TextDefinition message, bool isFailure
)
{
var ourPack = from.Backpack;
if (ourPack == null)
{
return false;
}
if (NeedHeat && !Find(from, m_HeatSources))
{
message = 1044487; // You must be near a fire source to cook.
return false;
}
if (NeedOven && !Find(from, m_Ovens))
{
message = 1044493; // You must be near an oven to bake that.
return false;
}
if (NeedMill && !Find(from, m_Mills))
{
message = 1044491; // You must be near a flour mill to do that.
return false;
}
var types = new Type[Resources.Count][];
var amounts = new int[Resources.Count];
maxAmount = int.MaxValue;
var resCol = UseSubRes2 ? craftSystem.CraftSubRes2 : craftSystem.CraftSubRes;
CraftRes res;
for (var i = 0; i < types.Length; ++i)
{
var craftRes = Resources[i];
var baseType = craftRes.ItemType;
// Resource Mutation
if (baseType == resCol.ResType && typeRes != null)
{
baseType = typeRes;
var subResource = resCol.SearchFor(baseType);
if (subResource != null && from.Skills[craftSystem.MainSkill].Base < subResource.RequiredSkill)
{
message = subResource.Message;
return false;
}
}
// ******************
for (var j = 0; types[i] == null && j < m_TypesTable.Length; ++j)
{
if (m_TypesTable[j][0] == baseType)
{
types[i] = m_TypesTable[j];
}
}
types[i] ??= new[] { baseType };
amounts[i] = craftRes.Amount;
// For stackable items that can be crafted more than one at a time
if (UseAllRes)
{
var tempAmount = ourPack.GetAmount(types[i]);
tempAmount /= amounts[i];
if (tempAmount < maxAmount)
{
maxAmount = tempAmount;
if (maxAmount == 0)
{
res = Resources[i];
if (res.Message.Number > 0)
{
message = res.Message.Number;
}
else if (!string.IsNullOrEmpty(res.Message.String))
{
message = res.Message.String;
}
else
{
message = 502925; // You don't have the resources required to make that item.
}
return false;
}
}
}
// ****************************
if (isFailure && !craftSystem.ConsumeOnFailure(from, types[i][0], this))
{
amounts[i] = 0;
}
}
// We adjust the amount of each resource to consume the max possible
if (UseAllRes)
{
for (var i = 0; i < amounts.Length; ++i)
{
amounts[i] *= maxAmount;
}
}
else
{
maxAmount = -1;
}
RecallRune consumeExtra = null;
if (NameNumber == 1041267)
{
// Runebooks are a special case, they need a blank recall rune
consumeExtra = ourPack.FindItemsByType<RecallRune>().Find(rune => !rune.Marked);
if (consumeExtra == null)
{
message = 1044253; // You don't have the components needed to make that.
return false;
}
}
int index;
if (consumeType == ConsumeType.None)
{
index = -1;
var isQuantityType = IsQuantityType(types);
// TODO: Optimize this
for (var i = 0; i < types.Length; i++)
{
var quantity = isQuantityType
? GetQuantity(ourPack, types[i])
: ourPack.GetBestGroupAmount(types[i], true, CheckHueGrouping);
if (quantity < amounts[i])
{
index = i;
break;
}
}
}
else
{
if (consumeType == ConsumeType.Half)
{
for (var i = 0; i < amounts.Length; i++)
{
amounts[i] /= 2;
if (amounts[i] < 1)
{
amounts[i] = 1;
}
}
}
m_ResHue = 0;
m_ResAmount = 0;
m_System = craftSystem;
index = IsQuantityType(types)
? ConsumeQuantity(ourPack, types, amounts)
: ourPack.ConsumeTotalGrouped(types, amounts, true, OnResourceConsumed, CheckHueGrouping);
resHue = m_ResHue;
}
if (index == -1)
{
if (consumeType != ConsumeType.None)
{
consumeExtra?.Delete();
}
return true;
}
res = Resources[index];
if (res.Message.Number > 0)
{
message = res.Message.Number;
}
else if (!string.IsNullOrEmpty(res.Message.String))
{
message = res.Message.String;
}
else
{
message = 502925; // You don't have the resources required to make that item.
}
return false;
}
private void OnResourceConsumed(Item item, int amount)
{
if (!RetainsColorFrom(m_System, item.GetType()))
{
return;
}
if (amount >= m_ResAmount)
{
m_ResHue = item.Hue;
m_ResAmount = amount;
}
}
private static int CheckHueGrouping(Item a, Item b) => b.Hue.CompareTo(a.Hue);
public double GetExceptionalChance(CraftSystem system, double chance, Mobile from)
{
if (ForceNonExceptional)
{
return 0.0;
}
var bonus = 0.0;
if (from.Talisman is BaseTalisman talisman && talisman.Skill == system.MainSkill)
{
chance -= talisman.SuccessBonus / 100.0;
bonus = talisman.ExceptionalBonus / 100.0;
}
chance = system.ECA switch
{
CraftECA.FiftyPercentChanceMinusTenPercent => chance * 0.5 - 0.1,
CraftECA.ChanceMinusSixtyToFortyFive => chance - Math.Clamp(
0.60 - (from.Skills[system.MainSkill].Value - 95.0) * 0.03,
0.45,
0.60
),
_ => chance - 0.6
};
return chance > 0 ? chance + bonus : chance;
}
public bool CheckSkills(
Mobile from, Type typeRes, CraftSystem craftSystem, ref int quality, out bool allRequiredSkills
) => CheckSkills(from, typeRes, craftSystem, ref quality, out allRequiredSkills, true);
public bool CheckSkills(
Mobile from, Type typeRes, CraftSystem craftSystem, ref int quality,
out bool allRequiredSkills, bool gainSkills
)
{
var chance = GetSuccessChance(from, typeRes, craftSystem, gainSkills, out allRequiredSkills);
if (GetExceptionalChance(craftSystem, chance, from) > Utility.RandomDouble())
{
quality = 2;
}
return chance > Utility.RandomDouble();
}
public double GetSuccessChance(
Mobile from, Type typeRes, CraftSystem craftSystem, bool gainSkills,
out bool allRequiredSkills
)
{
var minMainSkill = 0.0;
var maxMainSkill = 0.0;
var valMainSkill = 0.0;
allRequiredSkills = true;
for (var i = 0; i < Skills.Count; i++)
{
var craftSkill = Skills[i];
var minSkill = craftSkill.MinSkill;
var maxSkill = craftSkill.MaxSkill;
var valSkill = from.Skills[craftSkill.SkillToMake].Value;
if (valSkill < minSkill)
{
allRequiredSkills = false;
}
if (craftSkill.SkillToMake == craftSystem.MainSkill)
{
minMainSkill = minSkill;
maxMainSkill = maxSkill;
valMainSkill = valSkill;
}
if (gainSkills) // This is a passive check. Success chance is entirely dependant on the main skill
{
from.CheckSkill(craftSkill.SkillToMake, minSkill, maxSkill);
}
}
if (!allRequiredSkills)
{
return 0;
}
var minChance = craftSystem.GetChanceAtMin(this);
double chance = minChance + (valMainSkill - minMainSkill) / (maxMainSkill - minMainSkill) * (1.0 - minChance);
if (from.Talisman is BaseTalisman talisman && talisman.Skill == craftSystem.MainSkill)
{
chance += talisman.SuccessBonus / 100.0;
}
return chance;
}
public void Craft(Mobile from, CraftSystem craftSystem, Type typeRes, BaseTool tool)
{
if (!from.BeginAction<CraftSystem>())
{
from.SendLocalizedMessage(500119); // You must wait to perform another action
return;
}
if (RequiredExpansion != Expansion.None && from.NetState?.SupportsExpansion(RequiredExpansion) != true)
{
from.EndAction<CraftSystem>();
from.SendGump(
new CraftGump(
from,
craftSystem,
tool,
// The {0} expansion is required to attempt this item.
RequiredExpansionMessage(RequiredExpansion)
)
);
return;
}
var chance = GetSuccessChance(from, typeRes, craftSystem, false, out var allRequiredSkills);
if (!allRequiredSkills || chance <= 0.0)
{
from.EndAction<CraftSystem>();
from.SendGump(
new CraftGump(
from,
craftSystem,
tool,
1044153 // You don't have the required skills to attempt this item.
)
);
return;
}
if (Recipe != null && (from as PlayerMobile)?.HasRecipe(Recipe) == false)
{
from.EndAction<CraftSystem>();
from.SendGump(
new CraftGump(
from,
craftSystem,
tool,
1072847 // You must learn that recipe from a scroll.
)
);
return;
}
var badCraft = craftSystem.CanCraft(from, tool, ItemType);
if (badCraft > 0)
{
from.EndAction<CraftSystem>();
from.SendGump(new CraftGump(from, craftSystem, tool, badCraft));
return;
}
var resHue = 0;
var maxAmount = 0;
TextDefinition message = null;
if (!ConsumeRes(from, typeRes, craftSystem, ref resHue, ref maxAmount, ConsumeType.None, ref message))
{
from.EndAction<CraftSystem>();
from.SendGump(new CraftGump(from, craftSystem, tool, message));
return;
}
message = null;
if (!ConsumeAttributes(from, ref message, false))
{
from.EndAction<CraftSystem>();
from.SendGump(new CraftGump(from, craftSystem, tool, message));
return;
}
var context = craftSystem.GetContext(from);
context?.OnMade(this);
var iMin = craftSystem.MinCraftEffect;
var iMax = craftSystem.MaxCraftEffect - iMin + 1;
var iRandom = Utility.Random(iMax);
iRandom += iMin + 1;
new InternalTimer(from, craftSystem, this, typeRes, tool, iRandom).Start();
}
private static TextDefinition RequiredExpansionMessage(Expansion expansion)
{
return expansion switch
{
Expansion.SE => 1063307, // The "Samurai Empire" expansion is required to attempt this item.
Expansion.ML => 1072650, // The "Mondain's Legacy" expansion is required to attempt this item.
_ => $"The \"{ExpansionInfo.GetInfo(expansion).Name}\" expansion is required to attempt this item."
};
}
public void CompleteCraft(
int quality, bool makersMark, Mobile from, CraftSystem craftSystem, Type typeRes,
BaseTool tool, CustomCraft customCraft
)
{
var badCraft = craftSystem.CanCraft(from, tool, ItemType);
if (badCraft > 0)
{
if (tool?.Deleted == false && tool.UsesRemaining > 0)
{
from.SendGump(new CraftGump(from, craftSystem, tool, badCraft));
}
else
{
from.SendLocalizedMessage(badCraft);
}
return;
}
var checkResHue = 0;
var checkMaxAmount = 0;
TextDefinition checkMessage = null;
var consumeRes = ConsumeRes(
from,
typeRes,
craftSystem,
ref checkResHue,
ref checkMaxAmount,
ConsumeType.None,
ref checkMessage
);
// Not enough resource to craft it
if (!(consumeRes && ConsumeAttributes(from, ref checkMessage, false)))
{
if (tool?.Deleted == false && tool.UsesRemaining > 0)
{
from.SendGump(new CraftGump(from, craftSystem, tool, checkMessage));
}
else if (checkMessage.Number > 0)
{
from.SendLocalizedMessage(checkMessage.Number);
}
else
{
from.SendMessage(checkMessage.String);
}
return;
}
var toolBroken = false;
var ignored = 1;
var endquality = 1;
var resHue = 0;
var maxAmount = 0;
TextDefinition message = null;
var num = 0;
if (CheckSkills(from, typeRes, craftSystem, ref ignored, out var allRequiredSkills))
{
consumeRes = ConsumeRes(
from,
typeRes,
craftSystem,
ref resHue,
ref maxAmount,
ConsumeType.All,
ref message
);
// Not enough resource to craft it
if (!(consumeRes && ConsumeAttributes(from, ref message, true)))
{
if (tool?.Deleted == false && tool.UsesRemaining > 0)
{
from.SendGump(new CraftGump(from, craftSystem, tool, message));
}
else if (message != null)
{
if (message.Number > 0)
{
from.SendLocalizedMessage(message.Number);
}
else
{
from.SendMessage(message.String);
}
}
return;
}
tool.UsesRemaining--;
if (craftSystem is DefBlacksmithy)
{
var hammer = from.FindItemOnLayer<AncientSmithyHammer>(Layer.OneHanded);
if (hammer != null && hammer != tool)
{
hammer.UsesRemaining--;
if (hammer.UsesRemaining < 1)
{
hammer.Delete();
}
}
}
if (tool.UsesRemaining < 1 && tool.BreakOnDepletion)
{
toolBroken = true;
}
if (toolBroken)
{
tool.Delete();
}
Item item;
if (customCraft != null)
{
item = customCraft.CompleteCraft(out num);
}
else if (typeof(MapItem).IsAssignableFrom(ItemType) && from.Map != Map.Trammel && from.Map != Map.Felucca)
{
item = new IndecipherableMap();
from.SendLocalizedMessage(1070800); // The map you create becomes mysteriously indecipherable.
}
else
{
item = ItemType.CreateInstance<Item>();
}
if (item != null)
{
if (item is ICraftable craftable)
{
endquality = craftable.OnCraft(quality, makersMark, from, craftSystem, typeRes, tool, this, resHue);
}
else if (item.Hue == 0)
{
item.Hue = resHue;
}
if (maxAmount > 0)
{
if (!item.Stackable && item is IUsesRemaining remaining)
{
remaining.UsesRemaining *= maxAmount;
}
else
{
item.Amount = maxAmount;
}
}
from.AddToBackpack(item);
if (from.AccessLevel > AccessLevel.Player)
{
CommandLogging.WriteLine(
from,
$"Crafting {CommandLogging.Format(item)} with craft system {craftSystem.GetType().Name}"
);
}
// from.PlaySound( 0x57 );
}
if (num == 0)
{
num = craftSystem.PlayEndingEffect(from, false, true, toolBroken, endquality, makersMark, this);
}
var queryFactionImbue = false;
var availableSilver = 0;
FactionItemDefinition def = null;
Faction faction = null;
if (item is IFactionItem)
{
def = FactionItemDefinition.Identify(item);
if (def != null)
{
faction = Faction.Find(from);
if (faction != null)
{
var town = Town.FromRegion(from.Region);
if (town?.Owner == faction)
{
var pack = from.Backpack;
if (pack != null)
{
availableSilver = pack.GetAmount(typeof(Silver));
if (availableSilver >= def.SilverCost)
{
queryFactionImbue = Faction.IsNearType(from, def.VendorType, 12);
}
}
}
}
}
}
// TODO: Scroll imbuing
if (queryFactionImbue)
{
from.SendGump(
new FactionImbueGump(
quality,
item,
from,
craftSystem,
tool,
num,
availableSilver,
faction,
def
)
);
}
else if (tool?.Deleted == false && tool.UsesRemaining > 0)
{
from.SendGump(new CraftGump(from, craftSystem, tool, num));
}
else if (num > 0)
{
from.SendLocalizedMessage(num);
}
return;
}
if (!allRequiredSkills)
{
if (tool?.Deleted == false && tool.UsesRemaining > 0)
{
from.SendGump(new CraftGump(from, craftSystem, tool, 1044153));
}
else
{
from.SendLocalizedMessage(1044153); // You don't have the required skills to attempt this item.
}
return;
}
var consumeType = UseAllRes ? ConsumeType.Half : ConsumeType.All;
// Not enough resource to craft it
if (!ConsumeRes(from, typeRes, craftSystem, ref resHue, ref maxAmount, consumeType, ref message, true))
{
if (tool?.Deleted == false && tool.UsesRemaining > 0)
{
from.SendGump(new CraftGump(from, craftSystem, tool, message));
}
else if (message != null)
{
if (message.Number > 0)
{
from.SendLocalizedMessage(message.Number);
}
else
{
from.SendMessage(message.String);
}
}
return;
}
tool.UsesRemaining--;
if (tool.UsesRemaining < 1 && tool.BreakOnDepletion)
{
toolBroken = true;
}
if (toolBroken)
{
tool.Delete();
}
// SkillCheck failed.
num = craftSystem.PlayEndingEffect(from, true, true, toolBroken, endquality, false, this);
if (!tool.Deleted && tool.UsesRemaining > 0)
{
from.SendGump(new CraftGump(from, craftSystem, tool, num));
}
else if (num > 0)
{
from.SendLocalizedMessage(num);
}
}
private class InternalTimer : Timer
{
private readonly CraftItem m_CraftItem;
private readonly CraftSystem m_CraftSystem;
private readonly Mobile m_From;
private readonly int m_iCountMax;
private readonly BaseTool m_Tool;
private readonly Type m_TypeRes;
private int m_iCount;
public InternalTimer(
Mobile from, CraftSystem craftSystem, CraftItem craftItem, Type typeRes, BaseTool tool,
int iCountMax
) : base(TimeSpan.Zero, TimeSpan.FromSeconds(craftSystem.Delay), iCountMax)
{
m_From = from;
m_CraftItem = craftItem;
m_iCount = 0;
m_iCountMax = iCountMax;
m_CraftSystem = craftSystem;
m_TypeRes = typeRes;
m_Tool = tool;
}
protected override void OnTick()
{
m_iCount++;
m_From.DisruptiveAction();
if (m_iCount < m_iCountMax)
{
m_CraftSystem.PlayCraftEffect(m_From);
return;
}
m_From.EndAction<CraftSystem>();
var badCraft = m_CraftSystem.CanCraft(m_From, m_Tool, m_CraftItem.ItemType);
if (badCraft > 0)
{
if (m_Tool?.Deleted == false && m_Tool.UsesRemaining > 0)
{
m_From.SendGump(new CraftGump(m_From, m_CraftSystem, m_Tool, badCraft));
}
else
{
m_From.SendLocalizedMessage(badCraft);
}
return;
}
var quality = 1;
m_CraftItem.CheckSkills(m_From, m_TypeRes, m_CraftSystem, ref quality, out _, false);
var context = m_CraftSystem.GetContext(m_From);
if (context == null)
{
return;
}
if (typeof(CustomCraft).IsAssignableFrom(m_CraftItem.ItemType))
{
try
{
m_CraftItem.ItemType.CreateInstance<CustomCraft>(
m_From,
m_CraftItem,
m_CraftSystem,
m_TypeRes,
m_Tool,
quality
)?.EndCraftAction();
}
catch (Exception e)
{
Console.WriteLine(e);
}
return;
}
var makersMark = false;
if (quality == 2 && m_From.Skills[m_CraftSystem.MainSkill].Base >= 100.0)
{
makersMark = m_CraftItem.IsMarkable(m_CraftItem.ItemType);
}
if (makersMark && context.MarkOption == CraftMarkOption.PromptForMark)
{
m_From.SendGump(
new QueryMakersMarkGump(
quality,
m_From,
m_CraftItem,
m_CraftSystem,
m_TypeRes,
m_Tool
)
);
}
else
{
if (context.MarkOption == CraftMarkOption.DoNotMark)
{
makersMark = false;
}
m_CraftItem.CompleteCraft(quality, makersMark, m_From, m_CraftSystem, m_TypeRes, m_Tool, null);
}
}
}
}
}