Created
December 19, 2022 18:39
-
-
Save Vazde/a90a233bc673fadef7254069ea58c38b to your computer and use it in GitHub Desktop.
Create: Above and Beyond - Alchemical laser automation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- 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