1
0
Fork 0
mirror of https://github.com/luanti-org/luanti.git synced 2025-08-06 17:41:04 +00:00

Merge remote-tracking branch 'upstream/master' into Visuals-Vol-2

This commit is contained in:
Gefüllte Taubenbrust 2024-10-11 20:44:12 +02:00
commit b6c099073f
183 changed files with 3919 additions and 1642 deletions

View file

@ -405,10 +405,11 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 tool_reload_ratio)
// Compute absolute camera position and target
m_headnode->getAbsoluteTransformation().transformVect(m_camera_position, rel_cam_pos);
m_headnode->getAbsoluteTransformation().rotateVect(m_camera_direction, rel_cam_target - rel_cam_pos);
m_camera_direction = m_headnode->getAbsoluteTransformation()
.rotateAndScaleVect(rel_cam_target - rel_cam_pos);
v3f abs_cam_up;
m_headnode->getAbsoluteTransformation().rotateVect(abs_cam_up, rel_cam_up);
v3f abs_cam_up = m_headnode->getAbsoluteTransformation()
.rotateAndScaleVect(rel_cam_up);
// Separate camera position for calculation
v3f my_cp = m_camera_position;

View file

@ -827,7 +827,7 @@ bool Client::loadMedia(const std::string &data, const std::string &filename,
}
const char *model_ext[] = {
".x", ".b3d", ".obj", ".gltf",
".x", ".b3d", ".obj", ".gltf", ".glb",
NULL
};
name = removeStringEnd(filename, model_ext);
@ -1034,7 +1034,7 @@ void Client::Send(NetworkPacket* pkt)
m_con->Send(PEER_ID_SERVER, scf.channel, pkt, scf.reliable);
}
// Will fill up 12 + 12 + 4 + 4 + 4 + 1 + 1 + 1 bytes
// Will fill up 12 + 12 + 4 + 4 + 4 + 1 + 1 + 1 + 4 + 4 bytes
void writePlayerPos(LocalPlayer *myplayer, ClientMap *clientMap, NetworkPacket *pkt, bool camera_inverted)
{
v3f pf = myplayer->getPosition() * 100;
@ -1046,6 +1046,8 @@ void writePlayerPos(LocalPlayer *myplayer, ClientMap *clientMap, NetworkPacket *
u8 fov = std::fmin(255.0f, clientMap->getCameraFov() * 80.0f);
u8 wanted_range = std::fmin(255.0f,
std::ceil(clientMap->getWantedRange() * (1.0f / MAP_BLOCKSIZE)));
f32 movement_speed = myplayer->control.movement_speed;
f32 movement_dir = myplayer->control.movement_direction;
v3s32 position(pf.X, pf.Y, pf.Z);
v3s32 speed(sf.X, sf.Y, sf.Z);
@ -1060,10 +1062,13 @@ void writePlayerPos(LocalPlayer *myplayer, ClientMap *clientMap, NetworkPacket *
[12+12+4+4+4] u8 fov*80
[12+12+4+4+4+1] u8 ceil(wanted_range / MAP_BLOCKSIZE)
[12+12+4+4+4+1+1] u8 camera_inverted (bool)
[12+12+4+4+4+1+1+1] f32 movement_speed
[12+12+4+4+4+1+1+1+4] f32 movement_direction
*/
*pkt << position << speed << pitch << yaw << keyPressed;
*pkt << fov << wanted_range;
*pkt << camera_inverted;
*pkt << movement_speed << movement_dir;
}
void Client::interact(InteractAction action, const PointedThing& pointed)
@ -1142,7 +1147,7 @@ void Client::sendInit(const std::string &playerName)
NetworkPacket pkt(TOSERVER_INIT, 1 + 2 + 2 + (1 + playerName.size()));
pkt << (u8) SER_FMT_VER_HIGHEST_READ << (u16) 0;
pkt << (u16) CLIENT_PROTOCOL_VERSION_MIN << (u16) CLIENT_PROTOCOL_VERSION_MAX;
pkt << CLIENT_PROTOCOL_VERSION_MIN << LATEST_PROTOCOL_VERSION;
pkt << playerName;
Send(&pkt);
@ -1397,6 +1402,8 @@ void Client::sendPlayerPos()
u32 keyPressed = player->control.getKeysPressed();
bool camera_inverted = m_camera->getCameraMode() == CAMERA_MODE_THIRD_FRONT;
f32 movement_speed = player->control.movement_speed;
f32 movement_dir = player->control.movement_direction;
if (
player->last_position == player->getPosition() &&
@ -1406,7 +1413,9 @@ void Client::sendPlayerPos()
player->last_keyPressed == keyPressed &&
player->last_camera_fov == camera_fov &&
player->last_camera_inverted == camera_inverted &&
player->last_wanted_range == wanted_range)
player->last_wanted_range == wanted_range &&
player->last_movement_speed == movement_speed &&
player->last_movement_dir == movement_dir)
return;
player->last_position = player->getPosition();
@ -1417,8 +1426,10 @@ void Client::sendPlayerPos()
player->last_camera_fov = camera_fov;
player->last_camera_inverted = camera_inverted;
player->last_wanted_range = wanted_range;
player->last_movement_speed = movement_speed;
player->last_movement_dir = movement_dir;
NetworkPacket pkt(TOSERVER_PLAYERPOS, 12 + 12 + 4 + 4 + 4 + 1 + 1 + 1);
NetworkPacket pkt(TOSERVER_PLAYERPOS, 12 + 12 + 4 + 4 + 4 + 1 + 1 + 1 + 4 + 4);
writePlayerPos(player, &map, &pkt, camera_inverted);

View file

@ -35,6 +35,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "gameparams.h"
#include "script/common/c_types.h" // LuaError
#include "util/numeric.h"
#include "util/string.h" // StringMap
#ifdef SERVER
#error Do not include in server builds

View file

@ -1015,8 +1015,7 @@ int ClientMap::getBackgroundBrightness(float max_d, u32 daylight_factor,
v3f z_dir = z_directions[i];
core::CMatrix4<f32> a;
a.buildRotateFromTo(v3f(0,1,0), z_dir);
v3f dir = m_camera_direction;
a.rotateVect(dir);
v3f dir = a.rotateAndScaleVect(m_camera_direction);
int br = 0;
float step = BS*1.5;
if(max_d > 35*BS)

View file

@ -1052,7 +1052,7 @@ void GenericCAO::step(float dtime, ClientEnvironment *env)
walking = true;
}
v2s32 new_anim = v2s32(0,0);
v2f new_anim(0,0);
bool allow_update = false;
// increase speed if using fast or flying fast
@ -1799,10 +1799,9 @@ void GenericCAO::processMessage(const std::string &data)
phys.speed_walk = override_speed_walk;
}
} else if (cmd == AO_CMD_SET_ANIMATION) {
// TODO: change frames send as v2s32 value
v2f range = readV2F32(is);
if (!m_is_local_player) {
m_animation_range = v2s32((s32)range.X, (s32)range.Y);
m_animation_range = range;
m_animation_speed = readF32(is);
m_animation_blend = readF32(is);
// these are sent inverted so we get true when the server sends nothing
@ -1812,7 +1811,7 @@ void GenericCAO::processMessage(const std::string &data)
LocalPlayer *player = m_env->getLocalPlayer();
if(player->last_animation == LocalPlayerAnimation::NO_ANIM)
{
m_animation_range = v2s32((s32)range.X, (s32)range.Y);
m_animation_range = range;
m_animation_speed = readF32(is);
m_animation_blend = readF32(is);
// these are sent inverted so we get true when the server sends nothing

View file

@ -99,7 +99,7 @@ private:
v2s16 m_tx_basepos;
bool m_initial_tx_basepos_set = false;
bool m_tx_select_horiz_by_yawpitch = false;
v2s32 m_animation_range;
v2f m_animation_range;
float m_animation_speed = 15.0f;
float m_animation_blend = 0.0f;
bool m_animation_loop = true;

View file

@ -1016,13 +1016,6 @@ void MapblockMeshGenerator::drawGlasslikeFramedNode()
}
}
void MapblockMeshGenerator::drawAllfacesNode()
{
static const aabb3f box(-BS / 2, -BS / 2, -BS / 2, BS / 2, BS / 2, BS / 2);
useTile(0, 0, 0);
drawAutoLightedCuboid(box);
}
void MapblockMeshGenerator::drawTorchlikeNode()
{
u8 wall = cur_node.n.getWallMounted(nodedef);
@ -1545,6 +1538,17 @@ namespace {
};
}
void MapblockMeshGenerator::drawAllfacesNode()
{
static const aabb3f box(-BS / 2, -BS / 2, -BS / 2, BS / 2, BS / 2, BS / 2);
TileSpec tiles[6];
for (int face = 0; face < 6; face++)
getTile(nodebox_tile_dirs[face], &tiles[face]);
if (data->m_smooth_lighting)
getSmoothLightFrame();
drawAutoLightedCuboid(box, nullptr, tiles, 6);
}
void MapblockMeshGenerator::drawNodeboxNode()
{
TileSpec tiles[6];

View file

@ -43,6 +43,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "gui/touchcontrols.h"
#include "itemdef.h"
#include "log.h"
#include "log_internal.h"
#include "filesys.h"
#include "gameparams.h"
#include "gettext.h"
@ -413,16 +414,8 @@ class GameGlobalShaderConstantSetter : public IShaderConstantSetter
float m_user_exposure_compensation;
bool m_bloom_enabled;
CachedPixelShaderSetting<float> m_bloom_intensity_pixel{"bloomIntensity"};
float m_bloom_intensity;
CachedPixelShaderSetting<float> m_bloom_strength_pixel{"bloomStrength"};
float m_bloom_strength;
CachedPixelShaderSetting<float> m_bloom_radius_pixel{"bloomRadius"};
float m_bloom_radius;
CachedPixelShaderSetting<float> m_cloud_height_pixel{"cloudHeight"};
CachedPixelShaderSetting<float> m_cloud_thickness_pixel{"cloudThickness"};
CachedPixelShaderSetting<float> m_cloud_density_pixel{"cloudDensity"};
CachedPixelShaderSetting<float, 2> m_cloud_offset_pixel{"cloudOffset"};
CachedPixelShaderSetting<float> m_cloud_radius_pixel{"cloudRadius"};
CachedPixelShaderSetting<float> m_saturation_pixel{"saturation"};
float m_gamma;
CachedPixelShaderSetting<float> m_gamma_pixel{"gamma"};
@ -436,12 +429,8 @@ class GameGlobalShaderConstantSetter : public IShaderConstantSetter
CachedPixelShaderSetting<float>
m_volumetric_light_strength_pixel{"volumetricLightStrength"};
static constexpr std::array<const char*, 5> SETTING_CALLBACKS = {
static constexpr std::array<const char*, 1> SETTING_CALLBACKS = {
"exposure_compensation",
"bloom_intensity",
"bloom_strength_factor",
"bloom_radius",
"gamma"
};
public:
@ -449,14 +438,6 @@ public:
{
if (name == "exposure_compensation")
m_user_exposure_compensation = g_settings->getFloat("exposure_compensation", -1.0f, 1.0f);
if (name == "bloom_intensity")
m_bloom_intensity = g_settings->getFloat("bloom_intensity", 0.01f, 1.0f);
if (name == "bloom_strength_factor")
m_bloom_strength = RenderingEngine::BASE_BLOOM_STRENGTH * g_settings->getFloat("bloom_strength_factor", 0.1f, 10.0f);
if (name == "bloom_radius")
m_bloom_radius = g_settings->getFloat("bloom_radius", 0.1f, 8.0f);
if (name == "gamma")
m_gamma = g_settings->getFloat("gamma", 1.0f, 5.0f);
}
static void settingsCallback(const std::string &name, void *userdata)
@ -475,10 +456,6 @@ public:
m_user_exposure_compensation = g_settings->getFloat("exposure_compensation", -1.0f, 1.0f);
m_bloom_enabled = g_settings->getBool("enable_bloom");
m_bloom_intensity = g_settings->getFloat("bloom_intensity", 0.01f, 1.0f);
m_bloom_strength = RenderingEngine::BASE_BLOOM_STRENGTH * g_settings->getFloat("bloom_strength_factor", 0.1f, 10.0f);
m_bloom_radius = g_settings->getFloat("bloom_radius", 0.1f, 8.0f);
m_gamma = g_settings->getFloat("gamma", 1.0f, 5.0f);
m_volumetric_light_enabled = g_settings->getBool("enable_volumetric_lighting") && m_bloom_enabled;
}
@ -547,7 +524,9 @@ public:
m_texel_size0_vertex.set(m_texel_size0, services);
m_texel_size0_pixel.set(m_texel_size0, services);
const AutoExposure &exposure_params = m_client->getEnv().getLocalPlayer()->getLighting().exposure;
const auto &lighting = m_client->getEnv().getLocalPlayer()->getLighting();
const AutoExposure &exposure_params = lighting.exposure;
std::array<float, 7> exposure_buffer = {
std::pow(2.0f, exposure_params.luminance_min),
std::pow(2.0f, exposure_params.luminance_max),
@ -560,14 +539,14 @@ public:
m_exposure_params_pixel.set(exposure_buffer.data(), services);
if (m_bloom_enabled) {
m_bloom_intensity_pixel.set(&m_bloom_intensity, services);
m_bloom_radius_pixel.set(&m_bloom_radius, services);
m_bloom_strength_pixel.set(&m_bloom_strength, services);
float intensity = std::max(lighting.bloom_intensity, 0.0f);
m_bloom_intensity_pixel.set(&intensity, services);
float strength_factor = std::max(lighting.bloom_strength_factor, 0.0f);
m_bloom_strength_pixel.set(&strength_factor, services);
float radius = std::max(lighting.bloom_radius, 0.0f);
m_bloom_radius_pixel.set(&radius, services);
}
m_gamma_pixel.set(&m_gamma, services);
const auto &lighting = m_client->getEnv().getLocalPlayer()->getLighting();
float saturation = lighting.saturation;
m_saturation_pixel.set(&saturation, services);
video::SColorf artificial_light = lighting.artificial_light_color;
@ -773,6 +752,7 @@ protected:
void processUserInput(f32 dtime);
void processKeyInput();
void processItemSelection(u16 *new_playeritem);
bool shouldShowTouchControls();
void dropSelectedItem(bool single_item = false);
void openInventory();
@ -1615,6 +1595,14 @@ bool Game::createClient(const GameStartData &start_data)
return true;
}
bool Game::shouldShowTouchControls()
{
const std::string &touch_controls = g_settings->get("touch_controls");
if (touch_controls == "auto")
return RenderingEngine::getLastPointerType() == PointerType::Touch;
return is_yes(touch_controls);
}
bool Game::initGui()
{
m_game_ui->init();
@ -1629,7 +1617,7 @@ bool Game::initGui()
gui_chat_console = make_irr<GUIChatConsole>(guienv, guienv->getRootGUIElement(),
-1, chat_backend, client, &g_menumgr);
if (g_settings->getBool("touch_controls")) {
if (shouldShowTouchControls()) {
g_touchcontrols = new TouchControls(device, texture_src);
g_touchcontrols->setUseCrosshair(!isTouchCrosshairDisabled());
}
@ -2081,6 +2069,15 @@ void Game::updateStats(RunStats *stats, const FpsControl &draw_times,
void Game::processUserInput(f32 dtime)
{
bool desired = shouldShowTouchControls();
if (desired && !g_touchcontrols) {
g_touchcontrols = new TouchControls(device, texture_src);
} else if (!desired && g_touchcontrols) {
delete g_touchcontrols;
g_touchcontrols = nullptr;
}
// Reset input if window not active or some menu is active
if (!device->isWindowActive() || isMenuActive() || guienv->hasFocus(gui_chat_console.get())) {
if (m_game_focused) {
@ -2711,7 +2708,7 @@ void Game::updateCameraDirection(CameraOrientation *cam, float dtime)
cur_control->setVisible(false);
}
if (m_first_loop_after_window_activation) {
if (m_first_loop_after_window_activation && !g_touchcontrols) {
m_first_loop_after_window_activation = false;
input->setMousePos(driver->getScreenSize().Width / 2,
@ -2727,6 +2724,8 @@ void Game::updateCameraDirection(CameraOrientation *cam, float dtime)
m_first_loop_after_window_activation = true;
}
if (g_touchcontrols)
m_first_loop_after_window_activation = true;
}
// Get the factor to multiply with sensitivity to get the same mouse/joystick
@ -2792,9 +2791,10 @@ void Game::updatePlayerControl(const CameraOrientation &cam)
isKeyDown(KeyType::PLACE),
cam.camera_pitch,
cam.camera_yaw,
input->getMovementSpeed(),
input->getMovementDirection()
input->getJoystickSpeed(),
input->getJoystickDirection()
);
control.setMovementFromKeys();
// autoforward if set: move at maximum speed
if (player->getPlayerSettings().continuous_forward &&

View file

@ -536,9 +536,9 @@ void Hud::drawLuaElements(const v3s16 &camera_offset)
return; // Avoid zero divides
// Angle according to camera view
v3f fore(0.f, 0.f, 1.f);
scene::ICameraSceneNode *cam = client->getSceneManager()->getActiveCamera();
cam->getAbsoluteTransformation().rotateVect(fore);
v3f fore = cam->getAbsoluteTransformation()
.rotateAndScaleVect(v3f(0.f, 0.f, 1.f));
int angle = - fore.getHorizontalAngle().Y;
// Limit angle and ajust with given offset

View file

@ -1447,6 +1447,8 @@ bool ImageSource::generateImagePart(std::string_view part_of_name,
video::IImage *img = generateImage(filename, source_image_names);
if (img) {
upscaleImagesToMatchLargest(baseimg, img);
apply_mask(img, baseimg, v2s32(0, 0), v2s32(0, 0),
img->getDimension());
img->drop();

View file

@ -24,6 +24,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "gui/mainmenumanager.h"
#include "gui/touchcontrols.h"
#include "hud.h"
#include "log_internal.h"
#include "client/renderingengine.h"
void KeyCache::populate_nonchanging()
{
@ -141,6 +143,11 @@ bool MyEventReceiver::OnEvent(const SEvent &event)
}
}
if (event.EventType == EET_MOUSE_INPUT_EVENT && !event.MouseInput.Simulated)
last_pointer_type = PointerType::Mouse;
else if (event.EventType == EET_TOUCH_INPUT_EVENT)
last_pointer_type = PointerType::Touch;
// Let the menu handle events, if one is active.
if (isMenuActive()) {
if (g_touchcontrols)
@ -220,51 +227,42 @@ bool MyEventReceiver::OnEvent(const SEvent &event)
/*
* RealInputHandler
*/
float RealInputHandler::getMovementSpeed()
float RealInputHandler::getJoystickSpeed()
{
bool f = m_receiver->IsKeyDown(keycache.key[KeyType::FORWARD]),
b = m_receiver->IsKeyDown(keycache.key[KeyType::BACKWARD]),
l = m_receiver->IsKeyDown(keycache.key[KeyType::LEFT]),
r = m_receiver->IsKeyDown(keycache.key[KeyType::RIGHT]);
if (f || b || l || r)
{
// if contradictory keys pressed, stay still
if (f && b && l && r)
return 0.0f;
else if (f && b && !l && !r)
return 0.0f;
else if (!f && !b && l && r)
return 0.0f;
return 1.0f; // If there is a keyboard event, assume maximum speed
}
if (g_touchcontrols && g_touchcontrols->getMovementSpeed())
return g_touchcontrols->getMovementSpeed();
if (g_touchcontrols && g_touchcontrols->getJoystickSpeed())
return g_touchcontrols->getJoystickSpeed();
return joystick.getMovementSpeed();
}
float RealInputHandler::getMovementDirection()
float RealInputHandler::getJoystickDirection()
{
float x = 0, z = 0;
/* Check keyboard for input */
if (m_receiver->IsKeyDown(keycache.key[KeyType::FORWARD]))
z += 1;
if (m_receiver->IsKeyDown(keycache.key[KeyType::BACKWARD]))
z -= 1;
if (m_receiver->IsKeyDown(keycache.key[KeyType::RIGHT]))
x += 1;
if (m_receiver->IsKeyDown(keycache.key[KeyType::LEFT]))
x -= 1;
if (x != 0 || z != 0) /* If there is a keyboard event, it takes priority */
return std::atan2(x, z);
// `getMovementDirection() == 0` means forward, so we cannot use
// `getMovementDirection()` as a condition.
else if (g_touchcontrols && g_touchcontrols->getMovementSpeed())
return g_touchcontrols->getMovementDirection();
// `getJoystickDirection() == 0` means forward, so we cannot use
// `getJoystickDirection()` as a condition.
if (g_touchcontrols && g_touchcontrols->getJoystickSpeed())
return g_touchcontrols->getJoystickDirection();
return joystick.getMovementDirection();
}
v2s32 RealInputHandler::getMousePos()
{
auto control = RenderingEngine::get_raw_device()->getCursorControl();
if (control) {
return control->getPosition();
}
return m_mousepos;
}
void RealInputHandler::setMousePos(s32 x, s32 y)
{
auto control = RenderingEngine::get_raw_device()->getCursorControl();
if (control) {
control->setPosition(x, y);
} else {
m_mousepos = v2s32(x, y);
}
}
/*
* RandomInputHandler
*/
@ -320,25 +318,11 @@ void RandomInputHandler::step(float dtime)
counterMovement -= dtime;
if (counterMovement < 0.0) {
counterMovement = 0.1 * Rand(1, 40);
movementSpeed = Rand(0,100)*0.01;
movementDirection = Rand(-100, 100)*0.01 * M_PI;
joystickSpeed = Rand(0,100)*0.01;
joystickDirection = Rand(-100, 100)*0.01 * M_PI;
}
} else {
bool f = keydown[keycache.key[KeyType::FORWARD]],
l = keydown[keycache.key[KeyType::LEFT]];
if (f || l) {
movementSpeed = 1.0f;
if (f && !l)
movementDirection = 0.0;
else if (!f && l)
movementDirection = -M_PI_2;
else if (f && l)
movementDirection = -M_PI_4;
else
movementDirection = 0.0;
} else {
movementSpeed = 0.0;
movementDirection = 0.0;
}
joystickSpeed = 0.0f;
joystickDirection = 0.0f;
}
}

View file

@ -23,10 +23,14 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "joystick_controller.h"
#include <list>
#include "keycode.h"
#include "renderingengine.h"
class InputHandler;
enum class PointerType {
Mouse,
Touch,
};
/****************************************************************************
Fast key cache for main game loop
****************************************************************************/
@ -199,6 +203,8 @@ public:
JoystickController *joystick = nullptr;
PointerType getLastPointerType() { return last_pointer_type; }
private:
s32 mouse_wheel = 0;
@ -223,6 +229,8 @@ private:
// Intentionally not reset by clearInput/releaseAllKeys.
bool fullscreen_is_down = false;
PointerType last_pointer_type = PointerType::Mouse;
};
class InputHandler
@ -247,8 +255,8 @@ public:
virtual bool wasKeyReleased(GameKeyType k) = 0;
virtual bool cancelPressed() = 0;
virtual float getMovementSpeed() = 0;
virtual float getMovementDirection() = 0;
virtual float getJoystickSpeed() = 0;
virtual float getJoystickDirection() = 0;
virtual void clearWasKeyPressed() {}
virtual void clearWasKeyReleased() {}
@ -304,9 +312,9 @@ public:
return m_receiver->WasKeyReleased(keycache.key[k]) || joystick.wasKeyReleased(k);
}
virtual float getMovementSpeed();
virtual float getJoystickSpeed();
virtual float getMovementDirection();
virtual float getJoystickDirection();
virtual bool cancelPressed()
{
@ -331,25 +339,8 @@ public:
m_receiver->dontListenForKeys();
}
virtual v2s32 getMousePos()
{
auto control = RenderingEngine::get_raw_device()->getCursorControl();
if (control) {
return control->getPosition();
}
return m_mousepos;
}
virtual void setMousePos(s32 x, s32 y)
{
auto control = RenderingEngine::get_raw_device()->getCursorControl();
if (control) {
control->setPosition(x, y);
} else {
m_mousepos = v2s32(x, y);
}
}
virtual v2s32 getMousePos();
virtual void setMousePos(s32 x, s32 y);
virtual s32 getMouseWheel()
{
@ -388,8 +379,8 @@ public:
virtual bool wasKeyPressed(GameKeyType k) { return false; }
virtual bool wasKeyReleased(GameKeyType k) { return false; }
virtual bool cancelPressed() { return false; }
virtual float getMovementSpeed() { return movementSpeed; }
virtual float getMovementDirection() { return movementDirection; }
virtual float getJoystickSpeed() { return joystickSpeed; }
virtual float getJoystickDirection() { return joystickDirection; }
virtual v2s32 getMousePos() { return mousepos; }
virtual void setMousePos(s32 x, s32 y) { mousepos = v2s32(x, y); }
@ -403,6 +394,6 @@ private:
KeyList keydown;
v2s32 mousepos;
v2s32 mousespeed;
float movementSpeed;
float movementDirection;
float joystickSpeed;
float joystickDirection;
};

View file

@ -105,6 +105,8 @@ public:
u8 last_camera_fov = 0;
u8 last_wanted_range = 0;
bool last_camera_inverted = false;
f32 last_movement_speed = 0.0f;
f32 last_movement_dir = 0.0f;
float camera_impact = 0.0f;

View file

@ -357,16 +357,18 @@ void ParticleSpawner::spawnParticle(ClientEnvironment *env, float radius,
if (attached_absolute_pos_rot_matrix) {
// Apply attachment rotation
attached_absolute_pos_rot_matrix->rotateVect(pp.vel);
attached_absolute_pos_rot_matrix->rotateVect(pp.acc);
pp.vel = attached_absolute_pos_rot_matrix->rotateAndScaleVect(pp.vel);
pp.acc = attached_absolute_pos_rot_matrix->rotateAndScaleVect(pp.acc);
}
if (attractor_obj)
attractor_origin += attractor_obj->getPosition() / BS;
if (attractor_direction_obj) {
auto *attractor_absolute_pos_rot_matrix = attractor_direction_obj->getAbsolutePosRotMatrix();
if (attractor_absolute_pos_rot_matrix)
attractor_absolute_pos_rot_matrix->rotateVect(attractor_direction);
if (attractor_absolute_pos_rot_matrix) {
attractor_direction = attractor_absolute_pos_rot_matrix
->rotateAndScaleVect(attractor_direction);
}
}
pp.expirationtime = r_exp.pickWithin();

View file

@ -41,7 +41,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
RenderingEngine *RenderingEngine::s_singleton = nullptr;
const video::SColor RenderingEngine::MENU_SKY_COLOR = video::SColor(255, 140, 186, 250);
const float RenderingEngine::BASE_BLOOM_STRENGTH = 1.0f;
/* Helper classes */
@ -173,7 +172,7 @@ static irr::IrrlichtDevice *createDevice(SIrrlichtCreationParameters params, std
/* RenderingEngine class */
RenderingEngine::RenderingEngine(IEventReceiver *receiver)
RenderingEngine::RenderingEngine(MyEventReceiver *receiver)
{
sanity_check(!s_singleton);
@ -226,6 +225,8 @@ RenderingEngine::RenderingEngine(IEventReceiver *receiver)
// This changes the minimum allowed number of vertices in a VBO. Default is 500.
driver->setMinHardwareBufferVertexCount(4);
m_receiver = receiver;
s_singleton = this;
g_settings->registerChangedCallback("fullscreen", settingChangedCallback, this);

View file

@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <vector>
#include <memory>
#include <string>
#include "client/inputhandler.h"
#include "irrlichttypes_extrabloated.h"
#include "debug.h"
#include "client/shader.h"
@ -81,9 +82,8 @@ class RenderingEngine
{
public:
static const video::SColor MENU_SKY_COLOR;
static const float BASE_BLOOM_STRENGTH;
RenderingEngine(IEventReceiver *eventReceiver);
RenderingEngine(MyEventReceiver *eventReceiver);
~RenderingEngine();
void setResizable(bool resize);
@ -168,6 +168,12 @@ public:
const irr::core::dimension2d<u32> initial_screen_size,
const bool initial_window_maximized);
static PointerType getLastPointerType()
{
sanity_check(s_singleton && s_singleton->m_receiver);
return s_singleton->m_receiver->getLastPointerType();
}
private:
static void settingChangedCallback(const std::string &name, void *data);
v2u32 _getWindowSize() const;
@ -175,5 +181,6 @@ private:
std::unique_ptr<RenderingCore> core;
irr::IrrlichtDevice *m_device = nullptr;
irr::video::IVideoDriver *driver;
MyEventReceiver *m_receiver = nullptr;
static RenderingEngine *s_singleton;
};

View file

@ -322,6 +322,9 @@ public:
private:
// Are shaders even enabled?
bool m_enabled;
// The id of the thread that is allowed to use irrlicht directly
std::thread::id m_main_thread;
@ -360,6 +363,12 @@ ShaderSource::ShaderSource()
// Add a dummy ShaderInfo as the first index, named ""
m_shaderinfo_cache.emplace_back();
m_enabled = g_settings->getBool("enable_shaders");
if (!m_enabled) {
warningstream << "You are running " PROJECT_NAME_C " with shaders disabled, "
"this is not a recommended configuration." << std::endl;
}
// Add main global constant setter
addShaderConstantSetterFactory(new MainShaderConstantSetterFactory());
}
@ -368,9 +377,11 @@ ShaderSource::~ShaderSource()
{
MutexAutoLock lock(m_shaderinfo_cache_mutex);
if (!m_enabled)
return;
// Delete materials
video::IGPUProgrammingServices *gpu = RenderingEngine::get_video_driver()->
getGPUProgrammingServices();
auto *gpu = RenderingEngine::get_video_driver()->getGPUProgrammingServices();
for (ShaderInfo &i : m_shaderinfo_cache) {
if (!i.name.empty())
gpu->deleteShaderMaterial(i.material);
@ -499,9 +510,11 @@ void ShaderSource::rebuildShaders()
{
MutexAutoLock lock(m_shaderinfo_cache_mutex);
if (!m_enabled)
return;
// Delete materials
video::IGPUProgrammingServices *gpu = RenderingEngine::get_video_driver()->
getGPUProgrammingServices();
auto *gpu = RenderingEngine::get_video_driver()->getGPUProgrammingServices();
for (ShaderInfo &i : m_shaderinfo_cache) {
if (!i.name.empty()) {
gpu->deleteShaderMaterial(i.material);
@ -548,12 +561,11 @@ ShaderInfo ShaderSource::generateShader(const std::string &name,
}
shaderinfo.material = shaderinfo.base_material;
bool enable_shaders = g_settings->getBool("enable_shaders");
if (!enable_shaders)
if (!m_enabled)
return shaderinfo;
video::IVideoDriver *driver = RenderingEngine::get_video_driver();
video::IGPUProgrammingServices *gpu = driver->getGPUProgrammingServices();
auto *gpu = driver->getGPUProgrammingServices();
if (!driver->queryFeature(video::EVDF_ARB_GLSL) || !gpu) {
throw ShaderException(gettext("Shaders are enabled but GLSL is not "
"supported by the driver."));
@ -561,7 +573,7 @@ ShaderInfo ShaderSource::generateShader(const std::string &name,
// Create shaders header
bool fully_programmable = driver->getDriverType() == video::EDT_OGLES2 || driver->getDriverType() == video::EDT_OPENGL3;
std::stringstream shaders_header;
std::ostringstream shaders_header;
shaders_header
<< std::noboolalpha
<< std::showpoint // for GLSL ES
@ -588,10 +600,14 @@ ShaderInfo ShaderSource::generateShader(const std::string &name,
attribute mediump vec4 inVertexTangent;
attribute mediump vec4 inVertexBinormal;
)";
// Our vertex color has components reversed compared to what OpenGL
// normally expects, so we need to take that into account.
vertex_header += "#define inVertexColor (inVertexColor.bgra)\n";
fragment_header = R"(
precision mediump float;
)";
} else {
/* legacy OpenGL driver */
shaders_header << R"(
#version 120
#define lowp

View file

@ -137,8 +137,8 @@ void DirectionalLight::update_frustum(const Camera *cam, Client *client, bool fo
// when camera offset changes, adjust the current frustum view matrix to avoid flicker
v3s16 cam_offset = cam->getOffset();
if (cam_offset != shadow_frustum.camera_offset) {
v3f rotated_offset;
shadow_frustum.ViewMat.rotateVect(rotated_offset, intToFloat(cam_offset - shadow_frustum.camera_offset, BS));
v3f rotated_offset = shadow_frustum.ViewMat.rotateAndScaleVect(
intToFloat(cam_offset - shadow_frustum.camera_offset, BS));
shadow_frustum.ViewMat.setTranslation(shadow_frustum.ViewMat.getTranslation() + rotated_offset);
shadow_frustum.player += intToFloat(shadow_frustum.camera_offset - cam->getOffset(), BS);
shadow_frustum.camera_offset = cam_offset;

View file

@ -838,14 +838,10 @@ void Sky::updateStars()
);
core::CMatrix4<f32> a;
a.buildRotateFromTo(v3f(0, 1, 0), r);
v3f p = v3f(-d, 1, -d);
v3f p1 = v3f(d, 1, -d);
v3f p2 = v3f(d, 1, d);
v3f p3 = v3f(-d, 1, d);
a.rotateVect(p);
a.rotateVect(p1);
a.rotateVect(p2);
a.rotateVect(p3);
v3f p = a.rotateAndScaleVect(v3f(-d, 1, -d));
v3f p1 = a.rotateAndScaleVect(v3f(d, 1, -d));
v3f p2 = a.rotateAndScaleVect(v3f(d, 1, d));
v3f p3 = a.rotateAndScaleVect(v3f(-d, 1, d));
vertices.push_back(video::S3DVertex(p, {}, {}, {}));
vertices.push_back(video::S3DVertex(p1, {}, {}, {}));
vertices.push_back(video::S3DVertex(p2, {}, {}, {}));

View file

@ -26,6 +26,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <cassert>
#include <cstring> // memcpy
#include <memory>
namespace sound {

View file

@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#pragma once
#include <memory>
#include "al_helpers.h"
namespace sound {

View file

@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <sstream>
#include <unordered_set>
#include <algorithm>
#include <queue>
#include "gamedef.h"
#include "inventory.h"
#include "util/serialize.h"

View file

@ -25,6 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "porting.h"
#include "mapgen/mapgen.h" // Mapgen::setDefaultSettings
#include "util/string.h"
#include "server.h"
/*
@ -97,7 +98,20 @@ void set_default_settings()
// Client
settings->setDefault("address", "");
settings->setDefault("enable_sound", "true");
#if defined(__unix__) && !defined(__APPLE__) && !defined (__ANDROID__)
// On Linux+X11 (not Linux+Wayland or Linux+XWayland), I've encountered a bug
// where fake mouse events were generated from touch events if in relative
// mouse mode, resulting in the touchscreen controls being instantly disabled
// again and thus making them unusable.
// => We can't switch based on the last input method used.
// => Fall back to hardware detection.
settings->setDefault("touch_controls", bool_to_cstr(has_touch));
#else
settings->setDefault("touch_controls", "auto");
#endif
// Since GUI scaling shouldn't suddenly change during a session, we use
// hardware detection for "touch_gui" instead of switching based on the last
// input method used.
settings->setDefault("touch_gui", bool_to_cstr(has_touch));
settings->setDefault("sound_volume", "0.8");
settings->setDefault("sound_volume_unfocused", "0.3");
@ -335,9 +349,6 @@ void set_default_settings()
settings->setDefault("antialiasing", "none");
settings->setDefault("enable_bloom", "false");
settings->setDefault("enable_bloom_debug", "false");
settings->setDefault("bloom_strength_factor", "1.0");
settings->setDefault("bloom_intensity", "0.05");
settings->setDefault("bloom_radius", "1");
settings->setDefault("enable_volumetric_lighting", "false");
settings->setDefault("enable_bumpmaps", "false");
settings->setDefault("enable_water_reflections", "false");
@ -454,7 +465,9 @@ void set_default_settings()
settings->setDefault("enable_pvp", "true");
settings->setDefault("enable_mod_channels", "false");
settings->setDefault("disallow_empty_password", "false");
settings->setDefault("disable_anticheat", "false");
settings->setDefault("anticheat_flags", flagdesc_anticheat,
AC_DIGGING | AC_INTERACTION | AC_MOVEMENT);
settings->setDefault("anticheat_movement_tolerance", "1.0");
settings->setDefault("enable_rollback_recording", "false");
settings->setDefault("deprecated_lua_api_handling", "log");

View file

@ -31,6 +31,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "filesys.h"
#include "log.h"
#include "servermap.h"
#include "database/database.h"
#include "mapblock.h"
#include "mapgen/mg_biome.h"
#include "mapgen/mg_ore.h"
@ -185,10 +186,22 @@ SchematicManager *EmergeManager::getWritableSchematicManager()
return schemmgr;
}
void EmergeManager::initMap(MapDatabaseAccessor *holder)
{
FATAL_ERROR_IF(m_db, "Map database already initialized.");
assert(holder->dbase);
m_db = holder;
}
void EmergeManager::resetMap()
{
FATAL_ERROR_IF(m_threads_active, "Threads are still active.");
m_db = nullptr;
}
void EmergeManager::initMapgens(MapgenParams *params)
{
FATAL_ERROR_IF(!m_mapgens.empty(), "Mapgen already initialised.");
FATAL_ERROR_IF(!m_mapgens.empty(), "Mapgen already initialized.");
mgparams = params;
@ -303,6 +316,12 @@ bool EmergeManager::enqueueBlockEmergeEx(
}
size_t EmergeManager::getQueueSize()
{
MutexAutoLock queuelock(m_queue_mutex);
return m_blocks_enqueued.size();
}
bool EmergeManager::isBlockInQueue(v3s16 pos)
{
MutexAutoLock queuelock(m_queue_mutex);
@ -466,7 +485,7 @@ void EmergeThread::signal()
}
bool EmergeThread::pushBlock(const v3s16 &pos)
bool EmergeThread::pushBlock(v3s16 pos)
{
m_block_queue.push(pos);
return true;
@ -491,7 +510,7 @@ void EmergeThread::cancelPendingItems()
}
void EmergeThread::runCompletionCallbacks(const v3s16 &pos, EmergeAction action,
void EmergeThread::runCompletionCallbacks(v3s16 pos, EmergeAction action,
const EmergeCallbackList &callbacks)
{
m_emerge->reportCompletedEmerge(action);
@ -524,21 +543,38 @@ bool EmergeThread::popBlockEmerge(v3s16 *pos, BlockEmergeData *bedata)
}
EmergeAction EmergeThread::getBlockOrStartGen(
const v3s16 &pos, bool allow_gen, MapBlock **block, BlockMakeData *bmdata)
EmergeAction EmergeThread::getBlockOrStartGen(const v3s16 pos, bool allow_gen,
const std::string *from_db, MapBlock **block, BlockMakeData *bmdata)
{
MutexAutoLock envlock(m_server->m_env_mutex);
//TimeTaker tt("", nullptr, PRECISION_MICRO);
Server::EnvAutoLock envlock(m_server);
//g_profiler->avg("EmergeThread: lock wait time [us]", tt.stop());
auto block_ok = [] (MapBlock *b) {
return b && b->isGenerated();
};
// 1). Attempt to fetch block from memory
*block = m_map->getBlockNoCreateNoEx(pos);
if (*block) {
if ((*block)->isGenerated())
if (block_ok(*block)) {
// if we just read it from the db but the block exists that means
// someone else was faster. don't touch it to prevent data loss.
if (from_db)
verbosestream << "getBlockOrStartGen: block loading raced" << std::endl;
return EMERGE_FROM_MEMORY;
}
} else {
// 2). Attempt to load block from disk if it was not in the memory
*block = m_map->loadBlock(pos);
if (*block && (*block)->isGenerated())
if (!from_db) {
// 2). We should attempt loading it
return EMERGE_FROM_DISK;
}
// 2). Second invocation, we have the data
if (!from_db->empty()) {
*block = m_map->loadBlock(*from_db, pos);
if (block_ok(*block))
return EMERGE_FROM_DISK;
}
}
// 3). Attempt to start generation
@ -553,7 +589,7 @@ EmergeAction EmergeThread::getBlockOrStartGen(
MapBlock *EmergeThread::finishGen(v3s16 pos, BlockMakeData *bmdata,
std::map<v3s16, MapBlock *> *modified_blocks)
{
MutexAutoLock envlock(m_server->m_env_mutex);
Server::EnvAutoLock envlock(m_server);
ScopeProfiler sp(g_profiler,
"EmergeThread: after Mapgen::makeChunk", SPT_AVG);
@ -643,7 +679,8 @@ void *EmergeThread::run()
BEGIN_DEBUG_EXCEPTION_HANDLER
v3s16 pos;
std::map<v3s16, MapBlock *> modified_blocks;
std::map<v3s16, MapBlock*> modified_blocks;
std::string databuf;
m_map = &m_server->m_env->getServerMap();
m_emerge = m_server->getEmergeManager();
@ -669,13 +706,30 @@ void *EmergeThread::run()
continue;
}
g_profiler->add(m_name + ": processed [#]", 1);
if (blockpos_over_max_limit(pos))
continue;
bool allow_gen = bedata.flags & BLOCK_EMERGE_ALLOW_GEN;
EMERGE_DBG_OUT("pos=" << pos << " allow_gen=" << allow_gen);
action = getBlockOrStartGen(pos, allow_gen, &block, &bmdata);
action = getBlockOrStartGen(pos, allow_gen, nullptr, &block, &bmdata);
/* Try to load it */
if (action == EMERGE_FROM_DISK) {
auto &m_db = *m_emerge->m_db;
{
ScopeProfiler sp(g_profiler, "EmergeThread: load block - async (sum)");
MutexAutoLock dblock(m_db.mutex);
m_db.loadBlock(pos, databuf);
}
// actually load it, then decide again
action = getBlockOrStartGen(pos, allow_gen, &databuf, &block, &bmdata);
databuf.clear();
}
/* Generate it */
if (action == EMERGE_GENERATED) {
bool error = false;
m_trans_liquid = &bmdata.transforming_liquid;
@ -716,7 +770,7 @@ void *EmergeThread::run()
MapEditEvent event;
event.type = MEET_OTHER;
event.setModifiedBlocks(modified_blocks);
MutexAutoLock envlock(m_server->m_env_mutex);
Server::EnvAutoLock envlock(m_server);
m_map->dispatchEvent(event);
}
modified_blocks.clear();

View file

@ -46,6 +46,7 @@ class DecorationManager;
class SchematicManager;
class Server;
class ModApiMapgen;
struct MapDatabaseAccessor;
// Structure containing inputs/outputs for chunk generation
struct BlockMakeData {
@ -173,6 +174,10 @@ public:
SchematicManager *getWritableSchematicManager();
void initMapgens(MapgenParams *mgparams);
/// @param holder non-owned reference that must stay alive
void initMap(MapDatabaseAccessor *holder);
/// resets the reference
void resetMap();
void startThreads();
void stopThreads();
@ -191,6 +196,7 @@ public:
EmergeCompletionCallback callback,
void *callback_param);
size_t getQueueSize();
bool isBlockInQueue(v3s16 pos);
Mapgen *getCurrentMapgen();
@ -206,6 +212,9 @@ private:
std::vector<EmergeThread *> m_threads;
bool m_threads_active = false;
// The map database
MapDatabaseAccessor *m_db = nullptr;
std::mutex m_queue_mutex;
std::map<v3s16, BlockEmergeData> m_blocks_enqueued;
std::unordered_map<u16, u32> m_peer_queue_count;

View file

@ -40,7 +40,7 @@ class EmergeScripting;
class EmergeThread : public Thread {
public:
bool enable_mapgen_debug_info;
int id;
const int id; // Index of this thread
EmergeThread(Server *server, int ethreadid);
~EmergeThread() = default;
@ -49,7 +49,7 @@ public:
void signal();
// Requires queue mutex held
bool pushBlock(const v3s16 &pos);
bool pushBlock(v3s16 pos);
void cancelPendingItems();
@ -59,7 +59,7 @@ public:
protected:
void runCompletionCallbacks(
const v3s16 &pos, EmergeAction action,
v3s16 pos, EmergeAction action,
const EmergeCallbackList &callbacks);
private:
@ -79,8 +79,20 @@ private:
bool popBlockEmerge(v3s16 *pos, BlockEmergeData *bedata);
EmergeAction getBlockOrStartGen(
const v3s16 &pos, bool allow_gen, MapBlock **block, BlockMakeData *data);
/**
* Try to get a block from memory and decide what to do.
*
* @param pos block position
* @param from_db serialized block data, optional
* (for second call after EMERGE_FROM_DISK was returned)
* @param allow_gen allow invoking mapgen?
* @param block output pointer for block
* @param data info for mapgen
* @return what to do for this block
*/
EmergeAction getBlockOrStartGen(v3s16 pos, bool allow_gen,
const std::string *from_db, MapBlock **block, BlockMakeData *data);
MapBlock *finishGen(v3s16 pos, BlockMakeData *bmdata,
std::map<v3s16, MapBlock *> *modified_blocks);

View file

@ -26,6 +26,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <cerrno>
#include <fstream>
#include <atomic>
#include <memory>
#include "log.h"
#include "config.h"
#include "porting.h"
@ -34,6 +35,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <IFileArchive.h>
#include <IFileSystem.h>
#endif
#ifdef __linux__
#include <fcntl.h>
#include <sys/ioctl.h>
@ -42,6 +44,19 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#endif
#endif
#ifdef _WIN32
#include <windows.h>
#include <shlwapi.h>
#include <io.h>
#include <direct.h>
#else
#include <sys/types.h>
#include <dirent.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#endif
// Error from last OS call as string
#ifdef _WIN32
#define LAST_OS_ERROR() porting::ConvertError(GetLastError())
@ -58,11 +73,6 @@ namespace fs
* Windows *
***********/
#include <windows.h>
#include <shlwapi.h>
#include <io.h>
#include <direct.h>
std::vector<DirListNode> GetDirListing(const std::string &pathstring)
{
std::vector<DirListNode> listing;
@ -272,12 +282,6 @@ bool CopyFileContents(const std::string &source, const std::string &target)
* POSIX *
*********/
#include <sys/types.h>
#include <dirent.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
std::vector<DirListNode> GetDirListing(const std::string &pathstring)
{
std::vector<DirListNode> listing;
@ -380,41 +384,41 @@ bool RecursiveDelete(const std::string &path)
Execute the 'rm' command directly, by fork() and execve()
*/
infostream<<"Removing \""<<path<<"\""<<std::endl;
infostream << "Removing \"" << path << "\"" << std::endl;
pid_t child_pid = fork();
assert(IsPathAbsolute(path));
if(child_pid == 0)
{
const pid_t child_pid = fork();
if (child_pid == -1) {
errorstream << "fork errno: " << errno << ": " << strerror(errno)
<< std::endl;
return false;
}
if (child_pid == 0) {
// Child
const char *argv[4] = {
#ifdef __ANDROID__
"/system/bin/rm",
#else
"/bin/rm",
#endif
std::array<const char*, 4> argv = {
"rm",
"-rf",
path.c_str(),
NULL
nullptr
};
verbosestream<<"Executing '"<<argv[0]<<"' '"<<argv[1]<<"' '"
<<argv[2]<<"'"<<std::endl;
execvp(argv[0], const_cast<char**>(argv.data()));
execv(argv[0], const_cast<char**>(argv));
// Execv shouldn't return. Failed.
// note: use cerr because our logging won't flush in forked process
std::cerr << "exec errno: " << errno << ": " << strerror(errno)
<< std::endl;
_exit(1);
}
else
{
} else {
// Parent
int child_status;
int status;
pid_t tpid;
do{
tpid = wait(&child_status);
}while(tpid != child_pid);
return (child_status == 0);
do
tpid = waitpid(child_pid, &status, 0);
while (tpid != child_pid);
return WIFEXITED(status) && WEXITSTATUS(status) == 0;
}
}

View file

@ -34,19 +34,19 @@ class Camera;
class ModChannel;
class ModStorage;
class ModStorageDatabase;
struct SubgameSpec;
struct ModSpec;
struct ModIPCStore;
namespace irr::scene {
class IAnimatedMesh;
class ISceneManager;
}
struct SubgameSpec;
struct ModSpec;
/*
An interface for fetching game-global definitions like tool and
mapnode properties
*/
class IGameDef
{
public:
@ -63,6 +63,9 @@ public:
// environment thread.
virtual IRollbackManager* getRollbackManager() { return NULL; }
// Only usable on server.
virtual ModIPCStore *getModIPCStore() { return nullptr; }
// Shorthands
// TODO: these should be made const-safe so that a const IGameDef* is
// actually usable

View file

@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "client/renderingengine.h"
#include "client/shader.h"
#include "client/tile.h"
#include "clientdynamicinfo.h"
#include "config.h"
#include "content/content.h"
#include "content/mods.h"
@ -316,6 +317,7 @@ void GUIEngine::run()
);
const bool initial_window_maximized = !g_settings->getBool("fullscreen") &&
g_settings->getBool("window_maximized");
auto last_window_info = ClientDynamicInfo::getCurrent();
FpsControl fps_control;
f32 dtime = 0.0f;
@ -335,6 +337,11 @@ void GUIEngine::run()
updateTopLeftTextSize();
text_height = g_fontengine->getTextHeight();
}
auto window_info = ClientDynamicInfo::getCurrent();
if (!window_info.equal(last_window_info)) {
m_script->handleMainMenuEvent("WindowInfoChange");
last_window_info = window_info;
}
driver->beginScene(true, true, RenderingEngine::MENU_SKY_COLOR);

View file

@ -356,7 +356,7 @@ void GUIFormSpecMenu::parseContainerEnd(parserData* data, const std::string &)
void GUIFormSpecMenu::parseScrollContainer(parserData *data, const std::string &element)
{
std::vector<std::string> parts;
if (!precheckElement("scroll_container start", element, 4, 5, parts))
if (!precheckElement("scroll_container start", element, 4, 6, parts))
return;
std::vector<std::string> v_pos = split(parts[0], ',');
@ -367,6 +367,12 @@ void GUIFormSpecMenu::parseScrollContainer(parserData *data, const std::string &
if (parts.size() >= 5 && !parts[4].empty())
scroll_factor = stof(parts[4]);
std::optional<s32> content_padding_px;
if (parts.size() >= 6 && !parts[5].empty()) {
std::vector<std::string> v_size = { parts[5], parts[5] };
content_padding_px = getRealCoordinateGeometry(v_size)[orientation == "vertical" ? 1 : 0];
}
MY_CHECKPOS("scroll_container", 0);
MY_CHECKGEOM("scroll_container", 1);
@ -405,6 +411,7 @@ void GUIFormSpecMenu::parseScrollContainer(parserData *data, const std::string &
GUIScrollContainer *mover = new GUIScrollContainer(Environment,
clipper, spec_mover.fid, rect_mover, orientation, scroll_factor);
mover->setContentPadding(content_padding_px);
data->current_parent = mover;
@ -3608,7 +3615,7 @@ void GUIFormSpecMenu::showTooltip(const std::wstring &text,
int tooltip_offset_x = m_btn_height;
int tooltip_offset_y = m_btn_height;
if (m_pointer_type == PointerType::Touch) {
if (RenderingEngine::getLastPointerType() == PointerType::Touch) {
tooltip_offset_x *= 3;
tooltip_offset_y = 0;
if (m_pointer.X > (s32)screenSize.X / 2)

View file

@ -1146,7 +1146,7 @@ bool GUIHyperText::OnEvent(const SEvent &event)
}
}
break;
return true;
}
}
}

View file

@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "guiFormSpecMenu.h"
#include "client/hud.h"
#include "client/client.h"
#include "client/renderingengine.h"
#include <IVideoDriver.h>
GUIInventoryList::GUIInventoryList(gui::IGUIEnvironment *env,
@ -154,7 +155,7 @@ void GUIInventoryList::draw()
// Add hovering tooltip
bool show_tooltip = !item.empty() && hovering && !selected_item;
// Make it possible to see item tooltips on touchscreens
if (m_fs_menu->getPointerType() == PointerType::Touch) {
if (RenderingEngine::getLastPointerType() == PointerType::Touch) {
show_tooltip |= hovering && selected && m_fs_menu->getSelectedAmount() != 0;
}
if (show_tooltip) {

View file

@ -157,7 +157,7 @@ void GUIScene::setStyles(const std::array<StyleSpec, StyleSpec::NUM_STATES> &sty
/**
* Sets the frame loop range for the mesh
*/
void GUIScene::setFrameLoop(s32 begin, s32 end)
void GUIScene::setFrameLoop(f32 begin, f32 end)
{
if (m_mesh->getStartFrame() != begin || m_mesh->getEndFrame() != end)
m_mesh->setFrameLoop(begin, end);
@ -225,8 +225,7 @@ void GUIScene::setCameraRotation(v3f rot)
core::matrix4 mat;
mat.setRotationDegrees(rot);
m_cam_pos = v3f(0.f, 0.f, m_cam_distance);
mat.rotateVect(m_cam_pos);
m_cam_pos = mat.rotateAndScaleVect(v3f(0.f, 0.f, m_cam_distance));
m_cam_pos += m_target_pos;
m_cam->setPosition(m_cam_pos);

View file

@ -36,7 +36,7 @@ public:
scene::IAnimatedMeshSceneNode *setMesh(scene::IAnimatedMesh *mesh = nullptr);
void setTexture(u32 idx, video::ITexture *texture);
void setBackgroundColor(const video::SColor &color) noexcept { m_bgcolor = color; };
void setFrameLoop(s32 begin, s32 end);
void setFrameLoop(f32 begin, f32 end);
void setAnimationSpeed(f32 speed);
void enableMouseControl(bool enable) noexcept { m_mouse_ctrl = enable; };
void setRotation(v2f rot) noexcept { m_custom_rot = rot; };

View file

@ -45,6 +45,7 @@ public:
s32 getSmallStep() const { return small_step; }
s32 getPos() const;
s32 getTargetPos() const;
bool isHorizontal() const { return is_horizontal; }
void setMax(const s32 &max);
void setMin(const s32 &min);

View file

@ -67,6 +67,50 @@ void GUIScrollContainer::draw()
}
}
void GUIScrollContainer::setScrollBar(GUIScrollBar *scrollbar)
{
m_scrollbar = scrollbar;
if (m_scrollbar && m_content_padding_px.has_value() && m_scrollfactor != 0.0f) {
// Set the scrollbar max value based on the content size.
// Get content size based on elements
core::rect<s32> size;
for (gui::IGUIElement *e : Children) {
core::rect<s32> abs_rect = e->getAbsolutePosition();
size.addInternalPoint(abs_rect.LowerRightCorner);
}
s32 visible_content_px = (
m_orientation == VERTICAL
? AbsoluteClippingRect.getHeight()
: AbsoluteClippingRect.getWidth()
);
s32 total_content_px = *m_content_padding_px + (
m_orientation == VERTICAL
? (size.LowerRightCorner.Y - AbsoluteClippingRect.UpperLeftCorner.Y)
: (size.LowerRightCorner.X - AbsoluteClippingRect.UpperLeftCorner.X)
);
s32 hidden_content_px = std::max<s32>(0, total_content_px - visible_content_px);
m_scrollbar->setMin(0);
m_scrollbar->setMax(std::ceil(hidden_content_px / std::fabs(m_scrollfactor)));
// Note: generally, the scrollbar has the same size as the scroll container.
// However, in case it isn't, proportional adjustments are needed.
s32 scrollbar_px = (
m_scrollbar->isHorizontal()
? m_scrollbar->getRelativePosition().getWidth()
: m_scrollbar->getRelativePosition().getHeight()
);
m_scrollbar->setPageSize((total_content_px * scrollbar_px) / visible_content_px);
}
updateScrolling();
}
void GUIScrollContainer::updateScrolling()
{
s32 pos = m_scrollbar->getPos();

View file

@ -34,17 +34,18 @@ public:
virtual void draw() override;
inline void setContentPadding(std::optional<s32> padding)
{
m_content_padding_px = padding;
}
inline void onScrollEvent(gui::IGUIElement *caller)
{
if (caller == m_scrollbar)
updateScrolling();
}
inline void setScrollBar(GUIScrollBar *scrollbar)
{
m_scrollbar = scrollbar;
updateScrolling();
}
void setScrollBar(GUIScrollBar *scrollbar);
private:
enum OrientationEnum
@ -56,7 +57,8 @@ private:
GUIScrollBar *m_scrollbar;
OrientationEnum m_orientation;
f32 m_scrollfactor;
f32 m_scrollfactor; //< scrollbar pos * scrollfactor = scroll offset in pixels
std::optional<s32> m_content_padding_px; //< in pixels
void updateScrolling();
};

View file

@ -187,6 +187,7 @@ bool GUIModalMenu::simulateMouseEvent(ETOUCH_INPUT_EVENT touch_event, bool secon
mouse_event.EventType = EET_MOUSE_INPUT_EVENT;
mouse_event.MouseInput.X = m_pointer.X;
mouse_event.MouseInput.Y = m_pointer.Y;
mouse_event.MouseInput.Simulated = true;
switch (touch_event) {
case ETIE_PRESSED_DOWN:
mouse_event.MouseInput.Event = EMIE_LMOUSE_PRESSED_DOWN;
@ -210,7 +211,6 @@ bool GUIModalMenu::simulateMouseEvent(ETOUCH_INPUT_EVENT touch_event, bool secon
}
bool retval;
m_simulated_mouse = true;
do {
if (preprocessEvent(mouse_event)) {
retval = true;
@ -222,7 +222,6 @@ bool GUIModalMenu::simulateMouseEvent(ETOUCH_INPUT_EVENT touch_event, bool secon
}
retval = target->OnEvent(mouse_event);
} while (false);
m_simulated_mouse = false;
if (!retval && !second_try)
return simulateMouseEvent(touch_event, true);
@ -330,7 +329,6 @@ bool GUIModalMenu::preprocessEvent(const SEvent &event)
holder.grab(this); // keep this alive until return (it might be dropped downstream [?])
if (event.TouchInput.touchedCount == 1) {
m_pointer_type = PointerType::Touch;
m_pointer = v2s32(event.TouchInput.X, event.TouchInput.Y);
gui::IGUIElement *hovered = Environment->getRootGUIElement()->getElementFromPoint(core::position2d<s32>(m_pointer));
@ -373,9 +371,8 @@ bool GUIModalMenu::preprocessEvent(const SEvent &event)
}
if (event.EventType == EET_MOUSE_INPUT_EVENT) {
if (!m_simulated_mouse) {
// Only set the pointer type to mouse if this is a real mouse event.
m_pointer_type = PointerType::Mouse;
if (!event.MouseInput.Simulated) {
// Only process if this is a real mouse event.
m_pointer = v2s32(event.MouseInput.X, event.MouseInput.Y);
m_touch_hovered.reset();
}

View file

@ -26,11 +26,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <porting_android.h>
#endif
enum class PointerType {
Mouse,
Touch,
};
struct PointerAction {
v2s32 pos;
u64 time; // ms
@ -74,14 +69,10 @@ public:
porting::AndroidDialogState getAndroidUIInputState();
#endif
PointerType getPointerType() { return m_pointer_type; };
protected:
virtual std::wstring getLabelByID(s32 id) = 0;
virtual std::string getNameByID(s32 id) = 0;
// Stores the last known pointer type.
PointerType m_pointer_type = PointerType::Mouse;
// Stores the last known pointer position.
// If the last input event was a mouse event, it's the cursor position.
// If the last input event was a touch event, it's the finger position.
@ -102,9 +93,6 @@ protected:
// This is set to true if the menu is currently processing a second-touch event.
bool m_second_touch = false;
// This is set to true if the menu is currently processing a mouse event
// that was synthesized by the menu itself from a touch event.
bool m_simulated_mouse = false;
private:
IMenuManager *m_menumgr;

View file

@ -418,6 +418,11 @@ TouchControls::TouchControls(IrrlichtDevice *device, ISimpleTextureSource *tsrc)
m_status_text->setVisible(false);
}
TouchControls::~TouchControls()
{
releaseAll();
}
void TouchControls::addButton(std::vector<button_info> &buttons, touch_gui_button_id id,
const std::string &image, const recti &rect, bool visible)
{
@ -843,6 +848,7 @@ void TouchControls::emitMouseEvent(EMOUSE_INPUT_EVENT type)
event.MouseInput.Control = false;
event.MouseInput.ButtonStates = 0;
event.MouseInput.Event = type;
event.MouseInput.Simulated = true;
m_receiver->OnEvent(event);
}

View file

@ -33,6 +33,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "itemdef.h"
#include "client/game.h"
#include "util/basic_macros.h"
namespace irr
{
@ -136,6 +137,8 @@ class TouchControls
{
public:
TouchControls(IrrlichtDevice *device, ISimpleTextureSource *tsrc);
~TouchControls();
DISABLE_CLASS_COPY(TouchControls);
void translateEvent(const SEvent &event);
void applyContextControls(const TouchInteractionMode &mode);
@ -163,8 +166,8 @@ public:
*/
line3d<f32> getShootline() { return m_shootline; }
float getMovementDirection() { return m_joystick_direction; }
float getMovementSpeed() { return m_joystick_speed; }
float getJoystickDirection() { return m_joystick_direction; }
float getJoystickSpeed() { return m_joystick_speed; }
void step(float dtime);
inline void setUseCrosshair(bool use_crosshair) { m_draw_crosshair = use_crosshair; }

View file

@ -89,11 +89,11 @@ void ItemStackMetadata::deSerialize(std::istream &is)
while (!fnd.at_end()) {
std::string name = fnd.next(DESERIALIZE_KV_DELIM_STR);
std::string var = fnd.next(DESERIALIZE_PAIR_DELIM_STR);
m_stringvars[name] = var;
m_stringvars[name] = std::move(var);
}
} else {
// BACKWARDS COMPATIBILITY
m_stringvars[""] = in;
m_stringvars[""] = std::move(in);
}
}
updateToolCapabilities();

View file

@ -57,5 +57,8 @@ struct Lighting
float saturation {1.0f};
float volumetric_light_strength {0.0f};
video::SColor artificial_light_color{ 255, 133, 133, 133 };
video::SColor shadow_tint;
video::SColor shadow_tint {255, 0, 0, 0};
float bloom_intensity {0.05f};
float bloom_strength_factor {1.0f};
float bloom_radius {1.0f};
};

View file

@ -17,7 +17,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "log.h"
#include "log_internal.h"
#include "threading/mutex_auto_lock.h"
#include "debug.h"
@ -27,7 +27,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "config.h"
#include "exceptions.h"
#include "util/numeric.h"
#include "log.h"
#include "filesys.h"
#ifdef __ANDROID__

201
src/log.h
View file

@ -1,198 +1,9 @@
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
// SPDX-License-Identifier: LGPL-2.1-or-later
#pragma once
#include <atomic>
#include <map>
#include <queue>
#include <string_view>
#include <fstream>
#include <thread>
#include <mutex>
#include "threading/mutex_auto_lock.h"
#include "util/basic_macros.h"
#include "util/stream.h"
#include "irrlichttypes.h"
class ILogOutput;
enum LogLevel {
LL_NONE, // Special level that is always printed
LL_ERROR,
LL_WARNING,
LL_ACTION, // In-game actions
LL_INFO,
LL_VERBOSE,
LL_TRACE,
LL_MAX,
};
enum LogColor {
LOG_COLOR_NEVER,
LOG_COLOR_ALWAYS,
LOG_COLOR_AUTO,
};
typedef u8 LogLevelMask;
#define LOGLEVEL_TO_MASKLEVEL(x) (1 << x)
class Logger {
public:
void addOutput(ILogOutput *out);
void addOutput(ILogOutput *out, LogLevel lev);
void addOutputMasked(ILogOutput *out, LogLevelMask mask);
void addOutputMaxLevel(ILogOutput *out, LogLevel lev);
LogLevelMask removeOutput(ILogOutput *out);
void setLevelSilenced(LogLevel lev, bool silenced);
void registerThread(std::string_view name);
void deregisterThread();
void log(LogLevel lev, std::string_view text);
// Logs without a prefix
void logRaw(LogLevel lev, std::string_view text);
static LogLevel stringToLevel(std::string_view name);
static const char *getLevelLabel(LogLevel lev);
bool hasOutput(LogLevel level) {
return m_has_outputs[level].load(std::memory_order_relaxed);
}
bool isLevelSilenced(LogLevel level) {
return m_silenced_levels[level].load(std::memory_order_relaxed);
}
static LogColor color_mode;
private:
void logToOutputsRaw(LogLevel, std::string_view line);
void logToOutputs(LogLevel, const std::string &combined,
const std::string &time, const std::string &thread_name,
std::string_view payload_text);
const std::string &getThreadName();
std::vector<ILogOutput *> m_outputs[LL_MAX];
std::atomic<bool> m_has_outputs[LL_MAX];
std::atomic<bool> m_silenced_levels[LL_MAX];
std::map<std::thread::id, std::string> m_thread_names;
mutable std::mutex m_mutex;
};
class ILogOutput {
public:
virtual void logRaw(LogLevel, std::string_view line) = 0;
virtual void log(LogLevel, const std::string &combined,
const std::string &time, const std::string &thread_name,
std::string_view payload_text) = 0;
};
class ICombinedLogOutput : public ILogOutput {
public:
void log(LogLevel lev, const std::string &combined,
const std::string &time, const std::string &thread_name,
std::string_view payload_text)
{
logRaw(lev, combined);
}
};
class StreamLogOutput : public ICombinedLogOutput {
public:
StreamLogOutput(std::ostream &stream);
void logRaw(LogLevel lev, std::string_view line);
private:
std::ostream &m_stream;
bool is_tty = false;
};
class FileLogOutput : public ICombinedLogOutput {
public:
void setFile(const std::string &filename, s64 file_size_max);
void logRaw(LogLevel lev, std::string_view line)
{
m_stream << line << std::endl;
}
private:
std::ofstream m_stream;
};
class LogOutputBuffer : public ICombinedLogOutput {
public:
LogOutputBuffer(Logger &logger) :
m_logger(logger)
{
updateLogLevel();
};
virtual ~LogOutputBuffer()
{
m_logger.removeOutput(this);
}
void updateLogLevel();
void logRaw(LogLevel lev, std::string_view line);
void clear()
{
MutexAutoLock lock(m_buffer_mutex);
m_buffer = std::queue<std::string>();
}
bool empty() const
{
MutexAutoLock lock(m_buffer_mutex);
return m_buffer.empty();
}
std::string get()
{
MutexAutoLock lock(m_buffer_mutex);
if (m_buffer.empty())
return "";
std::string s = std::move(m_buffer.front());
m_buffer.pop();
return s;
}
private:
// g_logger serializes calls to logRaw() with a mutex, but that
// doesn't prevent get() / clear() from being called on top of it.
// This mutex prevents that.
mutable std::mutex m_buffer_mutex;
std::queue<std::string> m_buffer;
Logger &m_logger;
};
#ifdef __ANDROID__
class AndroidLogOutput : public ICombinedLogOutput {
public:
void logRaw(LogLevel lev, std::string_view line);
};
#endif
/*
* LogTarget
@ -325,16 +136,6 @@ private:
};
#ifdef __ANDROID__
extern AndroidLogOutput stdout_output;
extern AndroidLogOutput stderr_output;
#else
extern StreamLogOutput stdout_output;
extern StreamLogOutput stderr_output;
#endif
extern Logger g_logger;
/*
* By making the streams thread_local, each thread has its own
* private buffer. Two or more threads can write to the same stream

189
src/log_internal.h Normal file
View file

@ -0,0 +1,189 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
#pragma once
#include <atomic>
#include <map>
#include <queue>
#include <string_view>
#include <fstream>
#include <thread>
#include <mutex>
#include "threading/mutex_auto_lock.h"
#include "util/basic_macros.h"
#include "util/stream.h"
#include "irrlichttypes.h"
#include "log.h"
class ILogOutput;
enum LogLevel {
LL_NONE, // Special level that is always printed
LL_ERROR,
LL_WARNING,
LL_ACTION, // In-game actions
LL_INFO,
LL_VERBOSE,
LL_TRACE,
LL_MAX,
};
enum LogColor {
LOG_COLOR_NEVER,
LOG_COLOR_ALWAYS,
LOG_COLOR_AUTO,
};
typedef u8 LogLevelMask;
#define LOGLEVEL_TO_MASKLEVEL(x) (1 << x)
class Logger {
public:
void addOutput(ILogOutput *out);
void addOutput(ILogOutput *out, LogLevel lev);
void addOutputMasked(ILogOutput *out, LogLevelMask mask);
void addOutputMaxLevel(ILogOutput *out, LogLevel lev);
LogLevelMask removeOutput(ILogOutput *out);
void setLevelSilenced(LogLevel lev, bool silenced);
void registerThread(std::string_view name);
void deregisterThread();
void log(LogLevel lev, std::string_view text);
// Logs without a prefix
void logRaw(LogLevel lev, std::string_view text);
static LogLevel stringToLevel(std::string_view name);
static const char *getLevelLabel(LogLevel lev);
bool hasOutput(LogLevel level) {
return m_has_outputs[level].load(std::memory_order_relaxed);
}
bool isLevelSilenced(LogLevel level) {
return m_silenced_levels[level].load(std::memory_order_relaxed);
}
static LogColor color_mode;
private:
void logToOutputsRaw(LogLevel, std::string_view line);
void logToOutputs(LogLevel, const std::string &combined,
const std::string &time, const std::string &thread_name,
std::string_view payload_text);
const std::string &getThreadName();
std::vector<ILogOutput *> m_outputs[LL_MAX];
std::atomic<bool> m_has_outputs[LL_MAX];
std::atomic<bool> m_silenced_levels[LL_MAX];
std::map<std::thread::id, std::string> m_thread_names;
mutable std::mutex m_mutex;
};
class ILogOutput {
public:
virtual void logRaw(LogLevel, std::string_view line) = 0;
virtual void log(LogLevel, const std::string &combined,
const std::string &time, const std::string &thread_name,
std::string_view payload_text) = 0;
};
class ICombinedLogOutput : public ILogOutput {
public:
void log(LogLevel lev, const std::string &combined,
const std::string &time, const std::string &thread_name,
std::string_view payload_text)
{
logRaw(lev, combined);
}
};
class StreamLogOutput : public ICombinedLogOutput {
public:
StreamLogOutput(std::ostream &stream);
void logRaw(LogLevel lev, std::string_view line);
private:
std::ostream &m_stream;
bool is_tty = false;
};
class FileLogOutput : public ICombinedLogOutput {
public:
void setFile(const std::string &filename, s64 file_size_max);
void logRaw(LogLevel lev, std::string_view line)
{
m_stream << line << std::endl;
}
private:
std::ofstream m_stream;
};
class LogOutputBuffer : public ICombinedLogOutput {
public:
LogOutputBuffer(Logger &logger) :
m_logger(logger)
{
updateLogLevel();
};
virtual ~LogOutputBuffer()
{
m_logger.removeOutput(this);
}
void updateLogLevel();
void logRaw(LogLevel lev, std::string_view line);
void clear()
{
MutexAutoLock lock(m_buffer_mutex);
m_buffer = std::queue<std::string>();
}
bool empty() const
{
MutexAutoLock lock(m_buffer_mutex);
return m_buffer.empty();
}
std::string get()
{
MutexAutoLock lock(m_buffer_mutex);
if (m_buffer.empty())
return "";
std::string s = std::move(m_buffer.front());
m_buffer.pop();
return s;
}
private:
// g_logger serializes calls to logRaw() with a mutex, but that
// doesn't prevent get() / clear() from being called on top of it.
// This mutex prevents that.
mutable std::mutex m_buffer_mutex;
std::queue<std::string> m_buffer;
Logger &m_logger;
};
#ifdef __ANDROID__
class AndroidLogOutput : public ICombinedLogOutput {
public:
void logRaw(LogLevel lev, std::string_view line);
};
#endif
#ifdef __ANDROID__
extern AndroidLogOutput stdout_output;
extern AndroidLogOutput stderr_output;
#else
extern StreamLogOutput stdout_output;
extern StreamLogOutput stderr_output;
#endif
extern Logger g_logger;

View file

@ -31,6 +31,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "migratesettings.h"
#include "gettext.h"
#include "log.h"
#include "log_internal.h"
#include "util/quicktune.h"
#include "httpfetch.h"
#include "gameparams.h"
@ -728,7 +729,7 @@ static void startup_message()
print_version(infostream);
infostream << "SER_FMT_VER_HIGHEST_READ=" <<
TOSTRING(SER_FMT_VER_HIGHEST_READ) <<
" LATEST_PROTOCOL_VERSION=" << TOSTRING(LATEST_PROTOCOL_VERSION)
" LATEST_PROTOCOL_VERSION=" << LATEST_PROTOCOL_VERSION
<< std::endl;
}
@ -1278,8 +1279,7 @@ static bool recompress_map_database(const GameParams &game_params, const Setting
{
MapBlock mb(v3s16(0,0,0), &server);
u8 ver = readU8(iss);
mb.deSerialize(iss, ver, true);
ServerMap::deSerializeBlock(&mb, iss);
oss.str("");
oss.clear();

View file

@ -2,6 +2,7 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
#include "settings.h"
#include "server.h"
void migrate_settings()
{
@ -19,4 +20,12 @@ void migrate_settings()
g_settings->setBool("touch_gui", value);
g_settings->remove("enable_touch");
}
// Disables anticheat
if (g_settings->existsLocal("disable_anticheat")) {
if (g_settings->getBool("disable_anticheat")) {
g_settings->setFlagStr("anticheat_flags", 0, flagdesc_anticheat);
}
g_settings->remove("disable_anticheat");
}
}

View file

@ -4,6 +4,7 @@ set(common_network_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/mtp/impl.cpp
${CMAKE_CURRENT_SOURCE_DIR}/mtp/threads.cpp
${CMAKE_CURRENT_SOURCE_DIR}/networkpacket.cpp
${CMAKE_CURRENT_SOURCE_DIR}/networkprotocol.cpp
${CMAKE_CURRENT_SOURCE_DIR}/serveropcodes.cpp
${CMAKE_CURRENT_SOURCE_DIR}/serverpackethandler.cpp
${CMAKE_CURRENT_SOURCE_DIR}/socket.cpp

View file

@ -19,6 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "client/client.h"
#include "irr_v2d.h"
#include "util/base64.h"
#include "client/camera.h"
#include "client/mesh_generator_thread.h"
@ -1516,11 +1517,15 @@ void Client::handleCommand_LocalPlayerAnimations(NetworkPacket* pkt)
LocalPlayer *player = m_env.getLocalPlayer();
assert(player != NULL);
*pkt >> player->local_animations[0];
*pkt >> player->local_animations[1];
*pkt >> player->local_animations[2];
*pkt >> player->local_animations[3];
*pkt >> player->local_animation_speed;
for (int i = 0; i < 4; ++i) {
if (getProtoVersion() >= 46) {
*pkt >> player->local_animations[i];
} else {
v2s32 local_animation;
*pkt >> local_animation;
player->local_animations[i] = v2f::from(local_animation);
}
}
player->last_animation = LocalPlayerAnimation::NO_ANIM;
}
@ -1819,6 +1824,11 @@ void Client::handleCommand_SetLighting(NetworkPacket *pkt)
*pkt >> lighting.volumetric_light_strength;
if (pkt->getRemainingBytes() >= 4)
*pkt >> lighting.shadow_tint;
if (pkt->getRemainingBytes() >= 12) {
*pkt >> lighting.bloom_intensity
>> lighting.bloom_strength_factor
>> lighting.bloom_radius;
if (pkt->getRemainingBytes() >= 4)
*pkt >> lighting.artificial_light_color;
}
}

View file

@ -19,7 +19,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#pragma once
#include "util/pointer.h"
#include "util/pointer.h" // Buffer<T>
#include "irrlichttypes_bloated.h"
#include "networkprotocol.h"
#include <SColor.h>

View file

@ -0,0 +1,67 @@
// Minetest
// SPDX-License-Identifier: LGPL-2.1-or-later
#include "networkprotocol.h"
/*
PROTOCOL VERSION < 37:
Until (and including) version 0.4.17.1
PROTOCOL VERSION 37:
Redo detached inventory sending
Add TOCLIENT_NODEMETA_CHANGED
New network float format
ContentFeatures version 13
Add full Euler rotations instead of just yaw
Add TOCLIENT_PLAYER_SPEED
[bump for 5.0.0]
PROTOCOL VERSION 38:
Incremental inventory sending mode
Unknown inventory serialization fields no longer throw an error
Mod-specific formspec version
Player FOV override API
"ephemeral" added to TOCLIENT_PLAY_SOUND
PROTOCOL VERSION 39:
Updated set_sky packet
Adds new sun, moon and stars packets
Minimap modes
PROTOCOL VERSION 40:
TOCLIENT_MEDIA_PUSH changed, TOSERVER_HAVE_MEDIA added
PROTOCOL VERSION 41:
Added new particlespawner parameters
[scheduled bump for 5.6.0]
PROTOCOL VERSION 42:
TOSERVER_UPDATE_CLIENT_INFO added
new fields for TOCLIENT_SET_LIGHTING and TOCLIENT_SET_SKY
Send forgotten TweenedParameter properties
[scheduled bump for 5.7.0]
PROTOCOL VERSION 43:
"start_time" added to TOCLIENT_PLAY_SOUND
place_param2 type change u8 -> optional<u8>
[scheduled bump for 5.8.0]
PROTOCOL VERSION 44:
AO_CMD_SET_BONE_POSITION extended
Add TOCLIENT_MOVE_PLAYER_REL
Move default minimap from client-side C++ to server-side builtin Lua
[scheduled bump for 5.9.0]
PROTOCOL VERSION 45:
Minimap HUD element supports negative size values as percentages
[bump for 5.9.1]
PROTOCOL VERSION 46:
Move default hotbar from client-side C++ to server-side builtin Lua
Add shadow tint to Lighting packets
Add shadow color to CloudParam packets
Move death screen to server and make it a regular formspec
The server no longer triggers the hardcoded client-side death
formspec, but the client still supports it for compatibility with
old servers.
Rename TOCLIENT_DEATHSCREEN to TOCLIENT_DEATHSCREEN_LEGACY
Rename TOSERVER_RESPAWN to TOSERVER_RESPAWN_LEGACY
Support float animation frame numbers in TOCLIENT_LOCAL_PLAYER_ANIMATIONS
[scheduled bump for 5.10.0]
*/
const u16 LATEST_PROTOCOL_VERSION = 46;
// See also formspec [Version History] in doc/lua_api.md
const u16 FORMSPEC_API_VERSION = 8;

View file

@ -19,243 +19,18 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#pragma once
#include "util/string.h"
#include "irrTypes.h"
using namespace irr;
/*
changes by PROTOCOL_VERSION:
PROTOCOL_VERSION 3:
Base for writing changes here
PROTOCOL_VERSION 4:
Add TOCLIENT_MEDIA
Add TOCLIENT_TOOLDEF
Add TOCLIENT_NODEDEF
Add TOCLIENT_CRAFTITEMDEF
Add TOSERVER_INTERACT
Obsolete TOSERVER_CLICK_ACTIVEOBJECT
Obsolete TOSERVER_GROUND_ACTION
PROTOCOL_VERSION 5:
Make players to be handled mostly as ActiveObjects
PROTOCOL_VERSION 6:
Only non-cached textures are sent
PROTOCOL_VERSION 7:
Add TOCLIENT_ITEMDEF
Obsolete TOCLIENT_TOOLDEF
Obsolete TOCLIENT_CRAFTITEMDEF
Compress the contents of TOCLIENT_ITEMDEF and TOCLIENT_NODEDEF
PROTOCOL_VERSION 8:
Digging based on item groups
Many things
PROTOCOL_VERSION 9:
ContentFeatures and NodeDefManager use a different serialization
format; better for future version cross-compatibility
Many things
Obsolete TOCLIENT_PLAYERITEM
PROTOCOL_VERSION 10:
TOCLIENT_PRIVILEGES
Version raised to force 'fly' and 'fast' privileges into effect.
Node metadata change (came in later; somewhat incompatible)
PROTOCOL_VERSION 11:
TileDef in ContentFeatures
Nodebox drawtype
(some dev snapshot)
TOCLIENT_INVENTORY_FORMSPEC
(0.4.0, 0.4.1)
PROTOCOL_VERSION 12:
TOSERVER_INVENTORY_FIELDS
16-bit node ids
TOCLIENT_DETACHED_INVENTORY
PROTOCOL_VERSION 13:
InventoryList field "Width" (deserialization fails with old versions)
PROTOCOL_VERSION 14:
Added transfer of player pressed keys to the server
Added new messages for mesh and bone animation, as well as attachments
AO_CMD_SET_ANIMATION
AO_CMD_SET_BONE_POSITION
GENERIC_CMD_SET_ATTACHMENT
PROTOCOL_VERSION 15:
Serialization format changes
PROTOCOL_VERSION 16:
TOCLIENT_SHOW_FORMSPEC
PROTOCOL_VERSION 17:
Serialization format change: include backface_culling flag in TileDef
Added rightclickable field in nodedef
TOCLIENT_SPAWN_PARTICLE
TOCLIENT_ADD_PARTICLESPAWNER
TOCLIENT_DELETE_PARTICLESPAWNER
PROTOCOL_VERSION 18:
damageGroups added to ToolCapabilities
sound_place added to ItemDefinition
PROTOCOL_VERSION 19:
AO_CMD_SET_PHYSICS_OVERRIDE
PROTOCOL_VERSION 20:
TOCLIENT_HUDADD
TOCLIENT_HUDRM
TOCLIENT_HUDCHANGE
TOCLIENT_HUD_SET_FLAGS
PROTOCOL_VERSION 21:
TOCLIENT_BREATH
TOSERVER_BREATH
range added to ItemDefinition
drowning, leveled and liquid_range added to ContentFeatures
stepheight and collideWithObjects added to object properties
version, heat and humidity transfer in MapBock
automatic_face_movement_dir and automatic_face_movement_dir_offset
added to object properties
PROTOCOL_VERSION 22:
add swap_node
PROTOCOL_VERSION 23:
Obsolete TOSERVER_RECEIVED_MEDIA
Server: Stop using TOSERVER_CLIENT_READY
PROTOCOL_VERSION 24:
ContentFeatures version 7
ContentFeatures: change number of special tiles to 6 (CF_SPECIAL_COUNT)
PROTOCOL_VERSION 25:
Rename TOCLIENT_ACCESS_DENIED to TOCLIENT_ACCESS_DENIED_LEGAGY
Rename TOCLIENT_DELETE_PARTICLESPAWNER to
TOCLIENT_DELETE_PARTICLESPAWNER_LEGACY
Rename TOSERVER_PASSWORD to TOSERVER_PASSWORD_LEGACY
Rename TOSERVER_INIT to TOSERVER_INIT_LEGACY
Rename TOCLIENT_INIT to TOCLIENT_INIT_LEGACY
Add TOCLIENT_ACCESS_DENIED new opcode (0x0A), using error codes
for standard error, keeping customisation possible. This
permit translation
Add TOCLIENT_DELETE_PARTICLESPAWNER (0x53), fixing the u16 read and
reading u32
Add new opcode TOSERVER_INIT for client presentation to server
Add new opcodes TOSERVER_FIRST_SRP, TOSERVER_SRP_BYTES_A,
TOSERVER_SRP_BYTES_M, TOCLIENT_SRP_BYTES_S_B
for the three supported auth mechanisms around srp
Add new opcodes TOCLIENT_ACCEPT_SUDO_MODE and TOCLIENT_DENY_SUDO_MODE
for sudo mode handling (auth mech generic way of changing password).
Add TOCLIENT_HELLO for presenting server to client after client
presentation
Add TOCLIENT_AUTH_ACCEPT to accept connection from client
Rename GENERIC_CMD_SET_ATTACHMENT to AO_CMD_ATTACH_TO
PROTOCOL_VERSION 26:
Add TileDef tileable_horizontal, tileable_vertical flags
PROTOCOL_VERSION 27:
backface_culling: backwards compatibility for playing with
newer client on pre-27 servers.
Add nodedef v3 - connected nodeboxes
PROTOCOL_VERSION 28:
CPT2_MESHOPTIONS
PROTOCOL_VERSION 29:
Server doesn't accept TOSERVER_BREATH anymore
serialization of TileAnimation params changed
TAT_SHEET_2D
Removed client-sided chat perdiction
PROTOCOL VERSION 30:
New ContentFeatures serialization version
Add node and tile color and palette
Fix plantlike visual_scale being applied squared and add compatibility
with pre-30 clients by sending sqrt(visual_scale)
PROTOCOL VERSION 31:
Add tile overlay
Stop sending TOSERVER_CLIENT_READY
PROTOCOL VERSION 32:
Add fading sounds
PROTOCOL VERSION 33:
Add TOCLIENT_UPDATE_PLAYER_LIST and send the player list to the client,
instead of guessing based on the active object list.
PROTOCOL VERSION 34:
Add sound pitch
PROTOCOL VERSION 35:
Rename TOCLIENT_CHAT_MESSAGE to TOCLIENT_CHAT_MESSAGE_OLD (0x30)
Add TOCLIENT_CHAT_MESSAGE (0x2F)
This chat message is a signalisation message containing various
informations:
* timestamp
* sender
* type (RAW, NORMAL, ANNOUNCE, SYSTEM)
* content
Add TOCLIENT_CSM_RESTRICTION_FLAGS to define which CSM features should be
limited
Add settable player collisionbox. Breaks compatibility with older
clients as a 1-node vertical offset has been removed from player's
position
Add settable player stepheight using existing object property.
Breaks compatibility with older clients.
PROTOCOL VERSION 36:
Backwards compatibility drop
Add 'can_zoom' to player object properties
Add glow to object properties
Change TileDef serialization format.
Add world-aligned tiles.
Mod channels
Raise ObjectProperties version to 3 for removing 'can_zoom' and adding
'zoom_fov'.
Nodebox version 5
Add disconnected nodeboxes
Add TOCLIENT_FORMSPEC_PREPEND
PROTOCOL VERSION 37:
Redo detached inventory sending
Add TOCLIENT_NODEMETA_CHANGED
New network float format
ContentFeatures version 13
Add full Euler rotations instead of just yaw
Add TOCLIENT_PLAYER_SPEED
PROTOCOL VERSION 38:
Incremental inventory sending mode
Unknown inventory serialization fields no longer throw an error
Mod-specific formspec version
Player FOV override API
"ephemeral" added to TOCLIENT_PLAY_SOUND
PROTOCOL VERSION 39:
Updated set_sky packet
Adds new sun, moon and stars packets
Minimap modes
PROTOCOL VERSION 40:
TOCLIENT_MEDIA_PUSH changed, TOSERVER_HAVE_MEDIA added
PROTOCOL VERSION 41:
Added new particlespawner parameters
[scheduled bump for 5.6.0]
PROTOCOL VERSION 42:
TOSERVER_UPDATE_CLIENT_INFO added
new fields for TOCLIENT_SET_LIGHTING and TOCLIENT_SET_SKY
Send forgotten TweenedParameter properties
[scheduled bump for 5.7.0]
PROTOCOL VERSION 43:
"start_time" added to TOCLIENT_PLAY_SOUND
place_param2 type change u8 -> optional<u8>
[scheduled bump for 5.8.0]
PROTOCOL VERSION 44:
AO_CMD_SET_BONE_POSITION extended
Add TOCLIENT_MOVE_PLAYER_REL
Move default minimap from client-side C++ to server-side builtin Lua
[scheduled bump for 5.9.0]
PROTOCOL VERSION 45:
Minimap HUD element supports negative size values as percentages
[bump for 5.9.1]
PROTOCOL VERSION 46:
Move default hotbar from client-side C++ to server-side builtin Lua
Add shadow tint to Lighting packets
Add shadow color to CloudParam packets
Move death screen to server and make it a regular formspec
The server no longer triggers the hardcoded client-side death
formspec, but the client still supports it for compatibility with
old servers.
Rename TOCLIENT_DEATHSCREEN to TOCLIENT_DEATHSCREEN_LEGACY
Rename TOSERVER_RESPAWN to TOSERVER_RESPAWN_LEGACY
[scheduled bump for 5.10.0]
PROTOCOL VERSION 47:
Add artificial light color packet
*/
#define LATEST_PROTOCOL_VERSION 46
#define LATEST_PROTOCOL_VERSION_STRING TOSTRING(LATEST_PROTOCOL_VERSION)
extern const u16 LATEST_PROTOCOL_VERSION;
// Server's supported network protocol range
#define SERVER_PROTOCOL_VERSION_MIN 37
#define SERVER_PROTOCOL_VERSION_MAX LATEST_PROTOCOL_VERSION
constexpr u16 SERVER_PROTOCOL_VERSION_MIN = 37;
// Client's supported network protocol range
#define CLIENT_PROTOCOL_VERSION_MIN 37
#define CLIENT_PROTOCOL_VERSION_MAX LATEST_PROTOCOL_VERSION
constexpr u16 CLIENT_PROTOCOL_VERSION_MIN = 37;
// See also formspec [Version History] in doc/lua_api.md
#define FORMSPEC_API_VERSION 7
extern const u16 FORMSPEC_API_VERSION;
#define TEXTURENAME_ALLOWED_CHARS "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.-"
@ -965,6 +740,8 @@ enum ToServerCommand : u16
[2+12+12+4+4+4] u8 fov*80
[2+12+12+4+4+4+1] u8 ceil(wanted_range / MAP_BLOCKSIZE)
[2+12+12+4+4+4+1+1] u8 camera_inverted (bool)
[2+12+12+4+4+4+1+1+1] f32 movement_speed
[2+12+12+4+4+4+1+1+1+4] f32 movement_direction
*/
@ -1184,4 +961,4 @@ enum InteractAction : u8
INTERACT_PLACE, // 3: place block or item (to abovesurface)
INTERACT_USE, // 4: use item
INTERACT_ACTIVATE // 5: rightclick air ("activate")
};
};

View file

@ -135,10 +135,10 @@ void Server::handleCommand_Init(NetworkPacket* pkt)
// Figure out a working version if it is possible at all
if (max_net_proto_version >= SERVER_PROTOCOL_VERSION_MIN ||
min_net_proto_version <= SERVER_PROTOCOL_VERSION_MAX) {
min_net_proto_version <= LATEST_PROTOCOL_VERSION) {
// If maximum is larger than our maximum, go with our maximum
if (max_net_proto_version > SERVER_PROTOCOL_VERSION_MAX)
net_proto_version = SERVER_PROTOCOL_VERSION_MAX;
if (max_net_proto_version > LATEST_PROTOCOL_VERSION)
net_proto_version = LATEST_PROTOCOL_VERSION;
// Else go with client's maximum
else
net_proto_version = max_net_proto_version;
@ -477,12 +477,24 @@ void Server::process_PlayerPos(RemotePlayer *player, PlayerSAO *playersao,
u8 bits = 0; // bits instead of bool so it is extensible later
*pkt >> keyPressed;
player->control.unpackKeysPressed(keyPressed);
*pkt >> f32fov;
fov = (f32)f32fov / 80.0f;
*pkt >> wanted_range;
if (pkt->getRemainingBytes() >= 1)
*pkt >> bits;
if (pkt->getRemainingBytes() >= 8) {
*pkt >> player->control.movement_speed;
*pkt >> player->control.movement_direction;
} else {
player->control.movement_speed = 0.0f;
player->control.movement_direction = 0.0f;
player->control.setMovementFromKeys();
}
v3f position((f32)ps.X / 100.0f, (f32)ps.Y / 100.0f, (f32)ps.Z / 100.0f);
v3f speed((f32)ss.X / 100.0f, (f32)ss.Y / 100.0f, (f32)ss.Z / 100.0f);
@ -501,8 +513,6 @@ void Server::process_PlayerPos(RemotePlayer *player, PlayerSAO *playersao,
playersao->setWantedRange(wanted_range);
playersao->setCameraInverted(bits & 0x01);
player->control.unpackKeysPressed(keyPressed);
if (playersao->checkMovementCheat()) {
// Call callbacks
m_script->on_cheat(playersao, "moved_too_fast");
@ -1001,12 +1011,12 @@ void Server::handleCommand_Interact(NetworkPacket *pkt)
/*
Check that target is reasonably close
*/
static thread_local const bool enable_anticheat =
!g_settings->getBool("disable_anticheat");
static thread_local const u32 anticheat_flags =
g_settings->getFlagStr("anticheat_flags", flagdesc_anticheat, nullptr);
if ((action == INTERACT_START_DIGGING || action == INTERACT_DIGGING_COMPLETED ||
action == INTERACT_PLACE || action == INTERACT_USE) &&
enable_anticheat && !isSingleplayer()) {
(anticheat_flags & AC_INTERACTION) && !isSingleplayer()) {
v3f target_pos = player_pos;
if (pointed.type == POINTEDTHING_NODE) {
target_pos = intToFloat(pointed.node_undersurface, BS);
@ -1109,7 +1119,7 @@ void Server::handleCommand_Interact(NetworkPacket *pkt)
/* Cheat prevention */
bool is_valid_dig = true;
if (enable_anticheat && !isSingleplayer()) {
if ((anticheat_flags & AC_DIGGING) && !isSingleplayer()) {
v3s16 nocheat_p = playersao->getNoCheatDigPos();
float nocheat_t = playersao->getNoCheatDigTime();
playersao->noCheatDigEnd();

View file

@ -62,14 +62,14 @@ void NodeMetadata::serialize(std::ostream &os, u8 version, bool disk) const
void NodeMetadata::deSerialize(std::istream &is, u8 version)
{
clear();
int num_vars = readU32(is);
for(int i=0; i<num_vars; i++){
u32 num_vars = readU32(is);
for (u32 i = 0; i < num_vars; i++){
std::string name = deSerializeString16(is);
std::string var = deSerializeString32(is);
m_stringvars[name] = var;
m_stringvars[name] = std::move(var);
if (version >= 2) {
if (readU8(is) == 1)
markPrivate(name, true);
m_privatevars.insert(name);
}
}
@ -89,12 +89,12 @@ bool NodeMetadata::empty() const
}
void NodeMetadata::markPrivate(const std::string &name, bool set)
bool NodeMetadata::markPrivate(const std::string &name, bool set)
{
if (set)
m_privatevars.insert(name);
return m_privatevars.insert(name).second;
else
m_privatevars.erase(name);
return m_privatevars.erase(name) > 0;
}
int NodeMetadata::countNonPrivate() const
@ -144,6 +144,8 @@ void NodeMetadataList::serialize(std::ostream &os, u8 blockver, bool disk,
writeS16(os, p.Z);
} else {
// Serialize positions within a mapblock
static_assert(MAP_BLOCKSIZE * MAP_BLOCKSIZE * MAP_BLOCKSIZE <= U16_MAX,
"position too big to serialize");
u16 p16 = (p.Z * MAP_BLOCKSIZE + p.Y) * MAP_BLOCKSIZE + p.X;
writeU16(os, p16);
}
@ -246,8 +248,7 @@ void NodeMetadataList::set(v3s16 p, NodeMetadata *d)
void NodeMetadataList::clear()
{
if (m_is_metadata_owner) {
NodeMetadataMap::const_iterator it;
for (it = m_data.begin(); it != m_data.end(); ++it)
for (auto it = m_data.begin(); it != m_data.end(); ++it)
delete it->second;
}
m_data.clear();

View file

@ -57,7 +57,10 @@ public:
{
return m_privatevars.count(name) != 0;
}
void markPrivate(const std::string &name, bool set);
/// Marks a key as private.
/// @return metadata modified?
bool markPrivate(const std::string &name, bool set);
private:
int countNonPrivate() const;

View file

@ -40,6 +40,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <sys/time.h>
#endif
#include <queue>
/******************************************************************************/
/* Typedefs and macros */
/******************************************************************************/

View file

@ -173,6 +173,42 @@ u16 Player::getMaxHotbarItemcount()
return mainlist ? std::min(mainlist->getSize(), (u32) hud_hotbar_itemcount) : 0;
}
void PlayerControl::setMovementFromKeys()
{
bool a_up = direction_keys & (1 << 0),
a_down = direction_keys & (1 << 1),
a_left = direction_keys & (1 << 2),
a_right = direction_keys & (1 << 3);
if (a_up || a_down || a_left || a_right) {
// if contradictory keys pressed, stay still
if (a_up && a_down && a_left && a_right)
movement_speed = 0.0f;
else if (a_up && a_down && !a_left && !a_right)
movement_speed = 0.0f;
else if (!a_up && !a_down && a_left && a_right)
movement_speed = 0.0f;
else
// If there is a keyboard event, assume maximum speed
movement_speed = 1.0f;
}
// Check keyboard for input
float x = 0, y = 0;
if (a_up)
y += 1;
if (a_down)
y -= 1;
if (a_left)
x -= 1;
if (a_right)
x += 1;
if (x != 0 || y != 0)
// If there is a keyboard event, it takes priority
movement_direction = std::atan2(x, y);
}
#ifndef SERVER
u32 PlayerControl::getKeysPressed() const
@ -231,6 +267,11 @@ void PlayerControl::unpackKeysPressed(u32 keypress_bits)
zoom = keypress_bits & (1 << 9);
}
v2f PlayerControl::getMovement() const
{
return v2f(std::sin(movement_direction), std::cos(movement_direction)) * movement_speed;
}
static auto tie(const PlayerPhysicsOverride &o)
{
// Make sure to add new members to this list!

View file

@ -86,6 +86,11 @@ struct PlayerControl
movement_direction = a_movement_direction;
}
// Sets movement_speed and movement_direction according to direction_keys
// if direction_keys != 0, otherwise leaves them unchanged to preserve
// joystick input.
void setMovementFromKeys();
#ifndef SERVER
// For client use
u32 getKeysPressed() const;
@ -94,6 +99,7 @@ struct PlayerControl
// For server use
void unpackKeysPressed(u32 keypress_bits);
v2f getMovement() const;
u8 direction_keys = 0;
bool jump = false;
@ -102,7 +108,7 @@ struct PlayerControl
bool zoom = false;
bool dig = false;
bool place = false;
// Note: These four are NOT available on the server
// Note: These two are NOT available on the server
float pitch = 0.0f;
float yaw = 0.0f;
float movement_speed = 0.0f;
@ -197,7 +203,7 @@ public:
f32 movement_liquid_sink;
f32 movement_gravity;
v2s32 local_animations[4];
v2f local_animations[4];
float local_animation_speed;
std::string inventory_formspec;

View file

@ -113,14 +113,14 @@ public:
inline void setModified(const bool x) { m_dirty = x; }
void setLocalAnimations(v2s32 frames[4], float frame_speed)
void setLocalAnimations(v2f frames[4], float frame_speed)
{
for (int i = 0; i < 4; i++)
local_animations[i] = frames[i];
local_animation_speed = frame_speed;
}
void getLocalAnimations(v2s32 *frames, float *frame_speed)
void getLocalAnimations(v2f *frames, float *frame_speed)
{
for (int i = 0; i < 4; i++)
frames[i] = local_animations[i];

View file

@ -507,6 +507,7 @@ PackedValue *script_pack(lua_State *L, int idx)
void script_unpack(lua_State *L, PackedValue *pv)
{
assert(pv);
// table that tracks objects for keep_ref / PUSHREF (key = instr index)
lua_newtable(L);
const int top = lua_gettop(L);

View file

@ -50,11 +50,12 @@ AsyncEngine::~AsyncEngine()
}
// Wait for threads to finish
infostream << "AsyncEngine: Waiting for " << workerThreads.size()
<< " threads" << std::endl;
for (AsyncWorkerThread *workerThread : workerThreads) {
workerThread->wait();
}
// Force kill all threads
for (AsyncWorkerThread *workerThread : workerThreads) {
delete workerThread;
}

View file

@ -34,10 +34,11 @@ with this program; if not, write to the Free Software Foundation, Inc.,
class LuaABM : public ActiveBlockModifier {
private:
int m_id;
const int m_id;
std::vector<std::string> m_trigger_contents;
std::vector<std::string> m_required_neighbors;
std::vector<std::string> m_without_neighbors;
float m_trigger_interval;
u32 m_trigger_chance;
bool m_simple_catch_up;
@ -47,11 +48,13 @@ public:
LuaABM(int id,
const std::vector<std::string> &trigger_contents,
const std::vector<std::string> &required_neighbors,
const std::vector<std::string> &without_neighbors,
float trigger_interval, u32 trigger_chance, bool simple_catch_up,
s16 min_y, s16 max_y):
m_id(id),
m_trigger_contents(trigger_contents),
m_required_neighbors(required_neighbors),
m_without_neighbors(without_neighbors),
m_trigger_interval(trigger_interval),
m_trigger_chance(trigger_chance),
m_simple_catch_up(simple_catch_up),
@ -67,6 +70,10 @@ public:
{
return m_required_neighbors;
}
virtual const std::vector<std::string> &getWithoutNeighbors() const
{
return m_without_neighbors;
}
virtual float getTriggerInterval()
{
return m_trigger_interval;
@ -230,6 +237,11 @@ void ScriptApiEnv::readABMs()
read_nodenames(L, -1, required_neighbors);
lua_pop(L, 1);
std::vector<std::string> without_neighbors;
lua_getfield(L, current_abm, "without_neighbors");
read_nodenames(L, -1, without_neighbors);
lua_pop(L, 1);
float trigger_interval = 10.0;
getfloatfield(L, current_abm, "interval", trigger_interval);
@ -250,7 +262,8 @@ void ScriptApiEnv::readABMs()
lua_pop(L, 1);
LuaABM *abm = new LuaABM(id, trigger_contents, required_neighbors,
trigger_interval, trigger_chance, simple_catch_up, min_y, max_y);
without_neighbors, trigger_interval, trigger_chance,
simple_catch_up, min_y, max_y);
env->addActiveBlockModifier(abm);

View file

@ -6,6 +6,7 @@ set(common_SCRIPT_LUA_API_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/l_env.cpp
${CMAKE_CURRENT_SOURCE_DIR}/l_http.cpp
${CMAKE_CURRENT_SOURCE_DIR}/l_inventory.cpp
${CMAKE_CURRENT_SOURCE_DIR}/l_ipc.cpp
${CMAKE_CURRENT_SOURCE_DIR}/l_item.cpp
${CMAKE_CURRENT_SOURCE_DIR}/l_itemstackmeta.cpp
${CMAKE_CURRENT_SOURCE_DIR}/l_mapgen.cpp

View file

@ -160,7 +160,7 @@ void LuaEmergeAreaCallback(v3s16 blockpos, EmergeAction action, void *param)
// state must be protected by envlock
Server *server = state->script->getServer();
MutexAutoLock envlock(server->m_env_mutex);
Server::EnvAutoLock envlock(server);
state->refcount--;

View file

@ -0,0 +1,141 @@
// Minetest
// SPDX-License-Identifier: LGPL-2.1-or-later
#include "lua_api/l_ipc.h"
#include "lua_api/l_internal.h"
#include "common/c_packer.h"
#include "server.h"
#include "debug.h"
#include <chrono>
typedef std::shared_lock<std::shared_mutex> SharedReadLock;
typedef std::unique_lock<std::shared_mutex> SharedWriteLock;
static inline auto read_pv(lua_State *L, int idx)
{
std::unique_ptr<PackedValue> ret;
if (!lua_isnil(L, idx)) {
ret.reset(script_pack(L, idx));
if (ret->contains_userdata)
throw LuaError("Userdata not allowed");
}
return ret;
}
int ModApiIPC::l_ipc_get(lua_State *L)
{
auto *store = getGameDef(L)->getModIPCStore();
auto key = readParam<std::string>(L, 1);
{
SharedReadLock autolock(store->mutex);
auto it = store->map.find(key);
if (it == store->map.end())
lua_pushnil(L);
else
script_unpack(L, it->second.get());
}
return 1;
}
int ModApiIPC::l_ipc_set(lua_State *L)
{
auto *store = getGameDef(L)->getModIPCStore();
auto key = readParam<std::string>(L, 1);
luaL_checkany(L, 2);
auto pv = read_pv(L, 2);
{
SharedWriteLock autolock(store->mutex);
if (pv)
store->map[key] = std::move(pv);
else
store->map.erase(key); // delete the map value for nil
}
store->signal();
return 0;
}
int ModApiIPC::l_ipc_cas(lua_State *L)
{
auto *store = getGameDef(L)->getModIPCStore();
auto key = readParam<std::string>(L, 1);
luaL_checkany(L, 2);
const int idx_old = 2;
luaL_checkany(L, 3);
auto pv_new = read_pv(L, 3);
bool ok = false;
{
SharedWriteLock autolock(store->mutex);
// unpack and compare old value
auto it = store->map.find(key);
if (it == store->map.end()) {
ok = lua_isnil(L, idx_old);
} else {
script_unpack(L, it->second.get());
ok = lua_equal(L, idx_old, -1);
lua_pop(L, 1);
}
// put new value
if (ok) {
if (pv_new)
store->map[key] = std::move(pv_new);
else
store->map.erase(key);
}
}
if (ok)
store->signal();
lua_pushboolean(L, ok);
return 1;
}
int ModApiIPC::l_ipc_poll(lua_State *L)
{
auto *store = getGameDef(L)->getModIPCStore();
auto key = readParam<std::string>(L, 1);
auto timeout = std::chrono::milliseconds(
std::max<int>(0, luaL_checkinteger(L, 2))
);
bool ret;
{
SharedReadLock autolock(store->mutex);
// wait until value exists or timeout
ret = store->condvar.wait_for(autolock, timeout, [&] () -> bool {
return store->map.count(key) != 0;
});
}
lua_pushboolean(L, ret);
return 1;
}
/*
* Implementation note:
* Iterating over the IPC table is intentionally not supported.
* Mods should know what they have set.
* This has the nice side effect that mods are able to use a randomly generated key
* if they really *really* want to avoid other code touching their data.
*/
void ModApiIPC::Initialize(lua_State *L, int top)
{
FATAL_ERROR_IF(!getGameDef(L)->getModIPCStore(), "ModIPCStore missing from gamedef");
API_FCT(ipc_get);
API_FCT(ipc_set);
API_FCT(ipc_cas);
API_FCT(ipc_poll);
}

View file

@ -0,0 +1,17 @@
// Minetest
// SPDX-License-Identifier: LGPL-2.1-or-later
#pragma once
#include "lua_api/l_base.h"
class ModApiIPC : public ModApiBase {
private:
static int l_ipc_get(lua_State *L);
static int l_ipc_set(lua_State *L);
static int l_ipc_cas(lua_State *L);
static int l_ipc_poll(lua_State *L);
public:
static void Initialize(lua_State *L, int top);
};

View file

@ -41,7 +41,7 @@ void ItemStackMetaRef::clearMeta()
void ItemStackMetaRef::reportMetadataChange(const std::string *name)
{
// TODO
// nothing to do
}
// Exported functions
@ -89,7 +89,6 @@ ItemStackMetaRef::~ItemStackMetaRef()
void ItemStackMetaRef::create(lua_State *L, LuaItemStack *istack)
{
ItemStackMetaRef *o = new ItemStackMetaRef(istack);
//infostream<<"NodeMetaRef::create: o="<<o<<std::endl;
*(void **)(lua_newuserdata(L, sizeof(void *))) = o;
luaL_getmetatable(L, className);
lua_setmetatable(L, -2);
@ -98,9 +97,6 @@ void ItemStackMetaRef::create(lua_State *L, LuaItemStack *istack)
void ItemStackMetaRef::Register(lua_State *L)
{
registerMetadataClass(L, className, methods);
// Cannot be created from Lua
//lua_register(L, className, create_object);
}
const char ItemStackMetaRef::className[] = "ItemStackMetaRef";

View file

@ -260,12 +260,13 @@ int LuaLocalPlayer::l_get_control(lua_State *L)
set("zoom", c.zoom);
set("dig", c.dig);
set("place", c.place);
// Player movement in polar coordinates and non-binary speed
lua_pushnumber(L, c.movement_speed);
lua_setfield(L, -2, "movement_speed");
lua_pushnumber(L, c.movement_direction);
lua_setfield(L, -2, "movement_direction");
// Provide direction keys to ensure compatibility
v2f movement = c.getMovement();
lua_pushnumber(L, movement.X);
lua_setfield(L, -2, "movement_x");
lua_pushnumber(L, movement.Y);
lua_setfield(L, -2, "movement_y");
set("up", c.direction_keys & (1 << 0));
set("down", c.direction_keys & (1 << 1));
set("left", c.direction_keys & (1 << 2));

View file

@ -41,6 +41,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "content/mod_configuration.h"
#include "threading/mutex_auto_lock.h"
#include "common/c_converter.h"
#include "gui/guiOpenURL.h"
/******************************************************************************/
std::string ModApiMainMenu::getTextData(lua_State *L, const std::string &name)
@ -1034,7 +1035,14 @@ int ModApiMainMenu::l_get_min_supp_proto(lua_State *L)
int ModApiMainMenu::l_get_max_supp_proto(lua_State *L)
{
lua_pushinteger(L, CLIENT_PROTOCOL_VERSION_MAX);
lua_pushinteger(L, LATEST_PROTOCOL_VERSION);
return 1;
}
/******************************************************************************/
int ModApiMainMenu::l_get_formspec_version(lua_State *L)
{
lua_pushinteger(L, FORMSPEC_API_VERSION);
return 1;
}
@ -1046,6 +1054,22 @@ int ModApiMainMenu::l_open_url(lua_State *L)
return 1;
}
/******************************************************************************/
int ModApiMainMenu::l_open_url_dialog(lua_State *L)
{
GUIEngine* engine = getGuiEngine(L);
sanity_check(engine != NULL);
std::string url = luaL_checkstring(L, 1);
GUIOpenURLMenu* openURLMenu =
new GUIOpenURLMenu(engine->m_rendering_engine->get_gui_env(),
engine->m_parent, -1, engine->m_menumanager,
engine->m_texture_source.get(), url);
openURLMenu->drop();
return 1;
}
/******************************************************************************/
int ModApiMainMenu::l_open_dir(lua_State *L)
{
@ -1136,7 +1160,9 @@ void ModApiMainMenu::Initialize(lua_State *L, int top)
API_FCT(get_active_irrlicht_device);
API_FCT(get_min_supp_proto);
API_FCT(get_max_supp_proto);
API_FCT(get_formspec_version);
API_FCT(open_url);
API_FCT(open_url_dialog);
API_FCT(open_dir);
API_FCT(share_file);
API_FCT(do_async_callback);
@ -1166,6 +1192,7 @@ void ModApiMainMenu::InitializeAsync(lua_State *L, int top)
API_FCT(download_file);
API_FCT(get_min_supp_proto);
API_FCT(get_max_supp_proto);
API_FCT(get_formspec_version);
API_FCT(get_language);
API_FCT(gettext);
}

View file

@ -159,9 +159,13 @@ private:
static int l_get_max_supp_proto(lua_State *L);
static int l_get_formspec_version(lua_State *L);
// other
static int l_open_url(lua_State *L);
static int l_open_url_dialog(lua_State *L);
static int l_open_dir(lua_State *L);
static int l_share_file(lua_State *L);

View file

@ -58,6 +58,8 @@ void NodeMetaRef::reportMetadataChange(const std::string *name)
// Inform other things that the metadata has changed
NodeMetadata *meta = dynamic_cast<NodeMetadata*>(getmeta(false));
bool is_private_change = meta && name && meta->isPrivate(*name);
// If the metadata is now empty, get rid of it
if (meta && meta->empty()) {
clearMeta();
@ -67,7 +69,7 @@ void NodeMetaRef::reportMetadataChange(const std::string *name)
MapEditEvent event;
event.type = MEET_BLOCK_NODE_METADATA_CHANGED;
event.setPositionModified(m_p);
event.is_private_change = name && meta && meta->isPrivate(*name);
event.is_private_change = is_private_change;
m_env->getMap().dispatchEvent(event);
}
@ -94,21 +96,24 @@ int NodeMetaRef::l_mark_as_private(lua_State *L)
NodeMetaRef *ref = checkObject<NodeMetaRef>(L, 1);
NodeMetadata *meta = dynamic_cast<NodeMetadata*>(ref->getmeta(true));
assert(meta);
if (!meta)
return 0;
bool modified = false;
if (lua_istable(L, 2)) {
lua_pushnil(L);
while (lua_next(L, 2) != 0) {
// key at index -2 and value at index -1
luaL_checktype(L, -1, LUA_TSTRING);
meta->markPrivate(readParam<std::string>(L, -1), true);
modified |= meta->markPrivate(readParam<std::string>(L, -1), true);
// removes value, keeps key for next iteration
lua_pop(L, 1);
}
} else if (lua_isstring(L, 2)) {
meta->markPrivate(readParam<std::string>(L, 2), true);
modified |= meta->markPrivate(readParam<std::string>(L, 2), true);
}
ref->reportMetadataChange();
if (modified)
ref->reportMetadataChange();
return 0;
}
@ -145,12 +150,13 @@ bool NodeMetaRef::handleFromTable(lua_State *L, int table, IMetadata *_meta)
Inventory *inv = meta->getInventory();
lua_getfield(L, table, "inventory");
if (lua_istable(L, -1)) {
auto *gamedef = getGameDef(L);
int inventorytable = lua_gettop(L);
lua_pushnil(L);
while (lua_next(L, inventorytable) != 0) {
// key at index -2 and value at index -1
std::string name = luaL_checkstring(L, -2);
read_inventory_list(L, -1, inv, name.c_str(), getServer(L));
const char *name = luaL_checkstring(L, -2);
read_inventory_list(L, -1, inv, name, gamedef);
lua_pop(L, 1); // Remove value, keep key for next iteration
}
lua_pop(L, 1);
@ -177,7 +183,6 @@ NodeMetaRef::NodeMetaRef(IMetadata *meta):
void NodeMetaRef::create(lua_State *L, v3s16 p, ServerEnvironment *env)
{
NodeMetaRef *o = new NodeMetaRef(p, env);
//infostream<<"NodeMetaRef::create: o="<<o<<std::endl;
*(void **)(lua_newuserdata(L, sizeof(void *))) = o;
luaL_getmetatable(L, className);
lua_setmetatable(L, -2);

View file

@ -433,10 +433,10 @@ int ObjectRef::l_set_local_animation(lua_State *L)
if (player == nullptr)
return 0;
v2s32 frames[4];
v2f frames[4];
for (int i=0;i<4;i++) {
if (!lua_isnil(L, 2+1))
frames[i] = read_v2s32(L, 2+i);
frames[i] = read_v2f(L, 2+i);
}
float frame_speed = readParam<float>(L, 6, 30.0f);
@ -453,12 +453,12 @@ int ObjectRef::l_get_local_animation(lua_State *L)
if (player == nullptr)
return 0;
v2s32 frames[4];
v2f frames[4];
float frame_speed;
player->getLocalAnimations(frames, &frame_speed);
for (const v2s32 &frame : frames) {
push_v2s32(L, frame);
for (const v2f &frame : frames) {
push_v2f(L, frame);
}
lua_pushnumber(L, frame_speed);
@ -1622,6 +1622,13 @@ int ObjectRef::l_get_player_control(lua_State *L)
lua_setfield(L, -2, "dig");
lua_pushboolean(L, control.place);
lua_setfield(L, -2, "place");
v2f movement = control.getMovement();
lua_pushnumber(L, movement.X);
lua_setfield(L, -2, "movement_x");
lua_pushnumber(L, movement.Y);
lua_setfield(L, -2, "movement_y");
// Legacy fields to ensure mod compatibility
lua_pushboolean(L, control.dig);
lua_setfield(L, -2, "LMB");
@ -2626,6 +2633,7 @@ int ObjectRef::l_set_lighting(lua_State *L)
getfloatfield(L, -1, "intensity", lighting.shadow_intensity);
lua_getfield(L, -1, "tint");
read_color(L, -1, &lighting.shadow_tint);
lua_pop(L, 1); // tint
}
lua_pop(L, 1); // shadows
@ -2648,6 +2656,14 @@ int ObjectRef::l_set_lighting(lua_State *L)
lighting.volumetric_light_strength = rangelim(lighting.volumetric_light_strength, 0.0f, 1.0f);
}
lua_pop(L, 1); // volumetric_light
lua_getfield(L, 2, "bloom");
if (lua_istable(L, -1)) {
lighting.bloom_intensity = getfloatfield_default(L, -1, "intensity", lighting.bloom_intensity);
lighting.bloom_strength_factor = getfloatfield_default(L, -1, "strength_factor", lighting.bloom_strength_factor);
lighting.bloom_radius = getfloatfield_default(L, -1, "radius", lighting.bloom_radius);
}
lua_pop(L, 1); // bloom
}
getServer(L)->setLighting(player, lighting);
@ -2694,6 +2710,14 @@ int ObjectRef::l_get_lighting(lua_State *L)
lua_pushnumber(L, lighting.volumetric_light_strength);
lua_setfield(L, -2, "strength");
lua_setfield(L, -2, "volumetric_light");
lua_newtable(L); // "bloom"
lua_pushnumber(L, lighting.bloom_intensity);
lua_setfield(L, -2, "intensity");
lua_pushnumber(L, lighting.bloom_strength_factor);
lua_setfield(L, -2, "strength_factor");
lua_pushnumber(L, lighting.bloom_radius);
lua_setfield(L, -2, "radius");
lua_setfield(L, -2, "bloom");
return 1;
}

View file

@ -38,7 +38,7 @@ void PlayerMetaRef::clearMeta()
void PlayerMetaRef::reportMetadataChange(const std::string *name)
{
// TODO
// the server saves these on its own
}
// Creates an PlayerMetaRef and leaves it on top of stack
@ -54,9 +54,6 @@ void PlayerMetaRef::create(lua_State *L, IMetadata *metadata)
void PlayerMetaRef::Register(lua_State *L)
{
registerMetadataClass(L, className, methods);
// Cannot be created from Lua
// lua_register(L, className, create_object);
}
const char PlayerMetaRef::className[] = "PlayerMetaRef";

View file

@ -33,6 +33,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "convert_json.h"
#include "debug.h"
#include "log.h"
#include "log_internal.h"
#include "tool.h"
#include "filesys.h"
#include "settings.h"
@ -531,7 +532,7 @@ int ModApiUtil::l_get_version(lua_State *L)
lua_pushnumber(L, SERVER_PROTOCOL_VERSION_MIN);
lua_setfield(L, table, "proto_min");
lua_pushnumber(L, SERVER_PROTOCOL_VERSION_MAX);
lua_pushnumber(L, LATEST_PROTOCOL_VERSION);
lua_setfield(L, table, "proto_max");
if (strcmp(g_version_string, g_version_hash) != 0) {

View file

@ -35,6 +35,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "lua_api/l_util.h"
#include "lua_api/l_vmanip.h"
#include "lua_api/l_settings.h"
#include "lua_api/l_ipc.h"
extern "C" {
#include <lualib.h>
@ -89,5 +90,6 @@ void EmergeScripting::InitializeModApi(lua_State *L, int top)
ModApiMapgen::InitializeEmerge(L, top);
ModApiServer::InitializeAsync(L, top);
ModApiUtil::InitializeAsync(L, top);
ModApiIPC::Initialize(L, top);
// TODO ^ these should also be renamed to InitializeRO or such
}

View file

@ -46,6 +46,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "lua_api/l_settings.h"
#include "lua_api/l_http.h"
#include "lua_api/l_storage.h"
#include "lua_api/l_ipc.h"
extern "C" {
#include <lualib.h>
@ -121,6 +122,7 @@ void ServerScripting::initAsync()
asyncEngine.registerStateInitializer(ModApiCraft::InitializeAsync);
asyncEngine.registerStateInitializer(ModApiItem::InitializeAsync);
asyncEngine.registerStateInitializer(ModApiServer::InitializeAsync);
asyncEngine.registerStateInitializer(ModApiIPC::Initialize);
// not added: ModApiMapgen is a minefield for thread safety
// not added: ModApiHttp async api can't really work together with our jobs
// not added: ModApiStorage is probably not thread safe(?)
@ -176,6 +178,7 @@ void ServerScripting::InitializeModApi(lua_State *L, int top)
ModApiHttp::Initialize(L, top);
ModApiStorage::Initialize(L, top);
ModApiChannels::Initialize(L, top);
ModApiIPC::Initialize(L, top);
}
void ServerScripting::InitializeAsync(lua_State *L, int top)

View file

@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <zlib.h>
#include <zstd.h>
#include <memory>
/* report a zlib or i/o error */
static void zerr(int ret)

View file

@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <iostream>
#include <queue>
#include <algorithm>
#include "irr_v2d.h"
#include "network/connection.h"
#include "network/networkprotocol.h"
#include "network/serveropcodes.h"
@ -85,6 +86,15 @@ public:
{}
};
ModIPCStore::~ModIPCStore()
{
// we don't have to do this, it's pure debugging aid
if (!std::unique_lock(mutex, std::try_to_lock).owns_lock()) {
errorstream << FUNCTION_NAME << ": lock is still in use!" << std::endl;
assert(0);
}
}
class ServerThread : public Thread
{
public:
@ -133,9 +143,13 @@ void *ServerThread::run()
u64 t0 = porting::getTimeUs();
const Server::StepSettings step_settings = m_server->getStepSettings();
const auto step_settings = m_server->getStepSettings();
try {
// see explanation inside
if (dtime > step_settings.steplen)
m_server->yieldToOtherThreads(dtime);
m_server->AsyncRunStep(step_settings.pause ? 0.0f : dtime);
const float remaining_time = step_settings.steplen
@ -353,7 +367,7 @@ Server::~Server()
m_emerge->stopThreads();
if (m_env) {
MutexAutoLock envlock(m_env_mutex);
EnvAutoLock envlock(this);
infostream << "Server: Executing shutdown hooks" << std::endl;
try {
@ -389,6 +403,10 @@ Server::~Server()
infostream << "Server: Saving environment metadata" << std::endl;
m_env->saveMeta();
// Delete classes that depend on the environment
m_inventory_mgr.reset();
m_script.reset();
// Note that this also deletes and saves the map.
delete m_env;
m_env = nullptr;
@ -405,6 +423,9 @@ Server::~Server()
}
}
// emerge may depend on definition managers, so destroy first
m_emerge.reset();
// Delete the rest in the reverse order of creation
delete m_game_settings;
delete m_banmanager;
@ -461,7 +482,7 @@ void Server::init()
}
//lock environment
MutexAutoLock envlock(m_env_mutex);
EnvAutoLock envlock(this);
// Create the Map (loads map_meta.txt, overriding configured mapgen params)
auto startup_server_map = std::make_unique<ServerMap>(m_path_world, this,
@ -653,9 +674,9 @@ void Server::AsyncRunStep(float dtime, bool initial_step)
}
{
MutexAutoLock lock(m_env_mutex);
EnvAutoLock lock(this);
float max_lag = m_env->getMaxLagEstimate();
constexpr float lag_warn_threshold = 2.0f;
constexpr float lag_warn_threshold = 1.0f;
// Decrease value gradually, halve it every minute.
if (m_max_lag_decrease.step(dtime, 0.5f)) {
@ -686,7 +707,7 @@ void Server::AsyncRunStep(float dtime, bool initial_step)
static const float map_timer_and_unload_dtime = 2.92;
if(m_map_timer_and_unload_interval.step(dtime, map_timer_and_unload_dtime))
{
MutexAutoLock lock(m_env_mutex);
EnvAutoLock lock(this);
// Run Map's timers and unload unused data
ScopeProfiler sp(g_profiler, "Server: map timer and unload");
m_env->getMap().timerUpdate(map_timer_and_unload_dtime,
@ -704,7 +725,7 @@ void Server::AsyncRunStep(float dtime, bool initial_step)
*/
if (m_admin_chat) {
if (!m_admin_chat->command_queue.empty()) {
MutexAutoLock lock(m_env_mutex);
EnvAutoLock lock(this);
while (!m_admin_chat->command_queue.empty()) {
ChatEvent *evt = m_admin_chat->command_queue.pop_frontNoEx();
handleChatInterfaceEvent(evt);
@ -725,7 +746,7 @@ void Server::AsyncRunStep(float dtime, bool initial_step)
{
m_liquid_transform_timer -= m_liquid_transform_every;
MutexAutoLock lock(m_env_mutex);
EnvAutoLock lock(this);
ScopeProfiler sp(g_profiler, "Server: liquid transform");
@ -786,7 +807,7 @@ void Server::AsyncRunStep(float dtime, bool initial_step)
*/
{
//infostream<<"Server: Checking added and deleted active objects"<<std::endl;
MutexAutoLock envlock(m_env_mutex);
EnvAutoLock envlock(this);
// This guarantees that each object recomputes its cache only once per server step,
// unless get_effective_observers is called.
@ -831,7 +852,7 @@ void Server::AsyncRunStep(float dtime, bool initial_step)
Send object messages
*/
{
MutexAutoLock envlock(m_env_mutex);
EnvAutoLock envlock(this);
ScopeProfiler sp(g_profiler, "Server: send SAO messages");
// Key = object id
@ -933,7 +954,7 @@ void Server::AsyncRunStep(float dtime, bool initial_step)
*/
{
// We will be accessing the environment
MutexAutoLock lock(m_env_mutex);
EnvAutoLock lock(this);
// Single change sending is disabled if queue size is big
bool disable_single_change_sending = false;
@ -1040,7 +1061,7 @@ void Server::AsyncRunStep(float dtime, bool initial_step)
g_settings->getFloat("server_map_save_interval");
if (counter >= save_interval) {
counter = 0.0;
MutexAutoLock lock(m_env_mutex);
EnvAutoLock lock(this);
ScopeProfiler sp(g_profiler, "Server: map saving (sum)");
@ -1113,6 +1134,52 @@ void Server::Receive(float timeout)
}
}
void Server::yieldToOtherThreads(float dtime)
{
/*
* Problem: the server thread and emerge thread compete for the envlock.
* While the emerge thread needs it just once or twice for every processed item
* the server thread uses it much more generously.
* This is usually not a problem as the server sleeps between steps, which leaves
* enough chance. But if the server is overloaded it's busy all the time and
* - even with a fair envlock - the emerge thread can't get up to speed.
* This generally has a much worse impact on gameplay than server lag itself
* ever would.
*
* Workaround: If we detect that the server is overloaded, introduce some careful
* artificial sleeps to leave the emerge threads enough chance to do their job.
*
* In the future the emerge code should be reworked to exclusively use a result
* queue, thereby avoiding this problem (and terrible workaround).
*/
// don't activate workaround too quickly
constexpr size_t MIN_EMERGE_QUEUE_SIZE = 32;
const size_t qs_initial = m_emerge->getQueueSize();
if (qs_initial < MIN_EMERGE_QUEUE_SIZE)
return;
// give the thread a chance to run for every 28ms (on average)
// this was experimentally determined
const float QUANTUM = 28.0f / 1000;
// put an upper limit to not cause too much lag, also so this doesn't become self-sustaining
const int SLEEP_MAX = 10;
int sleep_count = std::clamp<int>(dtime / QUANTUM, 1, SLEEP_MAX);
ScopeProfiler sp(g_profiler, "Server::yieldTo...() sleep", SPT_AVG);
size_t qs = qs_initial;
while (sleep_count-- > 0) {
sleep_ms(1);
// abort if we don't make progress
size_t qs2 = m_emerge->getQueueSize();
if (qs2 >= qs || qs2 == 0)
break;
qs = qs2;
}
g_profiler->avg("Server::yieldTo...() progress [#]", qs_initial - qs);
}
PlayerSAO* Server::StageTwoClientInit(session_t peer_id)
{
std::string playername;
@ -1191,7 +1258,7 @@ inline void Server::handleCommand(NetworkPacket *pkt)
void Server::ProcessData(NetworkPacket *pkt)
{
// Environment is locked first.
MutexAutoLock envlock(m_env_mutex);
EnvAutoLock envlock(this);
ScopeProfiler sp(g_profiler, "Server: Process network packet (sum)");
u32 peer_id = pkt->getPeerId();
@ -1861,7 +1928,10 @@ void Server::SendSetLighting(session_t peer_id, const Lighting &lighting)
<< lighting.exposure.speed_bright_dark
<< lighting.exposure.center_weight_power;
pkt << lighting.volumetric_light_strength << lighting.shadow_tint << lighting.artificial_light_color;
pkt << lighting.volumetric_light_strength << lighting.shadow_tint;
pkt << lighting.bloom_intensity << lighting.bloom_strength_factor <<
lighting.bloom_radius;
pkt << lighting.artificial_light_color;
Send(&pkt);
}
@ -1928,14 +1998,21 @@ void Server::SendPlayerFov(session_t peer_id)
Send(&pkt);
}
void Server::SendLocalPlayerAnimations(session_t peer_id, v2s32 animation_frames[4],
void Server::SendLocalPlayerAnimations(session_t peer_id, v2f animation_frames[4],
f32 animation_speed)
{
NetworkPacket pkt(TOCLIENT_LOCAL_PLAYER_ANIMATIONS, 0,
peer_id);
pkt << animation_frames[0] << animation_frames[1] << animation_frames[2]
<< animation_frames[3] << animation_speed;
for (int i = 0; i < 4; ++i) {
if (m_clients.getProtocolVersion(peer_id) >= 46) {
pkt << animation_frames[i];
} else {
pkt << v2s32::from(animation_frames[i]);
}
}
pkt << animation_speed;
Send(&pkt);
}
@ -2363,8 +2440,7 @@ void Server::SendBlockNoLock(session_t peer_id, MapBlock *block, u8 ver,
void Server::SendBlocks(float dtime)
{
MutexAutoLock envlock(m_env_mutex);
//TODO check if one big lock could be faster then multiple small ones
EnvAutoLock envlock(this);
std::vector<PrioritySortedBlockTransfer> queue;
@ -2461,7 +2537,7 @@ bool Server::addMediaFile(const std::string &filename,
const char *supported_ext[] = {
".png", ".jpg", ".bmp", ".tga",
".ogg",
".x", ".b3d", ".obj", ".gltf",
".x", ".b3d", ".obj", ".gltf", ".glb",
// Custom translation file format
".tr",
NULL
@ -2695,7 +2771,7 @@ void Server::sendRequestedMedia(session_t peer_id,
void Server::stepPendingDynMediaCallbacks(float dtime)
{
MutexAutoLock lock(m_env_mutex);
EnvAutoLock lock(this);
for (auto it = m_pending_dyn_media.begin(); it != m_pending_dyn_media.end();) {
it->second.expiry_timer -= dtime;
@ -2914,7 +2990,7 @@ void Server::DeleteClient(session_t peer_id, ClientDeletionReason reason)
}
}
{
MutexAutoLock env_lock(m_env_mutex);
EnvAutoLock envlock(this);
m_clients.DeleteClient(peer_id);
}
}
@ -3366,7 +3442,7 @@ Address Server::getPeerAddress(session_t peer_id)
}
void Server::setLocalPlayerAnimations(RemotePlayer *player,
v2s32 animation_frames[4], f32 frame_speed)
v2f animation_frames[4], f32 frame_speed)
{
sanity_check(player);
player->setLocalAnimations(animation_frames, frame_speed);
@ -4107,7 +4183,7 @@ Translations *Server::getTranslationLanguage(const std::string &lang_code)
std::unordered_map<std::string, std::string> Server::getMediaList()
{
MutexAutoLock env_lock(m_env_mutex);
EnvAutoLock envlock(this);
std::unordered_map<std::string, std::string> ret;
for (auto &it : m_media) {
@ -4236,12 +4312,10 @@ u16 Server::getProtocolVersionMin()
min_proto = LATEST_PROTOCOL_VERSION;
return rangelim(min_proto,
SERVER_PROTOCOL_VERSION_MIN,
SERVER_PROTOCOL_VERSION_MAX);
LATEST_PROTOCOL_VERSION);
}
u16 Server::getProtocolVersionMax()
{
return g_settings->getBool("strict_protocol_version_checking")
? LATEST_PROTOCOL_VERSION
: SERVER_PROTOCOL_VERSION_MAX;
return LATEST_PROTOCOL_VERSION;
}

View file

@ -35,6 +35,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "util/metricsbackend.h"
#include "serverenvironment.h"
#include "server/clientiface.h"
#include "threading/ordered_mutex.h"
#include "chatmessage.h"
#include "sound.h"
#include "translation.h"
@ -46,6 +47,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <unordered_set>
#include <optional>
#include <string_view>
#include <shared_mutex>
#include <condition_variable>
class ChatEvent;
struct ChatEventChat;
@ -78,6 +81,20 @@ struct PackedValue;
struct ParticleParameters;
struct ParticleSpawnerParameters;
// Anticheat flags
enum {
AC_DIGGING = 0x01,
AC_INTERACTION = 0x02,
AC_MOVEMENT = 0x04
};
constexpr const static FlagDesc flagdesc_anticheat[] = {
{"digging", AC_DIGGING},
{"interaction", AC_INTERACTION},
{"movement", AC_MOVEMENT},
{NULL, 0}
};
enum ClientDeletionReason {
CDR_LEAVE,
CDR_TIMEOUT,
@ -141,6 +158,25 @@ struct ClientInfo {
std::string vers_string, lang_code;
};
struct ModIPCStore {
ModIPCStore() = default;
~ModIPCStore();
/// RW lock for this entire structure
std::shared_mutex mutex;
/// Signalled on any changes to the map contents
std::condition_variable_any condvar;
/**
* Map storing the data
*
* @note Do not store `nil` data in this map, instead remove the whole key.
*/
std::unordered_map<std::string, std::unique_ptr<PackedValue>> map;
/// @note Should be called without holding the lock.
inline void signal() { condvar.notify_all(); }
};
class Server : public con::PeerHandler, public MapEventReceiver,
public IGameDef
{
@ -166,9 +202,12 @@ public:
// Actual processing is done in another thread.
// This just checks if there was an error in that thread.
void step();
// This is run by ServerThread and does the actual processing
void AsyncRunStep(float dtime, bool initial_step = false);
void Receive(float timeout);
void yieldToOtherThreads(float dtime);
PlayerSAO* StageTwoClientInit(session_t peer_id);
/*
@ -297,12 +336,14 @@ public:
NodeDefManager* getWritableNodeDefManager();
IWritableCraftDefManager* getWritableCraftDefManager();
// Not under envlock
virtual const std::vector<ModSpec> &getMods() const;
virtual const ModSpec* getModSpec(const std::string &modname) const;
virtual const SubgameSpec* getGameSpec() const { return &m_gamespec; }
static std::string getBuiltinLuaPath();
virtual std::string getWorldPath() const { return m_path_world; }
virtual std::string getModDataPath() const { return m_path_mod_data; }
virtual ModIPCStore *getModIPCStore() { return &m_ipcstore; }
inline bool isSingleplayer() const
{ return m_simple_singleplayer_mode; }
@ -340,7 +381,7 @@ public:
Address getPeerAddress(session_t peer_id);
void setLocalPlayerAnimations(RemotePlayer *player, v2s32 animation_frames[4],
void setLocalPlayerAnimations(RemotePlayer *player, v2f animation_frames[4],
f32 frame_speed);
void setPlayerEyeOffset(RemotePlayer *player, v3f first, v3f third, v3f third_front);
@ -424,8 +465,14 @@ public:
// Bind address
Address m_bind_addr;
// Environment mutex (envlock)
std::mutex m_env_mutex;
// Public helper for taking the envlock in a scope
class EnvAutoLock {
public:
EnvAutoLock(Server *server): m_lock(server->m_env_mutex) {}
private:
std::lock_guard<ordered_mutex> m_lock;
};
protected:
/* Do not add more members here, this is only required to make unit tests work. */
@ -491,7 +538,7 @@ private:
virtual void SendChatMessage(session_t peer_id, const ChatMessage &message);
void SendTimeOfDay(session_t peer_id, u16 time, f32 time_speed);
void SendLocalPlayerAnimations(session_t peer_id, v2s32 animation_frames[4],
void SendLocalPlayerAnimations(session_t peer_id, v2f animation_frames[4],
f32 animation_speed);
void SendEyeOffset(session_t peer_id, v3f first, v3f third, v3f third_front);
void SendPlayerPrivileges(session_t peer_id);
@ -595,11 +642,13 @@ private:
*/
PlayerSAO *emergePlayer(const char *name, session_t peer_id, u16 proto_version);
void handlePeerChanges();
/*
Variables
*/
// Environment mutex (envlock)
ordered_mutex m_env_mutex;
// World directory
std::string m_path_world;
std::string m_path_mod_data;
@ -654,6 +703,8 @@ private:
std::unordered_map<std::string, Translations> server_translations;
ModIPCStore m_ipcstore;
/*
Threads
*/

View file

@ -33,6 +33,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <list>
#include <vector>
#include <set>
#include <unordered_map>
#include <unordered_set>
#include <memory>
#include <mutex>

View file

@ -519,12 +519,13 @@ void PlayerSAO::rightClick(ServerActiveObject *clicker)
void PlayerSAO::setHP(s32 target_hp, const PlayerHPChangeReason &reason, bool from_client)
{
target_hp = rangelim(target_hp, 0, U16_MAX);
if (target_hp == m_hp)
if (target_hp == m_hp || (m_hp == 0 && target_hp < 0))
return; // Nothing to do
s32 hp_change = m_env->getScriptIface()->on_player_hpchange(this, target_hp - (s32)m_hp, reason);
// Protect against overflow.
s32 hp_change = std::max<s64>((s64)target_hp - (s64)m_hp, S32_MIN);
hp_change = m_env->getScriptIface()->on_player_hpchange(this, hp_change, reason);
hp_change = std::min<s32>(hp_change, U16_MAX); // Protect against overflow
s32 hp = (s32)m_hp + hp_change;
@ -645,9 +646,12 @@ void PlayerSAO::setMaxSpeedOverride(const v3f &vel)
bool PlayerSAO::checkMovementCheat()
{
static thread_local const u32 anticheat_flags =
g_settings->getFlagStr("anticheat_flags", flagdesc_anticheat, nullptr);
if (m_is_singleplayer ||
isAttached() ||
g_settings->getBool("disable_anticheat")) {
!(anticheat_flags & AC_MOVEMENT)) {
m_last_good_position = m_base_position;
return false;
}
@ -728,6 +732,11 @@ bool PlayerSAO::checkMovementCheat()
required_time = MYMAX(required_time, d_vert / s);
}
static thread_local float anticheat_movement_tolerance =
std::max(g_settings->getFloat("anticheat_movement_tolerance"), 1.0f);
required_time /= anticheat_movement_tolerance;
if (m_move_pool.grab(required_time)) {
m_last_good_position = m_base_position;
} else {

View file

@ -827,6 +827,7 @@ struct ActiveABM
{
ActiveBlockModifier *abm;
std::vector<content_t> required_neighbors;
std::vector<content_t> without_neighbors;
int chance;
s16 min_y, max_y;
};
@ -885,6 +886,10 @@ public:
ndef->getIds(s, aabm.required_neighbors);
SORT_AND_UNIQUE(aabm.required_neighbors);
for (const auto &s : abm->getWithoutNeighbors())
ndef->getIds(s, aabm.without_neighbors);
SORT_AND_UNIQUE(aabm.without_neighbors);
// Trigger contents
std::vector<content_t> ids;
for (const auto &s : abm->getTriggerContents())
@ -996,8 +1001,11 @@ public:
continue;
// Check neighbors
if (!aabm.required_neighbors.empty()) {
const bool check_required_neighbors = !aabm.required_neighbors.empty();
const bool check_without_neighbors = !aabm.without_neighbors.empty();
if (check_required_neighbors || check_without_neighbors) {
v3s16 p1;
bool have_required = false;
for(p1.X = p0.X-1; p1.X <= p0.X+1; p1.X++)
for(p1.Y = p0.Y-1; p1.Y <= p0.Y+1; p1.Y++)
for(p1.Z = p0.Z-1; p1.Z <= p0.Z+1; p1.Z++)
@ -1015,12 +1023,25 @@ public:
MapNode n = map->getNode(p1 + block->getPosRelative());
c = n.getContent();
}
if (CONTAINS(aabm.required_neighbors, c))
goto neighbor_found;
if (check_required_neighbors && !have_required) {
if (CONTAINS(aabm.required_neighbors, c)) {
if (!check_without_neighbors)
goto neighbor_found;
have_required = true;
}
}
if (check_without_neighbors) {
if (CONTAINS(aabm.without_neighbors, c))
goto neighbor_invalid;
}
}
if (have_required || !check_required_neighbors)
goto neighbor_found;
// No required neighbor found
neighbor_invalid:
continue;
}
neighbor_found:
abms_run++;

View file

@ -63,6 +63,9 @@ public:
// Set of required neighbors (trigger doesn't happen if none are found)
// Empty = do not check neighbors
virtual const std::vector<std::string> &getRequiredNeighbors() const = 0;
// Set of without neighbors (trigger doesn't happen if any are found)
// Empty = do not check neighbors
virtual const std::vector<std::string> &getWithoutNeighbors() const = 0;
// Trigger interval in seconds
virtual float getTriggerInterval() = 0;
// Random chance of (1 / return value), 0 is disallowed

View file

@ -51,6 +51,18 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "database/database-postgresql.h"
#endif
/*
Helpers
*/
void MapDatabaseAccessor::loadBlock(v3s16 blockpos, std::string &ret)
{
ret.clear();
dbase->loadBlock(blockpos, &ret);
if (ret.empty() && dbase_ro)
dbase_ro->loadBlock(blockpos, &ret);
}
/*
ServerMap
*/
@ -67,7 +79,7 @@ ServerMap::ServerMap(const std::string &savedir, IGameDef *gamedef,
emerge->map_settings_mgr = &settings_mgr;
/*
Try to load map; if not found, create a new one.
Try to open map; if not found, create a new one.
*/
// Determine which database backend to use
@ -79,10 +91,10 @@ ServerMap::ServerMap(const std::string &savedir, IGameDef *gamedef,
conf.set("backend", "sqlite3");
}
std::string backend = conf.get("backend");
dbase = createDatabase(backend, savedir, conf);
m_db.dbase = createDatabase(backend, savedir, conf);
if (conf.exists("readonly_backend")) {
std::string readonly_dir = savedir + DIR_DELIM + "readonly";
dbase_ro = createDatabase(conf.get("readonly_backend"), readonly_dir, conf);
m_db.dbase_ro = createDatabase(conf.get("readonly_backend"), readonly_dir, conf);
}
if (!conf.updateConfigFile(conf_path.c_str()))
errorstream << "ServerMap::ServerMap(): Failed to update world.mt!" << std::endl;
@ -90,6 +102,9 @@ ServerMap::ServerMap(const std::string &savedir, IGameDef *gamedef,
m_savedir = savedir;
m_map_saving_enabled = false;
// Inform EmergeManager of db handles
m_emerge->initMap(&m_db);
m_save_time_counter = mb->addCounter(
"minetest_map_save_time", "Time spent saving blocks (in microseconds)");
m_save_count_counter = mb->addCounter(
@ -159,11 +174,15 @@ ServerMap::~ServerMap()
<< ", exception: " << e.what() << std::endl;
}
/*
Close database if it was opened
*/
delete dbase;
delete dbase_ro;
m_emerge->resetMap();
{
MutexAutoLock dblock(m_db.mutex);
delete m_db.dbase;
m_db.dbase = nullptr;
delete m_db.dbase_ro;
m_db.dbase_ro = nullptr;
}
deleteDetachedBlocks();
}
@ -547,9 +566,10 @@ void ServerMap::save(ModifiedState save_level)
void ServerMap::listAllLoadableBlocks(std::vector<v3s16> &dst)
{
dbase->listAllLoadableBlocks(dst);
if (dbase_ro)
dbase_ro->listAllLoadableBlocks(dst);
MutexAutoLock dblock(m_db.mutex);
m_db.dbase->listAllLoadableBlocks(dst);
if (m_db.dbase_ro)
m_db.dbase_ro->listAllLoadableBlocks(dst);
}
void ServerMap::listAllLoadedBlocks(std::vector<v3s16> &dst)
@ -597,17 +617,21 @@ MapDatabase *ServerMap::createDatabase(
void ServerMap::beginSave()
{
dbase->beginSave();
MutexAutoLock dblock(m_db.mutex);
m_db.dbase->beginSave();
}
void ServerMap::endSave()
{
dbase->endSave();
MutexAutoLock dblock(m_db.mutex);
m_db.dbase->endSave();
}
bool ServerMap::saveBlock(MapBlock *block)
{
return saveBlock(block, dbase, m_map_compression_level);
// FIXME: serialization happens under mutex
MutexAutoLock dblock(m_db.mutex);
return saveBlock(block, m_db.dbase, m_map_compression_level);
}
bool ServerMap::saveBlock(MapBlock *block, MapDatabase *db, int compression_level)
@ -634,18 +658,27 @@ bool ServerMap::saveBlock(MapBlock *block, MapDatabase *db, int compression_leve
return ret;
}
void ServerMap::loadBlock(std::string *blob, v3s16 p3d, MapSector *sector, bool save_after_load)
void ServerMap::deSerializeBlock(MapBlock *block, std::istream &is)
{
ScopeProfiler sp(g_profiler, "ServerMap: deSer block", SPT_AVG, PRECISION_MICRO);
u8 version = readU8(is);
if (is.fail())
throw SerializationError("Failed to read MapBlock version");
block->deSerialize(is, version, true);
}
MapBlock *ServerMap::loadBlock(const std::string &blob, v3s16 p3d, bool save_after_load)
{
ScopeProfiler sp(g_profiler, "ServerMap: load block", SPT_AVG, PRECISION_MICRO);
MapBlock *block = nullptr;
bool created_new = false;
try {
std::istringstream is(*blob, std::ios_base::binary);
v2s16 p2d(p3d.X, p3d.Z);
MapSector *sector = createSector(p2d);
u8 version = readU8(is);
if(is.fail())
throw SerializationError("ServerMap::loadBlock(): Failed"
" to read MapBlock version");
MapBlock *block = nullptr;
std::unique_ptr<MapBlock> block_created_new;
block = sector->getBlockNoCreateNoEx(p3d.Y);
if (!block) {
@ -654,31 +687,16 @@ void ServerMap::loadBlock(std::string *blob, v3s16 p3d, MapSector *sector, bool
}
{
ScopeProfiler sp(g_profiler, "ServerMap: deSer block", SPT_AVG, PRECISION_MICRO);
block->deSerialize(is, version, true);
std::istringstream iss(blob, std::ios_base::binary);
deSerializeBlock(block, iss);
}
// If it's a new block, insert it to the map
if (block_created_new) {
sector->insertBlock(std::move(block_created_new));
ReflowScan scanner(this, m_emerge->ndef);
scanner.scan(block, &m_transforming_liquid);
created_new = true;
}
/*
Save blocks loaded in old format in new format
*/
//if(version < SER_FMT_VER_HIGHEST_READ || save_after_load)
// Only save if asked to; no need to update version
if(save_after_load)
saveBlock(block);
// We just loaded it from, so it's up-to-date.
block->resetModified();
}
catch(SerializationError &e)
{
} catch (SerializationError &e) {
errorstream<<"Invalid block data in database"
<<" ("<<p3d.X<<","<<p3d.Y<<","<<p3d.Z<<")"
<<" (SerializationError): "<<e.what()<<std::endl;
@ -693,47 +711,51 @@ void ServerMap::loadBlock(std::string *blob, v3s16 p3d, MapSector *sector, bool
throw SerializationError("Invalid block data in database");
}
}
}
MapBlock* ServerMap::loadBlock(v3s16 blockpos)
{
ScopeProfiler sp(g_profiler, "ServerMap: load block", SPT_AVG, PRECISION_MICRO);
bool created_new = (getBlockNoCreateNoEx(blockpos) == NULL);
assert(block);
v2s16 p2d(blockpos.X, blockpos.Z);
if (created_new) {
ReflowScan scanner(this, m_emerge->ndef);
scanner.scan(block, &m_transforming_liquid);
std::string ret;
dbase->loadBlock(blockpos, &ret);
if (!ret.empty()) {
loadBlock(&ret, blockpos, createSector(p2d), false);
} else if (dbase_ro) {
dbase_ro->loadBlock(blockpos, &ret);
if (!ret.empty()) {
loadBlock(&ret, blockpos, createSector(p2d), false);
}
} else {
return NULL;
}
MapBlock *block = getBlockNoCreateNoEx(blockpos);
if (created_new && (block != NULL)) {
std::map<v3s16, MapBlock*> modified_blocks;
// Fix lighting if necessary
voxalgo::update_block_border_lighting(this, block, modified_blocks);
if (!modified_blocks.empty()) {
//Modified lighting, send event
MapEditEvent event;
event.type = MEET_OTHER;
event.setModifiedBlocks(modified_blocks);
dispatchEvent(event);
}
}
if (save_after_load)
saveBlock(block);
// We just loaded it, so it's up-to-date.
block->resetModified();
return block;
}
MapBlock* ServerMap::loadBlock(v3s16 blockpos)
{
std::string data;
{
ScopeProfiler sp(g_profiler, "ServerMap: load block - sync (sum)");
MutexAutoLock dblock(m_db.mutex);
m_db.loadBlock(blockpos, data);
}
if (!data.empty())
return loadBlock(data, blockpos);
return getBlockNoCreateNoEx(blockpos);
}
bool ServerMap::deleteBlock(v3s16 blockpos)
{
if (!dbase->deleteBlock(blockpos))
MutexAutoLock dblock(m_db.mutex);
if (!m_db.dbase->deleteBlock(blockpos))
return false;
MapBlock *block = getBlockNoCreateNoEx(blockpos);

View file

@ -33,9 +33,22 @@ class IRollbackManager;
class EmergeManager;
class ServerEnvironment;
struct BlockMakeData;
class MetricsBackend;
// TODO: this could wrap all calls to MapDatabase, including locking
struct MapDatabaseAccessor {
/// Lock, to be taken for any operation
std::mutex mutex;
/// Main database
MapDatabase *dbase = nullptr;
/// Fallback database for read operations
MapDatabase *dbase_ro = nullptr;
/// Load a block, taking dbase_ro into account.
/// @note call locked
void loadBlock(v3s16 blockpos, std::string &ret);
};
/*
ServerMap
@ -75,7 +88,7 @@ public:
MapBlock *createBlock(v3s16 p);
/*
Forcefully get a block from somewhere.
Forcefully get a block from somewhere (blocking!).
- Memory
- Load from disk
- Create blank filled with CONTENT_IGNORE
@ -114,9 +127,16 @@ public:
bool saveBlock(MapBlock *block) override;
static bool saveBlock(MapBlock *block, MapDatabase *db, int compression_level = -1);
MapBlock* loadBlock(v3s16 p);
// Database version
void loadBlock(std::string *blob, v3s16 p3d, MapSector *sector, bool save_after_load=false);
// Load block in a synchronous fashion
MapBlock *loadBlock(v3s16 p);
/// Load a block that was already read from disk. Used by EmergeManager.
/// @return non-null block (but can be blank)
MapBlock *loadBlock(const std::string &blob, v3s16 p, bool save_after_load=false);
// Helper for deserializing blocks from disk
// @throws SerializationError
static void deSerializeBlock(MapBlock *block, std::istream &is);
// Blocks are removed from the map but not deleted from memory until
// deleteDetachedBlocks() is called, since pointers to them may still exist
@ -185,8 +205,8 @@ private:
This is reset to false when written on disk.
*/
bool m_map_metadata_changed = true;
MapDatabase *dbase = nullptr;
MapDatabase *dbase_ro = nullptr;
MapDatabaseAccessor m_db;
// Map metrics
MetricGaugePtr m_loaded_blocks_gauge;

View file

@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "threading/thread.h"
#include "util/container.h"
#include "log.h"
#include "log_internal.h"
#include <set>
#include <sstream>

View file

@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "util/string.h"
#include <algorithm>
#include <fstream>
#include <map>
#define override_cast static_cast<override_t>

View file

@ -0,0 +1,46 @@
// Minetest
// SPDX-License-Identifier: LGPL-2.1-or-later
#pragma once
#include <cstdint>
#include <condition_variable>
/*
Fair mutex based on ticketing approach.
Satisfies `Mutex` C++11 requirements.
*/
class ordered_mutex {
public:
ordered_mutex() : next_ticket(0), counter(0) {}
void lock()
{
std::unique_lock autolock(cv_lock);
const auto ticket = next_ticket++;
cv.wait(autolock, [&] { return counter == ticket; });
}
bool try_lock()
{
std::lock_guard autolock(cv_lock);
if (counter != next_ticket)
return false;
next_ticket++;
return true;
}
void unlock()
{
{
std::lock_guard autolock(cv_lock);
counter++;
}
cv.notify_all(); // intentionally outside lock
}
private:
std::condition_variable cv;
std::mutex cv_lock;
uint_fast32_t next_ticket, counter;
};

View file

@ -25,7 +25,7 @@ DEALINGS IN THE SOFTWARE.
#include "threading/thread.h"
#include "threading/mutex_auto_lock.h"
#include "log.h"
#include "log_internal.h"
#include "porting.h"
// for setName

View file

@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "nodedef.h"
#include "itemdef.h"
#include "dummygamedef.h"
#include "log_internal.h"
#include "modchannels.h"
#include "util/numeric.h"
#include "porting.h"

View file

@ -42,6 +42,7 @@ public:
void testSafeWriteToFile();
void testCopyFileContents();
void testNonExist();
void testRecursiveDelete();
};
static TestFileSys g_test_instance;
@ -56,6 +57,7 @@ void TestFileSys::runTests(IGameDef *gamedef)
TEST(testSafeWriteToFile);
TEST(testCopyFileContents);
TEST(testNonExist);
TEST(testRecursiveDelete);
}
////////////////////////////////////////////////////////////////////////////////
@ -338,3 +340,32 @@ void TestFileSys::testNonExist()
auto ifs = open_ifstream(path.c_str(), false);
UASSERT(!ifs.good());
}
void TestFileSys::testRecursiveDelete()
{
std::string dirs[2];
dirs[0] = getTestTempDirectory() + DIR_DELIM "a";
dirs[1] = dirs[0] + DIR_DELIM "b";
std::string files[2] = {
dirs[0] + DIR_DELIM "file1",
dirs[1] + DIR_DELIM "file2"
};
for (auto &it : dirs)
fs::CreateDir(it);
for (auto &it : files)
open_ofstream(it.c_str(), false).close();
for (auto &it : dirs)
UASSERT(fs::IsDir(it));
for (auto &it : files)
UASSERT(fs::IsFile(it));
UASSERT(fs::RecursiveDelete(dirs[0]));
for (auto &it : dirs)
UASSERT(!fs::IsDir(it));
for (auto &it : files)
UASSERT(!fs::IsFile(it));
}

View file

@ -1,14 +1,14 @@
// Minetest
// SPDX-License-Identifier: LGPL-2.1-or-later
#include "CSceneManager.h"
#include "content/subgames.h"
#include "filesys.h"
#include "CReadFile.h"
#include "irr_v3d.h"
#include "irr_v2d.h"
#include "irr_ptr.h"
#include "ISkinnedMesh.h"
#include <irrlicht.h>
#include "catch.h"
@ -20,10 +20,16 @@ const auto gamespec = findSubgame("devtest");
if (!gamespec.isValid())
SKIP();
irr::scene::CSceneManager smgr(nullptr, nullptr, nullptr);
const auto loadMesh = [&smgr](const irr::io::path& filepath) {
irr::io::CReadFile file(filepath);
return smgr.getMesh(&file);
irr::SIrrlichtCreationParameters p;
p.DriverType = video::EDT_NULL;
auto *driver = irr::createDeviceEx(p);
REQUIRE(driver);
auto *smgr = driver->getSceneManager();
const auto loadMesh = [&] (const io::path& filepath) {
irr_ptr<io::IReadFile> file(driver->getFileSystem()->createAndOpenFile(filepath));
REQUIRE(file);
return smgr->getMesh(file.get());
};
const static auto model_stem = gamespec.gamemods_path +
@ -33,21 +39,21 @@ SECTION("error cases") {
const static auto invalid_model_path = gamespec.gamemods_path + DIR_DELIM + "gltf" + DIR_DELIM + "invalid" + DIR_DELIM;
SECTION("empty gltf file") {
CHECK(loadMesh(invalid_model_path + "empty.gltf") == nullptr);
CHECK(!loadMesh(invalid_model_path + "empty.gltf"));
}
SECTION("null file pointer") {
CHECK(smgr.getMesh(nullptr) == nullptr);
CHECK(!smgr->getMesh(nullptr));
}
SECTION("invalid JSON") {
CHECK(loadMesh(invalid_model_path + "json_missing_brace.gltf") == nullptr);
CHECK(!loadMesh(invalid_model_path + "json_missing_brace.gltf"));
}
// This is an example of something that should be validated by tiniergltf.
SECTION("invalid bufferview bounds")
{
CHECK(loadMesh(invalid_model_path + "invalid_bufferview_bounds.gltf") == nullptr);
CHECK(!loadMesh(invalid_model_path + "invalid_bufferview_bounds.gltf"));
}
}
@ -59,7 +65,7 @@ SECTION("minimal triangle") {
model_stem + "triangle_without_indices.gltf");
INFO(path);
const auto mesh = loadMesh(path);
REQUIRE(mesh != nullptr);
REQUIRE(mesh);
REQUIRE(mesh->getMeshBufferCount() == 1);
SECTION("vertex coordinates are correct") {
@ -82,8 +88,11 @@ SECTION("minimal triangle") {
}
SECTION("blender cube") {
const auto mesh = loadMesh(model_stem + "blender_cube.gltf");
REQUIRE(mesh != nullptr);
const auto path = GENERATE(
model_stem + "blender_cube.gltf",
model_stem + "blender_cube.glb");
const auto mesh = loadMesh(path);
REQUIRE(mesh);
REQUIRE(mesh->getMeshBufferCount() == 1);
SECTION("vertex coordinates are correct") {
REQUIRE(mesh->getMeshBuffer(0)->getVertexCount() == 24);
@ -136,7 +145,7 @@ SECTION("blender cube") {
SECTION("blender cube scaled") {
const auto mesh = loadMesh(model_stem + "blender_cube_scaled.gltf");
REQUIRE(mesh != nullptr);
REQUIRE(mesh);
REQUIRE(mesh->getMeshBufferCount() == 1);
SECTION("Scaling is correct") {
@ -157,7 +166,7 @@ SECTION("blender cube scaled") {
SECTION("blender cube matrix transform") {
const auto mesh = loadMesh(model_stem + "blender_cube_matrix_transform.gltf");
REQUIRE(mesh != nullptr);
REQUIRE(mesh);
REQUIRE(mesh->getMeshBufferCount() == 1);
SECTION("Transformation is correct") {
@ -183,7 +192,7 @@ SECTION("blender cube matrix transform") {
SECTION("snow man") {
const auto mesh = loadMesh(model_stem + "snow_man.gltf");
REQUIRE(mesh != nullptr);
REQUIRE(mesh);
REQUIRE(mesh->getMeshBufferCount() == 3);
SECTION("vertex coordinates are correct for all buffers") {
@ -338,7 +347,7 @@ SECTION("snow man") {
SECTION("simple sparse accessor")
{
const auto mesh = loadMesh(model_stem + "simple_sparse_accessor.gltf");
REQUIRE(mesh != nullptr);
REQUIRE(mesh);
const auto *vertices = reinterpret_cast<irr::video::S3DVertex *>(
mesh->getMeshBuffer(0)->getVertices());
const std::array<v3f, 14> expectedPositions = {
@ -363,4 +372,91 @@ SECTION("simple sparse accessor")
CHECK(vertices[i].Pos == expectedPositions[i]);
}
// https://github.com/KhronosGroup/glTF-Sample-Models/tree/main/2.0/SimpleSkin
SECTION("simple skin")
{
using ISkinnedMesh = irr::scene::ISkinnedMesh;
const auto mesh = loadMesh(model_stem + "simple_skin.gltf");
REQUIRE(mesh != nullptr);
auto csm = dynamic_cast<const ISkinnedMesh*>(mesh);
const auto joints = csm->getAllJoints();
REQUIRE(joints.size() == 3);
const auto findJoint = [&](const std::function<bool(ISkinnedMesh::SJoint*)> &predicate) {
for (std::size_t i = 0; i < joints.size(); ++i) {
if (predicate(joints[i])) {
return joints[i];
}
}
throw std::runtime_error("joint not found");
};
// Check the node hierarchy
const auto parent = findJoint([](auto joint) {
return !joint->Children.empty();
});
REQUIRE(parent->Children.size() == 1);
const auto child = parent->Children[0];
REQUIRE(child != parent);
SECTION("transformations are correct")
{
CHECK(parent->Animatedposition == v3f(0, 0, 0));
CHECK(parent->Animatedrotation == irr::core::quaternion());
CHECK(parent->Animatedscale == v3f(1, 1, 1));
CHECK(parent->GlobalInversedMatrix == irr::core::matrix4());
const v3f childTranslation(0, 1, 0);
CHECK(child->Animatedposition == childTranslation);
CHECK(child->Animatedrotation == irr::core::quaternion());
CHECK(child->Animatedscale == v3f(1, 1, 1));
irr::core::matrix4 inverseBindMatrix;
inverseBindMatrix.setInverseTranslation(childTranslation);
CHECK(child->GlobalInversedMatrix == inverseBindMatrix);
}
SECTION("weights are correct")
{
const auto weights = [&](const ISkinnedMesh::SJoint *joint) {
std::unordered_map<irr::u32, irr::f32> weights;
for (std::size_t i = 0; i < joint->Weights.size(); ++i) {
const auto weight = joint->Weights[i];
REQUIRE(weight.buffer_id == 0);
weights[weight.vertex_id] = weight.strength;
}
return weights;
};
const auto parentWeights = weights(parent);
const auto childWeights = weights(child);
const auto checkWeights = [&](irr::u32 index, irr::f32 parentWeight, irr::f32 childWeight) {
const auto getWeight = [](auto weights, auto index) {
const auto it = weights.find(index);
return it == weights.end() ? 0.0f : it->second;
};
CHECK(getWeight(parentWeights, index) == parentWeight);
CHECK(getWeight(childWeights, index) == childWeight);
};
checkWeights(0, 1.00, 0.00);
checkWeights(1, 1.00, 0.00);
checkWeights(2, 0.75, 0.25);
checkWeights(3, 0.75, 0.25);
checkWeights(4, 0.50, 0.50);
checkWeights(5, 0.50, 0.50);
checkWeights(6, 0.25, 0.75);
checkWeights(7, 0.25, 0.75);
checkWeights(8, 0.00, 1.00);
checkWeights(9, 0.00, 1.00);
}
SECTION("there should be a third node not involved in skinning")
{
const auto other = findJoint([&](auto joint) {
return joint != child && joint != parent;
});
CHECK(other->Weights.empty());
}
}
driver->closeDevice();
driver->drop();
}

View file

@ -122,7 +122,7 @@ void TestServerModManager::testGetMods()
ServerModManager sm(m_worlddir);
const auto &mods = sm.getMods();
// `ls ./games/devtest/mods | wc -l` + 1 (test mod)
UASSERTEQ(std::size_t, mods.size(), 32 + 1);
UASSERTEQ(std::size_t, mods.size(), 33 + 1);
// Ensure we found basenodes mod (part of devtest)
// and test_mod (for testing MINETEST_MOD_PATH).

View file

@ -23,6 +23,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#include "log.h"
#include "string.h"
#include <sstream>
#include <memory>
std::string colorize_url(const std::string &url)
{

View file

@ -35,7 +35,7 @@ u64 TimeTaker::stop(bool quiet)
if (m_result != nullptr) {
(*m_result) += dtime;
} else {
if (!quiet) {
if (!quiet && !m_name.empty()) {
infostream << m_name << " took "
<< dtime << TimePrecision_units[m_precision] << std::endl;
}