-- RSWarehouse.lua
-- Orignal Author: Chuck Burgess
-- updated by: Mathew Biltonen
-- Updated: 2025-10-17
local logFile = "RSWarehouse.log"
local time_between_runs = 30
-- Initialize Monitor
-- see: https://www.computercraft.info/wiki/Advanced_Monitor
local monitor = peripheral.find("monitor")
if not monitor then error("Monitor not found.") end
monitor.setTextScale(0.5)
monitor.clear()
monitor.setCursorPos(1, 1)
monitor.setCursorBlink(false)
print("Monitor initialized.")
-- Initialize RS Bridge
-- see: https://advancedperipherals.madefor.cc/peripherals/rs_bridge/
local bridge = peripheral.find("rs_bridge")
if not bridge then error("RS Bridge not found.") end
print("RS Bridge initialized.")
-- Initialize Colony Integrator
-- see: https://docs.advanced-peripherals.de/peripherals/colony_integrator/
local colony = peripheral.find("colony_integrator")
if not colony then error("Colony Integrator not found.") end
if not colony.isInColony then error("Colony Integrator is not in a colony.") end
print("Colony Integrator initialized.")
-- Establish the direction to transport the items into the Warehouse based on
-- where the entnglement block is sitting. Default to empty string.
local storage = peripheral.find("entangled:tile")
if not storage then error("Warehouse storage not found.") end
local direction = ""
local names = peripheral.getNames()
for _, pos in ipairs(names) do
if peripheral.getType(pos) == "entangled:tile" then
direction = pos
end
end
print("Warehouse storage initialized.")
----------------------------------------------------------------------------
-- FUNCTIONS
----------------------------------------------------------------------------
--[[
Table.Empty
@desc check to see if a table contains any data
@return boolean
]]
function table.empty (self)
for _, _ in pairs(self) do
return false
end
return true
end
--[[
Write To Log
@desc Write the specified `table` to the file surrounded by the `blockTop` and `blockBottom`
@return void
]]
function writeToLog(data, blockTop, blockBottom)
file.write("\n")
file.write(blockTop)
file.write("\n")
file.write(textutils.serialize(data, { allow_repetitions = true }))
file.write("\n")
file.write(blockBottom)
file.write("\n")
end
--[[
Process Work Request Item
@desc Determine if this item can be delivered to the warehouse from the storage
@return boolean
]]
function processWorkRequestItem(request)
if string.find(request.desc, "Tool of class") then return false end
if string.find(request.name, "Hoe") then return false end
if string.find(request.name, "Shovel") then return false end
if string.find(request.name, "Axe") then return false end
if string.find(request.name, "Pickaxe") then return false end
if string.find(request.name, "Bow") then return false end
if string.find(request.name, "Sword") then return false end
if string.find(request.name, "Shield") then return false end
if string.find(request.name, "Helmet") then return false end
if string.find(request.name, "Leather Cap") then return false end
if string.find(request.name, "Chestplate") then return false end
if string.find(request.name, "Tunic") then return false end
if string.find(request.name, "Pants") then return false end
if string.find(request.name, "Leggings") then return false end
if string.find(request.name, "Boots") then return false end
if request.name == "Rallying Banner" then return false end --bugged in alpha versions
if request.name == "Crafter" then return false end
if request.name == "Compostable" then return false end
if request.name == "Fertilizer" then return false end
if request.name == "Flowers" then return false end
if request.name == "Food" then return false end
if request.name == "Fuel" then return false end
if request.name == "Smeltable Ore" then return false end
if request.name == "Stack List" then return false end
-- you can add any new items here if they are found
return true
end
--[[
Monitor Print Row Justified
@desc Print a line of data to the in-game monitor
@return void
]]
function mPrintRowJustified(mon, y, pos, text, textcolor)
w, h = mon.getSize()
fg = colors.white
bg = colors.black
if pos == "left" then x = 1 end
if pos == "center" then x = math.floor((w - #text) / 2) end
if pos == "right" then x = w - #text end
mon.setTextColor(textcolor)
mon.setCursorPos(x, y)
mon.write(text)
mon.setTextColor(fg)
mon.setBackgroundColor(bg)
end
--[[
Display Timer
@desc Update the time on the monitor
@return void
]]
function displayTimer(mon, t)
now = os.time()
cycle = "day"
cycle_color = colors.orange
if now >= 4 and now < 6 then
cycle = "sunrise"
cycle_color = colors.yellow
elseif now >= 6 and now < 18 then
cycle = "day"
cycle_color = colors.lightBlue
elseif now >= 18 and now < 19.5 then
cycle = "sunset"
cycle_color = colors.magenta
elseif now >= 19.5 or now < 5 then
cycle = "night"
cycle_color = colors.red
end
timer_color = colors.green
if t < 15 then timer_color = colors.yellow end
if t < 5 then timer_color = colors.orange end
mPrintRowJustified(mon, 1, "left", string.format("Time: %s [%s] ", textutils.formatTime(now, false), cycle), cycle_color)
if cycle ~= "night" then
mPrintRowJustified(mon, 1, "right", string.format(" Remaining: %ss", t), timer_color)
else
mPrintRowJustified(mon, 1, "right", " Remaining: PAUSED", colors.red)
end
end
--[[
Create Colonist Data
@desc Build a table of Colonist making the request
@return table
]]
function createColonistData(colonist)
title_words = {}
words_in_name = 0
colonist_job = ""
word_count = 1
for word in colonist:gmatch("%S+") do
table.insert(title_words, word)
words_in_name = words_in_name + 1
end
if words_in_name >= 3 then colonist_name = title_words[words_in_name-2] .. " " .. title_words[words_in_name]
else colonist_name = colonist end
repeat
if colonist_job ~= "" then colonist_job = colonist_job .. " " end
colonist_job = colonist_job .. title_words[word_count]
word_count = word_count + 1
until word_count > words_in_name - 3
return { fullName = colonist, titleWords = title_words, job = colonist_job, name = colonist_name, wordsInName = words_in_name }
end
--[[
Get Work Request List (from colony)
@desc Build a table of the work request data from the colony
@return table
]]
function getWorkRequestList(colony)
requestList = {}
workRequests = colony.getRequests()
file = fs.open(logFile, "w")
for w in pairs(workRequests) do
writeToLog(workRequests[w], "--- Request start ---", "--- Request end ---");
name = workRequests[w].name -- the name of the count/item being requested
colonist = createColonistData(workRequests[w].target)
desc = workRequests[w].desc -- the request description
item = {}
-- create the filter item for the transfer request through the bridge
if workRequests[w].items and workRequests[w].items[1] then
if not workRequests[w].items[1].nbt or table.empty(workRequests[w].items[1].nbt) then
item = { name = workRequests[w].items[1].name, count = workRequests[w].count, displayName = workRequests[w].items[1].displayName}
else
item = { name = workRequests[w].items[1].name, count = workRequests[w].count, displayName = workRequests[w].items[1].displayName, nbt = workRequests[w].items[1].nbt}
end
end
-- how many items are needed to fulfill this request?
needed = workRequests[w].count
local newRecord = {}
newRecord.name = name
newRecord.desc = desc
newRecord.needed = needed
newRecord.item = item
newRecord.colonist = colonist
table.insert(requestList, newRecord)
writeToLog(newRecord, "--- Record start ---", "--- Record end ---");
end
file.close()
return requestList
end
--[[
Display List
@desc Update the monitor with the work request items currently in the system
@return void
]]
function displayList(mon, listName, itemList)
-- show the list header first
mPrintRowJustified(mon, row, "center", listName, colors.white)
row = row + 1
for e in pairs(itemList) do
record = itemList[e]
text = string.format("%d %s", record.provided , record.name)
mPrintRowJustified(mon, row, "left", text, record.color)
mPrintRowJustified(mon, row, "right", " " .. record.colonist, record.color)
row = row + 1
end
-- add a space at the end of the list
row = row + 1
end
-- Color References:
-- RED: work order can't be satisfied by Refined Storage (lack of pattern or lack of
-- required crafting ingredients).
-- YELLOW: order partially filled and a crafting job was scheduled for the rest.
-- GREEN: order fully filled.
-- BLUE: the Player needs to manually fill the work order. This includes some equipment as well as generic requests ike Compostables, Fuel, Food, Flowers, etc.
--[[
Scan Work Requests
@desc Manages all of the open work requests in the system and attempts to fulfill them from the inventory
@desc Not called at night (as determined by the server) since requests cannot be fulfilled anyway
@return void
]]
function scanWorkRequests(mon, bridge, direction)
print("\nScan starting at", textutils.formatTime(os.time(), false) .. " (" .. os.time() ..").")
builder_list = {}
nonbuilder_list = {}
equipment_list = {}
requestList = getWorkRequestList(colony)
for j, data in ipairs(requestList) do
color = colors.blue
provided = 0
if processWorkRequestItem(data) then
provided = bridge.exportItem(data.item, direction)
color = colors.lightGray
if provided >= data.needed then
color = colors.green
end
-- only handle the Non-NBT data items or empty nbt table item records
if provided < data.needed then
if bridge.isCrafting(data.item) then
color = colors.yellow
print("[Crafting]", data.name)
else
if bridge.craftItem(data.item) then
color = colors.yellow
print("[Scheduled]", data.item.count, "x", data.name)
else
color = colors.red
print("[Failed]", data.name)
end
end
end
else
nameString = data.name .. " [" .. data.colonist.fullName .. "]"
print("[Skipped]", nameString)
end
-- ---------------------------------------------------------------------
-- Build the newList data
-- ---------------------------------------------------------------------
-- create the target text
expectedList = "Builder"
colonist = data.colonist.name
if not string.find(data.colonist.fullName, "Builder") then
expectedList = ""
colonist = data.colonist.job .. " " .. data.colonist.name
if data.colonist.wordsInName < 3 then
colonist = data.colonist.name
end
end
-- create the name
listName = data.name
if string.find(data.desc, "level") then
expectedList = "Equipment"
level = "Any Level"
if string.find(data.desc, "with maximal level: Leather") then level = "Leather" end
if string.find(data.desc, "with maximal level: Gold") then level = "Gold" end
if string.find(data.desc, "with maximal level: Chain") then level = "Chain" end
if string.find(data.desc, "with maximal level: Wood or Gold") then level = "Wood or Gold" end
if string.find(data.desc, "with maximal level: Stone") then level = "Stone" end
if string.find(data.desc, "with maximal level: Iron") then level = "Iron" end
if string.find(data.desc, "with maximal level: Diamond") then level = "Diamond" end
listName = level .. " " .. data.name
if level == "Any Level" then listName = data.name .. " of any level" end
end
-- create the new list table defining what is inserted into a specific list
newList = { name=listName, colonist=colonist, needed=data.needed, provided=provided, color=color}
if expectedList == "Equipment" then
table.insert(equipment_list, newList)
elseif expectedList == "Builder" then
table.insert(builder_list, newList)
else
table.insert(nonbuilder_list, newList)
end
-- ---------------------------------------------------------------------
end
-- Show the various lists on the attached monitor.
mon.clear()
row = 3
if not table.empty(builder_list) then displayList(mon, "Builder Requests", builder_list) end
if not table.empty(nonbuilder_list) then displayList(mon, "Nonbuilder Requests", nonbuilder_list) end
if not table.empty(equipment_list) then displayList(mon, "Equipment", equipment_list) end
-- no requests
if row == 3 then
mPrintRowJustified(mon, row, "center", "No Open Requests", colors.white)
end
print("Scan completed at", textutils.formatTime(os.time(), false) .. " (" .. os.time() ..").")
end
--[[
MAIN
@desc establish the run times and execute the work request management
@return void
]]
local current_run = time_between_runs
scanWorkRequests(monitor, bridge, direction)
displayTimer(monitor, current_run)
local TIMER = os.startTimer(1)
while true do
local e = {os.pullEvent()}
if e[1] == "timer" and e[2] == TIMER then
now = os.time()
if now >= 5 and now < 19.5 then
current_run = current_run - 1
if current_run <= 0 then
scanWorkRequests(monitor, bridge, direction)
current_run = time_between_runs
end
end
displayTimer(monitor, current_run)
TIMER = os.startTimer(1)
elseif e[1] == "monitor_touch" then
os.cancelTimer(TIMER)
scanWorkRequests(monitor, bridge, direction)
current_run = time_between_runs
displayTimer(monitor, current_run)
TIMER = os.startTimer(1)
end
end