Navigation Menu

Skip to content

Commit

Permalink
Add minetest.register_lbm() to run code on block load only
Browse files Browse the repository at this point in the history
  • Loading branch information
est31 committed Mar 7, 2016
1 parent 88fbe7c commit d494733
Show file tree
Hide file tree
Showing 12 changed files with 493 additions and 25 deletions.
10 changes: 9 additions & 1 deletion builtin/game/register.lua
Expand Up @@ -11,10 +11,11 @@ local register_alias_raw = core.register_alias_raw
core.register_alias_raw = nil

--
-- Item / entity / ABM registration functions
-- Item / entity / ABM / LBM registration functions
--

core.registered_abms = {}
core.registered_lbms = {}
core.registered_entities = {}
core.registered_items = {}
core.registered_nodes = {}
Expand Down Expand Up @@ -79,6 +80,13 @@ function core.register_abm(spec)
spec.mod_origin = core.get_current_modname() or "??"
end

function core.register_lbm(spec)
-- Add to core.registered_lbms
check_modname_prefix(spec.name)
core.registered_lbms[#core.registered_lbms + 1] = spec
spec.mod_origin = core.get_current_modname() or "??"
end

function core.register_entity(name, prototype)
-- Check name
if name == nil then
Expand Down
16 changes: 16 additions & 0 deletions doc/lua_api.txt
Expand Up @@ -1793,6 +1793,7 @@ Call these functions only at load time!

* `minetest.register_entity(name, prototype table)`
* `minetest.register_abm(abm definition)`
* `minetest.register_lbm(lbm definition)`
* `minetest.register_node(name, node definition)`
* `minetest.register_tool(name, item definition)`
* `minetest.register_craftitem(name, item definition)`
Expand Down Expand Up @@ -3305,6 +3306,21 @@ Definition tables
action = func(pos, node, active_object_count, active_object_count_wider),
}

### LBM (LoadingBlockModifier) definition (`register_lbm`)

{
name = "modname:replace_legacy_door",
nodenames = {"default:lava_source"},
-- ^ List of node names to trigger the LBM on.
-- Also non-registered nodes will work.
-- Groups (as of group:groupname) will work as well.
run_at_every_load = false,
-- ^ Whether to run the LBM's action every time a block gets loaded,
-- and not just for blocks that were saved last time before LBMs were
-- introduced to the world.
action = func(pos, node),
}

### Item definition (`register_node`, `register_craftitem`, `register_tool`)

{
Expand Down
250 changes: 250 additions & 0 deletions src/environment.cpp
Expand Up @@ -48,6 +48,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,

#define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")"

#define LBM_NAME_ALLOWED_CHARS "abcdefghijklmnopqrstuvwxyz0123456789_:"

Environment::Environment():
m_time_of_day_speed(0),
m_time_of_day(9000),
Expand Down Expand Up @@ -270,6 +272,223 @@ ABMWithState::ABMWithState(ActiveBlockModifier *abm_):
timer = myrand_range(minval, maxval);
}

/*
LBMManager
*/

void LBMContentMapping::deleteContents()
{
for (std::vector<LoadingBlockModifierDef *>::iterator it = lbm_list.begin();
it != lbm_list.end(); ++it) {
delete *it;
}
}

void LBMContentMapping::addLBM(LoadingBlockModifierDef *lbm_def, IGameDef *gamedef)
{
// Add the lbm_def to the LBMContentMapping.
// Unknown names get added to the global NameIdMapping.
INodeDefManager *nodedef = gamedef->ndef();

lbm_list.push_back(lbm_def);

for (std::set<std::string>::const_iterator it = lbm_def->trigger_contents.begin();
it != lbm_def->trigger_contents.end(); ++it) {
std::set<content_t> c_ids;
bool found = nodedef->getIds(*it, c_ids);
if (!found) {
content_t c_id = gamedef->allocateUnknownNodeId(*it);
if (c_id == CONTENT_IGNORE) {
// Seems it can't be allocated.
warningstream << "Could not internalize node name \"" << *it
<< "\" while loading LBM \"" << lbm_def->name << "\"." << std::endl;
continue;
}
c_ids.insert(c_id);
}

for (std::set<content_t>::const_iterator iit =
c_ids.begin(); iit != c_ids.end(); ++iit) {
content_t c_id = *iit;
map[c_id].push_back(lbm_def);
}
}
}

