diff --git a/config.lua b/config.lua index e03ad86..7120243 100644 --- a/config.lua +++ b/config.lua @@ -7,6 +7,7 @@ pollFrequency = 30, categoryCycleFrequency = -1, activityTimeout = 60, + dropDirection = "forward" }, theme = { formatting = { @@ -84,8 +85,14 @@ }, peripherals = { monitor = nil, + self = "west", -- Cardinal direction or network name of the turtle + -- relative to storage chest(s) used + -- only required if output chest is "self" + -- or a relative position + -- ex: "west", "turtle_1" + selfRelativeOutput = nil, -- Same as above but for output chest exchangeChest = nil, - outputChest = "minecraft:chest_3", + outputChest = "self", -- Chest peripheral or self }, exchange = { enabled = true, diff --git a/core/ConfigValidator.lua b/core/ConfigValidator.lua index e793c1a..e0805c3 100644 --- a/core/ConfigValidator.lua +++ b/core/ConfigValidator.lua @@ -9,6 +9,7 @@ pollFrequency = "number", categoryCycleFrequency = "number", activityTimeout = "number", + dropDirection = "enum<'forward' | 'up' | 'down' | 'north' | 'south' | 'east' | 'west'>: direction" }, theme = { formatting = { @@ -75,8 +76,10 @@ }, peripherals = { monitor = "string?", - exchangeChest = "networked_chest?", - outputChest = "networked_chest", + self = "string?", + selfRelativeOutput = "string?", + exchangeChest = "chest?", + outputChest = "chest", }, exchange = { enabled = "boolean", @@ -167,26 +170,31 @@ error("Config value " .. subpath .. " must be a color") end end - if v == "networked_chest" then + if v == "chest" then if type(config[k]) ~= "string" then error("Config value " .. subpath .. " must be a networked chest") end - if config[k] == "left" or config[k] == "right" or config[k] == "front" or config[k] == "back" or config[k] == "top" or config[k] == "bottom" then + if not turtle and (config[k] == "left" or config[k] == "right" or config[k] == "front" or config[k] == "back" or config[k] == "top" or config[k] == "bottom") then error("Config value " .. subpath .. " must not be a relative position") end - local chestMethods = peripheral.getMethods(config[k]) - if not config[k] then - error("Config value " .. subpath .. " must refer to a valid peripheral") + if not turtle and config[k] == "self" then + error("Config value " .. subpath .. " can only be self for turtles") end - local hasDropMethod = false - for i = 1, #chestMethods do - if chestMethods[i] == "drop" then - hasDropMethod = true - break + if config[k] ~= "self" then + local chestMethods = peripheral.getMethods(config[k]) + if not chestMethods then + error("Config value " .. subpath .. " must refer to a valid peripheral") end - end - if not hasDropMethod then - error("Config value " .. subpath .. " must refer to a peripheral with an inventory") + local hasDropMethod = false + for i = 1, #chestMethods do + if chestMethods[i] == "drop" then + hasDropMethod = true + break + end + end + if not hasDropMethod then + error("Config value " .. subpath .. " must refer to a peripheral with an inventory") + end end end if v == "boolean" and type(config[k]) ~= "boolean" then diff --git a/core/ShopState.lua b/core/ShopState.lua index 080f1b2..e966896 100644 --- a/core/ShopState.lua +++ b/core/ShopState.lua @@ -57,6 +57,10 @@ return true end +local function isRelative(name) + return name == "top" or name == "bottom" or name == "left" or name == "right" or name == "front" or name == "back" +end + local function refund(currency, address, meta, value, message, error) message = message or "Here is your refund!" local returnTo = address @@ -72,6 +76,68 @@ end end +local function handlePurchase(transaction, meta, sentMetaname, transactionCurrency, transactionCurrency, state) + local purchasedProduct = nil + for _, product in ipairs(state.products) do + if product.address:lower() == sentMetaname:lower() then + purchasedProduct = product + break + end + end + if purchasedProduct then + local productPrice = Pricing.getProductPrice(purchasedProduct, transactionCurrency) + local amountPurchased = math.floor(transaction.value / productPrice) + if amountPurchased > 0 then + if purchasedProduct.quantity and purchasedProduct.quantity > 0 then + local productSources, available = ScanInventory.findProductItems(state.products, purchasedProduct, amountPurchased) + local refundAmount = transaction.value - (available * productPrice) + print("Purchased " .. available .. " of " .. purchasedProduct.name .. " for " .. transaction.from .. " for " .. transaction.value .. " " .. transactionCurrency.name .. " (refund " .. refundAmount .. ")") + if available > 0 then + for _, productSource in ipairs(productSources) do + if isRelative(state.config.peripherals.outputChest) then + -- Move to self first + if not turtle then + error("Relative output but not a turtle!") + end + peripheral.call(productSource.inventory, "pushItems", state.config.peripherals.self, productSource.slot, productSource.amount, 1) + peripheral.call(state.config.peripherals.outputChest, "pullItems", state.config.peripherals.selfRelativeOutput, productSource.slot, productSource.amount, 1) + peripheral.call(state.config.peripherals.outputChest, "drop", 1, productSource.amount, state.config.settings.dropDirection) + elseif state.config.peripherals.outputChest == "self" then + if not turtle then + error("Self output but not a turtle!") + end + peripheral.call(productSource.inventory, "pushItems", state.config.peripherals.self, productSource.slot, productSource.amount, 1) + if state.config.settings.dropDirection == "forward" then + turtle.drop(productSource.amount) + elseif state.config.settings.dropDirection == "up" then + turtle.dropUp(productSource.amount) + elseif state.config.settings.dropDirection == "down" then + turtle.dropDown(productSource.amount) + else + error("Invalid drop direction: " .. state.config.settings.dropDirection) + end + else + peripheral.call(productSource.inventory, "pushItems", state.config.peripherals.outputChest, productSource.slot, productSource.amount, 1) + peripheral.call(state.config.peripherals.outputChest, "drop", 1, productSource.amount, state.config.settings.dropDirection) + end + end + if refundAmount > 0 then + refund(transactionCurrency, transaction.from, meta, refundAmount, "Here is the funds remaining after your purchase!") + end + else + refund(transactionCurrency, transaction.from, meta, transaction.value, "Sorry, that item is out of stock!") + end + else + refund(transactionCurrency, transaction.from, meta, transaction.value, "Sorry, that item is out of stock!") + end + else + refund(transactionCurrency, transaction.from, meta, transaction.value, "You must purchase at least one of this product!", true) + end + else + refund(transactionCurrency, transaction.from, meta, transaction.value, "Must supply a valid product to purchase!", true) + end +end + -- Anytime the shop state is resumed, animation should be finished instantly. (call animation finish hooks) ---@param state ShopState local function runShop(state) @@ -118,40 +184,12 @@ if sentName and sentName:lower() == transactionCurrency.name:lower() then local meta = parseMeta(transaction.metadata) if sentMetaname then - local purchasedProduct = nil - for _, product in ipairs(state.products) do - if product.address:lower() == sentMetaname:lower() then - purchasedProduct = product - break - end - end - if purchasedProduct then - local productPrice = Pricing.getProductPrice(purchasedProduct, transactionCurrency) - local amountPurchased = math.floor(transaction.value / productPrice) - if amountPurchased > 0 then - if purchasedProduct.quantity and purchasedProduct.quantity > 0 then - local productSources, available = ScanInventory.findProductItems(state.products, purchasedProduct, amountPurchased) - local refundAmount = transaction.value - (available * productPrice) - print("Purchased " .. available .. " of " .. purchasedProduct.name .. " for " .. transaction.from .. " for " .. transaction.value .. " " .. transactionCurrency.name .. " (refund " .. refundAmount .. ")") - if available > 0 then - for _, productSource in ipairs(productSources) do - peripheral.call(productSource.inventory, "pushItems", state.config.peripherals.outputChest, productSource.slot, productSource.amount, 1) - peripheral.call(state.config.peripherals.outputChest, "drop", 1, productSource.amount, "up") - end - if refundAmount > 0 then - refund(transactionCurrency, transaction.from, meta, refundAmount, "Here is the funds remaining after your purchase!") - end - else - refund(transactionCurrency, transaction.from, meta, transaction.value, "Sorry, that item is out of stock!") - end - else - refund(transactionCurrency, transaction.from, meta, transaction.value, "Sorry, that item is out of stock!") - end - else - refund(transactionCurrency, transaction.from, meta, transaction.value, "You must purchase at least one of this product!", true) - end + success, err = pcall(handlePurchase, transaction, meta, sentMetaname, transactionCurrency, transactionCurrency, state) + if success then + -- Success :D else - refund(transactionCurrency, transaction.from, meta, transaction.value, "Must supply a valid product to purchase!", true) + refund(transactionCurrency, transaction.from, meta, transaction.value, "An error occurred while processing your purchase!", true) + error(err) end else refund(transactionCurrency, transaction.from, meta, transaction.value, "Must supply a product to purchase!", true) diff --git a/gitget b/gitget new file mode 100644 index 0000000..7846b5c --- /dev/null +++ b/gitget @@ -0,0 +1,135 @@ +--[[ /gitget +GitHub downloading utility for CC. +Developed by apemanzilla. + +This requires ElvishJerricco's JSON parsing API. +Direct link: http://pastebin.com/raw.php?i=4nRg9CHU +]]-- + +-- Edit these variables to use preset mode. +-- Whether to download the files asynchronously (huge speed benefits, will also retry failed files) +-- If false will download the files one by one and use the old output (List each file name as it's downloaded) instead of the progress bar +local async = true + +-- Whether to write to the terminal as files are downloaded +-- Note that unless checked for this will not affect pre-set start/done code below +local silent = false + +local preset = { + -- The GitHub account name + user = nil, + -- The GitHub repository name + repo = nil, + + -- The branch or commit tree to download (defaults to 'master') + branch = nil, + + -- The local folder to save all the files to (defaults to '/') + path = nil, + + -- Function to run before starting the download + start = function() + if not silent then print("Downloading files from GitHub...") end + end, + + -- Function to run when the download completes + done = function() + if not silent then print("Done") end + end +} + +-- Leave the rest of the program alone. +local args = {...} + +args[1] = preset.user or args[1] +args[2] = preset.repo or args[2] +args[3] = preset.branch or args[3] or "master" +args[4] = preset.path or args[4] or "" + +if #args < 2 then + print("Usage:\n"..((shell and shell.getRunningProgram()) or "gitget").." [branch/tree] [path]") error() +end + +local function save(data,file) + local file = shell.resolve(file:gsub("%%20"," ")) + if not (fs.exists(string.sub(file,1,#file - #fs.getName(file))) and fs.isDir(string.sub(file,1,#file - #fs.getName(file)))) then + if fs.exists(string.sub(file,1,#file - #fs.getName(file))) then fs.delete(string.sub(file,1,#file - #fs.getName(file))) end + fs.makeDir(string.sub(file,1,#file - #fs.getName(file))) + end + local f = fs.open(file,"w") + f.write(data) + f.close() +end + +local function download(url, file) + save(http.get(url).readAll(),file) +end + +if not json then + download("http://pastebin.com/raw.php?i=4nRg9CHU","json") + os.loadAPI("json") +end + +preset.start() +local data = json.decode(http.get("https://api.github.com/repos/"..args[1].."/"..args[2].."/git/trees/"..args[3].."?recursive=1").readAll()) +if data.message and data.message:find("API rate limit exceeded") then error("Out of API calls, try again later") end +if data.message and data.message == "Not found" then error("Invalid repository",2) else + for k,v in pairs(data.tree) do + -- Make directories + if v.type == "tree" then + fs.makeDir(fs.combine(args[4],v.path)) + if not hide_progress then + end + end + end + local drawProgress + if async and not silent then + local _, y = term.getCursorPos() + local wide, _ = term.getSize() + term.setCursorPos(1, y) + term.write("[") + term.setCursorPos(wide - 6, y) + term.write("]") + drawProgress = function(done, max) + local value = done / max + term.setCursorPos(2,y) + term.write(("="):rep(math.floor(value * (wide - 8)))) + local percent = math.floor(value * 100) .. "%" + term.setCursorPos(wide - percent:len(),y) + term.write(percent) + end + end + local filecount = 0 + local downloaded = 0 + local paths = {} + local failed = {} + for k,v in pairs(data.tree) do + -- Send all HTTP requests (async) + if v.type == "blob" then + v.path = v.path:gsub("%s","%%20") + local url = "https://raw.github.com/"..args[1].."/"..args[2].."/"..args[3].."/"..v.path,fs.combine(args[4],v.path) + if async then + http.request(url) + paths[url] = fs.combine(args[4],v.path) + filecount = filecount + 1 + else + download(url, fs.combine(args[4], v.path)) + if not silent then print(fs.combine(args[4], v.path)) end + end + end + end + while downloaded < filecount do + local e, a, b = os.pullEvent() + if e == "http_success" then + save(b.readAll(),paths[a]) + downloaded = downloaded + 1 + if not silent then drawProgress(downloaded,filecount) end + elseif e == "http_failure" then + -- Retry in 3 seconds + failed[os.startTimer(3)] = a + elseif e == "timer" and failed[a] then + http.request(failed[a]) + end + end +end +preset.done() \ No newline at end of file