-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathScriptableItem.cs
More file actions
135 lines (126 loc) · 5.95 KB
/
ScriptableItem.cs
File metadata and controls
135 lines (126 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
// Saves the item info in a ScriptableObject that can be used ingame by
// referencing it from a MonoBehaviour. It only stores an item's 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 items may not be referenced by any
// entity ingame (e.g. when a special event item isn't dropped anymore after the
// event). But all items 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.)
//
// An Item can be created by right clicking the Resources folder and selecting
// Create -> uMMORPG Item. Existing items can be found in the Resources folder.
//
// Note: this class is not abstract so we can create 'useless' items like recipe
// ingredients, etc.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
namespace uMMORPG
{
[CreateAssetMenu(menuName="uMMORPG Item/General", order=999)]
public partial class ScriptableItem : ScriptableObject
{
[Header("Base Stats")]
public int maxStack;
[Tooltip("Durability is only allowed for non-stackable items (if MaxStack is 1))")]
public int maxDurability = 0; // disabled by default
public long buyPrice;
public long sellPrice;
public long itemMallPrice;
public bool sellable;
public bool tradable;
public bool destroyable;
[SerializeField, TextArea(1, 30)] protected string toolTip; // not public, use ToolTip()
public Sprite image;
// tooltip /////////////////////////////////////////////////////////////////
// fill in all variables into the tooltip
// this saves us lots of ugly string concatenation code.
// (dynamic ones are filled in Item.cs)
// -> note: each tooltip can have any variables, or none if needed
// -> example usage:
/*
<b>{NAME}</b>
Description here...
Destroyable: {DESTROYABLE}
Sellable: {SELLABLE}
Tradable: {TRADABLE}
Amount: {AMOUNT}
Price: {BUYPRICE} Gold
<i>Sells for: {SELLPRICE} Gold</i>
*/
public virtual string ToolTip()
{
// note: caching StringBuilder is worse for GC because .Clear frees the internal array and reallocates.
StringBuilder tip = new StringBuilder(toolTip);
tip.Replace("{NAME}", name);
tip.Replace("{DESTROYABLE}", (destroyable ? "Yes" : "No"));
tip.Replace("{SELLABLE}", (sellable ? "Yes" : "No"));
tip.Replace("{TRADABLE}", (tradable ? "Yes" : "No"));
tip.Replace("{BUYPRICE}", buyPrice.ToString());
tip.Replace("{SELLPRICE}", sellPrice.ToString());
return tip.ToString();
}
// 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.
// -> we save the hash so the dynamic item part doesn't have to contain and
// sync the whole name over the network
static Dictionary<int, ScriptableItem> cache;
public static Dictionary<int, ScriptableItem> All
{
get
{
// not loaded yet?
if (cache == null)
{
// get all ScriptableItems in resources
ScriptableItem[] items = Resources.LoadAll<ScriptableItem>("");
// check for duplicates, then add to cache
List<string> duplicates = items.ToList().FindDuplicates(item => item.name);
if (duplicates.Count == 0)
{
cache = items.ToDictionary(item => item.name.GetStableHashCode(), item => item);
}
else
{
foreach (string duplicate in duplicates)
Debug.LogError("Resources folder contains multiple ScriptableItems 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;
}
}
// validation //////////////////////////////////////////////////////////////
void OnValidate()
{
// with durability, there is an odd situation where items with maxstack
// bigger than 1 would not be stackable if they have different
// durability. this causes a lot of gameplay frustration and can feel
// like a bug.
// -> let's limit durability to items that aren't stackable. everything
// else is just way too confusing.
if (maxStack > 1 && maxDurability != 0)
{
maxDurability = 0;
Debug.LogWarning(name + " maxDurability was reset to 0 because it's not stackable. Set maxStack to 1 if you want to use durability.");
}
// make sure that the sell price <= buy price to avoid exploitation
// (people should never buy an item for 1 gold and sell it for 2 gold)
sellPrice = Math.Min(sellPrice, buyPrice);
}
}
// ScriptableItem + Amount is useful for default items (e.g. spawn with 10 potions)
[Serializable]
public struct ScriptableItemAndAmount
{
public ScriptableItem item;
public int amount;
}
}