Initial commit
commit
86aabfa9b2
|
@ -0,0 +1,212 @@
|
|||
#People Mod
|
||||
|
||||
NOTE: Requires my fork of minetest to work properly. In particular:
|
||||
* https://github.com/CiaranG/minetest/commit/740f057253495633db4cc53c905d4ea4ff1167c8
|
||||
* https://github.com/CiaranG/minetest/commit/7b7e8bd4b924b74271eb26b30fa681eb8bd780c5
|
||||
* https://github.com/CiaranG/minetest/commit/8eb489ece109e56c4d0fa01a0b956f7fa9f0ab56
|
||||
* https://github.com/CiaranG/minetest/commit/8772041b43b78c86bc32e5682391286bca498848
|
||||
* https://github.com/CiaranG/minetest/commit/f5b73e0baabbfe68bc6a9a52c5221930e4306ed2
|
||||
|
||||
This mod provides 'people' (non player characters) that are programmable
|
||||
in-game. Each NPC's Lua code runs inside a secure sandbox for that NPC only.
|
||||
|
||||
Author: Ciaran Gultnieks
|
||||
License: LGPL
|
||||
|
||||
Includes lua sandbox setup code from mesecons by Jeija (also LGPL), and inspiration
|
||||
from the npcf mod, and (obviously!) the mesecons lua controller design.
|
||||
|
||||
The character model is a modified version of the default player model from
|
||||
the default minetest game.
|
||||
|
||||
##Requirements
|
||||
|
||||
Optionally, you might want these mods:
|
||||
* areas (my fork of it) - https://github.com/CiaranG/areas - for footpath
|
||||
navigation support
|
||||
* moddebug - https://gitlab.com/CiaranG/moddebug - for debug logging
|
||||
|
||||
##Usage
|
||||
|
||||
To create a lua_npc, issue this command:
|
||||
|
||||
/people create Bob
|
||||
|
||||
This places a person called "Bob" at your current location.
|
||||
|
||||
Then, right-click the npc at any time to program it. For now, until I think
|
||||
of a better name, we'll refer to the code you enter here as "the person's
|
||||
lua code"!
|
||||
|
||||
##The Person's Lua Code
|
||||
|
||||
This code, as entered in the right-click dialog for a person you own, is
|
||||
really an event handler. It runs in a sandbox, and when called will always
|
||||
have a variable called 'event'. This is a table, and the "type" field
|
||||
determines the type of event that caused the call.
|
||||
|
||||
Here is a very simple example:
|
||||
|
||||
if event.type == "program" then
|
||||
action = {"follow", name="Ciaran"}
|
||||
end
|
||||
|
||||
When you enter this and hit the Program button, the person will start following
|
||||
the player called Ciaran and keep doing that until they log off. Because the
|
||||
code does nothing in response to an "act" event, at that point they will go into
|
||||
a permanent wait (sleep) state.
|
||||
|
||||
##Events
|
||||
|
||||
The following event types are received by the person's lua code:
|
||||
|
||||
###act
|
||||
|
||||
This happens when the person has no current action (see the Actions section).
|
||||
The handler should select a new action, by setting the 'action' variable. If
|
||||
it fails to do so, the person will do an automatic wait.
|
||||
|
||||
###tell
|
||||
|
||||
This happens when a player (who must be the owner, current) sends a message
|
||||
to the person. It can be used to respond to commands.
|
||||
|
||||
###step
|
||||
|
||||
This happens only when the "step" action is running. It's called every server
|
||||
step, with event.dtime containing the time since the last step. The handler
|
||||
should update the speed and yaw variables, or set a new action.
|
||||
|
||||
###program
|
||||
|
||||
This happens when the lua code has been edited.
|
||||
|
||||
###activate
|
||||
|
||||
This happens when the entity is activated (either it's brand new, or is in
|
||||
a block that was unloaded but is now loaded). It is called with event.dtime_s
|
||||
containing the time since it was last active.
|
||||
|
||||
###punched
|
||||
|
||||
This happens when a player punches the entity. The name of the offending player
|
||||
is sent as event.puncher.
|
||||
|
||||
##Actions
|
||||
|
||||
Actions are the basic building blocks of object behaviour. A person always
|
||||
has a current action which they are completing, the execution of which is
|
||||
handled by the mod. The job of the lua programming within the person is to
|
||||
sit at a higher level of 'conciousness' and select the appropriate actions.
|
||||
|
||||
The person's lua code receives an "act" event when it should select an
|
||||
action - usually when the current action completes. It can also set/replace
|
||||
the current action in response to any other event. Setting the action is
|
||||
a simple case of setting the 'action' variable within the lua code.
|
||||
|
||||
An action is defined by a table, where the first element [1] is the ID of
|
||||
the action, and additional named fields contain parameters. For a simple
|
||||
example, {"go", pos={5,0,5}} is an action to move the person to the given
|
||||
position.
|
||||
|
||||
###Action types
|
||||
|
||||
The following actions are currently defined:
|
||||
|
||||
####go, with pos={x,y,z}
|
||||
Walks to the given position.
|
||||
|
||||
####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.
|
||||
|
||||
####follow, with name="playername"
|
||||
Follows the given player indefinitely (or at least until they log off).
|
||||
|
||||
####face, with pos={x,y,z}
|
||||
Standing still, face towards the given position.
|
||||
|
||||
####wait, with time
|
||||
Do nothing for that many seconds. This may allow the entity to be deactivated
|
||||
and unloaded, in which case it will be reactivated at the appropriate time.
|
||||
Short waits may not result in deactivation.
|
||||
|
||||
####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
|
||||
needs to do this. It can update 'speed' and 'yaw' and these will be used.
|
||||
|
||||
###Nesting
|
||||
|
||||
Any action can have a "prevaction" field, which should be a valid action
|
||||
itself. It will be set when the action has completed. This can be used to nest
|
||||
actions.
|
||||
|
||||
The actions themselves may make use of this functionality. For example, the
|
||||
follow action is implemented by repeatedly selecting positions near the target
|
||||
player, and then replacing it self with a "go" action with the original follow
|
||||
nested as a "prevaction" within it. Thus, when the "go" completes, the follow
|
||||
resumes once more.
|
||||
|
||||
##Chat Commands
|
||||
|
||||
People can be managed and controlled via chat commands.
|
||||
|
||||
The following commands are available:
|
||||
|
||||
###/people help
|
||||
Lists all the available commands.
|
||||
|
||||
###/people create <name>
|
||||
Creates a new person, at your current location.
|
||||
|
||||
###/people delete <name>
|
||||
Deletes a person.
|
||||
|
||||
###/people where <name>
|
||||
Tells you the current location of a person.
|
||||
|
||||
###/people summon <name>
|
||||
Moves the person to your current location.
|
||||
|
||||
###/people setowner <name> <playername>
|
||||
Changes the owner of the person.
|
||||
|
||||
###/people tell <name> <message>
|
||||
The person receives a "tell" event with the given message.
|
||||
|
||||
###/people status
|
||||
Lists all people in the word, and their current status (active/inactive).
|
||||
|
||||
###/people skin <name> <skin_name|list>
|
||||
Sets the skin of the person to the named one. (Or use 'list' to get a
|
||||
list of available skins, which come from the skins mod).
|
||||
|
||||
##Autonomy and Activation
|
||||
|
||||
People are 'autonomous' - i.e. they will continue to act even when no player
|
||||
is nearby. Indeed, they will continue to act even when no player at all is
|
||||
logged in to the server.
|
||||
|
||||
However, they are designed to be able to not remain active unless necessary.
|
||||
This is mostly done by use of the "wait" action.
|
||||
|
||||
If you set a person to wait for 5 minutes, then assuming no player or other
|
||||
active entity is nearby, the block the person is in (and surrounding blocks)
|
||||
will be unloaded, and the person will cease to exist as far as the minetest
|
||||
engine is concerned. However, after 5 minutes, this mod will reactivate it
|
||||
by reloading the block.
|
||||
|
||||
###Force Loading
|
||||
This mod uses the 'force loading' API (which is silly and inadequate) to
|
||||
reactive sleeping entities when it needs to. It does not require the
|
||||
persistence over server restarts of force-loaded blocks, and while it ought
|
||||
to clean up any force loading it does almost immediately, that cannot happen
|
||||
if there is a server crash or other unclean shutdown at the wrong moment.
|
||||
You could mitigate this by always removing the force loaded blocks file from
|
||||
the world directory on startup.
|
||||
|
||||
Ultimately it won't use this 'feature' at all.
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,225 @@
|
|||
|
||||
local dbg
|
||||
if moddebug then dbg=moddebug.dbg("people") else dbg={v1=function() end,v2=function() end,v3=function() end} end
|
||||
|
||||
--- Get a person name from the next argument
|
||||
-- @return The person name, the actual person, and the rest of the
|
||||
-- arguments. If the person name is nil, the arguments are invalid.
|
||||
-- If person is nil, the name is valid, but it's not a person that
|
||||
-- actually exists.
|
||||
local get_person = function(args)
|
||||
|
||||
if not args then return nil, nil, nil end
|
||||
|
||||
person_name, args = string.match(args, "^([^ ]+)(.*)")
|
||||
|
||||
if person_name and not people.is_valid_name(person_name) then
|
||||
return nil, nil, nil
|
||||
end
|
||||
local person
|
||||
if person_name then person = people.people[person_name] end
|
||||
if args then args = string.sub(args, 2) end
|
||||
return person_name, person, args
|
||||
|
||||
end
|
||||
|
||||
local subcmd = {}
|
||||
|
||||
subcmd.help = function(playername, args)
|
||||
msg = "Subcommands:"
|
||||
for c, _ in pairs(subcmd) do
|
||||
msg = msg.." "..c
|
||||
end
|
||||
return msg, true
|
||||
end
|
||||
|
||||
subcmd.create = function(playername, args)
|
||||
local person, person_name
|
||||
person_name, person, args = get_person(args)
|
||||
if not person_name then
|
||||
return "Name needs to be specified", false
|
||||
end
|
||||
if person then
|
||||
return "Person "..person_name.." already exists", false
|
||||
end
|
||||
local player = minetest.get_player_by_name(playername)
|
||||
if not player then
|
||||
return "people create can only be used by a player", false
|
||||
end
|
||||
local pos = player:getpos()
|
||||
local obj = minetest.add_entity(pos, "people:person")
|
||||
if not obj then
|
||||
return "Failed to add_entity", false
|
||||
end
|
||||
local ent = obj:get_luaentity()
|
||||
ent.name = person_name
|
||||
ent.owner = playername
|
||||
people.people_add(ent)
|
||||
return "Created "..person_name, true
|
||||
end
|
||||
|
||||
subcmd.delete = function(playername, args)
|
||||
local person, person_name
|
||||
person_name, person, args = get_person(args)
|
||||
if not person then
|
||||
return "Specify a valid person"
|
||||
end
|
||||
local ent = people.people[person_name].entity
|
||||
if not ent then
|
||||
return "Can't delete when inactive", false
|
||||
end
|
||||
ent.object:remove()
|
||||
people.people[person_name] = nil
|
||||
return "Deleted "..person_name, true
|
||||
end
|
||||
|
||||
subcmd.summon = function(playername, args)
|
||||
local person, person_name
|
||||
person_name, person, args = get_person(args)
|
||||
if not person then
|
||||
return "Specify a valid person"
|
||||
end
|
||||
local player = minetest.get_player_by_name(playername)
|
||||
if not player then
|
||||
return "people summon can only be used by a player", false
|
||||
end
|
||||
local pos = player:getpos()
|
||||
local ent = people.people[person_name].entity
|
||||
if not ent then
|
||||
return "Can't summon because not active", false
|
||||
end
|
||||
ent.object:setpos(pos)
|
||||
return "Summoned "..person_name, true
|
||||
end
|
||||
|
||||
subcmd.where = function(playername, args)
|
||||
local person, person_name
|
||||
person_name, person, args = get_person(args)
|
||||
if not person then
|
||||
return "Specify a valid person"
|
||||
end
|
||||
local ent = people.people[person_name].entity
|
||||
if not ent then
|
||||
return person_name.." is inactive", false
|
||||
end
|
||||
return person_name.." is at "..minetest.pos_to_string(ent.object:getpos()), true
|
||||
end
|
||||
|
||||
subcmd.setowner = function(playername, person, person_name, args)
|
||||
local person, person_name
|
||||
person_name, person, args = get_person(args)
|
||||
if not person then
|
||||
return "Specify a valid person"
|
||||
end
|
||||
local ent = people.people[person_name].entity
|
||||
if not ent then
|
||||
return person_name.." is inactive", false
|
||||
end
|
||||
if not minetest.get_player_by_name(args) then
|
||||
return "No such new owner"
|
||||
end
|
||||
ent.owner = args
|
||||
return "Set owner of "..person_name.. " to "..args, true
|
||||
end
|
||||
|
||||
subcmd.list = function(playername, args)
|
||||
local count = 0
|
||||
for k, v in pairs(people.people) do
|
||||
local msg = k.." : "
|
||||
if v.entity then
|
||||
msg = msg.."active at "..minetest.pos_to_string(v.entity.object:getpos())
|
||||
else
|
||||
msg = msg.."inactive for another "..v.waketime - minetest.get_gametime().."s at "..minetest.pos_to_string(v.pos)
|
||||
end
|
||||
minetest.chat_send_player(playername, msg)
|
||||
count = count + 1
|
||||
end
|
||||
return count.." people", true
|
||||
end
|
||||
|
||||
subcmd.tell = function(playername, args)
|
||||
local person, person_name
|
||||
person_name, person, args = get_person(args)
|
||||
if not person then
|
||||
return "Specify a valid person"
|
||||
end
|
||||
local ent = people.people[person_name].entity
|
||||
if not ent then
|
||||
return person.name.." is inactive, so can't hear you", false
|
||||
end
|
||||
|
||||
if playername ~= ent.owner then
|
||||
return person.name.." doesn't listen to you", false
|
||||
end
|
||||
|
||||
local sender = minetest.get_player_by_name(playername)
|
||||
if not sender then return end
|
||||
senderpos = sender:getpos()
|
||||
ent.on_tell(sender, senderpos, args)
|
||||
end
|
||||
|
||||
subcmd.skin = function(playername, args)
|
||||
local person, person_name
|
||||
person_name, person, args = get_person(args)
|
||||
if not args then
|
||||
return "More arguments needed", false
|
||||
end
|
||||
|
||||
if not minetest.get_modpath("skins") then
|
||||
return "Skins mod is not installed", false
|
||||
end
|
||||
|
||||
if person_name == "list" then
|
||||
local texlist = ""
|
||||
for _, skin in ipairs(skins.list) do
|
||||
if string.match(skin, "^character_") then
|
||||
if texlist ~= "" then texlist = texlist.." " end
|
||||
texlist = texlist..string.sub(skin, 11)
|
||||
end
|
||||
end
|
||||
return "Available skins: "..texlist, true
|
||||
end
|
||||
|
||||
if not person then
|
||||
return "Specify a valid person, or 'list'", false
|
||||
end
|
||||
if not args then
|
||||
return "Specify skin", false
|
||||
end
|
||||
|
||||
local req = "character_"..args
|
||||
for _, skin in ipairs(skins.list) do
|
||||
if skin == req then
|
||||
person.entity.props.textures = {req..".png"}
|
||||
person.entity:update_props()
|
||||
return "Skin updated", true
|
||||
end
|
||||
end
|
||||
return "No such skin as '"..args.."'", false
|
||||
|
||||
end
|
||||
|
||||
|
||||
minetest.register_chatcommand("people", {
|
||||
params = "<cmd> [name] [args]",
|
||||
description = "Commands for working with the people module. 'people help' for help.",
|
||||
func = function(name, param)
|
||||
|
||||
if not minetest.check_player_privs(name, {server=true}) then
|
||||
return "People commands are currently all restricted to admins", false
|
||||
end
|
||||
|
||||
local cmd, args
|
||||
cmd, args = string.match(param, "^([^ ]+)(.*)")
|
||||
if not cmd then return subcmd.help(nil, nil, nil, nil) end
|
||||
|
||||
if subcmd[cmd] then
|
||||
if args then args = string.sub(args, 2) end
|
||||
return subcmd[cmd](name, args)
|
||||
end
|
||||
return "No such people command '"..cmd.."' - see 'people help'", false
|
||||
end
|
||||
})
|
||||
|
||||
|
||||
|
|
@ -0,0 +1 @@
|
|||
areas?
|
|
@ -0,0 +1,778 @@
|
|||
|
||||
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)
|
||||
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
After Width: | Height: | Size: 3.1 KiB |
|
@ -0,0 +1,115 @@
|
|||
|
||||
local dbg
|
||||
if moddebug then dbg=moddebug.dbg("people") else dbg={v1=function() end,v2=function() end,v3=function() end} end
|
||||
|
||||
-- This table tracks all the people in the world. It's keyed on the person
|
||||
-- name, and each entry is a table keyed as follows:
|
||||
-- entity - the active luaentity reference, or nil if not active
|
||||
-- pos - when inactive, the entity's position
|
||||
-- waketime - time in seconds until this entity should be re-awoken,
|
||||
-- or nil (only ever set when inactive)
|
||||
-- waking - used internally to track wakeup status
|
||||
-- dead - used internally to track when dead
|
||||
-- Changes to this should be via the functions below.
|
||||
people.people = {}
|
||||
local file = io.open(minetest.get_worldpath().."/people_people", "r")
|
||||
if file then
|
||||
people.people = minetest.deserialize(file:read("*all"))
|
||||
file:close()
|
||||
end
|
||||
|
||||
local people_save = function()
|
||||
-- We save without the entity field, because all will be inactive
|
||||
-- when reloaded
|
||||
local speople = {}
|
||||
for k, v in pairs(people.people) do
|
||||
speople[k] = {
|
||||
pos = v.pos,
|
||||
waketime = v.waketime,
|
||||
}
|
||||
end
|
||||
local file = io.open(minetest.get_worldpath().."/people_people", "w")
|
||||
if file then
|
||||
file:write(minetest.serialize(speople))
|
||||
file:close()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
people.people_add = function(entity)
|
||||
people.people[entity.name] = {entity=entity}
|
||||
dbg.v2(entity.name.." added")
|
||||
end
|
||||
|
||||
people.people_set_active = function(entity)
|
||||
local pr = people.people[entity.name]
|
||||
if not pr then
|
||||
-- Should never happen
|
||||
dbg.v1(entity.name.." reactivated without existing record")
|
||||
pr = {}
|
||||
people.people[entity.name] = pr
|
||||
end
|
||||
pr.entity = entity
|
||||
if pr.waketime then
|
||||
local loadpos = pr.pos
|
||||
if not loadpos then
|
||||
dbg.v1("Activated entity has waketime but no pos")
|
||||
else
|
||||
dbg.v2("Free forceload for "..minetest.pos_to_string(loadpos))
|
||||
minetest.forceload_free_block(loadpos)
|
||||
end
|
||||
pr.pos = nil
|
||||
pr.waketime = nil
|
||||
pr.waking = nil
|
||||
end
|
||||
dbg.v2(entity.name.." activated")
|
||||
end
|
||||
|
||||
people.people_set_inactive = function(entity)
|
||||
if not people.people[entity.name] then
|
||||
dbg.v1("people_set_inactive for "..entity.name or nil.." with no people record")
|
||||
return
|
||||
end
|
||||
if people.people[entity.name].dead then
|
||||
people.people[entity.name] = nil
|
||||
dbg.v2(entity.name.." deactivated forever")
|
||||
else
|
||||
people.people[entity.name].entity = nil
|
||||
people.people[entity.name].pos = entity.object:getpos()
|
||||
if entity.wait == 0 then
|
||||
people.people[entity.name].waketime = minetest.get_gametime() + 1
|
||||
else
|
||||
people.people[entity.name].waketime = entity.wait
|
||||
end
|
||||
dbg.v2(entity.name.." deactivated")
|
||||
end
|
||||
people_save()
|
||||
end
|
||||
|
||||
|
||||
--- Called at regular intervals
|
||||
-- Handles waking entities that are unloaded and shouldn't be any more.
|
||||
-- Once a second is probably about right, although more frequently wouldn't
|
||||
-- hurt.
|
||||
people.people_wake = function()
|
||||
|
||||
for k, v in pairs(people.people) do
|
||||
if v.waketime and v.waketime <= minetest.get_gametime() and not v.waking then
|
||||
|
||||
-- Need to flag that we're doing it, because there will be a
|
||||
-- delay before the block is actually loaded.
|
||||
v.waking = true
|
||||
|
||||
-- Wake the entity by (yuk) setting a forceload. The activation
|
||||
-- of the entity will cause the forceload to be freed.
|
||||
dbg.v2("Set forceload for "..minetest.pos_to_string(v.pos))
|
||||
minetest.forceload_block(v.pos)
|
||||
|
||||
-- Only one at a time, even though this will introduce some
|
||||
-- inaccuracy as to when they actually wake
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
Loading…
Reference in New Issue