Skip to content

Instantly share code, notes, and snippets.

@NintendoFan37
Forked from adkinss/RSWarehouse.lua
Last active June 23, 2023 21:33
Show Gist options
  • Save NintendoFan37/be33a0727bb49b9988b8c63c04410bcb to your computer and use it in GitHub Desktop.
Save NintendoFan37/be33a0727bb49b9988b8c63c04410bcb to your computer and use it in GitHub Desktop.
Minecolonies + ComputerCraft (CC:Tweaked) + Refined Storage + Advanced Peripherals script to automatically fulfill open work requests
-- RSWarehouse.lua
-- Author: Scott Adkins <adkinss@gmail.com> (Zucanthor)
-- Published: 2021-09-21
--
-- This program monitors work requests for the Minecolonies Warehouse and
-- tries to fulfill requests from the Refined Storage network. If the
-- RS network doesn't have enough items and a crafting pattern exists, a
-- crafting job is scheduled to restock the items in order to fulfill the
-- work request. The script will continuously loop, monitoring for new
-- requests and checking on crafting jobs to fulfill previous requests.
-- The following is required for setup:
-- * 1 ComputerCraft Computer
-- * 1 or more ComputerCraft Monitors (recommend 3x3 monitors)
-- * 1 Advanced Peripheral Colony Integrator
-- * 1 Advanced Peripheral RS Bridge
-- * 1 Chest or other storage container
-- Attach an RS Cable from the RS network to the RS Bridge. Connect the
-- storage container to the Minecolonies Warehouse Hut block. One idea is
-- to set up a second RS network attached to the Warehouse Hut using an
-- External Storage connector and then attach an Importer for that network
-- to the storage container.
-- THINGS YOU CAN CUSTOMIZE IN THIS PROGRAM:
-- Line 59: Specify the side storage container is at.
-- Line 66: Name of log file for storing JSON data of all open requests.
-- Lines 231+: Any items you find that should be manually provided.
-- Line 373: Time in seconds between work order scans.
----------------------------------------------------------------------------
-- INITIALIZATION
----------------------------------------------------------------------------
-- Initialize Monitor
-- A future update may allow for multiple monitors. This would allow one
-- monitor to be used for logging and another to be used for work requests.
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
local bridge = peripheral.find("rsBridge")
if not bridge then error("RS Bridge not found.") end
print("RS Bridge initialized.")
-- Initialize Colony Integrator
local colony = peripheral.find("colonyIntegrator")
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.")
-- Point to location of chest or storage container
-- A future update may autodetect where the storage container is and error
-- out if no storage container is found.
local storage = "left"
print("Storage initialized.")
-- Name of log file to capture JSON data from the open requests. The log can
-- be too big to edit within CC, which may require a "pastebin put" if you want
-- to look at it. Logging could be improved to only capture Skipped items,
-- which in turn will make log files smaller and edittable in CC directly.
local logFile = "RSWarehouse.log"
----------------------------------------------------------------------------
-- FUNCTIONS
----------------------------------------------------------------------------
-- Prints to the screen one row after another, scrolling the screen when
-- reaching the bottom. Acts as a normal display where text is printed in
-- a standard way. Long lines are not wrapped and newlines are printed as
-- spaces, both to be addressed in a future update.
-- NOTE: No longer used in this program.
function mPrintScrollable(mon, ...)
w, h = mon.getSize()
x, y = mon.getCursorPos()
-- Blink the cursor like a normal display.
mon.setCursorBlink(true)
-- For multiple strings, append them with a space between each.
for i = 2, #arg do t = t.." "..arg[i] end
mon.write(arg[1])
if y >= h then
mon.scroll(1)
mon.setCursorPos(1, y)
else
mon.setCursorPos(1, y+1)
end
end
-- Prints strings left, centered, or right justified at a specific row and
-- specific foreground/background color.
function mPrintRowJustified(mon, y, pos, text, ...)
w, h = mon.getSize()
fg = mon.getTextColor()
bg = mon.getBackgroundColor()
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
if #arg > 0 then mon.setTextColor(arg[1]) end
if #arg > 1 then mon.setBackgroundColor(arg[2]) end
mon.setCursorPos(x, y)
mon.write(text)
mon.setTextColor(fg)
mon.setBackgroundColor(bg)
end
-- Utility function that returns true if the provided character is a digit.
-- Yes, this is a hack and there are better ways to do this. Clearly.
function isdigit(c)
if c == "0" then return true end
if c == "1" then return true end
if c == "2" then return true end
if c == "3" then return true end
if c == "4" then return true end
if c == "5" then return true end
if c == "6" then return true end
if c == "7" then return true end
if c == "8" then return true end
if c == "9" then return true end
return false
end
-- Utility function that displays current time and remaining time on timer.
-- For time of day, yellow is day, orange is sunset/sunrise, and red is night.
-- The countdown timer is orange over 15s, yellow under 15s, and red under 5s.
-- At night, the countdown timer is red and shows PAUSED insted of a time.
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.orange
elseif now >= 6 and now < 18 then
cycle = "day"
cycle_color = colors.yellow
elseif now >= 18 and now < 19.5 then
cycle = "sunset"
cycle_color = colors.orange
elseif now >= 19.5 or now < 5 then
cycle = "night"
cycle_color = colors.red
end
timer_color = colors.orange
if t < 15 then timer_color = colors.yellow end
if t < 5 then timer_color = colors.red 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
-- Scan all open work requests from the Warehouse and attempt to satisfy those
-- requests. Display all activity on the monitor, including time of day and the
-- countdown timer before next scan. This function is not called at night to
-- save on some ticks, as the colonists are in bed anyways. Items in red mean
-- work order can't be satisfied by Refined Storage (lack of pattern or lack of
-- required crafting ingredients). Yellow means order partially filled and a
-- crafting job was scheduled for the rest. Green means order fully filled.
-- Blue means the Player needs to manually fill the work order. This includes
-- equipment (Tools of Class), NBT items like armor, weapons and tools, as well
-- as generic requests ike Compostables, Fuel, Food, Flowers, etc.
function scanWorkRequests(mon, rs, chest)
-- Before we do anything, prep the log file for this scan.
-- The log file is truncated each time this function is called.
file = fs.open(logFile, "w")
print("\nScan starting at", textutils.formatTime(os.time(), false) .. " (" .. os.time() ..").")
-- We want to keep three different lists so that they can be
-- displayed on the monitor in a more intelligent way. The first
-- list is for the Builder requests. The second list is for the
-- non-Builder requests. The third list is for any armor, tools
-- and weapons requested by the colonists.
builder_list = {}
nonbuilder_list = {}
equipment_list = {}
-- Scan RS for all items in its network. Ignore items with NBT data.
-- If a Builder needs any items with NBT data, this function will need
-- to be updated to not ignore those items.
items = rs.listItems()
item_array = {}
for index, item in ipairs(items) do
if not item.nbt then
item_array[item.name] = item.amount
end
end
-- Scan the Warehouse for all open work requests. For each item, try to
-- provide as much as possible from RS, then craft whatever is needed
-- after that. Green means item was provided entirely. Yellow means item
-- is being crafted. Red means item is missing crafting recipe.
workRequests = colony.getRequests()
file.write(textutils.serialize(workRequests, { allow_repetitions = true } ))
for w in pairs(workRequests) do
name = workRequests[w].name
item = workRequests[w].items[1].name
target = workRequests[w].target
desc = workRequests[w].desc
needed = workRequests[w].count
provided = 0
target_words = {}
target_length = 0
for word in target:gmatch("%S+") do
table.insert(target_words, word)
target_length = target_length + 1
end
if target_length >= 3 then target_name = target_words[target_length-2] .. " " .. target_words[target_length]
else target_name = target end
target_type = ""
target_count = 1
repeat
if target_type ~= "" then target_type = target_type .. " " end
target_type = target_type .. target_words[target_count]
target_count = target_count + 1
until target_count > target_length - 3
useRS = 1
if string.find(desc, "Tool of class") then useRS = 0 end
if string.find(name, "Hoe") then useRS = 0 end
if string.find(name, "Shovel") then useRS = 0 end
if string.find(name, "Axe") then useRS = 0 end
if string.find(name, "Pickaxe") then useRS = 0 end
if string.find(name, "Bow") then useRS = 0 end
if string.find(name, "Sword") then useRS = 0 end
if string.find(name, "Shield") then useRS = 0 end
if string.find(name, "Helmet") then useRS = 0 end
if string.find(name, "Leather Cap") then useRS = 0 end
if string.find(name, "Chestplate") then useRS = 0 end
if string.find(name, "Tunic") then useRS = 0 end
if string.find(name, "Pants") then useRS = 0 end
if string.find(name, "Leggings") then useRS = 0 end
if string.find(name, "Boots") then useRS = 0 end
if name == "Rallying Banner" then useRS = 0 end --bugged in alpha versions
if name == "Crafter" then useRS = 0 end
if name == "Compostable" then useRS = 0 end
if name == "Fertilizer" then useRS = 0 end
if name == "Flowers" then useRS = 0 end
if name == "Food" then useRS = 0 end
if name == "Fuel" then useRS = 0 end
if name == "Smeltable Ore" then useRS = 0 end
if name == "Stack List" then useRS = 0 end
color = colors.blue
if useRS == 1 then
if item_array[item] then
provided = rs.exportItemToPeripheral({name=item, count=needed}, chest)
end
color = colors.green
if provided < needed then
if rs.isItemCrafting( { name = item } ) then
color = colors.yellow
print("[Crafting]", item)
else
if rs.craftItem({name=item, count=needed}) then
color = colors.yellow
print("[Scheduled]", needed, "x", item)
else
color = colors.red
print("[Failed]", item)
end
end
end
else
nameString = name .. " [" .. target .. "]"
print("[Skipped]", nameString)
end
if string.find(desc, "of class") then
level = "Any Level"
if string.find(desc, "with maximal level:Leather") then level = "Leather" end
if string.find(desc, "with maximal level:Gold") then level = "Gold" end
if string.find(desc, "with maximal level:Chain") then level = "Chain" end
if string.find(desc, "with maximal level:Wood or Gold") then level = "Wood or Gold" end
if string.find(desc, "with maximal level:Stone") then level = "Stone" end
if string.find(desc, "with maximal level:Iron") then level = "Iron" end
if string.find(desc, "with maximal level:Diamond") then level = "Diamond" end
new_name = level .. " " .. name
if level == "Any Level" then new_name = name .. " of any level" end
new_target = target_type .. " " .. target_name
equipment = { name=new_name, target=new_target, needed=needed, provided=provided, color=color}
table.insert(equipment_list, equipment)
elseif string.find(target, "Builder") then
builder = { name=name, item=item, target=target_name, needed=needed, provided=provided, color=color }
table.insert(builder_list, builder)
else
new_target = target_type .. " " .. target_name
if target_length < 3 then
new_target = target
end
nonbuilder = { name=name, target=new_target, needed=needed, provided=provided, color=color }
table.insert(nonbuilder_list, nonbuilder)
end
end
-- Show the various lists on the attached monitor.
row = 3
mon.clear()
header_shown = 0
for e in pairs(equipment_list) do
equipment = equipment_list[e]
if header_shown == 0 then
mPrintRowJustified(mon, row, "center", "Equipment")
header_shown = 1
row = row + 1
end
text = string.format("%d %s", equipment.needed, equipment.name)
mPrintRowJustified(mon, row, "left", text, equipment.color)
mPrintRowJustified(mon, row, "right", " " .. equipment.target, equipment.color)
row = row + 1
end
header_shown = 0
for b in pairs(builder_list) do
builder = builder_list[b]
if header_shown == 0 then
if row > 1 then row = row + 1 end
mPrintRowJustified(mon, row, "center", "Builder Requests")
header_shown = 1
row = row + 1
end
text = string.format("%d/%s", builder.provided, builder.name)
mPrintRowJustified(mon, row, "left", text, builder.color)
mPrintRowJustified(mon, row, "right", " " .. builder.target, builder.color)
row = row + 1
end
header_shown = 0
for n in pairs(nonbuilder_list) do
nonbuilder = nonbuilder_list[n]
if header_shown == 0 then
if row > 1 then row = row + 1 end
mPrintRowJustified(mon, row, "center", "Nonbuilder Requests")
header_shown = 1
row = row + 1
end
text = string.format("%d %s", nonbuilder.needed, nonbuilder.name)
if isdigit(nonbuilder.name:sub(1,1)) then
text = string.format("%d/%s", nonbuilder.provided, nonbuilder.name)
end
mPrintRowJustified(mon, row, "left", text, nonbuilder.color)
mPrintRowJustified(mon, row, "right", " " .. nonbuilder.target, nonbuilder.color)
row = row + 1
end
if row == 3 then mPrintRowJustified(mon, row, "center", "No Open Requests") end
print("Scan completed at", textutils.formatTime(os.time(), false) .. " (" .. os.time() ..").")
file.close()
end
----------------------------------------------------------------------------
-- MAIN
----------------------------------------------------------------------------
-- Scan for requests periodically. This will catch any updates that were
-- triggered from the previous scan. Right-clicking on the monitor will
-- trigger an immediate scan and reset the timer. Unfortunately, there is
-- no way to capture left-clicks on the monitor.
local time_between_runs = 30
local current_run = time_between_runs
scanWorkRequests(monitor, bridge, storage)
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, storage)
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, storage)
current_run = time_between_runs
displayTimer(monitor, current_run)
TIMER = os.startTimer(1)
end
end
@NintendoFan37
Copy link
Author

