diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 74ac53d0..ef7726e0 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -978,12 +978,14 @@ methods: ^ Dig node with the same effects that a player would cause - punch_node(pos) ^ Punch node with the same effects that a player would cause + +- get_meta(pos) -- Get a NodeMetaRef at that position +- get_node_timer(pos) -- Get NodeTimerRef - add_entity(pos, name): Spawn Lua-defined entity at position ^ Returns ObjectRef, or nil if failed - add_item(pos, item): Spawn item ^ Returns ObjectRef, or nil if failed -- get_meta(pos) -- Get a NodeMetaRef at that position - get_player_by_name(name) -- Get an ObjectRef to a player - get_objects_inside_radius(pos, radius) - set_timeofday(val): val: 0...1; 0 = midnight, 0.5 = midday @@ -1012,6 +1014,26 @@ methods: - to_table() -> nil or {fields = {...}, inventory = {list1 = {}, ...}} - from_table(nil or {}) ^ See "Node Metadata" + +NodeTimerRef: Node Timers - a high resolution persistent per-node timer +- Can be gotten via minetest.env:get_node_timer(pos) +methods: +- set(timeout,elapsed) + ^ set a timer's state + ^ timeout is in seconds, and supports fractional values (0.1 etc) + ^ elapsed is in seconds, and supports fractional values (0.1 etc) + ^ will trigger the node's on_timer function after timeout-elapsed seconds +- start(timeout) + ^ start a timer + ^ equivelent to set(timeout,0) +- stop() + ^ stops the timer +- get_timeout() -> current timeout in seconds + ^ if timeout is 0, timer is inactive +- get_elapsed() -> current elapsed time in seconds + ^ the node's on_timer function will be called after timeout-elapsed seconds +- is_started() -> boolean state of timer + ^ returns true if timer is started, otherwise false ObjectRef: Moving things in the game are generally these (basically reference to a C++ ServerActiveObject) @@ -1319,9 +1341,15 @@ Node definition (register_node) on_punch = func(pos, node, puncher), ^ default: minetest.node_punch ^ By default: does nothing - on_dig = func(pos, node, digger), + on_dig = func(pos, node, digger), ^ default: minetest.node_dig ^ By default: checks privileges, wears out tool and removes node + + on_timer = function(pos,elapsed), + ^ default: nil + ^ called by NodeTimers, see EnvRef and NodeTimerRef + ^ elapsed is the total time passed since the timer was started + ^ return true to run the timer for another cycle with the same timeout value on_receive_fields = func(pos, formname, fields, sender), ^ fields = {name1 = value1, name2 = value2, ...} diff --git a/src/environment.cpp b/src/environment.cpp index b55b3a6c..02ca1f71 100644 --- a/src/environment.cpp +++ b/src/environment.cpp @@ -774,10 +774,18 @@ void ServerEnvironment::activateBlock(MapBlock *block, u32 additional_dtime) activateObjects(block); // Run node timers - std::map elapsed_timers = + std::map elapsed_timers = block->m_node_timers.step((float)dtime_s); - if(!elapsed_timers.empty()) - errorstream<<"Node timers don't work yet!"<::iterator + i = elapsed_timers.begin(); + i != elapsed_timers.end(); i++){ + n = block->getNodeNoEx(i->first); + if(scriptapi_node_on_timer(m_lua,i->first,n,i->second.elapsed)) + block->setNodeTimer(i->first,NodeTimer(i->second.timeout,0)); + } + } /* Handle ActiveBlockModifiers */ ABMHandler abmhandler(m_abms, dtime_s, this, false); @@ -1058,10 +1066,18 @@ void ServerEnvironment::step(float dtime) "Timestamp older than 60s (step)"); // Run node timers - std::map elapsed_timers = - block->m_node_timers.step(dtime); - if(!elapsed_timers.empty()) - errorstream<<"Node timers don't work yet!"< elapsed_timers = + block->m_node_timers.step((float)dtime); + if(!elapsed_timers.empty()){ + MapNode n; + for(std::map::iterator + i = elapsed_timers.begin(); + i != elapsed_timers.end(); i++){ + n = block->getNodeNoEx(i->first); + if(scriptapi_node_on_timer(m_lua,i->first,n,i->second.elapsed)) + block->setNodeTimer(i->first,NodeTimer(i->second.timeout,0)); + } + } } } diff --git a/src/map.cpp b/src/map.cpp index c6e010c4..dc1f4506 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -1881,6 +1881,59 @@ void Map::removeNodeMetadata(v3s16 p) block->m_node_metadata.remove(p_rel); } +NodeTimer Map::getNodeTimer(v3s16 p) +{ + v3s16 blockpos = getNodeBlockPos(p); + v3s16 p_rel = p - blockpos*MAP_BLOCKSIZE; + MapBlock *block = getBlockNoCreateNoEx(blockpos); + if(!block){ + infostream<<"Map::getNodeTimer(): Need to emerge " + <m_node_timers.get(p_rel); + return t; +} + +void Map::setNodeTimer(v3s16 p, NodeTimer t) +{ + v3s16 blockpos = getNodeBlockPos(p); + v3s16 p_rel = p - blockpos*MAP_BLOCKSIZE; + MapBlock *block = getBlockNoCreateNoEx(blockpos); + if(!block){ + infostream<<"Map::setNodeTimer(): Need to emerge " + <m_node_timers.set(p_rel, t); +} + +void Map::removeNodeTimer(v3s16 p) +{ + v3s16 blockpos = getNodeBlockPos(p); + v3s16 p_rel = p - blockpos*MAP_BLOCKSIZE; + MapBlock *block = getBlockNoCreateNoEx(blockpos); + if(block == NULL) + { + infostream<<"WARNING: Map::removeNodeTimer(): Block not found" + <m_node_timers.remove(p_rel); +} + /* ServerMap */ diff --git a/src/map.h b/src/map.h index b25c2695..b561e5e7 100644 --- a/src/map.h +++ b/src/map.h @@ -32,6 +32,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "voxel.h" #include "modifiedstate.h" #include "util/container.h" +#include "nodetimer.h" extern "C" { #include "sqlite3.h" @@ -310,6 +311,15 @@ public: void setNodeMetadata(v3s16 p, NodeMetadata *meta); void removeNodeMetadata(v3s16 p); + /* + Node Timers + These are basically coordinate wrappers to MapBlock + */ + + NodeTimer getNodeTimer(v3s16 p); + void setNodeTimer(v3s16 p, NodeTimer t); + void removeNodeTimer(v3s16 p); + /* Misc. */ diff --git a/src/mapblock.cpp b/src/mapblock.cpp index efe628a4..62c670fd 100644 --- a/src/mapblock.cpp +++ b/src/mapblock.cpp @@ -621,9 +621,9 @@ void MapBlock::serialize(std::ostream &os, u8 version, bool disk) // (this field should have not been added) if(version == 23) writeU8(os, 0); - // Node timers (uncomment when node timers are taken into use) - /*if(version >= 24) - m_node_timers.serialize(os);*/ + // Node timers are in version 24 + if(version >= 24) + m_node_timers.serialize(os); // Static objects m_static_objects.serialize(os); @@ -703,15 +703,15 @@ void MapBlock::deSerialize(std::istream &is, u8 version, bool disk) if(disk) { // Node timers - if(version == 23) + if(version == 23){ // Read unused zero readU8(is); - // Uncomment when node timers are taken into use - /*else if(version >= 24){ + } + else if(version >= 24){ TRACESTREAM(<<"MapBlock::deSerialize "< NodeTimerList::step(float dtime) +std::map NodeTimerList::step(float dtime) { - std::map elapsed_timers; + std::map elapsed_timers; // Increment timers for(std::map::iterator i = m_data.begin(); @@ -126,13 +126,13 @@ std::map NodeTimerList::step(float dtime) v3s16 p = i->first; NodeTimer t = i->second; t.elapsed += dtime; - if(t.elapsed >= t.duration) - elapsed_timers.insert(std::make_pair(p, t.elapsed)); + if(t.elapsed >= t.timeout) + elapsed_timers.insert(std::make_pair(p, t)); else i->second = t; } // Delete elapsed timers - for(std::map::const_iterator + for(std::map::const_iterator i = elapsed_timers.begin(); i != elapsed_timers.end(); i++){ v3s16 p = i->first; diff --git a/src/nodetimer.h b/src/nodetimer.h index deb77f10..f8d3e1c5 100644 --- a/src/nodetimer.h +++ b/src/nodetimer.h @@ -35,15 +35,15 @@ with this program; if not, write to the Free Software Foundation, Inc., class NodeTimer { public: - NodeTimer(): duration(0.), elapsed(0.) {} - NodeTimer(f32 duration_, f32 elapsed_): - duration(duration_), elapsed(elapsed_) {} + NodeTimer(): timeout(0.), elapsed(0.) {} + NodeTimer(f32 timeout_, f32 elapsed_): + timeout(timeout_), elapsed(elapsed_) {} ~NodeTimer() {} void serialize(std::ostream &os) const; void deSerialize(std::istream &is); - f32 duration; + f32 timeout; f32 elapsed; }; @@ -81,7 +81,7 @@ public: } // A step in time. Returns map of elapsed timers. - std::map step(float dtime); + std::map step(float dtime); private: std::map m_data; diff --git a/src/scriptapi.cpp b/src/scriptapi.cpp index 6cb70168..12d2a824 100644 --- a/src/scriptapi.cpp +++ b/src/scriptapi.cpp @@ -3118,6 +3118,162 @@ const luaL_reg LuaPerlinNoise::methods[] = { {0,0} }; +/* + NodeTimerRef +*/ + +class NodeTimerRef +{ +private: + v3s16 m_p; + ServerEnvironment *m_env; + + static const char className[]; + static const luaL_reg methods[]; + + static int gc_object(lua_State *L) { + NodeTimerRef *o = *(NodeTimerRef **)(lua_touserdata(L, 1)); + delete o; + return 0; + } + + static NodeTimerRef *checkobject(lua_State *L, int narg) + { + luaL_checktype(L, narg, LUA_TUSERDATA); + void *ud = luaL_checkudata(L, narg, className); + if(!ud) luaL_typerror(L, narg, className); + return *(NodeTimerRef**)ud; // unbox pointer + } + + static int l_set(lua_State *L) + { + NodeTimerRef *o = checkobject(L, 1); + ServerEnvironment *env = o->m_env; + if(env == NULL) return 0; + f32 t = luaL_checknumber(L,2); + f32 e = luaL_checknumber(L,3); + env->getMap().setNodeTimer(o->m_p,NodeTimer(t,e)); + return 0; + } + + static int l_start(lua_State *L) + { + NodeTimerRef *o = checkobject(L, 1); + ServerEnvironment *env = o->m_env; + if(env == NULL) return 0; + f32 t = luaL_checknumber(L,2); + env->getMap().setNodeTimer(o->m_p,NodeTimer(t,0)); + return 0; + } + + static int l_stop(lua_State *L) + { + NodeTimerRef *o = checkobject(L, 1); + ServerEnvironment *env = o->m_env; + if(env == NULL) return 0; + env->getMap().removeNodeTimer(o->m_p); + return 0; + } + + static int l_is_started(lua_State *L) + { + NodeTimerRef *o = checkobject(L, 1); + ServerEnvironment *env = o->m_env; + if(env == NULL) return 0; + + NodeTimer t = env->getMap().getNodeTimer(o->m_p); + lua_pushboolean(L,(t.timeout != 0)); + return 1; + } + + static int l_get_timeout(lua_State *L) + { + NodeTimerRef *o = checkobject(L, 1); + ServerEnvironment *env = o->m_env; + if(env == NULL) return 0; + + NodeTimer t = env->getMap().getNodeTimer(o->m_p); + lua_pushnumber(L,t.timeout); + return 1; + } + + static int l_get_elapsed(lua_State *L) + { + NodeTimerRef *o = checkobject(L, 1); + ServerEnvironment *env = o->m_env; + if(env == NULL) return 0; + + NodeTimer t = env->getMap().getNodeTimer(o->m_p); + lua_pushnumber(L,t.elapsed); + return 1; + } + +public: + NodeTimerRef(v3s16 p, ServerEnvironment *env): + m_p(p), + m_env(env) + { + } + + ~NodeTimerRef() + { + } + + // Creates an NodeTimerRef and leaves it on top of stack + // Not callable from Lua; all references are created on the C side. + static void create(lua_State *L, v3s16 p, ServerEnvironment *env) + { + NodeTimerRef *o = new NodeTimerRef(p, env); + *(void **)(lua_newuserdata(L, sizeof(void *))) = o; + luaL_getmetatable(L, className); + lua_setmetatable(L, -2); + } + + static void set_null(lua_State *L) + { + NodeTimerRef *o = checkobject(L, -1); + o->m_env = NULL; + } + + static void Register(lua_State *L) + { + lua_newtable(L); + int methodtable = lua_gettop(L); + luaL_newmetatable(L, className); + int metatable = lua_gettop(L); + + lua_pushliteral(L, "__metatable"); + lua_pushvalue(L, methodtable); + lua_settable(L, metatable); // hide metatable from Lua getmetatable() + + lua_pushliteral(L, "__index"); + lua_pushvalue(L, methodtable); + lua_settable(L, metatable); + + lua_pushliteral(L, "__gc"); + lua_pushcfunction(L, gc_object); + lua_settable(L, metatable); + + lua_pop(L, 1); // drop metatable + + luaL_openlib(L, 0, methods, 0); // fill methodtable + lua_pop(L, 1); // drop methodtable + + // Cannot be created from Lua + //lua_register(L, className, create_object); + } +}; +const char NodeTimerRef::className[] = "NodeTimerRef"; +const luaL_reg NodeTimerRef::methods[] = { + method(NodeTimerRef, start), + method(NodeTimerRef, set), + method(NodeTimerRef, stop), + method(NodeTimerRef, is_started), + method(NodeTimerRef, get_timeout), + method(NodeTimerRef, get_elapsed), + {0,0} +}; + /* EnvRef */ @@ -3351,6 +3507,31 @@ private: return 1; } + // EnvRef:get_meta(pos) + static int l_get_meta(lua_State *L) + { + //infostream<<"EnvRef::l_get_meta()"<m_env; + if(env == NULL) return 0; + // Do it + v3s16 p = read_v3s16(L, 2); + NodeMetaRef::create(L, p, env); + return 1; + } + + // EnvRef:get_node_timer(pos) + static int l_get_node_timer(lua_State *L) + { + EnvRef *o = checkobject(L, 1); + ServerEnvironment *env = o->m_env; + if(env == NULL) return 0; + // Do it + v3s16 p = read_v3s16(L, 2); + NodeTimerRef::create(L, p, env); + return 1; + } + // EnvRef:add_entity(pos, entityname) -> ObjectRef or nil // pos = {x=num, y=num, z=num} static int l_add_entity(lua_State *L) @@ -3431,19 +3612,6 @@ private: return 0; } - // EnvRef:get_meta(pos) - static int l_get_meta(lua_State *L) - { - //infostream<<"EnvRef::l_get_meta()"<m_env; - if(env == NULL) return 0; - // Do it - v3s16 p = read_v3s16(L, 2); - NodeMetaRef::create(L, p, env); - return 1; - } - // EnvRef:get_player_by_name(name) static int l_get_player_by_name(lua_State *L) { @@ -3713,6 +3881,7 @@ const luaL_reg EnvRef::methods[] = { method(EnvRef, add_rat), method(EnvRef, add_firefly), method(EnvRef, get_meta), + method(EnvRef, get_node_timer), method(EnvRef, get_player_by_name), method(EnvRef, get_objects_inside_radius), method(EnvRef, set_timeofday), @@ -4729,6 +4898,7 @@ void scriptapi_export(lua_State *L, Server *server) LuaItemStack::Register(L); InvRef::Register(L); NodeMetaRef::Register(L); + NodeTimerRef::Register(L); ObjectRef::Register(L); EnvRef::Register(L); LuaPseudoRandom::Register(L); @@ -5482,6 +5652,29 @@ void scriptapi_node_after_destruct(lua_State *L, v3s16 p, MapNode node) script_error(L, "error: %s", lua_tostring(L, -1)); } +bool scriptapi_node_on_timer(lua_State *L, v3s16 p, MapNode node, f32 dtime) +{ + realitycheck(L); + assert(lua_checkstack(L, 20)); + StackUnroller stack_unroller(L); + + INodeDefManager *ndef = get_server(L)->ndef(); + + // Push callback function on stack + if(!get_item_callback(L, ndef->get(node).name.c_str(), "on_timer")) + return false; + + // Call function + push_v3s16(L, p); + lua_pushnumber(L,dtime); + if(lua_pcall(L, 2, 1, 0)) + script_error(L, "error: %s", lua_tostring(L, -1)); + if(lua_isboolean(L,-1) && lua_toboolean(L,-1) == true) + return true; + + return false; +} + void scriptapi_node_on_receive_fields(lua_State *L, v3s16 p, const std::string &formname, const std::map &fields, diff --git a/src/scriptapi.h b/src/scriptapi.h index 2fe662dd..2bacdeba 100644 --- a/src/scriptapi.h +++ b/src/scriptapi.h @@ -94,6 +94,8 @@ void scriptapi_node_on_construct(lua_State *L, v3s16 p, MapNode node); void scriptapi_node_on_destruct(lua_State *L, v3s16 p, MapNode node); // Node post-destructor void scriptapi_node_after_destruct(lua_State *L, v3s16 p, MapNode node); +// Node Timer event +bool scriptapi_node_on_timer(lua_State *L, v3s16 p, MapNode node, f32 dtime); // Called when a metadata form returns values void scriptapi_node_on_receive_fields(lua_State *L, v3s16 p, const std::string &formname, diff --git a/src/serialization.h b/src/serialization.h index 70b3ee74..266eada1 100644 --- a/src/serialization.h +++ b/src/serialization.h @@ -58,12 +58,13 @@ with this program; if not, write to the Free Software Foundation, Inc., 20: many existing content types translated to extended ones 21: dynamic content type allocation 22: minerals removed, facedir & wallmounted changed - 23: NodeTimers, new node metadata format + 23: new node metadata format + 24: NodeTimers */ // This represents an uninitialized or invalid format #define SER_FMT_VER_INVALID 255 // Highest supported serialization version -#define SER_FMT_VER_HIGHEST 23 +#define SER_FMT_VER_HIGHEST 24 // Lowest supported serialization version #define SER_FMT_VER_LOWEST 0 diff --git a/src/server.cpp b/src/server.cpp index 893d03b2..b3cbea6a 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -843,7 +843,7 @@ queue_full_break: /*timer_result = timer.stop(true); if(timer_result != 0) - infostream<<"GetNextBlocks duration: "<