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 = [[ local translation_file_header = [[
// This file is automatically generated // This file is automatically generated
// It contains a bunch of fake gettext calls, to tell xgettext about the strings in config files // 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() {]] fake_function() {]]
@ -110,15 +110,15 @@ local function create_translation_file(settings)
local result = { translation_file_header } local result = { translation_file_header }
for _, entry in ipairs(settings) do for _, entry in ipairs(settings) do
if entry.type == "category" then 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 else
if entry.readable_name then 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 end
if entry.comment ~= "" then if entry.comment ~= "" then
local comment_escaped = entry.comment:gsub("\n", "\\n") local comment_escaped = entry.comment:gsub("\n", "\\n")
comment_escaped = comment_escaped:gsub("\"", "\\\"") comment_escaped = comment_escaped:gsub("\"", "\\\"")
insert(result, "\tgettext(\"" .. comment_escaped .. "\");") insert(result, "\t/* xgettext:no-c-format */ gettext(\"" .. comment_escaped .. "\");")
end end
end 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, 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. 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 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 Note that `VoxelManip:read_from_map()` returns two position vectors. The region
formed by these positions indicate the minimum and maximum (respectively) 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 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()`, 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 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 Nodes in a VoxelManip object may also be read in bulk to a flat array table
using: using:
* `VoxelManip:get_data()` for node content (in Content ID form, see section * `VoxelManip:get_data()` for node content (in Content ID form, see section
[Content IDs]), [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. * `VoxelManip:get_param2_data()` for the node type-dependent "param2" values.
See section [Flat array format] for more details. 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 Once the bulk data has been edited to your liking, the internal VoxelManip
state can be set using: state can be set using:
* `VoxelManip:set_data()` for node content (in Content ID form, see section * `VoxelManip:set_data()` or
[Content IDs]), * `VoxelManip:set_light_data()` or
* `VoxelManip:set_light_data()` for node light levels, and * `VoxelManip:set_param2_data()`
* `VoxelManip:set_param2_data()` for the node type-dependent `param2` values.
The parameter to each of the above three functions can use any table at all in 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 the same flat array format as produced by `get_data()` etc. and is not required
to be a table retrieved from `get_data()`. to be a table retrieved from `get_data()`.
Once the internal VoxelManip state has been modified to your liking, the 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 ### Flat array format
@ -5180,15 +5180,22 @@ inside the VoxelManip.
Methods 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`. 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 * Note that calling this multiple times will *add* to the area loaded in the
VoxelManip, and not reset it. 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 * `write_to_map([light])`: Writes the data loaded from the `VoxelManip` back to
the map. the map.
* **important**: data must be set using `VoxelManip:set_data()` before * **important**: you should call `set_data()` before this, or nothing will change.
calling this.
* if `light` is true, then lighting is automatically recalculated. * if `light` is true, then lighting is automatically recalculated.
The default value is true. The default value is true.
If `light` is false, no light calculations happen, and you should correct 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. 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! * 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. * `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` `VoxelArea`
----------- -----------

View file

@ -23,8 +23,8 @@ Callbacks
* `core.button_handler(fields)`: called when a button is pressed. * `core.button_handler(fields)`: called when a button is pressed.
* `fields` = `{name1 = value1, name2 = value2, ...}` * `fields` = `{name1 = value1, name2 = value2, ...}`
* `core.event_handler(event)` * `core.event_handler(event)`
* `event`: `"MenuQuit"`, `"KeyEnter"`, `"ExitButton"`, `"EditBoxEnter"` or * `event`: `"MenuQuit"` (derived from `quit`) or `"FullscreenChange"`
`"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 * `core.on_before_close()`: called before the menu is closed, either to exit or
to join a game to join a game

View file

@ -31,16 +31,6 @@ struct TextDestNodeMetadata : public TextDest
m_p = p; m_p = p;
m_client = client; 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) void gotText(const StringMap &fields)
{ {
m_client->sendNodemetaFields(m_p, "", fields); m_client->sendNodemetaFields(m_p, "", fields);

View file

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

View file

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

View file

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

View file

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

View file

@ -68,8 +68,6 @@ struct TextDest
{ {
virtual ~TextDest() = default; virtual ~TextDest() = default;
// This is deprecated I guess? -celeron55
virtual void gotText(const std::wstring &text) {}
virtual void gotText(const StringMap &fields) = 0; virtual void gotText(const StringMap &fields) = 0;
std::string m_formname; std::string m_formname;
@ -493,6 +491,7 @@ private:
bool parseMiddleRect(const std::string &value, core::rect<s32> *parsed_rect); bool parseMiddleRect(const std::string &value, core::rect<s32> *parsed_rect);
void tryClose(); void tryClose();
void trySubmitClose();
void showTooltip(const std::wstring &text, const irr::video::SColor &color, void showTooltip(const std::wstring &text, const irr::video::SColor &color,
const irr::video::SColor &bgcolor); 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; 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); addArea(block_area_nodes);
for(s32 z=p_min.Z; z<=p_max.Z; z++) for(s32 z=p_min.Z; z<=p_max.Z; z++)
for(s32 y=p_min.Y; y<=p_max.Y; y++) for(s32 y=p_min.Y; y<=p_max.Y; y++)
for(s32 x=p_min.X; x<=p_max.X; x++) for(s32 x=p_min.X; x<=p_max.X; x++)
{ {
u8 flags = 0;
MapBlock *block;
v3s16 p(x,y,z); 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; continue;
bool block_data_inexistent = false; MapBlock *block = m_map->getBlockNoCreateNoEx(p);
{ if (block) {
TimeTaker timer2("emerge load", &emerge_load_time); block->copyTo(*this);
} else {
block = m_map->getBlockNoCreateNoEx(p);
if (!block)
block_data_inexistent = true;
else
block->copyTo(*this);
}
if(block_data_inexistent)
{
if (load_if_inexistent && !blockpos_over_max_limit(p)) { if (load_if_inexistent && !blockpos_over_max_limit(p)) {
block = m_map->emergeBlock(p, true); block = m_map->emergeBlock(p, true);
assert(block);
block->copyTo(*this); block->copyTo(*this);
} else { } else {
flags |= VMANIP_BLOCK_DATA_INEXIST;
// Mark area inexistent // Mark area inexistent
VoxelArea a(p*MAP_BLOCKSIZE, (p+1)*MAP_BLOCKSIZE-v3s16(1,1,1)); VoxelArea a(p*MAP_BLOCKSIZE, (p+1)*MAP_BLOCKSIZE-v3s16(1,1,1));
setFlags(a, VOXELFLAG_NO_DATA); setFlags(a, VOXELFLAG_NO_DATA);
} }
} }
m_loaded_blocks[p] = flags;
} }
if (all_new) if (all_new)
m_is_dirty = false; 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, void MMVManip::blitBackAll(std::map<v3s16, MapBlock*> *modified_blocks,
bool overwrite_generated) const bool overwrite_generated) const
{ {
@ -825,16 +852,27 @@ void MMVManip::blitBackAll(std::map<v3s16, MapBlock*> *modified_blocks,
return; return;
assert(m_map); assert(m_map);
/* size_t nload = 0;
Copy data of all blocks
*/ // Copy all the blocks with data back to the map
assert(!m_loaded_blocks.empty()); const auto loaded_blocks = getCoveredBlocks();
for (auto &loaded_block : m_loaded_blocks) { for (auto &it : loaded_blocks) {
v3s16 p = loaded_block.first; if (!it.second)
continue;
v3s16 p = it.first;
MapBlock *block = m_map->getBlockNoCreateNoEx(p); MapBlock *block = m_map->getBlockNoCreateNoEx(p);
bool existed = !(loaded_block.second & VMANIP_BLOCK_DATA_INEXIST); if (!block) {
if (!existed || (block == NULL) || if (!blockpos_over_max_limit(p)) {
(!overwrite_generated && block->isGenerated())) 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; continue;
block->copyFrom(*this); block->copyFrom(*this);
@ -844,6 +882,10 @@ void MMVManip::blitBackAll(std::map<v3s16, MapBlock*> *modified_blocks,
if(modified_blocks) if(modified_blocks)
(*modified_blocks)[p] = block; (*modified_blocks)[p] = block;
} }
if (nload > 0) {
verbosestream << "blitBackAll: " << nload << " blocks had to be loaded for writing" << std::endl;
}
} }
MMVManip *MMVManip::clone() const MMVManip *MMVManip::clone() const
@ -860,11 +902,7 @@ MMVManip *MMVManip::clone() const
ret->m_flags = new u8[size]; ret->m_flags = new u8[size];
memcpy(ret->m_flags, m_flags, size * sizeof(u8)); memcpy(ret->m_flags, m_flags, size * sizeof(u8));
} }
ret->m_is_dirty = m_is_dirty; 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; return ret;
} }

View file

@ -307,16 +307,29 @@ public:
MMVManip(Map *map); MMVManip(Map *map);
virtual ~MMVManip() = default; virtual ~MMVManip() = default;
virtual void clear() /*
{ Loads specified area from map and *adds* it to the area already
VoxelManipulator::clear(); contained in the VManip.
m_loaded_blocks.clear(); */
}
void initialEmerge(v3s16 blockpos_min, v3s16 blockpos_max, void initialEmerge(v3s16 blockpos_min, v3s16 blockpos_max,
bool load_if_inexistent = true); 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, void blitBackAll(std::map<v3s16, MapBlock*> * modified_blocks,
bool overwrite_generated = true) const; bool overwrite_generated = true) const;
@ -339,13 +352,4 @@ protected:
// may be null // may be null
Map *m_map = nullptr; 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) void Server::handleCommand_Init(NetworkPacket* pkt)
{ {
if(pkt->getSize() < 1)
return;
session_t peer_id = pkt->getPeerId(); session_t peer_id = pkt->getPeerId();
RemoteClient *client = getClient(peer_id, CS_Created); 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 << verbosestream << "Server: Got TOSERVER_INIT from " << addr_s <<
" (peer_id=" << peer_id << ")" << std::endl; " (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)) if (denyIfBanned(peer_id))
return; return;
@ -161,18 +148,14 @@ void Server::handleCommand_Init(NetworkPacket* pkt)
return; return;
} }
RemotePlayer *player = m_env->getPlayer(playername, true); // Do not allow multiple players in simple singleplayer mode
if (isSingleplayer() && !m_clients.getClientIDs(CS_HelloSent).empty()) {
// If player is already connected, cancel infostream << "Server: Not allowing another client (" << addr_s <<
if (player && player->getPeerId() != PEER_ID_INEXISTENT) { ") to connect in simple singleplayer mode" << std::endl;
actionstream << "Server: Player with name \"" << playername << DenyAccess(peer_id, SERVER_ACCESSDENIED_SINGLEPLAYER);
"\" tried to connect, but player with same name is already connected" << std::endl;
DenyAccess(peer_id, SERVER_ACCESSDENIED_ALREADY_CONNECTED);
return; return;
} }
// Or the "singleplayer" name to be used on regular servers
m_clients.setPlayerName(peer_id, playername);
if (!isSingleplayer() && strcasecmp(playername, "singleplayer") == 0) { if (!isSingleplayer() && strcasecmp(playername, "singleplayer") == 0) {
actionstream << "Server: Player with the name \"singleplayer\" tried " actionstream << "Server: Player with the name \"singleplayer\" tried "
"to connect from " << addr_s << std::endl; "to connect from " << addr_s << std::endl;
@ -180,12 +163,25 @@ void Server::handleCommand_Init(NetworkPacket* pkt)
return; 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; std::string reason;
if (m_script->on_prejoinplayer(playername, addr_s, &reason)) { if (m_script->on_prejoinplayer(playername, addr_s, &reason)) {
actionstream << "Server: Player with the name \"" << playerName << actionstream << "Server: Player with the name \"" << playerName <<
"\" tried to connect from " << addr_s << "\" 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; std::endl;
DenyAccess(peer_id, SERVER_ACCESSDENIED_CUSTOM_STRING, reason); DenyAccess(peer_id, SERVER_ACCESSDENIED_CUSTOM_STRING, reason);
return; return;
@ -195,14 +191,11 @@ void Server::handleCommand_Init(NetworkPacket* pkt)
infostream << "Server: New connection: \"" << playerName << "\" from " << infostream << "Server: New connection: \"" << playerName << "\" from " <<
addr_s << " (peer_id=" << peer_id << ")" << std::endl; addr_s << " (peer_id=" << peer_id << ")" << std::endl;
// Enforce user limit. // Early check for user limit, so the client doesn't need to run
// Don't enforce for users that have some admin right or mod permits it. // through the join process only to be denied.
if (m_clients.isUserLimitReached() && if (checkUserLimit(playerName, addr_s)) {
playername != g_settings->get("name") &&
!m_script->can_bypass_userlimit(playername, addr_s)) {
actionstream << "Server: " << playername << " tried to join from " << actionstream << "Server: " << playername << " tried to join from " <<
addr_s << ", but there are already max_users=" << addr_s << ", but the user limit was reached." << std::endl;
g_settings->getU16("max_users") << " players." << std::endl;
DenyAccess(peer_id, SERVER_ACCESSDENIED_TOO_MANY_USERS); DenyAccess(peer_id, SERVER_ACCESSDENIED_TOO_MANY_USERS);
return; return;
} }
@ -302,6 +295,7 @@ void Server::handleCommand_Init2(NetworkPacket* pkt)
sendMediaAnnouncement(peer_id, lang); sendMediaAnnouncement(peer_id, lang);
RemoteClient *client = getClient(peer_id, CS_InitDone); RemoteClient *client = getClient(peer_id, CS_InitDone);
assert(client);
// Keep client language for server translations // Keep client language for server translations
client->setLangCode(lang); client->setLangCode(lang);
@ -354,6 +348,8 @@ void Server::handleCommand_RequestMedia(NetworkPacket* pkt)
void Server::handleCommand_ClientReady(NetworkPacket* pkt) void Server::handleCommand_ClientReady(NetworkPacket* pkt)
{ {
session_t peer_id = pkt->getPeerId(); session_t peer_id = pkt->getPeerId();
RemoteClient *client = getClient(peer_id, CS_Created);
assert(client);
// decode all information first // decode all information first
u8 major_ver, minor_ver, patch_ver, reserved; u8 major_ver, minor_ver, patch_ver, reserved;
@ -364,8 +360,17 @@ void Server::handleCommand_ClientReady(NetworkPacket* pkt)
if (pkt->getRemainingBytes() >= 2) if (pkt->getRemainingBytes() >= 2)
*pkt >> formspec_ver; *pkt >> formspec_ver;
m_clients.setClientVersion(peer_id, major_ver, minor_ver, patch_ver, client->setVersionInfo(major_ver, minor_ver, patch_ver, full_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 // Emerge player
PlayerSAO* playersao = StageTwoClientInit(peer_id); PlayerSAO* playersao = StageTwoClientInit(peer_id);
@ -1426,7 +1431,7 @@ void Server::handleCommand_FirstSrp(NetworkPacket* pkt)
std::string salt, verification_key; std::string salt, verification_key;
std::string addr_s = getPeerAddress(peer_id).serializeString(); std::string addr_s = client->getAddress().serializeString();
u8 is_empty; u8 is_empty;
*pkt >> salt >> verification_key >> 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); RemoteClient *client = getClient(peer_id, CS_Invalid);
ClientState cstate = client->getState(); ClientState cstate = client->getState();
std::string addr_s = client->getAddress().serializeString();
if (!((cstate == CS_HelloSent) || (cstate == CS_Active))) { if (!((cstate == CS_HelloSent) || (cstate == CS_Active))) {
actionstream << "Server: got SRP _A packet in wrong state " << cstate << actionstream << "Server: got SRP _A packet in wrong state " << cstate <<
" from " << getPeerAddress(peer_id).serializeString() << " from " << addr_s <<
". Ignoring." << std::endl; ". Ignoring." << std::endl;
return; return;
} }
@ -1524,7 +1531,7 @@ void Server::handleCommand_SrpBytesA(NetworkPacket* pkt)
if (client->chosen_mech != AUTH_MECHANISM_NONE) { if (client->chosen_mech != AUTH_MECHANISM_NONE) {
actionstream << "Server: got SRP _A packet, while auth is already " actionstream << "Server: got SRP _A packet, while auth is already "
"going on with mech " << client->chosen_mech << " from " << "going on with mech " << client->chosen_mech << " from " <<
getPeerAddress(peer_id).serializeString() << addr_s <<
" (wantSudo=" << wantSudo << "). Ignoring." << std::endl; " (wantSudo=" << wantSudo << "). Ignoring." << std::endl;
if (wantSudo) { if (wantSudo) {
DenySudoAccess(peer_id); DenySudoAccess(peer_id);
@ -1541,7 +1548,7 @@ void Server::handleCommand_SrpBytesA(NetworkPacket* pkt)
infostream << "Server: TOSERVER_SRP_BYTES_A received with " infostream << "Server: TOSERVER_SRP_BYTES_A received with "
<< "based_on=" << int(based_on) << " and len_A=" << "based_on=" << int(based_on) << " and len_A="
<< bytes_A.length() << "." << std::endl; << bytes_A.length() << std::endl;
AuthMechanism chosen = (based_on == 0) ? AuthMechanism chosen = (based_on == 0) ?
AUTH_MECHANISM_LEGACY_PASSWORD : AUTH_MECHANISM_SRP; 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. // Right now, the auth mechs don't change between login and sudo mode.
if (!client->isMechAllowed(chosen)) { if (!client->isMechAllowed(chosen)) {
actionstream << "Server: Player \"" << client->getName() << actionstream << "Server: Player \"" << client->getName() <<
"\" at " << getPeerAddress(peer_id).serializeString() << "\" from " << addr_s <<
" tried to change password using unallowed mech " << chosen << " tried to change password using unallowed mech " << chosen <<
"." << std::endl; std::endl;
DenySudoAccess(peer_id); DenySudoAccess(peer_id);
return; return;
} }
} else { } else {
if (!client->isMechAllowed(chosen)) { if (!client->isMechAllowed(chosen)) {
actionstream << "Server: Client tried to authenticate from " << actionstream << "Server: Client tried to authenticate from " <<
getPeerAddress(peer_id).serializeString() << addr_s <<
" using unallowed mech " << chosen << "." << std::endl; " using unallowed mech " << chosen << std::endl;
DenyAccess(peer_id, SERVER_ACCESSDENIED_UNEXPECTED_DATA); DenyAccess(peer_id, SERVER_ACCESSDENIED_UNEXPECTED_DATA);
return; return;
} }

View file

@ -261,7 +261,7 @@ int ModApiServer::l_get_player_information(lua_State *L)
lua_settable(L, table); lua_settable(L, table);
lua_pushstring(L,"state"); lua_pushstring(L,"state");
lua_pushstring(L, ClientInterface::state2Name(info.state).c_str()); lua_pushstring(L, ClientInterface::state2Name(info.state));
lua_settable(L, table); lua_settable(L, table);
#endif #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.MinEdge);
push_v3s16(L, vm->m_area.MaxEdge); 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; return 2;
} }
@ -93,11 +126,12 @@ int LuaVoxelManip::l_set_data(lua_State *L)
lua_pop(L, 1); lua_pop(L, 1);
} }
// FIXME: in theory we should clear VOXELFLAG_NO_DATA here // Mark all data as present, since we just got it from Lua
// However there is no way to tell which values Lua code has intended to set // Note that we can't tell if the caller intended to put CONTENT_IGNORE or
// (if they were VOXELFLAG_NO_DATA before), and which were just not touched. // is just repeating the dummy values we push in l_get_data() in case
// In practice this doesn't cause problems because read_from_map() will cause // VOXELFLAG_NO_DATA is set. In practice this doesn't matter since ignore
// all covered blocks to be loaded anyway. // isn't written back to the map anyway.
vm->clearFlags(vm->m_area, VOXELFLAG_NO_DATA);
return 0; return 0;
} }
@ -344,6 +378,19 @@ int LuaVoxelManip::l_get_emerged_area(lua_State *L)
return 2; 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) : LuaVoxelManip::LuaVoxelManip(MMVManip *mmvm, bool is_mg_vm) :
is_mapgen_vm(is_mg_vm), is_mapgen_vm(is_mg_vm),
vm(mmvm) vm(mmvm)
@ -436,6 +483,7 @@ void LuaVoxelManip::Register(lua_State *L)
const char LuaVoxelManip::className[] = "VoxelManip"; const char LuaVoxelManip::className[] = "VoxelManip";
const luaL_Reg LuaVoxelManip::methods[] = { const luaL_Reg LuaVoxelManip::methods[] = {
luamethod(LuaVoxelManip, read_from_map), luamethod(LuaVoxelManip, read_from_map),
luamethod(LuaVoxelManip, initialize),
luamethod(LuaVoxelManip, get_data), luamethod(LuaVoxelManip, get_data),
luamethod(LuaVoxelManip, set_data), luamethod(LuaVoxelManip, set_data),
luamethod(LuaVoxelManip, get_node_at), luamethod(LuaVoxelManip, get_node_at),
@ -451,5 +499,6 @@ const luaL_Reg LuaVoxelManip::methods[] = {
luamethod(LuaVoxelManip, set_param2_data), luamethod(LuaVoxelManip, set_param2_data),
luamethod(LuaVoxelManip, was_modified), luamethod(LuaVoxelManip, was_modified),
luamethod(LuaVoxelManip, get_emerged_area), luamethod(LuaVoxelManip, get_emerged_area),
luamethod(LuaVoxelManip, close),
{0,0} {0,0}
}; };

View file

@ -24,6 +24,7 @@ private:
static int gc_object(lua_State *L); static int gc_object(lua_State *L);
static int l_read_from_map(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_get_data(lua_State *L);
static int l_set_data(lua_State *L); static int l_set_data(lua_State *L);
static int l_write_to_map(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_was_modified(lua_State *L);
static int l_get_emerged_area(lua_State *L); static int l_get_emerged_area(lua_State *L);
static int l_close(lua_State *L);
public: public:
MMVManip *vm = nullptr; 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) { if (peer_id != PEER_ID_INEXISTENT) {
Send(&pkt); Send(&pkt);
} else { } 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); ChatMessage chatmsg(line);
std::vector<session_t> clients = m_clients.getClientIDs(); SendChatMessage(PEER_ID_INEXISTENT, chatmsg);
for (u16 cid : clients)
SendChatMessage(cid, chatmsg);
return L""; return L"";
} }
@ -3357,6 +3356,15 @@ bool Server::denyIfBanned(session_t peer_id)
return false; 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) void Server::notifyPlayer(const char *name, const std::wstring &msg)
{ {
// m_env will be NULL if the server is initializing // 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) 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(); return getClient(peer_id, CS_Invalid)->getAddress();
} }

View file

@ -368,6 +368,7 @@ public:
void hudSetHotbarImage(RemotePlayer *player, const std::string &name); void hudSetHotbarImage(RemotePlayer *player, const std::string &name);
void hudSetHotbarSelectedImage(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); Address getPeerAddress(session_t peer_id);
void setLocalPlayerAnimations(RemotePlayer *player, v2f animation_frames[4], void setLocalPlayerAnimations(RemotePlayer *player, v2f animation_frames[4],
@ -611,6 +612,10 @@ private:
void handleChatInterfaceEvent(ChatEvent *evt); 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 // 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, std::wstring handleChat(const std::string &name, std::wstring wmessage_input,
bool check_shout_priv = false, RemotePlayer *player = nullptr); bool check_shout_priv = false, RemotePlayer *player = nullptr);

View file

@ -47,7 +47,7 @@ const char *ClientInterface::statenames[] = {
"SudoMode", "SudoMode",
}; };
std::string ClientInterface::state2Name(ClientState state) const char *ClientInterface::state2Name(ClientState state)
{ {
return statenames[state]; return statenames[state];
} }
@ -659,7 +659,7 @@ std::vector<session_t> ClientInterface::getClientIDs(ClientState min_state)
{ {
std::vector<session_t> reply; std::vector<session_t> reply;
RecursiveMutexAutoLock clientslock(m_clients_mutex); RecursiveMutexAutoLock clientslock(m_clients_mutex);
reply.reserve(m_clients.size());
for (const auto &m_client : m_clients) { for (const auto &m_client : m_clients) {
if (m_client.second->getState() >= min_state) if (m_client.second->getState() >= min_state)
reply.push_back(m_client.second->peer_id); 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() 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) void ClientInterface::step(float dtime)
@ -703,16 +699,13 @@ void ClientInterface::step(float dtime)
RecursiveMutexAutoLock clientslock(m_clients_mutex); RecursiveMutexAutoLock clientslock(m_clients_mutex);
for (const auto &it : m_clients) { for (const auto &it : m_clients) {
auto state = it.second->getState(); auto state = it.second->getState();
if (state >= CS_HelloSent) if (state >= CS_InitDone)
continue; continue;
if (it.second->uptime() <= LINGER_TIMEOUT) if (it.second->uptime() <= LINGER_TIMEOUT)
continue; continue;
// CS_Created means nobody has even noticed the client is there // Complain louder if this situation is unexpected
// (this is before on_prejoinplayer runs) auto &os = state == CS_Disconnecting || state == CS_Denied ?
// CS_Invalid should not happen infostream : warningstream;
// -> log those as warning, the rest as info
std::ostream &os = state == CS_Created || state == CS_Invalid ?
warningstream : infostream;
try { try {
Address addr = m_con->GetPeerAddress(it.second->peer_id); Address addr = m_con->GetPeerAddress(it.second->peer_id);
os << "Disconnecting lingering client from " 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); 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); RecursiveMutexAutoLock clientslock(m_clients_mutex);
for (auto &client_it : m_clients) { for (auto &[peer_id, client] : m_clients) {
RemoteClient *client = client_it.second; if (client->getState() >= state_min)
m_con->Send(peer_id, ccf.channel, pkt, ccf.reliable);
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);
}
} }
} }
RemoteClient* ClientInterface::getClientNoEx(session_t peer_id, ClientState state_min) RemoteClient* ClientInterface::getClientNoEx(session_t peer_id, ClientState state_min)
{ {
RecursiveMutexAutoLock clientslock(m_clients_mutex); RecursiveMutexAutoLock clientslock(m_clients_mutex);
RemoteClientMap::const_iterator n = m_clients.find(peer_id); RemoteClient *client = lockedGetClientNoEx(peer_id, state_min);
// The client may not exist; clients are immediately removed if their return client;
// 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* ClientInterface::lockedGetClientNoEx(session_t peer_id, ClientState state_min) 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 // The client may not exist; clients are immediately removed if their
// access is denied, and this event occurs later then. // access is denied, and this event occurs later then.
if (n == m_clients.end()) if (n == m_clients.end())
return NULL; return nullptr;
assert(n->second->peer_id == peer_id);
if (n->second->getState() >= state_min) if (n->second->getState() >= state_min)
return n->second; return n->second;
return NULL; return nullptr;
} }
ClientState ClientInterface::getClientState(session_t peer_id) ClientState ClientInterface::getClientState(session_t peer_id)
@ -825,16 +808,6 @@ ClientState ClientInterface::getClientState(session_t peer_id)
return n->second->getState(); 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) void ClientInterface::DeleteClient(session_t peer_id)
{ {
RecursiveMutexAutoLock conlock(m_clients_mutex); RecursiveMutexAutoLock conlock(m_clients_mutex);
@ -915,18 +888,3 @@ u16 ClientInterface::getProtocolVersion(session_t peer_id)
return n->second->net_proto_version; 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 */ /* mark blocks as not sent on all active clients */
void markBlocksNotSent(const std::vector<v3s16> &positions, bool low_priority = false); 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(); bool isUserLimitReached();
/* get list of client player names */ /* get list of client player names */
@ -458,7 +458,7 @@ public:
void sendCustom(session_t peer_id, u8 channel, NetworkPacket *pkt, bool reliable); void sendCustom(session_t peer_id, u8 channel, NetworkPacket *pkt, bool reliable);
/* send to all clients */ /* send to all clients */
void sendToAll(NetworkPacket *pkt); void sendToAll(NetworkPacket *pkt, ClientState state_min = CS_Active);
/* delete a client */ /* delete a client */
void DeleteClient(session_t peer_id); void DeleteClient(session_t peer_id);
@ -475,16 +475,9 @@ public:
/* get state of client by id*/ /* get state of client by id*/
ClientState getClientState(session_t peer_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 */ /* get protocol version of client */
u16 getProtocolVersion(session_t peer_id); 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 */ /* event to update client state */
void event(session_t peer_id, ClientStateEvent event); void event(session_t peer_id, ClientStateEvent event);
@ -495,7 +488,8 @@ public:
m_env = env; m_env = env;
} }
static std::string state2Name(ClientState state); static const char *state2Name(ClientState state);
protected: protected:
class AutoLock { class AutoLock {
public: public:
@ -514,9 +508,9 @@ private:
// Connection // Connection
std::shared_ptr<con::IConnection> m_con; std::shared_ptr<con::IConnection> m_con;
std::recursive_mutex m_clients_mutex; std::recursive_mutex m_clients_mutex;
// Connected clients (behind the con mutex) // Connected clients (behind the mutex)
RemoteClientMap m_clients; 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 // Environment
ServerEnvironment *m_env; ServerEnvironment *m_env;
@ -526,5 +520,7 @@ private:
static const char *statenames[]; 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(v3.intersect(v1) == v1.intersect(v3));
UASSERT(v1.intersect(v4) == UASSERT(v1.intersect(v4) ==
VoxelArea({-10, -2, -10}, {10, 2, 10})); 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() void TestVoxelArea::test_index_xyz_all_pos()

View file

@ -4,11 +4,13 @@
#include "test.h" #include "test.h"
#include <algorithm> #include <memory>
#include "gamedef.h" #include "gamedef.h"
#include "log.h" #include "log.h"
#include "voxel.h" #include "voxel.h"
#include "dummymap.h"
#include "irrlicht_changes/printing.h"
class TestVoxelManipulator : public TestBase { class TestVoxelManipulator : public TestBase {
public: public:
@ -17,59 +19,32 @@ public:
void runTests(IGameDef *gamedef); void runTests(IGameDef *gamedef);
void testVoxelArea(); void testBasic(const NodeDefManager *nodedef);
void testVoxelManipulator(const NodeDefManager *nodedef); void testEmerge(IGameDef *gamedef);
void testBlitBack(IGameDef *gamedef);
void testBlitBack2(IGameDef *gamedef);
}; };
static TestVoxelManipulator g_test_instance; static TestVoxelManipulator g_test_instance;
void TestVoxelManipulator::runTests(IGameDef *gamedef) void TestVoxelManipulator::runTests(IGameDef *gamedef)
{ {
TEST(testVoxelArea); TEST(testBasic, gamedef->ndef());
TEST(testVoxelManipulator, gamedef->getNodeDefManager()); TEST(testEmerge, gamedef);
TEST(testBlitBack, gamedef);
TEST(testBlitBack2, gamedef);
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
void TestVoxelManipulator::testVoxelArea() void TestVoxelManipulator::testBasic(const NodeDefManager *nodedef)
{
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)
{ {
VoxelManipulator v; VoxelManipulator v;
v.print(infostream, nodedef); 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.setNode(v3s16(-1,0,-1), MapNode(t_CONTENT_GRASS));
v.print(infostream, nodedef); 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); UASSERT(v.getNode(v3s16(-1,0,-1)).getContent() == t_CONTENT_GRASS);
EXCEPTION_CHECK(InvalidPositionException, v.getNode(v3s16(0,1,1))); 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 Debug stuff
*/ */
u64 emerge_time = 0; u64 emerge_time = 0;
u64 emerge_load_time = 0;
VoxelManipulator::~VoxelManipulator() VoxelManipulator::~VoxelManipulator()
{ {

View file

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