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:
commit
f3071dcd82
23 changed files with 499 additions and 341 deletions
|
@ -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
|
||||||
|
|
|
@ -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`
|
||||||
-----------
|
-----------
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
{
|
{
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
108
src/map.cpp
108
src/map.cpp
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
36
src/map.h
36
src/map.h
|
@ -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,
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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}
|
||||||
};
|
};
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
|
@ -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()
|
||||||
{
|
{
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue