diff --git a/MEWarehouse-Update8.lua b/MEWarehouse-Update8.lua new file mode 100644 index 0000000..4255204 --- /dev/null +++ b/MEWarehouse-Update8.lua @@ -0,0 +1,405 @@ +-- MEWarehouse.lua +-- Author: Scott Adkins (Zucanthor) +-- Edited by Gaiabot(in progress) +-- Published: 2021-09-21 +-- + +-- currently changing stuff over to ME bridge for me and my friends + +-- This program monitors work requests for the Minecolonies Warehouse and +-- tries to fulfill requests from the Refined Storage network. If the +-- ME 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 ME Cable from the ME network to the ME 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 63: Specify the side storage container is at. +-- Line 70: Name of log file for storing JSON data of all open requests. +-- Lines 235+: Any items you find that should be manually provided. +-- Line 377: 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("me_bridge") +if not bridge then error("ME Bridge not found.") end +print("ME Bridge initialized.") + +-- Initialize 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.") + +-- 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 = "right" +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 = "mewarehouse.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, me, 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. + filters = {} + items = me.getItems(filters) + + item_array = {} + for _, item in ipairs(items) do + if next(item.components) == nil then + item_array[item.name] = item.count + 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 + + useME = 1 + if string.find(desc, "Tool of class") then useME = 0 end + if string.find(name, "Hoe") then useME = 0 end + if string.find(name, "Shovel") then useME = 0 end + if string.find(name, "Axe") then useME = 0 end + if string.find(name, "Pickaxe") then useME = 0 end + if string.find(name, "Bow") then useME = 0 end + if string.find(name, "Sword") then useME = 0 end + if string.find(name, "Shield") then useME = 0 end + if string.find(name, "Helmet") then useME = 0 end + if string.find(name, "Leather Cap") then useME = 0 end + if string.find(name, "Chestplate") then useME = 0 end + if string.find(name, "Tunic") then useME = 0 end + if string.find(name, "Pants") then useME = 0 end + if string.find(name, "Leggings") then useME = 0 end + if string.find(name, "Boots") then useME = 0 end + if name == "Rallying Banner" then useME = 0 end --bugged in alpha versions + if name == "Crafter" then useME = 0 end + if name == "Compostable" then useME = 0 end + if name == "Fertilizer" then useME = 0 end + if name == "Flowers" then useME = 0 end + if name == "Food" then useME = 0 end + if name == "Fuel" then useME = 0 end + if name == "Smeltable Ore" then useME = 0 end + if name == "Stack List" then useME = 0 end + + color = colors.blue + if useME == 1 then + if item_array[item] then + provided = me.exportItem({name=item, count=needed}, chest) + end + + color = colors.green + if provided < needed then + -- only call isItemCrafting if it exists + if me.isCrafting and me.isCrafting{ name=item } then + color = colors.yellow + print("[Crafting]", item) + -- otherwise try to craft, if that function exists + elseif me.craftItem and me.craftItem({ name=item, count=needed }) then + color = colors.yellow + print("[Scheduled]", needed, "x", item) + else + color = colors.red + print("[Failed]", item) + 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 = 5 +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 \ No newline at end of file