gelatine87 icon

RSWarehouse+BuildingStatus

gelatine87 | PRO | 05/09/23 05:46:55 AM UTC | 0 ⭐ | 1755 👁️ | Never ⏰ | []
Lua |

21.86 KB

|

Gaming

|

0 👍

/

0 👎

-- RSWarehouse.lua
-- Author: Scott Adkins <[email protected]> (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, arg.
-- 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 monitors = {}
 
for _, name in pairs(peripheral.getNames()) do
  if peripheral.getType(name) == "monitor" then
    table.insert(monitors, name)
  end
end
 
local function displayButtons(monitors)
  for i, name in ipairs(monitors) do
    local monitor = peripheral.wrap(name)
    monitor.setTextScale(0.5)
    monitor.setCursorPos(1, 1)
    monitor.write("Press the button to select this as your main Monitor:")
    monitor.setCursorPos(1, 3)
    monitor.write("[ SELECT ]")
  end
end
 
displayButtons(monitors)
 
local selectedMonitor = nil
 
while not selectedMonitor do
  local event, side, x, y = os.pullEvent("monitor_touch")
 
  for i, name in ipairs(monitors) do
    if side == name then
      selectedMonitor = name
      break
    end
  end
end
 
local selectedMonitor = peripheral.wrap(selectedMonitor)
selectedMonitor.setTextScale(0.5)
selectedMonitor.setCursorPos(1, 1)
selectedMonitor.clear()
selectedMonitor.write("Request Monitor")
 
local otherMonitor = nil
for i, name in ipairs(monitors) do
  if name ~= selectedMonitor then
    otherMonitor = name
    break
  end
end
 
otherMonitor = peripheral.wrap(otherMonitor)
otherMonitor.setTextScale(0.5)
otherMonitor.setCursorPos(1, 1)
otherMonitor.clear()
otherMonitor.write("Building Status Monitor")
 
-- 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"
if storage == nil then
  error("No storage container found.")
else
  print("Storage initialized.")
end
 
 
-- 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)
    return c >= "0" and c <= "9"
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(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
 
-- Utility function that displays the building status' for each building within the colony
function displayBuildingInformation(monitor)
    local buildings = colony.getBuildings()
  
    -- Clear the monitor and set the cursor position
    monitor.clear()
    monitor.setCursorPos(1, 1)
 
    -- Display the header "Building Status" in the center of the monitor
    local width, height = monitor.getSize()
    local header = "Building Status"
    local headerStart = math.floor(width / 2 - string.len(header) / 2 + 1)
    monitor.setCursorPos(headerStart, 1)
    monitor.write(header)
 
    --Create separate tables for each building in the list based on it's status
    local workingOn = {}
    local maxLevel = {}
    local notMaxed = {}
    for i, building in ipairs(buildings) do              
        if building.isWorkingOn then
            table.insert(workingOn, building)
        elseif building.level == building.maxLevel then
            table.insert(maxLevel, building)
        else
            table.insert(notMaxed, building)
        end
    end
        
    --Sort the "other buildings" list by level
    table.sort(notMaxed, function(a,b)
        return a.level < b.level
    end)
 
    -- Display the workingOn building information
    local y = 2
    for i, building in ipairs(workingOn) do
        local color = monitor.getTextColor()
        if building.isWorkingOn and building.maxLevel == building.level then
            monitor.setTextColor(colors.red)
        elseif building.isWorkingOn then
            monitor.setTextColor(colors.yellow)
        end
 
        local temp = string.gsub(building.name, "com.minecolonies.building.", "")
        local line = string.upper(string.sub(temp, 1, 1)) .. string.sub(temp, 2)
        local level = building.level
        if building.isWorkingOn and building.level ~= building.maxLevel then
            level = building.level .. " --> " .. (building.level + 1)
        elseif building.isWorkingOn and building.level == building.maxLevel then
            level = "Repairing"
        end
 
        local lineStart = 1
        local levelStart = width - string.len(tostring(level)) + 1
        monitor.setCursorPos(lineStart, y)
        monitor.write(line)
        monitor.setCursorPos(levelStart, y)
        monitor.write(level)
        monitor.setTextColor(color)
        y = y + 1
    end
 
    for i, building in ipairs(notMaxed) do
        local color = monitor.getTextColor()
        if building.level == 0 then
            monitor.setTextColor(colors.lime)
        elseif building.level == 1 then
            monitor.setTextColor(colors.lightGray)
        elseif building.level == 2 then
            monitor.setTextColor(colors.white)
        elseif building.level == 3 then
            monitor.setTextColor(colors.lightBlue)
        elseif building.level == 4 then
            monitor.setTextColor(colors.cyan)
        end
 
 
        local temp = string.gsub(building.name, "com.minecolonies.building.", "")
        local line = string.upper(string.sub(temp, 1, 1)) .. string.sub(temp, 2)
        local level = building.level
        local lineStart = 1
        local levelStart = width - string.len(tostring(level)) + 1
        monitor.setCursorPos(lineStart, y)
        monitor.write(line)
        monitor.setCursorPos(levelStart, y)
        monitor.write(level)
        y = y + 1
    end
 
    for i, building in ipairs(maxLevel) do
      if building.name ~= "com.minecolonies.building.stash" then  
        local color = monitor.getTextColor()
        monitor.setTextColor(colors.blue)
        local temp = string.gsub(building.name, "com.minecolonies.building.", "")
        local line = string.upper(string.sub(temp, 1, 1)) .. string.sub(temp, 2)
        local level = building.level
        local lineStart = 1
        local levelStart = width - string.len(tostring(level)) + 1
        monitor.setCursorPos(lineStart, y)
        monitor.write(line)
        monitor.setCursorPos(levelStart, y)
        monitor.write(level)
        y = y + 1
      end
    end
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 = 10
local current_run = time_between_runs
scanWorkRequests(selectedMonitor, bridge, storage)
displayTimer(selectedMonitor, current_run)
displayBuildingInformation(otherMonitor)
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(selectedMonitor, bridge, storage)
                displayBuildingInformation(otherMonitor)
                current_run = time_between_runs
            end
        end
        displayTimer(selectedMonitor, current_run)
        TIMER = os.startTimer(1)
    elseif e[1] == "monitor_touch" then
        os.cancelTimer(TIMER)
        scanWorkRequests(selectedMonitor, bridge, storage)
        displayBuildingInformation(otherMonitor)
        current_run = time_between_runs
        displayTimer(selectedMonitor, current_run)
        TIMER = os.startTimer(1)
    end
end
 

Comments