779 lines
21 KiB
Lua
779 lines
21 KiB
Lua
|
|
local dbg
|
|
if moddebug then dbg=moddebug.dbg("people") else dbg={v1=function() end,v2=function() end,v3=function() end} end
|
|
|
|
people = {}
|
|
|
|
local people_modpath = minetest.get_modpath("people")
|
|
dofile(people_modpath .. "/commands.lua")
|
|
dofile(people_modpath .. "/tracking.lua")
|
|
|
|
|
|
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.get_player_last_online(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 face_dir = function(pos1, pos2)
|
|
local dx = pos1.x - pos2.x
|
|
local dz = pos2.z - pos1.z
|
|
return math.atan2(dx, dz)
|
|
end
|
|
|
|
local walk_velocity = function(speed, yaw)
|
|
if speed == 0 then
|
|
return {x=0, y=0, z=0}
|
|
end
|
|
yaw = yaw + math.pi * 0.5
|
|
local x = math.cos(yaw) * speed
|
|
local z = math.sin(yaw) * speed
|
|
return {x=x, y=0, z=z}
|
|
end
|
|
|
|
|
|
|
|
--- Find the next node along a footpath
|
|
-- All positions are exact (rounded) node positions.
|
|
-- @param curpos The position of a current footpath node
|
|
-- @param lastpos The position of the previous footpath node
|
|
-- @param samedironly True to only look in the current direction of travel
|
|
-- @return The position of the next footpath node, or nil
|
|
local footpath_findnext = function(curpos, lastpos, samedironly)
|
|
dbg.v3("footpath_findnext, cur="..minetest.pos_to_string(curpos)..", last="..minetest.pos_to_string(lastpos)..", same="..dump(samedironly))
|
|
local xz
|
|
if samedironly then
|
|
xz = {}
|
|
else
|
|
xz = {{x=1,z=0}, {x=0,z=1}, {x=-1,z=0}, {x=0,z=-1}}
|
|
end
|
|
-- Favour the direction we're already going
|
|
-- TODO - that means we could check it twice (but then again, only
|
|
-- if we reach an invalid bit of footpath!
|
|
table.insert(xz, 1, {x=curpos.x-lastpos.x, z=curpos.z-lastpos.z})
|
|
for y = 1, -1, -1 do
|
|
for _, cxz in ipairs(xz) do
|
|
local x = cxz.x
|
|
local z = cxz.z
|
|
local npos = vector.add(curpos, vector.new(x, y, z))
|
|
if not vector.equals(npos, lastpos) then
|
|
local n = minetest.get_node(npos)
|
|
if n.name == 'default:cobble' or
|
|
n.name == 'stairs:stair_cobble' or
|
|
n.name == 'default:wood' or
|
|
n.name == 'stairs:stair_wood' then
|
|
local n = minetest.get_node({x=npos.x,y=npos.y+1,z=npos.z})
|
|
if n.name == "default:snow" then
|
|
-- Snow is walkable, but we will either walk over it
|
|
-- or dig it out of the way, so that's fine...
|
|
dbg.v3("footpath_findnext found (snow-covered) "..minetest.pos_to_string(npos))
|
|
return npos
|
|
end
|
|
-- Otherwise, so long as there's nothing walkable above the
|
|
-- node it should be a valid footpath node...
|
|
if not minetest.registered_nodes[n.name].walkable then
|
|
dbg.v3("footpath_findnext found "..minetest.pos_to_string(npos))
|
|
return npos
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
return nil
|
|
end
|
|
|
|
|
|
local escape_pattern
|
|
do
|
|
local matches =
|
|
{
|
|
["^"] = "%^";
|
|
["$"] = "%$";
|
|
["("] = "%(";
|
|
[")"] = "%)";
|
|
["%"] = "%%";
|
|
["."] = "%.";
|
|
["["] = "%[";
|
|
["]"] = "%]";
|
|
["*"] = "%*";
|
|
["+"] = "%+";
|
|
["-"] = "%-";
|
|
["?"] = "%?";
|
|
["\0"] = "%z";
|
|
}
|
|
|
|
escape_pattern = function(s)
|
|
return (s:gsub(".", matches))
|
|
end
|
|
end
|
|
|
|
|
|
-- 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.
|
|
people.actions = {}
|
|
|
|
|
|
people.actions.step = function(state)
|
|
local event = {type="step", dtime=state.dtime}
|
|
local err = people.exec_event(self, event)
|
|
if err then dbg.v1(state.ent.name ..":Lua error "..err) end
|
|
state.yaw = self.env.yaw
|
|
state.speed = self.env.speed
|
|
return false
|
|
end
|
|
|
|
people.actions.face = function(state)
|
|
if state.action.pos and state.action.pos.x and state.action.pos.y and state.action.pos.z then
|
|
state.yaw = face_dir(statepos, state.action.pos)
|
|
end
|
|
return true
|
|
end
|
|
|
|
people.actions.wait = function(state)
|
|
if state.action.time and tonumber(state.action.time) then
|
|
state.wait = tonumber(state.action.time)
|
|
end
|
|
return true
|
|
end
|
|
|
|
people.actions.follow = function(state)
|
|
|
|
if not state.action.name or type(state.action.name) ~= "string" then
|
|
dbg.v1(state.ent.name.." has invalid follow target")
|
|
return true
|
|
end
|
|
|
|
local player = minetest.get_player_by_name(state.action.name)
|
|
if not player then
|
|
dbg.v1(state.ent.name.." can't find "..state.action.name.." to follow them")
|
|
return true
|
|
end
|
|
|
|
local targetpos = player:getpos()
|
|
local dist = vector.length(vector.subtract(targetpos, state.pos))
|
|
if dist < 5 then
|
|
-- We're near enough, so just face the target and wait a bit
|
|
state.yaw = face_dir(state.pos, targetpos)
|
|
state.wait = 4
|
|
return false
|
|
end
|
|
|
|
local dir = math.random(math.pi*2)
|
|
targetpos = vector.add(targetpos, walk_velocity(2, dir))
|
|
targetpos = vector.round(targetpos)
|
|
-- TODO find surface Y
|
|
dbg.v3(state.ent.name.." following "..state.action.name.." via "..minetest.pos_to_string(targetpos))
|
|
state.action = {"go", pos=targetpos, prevaction=state.action}
|
|
return false
|
|
|
|
end
|
|
|
|
people.actions.go = function(state)
|
|
|
|
if (not state.action.pos) and (not state.action.footpathdest) and state.action.name and areas and type(state.action.name) == "string" then
|
|
|
|
-- Look for a path node with the right name
|
|
local pathdestarea = areas:findNearestArea(state.pos, "^path_"..escape_pattern(state.action.name).."$")
|
|
if pathdestarea then
|
|
|
|
dbg.v2(state.ent.name.." selected path destination "..pathdestarea.name)
|
|
|
|
local pathstartarea = areas:findNearestArea(state.pos, "^path_.+")
|
|
if pathstartarea then
|
|
dbg.v2(state.ent.name.." selected path start "..pathstartarea.name.." at "..minetest.pos_to_string(pathstartarea.pos1))
|
|
state.action.pos = vector.new(pathstartarea.pos1)
|
|
state.action.pos.y = state.action.pos.y + 0.5
|
|
state.action.footpathdest = pathdestarea
|
|
return false
|
|
end
|
|
end
|
|
|
|
-- No path node, so look for any area with the actual name and
|
|
-- just go there. (TODO: could then find the nearest path junction
|
|
-- to it and head for that)
|
|
local nearest = areas:findNearestArea(state.pos, "$"..escape_pattern(state.action.name).."^")
|
|
if not nearest then
|
|
dbg.v2(state.ent.name.." couldn't find location "..state.action.name)
|
|
return true
|
|
end
|
|
|
|
dbg.v2(state.ent.name.." set destination for centre of area "..nearest.name)
|
|
state.action.pos = vector.interpolate(nearest.pos1, nearest.pos2, 0.5)
|
|
state.action.pos.y = state.action.pos.y - 0.5
|
|
return false
|
|
|
|
end
|
|
|
|
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 findNearestArea determines our lookahead distance
|
|
local curpatharea, distance = areas:findNearestArea(state.pos, "^path_.+", nil, 10)
|
|
dbg.v3(state.ent.name.." at new footpath node at "..minetest.pos_to_string(state.pos))
|
|
if curpatharea and curpatharea.pos1.x == state.pos.x and curpatharea.pos1.z == state.pos.z then
|
|
|
|
if curpatharea.name == state.action.footpathdest.name then
|
|
dbg.v2(state.ent.name.." arrived at footpath destination")
|
|
return true
|
|
end
|
|
|
|
-- We're at a junction
|
|
dbg.v2(state.ent.name.." is at footpath junction "..curpatharea.name.." - looking for route to "..state.action.name)
|
|
state.action.lastpathpos = vector.new(state.pos.x, state.pos.y - 0.5, state.pos.z)
|
|
local nextpathdest = areas:findNearestArea(state.pos, "^to_.*"..escape_pattern(state.action.name)..";.*", 3)
|
|
if not nextpathdest then
|
|
nextpathdest = areas:findNearestArea(state.pos, "^to_%*$", 3)
|
|
end
|
|
if not nextpathdest then
|
|
dbg.v2(state.ent.name.." can't find next footpath direction")
|
|
return true
|
|
end
|
|
dbg.v2(state.ent.name.." leaving junction via "..nextpathdest.name.." "..minetest.pos_to_string(nextpathdest.pos1))
|
|
state.action.pos = vector.add(nextpathdest.pos1, {x=0, y=-0.5, z=0})
|
|
return false
|
|
|
|
end
|
|
|
|
if not state.action.lastpathpos then
|
|
dbg.v1(state.ent.name.." should have lastpathpos")
|
|
return true
|
|
end
|
|
|
|
local thispathpos = vector.new(state.pos.x, state.pos.y - 0.5, state.pos.z)
|
|
local nextpathpos = footpath_findnext(thispathpos, state.action.lastpathpos, false)
|
|
if not nextpathpos then
|
|
dbg.v1(state.ent.name.." can't find next footpath node")
|
|
return true
|
|
end
|
|
|
|
if not distance then distance = 10 end
|
|
while distance > 2 do
|
|
-- Look ahead a bit while going in a straight line, to increase speed
|
|
local aheadpathpos = footpath_findnext(nextpathpos, thispathpos, true)
|
|
if aheadpathpos then
|
|
thispathpos = nextpathpos
|
|
nextpathpos = aheadpathpos
|
|
distance = distance - 1
|
|
else
|
|
break
|
|
end
|
|
end
|
|
state.action.lastpathpos = thispathpos
|
|
state.action.pos = vector.add(nextpathpos, {x=0, y=0.5, z=0})
|
|
dbg.v3(state.ent.name.." set next footpath dest "..minetest.pos_to_string(state.action.pos))
|
|
return false
|
|
|
|
end
|
|
|
|
if not state.action.pos or not state.action.pos.x or not state.action.pos.y or not state.action.pos.z then
|
|
dbg.v1(state.ent.name.." has invalid go action")
|
|
return true
|
|
end
|
|
|
|
-- Get our current destination, which might be either our
|
|
-- actual final one, or an intermediate point
|
|
local curdest
|
|
if state.action.intermediate then
|
|
curdest = state.action.intermediate[1]
|
|
else
|
|
curdest = state.action.pos
|
|
end
|
|
|
|
if not (type(curdest) == "table" and curdest.x and curdest.y and curdest.z) then
|
|
dbg.v1(state.ent.name.." has invalid destination")
|
|
curdest = pos
|
|
end
|
|
|
|
local distance = vector.distance({x=state.pos.x, y=0, z=state.pos.z}, {x=curdest.x, y=0, z=curdest.z})
|
|
if distance < 0.2 then
|
|
if state.action.intermediate then
|
|
dbg.v3(state.ent.name.." arrived intermediate ")
|
|
if #state.action.intermediate == 1 then
|
|
state.action.intermediate = nil
|
|
else
|
|
table.remove(state.action.intermediate, 1)
|
|
end
|
|
end
|
|
|
|
dbg.v2(state.ent.name.." arrived near "..minetest.pos_to_string(curdest))
|
|
state.setpos = {x=curdest.x, y=state.pos.y, z=curdest.z}
|
|
if state.action.footpathdest or not state.action.intermediate then
|
|
-- If we arrived at the pos, but we still have a
|
|
-- pathdest or intermediate dest, we carry on to do that.
|
|
state.action.pos = nil
|
|
return false
|
|
end
|
|
-- We've finished!
|
|
dbg.v1(state.ent.name.." completed go action at "..minetest.pos_to_string(curdest))
|
|
return true
|
|
|
|
else
|
|
|
|
local colres = state.ent.object:get_last_collision_result()
|
|
if colres.collides_xz then
|
|
dbg.v2(state.ent.name.." collided at "..minetest.pos_to_string(state.pos))
|
|
-- We've hit something...
|
|
if distance < 32 then
|
|
local path = minetest.find_path(state.pos, curdest,
|
|
distance + 2, 1, 3, "A*_noprefetch")
|
|
if not path then
|
|
|
|
dbg.v2(state.ent.name.." cannot find path from "..
|
|
minetest.pos_to_string(state.pos).." to "..
|
|
minetest.pos_to_string(curdest))
|
|
if state.action.intermediate then
|
|
state.action.intermediate = nil
|
|
end
|
|
state.wait = 2
|
|
return false
|
|
|
|
else
|
|
if not state.action.intermediate then
|
|
state.action.intermediate = {}
|
|
end
|
|
-- for i = #path, 2, -1 do -- OLD implementation - opposite order to new implementation!
|
|
for i = 1, #path-1 do
|
|
table.insert(state.action.intermediate, 1, {x=path[i].x, y=path[i].y + 0.5, z=path[i].z})
|
|
end
|
|
dbg.v3(state.ent.name.." found path: "..dump(path))
|
|
end
|
|
else
|
|
-- pick a couple of nearby points and walk to them, then try again
|
|
local randomdir = math.random(math.pi * 2)
|
|
vec = walk_velocity(24, randomdir)
|
|
local rpos = {x=math.floor(state.pos.x + vec.x + 0.5), y=state.pos.y, z=math.floor(state.pos.z + vec.z + 0.5)}
|
|
randomdir = math.random(math.pi * 2)
|
|
vec = walk_velocity(24, randomdir)
|
|
local rpos2 = {x=math.floor(rpos.x + vec.x + 0.5), y=state.pos.y, z=math.floor(rpos.z + vec.z + 0.5)}
|
|
if not state.action.intermediate then
|
|
state.action.intermediate = {}
|
|
end
|
|
table.insert(state.action.intermediate, 1, rpos2)
|
|
table.insert(state.action.intermediate, 1, rpos)
|
|
|
|
dbg.v2(state.ent.name.." hit something, far from destination - talking a random detour")
|
|
end
|
|
else
|
|
if distance < 1.5 then
|
|
state.speed = distance * 0.75
|
|
else
|
|
state.speed = 2
|
|
end
|
|
state.yaw = face_dir(state.pos, curdest)
|
|
end
|
|
|
|
end
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
local lastformbyplayer = {}
|
|
minetest.register_on_player_receive_fields(function(sender, formname, fields)
|
|
if formname ~= "people:form" then return end
|
|
if fields.quit then return end
|
|
if not fields.program then return end
|
|
local playername = sender:get_player_name()
|
|
local ent = lastformbyplayer[playername]
|
|
if not ent then return end
|
|
if playername ~= ent.owner then return end
|
|
ent.metadata.code = fields.code
|
|
local event = {type="program"}
|
|
local err = people.exec_event(ent, event)
|
|
if err then dbg.v1("Lua error "..err) end
|
|
end)
|
|
|
|
minetest.register_entity("people:person" ,{
|
|
hp_max = 1,
|
|
physical = true,
|
|
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,
|
|
name = "APerson",
|
|
owner = "",
|
|
metadata = {
|
|
code = "",
|
|
memory = {},
|
|
-- Current action. A table, as described in README.md
|
|
action = nil,
|
|
},
|
|
|
|
-- Time (game time) to wait until before doing anything else
|
|
wait = 0,
|
|
|
|
-- Lua environment
|
|
env = {},
|
|
|
|
stepheight = 1.1,
|
|
|
|
-- Customised properties
|
|
props = {},
|
|
|
|
on_activate = function(self, staticdata)
|
|
|
|
dbg.v3("on_activate, staticdata = "..dump(staticdata))
|
|
if staticdata and staticdata ~= "" then
|
|
local data = minetest.deserialize(staticdata)
|
|
if data then
|
|
if data.metadata then
|
|
self.metadata = data.metadata
|
|
end
|
|
if data.name then
|
|
self.name = data.name
|
|
end
|
|
if data.owner then
|
|
self.owner = data.owner
|
|
end
|
|
if data.wait then
|
|
self.wait = data.wait
|
|
end
|
|
if data.props then
|
|
self.props = data.props
|
|
end
|
|
end
|
|
|
|
if not people.is_valid_name(self.name) then
|
|
self.object:remove()
|
|
dbg.v1("Removing badly named person")
|
|
return
|
|
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
|
|
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,
|
|
metadata = self.metadata,
|
|
wait = self.wait,
|
|
props = self.props,
|
|
}
|
|
return minetest.serialize(data)
|
|
end,
|
|
|
|
on_punch = function(self, puncher, time_from_last_punch, tool_capabilities, dir, killed)
|
|
if killed then
|
|
if people.people[self.name] then
|
|
people.people[self.name].dead = true
|
|
else
|
|
dbg.v1("Unknown person killed")
|
|
end
|
|
return
|
|
end
|
|
|
|
local playername = puncher:get_player_name()
|
|
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()
|
|
errmsg = "..."
|
|
if playername == self.owner then
|
|
formspec = "size[10,8]"..
|
|
"textarea[0.2,0.6;10.2,5;code;;"..minetest.formspec_escape(self.metadata.code).."]"..
|
|
"button[3.75,6;2.5,1;program;Program]"..
|
|
"label[0.1,5;"..minetest.formspec_escape(errmsg).."]"
|
|
else
|
|
local message = "Hello non-owner"
|
|
formspec = "size[8,4]"
|
|
.."label[0,0;"..message.."]"
|
|
end
|
|
minetest.show_formspec(playername, "people:form", formspec)
|
|
lastformbyplayer[playername] = self
|
|
end,
|
|
|
|
on_tell = function(self, sender, senderpos, message)
|
|
local event = {type="tell", sender=sender, senderpos=senderpos, 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 return end
|
|
dbg.v3(self.name.." finished wait at "..minetest.get_gametime())
|
|
self.wait = 0
|
|
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 = self.object:getpos(),
|
|
|
|
-- 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 action
|
|
action = self.metadata.action,
|
|
|
|
-- 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 - 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,
|
|
}
|
|
|
|
local action_done = false
|
|
if not state.action or not state.action[1] then
|
|
-- No current action, so let the lua code choose one...
|
|
local event = {type="act", dtime=state.dtime}
|
|
local err = people.exec_event(self, event)
|
|
if err then dbg.v1("Lua error "..err) end
|
|
state.action = self.metadata.action
|
|
if not state.action then
|
|
self.metadata.action = {"wait", time=120}
|
|
dbg.v2(state.ent.name.." failed to choose action - setting wait")
|
|
else
|
|
dbg.v1(state.ent.name.." set new action: "..dump(self.metadata.action))
|
|
end
|
|
state.action = self.metadata.action
|
|
end
|
|
|
|
-- Run the appropriate handler for the current action...
|
|
local handler = people.actions[state.action[1]]
|
|
if handler then
|
|
action_done = handler(state)
|
|
-- Because state.action can be replaced...
|
|
self.metadata.action = state.action
|
|
else
|
|
dbg.v1(state.ent.name.." has unknown action: "..dump(self.metadata.action))
|
|
state.action = nil
|
|
end
|
|
|
|
if action_done then
|
|
if state.action.prevaction then
|
|
self.metadata.action = state.action.prevaction
|
|
else
|
|
self.metadata.action = nil
|
|
end
|
|
end
|
|
if state.wait ~= 0 then
|
|
self.wait = minetest.get_gametime() + state.wait
|
|
dbg.v3("Set "..self.name.." to 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 = "stand"
|
|
if state.speed > 0 then
|
|
wantanim = "walk"
|
|
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 = walk_velocity(state.speed, state.yaw)
|
|
setvel.y = -10
|
|
self.object:setvelocity(setvel)
|
|
self.object:setyaw(state.yaw)
|
|
|
|
end,
|
|
|
|
})
|
|
|
|
people.create_environment = function(ent)
|
|
|
|
return {
|
|
mem = ent.metadata.memory,
|
|
log = function(msg)
|
|
dbg.v1(" >> "..msg)
|
|
end,
|
|
tostring = tostring,
|
|
tonumber = tonumber,
|
|
type = type,
|
|
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.exec_event = function(ent, event)
|
|
|
|
local code = ent.metadata.code
|
|
local env = ent.env
|
|
env.action = ent.metadata.action
|
|
|
|
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.metadata.action) or env.action[1] ~= ent.metadata.action[1]) then
|
|
-- Cancel a wait if the action type was changed
|
|
ent.wait = 0
|
|
end
|
|
ent.metadata.action = env.action
|
|
|
|
end
|
|
|
|
local tick_wake = 0
|
|
local rate_wake = 1
|
|
|
|
minetest.register_globalstep(function(dtime)
|
|
|
|
tick_wake = tick_wake + dtime
|
|
|
|
if tick_wake > rate_wake then
|
|
tick_wake = 0
|
|
people.people_wake()
|
|
|
|
end
|
|
|
|
end)
|
|
|