905 lines
31 KiB
Lua
905 lines
31 KiB
Lua
local dbg
|
|
if moddebug then dbg=moddebug.dbg("people") else dbg={v1=function() end,v2=function() end,v3=function() end} end
|
|
|
|
people = {}
|
|
|
|
people.hud_show_by_player = {}
|
|
people.attach_by_player = {}
|
|
|
|
local people_modpath = minetest.get_modpath("people")
|
|
dofile(people_modpath .. "/commands.lua")
|
|
dofile(people_modpath .. "/tracking.lua")
|
|
dofile(people_modpath .. "/form.lua")
|
|
|
|
|
|
-- Contains all action handlers. Each action handler takes a 'state' table as
|
|
-- a parameter, and returns true if the current action is complete, false
|
|
-- otherwise. When complete, a second boolean is also returned which says
|
|
-- whether the action completed succesfully, or failed due to an error.
|
|
people.actions = {}
|
|
dofile(people_modpath .. "/actions/step.lua")
|
|
dofile(people_modpath .. "/actions/face.lua")
|
|
dofile(people_modpath .. "/actions/wait.lua")
|
|
dofile(people_modpath .. "/actions/follow.lua")
|
|
dofile(people_modpath .. "/actions/go.lua")
|
|
dofile(people_modpath .. "/actions/drop.lua")
|
|
dofile(people_modpath .. "/actions/dig.lua")
|
|
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.load_presets = function()
|
|
people.presets = {}
|
|
for p, priv in pairs({FollowOwner=false,
|
|
SimpleCommands=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
|
|
if file then
|
|
code = file:read("*all")
|
|
else
|
|
code = "MISSING"
|
|
end
|
|
table.insert(people.presets, {code=code, name=p, priv=priv})
|
|
end
|
|
end
|
|
people.load_presets()
|
|
|
|
people.is_valid_name = function(name)
|
|
if not name then return false end
|
|
-- Characters must be valid
|
|
if not name:match("^[A-Za-z0-9%_%-]+$") then return false end
|
|
-- Can't be the name of a player
|
|
if minetest.auth_table[name] then return false end
|
|
return true
|
|
end
|
|
|
|
|
|
local animations = {
|
|
stand = { x= 0, y= 79, },
|
|
lay = { x=162, y=166, },
|
|
walk = { x=168, y=187, },
|
|
mine = { x=189, y=198, },
|
|
walk_mine = { x=200, y=219, },
|
|
sit = { x= 81, y=160, },
|
|
}
|
|
|
|
|
|
local function copyaction(orig)
|
|
if type(orig) ~= "table" then return orig end
|
|
local copy = {}
|
|
for k, v in next, orig, nil do
|
|
copy[k] = copyaction(v)
|
|
end
|
|
return copy
|
|
end
|
|
|
|
|
|
local escape_pattern
|
|
do
|
|
local matches =
|
|
{
|
|
["^"] = "%^";
|
|
["$"] = "%$";
|
|
["("] = "%(";
|
|
[")"] = "%)";
|
|
["%"] = "%%";
|
|
["."] = "%.";
|
|
["["] = "%[";
|
|
["]"] = "%]";
|
|
["*"] = "%*";
|
|
["+"] = "%+";
|
|
["-"] = "%-";
|
|
["?"] = "%?";
|
|
["\0"] = "%z";
|
|
}
|
|
|
|
people.escape_pattern = function(s)
|
|
return (s:gsub(".", matches))
|
|
end
|
|
end
|
|
|
|
function people.get_inv(self, name)
|
|
local st = {}
|
|
if self.inventory then
|
|
local list = self.inventory:get_list(name)
|
|
if list then
|
|
for i=1,#list,1 do
|
|
table.insert(st,list[i]:to_string())
|
|
end
|
|
end
|
|
end
|
|
return st
|
|
end
|
|
|
|
function people.create(pos, name, owner)
|
|
local obj = minetest.add_entity(pos, "people:person")
|
|
if not obj then
|
|
return "Failed to add_entity"
|
|
end
|
|
local ent = obj:get_luaentity()
|
|
ent.name = name
|
|
ent.owner = owner
|
|
ent.inventory = minetest.create_detached_inventory("people_"..name, nil)
|
|
ent.inventory:set_size("main", 8*4);
|
|
people.people_add(ent)
|
|
return nil
|
|
end
|
|
|
|
minetest.register_entity("people:person" ,{
|
|
hp_max = 20,
|
|
physical = true,
|
|
|
|
-- TODO For now at least, don't collide with objects - specifically I don't
|
|
-- want them colliding with other 'people' or players, for the same reason
|
|
-- players don't collide with each other.
|
|
collide_with_objects = false,
|
|
|
|
armor_groups = {immortal=1},
|
|
collisionbox = {-0.35,0,-0.35, 0.35,1.75,0.35},
|
|
visual = "mesh",
|
|
visual_size = {x=1, y=1},
|
|
mesh = "people_character.x",
|
|
textures = {"people_character.png"},
|
|
makes_footstep_sound = true,
|
|
can_use_doors = true,
|
|
owner = "",
|
|
code = "@"..people.presets[1].name,
|
|
|
|
-- Current action. A table, as described in README.md
|
|
-- Is nil when it's time for the person to select a new action.
|
|
action = nil,
|
|
|
|
-- Time (game time) to wait until before doing anything else
|
|
wait = 0,
|
|
|
|
stepheight = 1.1,
|
|
|
|
on_activate = function(self, staticdata)
|
|
|
|
local setactive = false
|
|
self.memory = {}
|
|
self.env = {}
|
|
self.props = {}
|
|
-- The following controls gathering, which is the digging of nodes and
|
|
-- picking up of items, which happens in parallel with any other action.
|
|
--
|
|
self.gather = {
|
|
nodes = {}, -- list of nodes to dig up
|
|
topnodes = {}, -- list of topnodes to dig
|
|
items = {}, -- list of items to pick up
|
|
plant = nil, -- name of seed to plant, or nil
|
|
animal = nil, -- name of animal to gather from
|
|
animal_wield = nil, -- when animal set, the item to wield
|
|
}
|
|
self.name = "<new person>"
|
|
|
|
local restored
|
|
if staticdata and staticdata ~= "" then
|
|
restored = minetest.deserialize(staticdata)
|
|
if restored then
|
|
if restored.action then
|
|
self.action = restored.action
|
|
end
|
|
if restored.memory then
|
|
self.memory = restored.memory
|
|
else
|
|
self.memory = {}
|
|
end
|
|
if restored.code then
|
|
self.code = restored.code
|
|
else
|
|
self.code = "@"..people.presets[1].name
|
|
end
|
|
if restored.name then
|
|
self.name = restored.name
|
|
end
|
|
if restored.owner then
|
|
self.owner = restored.owner
|
|
end
|
|
if restored.wait then
|
|
self.wait = restored.wait
|
|
end
|
|
if restored.props then
|
|
self.props = restored.props
|
|
end
|
|
if restored.gather then
|
|
self.gather = restored.gather
|
|
-- backward compatibility...
|
|
if not self.gather.topnodes then
|
|
self.gather.topnodes = {}
|
|
end
|
|
else
|
|
self.gather = {
|
|
nodes = {},
|
|
topnodes = {},
|
|
items = {},
|
|
}
|
|
end
|
|
|
|
self.inventory = minetest.create_detached_inventory("people_"..self.name, nil)
|
|
self.inventory:set_size("main", 8*4);
|
|
if restored.inv_main then
|
|
local inv_main = restored.inv_main
|
|
for i=1,#inv_main,1 do
|
|
self.inventory:set_stack("main", i, inv_main[i])
|
|
end
|
|
end
|
|
|
|
if not people.is_valid_name(self.name) then
|
|
self.object:remove()
|
|
dbg.v1("Removing badly named person")
|
|
return
|
|
end
|
|
|
|
end
|
|
|
|
-- Entity becomes active.
|
|
people.people_set_active(self)
|
|
end
|
|
|
|
self.env = people.create_environment(self)
|
|
|
|
self:update_props()
|
|
|
|
self.anim = nil
|
|
|
|
local event = {type="activate", dtime_s=dtime_s}
|
|
local err = people.exec_event(self, event)
|
|
if err then dbg.v1("Lua error "..err) end
|
|
|
|
dbg.v3("Done on_activate for "..self.name)
|
|
|
|
end,
|
|
|
|
update_props = function(self)
|
|
self.object:set_properties(self.props)
|
|
end,
|
|
|
|
on_deactivate = function(self)
|
|
people.people_set_inactive(self)
|
|
end,
|
|
|
|
get_staticdata = function(self)
|
|
|
|
local data = {
|
|
name = self.name,
|
|
owner = self.owner,
|
|
code = self.code,
|
|
action = self.action,
|
|
memory = self.memory,
|
|
wait = self.wait,
|
|
props = self.props,
|
|
gather = self.gather,
|
|
inv_main = people.get_inv(self, "main")
|
|
}
|
|
return minetest.serialize(data)
|
|
end,
|
|
|
|
on_punch = function(self, puncher, time_from_last_punch, tool_capabilities, dir, killed)
|
|
local playername = puncher:get_player_name()
|
|
if killed then
|
|
if people.people[self.name] then
|
|
people.people[self.name].dead = true
|
|
if playername then
|
|
dbg.v1(self.name.." killed by "..playername)
|
|
else
|
|
dbg.v1(self.name.." killed by something mysterious")
|
|
end
|
|
else
|
|
dbg.v1("Unknown person killed")
|
|
end
|
|
return
|
|
end
|
|
|
|
if playername then
|
|
local event = {type="punched", puncher=playername}
|
|
local err = people.exec_event(self, event)
|
|
if err then dbg.v1("Lua error "..err) end
|
|
end
|
|
|
|
end,
|
|
|
|
on_rightclick = function(self, clicker)
|
|
local playername = clicker:get_player_name()
|
|
-- Use a temporary code editing variable until changes are submitted
|
|
self.fcode = self.code
|
|
people.show_form(self, playername)
|
|
end,
|
|
|
|
on_tell = function(self, sender, message)
|
|
local event = {type="tell", sender=sender:get_player_name(),
|
|
senderpos=sender:getpos(), message=message}
|
|
local err = people.exec_event(self, event)
|
|
if err then dbg.v1("Lua error "..err) end
|
|
end,
|
|
|
|
on_step = function(self, dtime)
|
|
|
|
if self.wait ~= 0 then
|
|
if minetest.get_gametime() < self.wait then
|
|
self.object:setvelocity({x=0,y=-10,z=0})
|
|
return
|
|
end
|
|
dbg.v3(self.name.." finished wait at "..minetest.get_gametime())
|
|
self.wait = 0
|
|
end
|
|
|
|
local pos = self.object:getpos()
|
|
|
|
-- If we don't have a current action, we let the lua code choose one...
|
|
if not self.action then
|
|
|
|
self.gather.lastpos = nil
|
|
|
|
local event = {type="act"}
|
|
local err = people.exec_event(self, event)
|
|
if err then dbg.v1("Lua error "..err) end
|
|
if not self.action then
|
|
self.action = {"wait", time=120}
|
|
dbg.v2(self.name.." failed to choose action - setting wait, at "..minetest.pos_to_string(pos))
|
|
else
|
|
dbg.v1(self.name.." set new action: "..minetest.serialize(self.action))
|
|
end
|
|
end
|
|
|
|
-- Handle gathering
|
|
-- TODO: Only happens during go and follow currently. Can't happen
|
|
-- during "dig" or any subaction that "dig" might set - which poses
|
|
-- a problem if "dig" were to use "go"!! Better way of handling this
|
|
-- required.
|
|
if self.action and (self.action[1] == "go" or
|
|
self.action[1] == "follow" or
|
|
self.action[1] == "wait") and
|
|
(#self.gather.items + #self.gather.nodes + #self.gather.topnodes > 0 or
|
|
self.gather.plant or self.gather.animal) then
|
|
local dist = 99
|
|
if self.gather.lastpos then
|
|
dist = vector.distance(pos, self.gather.lastpos)
|
|
end
|
|
if dist > 3 then
|
|
|
|
local done = false
|
|
|
|
if #self.gather.nodes > 0 then
|
|
dbg.v2(self.name.." looking for "..minetest.serialize(self.gather.nodes).." near "..minetest.pos_to_string(pos))
|
|
local foundpos = minetest.find_node_near(
|
|
vector.add(vector.round(pos), {x=0,y=1,z=0}),
|
|
3, self.gather.nodes)
|
|
if foundpos then
|
|
local n = minetest.get_node(foundpos)
|
|
dbg.v2(self.name.." found "..n.name.." at "..minetest.pos_to_string(foundpos))
|
|
self.action = {"dig", pos=foundpos, prevaction=self.action}
|
|
done = true
|
|
end
|
|
end
|
|
if not done and #self.gather.topnodes > 0 then
|
|
dbg.v2(self.name.." looking for topnodes "..minetest.serialize(self.gather.topnodes).." near "..minetest.pos_to_string(pos))
|
|
local minp = vector.add(pos, {x=-3,y=-3,z=-3})
|
|
local maxp = vector.add(pos, {x=3, y=3, z=3})
|
|
local pp = minetest.find_nodes_in_area(minp, maxp, self.gather.topnodes)
|
|
for _, ppp in pairs(pp) do
|
|
local n = minetest.get_node(ppp)
|
|
local pabove = vector.add(ppp, {x=0, y=1, z=0})
|
|
if minetest.get_node(pabove).name == "air" then
|
|
local pbelow = vector.add(ppp, {x=0, y=-1, z=0})
|
|
if minetest.get_node(pbelow).name == n.name then
|
|
dbg.v2(self.name.." found "..n.name.." at "..minetest.pos_to_string(ppp))
|
|
self.action = {"dig", pos=ppp, prevaction=self.action}
|
|
done = true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if not done and #self.gather.items > 0 then
|
|
dbg.v2(self.name.." looking for items "..minetest.serialize(self.gather.items).." near "..minetest.pos_to_string(pos))
|
|
local objects = minetest.get_objects_inside_radius(
|
|
vector.add(vector.round(pos), {x=0,y=1,z=0}), 6)
|
|
for _, obj in ipairs(objects) do
|
|
if obj:get_entity_name() == "__builtin:item" then
|
|
local ent = obj:get_luaentity()
|
|
if ent.itemstring then
|
|
for _, ti in ipairs(self.gather.items) do
|
|
if string.match("^" .. ti .. " .*", ent.itemstring) then
|
|
self.action = {"pickup", pos=obj:getpos(),
|
|
item=ti, prevaction=self.action}
|
|
done = true
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if done then break end
|
|
end
|
|
end
|
|
|
|
if not done and self.gather.plant then
|
|
|
|
if self.gather.plant and type(self.gather.plant) == "string" and
|
|
self.inventory:contains_item("main", ItemStack(self.gather.plant)) then
|
|
|
|
if self.gather.plant == "default:sapling" then
|
|
soilitems = {"default:dirt", "default:dirt_with_grass"}
|
|
elseif self.gather.plant == "farming_plus:cocoa_sapling" then
|
|
soilitems = {"default:desert_sand", "default:sand"}
|
|
else
|
|
soilitems = {"farming:soil_wet"}
|
|
end
|
|
|
|
soils = minetest.find_nodes_in_area(
|
|
{x=pos.x-3, y=pos.y-2, z=pos.z-3},
|
|
{x=pos.x+3, y=pos.y+2, z=pos.z+3},
|
|
soilitems)
|
|
for _, foundpos in pairs(soils) do
|
|
plantpos = {x=foundpos.x,y=foundpos.y+1,z=foundpos.z}
|
|
local n = minetest.get_node(plantpos)
|
|
if n.name == "air" then
|
|
dbg.v1(self.name.." found empty soil below "..minetest.pos_to_string(plantpos))
|
|
self.action = {"place", pos=plantpos, item=self.gather.plant, prevaction=self.action}
|
|
done = true
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if not done and animals and (self.gather.animal and self.gather.animal_wield) then
|
|
if self.inventory:contains_item("main", ItemStack(self.gather.animal_wield)) then
|
|
self.wielded = self.gather.animal_wield
|
|
|
|
local spinfo = animals.species[self.gather.animal]
|
|
if not spinfo then
|
|
dbg.v1(self.name.." trying to harvest unknown species "..self.gather.animal)
|
|
self.gather.animal = nil
|
|
self.gather.animal_wield = nil
|
|
else
|
|
local hitit = nil
|
|
local objects = minetest.get_objects_inside_radius(pos, 5)
|
|
for k, obj in ipairs(objects) do
|
|
if not obj:is_player() then
|
|
local ent = obj:get_luaentity()
|
|
if ent then
|
|
dbg.v3(self.name.." examining "..(ent.name or "<noname>"))
|
|
if ent.species == self.gather.animal then
|
|
if spinfo.is_harvestable and spinfo.is_harvestable(ent) then
|
|
hitit = ent
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if hitit then
|
|
dbg.v1(self.name.." decided to harvest "..hitit.name)
|
|
self.action = {"harvest", name=hitit.name, prevaction=self.action}
|
|
done = true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if not done then
|
|
-- We didn't find anything to do here, so we can move on
|
|
self.gather.lastpos = pos
|
|
end
|
|
|
|
end
|
|
end
|
|
|
|
-- Prepare state table for action handler
|
|
local state = {
|
|
|
|
-- In - current yaw, Out - used as new yaw
|
|
yaw = self.object:getyaw(),
|
|
|
|
-- In - current position
|
|
pos = pos,
|
|
|
|
-- In - 0, Out - new speed, in yaw direction
|
|
speed = 0,
|
|
|
|
-- Out - if set, a new position to set
|
|
setpos = nil,
|
|
|
|
-- In - current action, Out - modified or replaced action
|
|
action = self.action,
|
|
|
|
-- In - current gather settings, Out - modified or replaced gather settings
|
|
gather = self.gather,
|
|
|
|
-- In - 0, Out - non-zero sets a wait of that many seconds - doing this
|
|
-- will cause a wait and then the current action will
|
|
-- continue - different to setting a "wait" action!
|
|
wait = 0,
|
|
|
|
-- In - nil, Out - if set, use that animation - otherwise attempt to
|
|
-- select automatically
|
|
anim = nil,
|
|
|
|
-- In - 0, Out - damage to be applied.
|
|
damage = 0,
|
|
|
|
-- In - the entity, in case anything needs to be called on it (avoid
|
|
-- that if possible, but for example, getting the last collision
|
|
-- result is currently done this way)
|
|
ent = self,
|
|
|
|
-- In - dtime
|
|
dtime = dtime,
|
|
}
|
|
|
|
-- Run the appropriate handler for the current action...
|
|
local action_done = false
|
|
local action_success = false
|
|
local handler = people.actions[state.action[1]]
|
|
if handler then
|
|
action_done, action_success = handler(state)
|
|
-- Because state.action can be replaced...
|
|
if state.action ~= self.action then
|
|
dbg.v3(state.ent.name.." action handler replaced action with "..minetest.serialize(state.action))
|
|
self.action = state.action
|
|
end
|
|
if state.gather ~= self.gather then
|
|
self.gather = state.gather
|
|
end
|
|
else
|
|
dbg.v1(state.ent.name.." has unknown action: "..minetest.serialize(self.action))
|
|
self.action = nil
|
|
end
|
|
|
|
if action_done then
|
|
|
|
local lastaction = state.action
|
|
|
|
if state.action.prevaction then
|
|
dbg.v3(state.ent.name.." completed action - setting prevaction")
|
|
self.action = state.action.prevaction
|
|
else
|
|
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: "..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
|
|
|
|
end
|
|
end
|
|
|
|
|
|
if state.wait ~= 0 then
|
|
self.wait = minetest.get_gametime() + state.wait
|
|
dbg.v3(self.name.." will wait until "..self.wait)
|
|
if state.wait > 30 then
|
|
self.object:set_autonomous(0)
|
|
else
|
|
self.object:set_autonomous(1)
|
|
end
|
|
else
|
|
self.object:set_autonomous(1)
|
|
end
|
|
|
|
-- Set appropriate animation
|
|
local wantanim = state.anim
|
|
if not wantanim then
|
|
wantanim = "stand"
|
|
if state.speed > 0 then
|
|
wantanim = "walk"
|
|
end
|
|
end
|
|
if wantanim ~= self.anim then
|
|
self.object:set_animation(animations[wantanim], 15, 0)
|
|
self.anim = wantanim
|
|
end
|
|
|
|
if state.setpos then
|
|
self.object:setpos(state.setpos)
|
|
end
|
|
local setvel = vector.from_speed_yaw(state.speed, state.yaw)
|
|
setvel.y = -10
|
|
self.object:setvelocity(setvel)
|
|
self.object:setyaw(state.yaw)
|
|
|
|
end,
|
|
|
|
})
|
|
|
|
people.create_environment = function(ent)
|
|
|
|
return {
|
|
log = function(msg)
|
|
dbg.v1(" >> "..msg)
|
|
end,
|
|
dump = dump,
|
|
tostring = tostring,
|
|
tonumber = tonumber,
|
|
type = type,
|
|
vector = vector,
|
|
string = {
|
|
byte = string.byte,
|
|
char = string.char,
|
|
find = string.find,
|
|
format = string.format,
|
|
gmatch = string.gmatch,
|
|
gsub = string.gsub,
|
|
len = string.len,
|
|
lower = string.lower,
|
|
upper = string.upper,
|
|
match = string.match,
|
|
rep = string.rep,
|
|
reverse = string.reverse,
|
|
sub = string.sub,
|
|
},
|
|
math = {
|
|
abs = math.abs,
|
|
acos = math.acos,
|
|
asin = math.asin,
|
|
atan = math.atan,
|
|
atan2 = math.atan2,
|
|
ceil = math.ceil,
|
|
cos = math.cos,
|
|
cosh = math.cosh,
|
|
deg = math.deg,
|
|
exp = math.exp,
|
|
floor = math.floor,
|
|
fmod = math.fmod,
|
|
frexp = math.frexp,
|
|
huge = math.huge,
|
|
ldexp = math.ldexp,
|
|
log = math.log,
|
|
log10 = math.log10,
|
|
max = math.max,
|
|
min = math.min,
|
|
modf = math.modf,
|
|
pi = math.pi,
|
|
pow = math.pow,
|
|
rad = math.rad,
|
|
random = math.random,
|
|
sin = math.sin,
|
|
sinh = math.sinh,
|
|
sqrt = math.sqrt,
|
|
tan = math.tan,
|
|
tanh = math.tanh,
|
|
},
|
|
table = {
|
|
insert = table.insert,
|
|
maxn = table.maxn,
|
|
remove = table.remove,
|
|
sort = table.sort
|
|
}
|
|
}
|
|
end
|
|
|
|
local create_sandbox = function (code, env)
|
|
-- Create Sandbox
|
|
if code:byte(1) == 27 then
|
|
return _, "You Hacker You! Don't use binary code!"
|
|
end
|
|
f, msg = loadstring(code)
|
|
if not f then return nil, msg end
|
|
setfenv(f, env)
|
|
return f
|
|
end
|
|
|
|
local code_prohibited = function(code)
|
|
-- Clean code
|
|
local prohibited = {"while", "for", "repeat", "until", "function", "goto"}
|
|
for _, p in ipairs(prohibited) do
|
|
if string.find(code, p) then
|
|
return "Prohibited command: "..p
|
|
end
|
|
end
|
|
end
|
|
|
|
people.is_carrying = function(ent, item)
|
|
local inv = ent.inventory
|
|
local count = 0
|
|
for i, stack in ipairs(inv:get_list("main")) do
|
|
local nn = stack:get_name()
|
|
if nn == item then
|
|
count = count + stack:get_count()
|
|
end
|
|
end
|
|
return count
|
|
end
|
|
|
|
people.exec_event = function(ent, event)
|
|
|
|
dbg.v3(ent.name.." exec event "..minetest.serialize(event))
|
|
|
|
local code = ent.code
|
|
if string.sub(code, 1, 1) == "@" then
|
|
local f = string.sub(code, 2)
|
|
for _, p in pairs(people.presets) do
|
|
if p.name == f then
|
|
code = p.code
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
local env = ent.env
|
|
env.action = ent.action
|
|
env.name = ent.name
|
|
env.gather = ent.gather
|
|
env.mem = ent.memory
|
|
env.carrying = function(item)
|
|
return people.is_carrying(ent, item)
|
|
end
|
|
env.has_money = function()
|
|
local inv = ent.inventory
|
|
return currency.count_money(inv, "main")
|
|
end
|
|
env.vector = vector
|
|
|
|
env.event = event
|
|
|
|
local pos = ent.object:getpos()
|
|
local vel = ent.object:getvelocity()
|
|
local yaw = ent.object:getyaw()
|
|
env.pos = {x=pos.x,y=pos.y,z=pos.z}
|
|
env.vel = {x=vel.x,y=vel.y,z=vel.z}
|
|
env.yaw = yaw
|
|
env.speed = 0
|
|
|
|
local prohibited = code_prohibited(code)
|
|
if prohibited then return prohibited end
|
|
|
|
-- create the sandbox and execute code
|
|
local chunk, msg = create_sandbox(code, env)
|
|
if not chunk then return msg end
|
|
local success, msg = pcall(chunk)
|
|
if not success then return msg end
|
|
|
|
if env.action and ((not ent.action) or env.action[1] ~= ent.action[1]) then
|
|
-- Cancel a wait if the action type was changed
|
|
ent.wait = 0
|
|
end
|
|
if ent.action ~= env.action then
|
|
ent.action = copyaction(env.action)
|
|
end
|
|
ent.gather = env.gather
|
|
|
|
end
|
|
|
|
local tick_wake = 0
|
|
local rate_wake = 1
|
|
|
|
local tick_hud = 0
|
|
local rate_hud = 1.5
|
|
|
|
minetest.register_globalstep(function(dtime)
|
|
|
|
tick_wake = tick_wake + dtime
|
|
tick_hud = tick_hud + dtime
|
|
|
|
if tick_wake > rate_wake then
|
|
tick_wake = 0
|
|
people.people_wake()
|
|
end
|
|
|
|
if tick_hud > rate_hud then
|
|
tick_hud = 0
|
|
|
|
dbg.v3("HUD TICK")
|
|
|
|
for _, player in pairs(minetest.get_connected_players()) do
|
|
local playername = player:get_player_name()
|
|
local pshow = people.hud_show_by_player[playername]
|
|
if pshow then
|
|
if not people.people[pshow.name] then
|
|
-- Probably the person died or was deleted...
|
|
people.hud_show_by_player[playername] = nil
|
|
player:hud_remove(pshow.id)
|
|
else
|
|
local pos, col
|
|
local ent = people.people[pshow.name].entity
|
|
if ent then
|
|
pos = ent.object:getpos()
|
|
col = 0xFF00FF
|
|
else
|
|
pos = vector.new(people.people[pshow.name].pos)
|
|
col = 0xFFFF00
|
|
end
|
|
pos.y = pos.y + 1.5
|
|
if not pshow.hudid then
|
|
pshow.hudid = player:hud_add({
|
|
hud_elem_type = "waypoint",
|
|
name = pshow.name,
|
|
text = "m",
|
|
number = col,
|
|
world_pos = pos
|
|
})
|
|
pshow.pos = pos
|
|
pshow.col = col
|
|
else
|
|
dbg.v3("HUD DIST ".. vector.distance(pos, pshow.pos))
|
|
if pshow.pos and vector.distance(pos, pshow.pos) > 1 then
|
|
pshow.pos = pos
|
|
player:hud_change(pshow.hudid, "world_pos", pos)
|
|
end
|
|
if pshow.col and col ~= pshow.col then
|
|
pshow.col = col
|
|
player:hud_change(pshow.hudid, "number", col)
|
|
end
|
|
end
|
|
|
|
end
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
-- Get a fake player object that resembles a real one, for the specified
|
|
-- entity. Can be used, in some circumstances, for making API calls that
|
|
-- would normally require a real player.
|
|
--
|
|
-- The player name is actually returned as the owner of the person. This
|
|
-- should allow it to, for example, dig with the same permissions as the
|
|
-- owner in protected areas, open doors, etc.
|
|
people.get_fake_player = function(ent)
|
|
|
|
local defer = function(x)
|
|
return (function() return x end)
|
|
end
|
|
return {
|
|
get_inventory_formspec = defer(""),
|
|
get_look_dir = defer({x=1,y=0,z=0}),
|
|
get_look_pitch = defer(0),
|
|
get_look_yaw = defer(0),
|
|
get_player_control = defer({jump=false, right=false, left=false, LMB=false, RMB=false, sneak=false, aux1=false, down=false, up=false}),
|
|
get_player_control_bits = defer(0),
|
|
get_player_name = defer(ent.owner),
|
|
is_player = defer(true),
|
|
set_inventory_formspec = defer(),
|
|
getpos = defer(ent.object:getpos()),
|
|
get_hp = defer(ent.object:get_hp()),
|
|
get_inventory = defer(ent.inventory),
|
|
get_wielded_item = function()
|
|
if ent.wielded and ent.inventory:contains_item("main", ent.wielded) then
|
|
return ItemStack(ent.wielded)
|
|
end
|
|
return ItemStack("")
|
|
end,
|
|
get_wield_index = defer(),
|
|
get_wield_list = defer("main"),
|
|
moveto = defer(),
|
|
punch = defer(),
|
|
remove = defer(),
|
|
right_click = defer(),
|
|
setpos = defer(),
|
|
set_hp = defer(),
|
|
set_properties = defer(),
|
|
set_wielded_item = defer(false),
|
|
set_animation = defer(),
|
|
set_attach = defer(),
|
|
set_detach = defer(),
|
|
set_bone_position = defer(),
|
|
}
|
|
end
|
|
|