not sure how this site fully works, so putting a changelog here

first update: updated line 203 so that it actually works in the latest version

@solareon
Copy link

solareon commented Apr 7, 2023

The line 203 change does fix it but when you attach it to very large RS networks it tends to barf. It can be removed without issue at least in my preliminary testing

@NintendoFan37
Copy link
Author

NintendoFan37 commented Apr 7, 2023

The line 203 change does fix it but when you attach it to very large RS networks it tends to barf. It can be removed without issue at least in my preliminary testing

Oh, I'm having that same issue! So, what are you saying to do? Becuase without the change I could not get it to work

Edit: to be more specific, the issue is that depite having the items in the RS system, they dont get exported. It can only export some items consistantly, but other times just fails to export anything

@solareon
Copy link

solareon commented Apr 7, 2023

It seems that anything that has to call for a craft it doesn't work. If you just remove like 203 all together it works well with larger RS networks. 203 is just a debug line really.

@NintendoFan37
Copy link
Author

It seems that anything that has to call for a craft it doesn't work. If you just remove like 203 all together it works well with larger RS networks. 203 is just a debug line really.

You're totally right - the line change was just to get the code working without it actually giving an error. The issue I'm having has nothing to do with it

@NintendoFan37
Copy link
Author

Added fix for latest version, courtosey of ericsfinck

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment