From 869f10fa52ca6384d72e47dab93caa59800ea939 Mon Sep 17 00:00:00 2001 From: Rokas Puzonas Date: Fri, 10 Jun 2022 17:06:30 +0300 Subject: [PATCH] feat: initial commit --- .luarc.json | 6 ++ README.md | 3 + craft.lua | 186 ++++++++++++++++++++++++++++++++++++++++++++++++++++ init.lua | 148 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 343 insertions(+) create mode 100644 .luarc.json create mode 100644 README.md create mode 100644 craft.lua create mode 100644 init.lua diff --git a/.luarc.json b/.luarc.json new file mode 100644 index 0000000..02105df --- /dev/null +++ b/.luarc.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://raw.githubusercontent.com/sumneko/vscode-lua/master/setting/schema.json", + "Lua.diagnostics.globals": [ + "turtle" + ] +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..51f842b --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# qturle - Quick turtle + +A utility library for dealing with turtles in ComputerCraft. diff --git a/craft.lua b/craft.lua new file mode 100644 index 0000000..2723a66 --- /dev/null +++ b/craft.lua @@ -0,0 +1,186 @@ +assert(turtle, "The quickturtle library is for turtles only") + +local expect = require("cc.expect") + +local crafting_grid = {1, 2, 3, 5, 6, 7, 9, 10, 11} +local non_crafting_grid = {4, 8, 12, 13, 14, 15, 16} + +local function countAllItems() + local itemCounts = {} + + for i=1, 16 do + local detail = turtle.getItemDetail(i) + if detail then + itemCounts[detail.name] = itemCounts[detail.name] or 0 + itemCounts[detail.name] = itemCounts[detail.name] + detail.count + end + end + + return itemCounts +end + +local function calculcateMaxCraftsAmount(storedItems, minRequiredItems) + local amount = 64 + for name, minAmount in pairs(minRequiredItems) do + amount = math.min(amount, math.floor((storedItems[name] or 0) / minAmount)) + end + return math.max(amount, 1) +end + +local function transferToAnySlot(from_slot, available_slots, target_left_amount) + target_left_amount = target_left_amount or 0 + turtle.select(from_slot) + for _, to_slot in pairs(available_slots) do + turtle.transferTo(to_slot, turtle.getItemCount(from_slot) - target_left_amount) + if turtle.getItemCount(from_slot) == target_left_amount then break end + end +end + +local function countItems(items) + local count = {} + for _, name in pairs(items) do + count[name] = (count[name] or 0) + 1 + end + return count +end + +local function dropUnneededItems(slot, stored_items, needed_items) + -- If there are no items in this slot, do nothing + local detail = turtle.getItemDetail(slot) + if not detail then return end + + -- Check how many items are unneeded + -- If there are enough items, do nothing + local name = detail.name + local unneeded = stored_items[name] - needed_items[name] + if unneeded <= 0 then return end + + -- Drop the unneeded amount and update stored items table + turtle.select(slot) + stored_items[name] = stored_items[name] - math.min(unneeded, turtle.getItemCount()) + turtle.drop(unneeded) +end + +local function getItemName(slot) + local detail = turtle.getItemDetail(slot) + return detail and detail.name +end + +local function craft(recipe, craft_amount) + if not turtle.craft then + return false, "crafting table not found" + end + + if craft_amount ~= nil then + expect.expect(craft_amount, "number", "nil") + craft_amount = math.floor(craft_amount) + expect.range(craft_amount, 1, 64) + end + + local min_required_items = countItems(recipe) + local stored_items = countAllItems() + local needed_items + + -- Ensure that there are enough items before doing anything + do + -- If amount was not given, calculate how many items can be crafted + if not craft_amount then + craft_amount = calculcateMaxCraftsAmount(stored_items, min_required_items) + end + + -- Calculate the needed amounts of each item + needed_items = {} + for name, min_amount in pairs(min_required_items) do + needed_items[name] = min_amount * craft_amount + end + + -- Check if there are enough items for crafting the amount of items wanted + for name, needed_amount in pairs(needed_items) do + local missing_amount = needed_amount - (stored_items[name] or 0) + if missing_amount > 0 then + return false, ("not enough %s, missing %s"):format(name, missing_amount) + end + end + end + + -- Remove every item that is not going to be used up in the recipe + do + -- 1. First prioritize removing items from non crafting slots + for _, slot in ipairs(non_crafting_grid) do + dropUnneededItems(slot, stored_items, needed_items) + end + -- 2. If there are still items that need to be removed remove them from crafting slots + for _, slot in ipairs(crafting_grid) do + dropUnneededItems(slot, stored_items, needed_items) + end + end + + -- Rearrange items into their correct slots + for i, slot in pairs(crafting_grid) do + local recipe_item = recipe[i] + + -- If the slot is supposed to be empty, remove move all items to + -- a temporary slot + if not recipe_item then + if turtle.getItemCount(slot) > 0 then + transferToAnySlot(slot, non_crafting_grid) + end + else + -- Place item in temporary slot if it's name does not match + local current_item = getItemName(slot) + if current_item and current_item ~= recipe_item then + transferToAnySlot(slot, non_crafting_grid) + end + + -- If current slot is empty or not full, find items from other slots + -- First grab items from temporary slots + if turtle.getItemCount(slot) < craft_amount then + for _, search_slot in pairs(non_crafting_grid) do + if getItemName(search_slot) == recipe_item then + local missing_amount = craft_amount - turtle.getItemCount(slot) + turtle.select(search_slot) + turtle.transferTo(slot, missing_amount) + -- If there are enough items, search can be stopped + if turtle.getItemCount(slot) == craft_amount then break end + end + end + end + + -- If current slot is empty or not full, find items from other slots + -- Then grab items from other crafting grid slots + if turtle.getItemCount(slot) < craft_amount then + for j, search_slot in pairs(crafting_grid) do + local slot_item_name = getItemName(search_slot) + if slot_item_name == recipe_item then + local missing_amount = craft_amount - turtle.getItemCount(slot) + + -- Determine how many items can be taken from this slot + -- 1. If the item present in this slot is correct with the recipe + -- Only take as many items that are extra in that slot + -- 2. If the item present in this slot is not correct with the recipe + -- Take as much as you need + local transfer_amount + if recipe[j] ~= slot_item_name then + transfer_amount = missing_amount + else + local extra_amount = turtle.getItemCount(search_slot) - craft_amount + transfer_amount = math.min(missing_amount, extra_amount) + end + + if transfer_amount > 0 then + turtle.select(search_slot) + turtle.transferTo(slot, transfer_amount) + -- If there are enough items, search can be stopped + if turtle.getItemCount(slot) == craft_amount then break end + end + end + end + end + end + end + + -- Try crafting + -- return turtle.craft(craft_amount) +end + +return craft diff --git a/init.lua b/init.lua new file mode 100644 index 0000000..ae88e70 --- /dev/null +++ b/init.lua @@ -0,0 +1,148 @@ +assert(turtle, "The quickturtle library is for turtles only") +local qturtle = {} +local craftingGrid = {1, 2, 3, 5, 6, 7, 9, 10, 11} + +-- Can pass a 'n' paramter, to tell that it to repeat that action +-- if 'n' is negative it will execute the opposite of that function +local function createRepeatableFunction(positiveFunc, negativeFunc) + return function(n) + local func = positiveFunc + n = n or 1 + if n < 0 and negativeFunc then + func = negativeFunc + n = -n + end + + for i=1, n do + if func() == false then + return false, i-1 + end + end + + return true + end +end + +-- Mines in one place forever until there is not block there +local function createEnhancedDig(digFunc) + return function(force) + if force then + while digFunc() do end + else + return digFunc() + end + end +end + +-- If an item name is given, it will find it and place it +local function createEnhancedPlace(placeFunc) + return function(itemName) + if itemName and not qturtle.findAndSelectItem(itemName) then + return false, "Item not found" + end + return placeFunc() + end +end + +function qturtle.turnAround() + turtle.turnLeft() + turtle.turnLeft() +end + +function qturtle.findItem(itemName) + for i=1, 16 do + local detail = turtle.getItemDetail(i) + if detail and detail.name == itemName then + return i + end + end +end + +function qturtle.findAndSelectItem(itemName) + local selectedDetail = turtle.getItemDetail() + if selectedDetail and selectedDetail.name == itemName then + return true + end + + local slot = qturtle.findItem(itemName) + if not slot then return false end + turtle.select(slot) + return true +end + +function qturtle.findEmptySlot() + for i=1, 16 do + if turtle.getItemCount(i) == 0 then + return i + end + end +end + +function qturtle.doItemsMatch(slot1, slot2) + local detail1 = turtle.getItemDetail(slot1) + local detail2 = turtle.getItemDetail(slot2) + return (not detail1 and not detail2) or (detail1 and detail2 and detail1.name == detail2.name) +end + +function qturtle.stackItems() + for i=1, 15 do + local spaceLeft = turtle.getItemSpace(i) + if spaceLeft > 0 and turtle.getItemDetail(i) then + for j=i+1, 16 do + if qturtle.doItemsMatch(i, j) then + spaceLeft = spaceLeft - turtle.getItemCount() + turtle.select(j) + turtle.transferTo(i) + if spaceLeft <= 0 then break end + end + end + end + end +end + +function qturtle.createRecipe() + local recipe = { } + + for i, slot in ipairs(craftingGrid) do + local detail = turtle.getItemDetail(slot) + if detail then + recipe[i] = detail.name + end + end + + return recipe +end + +qturtle.turnLeft = createRepeatableFunction(turtle.turnLeft, turtle.turnRight) +qturtle.turnRight = createRepeatableFunction(turtle.turnRight, turtle.turnLeft) + +qturtle.forward = createRepeatableFunction(turtle.forward, turtle.back) +qturtle.back = createRepeatableFunction(turtle.back, turtle.forward) + +qturtle.dig = createEnhancedDig(turtle.dig) +qturtle.digUp = createEnhancedDig(turtle.digUp) +qturtle.digDown = createEnhancedDig(turtle.digDown) + +qturtle.place = createEnhancedPlace(turtle.place) +qturtle.placeUp = createEnhancedPlace(turtle.placeUp) +qturtle.placeDown = createEnhancedPlace(turtle.placeDown) + +qturtle.craftRecipe = require("craft") + +function qturtle.goDig() + qturtle.dig(true) + turtle.forward() +end + +function qturtle.goDigDown() + qturtle.dig(true) + turtle.down() +end + +function qturtle.goDigUp() + qturtle.digUp(true) + turtle.up() +end + + +return qturtle