This repository has been archived by the owner on Jun 6, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 4
/
modmain.lua
397 lines (332 loc) · 11.4 KB
/
modmain.lua
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
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
-- GLOBAL.CHEATS_ENABLED = true
-- GLOBAL.require("debugkeys")
-- GLOBAL.require("debugtools")
--- Sort through the bag and return the items' new offsets.
--
-- @param items
-- @param bag
-- @param offset Item position within bag.
-- @return Sorted item offsets
local function sortItems(items, bag, offset)
table.sort(items, function(a, b)
-- Sort by name then value.
if bag[offset].sortBy == 'name' then
if bag[offset].contents[a].obj.name ~= bag[offset].contents[b].obj.name then
return bag[offset].contents[a].obj.name < bag[offset].contents[b].obj.name
end
return bag[offset].contents[a].value > bag[offset].contents[b].value
-- Sort by value then name.
else
if bag[offset].contents[a].value ~= bag[offset].contents[b].value then
return bag[offset].contents[a].value > bag[offset].contents[b].value
end
return bag[offset].contents[a].obj.name < bag[offset].contents[b].obj.name
end
end)
return items
end
--- Does the item provide armour?
--
-- @param inst InventoryItem object
-- @return bool
local function itemIsArmour(inst)
return inst.components.armor ~= nil
end
--- Is the item a food for the current player?
--
-- @param inst InventoryItem object
-- @return bool
local function itemIsFood(inst)
local itemIsGear = inst.components.edible and inst.components.edible.foodtype == GLOBAL.FOODTYPE.GEARS
return inst.components.edible and (inst.components.perishable or itemIsGear)
end
--- Is the item a light?
--
-- @param inst InventoryItem object
-- @return bool
local function itemIsLight(inst)
return inst.components.fueled and (inst.components.lighter or inst.HasTag == "light")
end
--- Is the item a priority resource?
-- These items were manually selected from a frequency analysis of recipe components in the game
-- as of 10th March 2015. The idea is that the player will care most about having a quantity of
-- these items (because they are used commonly in item recipes), so let's sort them together.
--
-- @param inst InventoryItem object
-- @return bool
local function itemIsResource(inst)
-- Highest frequency to lowest fequency
local items = {
"Twigs",
"Nightmare Fuel",
"Rope",
"Gold Nugget",
"Boards",
"Silk",
"Papyrus",
"Cut Grass",
"Thulecite",
"Cut Stone",
"Flint",
"Log",
"Living Log",
"Pig Skin",
"Thulecite Fragments",
"Rocks",
"Nitre",
}
for i = 1, #items do
local keys = {}
if items[i] == inst.name then
return true
end
end
return false
end
--- Is the item a tool?
-- As of 28th August 2016, the only prefab with "terraformer" is the pitchfork.
--
-- @param inst InventoryItem object
-- @return bool
local function itemIsTool(inst)
return inst.components.equippable and inst.components.finiteuses and (inst.components.tool or inst.components.terraformer)
end
--- Is the item a weapon?
--
-- @param inst InventoryItem object
-- @return bool
local function itemIsWeapon(inst)
return inst.components.weapon ~= nil
end
--- Find the best slot in the backpack for an item. Suppports stacking.
-- Ported version of core's Inventory:GetNextAvailableSlot.
-- The backpack is a Container, not an Inventory object. See http://goo.gl/hX9R98
--
-- @param item InventoryItem object.
-- @param player
-- @return Offset, container.
local function getNextAvailableBackpackSlot(item, player)
local inventory = player.components.inventory
local backpack = inventory:GetOverflowContainer()
local prefabName = nil
-- Function assumes a backpack exists. Don't use it otherwise.
if backpack == nil then
return nil, nil
end
if item.components.stackable ~= nil then
prefabName = item.prefab
-- Check for stacks that aren't full.
for k, v in pairs(inventory.itemslots) do
if v.prefab == prefabName and v.components.stackable and not v.components.stackable:IsFull() then
return k, inventory.itemslots
end
end
for k, v in pairs(backpack.slots) do
if v.prefab == prefabName and v.components.stackable and not v.components.stackable:IsFull() then
return k, backpack
end
end
end
local empty = nil
-- Check for empty space in the container.
for k = 1, backpack.numslots do
if backpack:CanTakeItemInSlot(item, k) and not backpack:GetItemInSlot(k) then
if prefabName ~= nil then
if empty == nil then
empty = k
end
else
return k, backpack
end
end
end
return empty, backpack
end
--- Find the best slot in a player's overall inventory for the specified item to be put in. Supports stacking.
--
-- @param player
-- @param item InventoryItem object.
-- @param bagPreference If new slot required, which container to use. Either "backpack" or "inventory".
-- @return Offset, inventory/container object.
local function getNextAvailableInventorySlot(player, item, bagPreference)
local inventory = player.components.inventory
local backpack = inventory:GetOverflowContainer()
local backpackSlotCount = backpack and backpack:GetNumSlots() or 0
local container = nil
local slot = nil
-- Has the player chosen to store this type of item in their backpack?
if bagPreference == "backpack" and backpack and backpack:NumItems() < backpackSlotCount then
slot, container = getNextAvailableBackpackSlot(item, player)
if slot == nil then
slot, container = getNextAvailableInventorySlot(player, item, "inventory")
end
-- Has the player chosen to store this type of item in their inventory?
-- Or, did they want to store it in their backpack, but it has no space?
else
slot, container = inventory:GetNextAvailableSlot(item)
if slot == nil and backpack then
slot, container = getNextAvailableInventorySlot(player, item, "backpack")
end
end
-- If we got itemslots, return the original inventory
-- equipslots are handled in the calling code!
if slot then
if container == inventory.itemslots then
container = inventory
end
-- backpack is handled by default.
end
return slot, container
end
--- Sorts the player's inventory into a sensible order.
--
-- @param player Sort this player's inventory.
-- @param maxLights Max. number of torches to sort.
-- @param backpackCategory Category of item to sort into backpack.
local function sortInventory(player, maxLights, backpackCategory)
local inventory = player and player.components.inventory or nil
local isPlayerHurt = (player.components.health:GetPercent() * 100) <= 30
local backpack = inventory and inventory:GetOverflowContainer() or nil
local armourBag = { contents = {}, sortBy = 'value', type = 'armour' }
local foodBag = { contents = {}, sortBy = 'value', type = 'food' }
local lightBag = { contents = {}, sortBy = 'value', type = 'light' }
local miscBag = { contents = {}, sortBy = 'name', type = 'misc' }
local resourceBag = { contents = {}, sortBy = 'name', type = 'resources' }
local toolBag = { contents = {}, sortBy = 'name', type = 'tools' }
local weaponBag = { contents = {}, sortBy = 'value', type = 'weapons' }
if player:HasTag("playerghost") or not inventory then
return
end
local backpackSlotCount = backpack and backpack:GetNumSlots() or 0
local invSlotCount = inventory:GetNumSlots()
local totalSlots = backpackSlotCount + invSlotCount
-- Categorise the player's inventory.
for i = 1, totalSlots do
local item = nil
-- Loop through the main inventory.
if i <= invSlotCount then
item = inventory:GetItemInSlot(i)
-- Loop through the backpack.
else
if not backpack then
return
end
item = backpack:GetItemInSlot(i - invSlotCount)
end
-- Figure out what kind of item we're dealing with.
if item then
local bag = miscBag
local sort = 0
-- Armour (chest and head)
if itemIsArmour(item) then
bag = armourBag
sort = item.components.armor:GetPercent()
-- Food
elseif itemIsFood(item) then
bag = foodBag
sort = isPlayerHurt and item.components.edible.healthvalue or item.components.edible.hungervalue
-- Light
elseif itemIsLight(item) then
bag = lightBag
sort = item.components.fueled:GetPercent()
-- If bag has more lights than maxLights, store the extras in miscBag.
if #lightBag.contents >= maxLights then
bag = miscBag
end
-- Priority resources
elseif itemIsResource(item) then
bag = resourceBag
-- Tools
elseif itemIsTool(item) then
bag = toolBag
sort = item.components.finiteuses:GetUses()
-- Weapons (MUST be below the tools block)
elseif itemIsWeapon(item) then
bag = weaponBag
sort = item.components.weapon.damage
end
table.insert(bag.contents, {
obj = item,
value = sort
})
end
-- Detach the item from the player's inventory.
inventory:RemoveItem(item, true)
end
--[[
"Oh you may not think I'm pretty,
But don't judge on what you see,
I'll eat myself if you can find
A smarter hat than me."
--]]
local sortingHat = {
lightBag,
toolBag,
weaponBag,
foodBag,
armourBag,
resourceBag,
miscBag,
}
-- Sort the categorised items.
for i = 1, #sortingHat do
local keys = {}
for key in pairs(sortingHat[i].contents) do
table.insert(keys, key)
end
-- keys contains the sorted order for the current bag (sortingHat[i]).
keys = sortItems(keys, sortingHat, i)
for _, key in ipairs(keys) do
local bagPreference = "inventory"
local itemObj = sortingHat[i].contents[key].obj
-- Has the player chosen to store this type of item in their backpack?
if backpack and sortingHat[i].type == backpackCategory then
bagPreference = "backpack"
end
-- Put the item in its sorted slot/container.
local slot, container = getNextAvailableInventorySlot(player, itemObj, bagPreference)
if slot ~= nil and container == inventory.equipslots then
-- You can't GiveItem to equipslots. So manually combine the itemObj with the equipped stack.
local eitem = inventory:GetEquippedItem(slot)
-- Combine the sorted itemstack with the equipped stack. Remainder is returned to itemObj.
itemObj = (eitem.components.stackable ~= nil) and eitem.components.stackable:Put(itemObj) or itemObj
if itemObj ~= nil and itemObj:IsValid() then
-- Giving remainder into inventory.
local slot, container = getNextAvailableInventorySlot(player, itemObj, bagPreference)
container:GiveItem(itemObj, slot, nil)
else
-- Should we do something with itemObj?
print "The sorted item was consumed by the equipslot"
end
else
container:GiveItem(itemObj, slot, nil)
end
end
end
end
--- Inventory must be sorted server-side, so listen for a RPC.
AddModRPCHandler(modname, "dsiRemoteSortInventory", function(player, modVersion, maxLights, backpackCategory)
sortInventory(player, maxLights, backpackCategory)
end)
--- Press "G" to sort your inventory.
GLOBAL.TheInput:AddKeyDownHandler(GetModConfigData("keybind"), function()
if not (GLOBAL.TheFrontEnd:GetActiveScreen() and
GLOBAL.TheFrontEnd:GetActiveScreen().name and
type(GLOBAL.TheFrontEnd:GetActiveScreen().name) == "string" and
GLOBAL.TheFrontEnd:GetActiveScreen().name == "HUD") then
return
end
local backpackCategory = GetModConfigData("backpackCategory")
local maxLights = GLOBAL.tonumber(GetModConfigData("maxLights"))
local modVersion = GLOBAL.KnownModIndex:GetModInfo(modname).version
-- Server-side
if GLOBAL.TheNet:GetIsServer() then
sortInventory(GLOBAL.ThePlayer, maxLights, backpackCategory)
-- Client-side
else
SendModRPCToServer(MOD_RPC[modname]["dsiRemoteSortInventory"], modVersion, maxLights, backpackCategory)
end
if GLOBAL.ThePlayer and GetModConfigData("funMode") == "yes" then
GLOBAL.ThePlayer.SoundEmitter:PlaySound("dontstarve/creatures/perd/gobble")
end
end)