-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathPlayerCrafting.cs
More file actions
184 lines (168 loc) · 7.99 KB
/
PlayerCrafting.cs
File metadata and controls
184 lines (168 loc) · 7.99 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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using Mirror;
namespace uMMORPG
{
public enum CraftingState { None, InProgress, Success, Failed }
[RequireComponent(typeof(PlayerInventory))]
[DisallowMultipleComponent]
public class PlayerCrafting : NetworkBehaviour
{
[Header("Components")]
public Player player;
public PlayerInventory inventory;
[Header("Crafting")]
public List<int> indices = Enumerable.Repeat(-1, ScriptableRecipe.recipeSize).ToList();
[HideInInspector] public CraftingState state = CraftingState.None; // // client sided
ScriptableRecipe currentRecipe; // currently crafted recipe. cached to avoid searching ALL recipes in Craft()
[SyncVar, HideInInspector] public double endTime; // double for long term precision
[HideInInspector] public bool requestPending; // for state machine event
// crafting ////////////////////////////////////////////////////////////////
// the crafting system is designed to work with all kinds of commonly known
// crafting options:
// - item combinations: wood + stone = axe
// - weapon upgrading: axe + gem = strong axe
// - recipe items: axerecipe(item) + wood(item) + stone(item) = axe(item)
//
// players can craft at all times, not just at npcs, because that's the most
// realistic option
// craft a recipe with the combination of items and put result into inventory
// => we pass the recipe name so that we don't have to search ALL the
// recipes. this would slow down the server if we have lots of recipes.
// => we just let the client do the searching!
[Command]
public void CmdCraft(string recipeName, int[] clientIndices)
{
// validate: between 1 and 6, all valid, no duplicates?
// -> can be IDLE or MOVING (in which case we reset the movement)
if ((player.state == "IDLE" || player.state == "MOVING") &&
clientIndices.Length == ScriptableRecipe.recipeSize)
{
// find valid indices that are not '-1' and make sure there are no
// duplicates
List<int> validIndices = clientIndices.Where(index => 0 <= index && index < inventory.slots.Count && inventory.slots[index].amount > 0).ToList();
if (validIndices.Count > 0 && !validIndices.HasDuplicates())
{
// find recipe
if (ScriptableRecipe.All.TryGetValue(recipeName, out ScriptableRecipe recipe) &&
recipe.result != null)
{
// enough space?
Item result = new Item(recipe.result);
if (inventory.CanAdd(result, 1))
{
// cache recipe so we don't have to search for it again
// in Craft()
currentRecipe = recipe;
// store the crafting indices on the server. no need for
// a SyncList and unnecessary broadcasting.
// we already have a 'craftingIndices' variable anyway.
indices = clientIndices.ToList();
// start crafting
requestPending = true;
endTime = NetworkTime.time + recipe.craftingTime;
}
}
}
}
}
// finish the crafting
[Server]
public void Craft()
{
// should only be called while CRAFTING and if recipe still valid
// (no one should touch 'craftingRecipe', but let's just be sure.
// -> we already validated everything in CmdCraft. let's just craft.
if (player.state == "CRAFTING" &&
currentRecipe != null &&
currentRecipe.result != null)
{
// enough space?
Item result = new Item(currentRecipe.result);
if (inventory.CanAdd(result, 1))
{
// remove the ingredients from inventory in any case
foreach (ScriptableItemAndAmount ingredient in currentRecipe.ingredients)
if (ingredient.amount > 0 && ingredient.item != null)
inventory.Remove(new Item(ingredient.item), ingredient.amount);
// roll the dice to decide if we add the result or not
// IMPORTANT: we use rand() < probability to decide.
// => UnityEngine.Random.value is [0,1] inclusive:
// for 0% probability it's fine because it's never '< 0'
// for 100% probability it's not because it's not always '< 1', it might be == 1
// and if we use '<=' instead then it won't work for 0%
// => C#'s Random value is [0,1) exclusive like most random
// functions. this works fine.
if (new System.Random().NextDouble() < currentRecipe.probability)
{
// add result item to inventory
inventory.Add(new Item(currentRecipe.result), 1);
TargetCraftingSuccess();
}
else
{
TargetCraftingFailed();
}
// clear indices afterwards
// note: we set all to -1 instead of calling .Clear because
// that would clear all the slots in host mode.
// (don't clear in host mode, otherwise it clears the crafting
// UI for the player and we have to drag items into it again)
if (!isLocalPlayer)
for (int i = 0; i < ScriptableRecipe.recipeSize; ++i)
indices[i] = -1;
// clear recipe
currentRecipe = null;
}
}
}
// two rpcs for results to save 1 byte for the actual result
[TargetRpc] // only send to one client
public void TargetCraftingSuccess()
{
state = CraftingState.Success;
}
[TargetRpc] // only send to one client
public void TargetCraftingFailed()
{
state = CraftingState.Failed;
}
// drag & drop /////////////////////////////////////////////////////////////
void OnDragAndDrop_InventorySlot_CraftingIngredientSlot(int[] slotIndices)
{
// slotIndices[0] = slotFrom; slotIndices[1] = slotTo
// only if not crafting right now
if (state != CraftingState.InProgress)
{
if (!indices.Contains(slotIndices[0]))
{
indices[slotIndices[1]] = slotIndices[0];
state = CraftingState.None; // reset state
}
}
}
void OnDragAndDrop_CraftingIngredientSlot_CraftingIngredientSlot(int[] slotIndices)
{
// slotIndices[0] = slotFrom; slotIndices[1] = slotTo
// only if not crafting right now
if (state != CraftingState.InProgress)
{
// just swap them clientsided
int temp = indices[slotIndices[0]];
indices[slotIndices[0]] = indices[slotIndices[1]];
indices[slotIndices[1]] = temp;
state = CraftingState.None; // reset state
}
}
void OnDragAndClear_CraftingIngredientSlot(int slotIndex)
{
// only if not crafting right now
if (state != CraftingState.InProgress)
{
indices[slotIndex] = -1;
state = CraftingState.None; // reset state
}
}
}
}