const std::vector<LoadingBlockModifierDef *> *
LBMContentMapping::lookup(content_t c) const
{
container_map::const_iterator it = map.find(c);
if (it == map.end())
return NULL;
// This first dereferences the iterator, returning
// a std::vector<LoadingBlockModifierDef *>
// reference, then we convert it to a pointer.
return &(it->second);
}

LBMManager::~LBMManager()
{
for (std::map<std::string, LoadingBlockModifierDef *>::iterator it =
m_lbm_defs.begin(); it != m_lbm_defs.end(); ++it) {
delete it->second;
}
for (lbm_lookup_map::iterator it = m_lbm_lookup.begin();
it != m_lbm_lookup.end(); ++it) {
(it->second).deleteContents();
}
}

void LBMManager::addLBMDef(LoadingBlockModifierDef *lbm_def)
{
// Precondition, in query mode the map isn't used anymore
FATAL_ERROR_IF(m_query_mode == true,
"attempted to modify LBMManager in query mode");

if (!string_allowed(lbm_def->name, LBM_NAME_ALLOWED_CHARS)) {
throw ModError("Error adding LBM \"" + lbm_def->name +
"\": Does not follow naming conventions: "
"Only chararacters [a-z0-9_:] are allowed.");
}

m_lbm_defs[lbm_def->name] = lbm_def;
}

void LBMManager::loadIntroductionTimes(const std::string &times,
IGameDef *gamedef, u32 now)
{
m_query_mode = true;

// name -> time map.
// Storing it in a map first instead of
// handling the stuff directly in the loop
// removes all duplicate entries.
// TODO make this std::unordered_map
std::map<std::string, u32> introduction_times;

/*
The introduction times string consists of name~time entries,
with each entry terminated by a semicolon. The time is decimal.
*/

size_t idx = 0;
size_t idx_new;
while ((idx_new = times.find(";", idx)) != std::string::npos) {
std::string entry = times.substr(idx, idx_new - idx);
std::vector<std::string> components = str_split(entry, '~');
if (components.size() != 2)
throw SerializationError("Introduction times entry \""
+ entry + "\" requires exactly one '~'!");
const std::string &name = components[0];
u32 time = from_string<u32>(components[1]);
introduction_times[name] = time;
idx = idx_new + 1;
}

// Put stuff from introduction_times into m_lbm_lookup
for (std::map<std::string, u32>::const_iterator it = introduction_times.begin();
it != introduction_times.end(); ++it) {
const std::string &name = it->first;
u32 time = it->second;

std::map<std::string, LoadingBlockModifierDef *>::iterator def_it =
m_lbm_defs.find(name);
if (def_it == m_lbm_defs.end()) {
// This seems to be an LBM entry for
// an LBM we haven't loaded. Discard it.
continue;
}
LoadingBlockModifierDef *lbm_def = def_it->second;
if (lbm_def->run_at_every_load) {
// This seems to be an LBM entry for
// an LBM that runs at every load.
// Don't add it just yet.
continue;
}

m_lbm_lookup[time].addLBM(lbm_def, gamedef);

// Erase the entry so that we know later
// what elements didn't get put into m_lbm_lookup
m_lbm_defs.erase(name);
}

// Now also add the elements from m_lbm_defs to m_lbm_lookup
// that weren't added in the previous step.
// They are introduced first time to this world,
// or are run at every load (introducement time hardcoded to U32_MAX).

LBMContentMapping &lbms_we_introduce_now = m_lbm_lookup[now];
LBMContentMapping &lbms_running_always = m_lbm_lookup[U32_MAX];

for (std::map<std::string, LoadingBlockModifierDef *>::iterator it =
m_lbm_defs.begin(); it != m_lbm_defs.end(); ++it) {
if (it->second->run_at_every_load) {
lbms_running_always.addLBM(it->second, gamedef);
} else {
lbms_we_introduce_now.addLBM(it->second, gamedef);
}
}

// Clear the list, so that we don't delete remaining elements
// twice in the destructor
m_lbm_defs.clear();
}

