-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathScriptableRecipe.cs
More file actions
144 lines (130 loc) · 5.95 KB
/
ScriptableRecipe.cs
File metadata and controls
144 lines (130 loc) · 5.95 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
// Saves the crafting recipe info in a ScriptableObject that can be used ingame
// by referencing it from a MonoBehaviour. It only stores static data.
//
// We also add each one to a dictionary automatically, so that all of them can
// be found by name without having to put them all in a database. Note that we
// have to put them all into the Resources folder and use Resources.LoadAll to
// load them. This is important because some recipes may not be referenced by
// any entity ingame. But all recipes should still be loadable from the
// database, even if they are not referenced by anyone anymore. So we have to
// use Resources.Load. (before we added them to the dict in OnEnable, but that's
// only called for those that are referenced in the game. All others will be
// ignored be Unity.)
//
// A Recipe can be created by right clicking the Resources folder and selecting
// Create -> uMMORPG Recipe. Existing recipes can be found in the Resources
// folder.
using UnityEngine;
using System.Collections.Generic;
using System.Linq;
namespace uMMORPG
{
[CreateAssetMenu(fileName="New Recipe", menuName="uMMORPG Recipe", order=999)]
public class ScriptableRecipe : ScriptableObject
{
// fixed ingredient size for all recipes
public static int recipeSize = 6;
// ingredients and result
public List<ScriptableItemAndAmount> ingredients = new List<ScriptableItemAndAmount>(6);
public ScriptableItem result;
// crafting time in seconds
public float craftingTime = 1;
// probability of success
[Range(0, 1)] public float probability = 1;
// helper function to check if an item slot list has at least one valid item
bool IngredientsNotEmpty()
{
// avoid Linq for performance / GC
foreach (ScriptableItemAndAmount slot in ingredients)
if (slot.amount > 0 && slot.item != null)
return true;
return false;
}
int FindMatchingStack(List<ItemSlot> items, ScriptableItemAndAmount ingredient)
{
// avoid FindIndex for performance/GC
for (int i = 0; i < items.Count; ++i)
if (items[i].amount >= ingredient.amount &&
items[i].item.data == ingredient.item)
return i;
return -1;
}
// check if the list of items works for this recipe. the list shouldn't
// contain 'null'.
// (inheriting classes can modify the matching algorithm if needed)
public virtual bool CanCraftWith(List<ItemSlot> items)
{
// items list should not be touched, since it's often used to check more
// than one recipe. so let's just create a local copy.
items = new List<ItemSlot>(items);
// make sure that we have at least one ingredient
if (IngredientsNotEmpty())
{
// each ingredient in the list, with amount?
foreach (ScriptableItemAndAmount ingredient in ingredients)
{
if (ingredient.amount > 0 && ingredient.item != null)
{
// is there a stack with at least that amount and that item?
int index = FindMatchingStack(items, ingredient);
if (index != -1)
items.RemoveAt(index);
else
return false;
}
}
// and nothing else in the list?
return items.Count == 0;
}
else return false;
}
// caching /////////////////////////////////////////////////////////////////
// we can only use Resources.Load in the main thread. we can't use it when
// declaring static variables. so we have to use it as soon as 'All' is
// accessed for the first time from the main thread.
static Dictionary<string, ScriptableRecipe> cache;
public static Dictionary<string, ScriptableRecipe> All
{
get
{
// not loaded yet?
if (cache == null)
{
// get all ScriptableRecipes in resources
ScriptableRecipe[] recipes = Resources.LoadAll<ScriptableRecipe>("");
// check for duplicates, then add to cache
List<string> duplicates = recipes.ToList().FindDuplicates(recipe => recipe.name);
if (duplicates.Count == 0)
{
cache = recipes.ToDictionary(recipe => recipe.name, recipe => recipe);
}
else
{
foreach (string duplicate in duplicates)
Debug.LogError("Resources folder contains multiple ScriptableRecipes with the name " + duplicate + ". If you are using subfolders like 'Warrior/Ring' and 'Archer/Ring', then rename them to 'Warrior/(Warrior)Ring' and 'Archer/(Archer)Ring' instead.");
}
}
return cache;
}
}
// find a recipe based on item slots
public static ScriptableRecipe Find(List<ItemSlot> items)
{
// brute force and avoid Linq (good enough for now)
foreach (ScriptableRecipe recipe in All.Values)
if (recipe.CanCraftWith(items))
return recipe;
return null;
}
// validation //////////////////////////////////////////////////////////////
void OnValidate()
{
// force list size
// -> add if too few
for (int i = ingredients.Count; i < recipeSize; ++i)
ingredients.Add(new ScriptableItemAndAmount());
// -> remove if too many (if > recipeSize)
ingredients.RemoveRange(recipeSize, ingredients.Count - recipeSize);
}
}
}