diff --git a/appveyor.yml b/appveyor.yml
index f1a39cc..8b000b8 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -1,4 +1,4 @@
-version: 0.29.{build}
+version: 0.30.{build}
branches:
only:
- master
diff --git a/build/pack.bat b/build/pack.bat
index 3f5d7de..bce98d8 100644
--- a/build/pack.bat
+++ b/build/pack.bat
@@ -1,4 +1,4 @@
-set version=0.19.00
+set version=0.29.00
dotnet pack ../src/OpenRpg.Core -c Release -o ../../_dist /p:version=%version%
dotnet pack ../src/OpenRpg.Items -c Release -o ../../_dist /p:version=%version%
dotnet pack ../src/OpenRpg.Combat -c Release -o ../../_dist /p:version=%version%
@@ -10,4 +10,5 @@ dotnet pack ../src/OpenRpg.Quests -c Release -o ../../_dist /p:version=%version%
dotnet pack ../src/OpenRpg.Genres.Fantasy -c Release -o ../../_dist /p:version=%version%
dotnet pack ../src/OpenRpg.Cards -c Release -o ../../_dist /p:version=%version%
dotnet pack ../src/OpenRpg.CurveFunctions -c Release -o ../../_dist /p:version=%version%
-dotnet pack ../src/OpenRpg.Tags -c Release -o ../../_dist /p:version=%version%
\ No newline at end of file
+dotnet pack ../src/OpenRpg.Tags -c Release -o ../../_dist /p:version=%version%
+dotnet pack ../src/OpenRpg.Items.TradeSkills -c Release -o ../../_dist /p:version=%version%
\ No newline at end of file
diff --git a/src/OpenRpg.Items.TradeSkills/Calculator/ITradeSkillCalculator.cs b/src/OpenRpg.Items.TradeSkills/Calculator/ITradeSkillCalculator.cs
new file mode 100644
index 0000000..d2a4c46
--- /dev/null
+++ b/src/OpenRpg.Items.TradeSkills/Calculator/ITradeSkillCalculator.cs
@@ -0,0 +1,12 @@
+namespace OpenRpg.Items.TradeSkills.Calculator
+{
+ public interface ITradeSkillCalculator
+ {
+ float MinimumPointThreshold { get; set; }
+ float PointMultiplier { get; set; }
+ float MaximumSkillDifference { get; set; }
+
+ bool CanUseSkill(int skillScore, int skillDifficulty);
+ int CalculateSkillUpPointsFor(int skillScore, int skillDifficulty);
+ }
+}
\ No newline at end of file
diff --git a/src/OpenRpg.Items.TradeSkills/Calculator/TradeSkillCalculator.cs b/src/OpenRpg.Items.TradeSkills/Calculator/TradeSkillCalculator.cs
new file mode 100644
index 0000000..e1069d9
--- /dev/null
+++ b/src/OpenRpg.Items.TradeSkills/Calculator/TradeSkillCalculator.cs
@@ -0,0 +1,36 @@
+using System;
+using OpenRpg.CurveFunctions;
+using OpenRpg.CurveFunctions.Scaling;
+using OpenRpg.Items.TradeSkills.Calculator;
+
+public class TradeSkillCalculator : ITradeSkillCalculator
+{
+ public float MinimumPointThreshold { get; set; } = 0.5f;
+ public float PointMultiplier { get; set; } = 1.0f;
+ public float MaximumSkillDifference { get; set; } = 10.0f;
+
+ public IScalingFunction SkillPointCurve { get; }
+
+ public TradeSkillCalculator()
+ {
+ SkillPointCurve = new ScalingFunction(PresetCurves.InverseLinear, 0, 1, 0, MaximumSkillDifference);
+ }
+
+ public bool CanUseSkill(int skillScore, int skillDifficulty)
+ {
+ var skillDifference = skillDifficulty - skillScore;
+ var absoluteScore = Math.Abs(skillDifference);
+ return absoluteScore <= MaximumSkillDifference;
+ }
+
+ public int CalculateSkillUpPointsFor(int skillScore, int skillDifficulty)
+ {
+ var skillDifference = skillDifficulty - skillScore;
+ var absoluteScore = Math.Abs(skillDifference);
+ if (absoluteScore > MaximumSkillDifference) { return 0; }
+
+ var result = SkillPointCurve.Plot(absoluteScore);
+ if (result < MinimumPointThreshold) { return 0; }
+ return (int)Math.Round(result * PointMultiplier);
+ }
+}
\ No newline at end of file
diff --git a/src/OpenRpg.Items.TradeSkills/Crafting/ItemCraftingTemplate.cs b/src/OpenRpg.Items.TradeSkills/Crafting/ItemCraftingTemplate.cs
new file mode 100644
index 0000000..dd24ae1
--- /dev/null
+++ b/src/OpenRpg.Items.TradeSkills/Crafting/ItemCraftingTemplate.cs
@@ -0,0 +1,17 @@
+using System.Collections.Generic;
+
+namespace OpenRpg.Items.TradeSkills.Crafting
+{
+ public class ItemCraftingTemplate : TradeSkillTemplate
+ {
+ ///
+ /// The items required to craft this template
+ ///
+ public List InputItems { get; set; } = new List();
+
+ ///
+ /// The items output from this template
+ ///
+ public List OutputItems { get; set; } = new List();
+ }
+}
\ No newline at end of file
diff --git a/src/OpenRpg.Items.TradeSkills/Extensions/InventoryExtensions.cs b/src/OpenRpg.Items.TradeSkills/Extensions/InventoryExtensions.cs
new file mode 100644
index 0000000..a3e498c
--- /dev/null
+++ b/src/OpenRpg.Items.TradeSkills/Extensions/InventoryExtensions.cs
@@ -0,0 +1,35 @@
+using OpenRpg.Items.Extensions;
+using OpenRpg.Items.Inventory;
+using OpenRpg.Items.TradeSkills.Crafting;
+
+namespace OpenRpg.Items.TradeSkills.Extensions
+{
+ public static class InventoryExtensions
+ {
+ public static bool HasItemsRequiredFor(this IInventory inventory, ItemCraftingTemplate craftingTemplate)
+ {
+ foreach (var itemEntry in craftingTemplate.InputItems)
+ {
+ if (itemEntry.Variables.HasAmount())
+ {
+ var amount = itemEntry.Variables.Amount();
+ if (!inventory.HasItem(itemEntry.ItemTemplateId, amount))
+ { return false; }
+ }
+ else if (itemEntry.Variables.HasWeight())
+ {
+ var weight = itemEntry.Variables.Weight();
+ if (!inventory.HasItem(itemEntry.ItemTemplateId, weight))
+ { return false; }
+ }
+ else
+ {
+ if (!inventory.HasItem(itemEntry.ItemTemplateId))
+ { return false; }
+ }
+ }
+
+ return true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/OpenRpg.Items.TradeSkills/Extensions/TradeSkillItemEntryExtensions.cs b/src/OpenRpg.Items.TradeSkills/Extensions/TradeSkillItemEntryExtensions.cs
new file mode 100644
index 0000000..2ef27fd
--- /dev/null
+++ b/src/OpenRpg.Items.TradeSkills/Extensions/TradeSkillItemEntryExtensions.cs
@@ -0,0 +1,28 @@
+using System;
+using OpenRpg.Items.TradeSkills.Types;
+using OpenRpg.Items.TradeSkills.Variables;
+
+namespace OpenRpg.Items.TradeSkills.Extensions
+{
+ public static class TradeSkillItemEntryExtensions
+ {
+ public static bool HasAmount(this TradeSkillItemEntryVariables variables)
+ { return variables.ContainsKey(TradeSkillItemEntryVariableTypes.Amount); }
+
+ public static int Amount(this TradeSkillItemEntryVariables variables)
+ {
+ var amountObject = variables.Get(TradeSkillItemEntryVariableTypes.Amount);
+ var amount = Convert.ToInt32(amountObject);
+ return amount == 0 ? 1 : amount;
+ }
+
+ public static void Amount(this TradeSkillItemEntryVariables variables, int value)
+ { variables[TradeSkillItemEntryVariableTypes.Amount] = value; }
+
+ public static bool HasWeight(this TradeSkillItemEntryVariables variables)
+ { return variables.ContainsKey(TradeSkillItemEntryVariableTypes.Weight); }
+
+ public static float Weight(this TradeSkillItemEntryVariables variables) => Convert.ToSingle(variables.Get(TradeSkillItemEntryVariableTypes.Weight));
+ public static void Weight(this TradeSkillItemEntryVariables variables, float value) => variables[TradeSkillItemEntryVariableTypes.Weight] = value;
+ }
+}
\ No newline at end of file
diff --git a/src/OpenRpg.Items.TradeSkills/Gathering/ItemGatheringTemplate.cs b/src/OpenRpg.Items.TradeSkills/Gathering/ItemGatheringTemplate.cs
new file mode 100644
index 0000000..09dbe6a
--- /dev/null
+++ b/src/OpenRpg.Items.TradeSkills/Gathering/ItemGatheringTemplate.cs
@@ -0,0 +1,12 @@
+using System.Collections.Generic;
+
+namespace OpenRpg.Items.TradeSkills.Gathering
+{
+ public class ItemGatheringTemplate : TradeSkillTemplate
+ {
+ ///
+ /// The items output from this template
+ ///
+ public List OutputItems { get; set; } = new List();
+ }
+}
diff --git a/src/OpenRpg.Items.TradeSkills/ITradeSkillTemplate.cs b/src/OpenRpg.Items.TradeSkills/ITradeSkillTemplate.cs
new file mode 100644
index 0000000..b19e062
--- /dev/null
+++ b/src/OpenRpg.Items.TradeSkills/ITradeSkillTemplate.cs
@@ -0,0 +1,29 @@
+using OpenRpg.Core.Common;
+using OpenRpg.Core.Requirements;
+using OpenRpg.Items.TradeSkills.Variables;
+
+namespace OpenRpg.Items.TradeSkills
+{
+ public interface ITradeSkillTemplate : IHasDataId, IHasRequirements
+ {
+ ///
+ /// Time in seconds for the action to complete i.e gathered/created
+ ///
+ public float TimeToComplete { get; }
+
+ ///
+ /// The category of trade skill type
+ ///
+ public int SkillType { get; }
+
+ ///
+ /// Indicates how difficult this is to get, effects gather/creation rates and level up rates
+ ///
+ public int SkillDifficulty { get; }
+
+ ///
+ /// Variables for this template
+ ///
+ public ITradeSkillTemplateVariables Variables { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/OpenRpg.Items.TradeSkills/OpenRpg.Items.TradeSkills.csproj b/src/OpenRpg.Items.TradeSkills/OpenRpg.Items.TradeSkills.csproj
new file mode 100644
index 0000000..328caaa
--- /dev/null
+++ b/src/OpenRpg.Items.TradeSkills/OpenRpg.Items.TradeSkills.csproj
@@ -0,0 +1,21 @@
+
+
+
+ 0.0.0
+ netstandard2.1
+ OpenRpg.Items.TradeSkills
+ Grofit (LP)
+ https://github.com/openrpg/OpenRpg/blob/master/LICENSE
+ https://github.com/openrpg/OpenRpg
+ Adds the notion of trade skills such as gathering or crafting to OpenRpg
+ rpg game-development xna monogame unity godot
+ 8
+
+
+
+
+
+
+
+
+
diff --git a/src/OpenRpg.Items.TradeSkills/TradeSkillItemEntry.cs b/src/OpenRpg.Items.TradeSkills/TradeSkillItemEntry.cs
new file mode 100644
index 0000000..f07f5c0
--- /dev/null
+++ b/src/OpenRpg.Items.TradeSkills/TradeSkillItemEntry.cs
@@ -0,0 +1,11 @@
+using OpenRpg.Items.TradeSkills.Variables;
+
+namespace OpenRpg.Items.TradeSkills
+{
+ public class TradeSkillItemEntry
+ {
+ public int ItemTemplateId { get; set; }
+
+ public TradeSkillItemEntryVariables Variables { get; set; } = new DefaultTradeSkillItemEntryVariables();
+ }
+}
\ No newline at end of file
diff --git a/src/OpenRpg.Items.TradeSkills/TradeSkillTemplate.cs b/src/OpenRpg.Items.TradeSkills/TradeSkillTemplate.cs
new file mode 100644
index 0000000..1c00f0e
--- /dev/null
+++ b/src/OpenRpg.Items.TradeSkills/TradeSkillTemplate.cs
@@ -0,0 +1,40 @@
+using System;
+using System.Collections.Generic;
+using OpenRpg.Core.Requirements;
+using OpenRpg.Items.TradeSkills.Variables;
+
+namespace OpenRpg.Items.TradeSkills
+{
+ public class TradeSkillTemplate : ITradeSkillTemplate
+ {
+ ///
+ /// The Id for this template
+ ///
+ public int Id { get; set; }
+
+ ///
+ /// Gathering time in seconds, per unit gathered
+ ///
+ public float TimeToComplete { get; set; } = 1.0f;
+
+ ///
+ /// The category of skill type used for Gathering
+ ///
+ public int SkillType { get; set; }
+
+ ///
+ /// Indicates how difficult this is to get, effects if you can use the trade skill and skill up rates
+ ///
+ public int SkillDifficulty { get; set; }
+
+ ///
+ /// Requirements needed before this tradeskill is allowed
+ ///
+ public IEnumerable Requirements { get; set; } = Array.Empty();
+
+ ///
+ /// Variables for this template
+ ///
+ public ITradeSkillTemplateVariables Variables { get; set; } = new DefaultTradeSkillTemplateVariables();
+ }
+}
\ No newline at end of file
diff --git a/src/OpenRpg.Items.TradeSkills/Trading/ITrader.cs b/src/OpenRpg.Items.TradeSkills/Trading/ITrader.cs
new file mode 100644
index 0000000..036e422
--- /dev/null
+++ b/src/OpenRpg.Items.TradeSkills/Trading/ITrader.cs
@@ -0,0 +1,9 @@
+using System.Collections.Generic;
+
+namespace OpenRpg.Items.TradeSkills.Trading
+{
+ public interface ITrader
+ {
+ IReadOnlyList Items { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/OpenRpg.Items.TradeSkills/Trading/ItemTradeEntry.cs b/src/OpenRpg.Items.TradeSkills/Trading/ItemTradeEntry.cs
new file mode 100644
index 0000000..b621d1a
--- /dev/null
+++ b/src/OpenRpg.Items.TradeSkills/Trading/ItemTradeEntry.cs
@@ -0,0 +1,14 @@
+using OpenRpg.Items.TradeSkills.Variables;
+
+namespace OpenRpg.Items.TradeSkills.Trading
+{
+ public class ItemTradeEntry
+ {
+ public int ItemTemplateId { get; set; }
+
+ public float BuyRate { get; set; }
+ public float SellRate { get; set; }
+
+ private IItemTradeTradeEntryVariables Variables { get; set; } = new DefaultItemTradeEntryVariables();
+ }
+}
\ No newline at end of file
diff --git a/src/OpenRpg.Items.TradeSkills/Types/ItemCoreVariableTypes.cs b/src/OpenRpg.Items.TradeSkills/Types/ItemCoreVariableTypes.cs
new file mode 100644
index 0000000..cff5a63
--- /dev/null
+++ b/src/OpenRpg.Items.TradeSkills/Types/ItemCoreVariableTypes.cs
@@ -0,0 +1,12 @@
+using OpenRpg.Items.Types;
+
+namespace OpenRpg.Items.TradeSkills.Types
+{
+ public interface TradeSkillCoreVariableTypes : ItemCoreVariableTypes
+ {
+ public static int TradeSkillTemplateVariables = 50;
+ public static int TraderVariables = 51;
+ public static int ItemTradeEntryVariables = 52;
+ public static int TradeSkillItemEntryVariables = 53;
+ }
+}
\ No newline at end of file
diff --git a/src/OpenRpg.Items.TradeSkills/Types/TradeSkillItemEntryVariableTypes.cs b/src/OpenRpg.Items.TradeSkills/Types/TradeSkillItemEntryVariableTypes.cs
new file mode 100644
index 0000000..07e4d8d
--- /dev/null
+++ b/src/OpenRpg.Items.TradeSkills/Types/TradeSkillItemEntryVariableTypes.cs
@@ -0,0 +1,10 @@
+namespace OpenRpg.Items.TradeSkills.Types
+{
+ public interface TradeSkillItemEntryVariableTypes
+ {
+ public static readonly int Unknown = 0;
+
+ public static readonly int Amount = 1;
+ public static readonly int Weight = 2;
+ }
+}
\ No newline at end of file
diff --git a/src/OpenRpg.Items.TradeSkills/Types/TradeSkillTypes.cs b/src/OpenRpg.Items.TradeSkills/Types/TradeSkillTypes.cs
new file mode 100644
index 0000000..6996f7a
--- /dev/null
+++ b/src/OpenRpg.Items.TradeSkills/Types/TradeSkillTypes.cs
@@ -0,0 +1,7 @@
+namespace OpenRpg.Items.TradeSkills.Types
+{
+ public interface TradeSkillTypes
+ {
+ public static readonly int Unknown = 0;
+ }
+}
\ No newline at end of file
diff --git a/src/OpenRpg.Items.TradeSkills/Variables/DefaultItemTradeEntryVariables.cs b/src/OpenRpg.Items.TradeSkills/Variables/DefaultItemTradeEntryVariables.cs
new file mode 100644
index 0000000..6aabf18
--- /dev/null
+++ b/src/OpenRpg.Items.TradeSkills/Variables/DefaultItemTradeEntryVariables.cs
@@ -0,0 +1,13 @@
+using System.Collections.Generic;
+using OpenRpg.Core.Variables;
+using OpenRpg.Items.TradeSkills.Types;
+
+namespace OpenRpg.Items.TradeSkills.Variables
+{
+ public class DefaultItemTradeEntryVariables : DefaultVariables