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:
commit
b6c099073f
183 changed files with 3919 additions and 1642 deletions
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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 &&
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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, {}, {}, {}));
|
||||
|
|
|
@ -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 {
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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");
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -1146,7 +1146,7 @@ bool GUIHyperText::OnEvent(const SEvent &event)
|
|||
}
|
||||
}
|
||||
|
||||
break;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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; };
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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};
|
||||
};
|
||||
|
|
|
@ -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
201
src/log.h
|
@ -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
189
src/log_internal.h
Normal 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;
|
|
@ -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();
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
67
src/network/networkprotocol.cpp
Normal file
67
src/network/networkprotocol.cpp
Normal 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;
|
|
@ -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")
|
||||
};
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 */
|
||||
/******************************************************************************/
|
||||
|
|
|
@ -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!
|
||||
|
|
10
src/player.h
10
src/player.h
|
@ -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;
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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--;
|
||||
|
||||
|
|
141
src/script/lua_api/l_ipc.cpp
Normal file
141
src/script/lua_api/l_ipc.cpp
Normal 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);
|
||||
}
|
17
src/script/lua_api/l_ipc.h
Normal file
17
src/script/lua_api/l_ipc.h
Normal 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);
|
||||
};
|
|
@ -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";
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
130
src/server.cpp
130
src/server.cpp
|
@ -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;
|
||||
}
|
||||
|
|
63
src/server.h
63
src/server.h
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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++;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
46
src/threading/ordered_mutex.h
Normal file
46
src/threading/ordered_mutex.h
Normal 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;
|
||||
};
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue