Skip to content

Instantly share code, notes, and snippets.

@Vazde
Created December 19, 2022 18:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Vazde/a90a233bc673fadef7254069ea58c38b to your computer and use it in GitHub Desktop.
Save Vazde/a90a233bc673fadef7254069ea58c38b to your computer and use it in GitHub Desktop.
Create: Above and Beyond - Alchemical laser automation
-- Program to help in automating Alchemical Laser
-- in Minecraft modpack Create: Above and Beyond
-- There's probably an easier way, but this is mine.
-- Start reading from doLogic(), the loop is relatively simple:
-- * Unload minecart to storage/charger
-- * Load charged charge item + payload
-- * Laser
-- Reboot-safe; should be able to recover the state of the minecart at boot
-- In the game world this requires a circular track:
-- /X\ X = Laser
-- D U D = Detector, U = More Minecarts Minecart Unloader
-- \L/ L = More Minecarts Minecart Loader
-- XUL should all have Create Controller Rail on them, pointing clockwise.
-- The rails and the un/loaders+storage should be wired to
-- ComputerCraft: Tweaked computer, either via bundled redstone cable
-- or network cable for items.
-- See the initialization for what is expected and on what colors.
-- Additionally, the Detector rail should be connected to Project Red
-- RS Latch (called "sense" here). This way the computer is able to
-- asynchrously read the minecart running over the detector rail.
-- Lastly, place the one required Hopper Minecart on any of the powered rails.
local tArgs = { ... }
-- Are we producing Basalz Rods or Blizz Cubes?
-- * Input is either Basalt or Snowballs
if tArgs[1] == "b" then
isBasal = true
elseif tArgs[1] == "s" then
isBasal = false
else
error("Argument must be either b for basalz, or s for snowballs")
end
if isBasal then
-- Inventories for specific platforms and central storage
invLoader = peripheral.wrap("moreminecarts:minecart_loader_te_2")
invUnloader = peripheral.wrap("moreminecarts:minecart_unloader_te_2")
invCharger = peripheral.wrap("thermal:charge_bench_0")
invStorage = peripheral.wrap("storagedrawers:controller_3")
-- Could use the storage directly, but this way we can be sure of the slot
invInput = peripheral.wrap("storagedrawers:standard_drawers_1_1")
else
invLoader = peripheral.wrap("moreminecarts:minecart_loader_te_3")
invUnloader = peripheral.wrap("moreminecarts:minecart_unloader_te_3")
invCharger = peripheral.wrap("thermal:charge_bench_1")
invStorage = peripheral.wrap("storagedrawers:controller_4")
invInput = peripheral.wrap("storagedrawers:standard_drawers_1_2")
end
-- Rails on specific platforms
local colorSenseRead = colors.lightGray
local colorSenseReset = colors.white
local colorUnloader = colors.red
local colorLoader = colors.blue
local colorLaser = colors.green
-- Optional clutch for deployer (with not-gate)
local colorClutch = colors.pink
-- Check that all inventories have been installed
if invLoader == nil then error("Minecart Loader is missing") end
if invUnloader == nil then error("Minecart Unloader is missing") end
if invCharger == nil then error("Energetic Infuser is missing") end
if invStorage == nil then error("Storage is missing") end
if invInput == nil then error("Input is missing") end
function coalesce(val, fallback)
if val ~= nil then return val end
if fallback ~= nil then return fallback end
return "nil"
end
function pulseOutput(color)
rs.setBundledOutput("back", color)
os.sleep(0.6)
rs.setBundledOutput("back", 0)
end
function reportStatus(code, extra)
if extra == nil then
print(coalesce(code, "INVALID"))
else
print(coalesce(code, "INVALID") .. ": " .. tostring(extra))
end
end
-- Status reporting enums in case we want to send these to Prometheus / Postgres
S = {
reset = {
begin = "reset.begin",
progress = "reset.progress",
complete = "reset.complete",
fail = "reset.fail"
},
unload = {
begin = "unload.begin",
wait = "unload.wait",
complete = "unload.complete"
},
load = {
chargeableBegin = "load.chargeableBegin",
chargeableWait = "load.chargeableWait",
chargeableComplete = "load.chargeableComplete",
payloadBegin = "load.payloadBegin",
payloadSlotError = "load.payloadSlotError",
payloadComplete = "load.payloadComplete",
loaderWait = "load.loaderWait",
complete = "load.complete"
},
laser = {
begin = "laser.begin",
complete = "laser.complete"
}
}
function isItemChargeable(item)
if item.name == "thermal:flux_magnet" then return true end
if item.name == "appliedenergistics2:entropy_manipulator" then return true end
return false
end
-- Keeps pushing item stack until it's empty
function forcePush(from, fromSlot, to)
while true do
from.pushItems(peripheral.getName(to), fromSlot)
if from.getItemDetail(fromSlot) == nil then
return
end
-- Storage is full
-- TODO: report progress wait?
os.sleep(1)
end
end
function emptyUnloaderOnce()
-- Iterate backwards, as that way the
-- inventory contents are more stable.
for i = invUnloader.size(), 1, -1 do
local item = invUnloader.getItemDetail(i)
if item ~= nil then
if isItemChargeable(item) then
forcePush(invUnloader, i, invCharger)
else
forcePush(invUnloader, i, invStorage)
end
end
end
end
function isEmpty(inv)
local items = inv.list()
for i = 1, inv.size() do
if items[i] ~= nil then
return false
end
end
return true
end
-- Really make sure the unloader is empty (and hence the minecart)
function emptyUnloader()
while true do
-- Move out stuff from unloader
emptyUnloaderOnce()
reportStatus(S.unload.wait)
os.sleep(2)
if isEmpty(invUnloader) then
return
end
end
end
function doUnloader()
reportStatus(S.unload.begin)
emptyUnloader()
reportStatus(S.unload.complete)
pulseOutput(colorUnloader)
end
function waitUntilEmpty(inv)
while true do
os.sleep(2)
if isEmpty(invLoader) then
return
end
end
end
function loadChargeableOnce()
local items = invCharger.list()
for i = 1, invCharger.size() do
local item = invCharger.getItemDetail(i)
if item ~= nil then
if item.durability == 0 then
forcePush(invCharger, i, invLoader)
return true
end
end
end
return false
end
function loadChargeable()
reportStatus(S.load.chargeableBegin)
while loadChargeableOnce() ~= true do
reportStatus(S.load.chargeableWait)
os.sleep(2)
end
reportStatus(S.load.chargeableComplete)
end
function findPayloadSlot()
local name = isBasal and "minecraft:basalt" or "minecraft:snowball"
local items = invStorage.list()
for i = 1, invStorage.size() do
if items[i] ~= nil and items[i].name == name then
return i
end
end
reportStatus(S.load.payloadSlotError)
error("Can't find payload slot")
end
function loadPayload()
reportStatus(S.load.payloadBegin)
-- This is unable to find a slot it there's 0 items in the drawer.
--local payloadStorageSlot = findPayloadSlot()
local payloadCount = 0
for i = 2, 5 do
--local pushed = invStorage.pushItems(peripheral.getName(invLoader), payloadStorageSlot, 64, i)
local pushed = invInput.pushItems(peripheral.getName(invLoader), 2, 64, i)
payloadCount = payloadCount + pushed
end
reportStatus(S.load.payloadComplete, tostring(payloadCount))
end
function doLoader()
loadChargeable()
loadPayload()
reportStatus(S.load.loaderWait)
waitUntilEmpty(invLoader)
reportStatus(S.load.complete)
pulseOutput(colorLoader)
end
function doLaser()
reportStatus(S.laser.begin)
rs.setBundledOutput("back", colorClutch)
os.sleep(5)
rs.setBundledOutput("back", 0)
reportStatus(S.laser.complete)
pulseOutput(colorLaser)
end
function xor(a,b) return not a ~= not b end
function readSense()
-- Latch is not symmetric, its state must be flipped on the other build in my case
return xor(colors.test(rs.getBundledInput("back"), colorSenseRead), not isBasal)
end
function moveTrainToUnloader()
-- Drive the train until sense is triggered;
-- now we know the train is at unloader.
local allRails = colors.combine(colorUnloader, colorLoader, colorLaser)
rs.setBundledOutput("back", allRails)
local railsToTry = {colorLaser, colorLoader, colorUnloader}
-- TODO: for loops
reportStatus(S.reset.progress, 0)
pulseOutput(railsToTry[1])
os.sleep(1)
if readSense() then
return
end
reportStatus(S.reset.progress, 1)
pulseOutput(railsToTry[2])
os.sleep(1)
pulseOutput(railsToTry[1])
os.sleep(1)
if readSense() then
return
end
reportStatus(S.reset.progress, 2)
pulseOutput(railsToTry[3])
os.sleep(1)
pulseOutput(railsToTry[2])
os.sleep(1)
pulseOutput(railsToTry[1])
os.sleep(1)
if readSense() then
return
end
reportStatus(S.reset.fail)
error("Couldn't reset train")
end
function doReset()
reportStatus(S.reset.begin)
-- Ensure the latch is reset even if the train was moving
rs.setBundledOutput("back", 0)
os.sleep(1)
rs.setBundledOutput("back", 0)
-- Reset sense latch
pulseOutput(colorSenseReset)
moveTrainToUnloader()
-- And finally, reset the latch the train just triggered
reportStatus(S.reset.complete)
pulseOutput(colorSenseReset)
return true
end
function doLogic(index)
doUnloader()
doLoader()
doLaser()
-- Optional check to ensure that we still have an intact minecart
-- It would be strange if we didn't. Plus the execution is somewhat
-- synchronized due to the inventory things, so we are quite certain
-- the train is where it should be.
--waitForLatch() -- TODO
end
function doMain()
-- Get our bearings after boot
doReset()
while true do
doLogic()
print(os.clock())
os.sleep(5)
end
end
while true do
print("Program running")
local status, err = pcall(doMain)
print("ERROR:" .. coalesce(err))
os.sleep(5)
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment