APIs and examples and more item moving logic
parent
0cb5264e28
commit
0fc59a8d42
|
@ -0,0 +1,84 @@
|
|||
# ComputerTest API
|
||||
|
||||
This api is two parts, with notes/documentation above, and functions below
|
||||
|
||||
## Init Function
|
||||
See README.md for basic setup.
|
||||
|
||||
Once you have a turtle, you need an init function such as:
|
||||
```lua
|
||||
function init(turtle)
|
||||
turtle:moveForward()
|
||||
end
|
||||
```
|
||||
## Directions
|
||||
Many commands have directions. These come in sets like:
|
||||
```lua
|
||||
turtle:moveForward()
|
||||
turtle:moveBackward()
|
||||
turtle:moveRight()
|
||||
turtle:moveLeft()
|
||||
turtle:moveUp()
|
||||
turtle:moveDown()
|
||||
```
|
||||
|
||||
This will be referred to as `turtle:move[direction]()`
|
||||
|
||||
Up and down are always the same, but orientation is changed with
|
||||
```lua
|
||||
turtle:turnLeft()
|
||||
turtle:turnRight()
|
||||
```
|
||||
|
||||
## The turtle's inventory
|
||||
|
||||
The turtle has 16 'turtleslots' numbered 1-16.
|
||||
|
||||
## Config
|
||||
|
||||
Edit init.lua in the config object at the top to change the config.
|
||||
Actions take effect on restart/reload.
|
||||
|
||||
## Action Functions (do things)
|
||||
|
||||
### `turtle:yield(reason)`
|
||||
- Yields
|
||||
|
||||
Many functions yield to prevent lag, balance the mod, and to prevent hanging.
|
||||
You MUST have a yield in all time-consuming processes.
|
||||
If you don't yield, and your turtle takes too long, the entire server will lag/halt.
|
||||
The reason can be any string, and is visible in debug.txt (can turn on/off debugging in config)
|
||||
###`turtle:mine[direction]()`
|
||||
- Yields
|
||||
- Uses Fuel
|
||||
The turtle will mine the block next to it and take the items of the block. It will take items from chests, too
|
||||
They return true if the block was removed
|
||||
|
||||
## Inventory/Fuel functions
|
||||
|
||||
### `turtle:useFuel()`
|
||||
- Uses Fuel
|
||||
Burns fuel. Once you run out of fuel, your turtle is stuck.
|
||||
### `turtle:getFuel()`
|
||||
Returns the number actions you can take until needing to refuel.
|
||||
If this is zero, you won't be able to do much except refuel.
|
||||
### `turtle:itemRefuel(turtleslot)`
|
||||
Takes fuel from this turtleslot and increases the turtle's fuel
|
||||
###`turtle:move[direction]()`
|
||||
- Yields
|
||||
- Uses Fuel
|
||||
|
||||
Moves one block in that direction, yields, and consumes fuel
|
||||
|
||||
## Info functions
|
||||
|
||||
###`turtle:setName(name)`
|
||||
Sets the turtle's name
|
||||
###`turtle:debug(string)`
|
||||
Prints string to debug.txt (can be disabled in config)
|
||||
|
||||
## Other Functions
|
||||
|
||||
I might be missing some, check out turtle.lua for more!
|
||||
|
||||
All functions that start with TurtleEntity are callable (although some shouldn't be such as the ones marked as helper functions)
|
35
README.md
35
README.md
|
@ -11,6 +11,38 @@ A ComputerCraft-inspired mod for Minetest!
|
|||
- Turtles take time to do actions such as moving,mining,turning,etc
|
||||
- A "computertest" privilege, to limit turtle usage to trusted users (so only users with this privilege can use/edit turtles)
|
||||
|
||||
## An example of how to use this mod
|
||||
|
||||
1. Install the ComputerTest mod
|
||||
2. Get a turtle block using either
|
||||
- The command `/giveme computertest:turtle`
|
||||
- The creative menu
|
||||
- The recipe
|
||||
```
|
||||
III
|
||||
ICI
|
||||
IMI
|
||||
I = Steel Ingot , C = Chest , M = Mesa Block
|
||||
```
|
||||
3. Place the turtle
|
||||
4. Right click it and click "Upload Code"
|
||||
5. Paste this into the large field (sometimes Minetest requires you to paste twice) and click "Upload Code"
|
||||
```lua
|
||||
function init(turtle)
|
||||
while true do
|
||||
turtle:moveForward()
|
||||
turtle:turnRight()
|
||||
end
|
||||
end
|
||||
```
|
||||
6. Watch as it spins around!
|
||||
|
||||
## Other Information
|
||||
|
||||
The API.md contains important documentation for programming.
|
||||
|
||||
EXAMPLES.md contains some fun examples
|
||||
|
||||
## Changes are Welcome!
|
||||
|
||||
Anyone interested in adding these features can try in entity/turtle.lua, I'd be interested in any great working pull requests!
|
||||
|
@ -18,6 +50,5 @@ Anyone interested in adding these features can try in entity/turtle.lua, I'd be
|
|||
### Features to Add
|
||||
|
||||
- Add dumping into chests to create fully-auto mining
|
||||
- Add fuel, so everything consumes fuel until it runs out or refuels
|
||||
- Inventory management commands, such as crafting, sorting, and dropping
|
||||
- The turtle code isn't sandboxed, so turtles could call dangerous functions. This has been mitigated by the "computertest" privilege, but proper sandboxing would work best.
|
||||
- The turtle code isn't sandboxed, so turtles could call dangerous functions. This has been mitigated by the "computertest" privilege, but proper sandboxing would work best.
|
||||
|
|
|
@ -13,6 +13,12 @@ minetest.register_node("computertest:turtle", {
|
|||
},
|
||||
groups = {oddly_breakable_by_hand=2},
|
||||
paramtype2 = "facedir",
|
||||
after_place_node = function(pos, placer)
|
||||
if placer and placer:is_player() then
|
||||
local meta = minetest.get_meta(pos)
|
||||
meta:set_string("owner", placer:get_player_name())
|
||||
end
|
||||
end,
|
||||
on_construct = function(pos)
|
||||
local turtle = minetest.add_entity(pos,"computertest:turtle")
|
||||
turtle = turtle:get_luaentity()
|
||||
|
|
|
@ -8,7 +8,9 @@ local FORM_NOPRIV = "size[9,1;]label[0,0;You do not have the 'computertest' priv
|
|||
|
||||
local TURTLE_INVENTORYSIZE = 4*4
|
||||
|
||||
---@returns TurtleEntity of that ID
|
||||
local function getTurtle(id) return computertest.turtles[id] end
|
||||
---@returns true
|
||||
local function isValidInventoryIndex(index) return 0 < index and index <= TURTLE_INVENTORYSIZE end
|
||||
local function has_computertest_priv(player) return minetest.check_player_privs(player,"computertest") end
|
||||
|
||||
|
@ -28,12 +30,16 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
|
|||
return not turtle:upload_code_to_turtle(player,"",false)
|
||||
end
|
||||
elseif isForm(FORMNAME_TURTLE_TERMINAL) then
|
||||
if (fields.terminal_out ~= nil) then return true end
|
||||
if (fields.terminal_out ~= nil) then
|
||||
return true
|
||||
end
|
||||
local id = tonumber(string.sub(formname,1+string.len(FORMNAME_TURTLE_TERMINAL)))
|
||||
local turtle = getTurtle(id)
|
||||
turtle.lastCommandRan = fields.terminal_in
|
||||
local command = fields.terminal_in
|
||||
if command==nil or command=="" then return nil end
|
||||
if command==nil or command=="" then
|
||||
return nil
|
||||
end
|
||||
command = "function init(turtle) return "..command.." end"
|
||||
local commandResult = turtle:upload_code_to_turtle(player,command, true)
|
||||
if (commandResult==nil) then
|
||||
|
@ -45,7 +51,9 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
|
|||
minetest.show_formspec(player:get_player_name(),FORMNAME_TURTLE_TERMINAL..id,turtle:get_formspec_terminal());
|
||||
elseif isForm(FORMNAME_TURTLE_UPLOAD) then
|
||||
local id = tonumber(string.sub(formname,1+string.len(FORMNAME_TURTLE_UPLOAD)))
|
||||
if (fields.button_upload == nil or fields.upload == nil) then return true end
|
||||
if (fields.button_upload == nil or fields.upload == nil) then
|
||||
return true
|
||||
end
|
||||
local turtle = getTurtle(id)
|
||||
return not turtle:upload_code_to_turtle(player,fields.upload,false)
|
||||
else
|
||||
|
@ -58,16 +66,20 @@ end)
|
|||
local timer = 0
|
||||
minetest.register_globalstep(function(dtime)
|
||||
timer = timer + dtime
|
||||
if (timer >= computertest.config.turtle_tick) then
|
||||
while (timer >= computertest.config.turtle_tick) do
|
||||
for _,turtle in pairs(computertest.turtles) do
|
||||
if turtle.coroutine then
|
||||
if coroutine.status(turtle.coroutine)=="suspended" then
|
||||
--TODO check for fuel here
|
||||
local status, result = coroutine.resume(turtle.coroutine)
|
||||
minetest.log("coroutine stat "..dump(status).." said "..dump(result))
|
||||
--elseif coroutine.status(turtle.coroutine)=="dead" then
|
||||
--minetest.log("turtle #"..id.." has coroutine, but it's already done running")
|
||||
if (turtle.fuel>0) then
|
||||
local status, result = coroutine.resume(turtle.coroutine)
|
||||
turtle:debug("coroutine stat "..dump(status).." said "..dump(result))
|
||||
turtle:debug("fuel="..turtle.fuel)
|
||||
else
|
||||
turtle:debug("No Fuel in turtle")
|
||||
end
|
||||
end
|
||||
--elseif coroutine.status(turtle.coroutine)=="dead" then
|
||||
--minetest.log("turtle #"..id.." has coroutine, but it's already done running")
|
||||
elseif turtle.code then
|
||||
--minetest.log("turtle #"..id.." has no coroutine but has code! Making coroutine...")
|
||||
--TODO add some kinda timeout into coroutine
|
||||
|
@ -83,9 +95,10 @@ minetest.register_globalstep(function(dtime)
|
|||
end
|
||||
end)
|
||||
--Code responsible for generating turtle entity and turtle interface
|
||||
minetest.register_entity("computertest:turtle", {
|
||||
|
||||
local TurtleEntity = {
|
||||
initial_properties = {
|
||||
hp_max = 1,
|
||||
hp_max = 20,
|
||||
is_visible = true,
|
||||
makes_footstep_sound = false,
|
||||
physical = true,
|
||||
|
@ -101,260 +114,416 @@ minetest.register_entity("computertest:turtle", {
|
|||
"computertest_front.png",
|
||||
},
|
||||
automatic_rotate = 0,
|
||||
id = -1,
|
||||
id = -1
|
||||
},
|
||||
}
|
||||
|
||||
--MAIN TURTLE USER INTERFACE------------------------------------------
|
||||
get_formspec_inventory = function(turtle)
|
||||
return "size[12,5;]"
|
||||
.."button[0,0;2,1;open_terminal;Open Terminal]"
|
||||
.."button[2,0;2,1;upload_code;Upload Code]"
|
||||
.."button[4,0;2,1;factory_reset;Factory Reset]"
|
||||
.."set_focus[open_terminal;true]"
|
||||
.."list[".. turtle.inv_fullname..";main;8,1;4,4;]"
|
||||
.."background[8,1;1,1;computertest_inventory.png]"
|
||||
.."list[current_player;main;0,1;8,4;]";
|
||||
end,
|
||||
get_formspec_terminal = function(turtle)
|
||||
local previous_answers = turtle.previous_answers
|
||||
local parsed_output = "";
|
||||
for i=1, #previous_answers do parsed_output = parsed_output .. minetest.formspec_escape(previous_answers[i]).."," end
|
||||
--local saved_output = "";
|
||||
--for i=1, #previous_answers do saved_output = saved_output .. minetest.formspec_escape(previous_answers[i]).."\n" end
|
||||
return
|
||||
"size[12,9;]"
|
||||
.."field_close_on_enter[terminal_in;false]"
|
||||
.."field[0,0;12,1;terminal_in;;"..minetest.formspec_escape(turtle.lastCommandRan or "").."]"
|
||||
.."set_focus[terminal_in;true]"
|
||||
.."textlist[0,1;12,8;terminal_out;"..parsed_output.."]";
|
||||
end,
|
||||
get_formspec_upload = function(turtle)
|
||||
--TODO could indicate if code is already uploaded
|
||||
return
|
||||
"size[12,9;]"
|
||||
.."button[0,0;2,1;button_upload;Upload Code to #"..turtle.id.."]"
|
||||
.."field_close_on_enter[upload;false]"
|
||||
.."textarea[0,1;12,8;upload;;"..minetest.formspec_escape(turtle.codeUncompiled or "").."]"
|
||||
.."set_focus[upload;true]";
|
||||
end,
|
||||
---
|
||||
---@return true on success
|
||||
---
|
||||
upload_code_to_turtle = function(turtle,player, code_string,run_for_result)
|
||||
--Check permissions
|
||||
if (not has_computertest_priv(player)) then
|
||||
--minetest.debug(player:get_player_name().." does not have computertest priv")
|
||||
--MAIN TURTLE HELPER FUNCTIONS------------------------------------------
|
||||
function TurtleEntity:getTurtleslot(turtleslot)
|
||||
if not isValidInventoryIndex(turtleslot) then return nil end
|
||||
return self.inv:get_stack("main",turtleslot)
|
||||
end
|
||||
function TurtleEntity:setTurtleslot(turtleslot, stack)
|
||||
if not isValidInventoryIndex(turtleslot) then return false end
|
||||
self.inv:set_stack("main", turtleslot,stack)
|
||||
return true
|
||||
end
|
||||
function TurtleEntity:move(nodeLocation)
|
||||
--Verify new pos is empty
|
||||
if (nodeLocation == nil or minetest.get_node(nodeLocation).name~="air") then
|
||||
self:yield("Moving")
|
||||
return false
|
||||
end
|
||||
--Take Action
|
||||
self.object:set_pos(nodeLocation)
|
||||
self:yield("Moving",true)
|
||||
return true
|
||||
end
|
||||
function TurtleEntity:mine(nodeLocation)
|
||||
if nodeLocation == nil then
|
||||
return false
|
||||
end
|
||||
local node = minetest.get_node(nodeLocation)
|
||||
if (node.name=="air") then
|
||||
return false
|
||||
end
|
||||
--Try sucking the inventory (in case it's a chest)
|
||||
self:itemSuck(nodeLocation)
|
||||
local drops = minetest.get_node_drops(node)
|
||||
--TODO NOTE This violates spawn protection, but I know of no way to mine that abides by spawn protection AND picks up all items and contents (dig_node drops items and I don't know how to pick them up)
|
||||
minetest.remove_node(nodeLocation)
|
||||
for _, iteminfo in pairs(drops) do
|
||||
local stack = ItemStack(iteminfo)
|
||||
if self.inv:room_for_item("main",stack) then
|
||||
self.inv:add_item("main",stack)
|
||||
end
|
||||
end
|
||||
self:yield("Mining",true)
|
||||
return true
|
||||
end
|
||||
function TurtleEntity:build(nodeLocation, turtleslot)
|
||||
if nodeLocation == nil then return false end
|
||||
if not isValidInventoryIndex(turtleslot) then return false end
|
||||
|
||||
local node = minetest.get_node(nodeLocation)
|
||||
if node.name~="air" then return false end
|
||||
|
||||
--Build and consume item
|
||||
local stack = self:getTurtleslot("main", turtleslot)
|
||||
if stack:is_empty() then return false end
|
||||
local newstack, position_placed = minetest.item_place_node(stack, nil, { type="node", under=nodeLocation, above=self:get_pos()})
|
||||
self.inv:set_stack("main", turtleslot,newstack)
|
||||
|
||||
if position_placed == nil then
|
||||
self:yield("Building")
|
||||
return false
|
||||
end
|
||||
|
||||
self:yield("Building",true)
|
||||
return true
|
||||
end
|
||||
function TurtleEntity:scan(nodeLocation) return minetest.get_node(nodeLocation) end
|
||||
|
||||
--[[ Sucks inventory (chest, node, furnace, etc) at nodeLocation into turtle
|
||||
@returns true if it sucked everything up]]
|
||||
--function TurtleEntity:suckBlock(nodeLocation)
|
||||
-- local suckedEverything = true
|
||||
-- local nodeInventory = minetest.get_inventory({type="node", pos=nodeLocation})
|
||||
-- if not nodeInventory then
|
||||
-- return true --No node inventory, nothing left to suck
|
||||
-- end
|
||||
-- for listname,listStacks in pairs(nodeInventory:get_lists()) do
|
||||
-- for stackI,itemStack in pairs(listStacks) do
|
||||
-- if self.inv:room_for_item("main",itemStack) then
|
||||
-- local remainingItemStack = self.inv:add_item("main",itemStack)
|
||||
-- nodeInventory:set_stack(listname, stackI, remainingItemStack)
|
||||
-- else
|
||||
-- suckedEverything = false
|
||||
-- end
|
||||
-- end
|
||||
-- end
|
||||
-- return suckedEverything
|
||||
--end
|
||||
|
||||
---Takes everything from block that matches list
|
||||
--- @param filterlist - Something like {"default:stone","default:dirt"}
|
||||
--- @param isWhitelist - If true, only take things in list.
|
||||
--- If false, take everything EXCEPT the items in the list
|
||||
--- @param listname - take only from specific listname. If nil, take from every list
|
||||
--- @return boolean true unless any items can't fit
|
||||
function TurtleEntity:itemSuck(nodeLocation, filterlist, isWhitelist, listname)
|
||||
filterlist = filterlist or {}
|
||||
local suckedEverything = true
|
||||
local nodeInventory = minetest.get_inventory({type="node", pos=nodeLocation})
|
||||
if not nodeInventory then
|
||||
return true --No node inventory, nothing left to suck
|
||||
end
|
||||
|
||||
local function suckList(listname, listStacks)
|
||||
local function intable(item)
|
||||
for _,x in pairs(filterlist) do
|
||||
if item==x then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
local function sandbox(code)
|
||||
--TODO sandbox this!
|
||||
--Currently returns function that defines init and loop. In the future, this should probably just initialize it using some callbacks
|
||||
if (code =="") then return nil end
|
||||
return loadstring(code)
|
||||
end
|
||||
turtle.codeUncompiled = code_string
|
||||
turtle.coroutine = nil
|
||||
turtle.code = sandbox(turtle.codeUncompiled)
|
||||
if (run_for_result) then
|
||||
--TODO run subroutine once, if it returns a value, return that here
|
||||
return "Ran"
|
||||
end
|
||||
return turtle.code ~= nil
|
||||
end,
|
||||
--MAIN END TURTLE USER INTERFACE------------------------------------------
|
||||
--- From 0 to 3
|
||||
set_heading = function(turtle,heading)
|
||||
heading = (tonumber(heading) or 0)%4
|
||||
if turtle.heading ~= heading then
|
||||
turtle.heading = heading
|
||||
turtle.object:set_yaw(turtle.heading * 3.14159265358979323/2)
|
||||
if (coroutine.running() == turtle.coroutine) then turtle:yield("Turning",true) end
|
||||
end
|
||||
end,
|
||||
get_heading = function(self)
|
||||
return self.heading
|
||||
end,
|
||||
on_activate = function(turtle, staticdata, dtime_s)
|
||||
--TODO use staticdata to load previous state, such as inventory and whatnot
|
||||
--Give ID
|
||||
computertest.num_turtles = computertest.num_turtles+1
|
||||
turtle.id = computertest.num_turtles
|
||||
turtle.heading = 0
|
||||
turtle.previous_answers = {}
|
||||
turtle.coroutine = nil
|
||||
turtle.fuel = 100
|
||||
--Give her an inventory
|
||||
turtle.inv_name = "computertest:turtle:".. turtle.id
|
||||
turtle.inv_fullname = "detached:".. turtle.inv_name
|
||||
local inv = minetest.create_detached_inventory(turtle.inv_name,{})
|
||||
if inv == nil or inv == false then error("Could not spawn inventory")end
|
||||
inv:set_size("main", TURTLE_INVENTORYSIZE)
|
||||
if turtle.inv ~= nil then inv.set_lists(turtle.inv) end
|
||||
turtle.inv = inv
|
||||
-- Add to turtle list
|
||||
computertest.turtles[turtle.id] = turtle
|
||||
end,
|
||||
on_rightclick = function(self, clicker)
|
||||
if not clicker or not clicker:is_player() then return end
|
||||
if (not has_computertest_priv(clicker)) then
|
||||
minetest.show_formspec(clicker:get_player_name(),FORMNAME_TURTLE_NOPRIV,FORM_NOPRIV);
|
||||
return
|
||||
for stackI,itemStack in pairs(listStacks) do
|
||||
if intable(itemStack:get_name()) == isWhitelist then
|
||||
local remainingItemStack = self.inv:add_item("main",itemStack)
|
||||
nodeInventory:set_stack(listname, stackI, remainingItemStack)
|
||||
suckedEverything = suckedEverything and remainingItemStack:is_empty()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if listname then
|
||||
suckList(listname, nodeInventory:get_list(listname))
|
||||
else
|
||||
for listname,listStacks in pairs(nodeInventory:get_lists()) do
|
||||
suckList(listname, listStacks)
|
||||
end
|
||||
end
|
||||
return suckedEverything
|
||||
end
|
||||
|
||||
function TurtleEntity:itemSuckForward(filterlist, isWhitelist, listname) return self:itemSuck(self:getLocForward(), filterlist, isWhitelist, listname) end
|
||||
|
||||
---Takes everything from block that matches list
|
||||
--- @param filterlist - Something like {"default:stone","default:dirt"}
|
||||
--- @param isWhitelist - If true, only take things in list.
|
||||
--- If false, take everything EXCEPT the items in the list
|
||||
--- @param listname - take only from specific listname. If nil, take from every list
|
||||
--- @return boolean true if stack completely pushed
|
||||
function TurtleEntity:itemPush(nodeLocation, turtleslot, filterlist, isWhitelist, listname)
|
||||
listname = listname or "main"
|
||||
|
||||
local nodeInventory = minetest.get_inventory({type="node", pos=nodeLocation})--InvRef
|
||||
if not nodeInventory then
|
||||
return false --No node inventory (Ex: Not a chest)
|
||||
end
|
||||
|
||||
--Try putting my stack somewhere
|
||||
local toPush = self:getTurtleslot(turtleslot)
|
||||
local remainingItemStack = nodeInventory:add_item(listname,toPush)
|
||||
self:setTurtleslot(turtleslot, remainingItemStack)
|
||||
|
||||
return remainingItemStack:is_empty()
|
||||
end
|
||||
---
|
||||
---@returns true on success
|
||||
---
|
||||
function TurtleEntity:upload_code_to_turtle(player, code_string,run_for_result)
|
||||
--Check permissions
|
||||
if (not has_computertest_priv(player)) then
|
||||
--minetest.debug(player:get_player_name().." does not have computertest priv")
|
||||
return false
|
||||
end
|
||||
local function sandbox(code)
|
||||
--TODO sandbox this!
|
||||
--Currently returns function that defines init and loop. In the future, this should probably just initialize it using some callbacks
|
||||
if (code =="") then return nil end
|
||||
return loadstring(code)
|
||||
end
|
||||
self.codeUncompiled = code_string
|
||||
self.coroutine = nil
|
||||
self.code = sandbox(self.codeUncompiled)
|
||||
if (run_for_result) then
|
||||
--TODO run subroutine once, if it returns a value, return that here
|
||||
return "Ran"
|
||||
end
|
||||
return self.code ~= nil
|
||||
end
|
||||
--MAIN TURTLE USER INTERFACE------------------------------------------
|
||||
function TurtleEntity:get_formspec_inventory()
|
||||
return "size[12,5;]"
|
||||
.."button[0,0;2,1;open_terminal;Open Terminal]"
|
||||
.."button[2,0;2,1;upload_code;Upload Code]"
|
||||
.."button[4,0;2,1;factory_reset;Factory Reset]"
|
||||
.."set_focus[open_terminal;true]"
|
||||
.."list[".. self.inv_fullname..";main;8,1;4,4;]"
|
||||
.."background[8,1;1,1;computertest_inventory.png]"
|
||||
.."list[current_player;main;0,1;8,4;]";
|
||||
end
|
||||
function TurtleEntity:get_formspec_terminal()
|
||||
local previous_answers = self.previous_answers
|
||||
local parsed_output = "";
|
||||
for i=1, #previous_answers do
|
||||
parsed_output = parsed_output .. minetest.formspec_escape(previous_answers[i])..","
|
||||
end
|
||||
return
|
||||
"size[12,9;]"
|
||||
.."field_close_on_enter[terminal_in;false]"
|
||||
.."field[0,0;12,1;terminal_in;;"..minetest.formspec_escape(self.lastCommandRan or "").."]"
|
||||
.."set_focus[terminal_in;true]"
|
||||
.."textlist[0,1;12,8;terminal_out;"..parsed_output.."]";
|
||||
end
|
||||
function TurtleEntity:get_formspec_upload()
|
||||
--TODO could indicate if code is already uploaded
|
||||
return
|
||||
"size[12,9;]"
|
||||
.."button[0,0;3,1;button_upload;Upload Code to '"..self.name.."']"
|
||||
.."field_close_on_enter[upload;false]"
|
||||
.."textarea[0,1;12,8;upload;;"..minetest.formspec_escape(self.codeUncompiled or "").."]"
|
||||
.."set_focus[upload;true]";
|
||||
end
|
||||
--MAIN TURTLE ENTITY FUNCTIONS------------------------------------------
|
||||
function TurtleEntity:on_activate(staticdata, dtime_s)
|
||||
--TODO use staticdata to load previous state, such as inventory and whatnot
|
||||
--Give ID
|
||||
computertest.num_turtles = computertest.num_turtles+1
|
||||
self.id = computertest.num_turtles
|
||||
self.name = "Unnamed #"..self.id
|
||||
--self.owner = minetest.get_meta(pos):get_string("owner")
|
||||
self.heading = 0
|
||||
self.previous_answers = {}
|
||||
self.coroutine = nil
|
||||
self.fuel = computertest.config.fuel_initial
|
||||
--Give her an inventory
|
||||
self.inv_name = "computertest:turtle:".. self.id
|
||||
self.inv_fullname = "detached:".. self.inv_name
|
||||
local inv = minetest.create_detached_inventory(self.inv_name,{})
|
||||
if inv == nil or inv == false then error("Could not spawn inventory")end
|
||||
inv:set_size("main", TURTLE_INVENTORYSIZE)
|
||||
if self.inv ~= nil then inv.set_lists(self.inv) end
|
||||
self.inv = inv
|
||||
-- Add to turtle list
|
||||
computertest.turtles[self.id] = self
|
||||
end
|
||||
function TurtleEntity:on_rightclick(clicker)
|
||||
if not clicker or not clicker:is_player() then
|
||||
return
|
||||
end
|
||||
if (not has_computertest_priv(clicker))then
|
||||
minetest.show_formspec(clicker:get_player_name(),FORMNAME_TURTLE_NOPRIV,FORM_NOPRIV);
|
||||
return
|
||||
end
|
||||
minetest.show_formspec(clicker:get_player_name(), FORMNAME_TURTLE_INVENTORY.. self.id,
|
||||
self:get_formspec_inventory())
|
||||
end
|
||||
function TurtleEntity:get_staticdata()
|
||||
minetest.debug("Deleting/Forgetting all data of turtle "..self.name)
|
||||
local static = {}
|
||||
|
||||
minetest.show_formspec(clicker:get_player_name(), FORMNAME_TURTLE_INVENTORY.. self.id,
|
||||
self:get_formspec_inventory())
|
||||
end,
|
||||
get_staticdata = function(self)
|
||||
-- TODO convert inventory and internal code to string and back somehow, or else it'll be deleted every time the entity gets unloaded
|
||||
minetest.debug("Deleting all data of turtle")
|
||||
end,
|
||||
turtle_move_withHeading = function (turtle,numForward,numRight,numUp)
|
||||
local new_pos = turtle:get_nearby_pos(numForward,numRight,numUp)
|
||||
--Verify new pos is empty
|
||||
if (new_pos == nil or minetest.get_node(new_pos).name~="air") then
|
||||
turtle:yield("Moving",true)
|
||||
return false
|
||||
end
|
||||
--Take Action
|
||||
turtle.object:set_pos(new_pos)
|
||||
turtle:yield("Moving",true)
|
||||
return true
|
||||
end,
|
||||
get_nearby_pos = function(turtle, numForward, numRight, numUp)
|
||||
local pos = turtle:get_pos()
|
||||
if pos==nil then return nil end -- To prevent unloaded turtles from trying to load things
|
||||
local new_pos = vector.new(pos)
|
||||
if turtle:get_heading()%4==0 then new_pos.z=pos.z-numForward;new_pos.x=pos.x-numRight; end
|
||||
if turtle:get_heading()%4==1 then new_pos.x=pos.x+numForward;new_pos.z=pos.z-numRight; end
|
||||
if turtle:get_heading()%4==2 then new_pos.z=pos.z+numForward;new_pos.x=pos.x+numRight; end
|
||||
if turtle:get_heading()%4==3 then new_pos.x=pos.x-numForward;new_pos.z=pos.z+numRight; end
|
||||
new_pos.y = pos.y + (numUp or 0)
|
||||
return new_pos
|
||||
end,
|
||||
mine = function(turtle, nodeLocation)
|
||||
if nodeLocation == nil then return false end
|
||||
local node = minetest.get_node(nodeLocation)
|
||||
if (node.name=="air") then return false end
|
||||
--Try sucking the inventory (in case it's a chest)
|
||||
turtle:suckBlock(nodeLocation)
|
||||
local drops = minetest.get_node_drops(node)
|
||||
--NOTE This violates spawn protection, but I know of no way to mine that abides by spawn protection AND picks up all items and contents (dig_node drops items and I don't know how to pick them up)
|
||||
minetest.remove_node(nodeLocation)
|
||||
for _, iteminfo in pairs(drops) do
|
||||
local stack = ItemStack(iteminfo)
|
||||
if turtle.inv:room_for_item("main",stack) then
|
||||
turtle.inv:add_item("main",stack)
|
||||
end
|
||||
end
|
||||
turtle:yield("Mining",true)
|
||||
return true
|
||||
end,
|
||||
useFuel = function(turtle)
|
||||
if turtle.fuel > 0 then
|
||||
turtle.fuel = turtle.fuel - 1;
|
||||
end
|
||||
end,
|
||||
-- MAIN TURTLE INTERFACE ---------------------------------------
|
||||
yield = function(turtle,reason,useFuel)
|
||||
-- Yield at least once
|
||||
if (coroutine.running() == turtle.coroutine) then
|
||||
coroutine.yield(reason)
|
||||
end
|
||||
--Use a fuel if requested
|
||||
if useFuel then turtle:useFuel() end
|
||||
end,
|
||||
--static = self -- Would this work..? no.
|
||||
|
||||
moveForward = function(turtle) return turtle:turtle_move_withHeading( 1, 0, 0) end,
|
||||
moveBackward = function(turtle) return turtle:turtle_move_withHeading(-1, 0, 0) end,
|
||||
moveRight = function(turtle) return turtle:turtle_move_withHeading( 0, 1, 0) end,
|
||||
moveLeft = function(turtle) return turtle:turtle_move_withHeading( 0,-1, 0) end,
|
||||
moveUp = function(turtle) return turtle:turtle_move_withHeading( 0, 0, 1) end,
|
||||
moveDown = function(turtle) return turtle:turtle_move_withHeading( 0, 0,-1) end,
|
||||
|
||||
turnLeft = function(turtle) return turtle:set_heading(turtle:get_heading()+1) end,
|
||||
turnRight = function(turtle) return turtle:set_heading(turtle:get_heading()-1) end,
|
||||
static.complete = true
|
||||
return minetest.serialize(static)
|
||||
end
|
||||
--MAIN PLAYER INTERFACE (CALL THESE)------------------------------------------
|
||||
function TurtleEntity:getLoc() return self.object:get_pos() end
|
||||
function TurtleEntity:getLocRelative(numForward,numUp,numRight)
|
||||
local pos = self:getLoc()
|
||||
if pos==nil then
|
||||
return nil -- To prevent unloaded turtles from trying to load things
|
||||
end
|
||||
local new_pos = vector.new(pos)
|
||||
if self:get_heading()%4==0 then new_pos.z=pos.z-numForward;new_pos.x=pos.x-numRight; end
|
||||
if self:get_heading()%4==1 then new_pos.x=pos.x+numForward;new_pos.z=pos.z-numRight; end
|
||||
if self:get_heading()%4==2 then new_pos.z=pos.z+numForward;new_pos.x=pos.x+numRight; end
|
||||
if self:get_heading()%4==3 then new_pos.x=pos.x-numForward;new_pos.z=pos.z+numRight; end
|
||||
new_pos.y = pos.y + (numUp or 0)
|
||||
return new_pos
|
||||
end
|
||||
function TurtleEntity:getLocForward() return self:getLocRelative( 1,0,0) end
|
||||
function TurtleEntity:getLocBackward() return self:getLocRelative(-1,0,0) end
|
||||
function TurtleEntity:getLocUp() return self:getLocRelative(0, 1,0) end
|
||||
function TurtleEntity:getLocDown() return self:getLocRelative(0,-1,0) end
|
||||
function TurtleEntity:getLocRight() return self:getLocRelative(0,0, 1) end
|
||||
function TurtleEntity:getLocLeft() return self:getLocRelative(0,0,-1) end
|
||||
---Consumes a fuel point
|
||||
function TurtleEntity:useFuel()
|
||||
if self.fuel > 0 then
|
||||
self.fuel = self.fuel - 1;
|
||||
end
|
||||
end
|
||||
--- From 0 to 3
|
||||
function TurtleEntity:set_heading(heading)
|
||||
heading = (tonumber(heading) or 0)%4
|
||||
if self.heading ~= heading then
|
||||
self.heading = heading
|
||||
self.object:set_yaw(self.heading * 3.14159265358979323/2)
|
||||
if (coroutine.running() == self.coroutine) then self:yield("Turning",true) end
|
||||
end
|
||||
end
|
||||
function TurtleEntity:get_heading() return self.heading end
|
||||
function TurtleEntity:turnLeft() return self:set_heading(self:get_heading()+1) end
|
||||
function TurtleEntity:turnRight() return self:set_heading(self:get_heading()-1) end
|
||||
|
||||
mineForward = function(turtle) return turtle:mine(turtle:get_nearby_pos (1,0,0)) end,
|
||||
mineBackward = function(turtle) return turtle:mine(turtle:get_nearby_pos (-1,0,0)) end,
|
||||
mineRight = function(turtle) return turtle:mine(turtle:get_nearby_pos (0,1,0)) end,
|
||||
mineLeft = function(turtle) return turtle:mine(turtle:get_nearby_pos (0,-1,0)) end,
|
||||
mineUp = function(turtle) return turtle:mine(turtle:get_nearby_pos (0,0,1)) end,
|
||||
mineDown = function(turtle) return turtle:mine(turtle:get_nearby_pos (0,0,-1)) end,
|
||||
function TurtleEntity:moveForward() return self:move(self:getLocForward()) end
|
||||
function TurtleEntity:moveBackward() return self:move(self:getLocBackward()) end
|
||||
function TurtleEntity:moveUp() return self:move(self:getLocUp()) end
|
||||
function TurtleEntity:moveDown() return self:move(self:getLocDown()) end
|
||||
function TurtleEntity:moveRight() return self:move(self:getLocRight()) end
|
||||
function TurtleEntity:moveLeft() return self:move(self:getLocLeft()) end
|
||||
|
||||
get_pos = function(turtle) return turtle.object:get_pos() end,
|
||||
get_fuel = function(turtle) return turtle.fuel end,
|
||||
function TurtleEntity:mineForward() return self:mine(self:getLocForward()) end
|
||||
function TurtleEntity:mineBackward() return self:mine(self:getLocBackward()) end
|
||||
function TurtleEntity:mineUp() return self:mine(self:getLocUp()) end
|
||||
function TurtleEntity:mineDown() return self:mine(self:getLocDown()) end
|
||||
function TurtleEntity:mineRight() return self:mine(self:getLocRight()) end
|
||||
function TurtleEntity:mineLeft() return self:mine(self:getLocLeft()) end
|
||||
|
||||
--[[ Sucks inventory (chest, node, furnace, etc) at nodeLocation into turtle
|
||||
@returns true if it sucked everything up]]
|
||||
suckBlock = function(turtle, nodeLocation)
|
||||
local suckedEverything = true
|
||||
local nodeInventory = minetest.get_inventory({type="node", pos=nodeLocation})
|
||||
if not nodeInventory then
|
||||
return false --No node inventory
|
||||
end
|
||||
for listName,listStacks in pairs(nodeInventory:get_lists()) do
|
||||
for stackI,itemStack in pairs(listStacks) do
|
||||
if turtle.inv:room_for_item("main",itemStack) then
|
||||
local remainingItemStack = turtle.inv:add_item("main",itemStack)
|
||||
nodeInventory:set_stack(listName, stackI, remainingItemStack)
|
||||
else
|
||||
suckedEverything = false
|
||||
end
|
||||
end
|
||||
end
|
||||
return suckedEverything
|
||||
end,
|
||||
function TurtleEntity:buildForward(turtleslot) return self:build(self:getLocForward(),turtleslot) end
|
||||
function TurtleEntity:buildBackward(turtleslot) return self:build(self:getLocBackward(),turtleslot) end
|
||||
function TurtleEntity:buildUp(turtleslot) return self:build(self:getLocUp(),turtleslot) end
|
||||
function TurtleEntity:buildDown(turtleslot) return self:build(self:getLocDown(),turtleslot) end
|
||||
function TurtleEntity:buildRight(turtleslot) return self:build(self:getLocRight(),turtleslot) end
|
||||
function TurtleEntity:buildLeft(turtleslot) return self:build(self:getLocLeft(),turtleslot) end
|
||||
|
||||
-- MAIN INVENTORY COMMANDS--------------------------
|
||||
--- TODO drops item onto ground
|
||||
itemDrop = function(turtle,itemslot)
|
||||
end,
|
||||
--- TODO Returns ItemStack on success or nil on failure
|
||||
---Ex: turtle:itemGet(3):get_name() -> "default:stone"
|
||||
itemGet = function(turtle,itemslot)
|
||||
if isValidInventoryIndex(itemslot) then
|
||||
return turtle.inv:get_stack("main",itemslot)
|
||||
end
|
||||
return nil
|
||||
end,
|
||||
--- Swaps itemstacks in slots A and B
|
||||
itemMove = function(turtle, itemslotA, itemslotB)
|
||||
if (not isValidInventoryIndex(itemslotA)) or (not isValidInventoryIndex(itemslotB)) then
|
||||
turtle:yield("Inventorying")
|
||||
return false
|
||||
end
|
||||
---Scan gets the info of this node
|
||||
function TurtleEntity:scanForward() return self:scan(self:getLocForward()) end
|
||||
function TurtleEntity:scanBackward() return self:scan(self:getLocBackward()) end
|
||||
function TurtleEntity:scanUp() return self:scan(self:getLocUp()) end
|
||||
function TurtleEntity:scanDown() return self:scan(self:getLocDown()) end
|
||||
function TurtleEntity:scanRight() return self:scan(self:getLocRight()) end
|
||||
function TurtleEntity:scanLeft() return self:scan(self:getLocLeft()) end
|
||||
|
||||
local stackA = turtle.inv:get_stack("main",itemslotA)
|
||||
local stackB = turtle.inv:get_stack("main",itemslotB)
|
||||
function TurtleEntity:yield(reason,useFuel)
|
||||
-- Yield at least once
|
||||
if (coroutine.running() == self.coroutine) then
|
||||
coroutine.yield(reason)
|
||||
end
|
||||
--Use a fuel if requested
|
||||
if useFuel then self:useFuel() end
|
||||
end
|
||||
|
||||
minetest.debug(dump(stackA:to_string()))
|
||||
minetest.debug(dump(stackB:to_string()))
|
||||
--Inventory Interface
|
||||
-- MAIN INVENTORY COMMANDS--------------------------
|
||||
--- TODO drops item onto ground
|
||||
function TurtleEntity:itemDrop(itemslot)
|
||||
|
||||
turtle.inv:set_stack("main",itemslotA,stackB)
|
||||
turtle.inv:set_stack("main",itemslotB,stackA)
|
||||
end
|
||||
--- TODO Returns ItemStack on success or nil on failure
|
||||
---Ex: turtle:itemGet(3):get_name() -> "default:stone"
|
||||
function TurtleEntity:itemGet(turtleslot)
|
||||
return self:getTurtleslot(turtleslot)
|
||||
end
|
||||
--- Swaps itemstacks in slots A and B
|
||||
function TurtleEntity:itemMove(turtleslotA, turtleslotB)
|
||||
if (not isValidInventoryIndex(turtleslotA)) or (not isValidInventoryIndex(turtleslotB)) then
|
||||
self:yield("Inventorying")
|
||||
return false
|
||||
end
|
||||
|
||||
turtle:yield("Inventorying")
|
||||
return true
|
||||
end,
|
||||
--- TODO Pushes item into forward-facing chest
|
||||
--- TODO after getting this working, add a general function with whitelists
|
||||
itemPush = function(turtle, itemslot, listname)
|
||||
listname = listname or "main"
|
||||
end,
|
||||
--- TODO craft using top right 3x3 grid, and put result in itemslotResult
|
||||
itemCraft = function(turtle,itemslotResult)
|
||||
end,
|
||||
itemRefuel = function(turtle,itemslot)
|
||||
local burntime = 0--TODO get burntime
|
||||
-- If fuels are defined like this, how do I get the burntime back, given an item?
|
||||
--[[ minetest.register_craft("",{
|
||||
type = "fuel",
|
||||
recipe = "bucket:bucket_lava",
|
||||
burntime = 60,
|
||||
replacements = {{"bucket:bucket_lava", "bucket:bucket_empty"}},
|
||||
})]]
|
||||
local stackA = self:getTurtleslot(turtleslotA)
|
||||
local stackB = self:getTurtleslot(turtleslotB)
|
||||
|
||||
turtle.fuel = turtle.fuel + burntime * computertest.config.fuel_multiplier
|
||||
end,
|
||||
-- MAIN TURTLE INTERFACE END---------------------------------------
|
||||
})
|
||||
self:setTurtleslot(turtleslotA,stackB)
|
||||
self:setTurtleslot(turtleslotB,stackA)
|
||||
|
||||
self:yield("Inventorying")
|
||||
return true
|
||||
end
|
||||
|
||||
function TurtleEntity:itemPushForward(turtleslot, listname) return self:itemPush(turtleslot, self:getLocForward(), listname) end
|
||||
function TurtleEntity:itemPushBackward(turtleslot, listname) return self:itemPush(turtleslot, self:getLocBackward(), listname) end
|
||||
function TurtleEntity:itemPushUp(turtleslot, listname) return self:itemPush(turtleslot, self:getLocUp(), listname) end
|
||||
function TurtleEntity:itemPushDown(turtleslot, listname) return self:itemPush(turtleslot, self:getLocDown(), listname) end
|
||||
function TurtleEntity:itemPushRight(turtleslot, listname) return self:itemPush(turtleslot, self:getLocRight(), listname) end
|
||||
function TurtleEntity:itemPushLeft(turtleslot, listname) return self:itemPush(turtleslot, self:getLocLeft(), listname) end
|
||||
|
||||
--- TODO craft using top right 3x3 grid, and put result in itemslotResult
|
||||
function TurtleEntity:itemCraft(itemslotResult)
|
||||
-- Use minetest.get_craft_result
|
||||
end
|
||||
--- @returns True if fuel was consumed. False if itemslot did not have fuel.
|
||||
function TurtleEntity:itemRefuel(turtleslot)
|
||||
if (not isValidInventoryIndex(turtleslot)) then
|
||||
return false
|
||||
end
|
||||
|
||||
local stack = self:getTurtleslot(turtleslot)
|
||||
|
||||
local burntime = 0
|
||||
local quantity = stack:get_count()
|
||||
|
||||
--TODO how do you get burntime?
|
||||
--minetest.debug(dump(minetest.get_all_craft_recipes("default:coal_lump")))
|
||||
if (stack:get_name() == "default:coal_lump") then burntime = 40 end
|
||||
|
||||
if (burntime <= 0) then
|
||||
return false
|
||||
end
|
||||
|
||||
self:setTurtleslot(turtleslot,nil)
|
||||
self.fuel = self.fuel + quantity * burntime * computertest.config.fuel_multiplier
|
||||
self:yield("Fueling")
|
||||
return true
|
||||
end
|
||||
|
||||
function TurtleEntity:getFuel() return self.fuel end
|
||||
function TurtleEntity:setName(name) self.name = minetest.formspec_escape(name) end
|
||||
|
||||
function TurtleEntity:debug(string)
|
||||
if computertest.config.debug then
|
||||
minetest.debug("computertest turtle #"..self.id..": "..string)
|
||||
end
|
||||
end
|
||||
function TurtleEntity:dump(object) return dump(object) end
|
||||
|
||||
-- MAIN TURTLE INTERFACE END---------------------------------------
|
||||
-- MAIN TURTLE INTERFACE END---------------------------------------
|
||||
-- MAIN TURTLE INTERFACE END---------------------------------------
|
||||
|
||||
minetest.register_entity("computertest:turtle", TurtleEntity)
|
|
@ -0,0 +1,53 @@
|
|||
|
||||
-- Generally speaking, "do action" functions return true on success and false on failure
|
||||
-- See example #1 for information on how to run code
|
||||
|
||||
function init(turtle)
|
||||
---set_name can be used in identification
|
||||
turtle:set_name("Mining Turtle")
|
||||
|
||||
---Move commands move the turtle relative to its heading, and take one "turtle_tick"
|
||||
---They return true if the turtle successfully moved
|
||||
turtle:moveForward()
|
||||
turtle:moveBackward()
|
||||
turtle:moveRight()
|
||||
turtle:moveLeft()
|
||||
turtle:moveUp()
|
||||
turtle:moveDown()
|
||||
---Turn commands turn the turtle 90 degrees, and take one "turtle_tick"
|
||||
turtle:turnLeft()
|
||||
turtle:turnRight()
|
||||
---Mine commands break and take the items of the block, take one "turtle_tick", and use fuel
|
||||
---They return true if the block was removed
|
||||
turtle:mineForward()
|
||||
turtle:mineBackward()
|
||||
turtle:mineRight()
|
||||
turtle:mineLeft()
|
||||
turtle:mineUp()
|
||||
turtle:mineDown()
|
||||
|
||||
---Informational commands happen instantly (don't yield)
|
||||
turtle:get_pos()--Returns something like {x = -277,y = 6,z = -1558}
|
||||
---More turtle commands can be viewed in entity/turtle.lua (only use the ones in the interface)
|
||||
|
||||
---For inventory commands, remember that lua starts from one (itemslots go from 1-16)
|
||||
turtle:itemMove(1,2) -- Swap item in slot 1 with item in slot two
|
||||
turtle:itemRefuel(5) -- Takes fuel from slot five. The turtle needs fuel to do actions
|
||||
|
||||
--- Build commands take an Itemstack and place it as a Node. Give them an inventory itemnumber (1-16)
|
||||
--- Build commands yield and use fuel
|
||||
turtle:buildForward(1) -- Places the block in slot one infront of it
|
||||
turtle:buildBackward(1)
|
||||
turtle:buildLeft(5) -- Places the block in slot five (under slot one) to the left of it
|
||||
turtle:buildRight(1)
|
||||
turtle:buildUp(1)
|
||||
turtle:buildDown(1)
|
||||
|
||||
|
||||
---IMPORTANT:
|
||||
---Yield waits one "turtle_tick" (the time this takes can be changed in the config). It does not use fuel.
|
||||
---You MUST yield in anything that could be an infinite loop, or else your turtle will deadlock and die
|
||||
--- Infinite loops, such as "while true do end", MUST be replaced with "while true do turtle:yield() end"
|
||||
--- or else the server thread will freeze (This is VERY bad, the server would need restarting)
|
||||
turtle:yield(reason_string) -- Reason string can be something like "Ran out of Fuel"
|
||||
end
|
|
@ -1,17 +1,11 @@
|
|||
--This simple example will make the turtle spin clockwise forever (or until it hits an obstacle)
|
||||
--1. Install the ComputerTest mod
|
||||
--2. Get a turtle block (craftable from one dirt)
|
||||
--3. Place the block
|
||||
--4. Click "Upload Code"
|
||||
--5. Paste this into the large field (sometimes Minetest requires you to paste twice) and click "Upload Code"
|
||||
--6. Watch as it digs a hole!
|
||||
|
||||
---The turtle calls init(turtle) on startup
|
||||
---@param turtle self This is the turtle, and has many functions (see example 3 for more)
|
||||
---@param turtle self This is the turtle, and has many functions (see the API example for more)
|
||||
function init(turtle)
|
||||
while true do
|
||||
--All actions (moving, turning, mining, etc) take one "turtle_tick"
|
||||
-- to run to prevent destruction and increase realism
|
||||
--Many actions (moving, turning, mining, etc) take one "turtle_tick"
|
||||
-- to run to prevent destruction and increase realism. They also use fuel.
|
||||
turtle:moveForward()
|
||||
turtle:turnRight()
|
||||
end
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
--WARNING this is the answers to example #2, look at example #2 first!
|
||||
--WARNING this is the answers to example #2, look at example #2 first!
|
||||
--WARNING this is the answers to example #2, look at example #2 first!
|
||||
--WARNING this is the answers to example #2, look at example #2 first!
|
||||
--WARNING this is the answers to example #2, look at example #2 first!
|
||||
|
||||
--One solution is to do the same thing, just with more loops
|
||||
local function quarry_with_loops(turtle, size)
|
||||
local x,y,z = 0,0,0
|
||||
while true do
|
||||
while true do
|
||||
while x<size-1 do
|
||||
turtle:mineForward()
|
||||
if turtle:moveForward() then x=x+1 end
|
||||
end
|
||||
while x>0 do
|
||||
if turtle:moveBackward() then x=x-1 end
|
||||
end
|
||||
if z==size then break end
|
||||
turtle:mineRight()
|
||||
if turtle:moveRight() then z=z+1 end
|
||||
end
|
||||
while z>0 do
|
||||
if turtle:moveLeft() then z=z-1 end
|
||||
end
|
||||
if y==size then break end
|
||||
turtle:mineDown()
|
||||
if turtle:moveDown() then y=y+1 end
|
||||
end
|
||||
while y>0 do
|
||||
if turtle:moveUp() then y=y-1 end
|
||||
end
|
||||
end
|
||||
|
||||
local function quarry_with_lambdas(turtle, size)
|
||||
local function quarryLine(moveIn,moveOut,lineEnd)
|
||||
for _ = 1, size-1 do moveIn() end
|
||||
lineEnd()
|
||||
for _ = 1, size-1 do moveOut() end
|
||||
end
|
||||
local quarryX = (function()
|
||||
quarryLine(
|
||||
(function() turtle:mineForward() turtle:moveForward() end),
|
||||
(function()turtle:moveBackward()end),
|
||||
(function()end))
|
||||
end)
|
||||
local quarryY = (function()
|
||||
quarryLine(
|
||||
(function() quarryX()turtle:mineRight();turtle:moveRight();end),
|
||||
(function()turtle:moveLeft()end),
|
||||
(function()quarryX()end))
|
||||
end)
|
||||
local quarryZ = (function()
|
||||
quarryLine(
|
||||
(function() quarryY() turtle:mineDown() turtle:moveDown() end),
|
||||
(function() turtle:moveUp() end),
|
||||
(function()quarryY()end))
|
||||
end )
|
||||
quarryZ()
|
||||
end
|
||||
function init(turtle)
|
||||
quarry_with_lambdas(turtle,3)
|
||||
turtle:turnRight()
|
||||
turtle:turnRight()
|
||||
quarry_with_loops(turtle,3)
|
||||
end
|
|
@ -3,29 +3,21 @@
|
|||
|
||||
--See example #1 on how to upload code
|
||||
function init(turtle)
|
||||
local size = 3
|
||||
local y=0
|
||||
while y<size do
|
||||
local z=0
|
||||
while z <size do
|
||||
local x=1
|
||||
while x<size do
|
||||
turtle:mineForward()
|
||||
if turtle:moveForward() then x=x+1 end
|
||||
end
|
||||
while x>0 do
|
||||
if turtle:moveBackward() then x=x-1 end
|
||||
end
|
||||
turtle:mineRight()
|
||||
if turtle:moveRight() then z = z +1 end
|
||||
local size = 5
|
||||
local z =0
|
||||
while z <size do
|
||||
local x=0
|
||||
while x<size do
|
||||
turtle:mineForward()
|
||||
if turtle:moveForward() then x=x+1 end
|
||||
end
|
||||
while z >0 do
|
||||
if turtle:moveLeft() then z = z -1 end
|
||||
while x>0 do
|
||||
if turtle:moveBackward() then x=x-1 end
|
||||
end
|
||||
turtle:mineDown()
|
||||
if turtle:moveDown() then y=y+1 end
|
||||
turtle:mineRight()
|
||||
if turtle:moveRight() then z = z +1 end
|
||||
end
|
||||
while y>0 do
|
||||
if turtle:moveUp() then y=y-1 end
|
||||
while z >0 do
|
||||
if turtle:moveLeft() then z = z -1 end
|
||||
end
|
||||
end
|
|
@ -1,31 +0,0 @@
|
|||
|
||||
-- Generally speaking, "do action" functions return true on success and false on failure
|
||||
|
||||
function init(turtle)
|
||||
---Yield waits one "turtle_tick"
|
||||
turtle:yield()
|
||||
---Move commands move the turtle relative to its heading, and take one "turtle_tick"
|
||||
---They return true if the turtle successfully moved
|
||||
turtle:moveForward()
|
||||
turtle:moveBackward()
|
||||
turtle:moveRight()
|
||||
turtle:moveLeft()
|
||||
turtle:moveUp()
|
||||
turtle:moveDown()
|
||||
---Turn commands turn the turtle 90 degrees, and take one "turtle_tick"
|
||||
turtle:turnLeft()
|
||||
turtle:turnRight()
|
||||
---Mine commands break and take the items of the block, and take one "turtle_tick"
|
||||
---They return true if the block was removed
|
||||
turtle:mineForward()
|
||||
turtle:mineBackward()
|
||||
turtle:mineRight()
|
||||
turtle:mineLeft()
|
||||
turtle:mineUp()
|
||||
turtle:mineDown()
|
||||
---Informational commands happen instantly (don't yield)
|
||||
turtle:get_pos()--Returns something like {x = -277,y = 6,z = -1558}
|
||||
---More turtle commands can be viewed in entity/turtle.lua (only use the ones in the interface)
|
||||
|
||||
---For inventory commands, remember that lua starts from one (itemslots go from 1-16)
|
||||
end
|
|
@ -1,5 +1,20 @@
|
|||
--If you're low on fuel, grab fuel from somewhere
|
||||
local function checkFuel(turtle)
|
||||
while turtle:getFuel() < 10 do
|
||||
for i = 1, 16 do
|
||||
turtle:itemRefuel(i)
|
||||
end
|
||||
--If you're completely out of fuel, then wait
|
||||
--(If you don't do this, the turtle will deadlock all turtles)
|
||||
if turtle:getFuel() < 10 then turtle:yield("No fuel") end
|
||||
end
|
||||
end
|
||||
|
||||
function init(turtle)
|
||||
while true do
|
||||
checkFuel(turtle)
|
||||
turtle:itemMove(1,2)
|
||||
turtle:turnRight()
|
||||
turtle:buildForward(4)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,29 @@
|
|||
function buildcube(turtle, size)
|
||||
local function buildLine(moveIn,moveOut,each)
|
||||
for _ = 1, size-1 do moveIn() end
|
||||
for _ = size-1,1,-1 do
|
||||
each()
|
||||
moveOut()
|
||||
end
|
||||
each()
|
||||
end
|
||||
local buildX = (function()
|
||||
buildLine((function()turtle:moveForward()end),
|
||||
(function()turtle:moveBackward()end),
|
||||
(function()turtle:buildForward(1)end))
|
||||
end)
|
||||
local buildY = (function()
|
||||
buildLine((function()turtle:moveRight()end),
|
||||
(function()turtle:moveLeft()end),
|
||||
(function()buildX()end))
|
||||
end)
|
||||
local buildZ = (function()
|
||||
buildLine((function()turtle:moveUp()end),
|
||||
(function()turtle:moveDown()end),
|
||||
(function()buildY()end))
|
||||
end)
|
||||
buildZ()
|
||||
end
|
||||
function init(turtle)
|
||||
buildcube(turtle,3)
|
||||
end
|
|
@ -0,0 +1,51 @@
|
|||
local function buildAny(turtle)
|
||||
local ret = false
|
||||
for i = 1, 16 do
|
||||
ret = ret or turtle:buildForward(i)
|
||||
end
|
||||
return ret
|
||||
end
|
||||
---Builds a sphere centered on the turtle
|
||||
local function buildSphere(turtle, radius)
|
||||
local x,y,z = 0,0,0
|
||||
local function shouldBuildSphere(x,y,z) return x*x + y*y + z*z <= radius * radius end
|
||||
local function moreLeftToBuild()
|
||||
for x2=x,radius do
|
||||
if shouldBuildSphere(x2,y,z,radius) then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
turtle:moveBackward()
|
||||
for _=1,radius do
|
||||
turtle:moveLeft();z=z-1
|
||||
turtle:moveBackward();x=x-1
|
||||
turtle:moveDown();y=y-1
|
||||
end
|
||||
while true do
|
||||
while true do
|
||||
while x<=radius and moreLeftToBuild() do
|
||||
if turtle:moveForward() then x=x+1 end
|
||||
end
|
||||
while x>-radius do
|
||||
if turtle:moveBackward() then x=x-1 end
|
||||
if shouldBuildSphere(x,y,z) then
|
||||
while not buildAny(turtle) do
|
||||
turtle:yield("NO BLOCKS")
|
||||
end
|
||||
end
|
||||
end
|
||||
if z==radius then break end
|
||||
if turtle:moveRight() then z=z+1 end
|
||||
end
|
||||
if y==radius then break end
|
||||
while z>-radius do
|
||||
if turtle:moveLeft() then z=z-1 end
|
||||
end
|
||||
if turtle:moveUp() then y=y+1 end
|
||||
end
|
||||
end
|
||||
|
||||
function init(turtle)
|
||||
for _=1,21 do turtle:moveUp() end
|
||||
buildSphere(turtle,20)
|
||||
end
|
|
@ -0,0 +1,77 @@
|
|||
--- Mines an entire forest
|
||||
|
||||
local x,y,z = 0,0,0
|
||||
local turtle = nil
|
||||
|
||||
function dumpItems()
|
||||
local sx,sy,sz = x,y,z -- save
|
||||
moveTo(0,0,0)
|
||||
turtle:turnLeft()
|
||||
turtle:turnLeft()
|
||||
for i = 1, 16 do turtle:itemPushForward(i) end
|
||||
turtle:turnLeft()
|
||||
turtle:turnLeft()
|
||||
moveTo(sx,sy,sz)
|
||||
end
|
||||
|
||||
function moveTo(tox,toy,toz)
|
||||
while y<toy do turtle:mineUp() ; turtle:moveUp() ; y=y+1 end
|
||||
while x>tox do turtle:mineBackward() ; turtle:moveBackward() ; x=x-1 end
|
||||
while x<tox do turtle:mineForward() ; turtle:moveForward() ; x=x+1 end
|
||||
while z>toz do turtle:mineLeft() ; turtle:moveLeft() ; z=z-1 end
|
||||
while z<toz do turtle:mineRight() ; turtle:moveRight() ; z=z+1 end
|
||||
while y>toy do turtle:mineDown() ; turtle:moveDown() ; y=y-1 end
|
||||
x=tox; y=toy; z=toz
|
||||
end
|
||||
|
||||
function scan()
|
||||
local scanlist = {}
|
||||
table.insert(scanlist,{0,0,0})
|
||||
local function processScan(point,name)
|
||||
local names = {'default:tree', 'default:leaves',
|
||||
'default:jungletree','default:jungleleaves',
|
||||
'default:pine_tree','default:pine_needles',
|
||||
'default:acacia_tree','default:acacia_leaves',
|
||||
'default:aspen_tree','default:aspen_leaves'}
|
||||
local function has(array,value)
|
||||
for k,v in pairs(array) do
|
||||
if value==v then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
if has(names,name) then
|
||||
turtle:debug("name: "..turtle:dump(name))
|
||||
table.insert(scanlist,point)
|
||||
end
|
||||
end
|
||||
local function mineHere()
|
||||
local point = table.remove(scanlist,#scanlist)
|
||||
moveTo(point[1],point[2],point[3])
|
||||
|
||||
processScan({point[1]-1,point[2],point[3]},turtle:scanBackward().name)
|
||||
processScan({point[1]+1,point[2],point[3]},turtle:scanForward() .name)
|
||||
|
||||
processScan({point[1],point[2]-1,point[3]},turtle:scanDown() .name)
|
||||
processScan({point[1],point[2]+1,point[3]},turtle:scanUp() .name)
|
||||
|
||||
processScan({point[1],point[2],point[3]-1},turtle:scanLeft() .name)
|
||||
processScan({point[1],point[2],point[3]+1},turtle:scanRight() .name)
|
||||
|
||||
table.sort(scanlist,(function(a,b) return a[1] < b[1] end))
|
||||
end
|
||||
while next(scanlist) do
|
||||
mineHere()
|
||||
end
|
||||
moveTo(0,0,0)
|
||||
end
|
||||
|
||||
function init(turtleL)
|
||||
turtle = turtleL
|
||||
while true do
|
||||
scan()
|
||||
turtle:yield("Done Scanning")
|
||||
end
|
||||
end
|
|
@ -0,0 +1,29 @@
|
|||
---Dumps everything from the turtle forward if it's in the list
|
||||
--- @param list - Something like {"default:stone","default:dirt"}
|
||||
--- @param isWhitelist - If true, only dump things in list.
|
||||
--- If false, dump everything EXCEPT the items in the list
|
||||
--- @param listname @see itemPush
|
||||
--- @return boolean true unless any items can't fit
|
||||
local function itemPushForwardFiltered(turtle, list, isWhitelist, listname)
|
||||
local success = true
|
||||
local function intable(item)
|
||||
for _,x in pairs(list) do
|
||||
if item==x then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
for turtleslot = 1, 16 do
|
||||
if intable(turtle:itemGet(turtleslot):get_name()) == isWhitelist then
|
||||
success = success and turtle:itemPushForward(turtleslot,listname)
|
||||
end
|
||||
end
|
||||
return success
|
||||
end
|
||||
function init(turtle)
|
||||
while true do
|
||||
itemPushForwardFiltered(turtle,{"default:cobble","default:sand"}, true, "src")
|
||||
itemPushForwardFiltered(turtle,{"default:coal_lump"}, true, "fuel")
|
||||
turtle:itemSuckForward({"default:stone","default:glass"},true,"dst")
|
||||
turtle:yield("Waiting")
|
||||
end
|
||||
end
|
|
@ -0,0 +1,13 @@
|
|||
# Examples
|
||||
See README.md for how to run examples.
|
||||
|
||||
# 0-api.lua
|
||||
|
||||
The 0-api.lua contains all of the things a turtle can do!
|
||||
|
||||
# 2-quarry.lua
|
||||
|
||||
This contains a practice on learning how to write turtle code.
|
||||
See 2-quarry-answers.lua when you're done!
|
||||
|
||||
# 7-lumber.lua will walk down trees and mine them
|
9
init.lua
9
init.lua
|
@ -1,9 +1,14 @@
|
|||
computertest = {
|
||||
config = {
|
||||
--Turtles are yielded after calling long events, and resumed this often (in seconds)
|
||||
turtle_tick = 0.5,
|
||||
--This is how long timed turtle actions take, such as mining, moving, and placing
|
||||
turtle_tick = .5,--Default: 0.5 seconds
|
||||
--Fuel is measured in burntime seconds. A 1 second fuel allows this many actions
|
||||
fuel_multiplier = 50,
|
||||
fuel_multiplier = 50,--Default: 50 actions per fuel second
|
||||
--Fuel inside of turtles when they spawn
|
||||
fuel_initial = 10000000000,--Default: 1000 actions
|
||||
--Allows turtle:debug(string) to go to debug.txt
|
||||
debug = true,
|
||||
},
|
||||
turtles = {},
|
||||
num_turtles = 0,
|
||||
|
|
Loading…
Reference in New Issue