From a5c2ff2ee1992f4a66ccae768fb4a3c8fe499e4f Mon Sep 17 00:00:00 2001 From: Ciaran Gultnieks Date: Tue, 19 May 2015 23:29:08 +0100 Subject: [PATCH] A ton of stuff --- README.md | 131 +++++++++++++--- actions/building.lua | 213 ++++++++++++++++++++++++++ actions/buy_and_sell.lua | 13 +- actions/claimplot.lua | 39 +++++ actions/craft.lua | 80 ++++++++++ actions/dig.lua | 3 +- actions/go.lua | 180 +++++++++++++--------- actions/obtain.lua | 67 +++++++++ actions/place.lua | 40 ++++- actions/stash_and_retrieve.lua | 57 ++++--- actions/tunnel.lua | 268 +++++++++++++++++++++++++++++++++ commands.lua | 42 ++++++ form.lua | 34 ++++- init.lua | 30 +++- presets/FarmHand.lua | 57 ++++--- presets/Miner.lua | 231 ++++++++++++++++++++++++++++ presets/TreeFarmer.lua | 8 +- 17 files changed, 1330 insertions(+), 163 deletions(-) create mode 100644 actions/building.lua create mode 100644 actions/claimplot.lua create mode 100644 actions/craft.lua create mode 100644 actions/obtain.lua create mode 100644 actions/tunnel.lua create mode 100644 presets/Miner.lua diff --git a/README.md b/README.md index 2be2628..0b40a26 100644 --- a/README.md +++ b/README.md @@ -134,7 +134,14 @@ it fails to do so, the person will do an automatic wait. ### actfail -This happens when an action fails. +This happens when an action fails, unless there is a 'prevaction' set. (To +get a fail, but still have a follow-on action, use 'successaction' instead!) + +There is also event.failedaction available, which is the action that actually +failed, and event.failcode which is the failure code that the action returned. +That is normally just false, but some action handlers can return special +values here (anything non-true would be a fail) to indicate particular +things. ### tell @@ -186,14 +193,21 @@ position. The following actions are currently defined: -#### go, with pos={x,y,z} -Walks to the given position. +#### go -#### go, with name="string" -Walks to a named position - names are looked up using the areas module, if -present This converts to go, pos={x,y,z}, with name retained for reference. +Go (walk) to a location. This generally decomposes into a more complex set +of actions, the ultimate aim of which is to get to the given destination +somehow. -Names are most frequently used in conjunction with footpaths. +One of the following parameters is needed: + +* pos={x,y,z} - specifies an absolute position to go to +* rel={x,y,z} - to go a relative offset from the current position +* name=string - specifies a footpath node or area (see the Navigation + section + +Many other parameters are handled by the go action, but these are used +internally only. #### follow, with name="playername" Follows the given player indefinitely (or at least until they log off). @@ -220,12 +234,17 @@ Dig the node at the given position. Must be within range. #### rightclicknode, with pos={x,y,z} Right-click the node at the given position. Must be within range. -#### place, with pos={x,y,z} and item=name -Places the given item. Must be in the inventory, and in range. +#### place +Places an item (node). Must be in the inventory, and in range. + +* Required parameter: pos={x,y,z} +* Required parameter: item=itemname +* Optional parameter: param2=value (e.g. for setting facedir) +* Optional parameter: againstdir={x,y,z} #### stash -* Required parameter: items={"item1", ..."itemn"} +* Required parameter: items={item1, ..., itemn"} * Optional parameter: pos={x,y,z} - position of node to stash in * Optional parameter: dest="node" - node name to stash in (e.g. "default:chest") @@ -237,22 +256,24 @@ Specifically, it needs to be a node with a "main" inventory list. So for a normal chest, use "default:chest" for the nodename. Each item in the list can be: -* Just the item name (e.g. "default:cobble") to put one item in +* Just the item name (e.g. "default:cobble") to put all held in * Item name and count (e.g. "default:cobble 50") to put that many in -* Item name preceded by an asterisk (e.g. "\*default:cobble") , to put all - carried in +* Table ({"default:cobble", keep=5}) to keep 5 but put the rest in -#### retrieve, with src="nodename" and items={"item1", "itemn"} +#### retrieve Gets items from a source node, which should be something like a chest. Specifically, it needs to be a node with a "main" inventory list. So for a normal chest, use "default:chest" for the nodename. -Each item in the list can be: -* Just the item name (e.g. "default:cobble") to get one item out -* Item name and count (e.g. "default:cobble 50") to get that many out -* Item name preceded by an asterisk (e.g. "\*default:cobble") , to get all - of that kind of item out +* Required parameter: items={item1, ..., itemn"} +* Optional parameter: pos={x,y,z} - position of node to stash in +* Optional parameter: src="node" - node name to retrieve from + (e.g. "default:chest") + +(You need either pos or dest, or nothing will happen) + +The items in the list are defined in the same way as for the stash action. #### sell @@ -266,13 +287,32 @@ afford to buy it all. #### buy -* Required parameter: item="itemstring" +* Required parameter: item="itemname" * Required parameter: num=amount Need to be standing within range of a shop (barrel mod) that sells the item in question, and have enough money. If the person doesn't have enough money to buy the amount specified, a lesser amount may be bought instead. +#### obtain + +Meta-action to obtain some of an item by whatever means possible. This could +use crafting, buying, mining, whatever, the only aim is to get it. + +The specified amount is the total amount we want to end up having - so if we +already have that many, the action will not do anything. Otherwise, it could +use one or more of the available methods until the total is achieved. + +* Required paramter: item="itemname" +* Required parameter: num=amount + +#### craft + +Craft an item. + +* Required paramter: item="itemname" +* Required parameter: num=amount + #### gather Changes current gathering settings. @@ -322,6 +362,57 @@ The correct item should already be wielded. This is normally used in conjunction with gathering, in which case the wielding is already handled. +#### tunnel + +Tunnel, from the current position. Also builds bridges! + +Parameters: + +* `dir = {x,y,z}` - direction - either x or z must be 0. and the + other +/-1, while y can be 0 or +/-1. +* 'distance = n` - distance to go for (reduces as the action + proceeds) +* `floor=item` - material to use for floor - optional +* `wall=item` - material to use for wall linings - optional +* `ceiling=item` - material to use for ceiling - optional +* `steps=item` - material to use for steps, on slopes. +* `lighting=item` - material (most likely default:torch) to use for + lighting, which is placed against walls at + 4 metre intervals. Optional. +* `support_top=item` - material to use for top supports, which are placed + against the ceiling at 8 metre intervals. Optional. +* `support_ceiling=item` - like support_top, but at ceiling level, not below. +* `support_side=item` - material to use for side supports. Optional. +* `support_side_facein=? - set to true to make the side supports face in + towards the tunnel centre. (Try it with slabs, for + example!) +* `support_wall=item` - like support_side, but at wall level, not within. + +#### building + +Make a building, from current position. + +The current position is designated as the entrance to the building. + +Parameters: + +* `dir = {x,y,z}` - direction - either x or z must be 0. and the + other +/-1, while y can be 0 or +/-1. +* `width = n` - the width of the building +* `depth = n` - the depth of the building +* `height = n` - the height of the building +* `wall = item` - material to use for walls +* `outside_floor = item` - material to use for the floor outside the walls. + Optional. Does not include the entrance. + This can also be a table, with two entries, in + which case the floor can be either of those, and + if it's not it's replaced with the first. (Example + use case of that is if it's dirt, in which case + it can change to dirt_with_grass, and be left + alone!) +* `door=item` - Door to use. Doorways will be left open if not + specified. + #### step Allow's the person's lua code to handle on_step itself. It will receive "step" events (with dtime) and should reset the action when it no longer diff --git a/actions/building.lua b/actions/building.lua new file mode 100644 index 0000000..a5385ed --- /dev/null +++ b/actions/building.lua @@ -0,0 +1,213 @@ + +local dbg +if moddebug then dbg=moddebug.dbg("people") else dbg={v1=function() end,v2=function() end,v3=function() end} end + +local function rotate(rel, dir) + if dir.z < 0 then + return {x=-rel.x, y=rel.y, z=-rel.z} + elseif dir.z > 0 then + return {x=rel.x, y=rel.y, z=rel.z} + elseif dir.x > 0 then + return {x=rel.z, y=rel.y, z=-rel.x} + else + return {x=-rel.z, y=rel.y, z=rel.x} + end +end + +people.actions.building = function(state) + + if type(state.action.dir) ~= "table" then + dbg.v1(state.ent.name.." has invalid dir for building action") + return true, false + end + if state.action.dir.y ~= 0 then + dbg.v1(state.ent.name.." has invalid y component of dir for building action") + return true, false + end + if math.abs(state.action.dir.x) + math.abs(state.action.dir.z) ~= 1 then + dbg.v1(state.ent.name.." has invalid x/z components of dir for building action") + return true, false + end + if type(state.action.height) ~= "number" or state.action.height < 1 + or state.action.height > 5 then + dbg.v1(state.ent.name.." has invalid height for building action") + return true, false + end + if type(state.action.width) ~= "number" or state.action.width < 1 + or state.action.width > 15 or state.action.width % 2 == 0 then + dbg.v1(state.ent.name.." has invalid width for building action") + return true, false + end + if type(state.action.depth) ~= "number" or state.action.depth < 1 + or state.action.depth > 15 then + dbg.v1(state.ent.name.." has invalid depth for building action") + return true, false + end + + if not state.action.startpos then + state.action.startpos = vector.round(state.pos) + end + + if not state.action.cpos then + state.action.cpos = 0 + end + + -- The following only pans out if the width is an odd + -- number! TODO + local toleft = math.ceil(state.action.width / 2) + local toback = state.action.depth + 1 + local toright = state.action.width + 1 + local tofront = toback + local toleft2 = toleft + + if state.action.atcpos then + + -- So we're at a position, cpos... + -- 0 = in front of the entrance + -- 1 - (toleft - 1) = along front wall, including corner + + if state.action.wall then + local wallpos + local starty = 0 + if state.action.cpos > 0 and state.action.cpos < toleft then + wallpos = {x=0, z=1} + elseif state.action.cpos > toleft and state.action.cpos < toleft + toback - 1 then + -- Wall down left side of building... + wallpos = {x=1, z=0} + elseif state.action.cpos > toleft + toback and state.action.cpos < toleft + toback + toright then + -- Back wall + wallpos = {x=0, z=-1} + elseif state.action.cpos > toleft + toback + toright and state.action.cpos < toleft + toback + toright + tofront then + -- Wall down right side of building... + wallpos = {x=-1, z=0} + elseif state.action.cpos >= toleft + toback + toright + tofront + 1 and state.action.cpos < toleft + toback + toright + tofront + toleft2 then + wallpos = {x=0, z=1} + elseif state.action.cpos == toleft + toback + toright + tofront + toleft2 then + -- This should be the very last point on the circuit, which is + -- in front of the door! + wallpos = {x=0, z=1} + starty = 2 + end + + if wallpos then + + for y = 0, state.action.height - 1 do + local thisoff = {x=wallpos.x, y=y, z=wallpos.z} + local woff = rotate(thisoff, state.action.dir) + local thispos = vector.add(state.pos, woff) + local dignode = minetest.get_node(thispos) + -- Note that we have to substring the dignode, because + -- the placed bottom half of the door doesn't have the + -- same name as the item it came from. + if y == 0 and starty == 2 and state.action.door and + dignode.name:sub(1, state.action.door:len()) + ~= state.action.door then + -- We place the door against the side, just to allow for + -- there being a right-click handling node (e.g. + -- junction) below it. + todir = {x=-1,y=0,z=0} + todir = rotate(todir, state.action.dir) + state.action = {"place", pos=thispos, + item=state.action.door, + successaction=state.action, + againstdir=todir} + return false + elseif y == starty and starty > 0 and dignode.name == "air" then + -- Similarly, we have to watch out if we're placing + -- above a door (it will open/shut!), or above a hole + -- where a door would go. Maybe the auto-against-dir + -- code in the place action should just be detecting + -- right-clickable things and avoiding them, to save + -- all this. Or 'holding shift'?? + todir = {x=-1,y=0,z=0} + todir = rotate(todir, state.action.dir) + state.action = {"place", pos=thispos, + item=state.action.wall, + successaction=state.action, + againstdir=todir} + return false + elseif y >= starty and dignode.name == "air" then + state.action = {"place", pos=thispos, item=state.action.wall, successaction=state.action} + return false + elseif (y >= starty and dignode.name ~= state.action.wall) + or (y < starty and dignode.name ~= "air" and + dignode.name:sub(1, state.action.door:len()) + ~= state.action.door) then + state.action = {"dig", pos=thispos, successaction=state.action} + return false + end + end + end + end + + state.action.atcpos = false + state.action.cpos = state.action.cpos + 1 + if state.action.cpos > toleft + toback + toright + tofront + toleft2 then + dbg.v1(state.ent.name.." has completed outer circuit of walls") + return true, true + end + + end + + -- Calculate RELATIVE direction to move + local movedir + if state.action.cpos == 0 then + movedir = {x=0, z=-1} + elseif state.action.cpos <= toleft then + movedir = {x=-1, z=0} + elseif state.action.cpos <= toleft + toback then + movedir = {x=0, z=1} + elseif state.action.cpos <= toleft + toback + toright then + movedir = {x=1, z=0} + elseif state.action.cpos <= toleft + toback + toright + tofront then + movedir = {x=0, z=-1} + else + movedir = {x=-1, z=0} + end + movedir.y = 0 + + -- Rotate the movement direction to world orientation... + local wmovedir = rotate(movedir, state.action.dir) + + local newdest = vector.add(vector.round(state.pos), wmovedir) + + if state.action.outside_floor and state.action.cpos > 1 and + state.action.cpos < toleft + toback + toright + tofront + toleft2 then + + local floor = state.action.outside_floor + local altfloor = nil + if type(floor) == "table" then + floor = state.action.outside_floor[1] + altfloor = state.action.outside_floor[2] + end + + local thispos = {x=newdest.x, y=newdest.y - 1, z=newdest.z} + local dignode = minetest.get_node(thispos) + if dignode.name == "air" then + state.action = {"place", pos=thispos, item=floor, successaction=state.action} + return false + elseif dignode.name ~= floor and dignode.name ~= altfloor then + state.action = {"dig", pos=thispos, successaction=state.action} + return false + end + end + + -- Make sure the area outside the walls is clear as we go round + for y = 0, state.action.height - 1 do + local thispos = {x=newdest.x, y=newdest.y + y, z=newdest.z} + local dignode = minetest.get_node(thispos) + if dignode.name ~= "air" then + state.action = {"dig", pos=thispos, successaction=state.action} + return false + end + end + + dbg.v3(state.ent.name.." circling to "..minetest.pos_to_string(newdest).. + ", cpos="..state.action.cpos..", movedir="..minetest.pos_to_string(movedir).. + ", wmovedir="..minetest.pos_to_string(wmovedir)) + state.action.atcpos = true + state.action = {"go", pos=newdest, successaction=state.action} + return false + +end + diff --git a/actions/buy_and_sell.lua b/actions/buy_and_sell.lua index 5f90c98..dc2a7cf 100644 --- a/actions/buy_and_sell.lua +++ b/actions/buy_and_sell.lua @@ -75,6 +75,7 @@ people.actions.buy = function(state) local repeats = math.ceil(state.action.num / shopinfo.saleunit) local cashneeded = repeats * shopinfo.price + dbg.v3(state.ent.name.." trying to put $"..cashneeded.." into shop") if not currency.remove_from_inv(state.ent.inventory, "main", cashneeded) then dbg.v1(state.ent.name.." doesn't have enough money to buy") return true, false @@ -83,8 +84,14 @@ people.actions.buy = function(state) -- to check and roll back, as we do for getting money currency.put_to_inv(shopinv, "exchange", cashneeded) + local bought = 0 while repeats > 0 do - if not barrel.do_buy(shopinfo.pos) then break end + local success, msg = barrel.do_buy(shopinfo.pos) + if not success then + dbg.v3(state.ent.name.." failed during buy loop - shop said: "..msg) + break + end + bought = bought + shopinfo.saleunit repeats = repeats - 1 end @@ -92,12 +99,12 @@ people.actions.buy = function(state) dbg.v1(state.ent.name.." couldn't retrieve everything from shop inventory") end - dbg.v1(state.ent.name.." finished buy action") + dbg.v1(state.ent.name.." finished buy action - bought "..bought.." "..state.action.item) return true, true end people.actions.sell = function(state) - if not (state.action.item) then + if not state.action.item then dbg.v1(state.ent.name.." has invalid sell action") return true, false end diff --git a/actions/claimplot.lua b/actions/claimplot.lua new file mode 100644 index 0000000..ce013e8 --- /dev/null +++ b/actions/claimplot.lua @@ -0,0 +1,39 @@ + + +local dbg +if moddebug then dbg=moddebug.dbg("people") else dbg={v1=function() end,v2=function() end,v3=function() end} end + +people.actions.claimplot = function(state) + + if not state.action.name or type(state.action.name) ~= "string" then + dbg.v1(state.ent.name.." has invalid claimplot action") + return true, false + end + + local destpos = footpath.get_nodepos(name) + if destpos then + dbg.v1(state.ent.name.." can't claim "..state.action.name.." - it already exists") + return true, false + end + + if not state.action.at then + local plot = footpath.nearest_vacant_plot(state.pos) + if not plot then + dbg.v1(state.ent.name.." can't claim "..state.action.name.." - there are no free plots") + return true, false + end + state.action.at = plot.name + state.action = {"go", name=plot.name, successaction=state.action} + return false + end + + local result = footpath.claim_plot(state.action.at, state.action.name) + if result ~= true then + dbg.v1(state.ent.name.." failed to claim plot "..state.action.name.." at "..state.action.at.." - "..result) + return true, false + end + + dbg.v1(state.ent.name.." claimed plot "..state.action.name.." at "..state.action.at) + return true, true + +end diff --git a/actions/craft.lua b/actions/craft.lua new file mode 100644 index 0000000..e151698 --- /dev/null +++ b/actions/craft.lua @@ -0,0 +1,80 @@ + +local dbg +if moddebug then dbg=moddebug.dbg("people") else dbg={v1=function() end,v2=function() end,v3=function() end} end + +people.actions.craft = function(state) + + if type(state.action.num) ~= "number" then + dbg.v1(state.ent.name.." has invalid num for craft action") + return true, false + end + if type(state.action.item) ~= "string" then + dbg.v1(state.ent.name.." has invalid item for craft action") + return true, false + end + -- TODO check it's a real item too! + + local recipes = minetest.get_all_craft_recipes(state.action.item) + if not recipes or #recipes == 0 then + dbg.v2(state.ent.name.." can't craft "..state.action.item.. " - no recipes") + return true, false + end + + local tomake = state.action.num + for _, recipe in pairs(recipes) do + dbg.v3(state.ent.name.." trying recipe "..minetest.serialize(recipe)) + if recipe.type == "normal" then + local output = ItemStack(recipe.output) + local needed = {} + for _, item in pairs(recipe.items) do + + -- TODO - we can't handle groups in recipes here yet, so + -- we'll just translate things as we come across + -- them for now. + if item == "group:stick" then + item = "default:stick" + elseif item == "group:wood" then + item = "default:wood" + end + + if needed[item] then + needed[item] = needed[item] + 1 + else + needed[item] = 1 + end + end + + while tomake > 0 do + local gotingredients = true + for item, count in pairs(needed) do + if not state.ent.inventory:contains_item("main", ItemStack(item.." "..count)) then + dbg.v3(state.ent.name.." doesn't have the "..count.." "..item.." needed") + gotingredients = false + break + end + end + if not gotingredients then break end + for item, count in pairs(needed) do + state.ent.inventory:remove_item("main", ItemStack(item.." "..count)) + end + state.ent.inventory:add_item("main", output) + dbg.v3(state.ent.name.." crafted "..output:get_count().." "..state.action.item) + tomake = tomake - output:get_count() + end + + else + dbg.v3(state.ent.name.." doesn't know how to do '"..recipe.type.."'") + end + end + + if tomake <= 0 then + dbg.v1(state.ent.name.." successfully crafted "..state.action.num.." "..state.action.item) + return true, true + end + + dbg.v1(state.ent.name.." couldn't craft (any or enough) "..state.action.item) + return true, false + +end + + diff --git a/actions/dig.lua b/actions/dig.lua index 4dfd374..d88e957 100644 --- a/actions/dig.lua +++ b/actions/dig.lua @@ -40,6 +40,7 @@ people.actions.dig = function(state) -- * selecting correct tool -- * proper drops (e.g. custom handlers in other mods) local n = minetest.get_node(state.action.pos) + local dign = n.name if false then @@ -94,7 +95,7 @@ people.actions.dig = function(state) return true, false end end - dbg.v1(state.ent.name.." dug "..n.name.." at "..minetest.pos_to_string(state.action.pos)) + dbg.v1(state.ent.name.." dug "..dign.." at "..minetest.pos_to_string(state.action.pos)) return true, true diff --git a/actions/go.lua b/actions/go.lua index 6d2b776..6a29798 100644 --- a/actions/go.lua +++ b/actions/go.lua @@ -14,6 +14,12 @@ local PNTP = 0.5 people.actions.go = function(state) + if state.action.rel then + state.action.pos = vector.add(vector.round(state.pos), state.action.rel) + dbg.v2(state.ent.name.." converted relative go to destination "..minetest.pos_to_string(state.action.pos)) + state.action.rel = nil + end + if (not state.action.pos) and (not state.action.footpathdest) and state.action.name then -- The action says to go to a named place. We're going to convert this @@ -35,7 +41,7 @@ people.actions.go = function(state) -- Find the nearest footpath junction to us. We'll head to that -- first (as a pos) and then follow the footpath network from -- there. - local pathstartnode = footpath.nearest_node(state.pos) + local pathstartnode = footpath.nearest_junction(state.pos) if pathstartnode then dbg.v2(state.ent.name.." selected path start "..pathstartnode.name.." at "..minetest.pos_to_string(pathstartnode.pos)) state.action.pos = vector.new(pathstartnode.pos) @@ -65,9 +71,9 @@ people.actions.go = function(state) if not state.action.pos and state.action.footpathdest then -- path handling - only comes here when we've arrived at a new path square - -- (note, the range passed to nearest_node determines our lookahead distance + -- (note, the range passed to nearest_junction determines our lookahead distance dbg.v3(state.ent.name.." at new footpath node at "..minetest.pos_to_string(state.pos)) - local curpathnode, distance = footpath.nearest_node(state.pos, nil, 10) + local curpathnode, distance = footpath.nearest_junction(state.pos, nil, 10) if curpathnode and curpathnode.pos.x == state.pos.x and curpathnode.pos.z == state.pos.z then if curpathnode.name == state.action.footpathdest then @@ -77,6 +83,36 @@ people.actions.go = function(state) state.action.pos = state.action.endpos return false end + if state.action.faceexit then + local jct = footpath.get_junction(curpathnode.name) + if not jct then + dbg.v1(state.ent.name.." couldn't do faceexit at "..curpathnode.name.." - no such junction") + return true, false + end + local fdir = nil + for dir, exit in pairs(jct.exits) do + if exit == state.action.faceexit then + fdir = dir + break + end + end + if not fdir then + dbg.v1(state.ent.name.." couldn't do faceexit at "..curpathnode.name.." - no such exit") + return truen, false + end + local fpos + if fdir == "n" then + fpos = vector.add(state.pos, {x=0, y=0, z=1}) + elseif fdir == "s" then + fpos = vector.add(state.pos, {x=0, y=0, z=-1}) + elseif fdir == "e" then + fpos = vector.add(state.pos, {x=1, y=0, z=0}) + else + fpos = vector.add(state.pos, {x=-1, y=0, z=0}) + end + state.yaw = vector.get_yaw(state.pos, fpos) + dbg.v2(state.ent.name.." set faceexit yaw of "..state.yaw) + end return true, true end @@ -129,7 +165,7 @@ people.actions.go = function(state) -- extra 0.5 allowing for dodgy step-ups, for now... local thispathpos = vector.round({x=state.pos.x, y=state.pos.y + 0.5, z=state.pos.z}) thispathpos.y = thispathpos.y - 1 - local nextpathpos = footpath.findnext(thispathpos, state.action.lastpathpos, false) + local nextpathpos, digit = footpath.findnext(thispathpos, state.action.lastpathpos, false) if nextpathpos == "unloaded" then state.wait = 1 dbg.v3(state.ent.name.." waiting for block load") @@ -143,15 +179,15 @@ people.actions.go = function(state) end if not distance or distance > 10 then distance = 10 end - while distance > 2 do + while distance > 2 and not digit do -- Look ahead a bit while going in a straight line, to increase speed - local aheadpathpos = footpath.findnext(nextpathpos, thispathpos, true) + local aheadpathpos, digit = footpath.findnext(nextpathpos, thispathpos, true) if aheadpathpos == "unloaded" then state.wait = 1 dbg.v3(state.ent.name.." waiting for block load for lookahead") return false end - if aheadpathpos then + if aheadpathpos and not digit then thispathpos = nextpathpos nextpathpos = aheadpathpos distance = distance - 1 @@ -161,6 +197,10 @@ people.actions.go = function(state) end state.action.lastpathpos = thispathpos state.action.pos = vector.add(nextpathpos, {x=0, y=0.5, z=0}) + if digit then + dbg.v3(state.ent.name.." clearing snow from path at "..minetest.pos_to_string(state.action.pos)) + state.action={"go", pos=state.action.pos, prevaction={"dig", pos=vector.add(nextpathpos, {x=0, y=1, z=0}), prevaction=state.action}} + end dbg.v3(state.ent.name.." set next footpath dest "..minetest.pos_to_string(state.action.pos)) return false @@ -190,9 +230,9 @@ people.actions.go = function(state) -- If it's a long way to the destination position, see if we can find -- a route using a footpath instead. if footpath and distance > 30 and not state.action.footpathdest then - local pathstartnode = footpath.nearest_node(state.pos, nil, distance) + local pathstartnode = footpath.nearest_junction(state.pos, nil, distance) if pathstartnode then - local pathdestnode = footpath.nearest_node(state.action.pos, nil, distance) + local pathdestnode = footpath.nearest_junction(state.action.pos, nil, distance) if pathdestarea then state.action.endpos = state.action.pos state.action.pos = vector.new(pathstartnode.pos) @@ -271,66 +311,70 @@ people.actions.go = function(state) if ahead > 1 then ahead = 1 end local nd = vector.from_speed_yaw(ahead, state.yaw) local nextpos = vector.add(vector.round(state.pos), nd) - nextpos.y = nextpos.y + 2 - -- How far down we'll look, which needs to be at least this to - -- be able to descend stairs. - local ncount = 6 - local diags = "at:" ..minetest.pos_to_string(state.pos) - local notwalkable = 0 - local unloaded = false - diags = diags .. " next+1:" ..minetest.pos_to_string(nextpos) - while ncount > 0 do - local nn = minetest.get_node(nextpos).name - diags = diags .. ":" .. nn - if nn == "ignore" then - unloaded = true - break - end - nd = minetest.registered_nodes[nn] - if not nd then - erk = "mysterious " .. nn - break - end - local gg = nd.groups or {} - if gg.water or gg.lava then - erk = nn - break - end + -- Don't look at the ahead position if it's the same node we're + -- already at... + if not vector.equals(vector.round(state.pos), nextpos) then + nextpos.y = nextpos.y + 2 + -- How far down we'll look, which needs to be at least this to + -- be able to descend stairs. + local ncount = 6 + local diags = "at:" ..minetest.pos_to_string(state.pos) + local notwalkable = 0 + local unloaded = false + diags = diags .. " next+1:" ..minetest.pos_to_string(nextpos) + while ncount > 0 do + local nn = minetest.get_node(nextpos).name + diags = diags .. ":" .. nn + if nn == "ignore" then + unloaded = true + break + end + nd = minetest.registered_nodes[nn] + if not nd then + erk = "mysterious " .. nn + break + end + local gg = nd.groups or {} + if gg.water or gg.lava then + erk = nn + break + end - -- Use 'walkable' to decide if we will bump into, and/or can stand - -- on the node. But with a bit of variation. We ignore doors so we - -- bump into them on purpose!? - local iswalkable = nd.walkable - if iswalkable and state.ent.can_use_doors and ( - (nd.groups.door and nd.groups.door ~= 0) or - (nd.groups.gate and nd.groups.gate ~= 0)) then - iswalkable = false - end + -- Use 'walkable' to decide if we will bump into, and/or can stand + -- on the node. But with a bit of variation. We ignore doors so we + -- bump into them on purpose!? + local iswalkable = nd.walkable + if iswalkable and state.ent.can_use_doors and ( + (nd.groups.door and nd.groups.door ~= 0) or + (nd.groups.gate and nd.groups.gate ~= 0)) then + iswalkable = false + end - if iswalkable and ncount < 6 then - break + if iswalkable and ncount < 6 then + break + end + if not iswalkable then + notwalkable = notwalkable + 1 + end + nextpos.y = nextpos.y - 1 + ncount = ncount - 1 end - if not iswalkable then - notwalkable = notwalkable + 1 + if unloaded then + -- If we hit an unloaded block, just wait until it loads. + dbg.v3(state.ent.name.." waiting for unloaded to check ahead") + state.wait = 1 + return false + end + if ncount == 0 then + erk = "fall" + elseif notwalkable < 2 then + erk = "bump" + end + if erk then + dbg.v1(state.ent.name.." aborted go due to fear of " .. + erk .. " - " .. diags) + unwholesome = true end - nextpos.y = nextpos.y - 1 - ncount = ncount - 1 - end - if unloaded then - -- If we hit an unloaded block, just wait until it loads. - dbg.v3(state.ent.name.." waiting for unloaded to check ahead") - state.wait = 1 - return false - end - if ncount == 0 then - erk = "fall" - elseif notwalkable < 2 then - erk = "bump" - end - if erk then - dbg.v1(state.ent.name.." aborted go due to fear of " .. - erk .. " - " .. diags) - unwholesome = true end end @@ -338,8 +382,10 @@ people.actions.go = function(state) if distance < 32 then if state.action.triedpath then - dbg.v2(state.ent.name.." is not trying a path to "..minetest.pos_to_string(curdest).." again") - return true, false + state.action.triedpath = false + dbg.v2(state.ent.name.." is waiting before trying a path again ") + state.action = {"wait", time=30, prevaction=state.action} + return false end local spos = vector.new(state.pos) diff --git a/actions/obtain.lua b/actions/obtain.lua new file mode 100644 index 0000000..4397d0f --- /dev/null +++ b/actions/obtain.lua @@ -0,0 +1,67 @@ + +local dbg +if moddebug then dbg=moddebug.dbg("people") else dbg={v1=function() end,v2=function() end,v3=function() end} end + +people.actions.obtain = function(state) + + if type(state.action.num) ~= "number" then + dbg.v1(state.ent.name.." has invalid num for obtain action") + return true, false + end + if type(state.action.item) ~= "string" then + dbg.v1(state.ent.name.." has invalid item for obtain action") + return true, false + end + -- TODO check it's a real item too! + + -- See how many we've got, and therefore how many we need... + local inv = state.ent.inventory + local count = 0 + for i, stack in ipairs(inv:get_list("main")) do + local nn = stack:get_name() + if nn == state.action.item then + count = count + stack:get_count() + end + end + local needed = state.action.num - count + if needed <= 0 then + dbg.v2(state.ent.name.." has "..count.." "..state.action.item) + return true, true + end + + if not state.action.crafted then + state.action.crafted = true + state.action = {"craft", item=state.action.item, num=needed, + prevaction=state.action} + return false + end + + if not state.action.bought then + state.action.bought = true + + -- TODO - Here is where we need knowledge or memory of what goods can + -- be purchased where. Before we have that, we're just going to hard + -- code a few things to test the concept... + local where = nil + if state.action.item == "default:wood" then + where = "Building Supplies 2" + elseif state.action.item == "default:stone" then + where = "Building Supplies 1" + end + if where then + state.action = {"go", name=where, prevaction= + {"buy", item=state.action.item, num=needed, prevaction= + state.action}} + return false + else + dbg.v3(state.ent.name.." doesn't know where to buy "..state.action.item) + end + + end + + + dbg.v1(state.ent.name.." can't obtain "..state.action.item) + return true, false + +end + diff --git a/actions/place.lua b/actions/place.lua index 49bdd37..5533bbe 100644 --- a/actions/place.lua +++ b/actions/place.lua @@ -11,7 +11,7 @@ people.actions.place = function(state) end local distance = vector.distance(state.pos, state.action.pos) - if distance > 5 then + if distance > 5.5 then dbg.v1(state.ent.name.." too far away to place - distance is "..distance) return true, false end @@ -42,15 +42,47 @@ people.actions.place = function(state) local ii = ItemStack(state.action.item.." 1") if not inv:contains_item("main", ii) then dbg.v1(state.ent.name.." doesn't have "..state.action.item.." to place") - return true, false + return true, {"need", item=state.action.item} end + + -- We need a node to build against... + local todir = state.action.againstdir + if not todir then + local trydirs + if state.action.againstdir then + trydirs = {againstdir} + else + trydirs = {{x=0, y=-1, z=0}, + {x=0, y=1, z=0}, + {x=-1, y=0, z=0}, + {x=1, y=0, z=0}, + {x=0, y=0, z=-1}, + {x=0, y=0, z=1}} + end + for _, trydir in pairs(trydirs) do + local n = minetest.get_node(vector.add(state.action.pos, trydir)) + if n.name ~= "air" then + todir = trydir + break + end + end + if not todir then + dbg.v1(state.ent.name.." can't place - nothing to build against") + return true, false + end + end + local pointed_thing = {type="node", - under={x=state.action.pos.x,y=state.action.pos.y-1,z=state.action.pos.z}, + under=vector.add(state.action.pos, todir), above=vector.new(state.action.pos)} onplace = minetest.registered_items[state.action.item].on_place if not onplace then onplace = minetest.item_place end - rstack = onplace(ii, people.get_fake_player(state.ent), pointed_thing) + -- Setting the param2 here, but nil is acceptable if it wasn't given as a + -- parameter to the action (normally it shouldn't need to be - maybe just + -- for stairs?) + rstack = onplace(ii, people.get_fake_player(state.ent), pointed_thing, state.action.param2) inv:remove_item("main", ItemStack(state.action.item.." 1")) + dbg.v1(state.ent.name.." placed "..state.action.item.." at "..minetest.pos_to_string(state.action.pos)) return true, true diff --git a/actions/stash_and_retrieve.lua b/actions/stash_and_retrieve.lua index fff340a..496a147 100644 --- a/actions/stash_and_retrieve.lua +++ b/actions/stash_and_retrieve.lua @@ -4,32 +4,43 @@ if moddebug then dbg=moddebug.dbg("people") else dbg={v1=function() end,v2=funct local function move_items(srcinv, destinv, itemspec) - local itemname - local count = 0 - if string.sub(itemspec, 1, 1) == "*" then - itemname = string.sub(itemspec, 2) - itemspec = itemname .. " 99" - while true do - local stack = srcinv:remove_item("main", ItemStack(itemspec)) - if stack:get_count() == 0 then break end - count = count + stack:get_count() - stack = destinv:add_item("main", stack) - count = count - stack:get_count() - if stack:get_count() ~= 0 then - srcinv:add_item("main", stack) - break - end - end + local removespec + local single = true + local keepstack = nil + + if type(itemspec) == "string" then + removespec = itemspec else - local stack = srcinv:remove_item("main", ItemStack(itemspec)) - count = count + stack:get_count() - itemname = stack:get_name() - stack = destinv:add_item("main", stack) - count = count - stack:get_count() - srcinv:add_item("main", stack) + removespec = itemspec[1] + if itemspec.keep then + keepstack = srcinv:remove_item("main", ItemStack(removespec.." "..itemspec.keep)) + end + end + if not removespec:find(" ") then + removespec = removespec.." 99" + single = false end - return itemname, count + local count = 0 + while true do + local stack = srcinv:remove_item("main", ItemStack(removespec)) + dbg.v3("remove_item of '"..removespec.."' removed a stack of "..stack:get_count()) + if stack:get_count() == 0 then break end + count = count + stack:get_count() + local leftover = destinv:add_item("main", stack) + count = count - leftover:get_count() + if leftover:get_count() ~= 0 then + srcinv:add_item("main", stack) + break + end + if single then break end + end + + if keepstack then + srcinv:add_item("main", keepstack) + end + + return removespec, count end diff --git a/actions/tunnel.lua b/actions/tunnel.lua new file mode 100644 index 0000000..a789a06 --- /dev/null +++ b/actions/tunnel.lua @@ -0,0 +1,268 @@ + +local dbg +if moddebug then dbg=moddebug.dbg("people") else dbg={v1=function() end,v2=function() end,v3=function() end} end + +local function rotate(rel, dir) + if dir.z < 0 then + return {x=-rel.x, y=rel.y, z=-rel.z} + elseif dir.z > 0 then + return {x=rel.x, y=rel.y, z=rel.z} + elseif dir.x > 0 then + return {x=rel.z, y=rel.y, z=-rel.x} + else + return {x=-rel.z, y=rel.y, z=rel.x} + end +end + +people.actions.tunnel = function(state) + + if type(state.action.distance) ~= "number" then + dbg.v1(state.ent.name.." has invalid distance for tunnel action") + return true, false + end + if type(state.action.dir) ~= "table" then + dbg.v1(state.ent.name.." has invalid dir for tunnel action") + return true, false + end + if state.action.dir.y ~= 0 then + dbg.v1(state.ent.name.." has invalid y component of dir for tunnel action") + return true, false + end + if math.abs(state.action.dir.x) + math.abs(state.action.dir.z) ~= 1 then + dbg.v1(state.ent.name.." has invalid x/z components of dir for tunnel action") + return true, false + end + + -- On starting, keep the original position and distance. This will allow + -- us to break off and resume if, for example, we run out of materials or + -- room to carry the spoils. + if not state.action.startpos then + state.action.startpos = vector.round(state.pos) + state.action.startdist = state.action.distance + end + + if(state.action.distance <= 0) then + dbg.v1(state.ent.name.." completed tunnelling") + return true, true + end + + -- digpos will be the base centre of the new cross-sectional area + -- we're going to dig. + local digpos = vector.add(vector.round(state.pos), state.action.dir) + + -- Tweak (hack) because if we're laying steps, we're standing on them as + -- we go down. This sucks, and even more so because we might want to + -- skip the steps until another time if we don't have the materials + if state.action.dir.y == -1 and state.action.steps and state.action.distance ~= state.action.startdist then + digpos.y = digpos.y - 1 + end + + local relxaxis + if state.action.dir.x == 0 then + relxaxis = "x" + else + relxaxis = "z" + end + + local height = 2 + if state.action.dir.y ~= 0 then + if state.action.steps then + height = 4 + else + height = 3 + end + end + + -- Make it so we dig from top to bottom when tunnelling downwards, and + -- bottom to top otherwise. + local yfrom, yto, ydir + if state.action.dir.y < 0 then + yfrom = height + yto = 0 + ydir = -1 + else + yfrom = 0 + yto = height + ydir = 1 + end + + -- "Special" positions are those where supports and lighting are placed + local special_sup = (state.action.startdist - state.action.distance + 2) % 8 == 7 + local special_light = (state.action.startdist - state.action.distance) % 4 == 3 + + -- TODO - the following is all a bit ineffecient, because we look at the + -- whole cross-section every time through. We could keep track of + -- where we've got to during a run and skip some. + + for y = yfrom, yto, ydir do + for x = -1, 1 do + local thisoff = {x=0, y=y, z=0} + thisoff[relxaxis] = x + local thispos = vector.add(digpos, thisoff) + local dignode = minetest.get_node(thispos) + + -- Steps at the bottom row if needed. + if y == 0 and state.action.steps and state.action.dir.y ~= 0 then + + if dignode.name == "air" then + state.action = {"place", pos=thispos, item=state.action.steps, successaction=state.action, + param2=minetest.dir_to_facedir({x=-state.action.dir.x,y=0,z=-state.action.dir.z}, false)} + return false + elseif dignode.name ~= state.action.steps then + state.action = {"dig", pos=thispos, successaction=state.action} + return false + end + + elseif y == 2 and x ~= 0 and state.action.lighting and special_light then + + -- We don't actually place lighting (e.g. torches) here, + -- because we do walls later and that would disturb them. + -- But we need to make sure we don't remove existing ones. + if dignode.name ~= "air" and + dignode.name ~= state.action.lighting then + state.action = {"dig", pos=thispos, successaction=state.action} + return false + end + + elseif y == height and state.action.support_top and special_sup then + + if dignode.name == "air" then + state.action = {"place", pos=thispos, item=state.action.support_top, successaction=state.action} + return false + elseif dignode.name ~= state.action.support_top then + state.action = {"dig", pos=thispos, successaction=state.action} + return false + end + + elseif x ~= 0 and state.action.support_side and special_sup then + + if dignode.name == "air" then + local param2 + if state.action.support_side_facein then + local facevec = rotate({x=-x, y=0, z=0}, + state.action.dir) + if facevec.x > 0 then + param2 = 4 * 4 + elseif facevec.x < 0 then + param2 = 4 * 3 + elseif facevec.z > 0 then + param2 = 4 * 2 + else + param2 = 4 * 1 + end + end + state.action = {"place", pos=thispos, + item=state.action.support_side, + successaction=state.action, + param2=param2} + return false + elseif dignode.name ~= state.action.support_side then + state.action = {"dig", pos=thispos, successaction=state.action} + return false + end + + else + + if dignode.name ~= "air" then + state.action = {"dig", pos=thispos, successaction=state.action} + return false + end + + end + + end + end + + if state.action.floor and state.action.dir.y == 0 then + for x = -1, 1 do + local thisoff = {x=0, y=-1, z=0} + thisoff[relxaxis] = x + local thispos = vector.add(digpos, thisoff) + local dignode = minetest.get_node(thispos) + if dignode.name == "air" then + state.action = {"place", pos=thispos, item=state.action.floor, successaction=state.action} + return false + elseif dignode.name ~= state.action.floor then + state.action = {"dig", pos=thispos, successaction=state.action} + return false + end + end + + end + + local wall = state.action.wall + if special_sup and state.action.support_wall then + wall = state.action.support_wall + end + + if wall then + for y = 0, height + 1 do + local thisoff = {x=0, y=y, z=0} + for x = -2, 2, 4 do + thisoff[relxaxis] = x + local thispos = vector.add(digpos, thisoff) + local dignode = minetest.get_node(thispos) + if dignode.name == "air" then + state.action = {"place", pos=thispos, item=wall, successaction=state.action} + return false + elseif dignode.name ~= wall then + state.action = {"dig", pos=thispos, successaction=state.action} + return false + end + end + end + end + + if state.action.lighting and special_light then + local thisoff = {x=0, y=2, z=0} + for x = -1, 1, 2 do + thisoff[relxaxis] = x + local thispos = vector.add(digpos, thisoff) + local dignode = minetest.get_node(thispos) + if dignode.name ~= state.action.lighting then + local againstdir = {x=0, y=0, z=0} + againstdir[relxaxis] = x + state.action = {"place", pos=thispos, item=state.action.lighting, successaction=state.action, + againstdir=againstdir} + return false + end + end + + end + + local ceiling = state.action.ceiling + if special_sup and state.action.support_ceiling then + ceiling = state.action.support_ceiling + end + + if ceiling then + -- We need to place the ceiling nodes against something - we use + -- the walls. + local againstdir = {x=0, y=0, z=0} + againstdir[relxaxis] = -1 + for x = -1, 1 do + local thisoff = {x=0, y=height+1, z=0} + thisoff[relxaxis] = x + local thispos = vector.add(digpos, thisoff) + local dignode = minetest.get_node(thispos) + if dignode.name == "air" then + state.action = {"place", pos=thispos, item=ceiling, successaction=state.action, + againstdir=againstdir} + return false + elseif dignode.name ~= ceiling then + state.action = {"dig", pos=thispos, successaction=state.action} + return false + end + end + end + + -- We've cleared the cross-section at the next position, so we can move + -- forward a step... + state.action.distance = state.action.distance - 1 + state.action = {"go", pos=digpos, successaction=state.action} + + return false + +end + + diff --git a/commands.lua b/commands.lua index 6319c19..9a3fbed 100644 --- a/commands.lua +++ b/commands.lua @@ -11,6 +11,7 @@ local get_person = function(args) if not args then return nil, nil, nil end + local person_name person_name, args = string.match(args, "^([^ ]+)(.*)") if person_name and not people.is_valid_name(person_name) then @@ -174,6 +175,47 @@ subcmd.where = { end } +subcmd.attach = { + params = "[]", + desc = "Attach to a person (or remove, with no parameter)", + exec = function(playername, args) + + if not minetest.check_player_privs(playername, {server=true}) then + return false, "Only admins can attach to people" + end + + local function removecur(playername) + if not people.attach_by_player[playername] then return end + local player = minetest.get_player_by_name(playername) + player:set_detach() + end + + local person, person_name + person_name, person, args = get_person(args) + if not person then + if args then + return false, "No such person as "..person_name + end + if not people.attach_by_player[playername] then + return false, "Not attached to a person" + end + removecur(playername) + local n = people.attach_by_player[playername].name + people.attach_by_player[playername] = nil + return true, "Detached from "..n + end + removecur(playername) + people.attach_by_player[playername] = {name=person_name} + local player = minetest.get_player_by_name(playername) + local ent = person.entity + if not ent then + return false, nil, true + end + player:set_attach(ent.object, "", {x=0,y=0,z=0}, {x=0,y=0,z=0}) + return true, "Attached to "..person_name + end +} + subcmd.track = { params = "[]", desc = "Track a person on the HUD (or remove, with no parameter)", diff --git a/form.lua b/form.lua index cfb4aef..5c21fc3 100644 --- a/form.lua +++ b/form.lua @@ -25,6 +25,9 @@ minetest.register_on_player_receive_fields(function(sender, formname, fields) "list[current_player;main;0,5;8,4;]" minetest.show_formspec(playername, "people:invform", formspec) + elseif fields.skin then + minetest.show_formspec(playername, "people:skinform", skins.formspec.main(playername)) + elseif fields.program then if string.sub(entity.fcode, 1, 1) ~= "@" then entity.code = fields.code @@ -33,7 +36,10 @@ minetest.register_on_player_receive_fields(function(sender, formname, fields) end -- Some things get reset when programming... - entity.gather = {items={}, nodes={}, topnodes={}, plant=nil} + entity.gather = {items={}, nodes={}, topnodes={}, plant=nil, + animal = nil, -- name of animal to gather from + animal_wield = nil, -- when animal set, the item to wield + } entity.action = nil entity.wait = 0 @@ -64,6 +70,27 @@ minetest.register_on_player_receive_fields(function(sender, formname, fields) -- Just a placeholder - doesn't need any handling currently + elseif formname == "people:skinform" then + local playername = sender:get_player_name() + local entity = lastformbyplayer[playername] + if not entity then return end + if playername ~= entity.owner then return end + if fields.main then + people.show_form(entity, playername) + return + end + for field, _ in pairs(fields) do + if string.sub(field,0,string.len("skins_set_")) == "skins_set_" then + local req = skins.list[tonumber(string.sub(field,string.len("skins_set_")+1))] + entity.props.textures = {req..".png"} + entity:update_props() + end + if string.sub(field,0,string.len("skins_page_")) == "skins_page_" then + skins.pages[playername] = tonumber(string.sub(field,string.len("skins_page_")+1)) + minetest.show_formspec(playername, "people:skinform", skins.formspec.main(playername)) + end + end + end end) @@ -105,8 +132,9 @@ people.show_form = function(entity, playername) presetnum = 1 entity.fcode = "@Example" end - formspec = formspec.."button[2.5,6;2.5,1;program;Program]".. - "button[5.5,6;2.5,1;inventory;Inventory]".. + formspec = formspec.."button[1.5,5;2.5,1;program;Program]".. + "button[4,5;2.5,1;inventory;Inventory]".. + "button[6.5,5;2.5,1;skin;Skin]".. "label[0,7;Preset]".. "dropdown[2,7;3;preset;"..prelist..";"..presetnum + 1 .."]" else diff --git a/init.lua b/init.lua index 15722a0..2ceb2f0 100644 --- a/init.lua +++ b/init.lua @@ -4,6 +4,7 @@ if moddebug then dbg=moddebug.dbg("people") else dbg={v1=function() end,v2=funct people = {} people.hud_show_by_player = {} +people.attach_by_player = {} local people_modpath = minetest.get_modpath("people") dofile(people_modpath .. "/commands.lua") @@ -27,10 +28,15 @@ dofile(people_modpath .. "/actions/place.lua") dofile(people_modpath .. "/actions/gather.lua") dofile(people_modpath .. "/actions/stash_and_retrieve.lua") dofile(people_modpath .. "/actions/buy_and_sell.lua") +dofile(people_modpath .. "/actions/obtain.lua") +dofile(people_modpath .. "/actions/craft.lua") dofile(people_modpath .. "/actions/attackplayer.lua") dofile(people_modpath .. "/actions/pickup.lua") dofile(people_modpath .. "/actions/rightclicknode.lua") dofile(people_modpath .. "/actions/harvest.lua") +dofile(people_modpath .. "/actions/tunnel.lua") +dofile(people_modpath .. "/actions/building.lua") +dofile(people_modpath .. "/actions/claimplot.lua") people.presets = {} for p, priv in pairs({FollowOwner=false, @@ -38,6 +44,7 @@ for p, priv in pairs({FollowOwner=false, RouteWalker=false, FarmHand=true, CowTest=true, + Miner=true, TreeFarmer=true}) do local file = io.open(minetest.get_modpath("people").."/presets/"..p..".lua", "r") local code @@ -554,23 +561,28 @@ minetest.register_entity("people:person" ,{ if action_done then + local lastaction = state.action + if state.action.prevaction then - dbg.v3(state.ent.name.." completed action - setting previous action") + dbg.v3(state.ent.name.." completed action - setting prevaction") self.action = state.action.prevaction else - if(action_success) then + if action_success == true then dbg.v3(state.ent.name.." completed action") + if state.action.successaction then + dbg.v3(state.ent.name.." completed action - setting successaction") + self.action = state.action.successaction + else + self.action = nil + end else - dbg.v3(state.ent.name.." failed action") - end - - if not action_success then - local event = {type="actfail"} + dbg.v3(state.ent.name.." failed action: "..minetest.serialize(action_success)) + self.action = nil + local event = {type="actfail", failedaction=lastaction, failcode=action_success} local err = people.exec_event(self, event) if err then dbg.v1("Lua error "..err) end end - self.action = nil end end @@ -716,6 +728,7 @@ people.exec_event = function(ent, event) local env = ent.env env.action = ent.action + env.name = ent.name env.gather = ent.gather env.mem = ent.memory env.carrying = function(item) @@ -733,6 +746,7 @@ people.exec_event = function(ent, event) local inv = ent.inventory return currency.count_money(inv, "main") end + env.vector = vector env.event = event diff --git a/presets/FarmHand.lua b/presets/FarmHand.lua index 82e8e8d..14f6331 100644 --- a/presets/FarmHand.lua +++ b/presets/FarmHand.lua @@ -132,16 +132,17 @@ if event.type == "program" then {"go", pos={x=174, y=11.5, z=320}}, {"go", pos={x=174, y=11.5, z=341}}, - {"stash", dest="default:chest", items={"*farming:weed", "*farming:cotton", - "*farming:wheat", "*farming_plus:carrot_item", "*farming:seed_wheat", - "*farming_plus:rhubarb_item", - "*farming_plus:carrot_seed", "*farming:seed_cotton", - "*farming_plus:tomato_seed", "*farming_plus:tomato_item", - "*farming_plus:rhubarb_seed", "*farming_plus:orange_seed", - "*farming_plus:potato_seed", "*farming_plus:potato_item", - "*farming_plus:orange_item", - "*farming_plus:strawberry_item", "*farming_plus:strawberry_seed", - "*farming_plus:cocoa", "default:tree"}}, + {"stash", dest="default:chest", items={"farming:weed", "farming:cotton", + "farming:wheat", "farming_plus:carrot_item", "farming:seed_wheat", + "farming_plus:rhubarb_item", + "farming_plus:carrot_seed", "farming:seed_cotton", + "farming_plus:tomato_seed", "farming_plus:tomato_item", + "farming_plus:rhubarb_seed", "farming_plus:orange_seed", + "farming_plus:potato_seed", "farming_plus:potato_item", + "farming_plus:orange_item", + "farming:pumpkin", + "farming_plus:strawberry_item", "farming_plus:strawberry_seed", + "farming_plus:cocoa", "default:tree"}}, {"go", pos={x=174, y=11.5, z=320}}, @@ -165,14 +166,13 @@ if event.type == "program" then -- Chuck all harvested stuff in the recycle bin. It will end up -- at the factory. - {"go", name="path_Jeiffel Tower"}, + {"go", name="Jeiffel Recycle Bin"}, {"wait", time=5}, - {"go", pos={x=-221,y=1.5,z=-187}}, {"stash", dest="default:chest", - items={"*default:tree", - "*default:leaves", - "*default:apple", - "*default:sapling"}}, + items={"default:tree", + "default:leaves", + "default:apple", + "default:sapling"}}, -- Turn off gathering now {"gather"}, @@ -181,19 +181,18 @@ if event.type == "program" then -- ******************** -- Head f0r the cactus farm, but get some flowers on the way -- - {"go", name="path_Jeiffel Station"}, {"gather", nodes={"flowers:geranium", "flowers:rose", "flowers:dandelion_white", "flowers:dandelion_yellow", "flowers:viola", "flowers:tulip"}}, - {"go", name="path_Cactus Approach"}, + {"go", name="Near Cactusville"}, {"gather"}, -- ******************** -- Cactus Farm - {"go", name="path_Cactus Farm"}, + {"go", name="Cactus Farm"}, {"wait", time=10}, {"gather", topnodes={"default:cactus"}}, {"go", pos={x=-34, y=11.5, z=-462}}, @@ -203,14 +202,14 @@ if event.type == "program" then {"go", pos={x=-33, y=11.5, z=-465}}, {"go", pos={x=-34, y=11.5, z=-462}}, {"gather"}, - {"go", name="path_Cactusville Square"}, - {"stash", items={"*default:cactus", - "*flowers:geranium", - "*flowers:rose", - "*flowers:dandelion_white", - "*flowers:dandelion_yellow", - "*flowers:viola", - "*flowers:tulip", + {"go", name="Cactusville Square"}, + {"stash", items={"default:cactus", + "flowers:geranium", + "flowers:rose", + "flowers:dandelion_white", + "flowers:dandelion_yellow", + "flowers:viola", + "flowers:tulip", }, dest="default:chest"}, @@ -223,15 +222,13 @@ if event.type == "program" then {"go", pos={x=153, y=4.5, z=203}}, {"go", pos={x=156, y=4.5, z=202}}, {"go", pos={x=156, y=4.5, z=199}}, - {"stash", items={"*default:papyrus"}, dest="default:chest"}, + {"stash", items={"default:papyrus"}, dest="default:chest"}, {"go", pos={x=156, y=4.5, z=202}}, {"go", pos={x=153, y=4.5, z=203}}, {"go", name="path_Industrial Estate"}, -- ***************** -- A bit of random wandering f0r test purposes - {"go", name="path_Building Supplies Shop"}, - {"wait", time=10}, {"go", name="path_Mia's Shop"}, {"wait", time=10}, {"go", name="path_Adventure Shop"}, diff --git a/presets/Miner.lua b/presets/Miner.lua new file mode 100644 index 0000000..0890378 --- /dev/null +++ b/presets/Miner.lua @@ -0,0 +1,231 @@ + +local MODE_selling = 1 -- Sell stuff +local MODE_inventory = 2 -- Manage our inventory +local MODE_mining = 3 -- Do mining +local MODE_exitmine = 4 -- Get out of the mine +local MODE_minebuilding = 5 -- Build the mine building +local MODE_homebuilding = 6 -- Build a home +local MODE_panic = 7 -- Don't know what to do + +local inventory_actions = { + + -- All these have a prevaction on to prevent them raising errors. + -- We still (probably) want to try and carry on mining if we're + -- missing some materials - maybe we'll be able to get them later. + {"obtain", item="stairs:stair_cobble", num=40, prevaction={"wait", time=1}}, + {"obtain", item="default:stone", num=40, prevaction={"wait", time=1}}, + {"obtain", item="default:wood", num=40, prevaction={"wait", time=1}}, + {"obtain", item="stairs:slab_wood", num=20, prevaction={"wait", time=1}}, + {"obtain", item="default:stick", num=40, prevaction={"wait", time=1}}, + {"obtain", item="default:torch", num=20, prevaction={"wait", time=1}}, + + {"go", name="Jeiffel Recycle Bin", successaction={"stash", + dest="default:chest", + items={{"default:dirt", keep=50}, + {"default:cobble", keep=50}, + "default:snow", + "default:gravel" + } + }}, + + {"go", name="Miner Willy's"} + +} + +if event.type == "program" then + mem.mode = MODE_selling + mem.cur = 1 + mem.lastxmode = MODE_mining + mem.needed = {} + +elseif event.type == "actfail" then + + if mem.mode == MODE_inventory or mem.mode == MODE_selling or + mem.mode == MODE_minebuilding or mem.mode == MODE_homebuilding then + + if type(event.failcode) == "table" and event.failcode[1] == "need" then + table.insert(mem.needed, event.failcode.item) + end + + action = {"wait", time=5} + mem.mode = MODE_selling + mem.cur = 1 + elseif mem.mode == MODE_mining then + mem.mode = MODE_exitmine + mem.cur = #mem.exitmine + elseif mem.mode == MODE_exitmine then + -- If something goes wrong when exiting the mine, just keep + -- trying + action = {"wait", time=5} + mem.cur = mem.cur + 1 + else + mem.mode = MODE_panic + end + +elseif event.type == "act" then + + if mem.mode == MODE_selling then + + if carrying("default:iron_lump") > 0 then + action = {"go", name="Mineral Shop", successaction={"sell", item="default:iron_lump"}} + return + end + -- TODO - need to sell excess coal here, but the sell action currently + -- doesn't allow an amount to be specified, so we'd sell it all! + mem.mode = MODE_inventory + mem.cur = 1 + action = {"wait", time=1} + + elseif mem.mode == MODE_inventory then + + if #mem.needed > 0 then + action={"obtain", item=mem.needed[1], num=1, prevaction={"wait", time=1}} + table.remove(mem.needed, 1) + return + end + + action = inventory_actions[mem.cur] + mem.cur = mem.cur + 1 + if mem.cur > #inventory_actions then + + if mem.lastxmode == MODE_minebuilding then + mem.mode = MODE_mining + elseif mem.lastxmode == MODE_mining then + mem.mode = MODE_homebuilding + elseif mem.lastxmode == MODE_homebuilding then + mem.mode = MODE_mining + end + mem.lastxmode = mem.mode + mem.cur = 1 + end + + elseif mem.mode == MODE_mining then + + if mem.cur == 1 then + action = {"go", name="Miner Willy's", + successaction={"gather", items={"default:torch"}}} + mem.exitmine = {} + mem.cur = 2 + elseif mem.cur == 2 then + + -- Select some materials to fancy up the tunnel, if we're carrying + -- them. If not, we'll do it without them and they'll get done + -- another time. + local steps = nil + local wall_and_floor = nil + local support_c = nil + local support_s = nil + local lighting = nil + if carrying("stairs:stair_cobble") > 3 then + steps = "stairs:stair_cobble" + if carrying("default:wood") > 3 then + support_c = "default:wood" + end + if carrying("stairs:slab_wood") > 3 then + support_s = "stairs:slab_wood" + end + if carrying("default:stone") > 10 then + wall_and_floor = "default:stone" + end + end + if carrying("default:torch") > 1 then + lighting = "default:torch" + end + + action = {"tunnel", distance=40, dir={x=0,y=-1,z=-1}, steps=steps, + lighting=lighting, support_ceiling=support_c, + support_side=support_s, + support_side_facein=true, + wall=wall_and_floor, floor=wall_and_floor} + action.successaction= {"tunnel", distance=8, dir={x=0,y=0,z=-1}, + lighting=lighting, floor=wall_and_floor, + ceiling=wall_and_floor} + action.successaction.successaction={"go", rel={x=1,y=0,z=2}} + table.insert(mem.exitmine, {"go", name="Miner Willy's"}) + mem.cur = 3 + + elseif mem.cur == 3 then + + action = {"tunnel", distance=20, dir={x=1,y=-1,z=0}, steps="stairs:stair_cobble", + lighting="default:torch", wall="default:stone", floor="default:stone"} + action.successaction= + {"tunnel", distance=8, dir={x=1,y=0,z=0}, lighting="default:torch", successaction= + {"go", rel={x=-1,y=0,z=1}} } + table.insert(mem.exitmine, {"go", pos=vector.subtract(pos, {x=1,y=0,z=0})}) + mem.cur = 4 + + elseif mem.cur == 4 then + mem.mode = MODE_exitmine + mem.cur = #mem.exitmine + action = {"wait", time=1} + end + + elseif mem.mode == MODE_exitmine then + if mem.cur == 0 or not mem.exitmine then + mem.exitmine = nil + mem.mode = MODE_selling + mem.cur = 1 + action = {"gather"} + else + action = mem.exitmine[mem.cur] + mem.cur = mem.cur - 1 + end + + elseif mem.mode == MODE_minebuilding then + + if mem.cur == 1 then + action = {"go", name="Miner Willy's"} + mem.cur = 2 + elseif mem.cur == 2 then + action = {"building", dir={x=0,y=-1,z=-1}, + width=5, depth=7, height=5, + wall="default:stone", + ceiling="default:wood", + outside_floor={"default:dirt", "default:dirt_with_grass"}, + door="doors:door_wood" + } + mem.cur = 3 + else + mem.mode = MODE_selling + mem.cur = 1 + action = {"wait", time=5} + end + + elseif mem.mode == MODE_homebuilding then + + local home_jctname = "people_"..name.."_home" + + if mem.cur == 1 then + -- Attempt to claim our plot. If we already have it, this will fail + -- and we'll just do the wait. + action = {"claimplot", name=home_jctname, prevaction={"wait", time=1}} + mem.cur = 2 + elseif mem.cur == 2 then + action = {"go", name=home_jctname, faceexit="@build"} + mem.cur = 3 + elseif mem.cur == 3 then + + -- We'd better be facing the right way!! + local bdir = vector.round(vector.from_speed_yaw(1, yaw)) + + action = {"building", dir=bdir, + width=5, depth=5, height=4, + wall="default:stone", + ceiling="default:wood", + outside_floor={"default:dirt", "default:dirt_with_grass"}, + door="doors:door_wood" + } + mem.cur = 4 + else + mem.mode = MODE_selling + mem.cur = 1 + action = {"wait", time=5} + end + + else + action = {"wait", time=600} + end + + +end + diff --git a/presets/TreeFarmer.lua b/presets/TreeFarmer.lua index 8195a43..7612a2e 100644 --- a/presets/TreeFarmer.lua +++ b/presets/TreeFarmer.lua @@ -25,10 +25,10 @@ if event.type == "program" then {"wait", time=5}, {"go", pos={x=-221,y=1.5,z=-187}}, {"stash", dest="default:chest", - items={"*default:tree", - "*default:leaves", - "*default:apple", - "*default:sapling"}} + items={"default:tree", + "default:leaves", + "default:apple", + "default:sapling"}} }