std::string LBMManager::createIntroductionTimesString()
{
// Precondition, we must be in query mode
FATAL_ERROR_IF(m_query_mode == false,
"attempted to query on non fully set up LBMManager");

std::ostringstream oss;
for (lbm_lookup_map::iterator it = m_lbm_lookup.begin();
it != m_lbm_lookup.end(); ++it) {
u32 time = it->first;
std::vector<LoadingBlockModifierDef *> &lbm_list = it->second.lbm_list;
for (std::vector<LoadingBlockModifierDef *>::iterator iit = lbm_list.begin();
iit != lbm_list.end(); ++iit) {
// Don't add if the LBM runs at every load,
// then introducement time is hardcoded
// and doesn't need to be stored
if ((*iit)->run_at_every_load)
continue;
oss << (*iit)->name << "~" << time << ";";
}
}
return oss.str();
}

void LBMManager::applyLBMs(ServerEnvironment *env, MapBlock *block, u32 stamp)
{
// Precondition, we need m_lbm_lookup to be initialized
FATAL_ERROR_IF(m_query_mode == false,
"attempted to query on non fully set up LBMManager");
v3s16 pos_of_block = block->getPosRelative();
v3s16 pos;
MapNode n;
content_t c;
lbm_lookup_map::const_iterator it = getLBMsIntroducedAfter(stamp);
for (pos.X = 0; pos.X < MAP_BLOCKSIZE; pos.X++)
for (pos.Y = 0; pos.Y < MAP_BLOCKSIZE; pos.Y++)
for (pos.Z = 0; pos.Z < MAP_BLOCKSIZE; pos.Z++)
{
n = block->getNodeNoEx(pos);
c = n.getContent();
for (LBMManager::lbm_lookup_map::const_iterator iit = it;
iit != m_lbm_lookup.end(); ++iit) {
const std::vector<LoadingBlockModifierDef *> *lbm_list =
iit->second.lookup(c);
if (!lbm_list)
continue;
for (std::vector<LoadingBlockModifierDef *>::const_iterator iit =
lbm_list->begin(); iit != lbm_list->end(); ++iit) {
(*iit)->trigger(env, pos + pos_of_block, n);
}
}
}
}

/*
ActiveBlockList
*/
Expand Down Expand Up @@ -505,6 +724,9 @@ void ServerEnvironment::saveMeta()
args.setU64("game_time", m_game_time);
args.setU64("time_of_day", getTimeOfDay());
args.setU64("last_clear_objects_time", m_last_clear_objects_time);
args.setU64("lbm_introduction_times_version", 1);
args.set("lbm_introduction_times",
m_lbm_mgr.createIntroductionTimesString());
args.writeLines(ss);
ss<<"EnvArgsEnd\n";

Expand Down Expand Up @@ -555,6 +777,26 @@ void ServerEnvironment::loadMeta()
// If missing, do as if clearObjects was never called
m_last_clear_objects_time = 0;
}

std::string lbm_introduction_times = "";
try {
u64 ver = args.getU64("lbm_introduction_times_version");
if (ver == 1) {
lbm_introduction_times = args.get("lbm_introduction_times");
} else {
infostream << "ServerEnvironment::loadMeta(): Non-supported"
<< " introduction time version " << ver << std::endl;
}
} catch (SettingNotFoundException &e) {
// No problem, this is expected. Just continue with an empty string
}
m_lbm_mgr.loadIntroductionTimes(lbm_introduction_times, m_gamedef, m_game_time);

}

void ServerEnvironment::loadDefaultMeta()
{
m_lbm_mgr.loadIntroductionTimes("", m_gamedef, m_game_time);
}

struct ActiveABM
Expand Down Expand Up @@ -770,6 +1012,9 @@ void ServerEnvironment::activateBlock(MapBlock *block, u32 additional_dtime)
// Activate stored objects
activateObjects(block, dtime_s);

/* Handle LoadingBlockModifiers */
m_lbm_mgr.applyLBMs(this, block, stamp);

// Run node timers
std::map<v3s16, NodeTimer> elapsed_timers =
block->m_node_timers.step((float)dtime_s);
Expand All @@ -795,6 +1040,11 @@ void ServerEnvironment::addActiveBlockModifier(ActiveBlockModifier *abm)
m_abms.push_back(ABMWithState(abm));
}

void ServerEnvironment::addLoadingBlockModifierDef(LoadingBlockModifierDef *lbm)
{
m_lbm_mgr.addLBMDef(lbm);
}

bool ServerEnvironment::setNode(v3s16 p, const MapNode &n)
{
INodeDefManager *ndef = m_gamedef->ndef();
Expand Down

1 comment on commit d494733

@JurajVajda
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yay! I need this so much..hope i can replace my abm with this and cleanup my map from unknown nodes without a lag...+1 for merging

Please sign in to comment.