1
-- Armorlock by hugeblank, March 2023
2
-- A simple and extensible armor swapping software.
3
-- - Configurable via the "armorlock" namespace:
4
-- - usage: `set armorlock.elytra_id sc_goodies:white_elytra`
5
-- - storage inventory target (storage) - Required
6
-- - manipulator target (manipulator) - Required
7
-- - player target(username) - Required
8
-- - elytra item target (elytra_id) - Optional, defaults to "minecraft:elytra"
9
-- - off-ground duration before elytra swap triggers (air_ticks) - Optional, defaults to 8
10
-- - held item slot to lock elytra swapping (held_slot) - Optional, defaults to 1
11
-- - Builtin handling for elytra swapping and gold boots:
12
-- - Elytra:
13
-- - Swaps ON if off-ground for a configurable amount of ticks (air_ticks).
14
-- - Swaps OFF on contact with the ground, UNLESS holding shift
15
-- - Will NOT swap if selecting the first inventory slot
16
-- - Boots:
17
-- - Swaps ON when in the nether
18
-- - Swaps OFF when not in the nether
19
-- - Easy to extend with the createslotHandler function:
20
-- - Easily creates a way of tracking and toggling armor swapping
21
22
23
-- Feel free to modify to your need, but don't expect/assume I'll help in any specific circumstance :thumbs_up:
24
25
assert(chatbox, "Chatbox not connected")
26
27
local config = {
28
manipulator = settings.get("armorlock.manipulator"),
29
storage = settings.get("armorlock.storage"),
30
username = settings.get("armorlock.username"),
31
air_ticks = tonumber(settings.get("armorlock.air_ticks")) or 8,
32
held_slot = tonumber(settings.get("armorlock.held_slot")) or 1,
33
elytra_id = settings.get("armorlock.elytra_id") or "minecraft:elytra",
34
}
35
-- Just a big old block of assertions, don't mind it.
36
assert(config.manipulator, "manipulator id missing, set with: `set armorlock.manipulator <peripheral id>`")
37
assert(config.storage, "storage id missing, set with: `set armorlock.storage <peripheral id>`")
38
assert(peripheral.isPresent(config.manipulator), "Could not find manipulator "..tostring(config.manipulator))
39
assert(peripheral.isPresent(config.storage), "Could not find storage "..tostring(config.storage))
40
assert(tonumber(config.air_ticks), "air ticks must be a number")
41
config.air_ticks = tonumber(config.air_ticks)
42
assert(config.air_ticks > 0, "air ticks must be greater than 0")
43
assert(tonumber(config.held_slot), "held slot must be a number")
44
config.held_slot = tonumber(config.held_slot)
45
assert(config.held_slot > 0 and config.held_slot < 10, "held slot must be between 1 and 9 inclusive")
46
if not config.elytra_id:find("elytra") then
47
print("warn: armorlock.elytra_id does not contain elytra in the name, swapping may not function as intended")
48
end
49
50
local manipulator = peripheral.wrap(config.manipulator)
51
assert(manipulator.getMetaOwner, "Introspection Module & Entity sensor required for this program.")
52
53
local function swapItem(equipmentinv, condition, storageslot, equipmentslot)
54
assert(storageslot%2 == 1, "Storage slot must be odd!")
55
if condition then
56
equipmentinv.pushItems(config.storage, equipmentslot, 1, storageslot)
57
equipmentinv.pullItems(config.storage, storageslot+1, 1, equipmentslot)
58
else
59
equipmentinv.pushItems(config.storage, equipmentslot, 1, storageslot+1)
60
equipmentinv.pullItems(config.storage, storageslot, 1, equipmentslot)
61
end
62
end
63
64
-- Autoincrement value to control inventory spaces.
65
-- Make sure you're providing an inventory with enough spaces for the amount of slotHandlers you need!
66
-- floor(size/2) = max amount of slotHandlers.
67
local slotoffset = 1
68
69
-- Whether swapping is locked. Slot handlers should respect this value.
70
local lock = false
71
72
-- Handler functions
73
local handlers = {}
74
75
--- Creates a pair of functions to get and toggle the state of the armor in the players inventory
76
--- Passes these functions into the handler function when initially invoked
77
--- @param equipment function[] the equipment pseudo-peripheral provided by the manipulator
78
--- @param equipmentslot int the slot number of the equipment to be swapped. See the comment on the line that defines the equipment variable for the mapping.
79
--- @param name string the MC identifier of the item to be worn in the "active" state, where the condition is being met.
80
--- @param handler function The handler for this equipment slot
81
local function createSlotHandler(equipment, equipmentslot, name, handler)
82
local storageslot = slotoffset
83
slotoffset = slotoffset + 2
84
local list = equipment.list()
85
local condition = list[equipmentslot] and list[equipmentslot].name == name
86
local getState, toggle = function()
87
return condition
88
end, function()
89
swapItem(equipment, condition, storageslot, equipmentslot)
90
condition = not condition
91
return condition
92
end
93
handlers[#handlers+1] = {
94
getState,
95
toggle,
96
function()
97
handler(getState, toggle)
98
end,
99
}
100
end
101
102
while true do
103
local ok, err = pcall(function()
104
-- Reset values
105
slotoffset, handlers, lock = 1, {}, false
106
local equipment = manipulator.getEquipment() -- {mainhand, offhand, boots, leggings, chest, helmet} <- slot ID to use for each armor slot
107
local list = equipment.list()
108
109
createSlotHandler(equipment, 5, config.elytra_id, function(isWearingElytra, swap) -- Elytra
110
local air_ticks = 0
111
while true do
112
if lock then
113
os.pullEvent("armor_lock")
114
end
115
local meta = manipulator.getMetaOwner()
116
if meta.isAirborne then
117
air_ticks = air_ticks+1
118
else
119
air_ticks = 0
120
end
121
local wearingElytra = isWearingElytra()
122
if (not wearingElytra and meta.heldItemSlot ~= config.held_slot and (air_ticks == config.air_ticks or meta.isSwimming)) or (wearingElytra and not (meta.isAirborne or meta.isSwimming or meta.isSneaking) and not meta.isElytraFlying) then
123
swap()
124
end
125
end
126
end)
127
128
-- Change 3 to 6, and "minecraft:gold_boots" to "minecraft:gold_helmet" if you want to swap the helmet instead
129
createSlotHandler(equipment, 3, "minecraft:gold_boots", function(isWearingGold, swap) -- Boots
130
while true do
131
if lock then
132
os.pullEvent("armor_lock")
133
end
134
local _, user, origin, destination = os.pullEvent("world_change")
135
if user == config.username then
136
local wearingGold = isWearingGold()
137
if (destination == "minecraft:the_nether" and not wearingGold) or (origin == "minecraft:the_nether" and wearingGold) then
138
swap()
139
end
140
end
141
end
142
end)
143
-- Add your own swap handler methods here. Handle swap conditions by passing a function into the waitForAny method below.
144
145
chatbox.tell(config.username, "&aactive", "armorlock", nil, "format")
146
147
local threads = {} -- Break out the handler functions to be thrown into parallel
148
for _, functions in ipairs(handlers) do
149
threads[#threads+1] = functions[3]
150
end
151
parallel.waitForAny(
152
function() -- Disconnect handler
153
while true do
154
local _, user = os.pullEvent("leave")
155
if user == config.username then
156
return
157
end
158
end
159
end,
160
function() -- Armor lock toggle handler
161
while true do
162
local _, user, command, args, data = os.pullEvent("command")
163
if user == config.username and command == "armorlock" then
164
lock = not lock
165
os.queueEvent("armor_lock")
166
for _, functions in ipairs(handlers) do
167
if functions[1]() ~= lock then
168
functions[2]()
169
end
170
end
171
end
172
end
173
end,
174
table.unpack(threads)
175
)
176
end) -- The above functions have a high potential of erroring on player death or disconnect
177
if not ok then
178
printError(err)
179
-- If you want to get a notification in chat of every error, uncomment the following line:
180
-- chatbox.tell(config.username, "Error:\n&c"..tostring(err), "armorlock", nil, "format")
181
end
182
-- Check the playerlist first to make sure the player is still online (probably dead)
183
local list = chatbox.getPlayerList()
184
for i, v in ipairs(list) do
185
list[v] = i
186
end
187
if not list[config.username] then -- If they're not online, wait for them to come back.
188
while true do
189
local _, user = os.pullEvent("join")
190
if user == config.username then
191
break
192
end
193
end
194
else -- If they're presumably dead, wait a second before running again.
195
sleep(1)
196
end
197
handlers = {}
198
end
199