1
0
Fork 0
mirror of https://github.com/luanti-org/luanti.git synced 2025-09-15 18:57:08 +00:00

Merge branch 'luanti-org:master' into master

This commit is contained in:
BlackImpostor 2025-05-31 08:03:04 +03:00 committed by GitHub
commit f3071dcd82
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 499 additions and 341 deletions

View file

@ -102,7 +102,7 @@ end
local translation_file_header = [[
// This file is automatically generated
// It contains a bunch of fake gettext calls, to tell xgettext about the strings in config files
// To update it, refer to the bottom of builtin/mainmenu/dlg_settings_advanced.lua
// To update it, refer to the bottom of builtin/common/settings/init.lua
fake_function() {]]
@ -110,15 +110,15 @@ local function create_translation_file(settings)
local result = { translation_file_header }
for _, entry in ipairs(settings) do
if entry.type == "category" then
insert(result, sprintf("\tgettext(%q);", entry.name))
insert(result, sprintf("\t/* xgettext:no-c-format */ gettext(%q);", entry.name))
else
if entry.readable_name then
insert(result, sprintf("\tgettext(%q);", entry.readable_name))
insert(result, sprintf("\t/* xgettext:no-c-format */ gettext(%q);", entry.readable_name))
end
if entry.comment ~= "" then
local comment_escaped = entry.comment:gsub("\n", "\\n")
comment_escaped = comment_escaped:gsub("\"", "\\\"")
insert(result, "\tgettext(\"" .. comment_escaped .. "\");")
insert(result, "\t/* xgettext:no-c-format */ gettext(\"" .. comment_escaped .. "\");")
end
end
end

View file

@ -5005,7 +5005,8 @@ A VoxelManip object can be created any time using either:
If the optional position parameters are present for either of these routines,
the specified region will be pre-loaded into the VoxelManip object on creation.
Otherwise, the area of map you wish to manipulate must first be loaded into the
VoxelManip object using `VoxelManip:read_from_map()`.
VoxelManip object using `VoxelManip:read_from_map()`, or an empty one created
with `VoxelManip:initialize()`.
Note that `VoxelManip:read_from_map()` returns two position vectors. The region
formed by these positions indicate the minimum and maximum (respectively)
@ -5016,14 +5017,14 @@ be queried any time after loading map data with `VoxelManip:get_emerged_area()`.
Now that the VoxelManip object is populated with map data, your mod can fetch a
copy of this data using either of two methods. `VoxelManip:get_node_at()`,
which retrieves an individual node in a MapNode formatted table at the position
requested is the simplest method to use, but also the slowest.
requested. This is the simplest method to use, but also the slowest.
Nodes in a VoxelManip object may also be read in bulk to a flat array table
using:
* `VoxelManip:get_data()` for node content (in Content ID form, see section
[Content IDs]),
* `VoxelManip:get_light_data()` for node light levels, and
* `VoxelManip:get_light_data()` for node param (usually light levels), and
* `VoxelManip:get_param2_data()` for the node type-dependent "param2" values.
See section [Flat array format] for more details.
@ -5038,17 +5039,16 @@ internal state unless otherwise explicitly stated.
Once the bulk data has been edited to your liking, the internal VoxelManip
state can be set using:
* `VoxelManip:set_data()` for node content (in Content ID form, see section
[Content IDs]),
* `VoxelManip:set_light_data()` for node light levels, and
* `VoxelManip:set_param2_data()` for the node type-dependent `param2` values.
* `VoxelManip:set_data()` or
* `VoxelManip:set_light_data()` or
* `VoxelManip:set_param2_data()`
The parameter to each of the above three functions can use any table at all in
the same flat array format as produced by `get_data()` etc. and is not required
to be a table retrieved from `get_data()`.
Once the internal VoxelManip state has been modified to your liking, the
changes can be committed back to the map by calling `VoxelManip:write_to_map()`
changes can be committed back to the map by calling `VoxelManip:write_to_map()`.
### Flat array format
@ -5180,15 +5180,22 @@ inside the VoxelManip.
Methods
-------
* `read_from_map(p1, p2)`: Loads a chunk of map into the VoxelManip object
* `read_from_map(p1, p2)`: Loads a part of the map into the VoxelManip object
containing the region formed by `p1` and `p2`.
* returns actual emerged `pmin`, actual emerged `pmax`
* returns actual emerged `pmin`, actual emerged `pmax` (MapBlock-aligned)
* Note that calling this multiple times will *add* to the area loaded in the
VoxelManip, and not reset it.
* `initialize(p1, p2, [node])`: Clears and resizes the VoxelManip object to
comprise the region formed by `p1` and `p2`.
* **No data** is read from the map, so you can use this to treat `VoxelManip`
objects as general containers of node data.
* `node`: if present the data will be filled with this node; if not it will
be uninitialized
* returns actual emerged `pmin`, actual emerged `pmax` (MapBlock-aligned)
* (introduced in 5.13.0)
* `write_to_map([light])`: Writes the data loaded from the `VoxelManip` back to
the map.
* **important**: data must be set using `VoxelManip:set_data()` before
calling this.
* **important**: you should call `set_data()` before this, or nothing will change.
* if `light` is true, then lighting is automatically recalculated.
The default value is true.
If `light` is false, no light calculations happen, and you should correct
@ -5249,6 +5256,15 @@ Methods
where the engine will keep the map and the VM in sync automatically.
* Note: this doesn't do what you think it does and is subject to removal. Don't use it!
* `get_emerged_area()`: Returns actual emerged minimum and maximum positions.
* "Emerged" does not imply that this region was actually loaded from the map,
if `initialize()` has been used.
* `close()`: Frees the data buffers associated with the VoxelManip object.
It will become empty.
* Since Lua's garbage collector is not aware of the potentially significant
memory behind a VoxelManip, frequent VoxelManip usage can cause the server to
run out of RAM. Therefore it's recommend to call this method once you're done
with the VoxelManip.
* (introduced in 5.13.0)
`VoxelArea`
-----------

View file

@ -23,8 +23,8 @@ Callbacks
* `core.button_handler(fields)`: called when a button is pressed.
* `fields` = `{name1 = value1, name2 = value2, ...}`
* `core.event_handler(event)`
* `event`: `"MenuQuit"`, `"KeyEnter"`, `"ExitButton"`, `"EditBoxEnter"` or
`"FullscreenChange"`
* `event`: `"MenuQuit"` (derived from `quit`) or `"FullscreenChange"`
The main menu may issue custom events, such as `"Refresh"` (server list).
* `core.on_before_close()`: called before the menu is closed, either to exit or
to join a game

View file

@ -31,16 +31,6 @@ struct TextDestNodeMetadata : public TextDest
m_p = p;
m_client = client;
}
// This is deprecated I guess? -celeron55
void gotText(const std::wstring &text)
{
std::string ntext = wide_to_utf8(text);
infostream << "Submitting 'text' field of node at (" << m_p.X << ","
<< m_p.Y << "," << m_p.Z << "): " << ntext << std::endl;
StringMap fields;
fields["text"] = ntext;
m_client->sendNodemetaFields(m_p, "", fields);
}
void gotText(const StringMap &fields)
{
m_client->sendNodemetaFields(m_p, "", fields);

View file

@ -27,12 +27,13 @@ public:
void fill(v3s16 bpmin, v3s16 bpmax, MapNode n)
{
for (s16 z = bpmin.Z; z <= bpmax.Z; z++)
for (s16 y = bpmin.Y; y <= bpmax.Y; y++)
for (s16 x = bpmin.X; x <= bpmax.X; x++) {
for (s16 x = bpmin.X; x <= bpmax.X; x++)
for (s16 y = bpmin.Y; y <= bpmax.Y; y++) {
MapBlock *block = getBlockNoCreateNoEx({x, y, z});
if (block) {
auto *data = block->getData();
for (size_t i = 0; i < MapBlock::nodecount; i++)
block->getData()[i] = n;
data[i] = n;
block->expireIsAirCache();
}
}

View file

@ -40,12 +40,6 @@ void TextDestGuiEngine::gotText(const StringMap &fields)
m_engine->getScriptIface()->handleMainMenuButtons(fields);
}
/******************************************************************************/
void TextDestGuiEngine::gotText(const std::wstring &text)
{
m_engine->getScriptIface()->handleMainMenuEvent(wide_to_utf8(text));
}
/******************************************************************************/
MenuTextureSource::~MenuTextureSource()
{

View file

@ -61,12 +61,6 @@ public:
*/
void gotText(const StringMap &fields);
/**
* receive text/events transmitted by guiFormSpecMenu
* @param text textual representation of event
*/
void gotText(const std::wstring &text);
private:
/** target to transmit data to */
GUIEngine *m_engine = nullptr;

View file

@ -77,6 +77,9 @@
return; \
}
// Element ID of the "Proceed" button shown for sizeless formspecs
constexpr s32 ID_PROCEED_BTN = 257;
/*
GUIFormSpecMenu
*/
@ -2998,7 +3001,7 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
gui::IGUIElement *focused_element = Environment->getFocus();
if (focused_element && focused_element->getParent() == this) {
s32 focused_id = focused_element->getID();
if (focused_id > 257) {
if (focused_id > ID_PROCEED_BTN) {
for (const GUIFormSpecMenu::FieldSpec &field : m_fields) {
if (field.fid == focused_id) {
m_focused_element = field.fname;
@ -3308,7 +3311,7 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
size.X / 2 - 70, pos.Y,
size.X / 2 - 70 + 140, pos.Y + m_btn_height * 2
);
GUIButton::addButton(Environment, mydata.rect, m_tsrc, this, 257,
GUIButton::addButton(Environment, mydata.rect, m_tsrc, this, ID_PROCEED_BTN,
wstrgettext("Proceed").c_str());
}
}
@ -4031,12 +4034,7 @@ bool GUIFormSpecMenu::preprocessEvent(const SEvent& event)
if (m_joystick->wasKeyDown(KeyType::ESC)) {
tryClose();
} else if (m_joystick->wasKeyDown(KeyType::JUMP)) {
if (m_allowclose) {
acceptInput(quit_mode_accept);
quitMenu();
} else {
acceptInput(quit_mode_try);
}
trySubmitClose();
}
}
return handled;
@ -4056,6 +4054,16 @@ void GUIFormSpecMenu::tryClose()
}
}
void GUIFormSpecMenu::trySubmitClose()
{
if (m_allowclose) {
acceptInput(quit_mode_accept);
quitMenu();
} else {
acceptInput(quit_mode_try);
}
}
bool GUIFormSpecMenu::OnEvent(const SEvent& event)
{
if (event.EventType==EET_KEY_INPUT_EVENT) {
@ -4099,12 +4107,7 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
break;
}
if (current_keys_pending.key_enter) {
if (m_allowclose) {
acceptInput(quit_mode_accept);
quitMenu();
} else {
acceptInput(quit_mode_try);
}
trySubmitClose();
} else {
acceptInput();
}
@ -4818,12 +4821,18 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
}
if (event.EventType == EET_GUI_EVENT) {
if (event.GUIEvent.EventType == gui::EGET_TAB_CHANGED
&& isVisible()) {
// find the element that was clicked
const s32 caller_id = event.GUIEvent.Caller->getID();
bool close_on_enter;
switch (event.GUIEvent.EventType) {
case gui::EGET_TAB_CHANGED:
if (!isVisible())
break;
// find the element that was clicked
for (GUIFormSpecMenu::FieldSpec &s : m_fields) {
if ((s.ftype == f_TabHeader) &&
(s.fid == event.GUIEvent.Caller->getID())) {
if (s.ftype == f_TabHeader &&
s.fid == caller_id) {
if (!s.sound.empty() && m_sound_manager)
m_sound_manager->playSound(0, SoundSpec(s.sound, 1.0f));
s.send = true;
@ -4832,26 +4841,26 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
return true;
}
}
}
if (event.GUIEvent.EventType == gui::EGET_ELEMENT_FOCUS_LOST
&& isVisible()) {
break;
case gui::EGET_ELEMENT_FOCUS_LOST:
if (!isVisible())
break;
if (!canTakeFocus(event.GUIEvent.Element)) {
infostream<<"GUIFormSpecMenu: Not allowing focus change."
<<std::endl;
// Returning true disables focus change
return true;
}
}
if ((event.GUIEvent.EventType == gui::EGET_BUTTON_CLICKED) ||
(event.GUIEvent.EventType == gui::EGET_CHECKBOX_CHANGED) ||
(event.GUIEvent.EventType == gui::EGET_COMBO_BOX_CHANGED) ||
(event.GUIEvent.EventType == gui::EGET_SCROLL_BAR_CHANGED)) {
s32 caller_id = event.GUIEvent.Caller->getID();
break;
if (caller_id == 257) {
acceptInput(quit_mode_accept);
m_text_dst->gotText(L"ExitButton");
quitMenu();
case gui::EGET_BUTTON_CLICKED:
case gui::EGET_CHECKBOX_CHANGED:
case gui::EGET_COMBO_BOX_CHANGED:
case gui::EGET_SCROLL_BAR_CHANGED:
if (caller_id == ID_PROCEED_BTN) {
trySubmitClose();
return true;
}
@ -4881,7 +4890,6 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
if (s.is_exit) {
acceptInput(quit_mode_accept);
m_text_dst->gotText(L"ExitButton");
quitMenu();
return true;
}
@ -4922,60 +4930,57 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
s.send = false;
}
}
}
if (event.GUIEvent.EventType == gui::EGET_SCROLL_BAR_CHANGED) {
// move scroll_containers
for (const std::pair<std::string, GUIScrollContainer *> &c : m_scroll_containers)
c.second->onScrollEvent(event.GUIEvent.Caller);
}
if (event.GUIEvent.EventType == gui::EGET_SCROLL_BAR_CHANGED) {
// move scroll_containers
for (const std::pair<std::string, GUIScrollContainer *> &c : m_scroll_containers)
c.second->onScrollEvent(event.GUIEvent.Caller);
}
break;
if (event.GUIEvent.EventType == gui::EGET_EDITBOX_ENTER) {
if (event.GUIEvent.Caller->getID() > 257) {
bool close_on_enter = true;
for (GUIFormSpecMenu::FieldSpec &s : m_fields) {
if (s.ftype == f_Unknown &&
s.fid == event.GUIEvent.Caller->getID()) {
current_field_enter_pending = s.fname;
auto it = field_close_on_enter.find(s.fname);
if (it != field_close_on_enter.end())
close_on_enter = (*it).second;
case gui::EGET_EDITBOX_ENTER:
if (caller_id <= ID_PROCEED_BTN)
break;
break;
}
close_on_enter = true;
for (GUIFormSpecMenu::FieldSpec &s : m_fields) {
if (s.ftype == f_Unknown &&
s.fid == caller_id) {
current_field_enter_pending = s.fname;
auto it = field_close_on_enter.find(s.fname);
if (it != field_close_on_enter.end())
close_on_enter = (*it).second;
break;
}
}
current_keys_pending.key_enter = true;
current_keys_pending.key_enter = true;
if (close_on_enter) {
if (m_allowclose) {
acceptInput(quit_mode_accept);
quitMenu();
} else {
acceptInput(quit_mode_try);
}
} else {
if (close_on_enter)
trySubmitClose();
else
acceptInput();
return true;
case gui::EGET_TABLE_CHANGED:
if (caller_id <= ID_PROCEED_BTN)
break;
// find the element that was clicked
for (GUIFormSpecMenu::FieldSpec &s : m_fields) {
// if it's a table, set the send field
// so lua knows which table was changed
if (s.ftype == f_Table && s.fid == caller_id) {
s.send = true;
acceptInput();
s.send = false;
}
return true;
}
}
return true;
if (event.GUIEvent.EventType == gui::EGET_TABLE_CHANGED) {
int current_id = event.GUIEvent.Caller->getID();
if (current_id > 257) {
// find the element that was clicked
for (GUIFormSpecMenu::FieldSpec &s : m_fields) {
// if it's a table, set the send field
// so lua knows which table was changed
if ((s.ftype == f_Table) && (s.fid == current_id)) {
s.send = true;
acceptInput();
s.send=false;
}
}
return true;
}
default:
break;
}
}

View file

@ -68,8 +68,6 @@ struct TextDest
{
virtual ~TextDest() = default;
// This is deprecated I guess? -celeron55
virtual void gotText(const std::wstring &text) {}
virtual void gotText(const StringMap &fields) = 0;
std::string m_formname;
@ -493,6 +491,7 @@ private:
bool parseMiddleRect(const std::string &value, core::rect<s32> *parsed_rect);
void tryClose();
void trySubmitClose();
void showTooltip(const std::wstring &text, const irr::video::SColor &color,
const irr::video::SColor &bgcolor);

View file

@ -772,52 +772,79 @@ void MMVManip::initialEmerge(v3s16 p_min, v3s16 p_max, bool load_if_inexistent)
infostream<<std::endl;
}
const bool all_new = m_area.hasEmptyExtent() || block_area_nodes.contains(m_area);
std::map<v3s16, bool> had_blocks;
// we can skip this calculation if the areas are disjoint
if (!m_area.intersect(block_area_nodes).hasEmptyExtent())
had_blocks = getCoveredBlocks();
const bool all_new = m_area.hasEmptyExtent();
addArea(block_area_nodes);
for(s32 z=p_min.Z; z<=p_max.Z; z++)
for(s32 y=p_min.Y; y<=p_max.Y; y++)
for(s32 x=p_min.X; x<=p_max.X; x++)
{
u8 flags = 0;
MapBlock *block;
v3s16 p(x,y,z);
if (m_loaded_blocks.count(p) > 0)
// if this block was already in the vmanip and it has data, skip
if (auto it = had_blocks.find(p); it != had_blocks.end() && it->second)
continue;
bool block_data_inexistent = false;
{
TimeTaker timer2("emerge load", &emerge_load_time);
block = m_map->getBlockNoCreateNoEx(p);
if (!block)
block_data_inexistent = true;
else
block->copyTo(*this);
}
if(block_data_inexistent)
{
MapBlock *block = m_map->getBlockNoCreateNoEx(p);
if (block) {
block->copyTo(*this);
} else {
if (load_if_inexistent && !blockpos_over_max_limit(p)) {
block = m_map->emergeBlock(p, true);
assert(block);
block->copyTo(*this);
} else {
flags |= VMANIP_BLOCK_DATA_INEXIST;
// Mark area inexistent
VoxelArea a(p*MAP_BLOCKSIZE, (p+1)*MAP_BLOCKSIZE-v3s16(1,1,1));
setFlags(a, VOXELFLAG_NO_DATA);
}
}
m_loaded_blocks[p] = flags;
}
if (all_new)
m_is_dirty = false;
}
std::map<v3s16, bool> MMVManip::getCoveredBlocks() const
{
std::map<v3s16, bool> ret;
if (m_area.hasEmptyExtent())
return ret;
// Figure out if *any* node in this block has data according to m_flags
const auto &check_block = [this] (v3s16 bp) -> bool {
v3s16 pmin = bp * MAP_BLOCKSIZE;
v3s16 pmax = pmin + v3s16(MAP_BLOCKSIZE-1);
for(s16 z=pmin.Z; z<=pmax.Z; z++)
for(s16 y=pmin.Y; y<=pmax.Y; y++)
for(s16 x=pmin.X; x<=pmax.X; x++) {
if (!(m_flags[m_area.index(x,y,z)] & VOXELFLAG_NO_DATA))
return true;
}
return false;
};
v3s16 bpmin = getNodeBlockPos(m_area.MinEdge);
v3s16 bpmax = getNodeBlockPos(m_area.MaxEdge);
if (bpmin * MAP_BLOCKSIZE != m_area.MinEdge)
throw BaseException("MMVManip not block-aligned");
if ((bpmax+1) * MAP_BLOCKSIZE - v3s16(1) != m_area.MaxEdge)
throw BaseException("MMVManip not block-aligned");
for(s16 z=bpmin.Z; z<=bpmax.Z; z++)
for(s16 y=bpmin.Y; y<=bpmax.Y; y++)
for(s16 x=bpmin.X; x<=bpmax.X; x++) {
v3s16 bp(x,y,z);
ret[bp] = check_block(bp);
}
return ret;
}
void MMVManip::blitBackAll(std::map<v3s16, MapBlock*> *modified_blocks,
bool overwrite_generated) const
{
@ -825,16 +852,27 @@ void MMVManip::blitBackAll(std::map<v3s16, MapBlock*> *modified_blocks,
return;
assert(m_map);
/*
Copy data of all blocks
*/
assert(!m_loaded_blocks.empty());
for (auto &loaded_block : m_loaded_blocks) {
v3s16 p = loaded_block.first;
size_t nload = 0;
// Copy all the blocks with data back to the map
const auto loaded_blocks = getCoveredBlocks();
for (auto &it : loaded_blocks) {
if (!it.second)
continue;
v3s16 p = it.first;
MapBlock *block = m_map->getBlockNoCreateNoEx(p);
bool existed = !(loaded_block.second & VMANIP_BLOCK_DATA_INEXIST);
if (!existed || (block == NULL) ||
(!overwrite_generated && block->isGenerated()))
if (!block) {
if (!blockpos_over_max_limit(p)) {
block = m_map->emergeBlock(p, true);
nload++;
}
}
if (!block) {
warningstream << "blitBackAll: Couldn't load block " << p
<< " to write data to map" << std::endl;
continue;
}
if (!overwrite_generated && block->isGenerated())
continue;
block->copyFrom(*this);
@ -844,6 +882,10 @@ void MMVManip::blitBackAll(std::map<v3s16, MapBlock*> *modified_blocks,
if(modified_blocks)
(*modified_blocks)[p] = block;
}
if (nload > 0) {
verbosestream << "blitBackAll: " << nload << " blocks had to be loaded for writing" << std::endl;
}
}
MMVManip *MMVManip::clone() const
@ -860,11 +902,7 @@ MMVManip *MMVManip::clone() const
ret->m_flags = new u8[size];
memcpy(ret->m_flags, m_flags, size * sizeof(u8));
}
ret->m_is_dirty = m_is_dirty;
// Even if the copy is disconnected from a map object keep the information
// needed to write it back to one
ret->m_loaded_blocks = m_loaded_blocks;
return ret;
}

View file

@ -307,16 +307,29 @@ public:
MMVManip(Map *map);
virtual ~MMVManip() = default;
virtual void clear()
{
VoxelManipulator::clear();
m_loaded_blocks.clear();
}
/*
Loads specified area from map and *adds* it to the area already
contained in the VManip.
*/
void initialEmerge(v3s16 blockpos_min, v3s16 blockpos_max,
bool load_if_inexistent = true);
// This is much faster with big chunks of generated data
/**
Uses the flags array to determine which blocks the VManip covers,
and for which of them we have any data.
@warning requires VManip area to be block-aligned
@return map of blockpos -> any data?
*/
std::map<v3s16, bool> getCoveredBlocks() const;
/**
Writes data in VManip back to the map. Blocks without any data in the VManip
are skipped.
@note VOXELFLAG_NO_DATA is checked per-block, not per-node. So you need
to ensure that the relevant parts of m_data are initialized.
@param modified_blocks output array of touched blocks (optional)
@param overwrite_generated if false, blocks marked as generate in the map are not changed
*/
void blitBackAll(std::map<v3s16, MapBlock*> * modified_blocks,
bool overwrite_generated = true) const;
@ -339,13 +352,4 @@ protected:
// may be null
Map *m_map = nullptr;
/*
key = blockpos
value = flags describing the block
*/
std::map<v3s16, u8> m_loaded_blocks;
enum : u8 {
VMANIP_BLOCK_DATA_INEXIST = 1 << 0,
};
};

View file

@ -40,10 +40,6 @@ void Server::handleCommand_Deprecated(NetworkPacket* pkt)
void Server::handleCommand_Init(NetworkPacket* pkt)
{
if(pkt->getSize() < 1)
return;
session_t peer_id = pkt->getPeerId();
RemoteClient *client = getClient(peer_id, CS_Created);
@ -75,15 +71,6 @@ void Server::handleCommand_Init(NetworkPacket* pkt)
verbosestream << "Server: Got TOSERVER_INIT from " << addr_s <<
" (peer_id=" << peer_id << ")" << std::endl;
// Do not allow multiple players in simple singleplayer mode.
// This isn't a perfect way to do it, but will suffice for now
if (m_simple_singleplayer_mode && !m_clients.getClientIDs().empty()) {
infostream << "Server: Not allowing another client (" << addr_s <<
") to connect in simple singleplayer mode" << std::endl;
DenyAccess(peer_id, SERVER_ACCESSDENIED_SINGLEPLAYER);
return;
}
if (denyIfBanned(peer_id))
return;
@ -161,18 +148,14 @@ void Server::handleCommand_Init(NetworkPacket* pkt)
return;
}
RemotePlayer *player = m_env->getPlayer(playername, true);
// If player is already connected, cancel
if (player && player->getPeerId() != PEER_ID_INEXISTENT) {
actionstream << "Server: Player with name \"" << playername <<
"\" tried to connect, but player with same name is already connected" << std::endl;
DenyAccess(peer_id, SERVER_ACCESSDENIED_ALREADY_CONNECTED);
// Do not allow multiple players in simple singleplayer mode
if (isSingleplayer() && !m_clients.getClientIDs(CS_HelloSent).empty()) {
infostream << "Server: Not allowing another client (" << addr_s <<
") to connect in simple singleplayer mode" << std::endl;
DenyAccess(peer_id, SERVER_ACCESSDENIED_SINGLEPLAYER);
return;
}
m_clients.setPlayerName(peer_id, playername);
// Or the "singleplayer" name to be used on regular servers
if (!isSingleplayer() && strcasecmp(playername, "singleplayer") == 0) {
actionstream << "Server: Player with the name \"singleplayer\" tried "
"to connect from " << addr_s << std::endl;
@ -180,12 +163,25 @@ void Server::handleCommand_Init(NetworkPacket* pkt)
return;
}
{
RemotePlayer *player = m_env->getPlayer(playername, true);
// If player is already connected, cancel
if (player && player->getPeerId() != PEER_ID_INEXISTENT) {
actionstream << "Server: Player with name \"" << playername <<
"\" tried to connect, but player with same name is already connected" << std::endl;
DenyAccess(peer_id, SERVER_ACCESSDENIED_ALREADY_CONNECTED);
return;
}
}
client->setName(playerName);
{
std::string reason;
if (m_script->on_prejoinplayer(playername, addr_s, &reason)) {
actionstream << "Server: Player with the name \"" << playerName <<
"\" tried to connect from " << addr_s <<
" but it was disallowed for the following reason: " << reason <<
" but was disallowed for the following reason: " << reason <<
std::endl;
DenyAccess(peer_id, SERVER_ACCESSDENIED_CUSTOM_STRING, reason);
return;
@ -195,14 +191,11 @@ void Server::handleCommand_Init(NetworkPacket* pkt)
infostream << "Server: New connection: \"" << playerName << "\" from " <<
addr_s << " (peer_id=" << peer_id << ")" << std::endl;
// Enforce user limit.
// Don't enforce for users that have some admin right or mod permits it.
if (m_clients.isUserLimitReached() &&
playername != g_settings->get("name") &&
!m_script->can_bypass_userlimit(playername, addr_s)) {
// Early check for user limit, so the client doesn't need to run
// through the join process only to be denied.
if (checkUserLimit(playerName, addr_s)) {
actionstream << "Server: " << playername << " tried to join from " <<
addr_s << ", but there are already max_users=" <<
g_settings->getU16("max_users") << " players." << std::endl;
addr_s << ", but the user limit was reached." << std::endl;
DenyAccess(peer_id, SERVER_ACCESSDENIED_TOO_MANY_USERS);
return;
}
@ -302,6 +295,7 @@ void Server::handleCommand_Init2(NetworkPacket* pkt)
sendMediaAnnouncement(peer_id, lang);
RemoteClient *client = getClient(peer_id, CS_InitDone);
assert(client);
// Keep client language for server translations
client->setLangCode(lang);
@ -354,6 +348,8 @@ void Server::handleCommand_RequestMedia(NetworkPacket* pkt)
void Server::handleCommand_ClientReady(NetworkPacket* pkt)
{
session_t peer_id = pkt->getPeerId();
RemoteClient *client = getClient(peer_id, CS_Created);
assert(client);
// decode all information first
u8 major_ver, minor_ver, patch_ver, reserved;
@ -364,8 +360,17 @@ void Server::handleCommand_ClientReady(NetworkPacket* pkt)
if (pkt->getRemainingBytes() >= 2)
*pkt >> formspec_ver;
m_clients.setClientVersion(peer_id, major_ver, minor_ver, patch_ver,
full_ver);
client->setVersionInfo(major_ver, minor_ver, patch_ver, full_ver);
// Since only active clients count for the user limit, two could race the
// join process so we have to do a final check for the user limit here.
std::string addr_s = client->getAddress().serializeString();
if (checkUserLimit(client->getName(), addr_s)) {
actionstream << "Server: " << client->getName() << " tried to join from " <<
addr_s << ", but the user limit was reached (late)." << std::endl;
DenyAccess(peer_id, SERVER_ACCESSDENIED_TOO_MANY_USERS);
return;
}
// Emerge player
PlayerSAO* playersao = StageTwoClientInit(peer_id);
@ -1426,7 +1431,7 @@ void Server::handleCommand_FirstSrp(NetworkPacket* pkt)
std::string salt, verification_key;
std::string addr_s = getPeerAddress(peer_id).serializeString();
std::string addr_s = client->getAddress().serializeString();
u8 is_empty;
*pkt >> salt >> verification_key >> is_empty;
@ -1512,9 +1517,11 @@ void Server::handleCommand_SrpBytesA(NetworkPacket* pkt)
RemoteClient *client = getClient(peer_id, CS_Invalid);
ClientState cstate = client->getState();
std::string addr_s = client->getAddress().serializeString();
if (!((cstate == CS_HelloSent) || (cstate == CS_Active))) {
actionstream << "Server: got SRP _A packet in wrong state " << cstate <<
" from " << getPeerAddress(peer_id).serializeString() <<
" from " << addr_s <<
". Ignoring." << std::endl;
return;
}
@ -1524,7 +1531,7 @@ void Server::handleCommand_SrpBytesA(NetworkPacket* pkt)
if (client->chosen_mech != AUTH_MECHANISM_NONE) {
actionstream << "Server: got SRP _A packet, while auth is already "
"going on with mech " << client->chosen_mech << " from " <<
getPeerAddress(peer_id).serializeString() <<
addr_s <<
" (wantSudo=" << wantSudo << "). Ignoring." << std::endl;
if (wantSudo) {
DenySudoAccess(peer_id);
@ -1541,7 +1548,7 @@ void Server::handleCommand_SrpBytesA(NetworkPacket* pkt)
infostream << "Server: TOSERVER_SRP_BYTES_A received with "
<< "based_on=" << int(based_on) << " and len_A="
<< bytes_A.length() << "." << std::endl;
<< bytes_A.length() << std::endl;
AuthMechanism chosen = (based_on == 0) ?
AUTH_MECHANISM_LEGACY_PASSWORD : AUTH_MECHANISM_SRP;
@ -1550,17 +1557,17 @@ void Server::handleCommand_SrpBytesA(NetworkPacket* pkt)
// Right now, the auth mechs don't change between login and sudo mode.
if (!client->isMechAllowed(chosen)) {
actionstream << "Server: Player \"" << client->getName() <<
"\" at " << getPeerAddress(peer_id).serializeString() <<
"\" from " << addr_s <<
" tried to change password using unallowed mech " << chosen <<
"." << std::endl;
std::endl;
DenySudoAccess(peer_id);
return;
}
} else {
if (!client->isMechAllowed(chosen)) {
actionstream << "Server: Client tried to authenticate from " <<
getPeerAddress(peer_id).serializeString() <<
" using unallowed mech " << chosen << "." << std::endl;
addr_s <<
" using unallowed mech " << chosen << std::endl;
DenyAccess(peer_id, SERVER_ACCESSDENIED_UNEXPECTED_DATA);
return;
}

View file

@ -261,7 +261,7 @@ int ModApiServer::l_get_player_information(lua_State *L)
lua_settable(L, table);
lua_pushstring(L,"state");
lua_pushstring(L, ClientInterface::state2Name(info.state).c_str());
lua_pushstring(L, ClientInterface::state2Name(info.state));
lua_settable(L, table);
#endif

View file

@ -44,7 +44,40 @@ int LuaVoxelManip::l_read_from_map(lua_State *L)
push_v3s16(L, vm->m_area.MinEdge);
push_v3s16(L, vm->m_area.MaxEdge);
return 2;
}
int LuaVoxelManip::l_initialize(lua_State *L)
{
MAP_LOCK_REQUIRED;
LuaVoxelManip *o = checkObject<LuaVoxelManip>(L, 1);
MMVManip *vm = o->vm;
if (o->is_mapgen_vm)
throw LuaError("Cannot modify mapgen VoxelManip object");
VoxelArea area;
{
v3s16 bp1 = getNodeBlockPos(check_v3s16(L, 2));
v3s16 bp2 = getNodeBlockPos(check_v3s16(L, 3));
sortBoxVerticies(bp1, bp2);
area = VoxelArea(bp1 * MAP_BLOCKSIZE, (bp2+1) * MAP_BLOCKSIZE - v3s16(1));
}
assert(!area.hasEmptyExtent());
vm->clear();
vm->addArea(area);
if (lua_istable(L, 4)) {
MapNode n = readnode(L, 4);
const u32 volume = vm->m_area.getVolume();
for (u32 i = 0; i != volume; i++)
vm->m_data[i] = n;
vm->clearFlags(vm->m_area, VOXELFLAG_NO_DATA);
}
push_v3s16(L, vm->m_area.MinEdge);
push_v3s16(L, vm->m_area.MaxEdge);
return 2;
}
@ -93,11 +126,12 @@ int LuaVoxelManip::l_set_data(lua_State *L)
lua_pop(L, 1);
}
// FIXME: in theory we should clear VOXELFLAG_NO_DATA here
// However there is no way to tell which values Lua code has intended to set
// (if they were VOXELFLAG_NO_DATA before), and which were just not touched.
// In practice this doesn't cause problems because read_from_map() will cause
// all covered blocks to be loaded anyway.
// Mark all data as present, since we just got it from Lua
// Note that we can't tell if the caller intended to put CONTENT_IGNORE or
// is just repeating the dummy values we push in l_get_data() in case
// VOXELFLAG_NO_DATA is set. In practice this doesn't matter since ignore
// isn't written back to the map anyway.
vm->clearFlags(vm->m_area, VOXELFLAG_NO_DATA);
return 0;
}
@ -344,6 +378,19 @@ int LuaVoxelManip::l_get_emerged_area(lua_State *L)
return 2;
}
int LuaVoxelManip::l_close(lua_State *L)
{
NO_MAP_LOCK_REQUIRED;
LuaVoxelManip *o = checkObject<LuaVoxelManip>(L, 1);
if (o->is_mapgen_vm)
throw LuaError("Cannot dispose of mapgen VoxelManip object");
o->vm->clear();
return 0;
}
LuaVoxelManip::LuaVoxelManip(MMVManip *mmvm, bool is_mg_vm) :
is_mapgen_vm(is_mg_vm),
vm(mmvm)
@ -436,6 +483,7 @@ void LuaVoxelManip::Register(lua_State *L)
const char LuaVoxelManip::className[] = "VoxelManip";
const luaL_Reg LuaVoxelManip::methods[] = {
luamethod(LuaVoxelManip, read_from_map),
luamethod(LuaVoxelManip, initialize),
luamethod(LuaVoxelManip, get_data),
luamethod(LuaVoxelManip, set_data),
luamethod(LuaVoxelManip, get_node_at),
@ -451,5 +499,6 @@ const luaL_Reg LuaVoxelManip::methods[] = {
luamethod(LuaVoxelManip, set_param2_data),
luamethod(LuaVoxelManip, was_modified),
luamethod(LuaVoxelManip, get_emerged_area),
luamethod(LuaVoxelManip, close),
{0,0}
};

View file

@ -24,6 +24,7 @@ private:
static int gc_object(lua_State *L);
static int l_read_from_map(lua_State *L);
static int l_initialize(lua_State *L);
static int l_get_data(lua_State *L);
static int l_set_data(lua_State *L);
static int l_write_to_map(lua_State *L);
@ -45,6 +46,8 @@ private:
static int l_was_modified(lua_State *L);
static int l_get_emerged_area(lua_State *L);
static int l_close(lua_State *L);
public:
MMVManip *vm = nullptr;

View file

@ -1572,7 +1572,8 @@ void Server::SendChatMessage(session_t peer_id, const ChatMessage &message)
if (peer_id != PEER_ID_INEXISTENT) {
Send(&pkt);
} else {
m_clients.sendToAll(&pkt);
// If a client has completed auth but is still joining, still send chat
m_clients.sendToAll(&pkt, CS_InitDone);
}
}
@ -3183,9 +3184,7 @@ std::wstring Server::handleChat(const std::string &name,
ChatMessage chatmsg(line);
std::vector<session_t> clients = m_clients.getClientIDs();
for (u16 cid : clients)
SendChatMessage(cid, chatmsg);
SendChatMessage(PEER_ID_INEXISTENT, chatmsg);
return L"";
}
@ -3357,6 +3356,15 @@ bool Server::denyIfBanned(session_t peer_id)
return false;
}
bool Server::checkUserLimit(const std::string &player_name, const std::string &addr_s)
{
if (!m_clients.isUserLimitReached())
return false;
if (player_name == g_settings->get("name")) // admin can always join
return false;
return !m_script->can_bypass_userlimit(player_name, addr_s);
}
void Server::notifyPlayer(const char *name, const std::wstring &msg)
{
// m_env will be NULL if the server is initializing
@ -3490,7 +3498,6 @@ void Server::hudSetHotbarSelectedImage(RemotePlayer *player, const std::string &
Address Server::getPeerAddress(session_t peer_id)
{
// Note that this is only set after Init was received in Server::handleCommand_Init
return getClient(peer_id, CS_Invalid)->getAddress();
}

View file

@ -368,6 +368,7 @@ public:
void hudSetHotbarImage(RemotePlayer *player, const std::string &name);
void hudSetHotbarSelectedImage(RemotePlayer *player, const std::string &name);
/// @note this is only available for client state >= CS_HelloSent
Address getPeerAddress(session_t peer_id);
void setLocalPlayerAnimations(RemotePlayer *player, v2f animation_frames[4],
@ -611,6 +612,10 @@ private:
void handleChatInterfaceEvent(ChatEvent *evt);
/// @brief Checks if user limit allows a potential client to join
/// @return true if the client can NOT join
bool checkUserLimit(const std::string &player_name, const std::string &addr_s);
// This returns the answer to the sender of wmessage, or "" if there is none
std::wstring handleChat(const std::string &name, std::wstring wmessage_input,
bool check_shout_priv = false, RemotePlayer *player = nullptr);

View file

@ -47,7 +47,7 @@ const char *ClientInterface::statenames[] = {
"SudoMode",
};
std::string ClientInterface::state2Name(ClientState state)
const char *ClientInterface::state2Name(ClientState state)
{
return statenames[state];
}
@ -659,7 +659,7 @@ std::vector<session_t> ClientInterface::getClientIDs(ClientState min_state)
{
std::vector<session_t> reply;
RecursiveMutexAutoLock clientslock(m_clients_mutex);
reply.reserve(m_clients.size());
for (const auto &m_client : m_clients) {
if (m_client.second->getState() >= min_state)
reply.push_back(m_client.second->peer_id);
@ -677,14 +677,10 @@ void ClientInterface::markBlocksNotSent(const std::vector<v3s16> &positions, boo
}
}
/**
* Verify if user limit was reached.
* User limit count all clients from HelloSent state (MT protocol user) to Active state
* @return true if user limit was reached
*/
bool ClientInterface::isUserLimitReached()
{
return getClientIDs(CS_HelloSent).size() >= g_settings->getU16("max_users");
// Note that this only counts clients that have fully joined
return getClientIDs().size() >= g_settings->getU16("max_users");
}
void ClientInterface::step(float dtime)
@ -703,16 +699,13 @@ void ClientInterface::step(float dtime)
RecursiveMutexAutoLock clientslock(m_clients_mutex);
for (const auto &it : m_clients) {
auto state = it.second->getState();
if (state >= CS_HelloSent)
if (state >= CS_InitDone)
continue;
if (it.second->uptime() <= LINGER_TIMEOUT)
continue;
// CS_Created means nobody has even noticed the client is there
// (this is before on_prejoinplayer runs)
// CS_Invalid should not happen
// -> log those as warning, the rest as info
std::ostream &os = state == CS_Created || state == CS_Invalid ?
warningstream : infostream;
// Complain louder if this situation is unexpected
auto &os = state == CS_Disconnecting || state == CS_Denied ?
infostream : warningstream;
try {
Address addr = m_con->GetPeerAddress(it.second->peer_id);
os << "Disconnecting lingering client from "
@ -770,33 +763,22 @@ void ClientInterface::sendCustom(session_t peer_id, u8 channel, NetworkPacket *p
m_con->Send(peer_id, channel, pkt, reliable);
}
void ClientInterface::sendToAll(NetworkPacket *pkt)
void ClientInterface::sendToAll(NetworkPacket *pkt, ClientState state_min)
{
auto &ccf = clientCommandFactoryTable[pkt->getCommand()];
FATAL_ERROR_IF(!ccf.name, "packet type missing in table");
RecursiveMutexAutoLock clientslock(m_clients_mutex);
for (auto &client_it : m_clients) {
RemoteClient *client = client_it.second;
if (client->net_proto_version != 0) {
auto &ccf = clientCommandFactoryTable[pkt->getCommand()];
FATAL_ERROR_IF(!ccf.name, "packet type missing in table");
m_con->Send(client->peer_id, ccf.channel, pkt, ccf.reliable);
}
for (auto &[peer_id, client] : m_clients) {
if (client->getState() >= state_min)
m_con->Send(peer_id, ccf.channel, pkt, ccf.reliable);
}
}
RemoteClient* ClientInterface::getClientNoEx(session_t peer_id, ClientState state_min)
{
RecursiveMutexAutoLock clientslock(m_clients_mutex);
RemoteClientMap::const_iterator n = m_clients.find(peer_id);
// The client may not exist; clients are immediately removed if their
// access is denied, and this event occurs later then.
if (n == m_clients.end())
return NULL;
if (n->second->getState() >= state_min)
return n->second;
return NULL;
RemoteClient *client = lockedGetClientNoEx(peer_id, state_min);
return client;
}
RemoteClient* ClientInterface::lockedGetClientNoEx(session_t peer_id, ClientState state_min)
@ -805,12 +787,13 @@ RemoteClient* ClientInterface::lockedGetClientNoEx(session_t peer_id, ClientStat
// The client may not exist; clients are immediately removed if their
// access is denied, and this event occurs later then.
if (n == m_clients.end())
return NULL;
return nullptr;
assert(n->second->peer_id == peer_id);
if (n->second->getState() >= state_min)
return n->second;
return NULL;
return nullptr;
}
ClientState ClientInterface::getClientState(session_t peer_id)
@ -825,16 +808,6 @@ ClientState ClientInterface::getClientState(session_t peer_id)
return n->second->getState();
}
void ClientInterface::setPlayerName(session_t peer_id, const std::string &name)
{
RecursiveMutexAutoLock clientslock(m_clients_mutex);
RemoteClientMap::iterator n = m_clients.find(peer_id);
// The client may not exist; clients are immediately removed if their
// access is denied, and this event occurs later then.
if (n != m_clients.end())
n->second->setName(name);
}
void ClientInterface::DeleteClient(session_t peer_id)
{
RecursiveMutexAutoLock conlock(m_clients_mutex);
@ -915,18 +888,3 @@ u16 ClientInterface::getProtocolVersion(session_t peer_id)
return n->second->net_proto_version;
}
void ClientInterface::setClientVersion(session_t peer_id, u8 major, u8 minor, u8 patch,
const std::string &full)
{
RecursiveMutexAutoLock conlock(m_clients_mutex);
// Error check
RemoteClientMap::iterator n = m_clients.find(peer_id);
// No client to set versions
if (n == m_clients.end())
return;
n->second->setVersionInfo(major, minor, patch, full);
}

View file

@ -445,7 +445,7 @@ public:
/* mark blocks as not sent on all active clients */
void markBlocksNotSent(const std::vector<v3s16> &positions, bool low_priority = false);
/* verify is server user limit was reached */
/* verify if server user limit was reached */
bool isUserLimitReached();
/* get list of client player names */
@ -458,7 +458,7 @@ public:
void sendCustom(session_t peer_id, u8 channel, NetworkPacket *pkt, bool reliable);
/* send to all clients */
void sendToAll(NetworkPacket *pkt);
void sendToAll(NetworkPacket *pkt, ClientState state_min = CS_Active);
/* delete a client */
void DeleteClient(session_t peer_id);
@ -475,16 +475,9 @@ public:
/* get state of client by id*/
ClientState getClientState(session_t peer_id);
/* set client playername */
void setPlayerName(session_t peer_id, const std::string &name);
/* get protocol version of client */
u16 getProtocolVersion(session_t peer_id);
/* set client version */
void setClientVersion(session_t peer_id, u8 major, u8 minor, u8 patch,
const std::string &full);
/* event to update client state */
void event(session_t peer_id, ClientStateEvent event);
@ -495,7 +488,8 @@ public:
m_env = env;
}
static std::string state2Name(ClientState state);
static const char *state2Name(ClientState state);
protected:
class AutoLock {
public:
@ -514,9 +508,9 @@ private:
// Connection
std::shared_ptr<con::IConnection> m_con;
std::recursive_mutex m_clients_mutex;
// Connected clients (behind the con mutex)
// Connected clients (behind the mutex)
RemoteClientMap m_clients;
std::vector<std::string> m_clients_names; //for announcing masterserver
std::vector<std::string> m_clients_names; // for announcing to server list
// Environment
ServerEnvironment *m_env;
@ -526,5 +520,7 @@ private:
static const char *statenames[];
static constexpr int LINGER_TIMEOUT = 10;
// Note that this puts a fixed timeout on the init & auth phase for a client.
// (lingering is enforced until CS_InitDone)
static constexpr int LINGER_TIMEOUT = 12;
};

View file

@ -264,6 +264,10 @@ void TestVoxelArea::test_intersect()
UASSERT(v3.intersect(v1) == v1.intersect(v3));
UASSERT(v1.intersect(v4) ==
VoxelArea({-10, -2, -10}, {10, 2, 10}));
// edge cases
UASSERT(VoxelArea().intersect(v1).hasEmptyExtent());
UASSERT(v1.intersect(VoxelArea()).hasEmptyExtent());
}
void TestVoxelArea::test_index_xyz_all_pos()

View file

@ -4,11 +4,13 @@
#include "test.h"
#include <algorithm>
#include <memory>
#include "gamedef.h"
#include "log.h"
#include "voxel.h"
#include "dummymap.h"
#include "irrlicht_changes/printing.h"
class TestVoxelManipulator : public TestBase {
public:
@ -17,59 +19,32 @@ public:
void runTests(IGameDef *gamedef);
void testVoxelArea();
void testVoxelManipulator(const NodeDefManager *nodedef);
void testBasic(const NodeDefManager *nodedef);
void testEmerge(IGameDef *gamedef);
void testBlitBack(IGameDef *gamedef);
void testBlitBack2(IGameDef *gamedef);
};
static TestVoxelManipulator g_test_instance;
void TestVoxelManipulator::runTests(IGameDef *gamedef)
{
TEST(testVoxelArea);
TEST(testVoxelManipulator, gamedef->getNodeDefManager());
TEST(testBasic, gamedef->ndef());
TEST(testEmerge, gamedef);
TEST(testBlitBack, gamedef);
TEST(testBlitBack2, gamedef);
}
////////////////////////////////////////////////////////////////////////////////
void TestVoxelManipulator::testVoxelArea()
{
VoxelArea a(v3s16(-1,-1,-1), v3s16(1,1,1));
UASSERT(a.index(0,0,0) == 1*3*3 + 1*3 + 1);
UASSERT(a.index(-1,-1,-1) == 0);
VoxelArea c(v3s16(-2,-2,-2), v3s16(2,2,2));
// An area that is 1 bigger in x+ and z-
VoxelArea d(v3s16(-2,-2,-3), v3s16(3,2,2));
std::list<VoxelArea> aa;
d.diff(c, aa);
// Correct results
std::vector<VoxelArea> results;
results.emplace_back(v3s16(-2,-2,-3), v3s16(3,2,-3));
results.emplace_back(v3s16(3,-2,-2), v3s16(3,2,2));
UASSERT(aa.size() == results.size());
infostream<<"Result of diff:"<<std::endl;
for (auto it = aa.begin(); it != aa.end(); ++it) {
it->print(infostream);
infostream << std::endl;
auto j = std::find(results.begin(), results.end(), *it);
UASSERT(j != results.end());
results.erase(j);
}
}
void TestVoxelManipulator::testVoxelManipulator(const NodeDefManager *nodedef)
void TestVoxelManipulator::testBasic(const NodeDefManager *nodedef)
{
VoxelManipulator v;
v.print(infostream, nodedef);
UASSERT(v.m_area.hasEmptyExtent());
infostream << "*** Setting (-1,0,-1)=2 ***" << std::endl;
infostream << "*** Setting (-1,0,-1) ***" << std::endl;
v.setNode(v3s16(-1,0,-1), MapNode(t_CONTENT_GRASS));
v.print(infostream, nodedef);
@ -89,3 +64,118 @@ void TestVoxelManipulator::testVoxelManipulator(const NodeDefManager *nodedef)
UASSERT(v.getNode(v3s16(-1,0,-1)).getContent() == t_CONTENT_GRASS);
EXCEPTION_CHECK(InvalidPositionException, v.getNode(v3s16(0,1,1)));
}
void TestVoxelManipulator::testEmerge(IGameDef *gamedef)
{
constexpr int bs = MAP_BLOCKSIZE;
DummyMap map(gamedef, {0,0,0}, {1,1,1});
map.fill({0,0,0}, {1,1,1}, CONTENT_AIR);
MMVManip vm(&map);
UASSERT(!vm.isOrphan());
// emerge something
vm.initialEmerge({0,0,0}, {0,0,0});
UASSERTEQ(auto, vm.m_area.MinEdge, v3s16(0));
UASSERTEQ(auto, vm.m_area.MaxEdge, v3s16(bs-1));
UASSERTEQ(auto, vm.getNodeNoExNoEmerge({0,0,0}).getContent(), CONTENT_AIR);
map.setNode({0, 1,0}, t_CONTENT_BRICK);
map.setNode({0,bs+1,0}, t_CONTENT_BRICK);
// emerge top block: this should not re-read the first one
vm.initialEmerge({0,0,0}, {0,1,0});
UASSERTEQ(auto, vm.m_area.getExtent(), v3s32(bs,2*bs,bs));
UASSERTEQ(auto, vm.getNodeNoExNoEmerge({0, 1,0}).getContent(), CONTENT_AIR);
UASSERTEQ(auto, vm.getNodeNoExNoEmerge({0,bs+1,0}).getContent(), t_CONTENT_BRICK);
// emerge out of bounds: should produce empty data
vm.initialEmerge({0,2,0}, {0,2,0}, false);
UASSERTEQ(auto, vm.m_area.getExtent(), v3s32(bs,3*bs,bs));
UASSERTEQ(auto, vm.getNodeNoExNoEmerge({0,2*bs,0}).getContent(), CONTENT_IGNORE);
UASSERT(!vm.exists({0,2*bs,0}));
// clear
vm.clear();
UASSERT(vm.m_area.hasEmptyExtent());
}
void TestVoxelManipulator::testBlitBack(IGameDef *gamedef)
{
DummyMap map(gamedef, {-1,-1,-1}, {1,1,1});
map.fill({0,0,0}, {0,0,0}, CONTENT_AIR);
std::unique_ptr<MMVManip> vm2;
{
MMVManip vm(&map);
vm.initialEmerge({0,0,0}, {0,0,0});
UASSERT(vm.exists({0,0,0}));
vm.setNodeNoEmerge({0,0,0}, t_CONTENT_STONE);
vm.setNodeNoEmerge({1,1,1}, t_CONTENT_GRASS);
vm.setNodeNoEmerge({2,2,2}, CONTENT_IGNORE);
// test out clone and reparent too
vm2.reset(vm.clone());
}
UASSERT(vm2);
UASSERT(vm2->isOrphan());
vm2->reparent(&map);
std::map<v3s16, MapBlock*> modified;
vm2->blitBackAll(&modified);
UASSERTEQ(size_t, modified.size(), 1);
UASSERTEQ(auto, modified.begin()->first, v3s16(0,0,0));
UASSERTEQ(auto, map.getNode({0,0,0}).getContent(), t_CONTENT_STONE);
UASSERTEQ(auto, map.getNode({1,1,1}).getContent(), t_CONTENT_GRASS);
// ignore nodes are not written (is this an intentional feature?)
UASSERTEQ(auto, map.getNode({2,2,2}).getContent(), CONTENT_AIR);
}
void TestVoxelManipulator::testBlitBack2(IGameDef *gamedef)
{
constexpr int bs = MAP_BLOCKSIZE;
DummyMap map(gamedef, {0,0,0}, {1,1,1});
map.fill({0,0,0}, {1,1,1}, CONTENT_AIR);
// Create a vmanip "manually" without using initialEmerge
MMVManip vm(&map);
vm.addArea(VoxelArea({0,0,0}, v3s16(1,2,1) * bs - v3s16(1)));
// Lower block is initialized with ignore, upper with lava
for(s16 z=0; z<bs; z++)
for(s16 y=0; y<2*bs; y++)
for(s16 x=0; x<bs; x++) {
auto c = y >= bs ? t_CONTENT_LAVA : CONTENT_IGNORE;
vm.setNodeNoEmerge({x,y,z}, c);
}
// But pretend the upper block was not actually initialized
vm.setFlags(VoxelArea({0,bs,0}, v3s16(1,2,1) * bs - v3s16(1)), VOXELFLAG_NO_DATA);
// Add a node to the lower one
vm.setNodeNoEmerge({0,1,0}, t_CONTENT_TORCH);
// Verify covered blocks
{
auto cov = vm.getCoveredBlocks();
UASSERTEQ(size_t, cov.size(), 2);
auto it = cov.find({0,0,0});
UASSERT(it != cov.end() && it->second);
it = cov.find({0,1,0});
UASSERT(it != cov.end() && !it->second);
}
// Now blit it back
std::map<v3s16, MapBlock*> modified;
vm.blitBackAll(&modified);
// The lower block data should have been written
UASSERTEQ(size_t, modified.size(), 1);
UASSERTEQ(auto, modified.begin()->first, v3s16(0,0,0));
UASSERTEQ(auto, map.getNode({0,1,0}).getContent(), t_CONTENT_TORCH);
// The upper one should not!
UASSERTEQ(auto, map.getNode({0,bs,0}).getContent(), CONTENT_AIR);
}

View file

@ -15,7 +15,6 @@
Debug stuff
*/
u64 emerge_time = 0;
u64 emerge_load_time = 0;
VoxelManipulator::~VoxelManipulator()
{

View file

@ -34,7 +34,6 @@ class NodeDefManager;
Debug stuff
*/
extern u64 emerge_time;
extern u64 emerge_load_time;
/*
This class resembles aabbox3d<s16> a lot, but has inclusive
@ -469,7 +468,7 @@ public:
Control
*/
virtual void clear();
void clear();
void print(std::ostream &o, const NodeDefManager *nodemgr,
VoxelPrintMode mode=VOXELPRINT_MATERIAL) const;