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

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

This commit is contained in:
Gefüllte Taubenbrust 2025-05-20 20:01:46 +02:00
commit e05544b84e
340 changed files with 73244 additions and 25967 deletions

View file

@ -375,6 +375,9 @@ endif()
check_include_files(endian.h HAVE_ENDIAN_H)
if((NOT PROJECT_SOURCE_DIR STREQUAL PROJECT_BINARY_DIR) AND EXISTS "${PROJECT_SOURCE_DIR}/cmake_config.h")
message(FATAL_ERROR "You are doing an out-of-tree build, but build artifacts are left in-tree. This will break the build!")
endif()
configure_file(
"${PROJECT_SOURCE_DIR}/cmake_config.h.in"
"${PROJECT_BINARY_DIR}/cmake_config.h"
@ -591,9 +594,12 @@ if(USE_CURL)
endif()
# When cross-compiling assume the user doesn't want to run the executable anyway,
# otherwise place it in <source dir>/bin/ since Luanti can only run from there.
if(NOT CMAKE_CROSSCOMPILING)
# When cross-compiling place the executable in <build dir>/bin so that multiple
# targets can be built from the same source folder. Otherwise, place it in
# <source dir>/bin/ since Luanti can only run from there.
if(CMAKE_CROSSCOMPILING)
set(EXECUTABLE_OUTPUT_PATH "${CMAKE_BINARY_DIR}/bin")
else()
set(EXECUTABLE_OUTPUT_PATH "${CMAKE_SOURCE_DIR}/bin")
endif()
@ -1114,20 +1120,17 @@ elseif (USE_GETTEXT)
set_mo_paths(MO_BUILD_PATH MO_DEST_PATH ${LOCALE})
set(MO_FILE_PATH "${MO_BUILD_PATH}/${PROJECT_NAME}.mo")
add_custom_command(OUTPUT ${MO_BUILD_PATH}
COMMAND ${CMAKE_COMMAND} -E make_directory ${MO_BUILD_PATH}
COMMENT "mo-update [${LOCALE}]: Creating locale directory.")
add_custom_command(
OUTPUT ${MO_FILE_PATH}
COMMAND ${CMAKE_COMMAND} -E make_directory ${MO_BUILD_PATH}
COMMAND ${GETTEXT_MSGFMT} -o ${MO_FILE_PATH} ${PO_FILE_PATH}
DEPENDS ${MO_BUILD_PATH} ${PO_FILE_PATH}
DEPENDS ${PO_FILE_PATH}
WORKING_DIRECTORY "${GETTEXT_PO_PATH}/${LOCALE}"
COMMENT "mo-update [${LOCALE}]: Creating mo file."
COMMENT "mo-update [${LOCALE}]: Creating mo file"
)
list(APPEND MO_FILES ${MO_FILE_PATH})
endforeach()
add_custom_target(translations ALL COMMENT "mo update" DEPENDS ${MO_FILES})
add_custom_target(translations ALL COMMENT "mo-update" DEPENDS ${MO_FILES})
endif()

View file

@ -367,9 +367,6 @@ Client::~Client()
for (auto &csp : m_sounds_client_to_server)
m_sound->freeId(csp.first);
m_sounds_client_to_server.clear();
// Go back to our mainmenu fonts
g_fontengine->clearMediaFonts();
}
void Client::connect(const Address &address, const std::string &address_name)

View file

@ -2,7 +2,6 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
// Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
#include "IAttributes.h"
#include "gui/mainmenumanager.h"
#include "clouds.h"
#include "gui/touchcontrols.h"
@ -111,9 +110,6 @@ bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args)
init_input();
m_rendering_engine->get_scene_manager()->getParameters()->
setAttribute(scene::ALLOW_ZWRITE_ON_TRANSPARENT, true);
guienv = m_rendering_engine->get_gui_env();
config_guienv();
g_settings->registerChangedCallback("dpi_change_notifier", setting_changed_callback, this);
@ -126,7 +122,7 @@ bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args)
// This is only global so it can be used by RenderingEngine::draw_load_screen().
assert(!g_menucloudsmgr && !g_menuclouds);
std::unique_ptr<IWritableShaderSource> ssrc(createShaderSource());
ssrc->addShaderConstantSetterFactory(new FogShaderConstantSetterFactory());
ssrc->addShaderUniformSetterFactory(new FogShaderUniformSetterFactory());
g_menucloudsmgr = m_rendering_engine->get_scene_manager()->createNewSceneManager();
g_menuclouds = new Clouds(g_menucloudsmgr, ssrc.get(), -1, rand());
g_menuclouds->setHeight(100.0f);
@ -535,6 +531,16 @@ void ClientLauncher::main_menu(MainMenuData *menudata)
{
bool *kill = porting::signal_handler_killstatus();
video::IVideoDriver *driver = m_rendering_engine->get_video_driver();
auto *device = m_rendering_engine->get_raw_device();
// Wait until app is in foreground because of #15883
infostream << "Waiting for app to be in foreground" << std::endl;
while (m_rendering_engine->run() && !*kill) {
if (device->isWindowVisible())
break;
sleep_ms(25);
}
infostream << "Waited for app to be in foreground" << std::endl;
infostream << "Waiting for other menus" << std::endl;
auto framemarker = FrameMarker("ClientLauncher::main_menu()-wait-frame").started();
@ -552,7 +558,7 @@ void ClientLauncher::main_menu(MainMenuData *menudata)
framemarker.end();
infostream << "Waited for other menus" << std::endl;
auto *cur_control = m_rendering_engine->get_raw_device()->getCursorControl();
auto *cur_control = device->getCursorControl();
if (cur_control) {
// Cursor can be non-visible when coming from the game
cur_control->setVisible(true);

View file

@ -120,6 +120,11 @@ namespace {
inline T subtract_or_zero(T a, T b) {
return b >= a ? T(0) : (a - b);
}
// file-scope thread-local instances of the above two data structures, because
// allocating memory in a hot path can be expensive.
thread_local MeshBufListMaps tl_meshbuflistmaps;
thread_local DrawDescriptorList tl_drawdescriptorlist;
}
void CachedMeshBuffer::drop()
@ -800,6 +805,27 @@ void MeshBufListMaps::addFromBlock(v3s16 block_pos, MapBlockMesh *block_mesh,
}
}
namespace {
// there is no convenient scope this would fit, so it's global
struct {
u32 total = 0, cache_miss = 0;
inline void increment(bool hit)
{
total++;
cache_miss += hit ? 0 : 1;
}
inline void commit(Profiler *profiler)
{
if (total == 0)
return;
float rate = (total - cache_miss) / (float)total;
profiler->avg("CM::transformBuffers...: cache hit rate [%]", 100 * rate);
*this = {0, 0};
}
} buffer_transform_stats;
}
/**
* Copy a list of mesh buffers into the draw order, while potentially
* merging some.
@ -874,7 +900,7 @@ static u32 transformBuffersToDrawOrder(
// try to take from cache
auto it2 = dynamic_buffers.find(key);
if (it2 != dynamic_buffers.end()) {
g_profiler->avg("CM::transformBuffersToDO: cache hit rate", 1);
buffer_transform_stats.increment(true);
const auto &use_mat = to_merge.front().second->getMaterial();
assert(!it2->second.buf.empty());
for (auto *buf : it2->second.buf) {
@ -884,7 +910,7 @@ static u32 transformBuffersToDrawOrder(
}
it2->second.age = 0;
} else if (!key.empty()) {
g_profiler->avg("CM::transformBuffersToDO: cache hit rate", 0);
buffer_transform_stats.increment(false);
// merge and save to cache
auto &put_buffers = dynamic_buffers[key];
scene::SMeshBuffer *tmp = nullptr;
@ -978,8 +1004,10 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
*/
TimeTaker tt_collect("");
MeshBufListMaps grouped_buffers;
DrawDescriptorList draw_order;
MeshBufListMaps &grouped_buffers = tl_meshbuflistmaps;
DrawDescriptorList &draw_order = tl_drawdescriptorlist;
grouped_buffers.clear();
draw_order.clear();
auto is_frustum_culled = m_client->getCamera()->getFrustumCuller();
@ -1107,6 +1135,8 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
}
}
g_profiler->avg(prefix + "merged buffers in cache [#]", cached_count);
buffer_transform_stats.commit(g_profiler);
}
if (pass == scene::ESNRP_TRANSPARENT) {
@ -1375,8 +1405,10 @@ void ClientMap::renderMapShadows(video::IVideoDriver *driver,
return intToFloat(mesh_grid.getMeshPos(pos) * MAP_BLOCKSIZE - m_camera_offset, BS);
};
MeshBufListMaps grouped_buffers;
DrawDescriptorList draw_order;
MeshBufListMaps &grouped_buffers = tl_meshbuflistmaps;
DrawDescriptorList &draw_order = tl_drawdescriptorlist;
grouped_buffers.clear();
draw_order.clear();
std::size_t count = 0;
std::size_t meshes_per_frame = m_drawlist_shadow.size() / total_frames + 1;

View file

@ -144,6 +144,31 @@ void SmoothTranslatorWrappedv3f::translate(f32 dtime)
Other stuff
*/
static bool setMaterialTextureAndFilters(video::SMaterial &material,
const std::string &texturestring, ITextureSource *tsrc)
{
bool use_trilinear_filter = g_settings->getBool("trilinear_filter");
bool use_bilinear_filter = g_settings->getBool("bilinear_filter");
bool use_anisotropic_filter = g_settings->getBool("anisotropic_filter");
video::ITexture *texture = tsrc->getTextureForMesh(texturestring);
if (!texture)
return false;
material.setTexture(0, texture);
// don't filter low-res textures, makes them look blurry
const core::dimension2d<u32> &size = texture->getOriginalSize();
if (std::min(size.Width, size.Height) < TEXTURE_FILTER_MIN_SIZE)
use_trilinear_filter = use_bilinear_filter = false;
material.forEachTexture([=] (auto &tex) {
setMaterialFilters(tex, use_bilinear_filter, use_trilinear_filter,
use_anisotropic_filter);
});
return true;
}
static void setBillboardTextureMatrix(scene::IBillboardSceneNode *bill,
float txs, float tys, int col, int row)
{
@ -386,6 +411,32 @@ void GenericCAO::setChildrenVisible(bool toset)
void GenericCAO::setAttachment(object_t parent_id, const std::string &bone,
v3f position, v3f rotation, bool force_visible)
{
// Do checks to avoid circular references
// See similar check in `UnitSAO::setAttachment` (but with different types).
{
auto *obj = m_env->getActiveObject(parent_id);
if (obj == this) {
assert(false);
return;
}
bool problem = false;
if (obj) {
// The chain of wanted parent must not refer or contain "this"
for (obj = obj->getParent(); obj; obj = obj->getParent()) {
if (obj == this) {
problem = true;
break;
}
}
}
if (problem) {
warningstream << "Network or mod bug: "
<< "Attempted to attach object " << m_id << " to parent "
<< parent_id << " but former is an (in)direct parent of latter." << std::endl;
return;
}
}
const auto old_parent = m_attachment_parent_id;
m_attachment_parent_id = parent_id;
m_attachment_bone = bone;
@ -1238,10 +1289,6 @@ void GenericCAO::updateTextures(std::string mod)
{
ITextureSource *tsrc = m_client->tsrc();
bool use_trilinear_filter = g_settings->getBool("trilinear_filter");
bool use_bilinear_filter = g_settings->getBool("bilinear_filter");
bool use_anisotropic_filter = g_settings->getBool("anisotropic_filter");
m_previous_texture_modifier = m_current_texture_modifier;
m_current_texture_modifier = mod;
@ -1254,12 +1301,7 @@ void GenericCAO::updateTextures(std::string mod)
video::SMaterial &material = m_spritenode->getMaterial(0);
material.MaterialType = m_material_type;
material.setTexture(0, tsrc->getTextureForMesh(texturestring));
material.forEachTexture([=] (auto &tex) {
setMaterialFilters(tex, use_bilinear_filter, use_trilinear_filter,
use_anisotropic_filter);
});
setMaterialTextureAndFilters(material, texturestring, tsrc);
}
}
@ -1273,29 +1315,12 @@ void GenericCAO::updateTextures(std::string mod)
if (texturestring.empty())
continue; // Empty texture string means don't modify that material
texturestring += mod;
video::ITexture *texture = tsrc->getTextureForMesh(texturestring);
if (!texture) {
errorstream<<"GenericCAO::updateTextures(): Could not load texture "<<texturestring<<std::endl;
continue;
}
// Set material flags and texture
video::SMaterial &material = m_animated_meshnode->getMaterial(i);
material.MaterialType = m_material_type;
material.TextureLayers[0].Texture = texture;
material.BackfaceCulling = m_prop.backface_culling;
// don't filter low-res textures, makes them look blurry
// player models have a res of 64
const core::dimension2d<u32> &size = texture->getOriginalSize();
const u32 res = std::min(size.Height, size.Width);
use_trilinear_filter &= res > 64;
use_bilinear_filter &= res > 64;
material.forEachTexture([=] (auto &tex) {
setMaterialFilters(tex, use_bilinear_filter, use_trilinear_filter,
use_anisotropic_filter);
});
setMaterialTextureAndFilters(material, texturestring, tsrc);
}
}
}
@ -1313,13 +1338,7 @@ void GenericCAO::updateTextures(std::string mod)
// Set material flags and texture
video::SMaterial &material = m_meshnode->getMaterial(i);
material.MaterialType = m_material_type;
material.setTexture(0, tsrc->getTextureForMesh(texturestring));
material.getTextureMatrix(0).makeIdentity();
material.forEachTexture([=] (auto &tex) {
setMaterialFilters(tex, use_bilinear_filter, use_trilinear_filter,
use_anisotropic_filter);
});
setMaterialTextureAndFilters(material, texturestring, tsrc);
}
} else if (m_prop.visual == OBJECTVISUAL_UPRIGHT_SPRITE) {
scene::IMesh *mesh = m_meshnode->getMesh();
@ -1330,12 +1349,7 @@ void GenericCAO::updateTextures(std::string mod)
tname += mod;
auto &material = m_meshnode->getMaterial(0);
material.setTexture(0, tsrc->getTextureForMesh(tname));
material.forEachTexture([=] (auto &tex) {
setMaterialFilters(tex, use_bilinear_filter, use_trilinear_filter,
use_anisotropic_filter);
});
setMaterialTextureAndFilters(material, tname, tsrc);
}
{
std::string tname = "no_texture.png";
@ -1346,12 +1360,7 @@ void GenericCAO::updateTextures(std::string mod)
tname += mod;
auto &material = m_meshnode->getMaterial(1);
material.setTexture(0, tsrc->getTextureForMesh(tname));
material.forEachTexture([=] (auto &tex) {
setMaterialFilters(tex, use_bilinear_filter, use_trilinear_filter,
use_anisotropic_filter);
});
setMaterialTextureAndFilters(material, tname, tsrc);
}
// Set mesh color (only if lighting is disabled)
if (m_prop.glow < 0)

View file

@ -1676,7 +1676,7 @@ void MapblockMeshGenerator::drawMeshNode()
if (cur_node.f->mesh_ptr) {
// clone and rotate mesh
mesh = cloneMesh(cur_node.f->mesh_ptr);
mesh = cloneStaticMesh(cur_node.f->mesh_ptr);
bool modified = true;
if (facedir)
rotateMeshBy6dFacedir(mesh, facedir);

View file

@ -5,7 +5,6 @@
#include "game.h"
#include <cmath>
#include "IAttributes.h"
#include "client/renderingengine.h"
#include "camera.h"
#include "client.h"
@ -188,7 +187,7 @@ public:
typedef s32 SamplerLayer_t;
class GameGlobalShaderConstantSetter : public IShaderConstantSetter
class GameGlobalShaderUniformSetter : public IShaderUniformSetter
{
Sky *m_sky;
Client *m_client;
@ -206,10 +205,6 @@ class GameGlobalShaderConstantSetter : public IShaderConstantSetter
CachedPixelShaderSetting<float, 3> m_camera_offset_pixel{ "cameraOffset" };
CachedVertexShaderSetting<float, 3> m_camera_position_vertex{"cameraPosition"};
CachedPixelShaderSetting<float, 3> m_camera_position_pixel{"cameraPosition"};
CachedPixelShaderSetting<SamplerLayer_t> m_texture0{"texture0"};
CachedPixelShaderSetting<SamplerLayer_t> m_texture1{"texture1"};
CachedPixelShaderSetting<SamplerLayer_t> m_texture2{"texture2"};
CachedPixelShaderSetting<SamplerLayer_t> m_texture3{"texture3"};
CachedVertexShaderSetting<float, 2> m_texel_size0_vertex{"texelSize0"};
CachedPixelShaderSetting<float, 2> m_texel_size0_pixel{"texelSize0"};
v2f m_texel_size0;
@ -264,12 +259,12 @@ public:
static void settingsCallback(const std::string &name, void *userdata)
{
reinterpret_cast<GameGlobalShaderConstantSetter*>(userdata)->onSettingsChange(name);
reinterpret_cast<GameGlobalShaderUniformSetter*>(userdata)->onSettingsChange(name);
}
void setSky(Sky *sky) { m_sky = sky; }
GameGlobalShaderConstantSetter(Sky *sky, Client *client) :
GameGlobalShaderUniformSetter(Sky *sky, Client *client) :
m_sky(sky),
m_client(client)
{
@ -283,12 +278,12 @@ public:
m_color_grading_enabled = g_settings->getBool("enable_color_grading");
}
~GameGlobalShaderConstantSetter()
~GameGlobalShaderUniformSetter()
{
g_settings->deregisterAllChangedCallbacks(this);
}
void onSetConstants(video::IMaterialRendererServices *services) override
void onSetUniforms(video::IMaterialRendererServices *services) override
{
u32 daynight_ratio = (float)m_client->getEnv().getDayNightRatio();
video::SColorf sunlight;
@ -319,16 +314,6 @@ public:
m_camera_position_vertex.set(camera_position, services);
m_camera_position_pixel.set(camera_position, services);
SamplerLayer_t tex_id;
tex_id = 0;
m_texture0.set(&tex_id, services);
tex_id = 1;
m_texture1.set(&tex_id, services);
tex_id = 2;
m_texture2.set(&tex_id, services);
tex_id = 3;
m_texture3.set(&tex_id, services);
m_texel_size0_vertex.set(m_texel_size0, services);
m_texel_size0_pixel.set(m_texel_size0, services);
@ -440,34 +425,118 @@ public:
};
class GameGlobalShaderConstantSetterFactory : public IShaderConstantSetterFactory
class GameGlobalShaderUniformSetterFactory : public IShaderUniformSetterFactory
{
Sky *m_sky = nullptr;
Client *m_client;
std::vector<GameGlobalShaderConstantSetter *> created_nosky;
std::vector<GameGlobalShaderUniformSetter *> created_nosky;
public:
GameGlobalShaderConstantSetterFactory(Client *client) :
GameGlobalShaderUniformSetterFactory(Client *client) :
m_client(client)
{}
void setSky(Sky *sky)
{
m_sky = sky;
for (GameGlobalShaderConstantSetter *ggscs : created_nosky) {
for (GameGlobalShaderUniformSetter *ggscs : created_nosky) {
ggscs->setSky(m_sky);
}
created_nosky.clear();
}
virtual IShaderConstantSetter* create()
virtual IShaderUniformSetter* create()
{
auto *scs = new GameGlobalShaderConstantSetter(m_sky, m_client);
auto *scs = new GameGlobalShaderUniformSetter(m_sky, m_client);
if (!m_sky)
created_nosky.push_back(scs);
return scs;
}
};
class NodeShaderConstantSetter : public IShaderConstantSetter
{
public:
NodeShaderConstantSetter() = default;
~NodeShaderConstantSetter() = default;
void onGenerate(const std::string &name, ShaderConstants &constants) override
{
if (constants.find("DRAWTYPE") == constants.end())
return; // not a node shader
[[maybe_unused]] const auto drawtype =
static_cast<NodeDrawType>(std::get<int>(constants["DRAWTYPE"]));
[[maybe_unused]] const auto material_type =
static_cast<MaterialType>(std::get<int>(constants["MATERIAL_TYPE"]));
#define PROVIDE(constant) constants[ #constant ] = (int)constant
PROVIDE(NDT_NORMAL);
PROVIDE(NDT_AIRLIKE);
PROVIDE(NDT_LIQUID);
PROVIDE(NDT_FLOWINGLIQUID);
PROVIDE(NDT_GLASSLIKE);
PROVIDE(NDT_ALLFACES);
PROVIDE(NDT_ALLFACES_OPTIONAL);
PROVIDE(NDT_TORCHLIKE);
PROVIDE(NDT_SIGNLIKE);
PROVIDE(NDT_PLANTLIKE);
PROVIDE(NDT_FENCELIKE);
PROVIDE(NDT_RAILLIKE);
PROVIDE(NDT_NODEBOX);
PROVIDE(NDT_GLASSLIKE_FRAMED);
PROVIDE(NDT_FIRELIKE);
PROVIDE(NDT_GLASSLIKE_FRAMED_OPTIONAL);
PROVIDE(NDT_PLANTLIKE_ROOTED);
PROVIDE(TILE_MATERIAL_BASIC);
PROVIDE(TILE_MATERIAL_ALPHA);
PROVIDE(TILE_MATERIAL_LIQUID_TRANSPARENT);
PROVIDE(TILE_MATERIAL_LIQUID_OPAQUE);
PROVIDE(TILE_MATERIAL_WAVING_LEAVES);
PROVIDE(TILE_MATERIAL_WAVING_PLANTS);
PROVIDE(TILE_MATERIAL_OPAQUE);
PROVIDE(TILE_MATERIAL_WAVING_LIQUID_BASIC);
PROVIDE(TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT);
PROVIDE(TILE_MATERIAL_WAVING_LIQUID_OPAQUE);
PROVIDE(TILE_MATERIAL_PLAIN);
PROVIDE(TILE_MATERIAL_PLAIN_ALPHA);
#undef PROVIDE
bool enable_waving_water = g_settings->getBool("enable_waving_water");
constants["ENABLE_WAVING_WATER"] = enable_waving_water ? 1 : 0;
if (enable_waving_water) {
constants["WATER_WAVE_HEIGHT"] = g_settings->getFloat("water_wave_height");
constants["WATER_WAVE_LENGTH"] = g_settings->getFloat("water_wave_length");
constants["WATER_WAVE_SPEED"] = g_settings->getFloat("water_wave_speed");
}
switch (material_type) {
case TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT:
case TILE_MATERIAL_WAVING_LIQUID_OPAQUE:
case TILE_MATERIAL_WAVING_LIQUID_BASIC:
constants["MATERIAL_WAVING_LIQUID"] = 1;
break;
default:
constants["MATERIAL_WAVING_LIQUID"] = 0;
break;
}
switch (material_type) {
case TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT:
case TILE_MATERIAL_WAVING_LIQUID_OPAQUE:
case TILE_MATERIAL_WAVING_LIQUID_BASIC:
case TILE_MATERIAL_LIQUID_TRANSPARENT:
constants["MATERIAL_WATER_REFLECTIONS"] = 1;
break;
default:
constants["MATERIAL_WATER_REFLECTIONS"] = 0;
break;
}
constants["ENABLE_WAVING_LEAVES"] = g_settings->getBool("enable_waving_leaves") ? 1 : 0;
constants["ENABLE_WAVING_PLANTS"] = g_settings->getBool("enable_waving_plants") ? 1 : 0;
}
};
/****************************************************************************
****************************************************************************/
@ -882,11 +951,6 @@ Game::Game() :
}
/****************************************************************************
MinetestApp Public
****************************************************************************/
Game::~Game()
{
delete client;
@ -939,8 +1003,6 @@ bool Game::startup(bool *kill,
driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, g_settings->getBool("mip_map"));
smgr->getParameters()->setAttribute(scene::OBJ_LOADER_IGNORE_MATERIAL_FILES, true);
// Reinit runData
runData = GameRunData();
runData.time_from_last_punch = 10.0;
@ -1001,6 +1063,10 @@ void Game::run()
const bool initial_window_maximized = !g_settings->getBool("fullscreen") &&
g_settings->getBool("window_maximized");
#ifdef __ANDROID__
porting::setPlayingNowNotification(true);
#endif
auto framemarker = FrameMarker("Game::run()-frame").started();
while (m_rendering_engine->run()
@ -1083,32 +1149,35 @@ void Game::run()
framemarker.end();
#ifdef __ANDROID__
porting::setPlayingNowNotification(false);
#endif
RenderingEngine::autosaveScreensizeAndCo(initial_screen_size, initial_window_maximized);
}
void Game::shutdown()
{
// Clear text when exiting.
// Delete text and menus first
m_game_ui->clearText();
m_game_formspec.reset();
while (g_menumgr.menuCount() > 0) {
g_menumgr.deleteFront();
}
if (g_touchcontrols)
g_touchcontrols->hide();
// only if the shutdown progress bar isn't shown yet
if (m_shutdown_progress == 0.0f)
showOverlayMessage(N_("Shutting down..."), 0, 0);
clouds.reset();
gui_chat_console.reset();
sky.reset();
/* cleanup menus */
while (g_menumgr.menuCount() > 0) {
g_menumgr.deleteFront();
}
// only if the shutdown progress bar isn't shown yet
if (m_shutdown_progress == 0.0f)
showOverlayMessage(N_("Shutting down..."), 0, 0);
chat_backend->addMessage(L"", L"# Disconnected.");
chat_backend->addMessage(L"", L"");
@ -1334,11 +1403,13 @@ bool Game::createClient(const GameStartData &start_data)
return false;
}
auto *scsf = new GameGlobalShaderConstantSetterFactory(client);
shader_src->addShaderConstantSetterFactory(scsf);
shader_src->addShaderConstantSetter(new NodeShaderConstantSetter());
shader_src->addShaderConstantSetterFactory(
new FogShaderConstantSetterFactory());
auto *scsf = new GameGlobalShaderUniformSetterFactory(client);
shader_src->addShaderUniformSetterFactory(scsf);
shader_src->addShaderUniformSetterFactory(
new FogShaderUniformSetterFactory());
ShadowRenderer::preInit(shader_src);

View file

@ -16,7 +16,6 @@
#include "gui/touchcontrols.h"
#include "gui/touchscreeneditor.h"
#include "gui/guiPasswordChange.h"
#include "gui/guiKeyChangeMenu.h"
#include "gui/guiPasswordChange.h"
#include "gui/guiOpenURL.h"
#include "gui/guiVolumeChange.h"
@ -205,6 +204,9 @@ void GameFormSpec::init(Client *client, RenderingEngine *rendering_engine, Input
m_input = input;
m_pause_script = std::make_unique<PauseMenuScripting>(client);
m_pause_script->loadBuiltin();
// Make sure any remaining game callback requests are cleared out.
*g_gamecallback = MainGameCallback();
}
void GameFormSpec::deleteFormspec()
@ -213,20 +215,22 @@ void GameFormSpec::deleteFormspec()
m_formspec->drop();
m_formspec = nullptr;
}
m_formname.clear();
}
GameFormSpec::~GameFormSpec() {
void GameFormSpec::reset()
{
if (m_formspec)
m_formspec->quitMenu();
this->deleteFormspec();
deleteFormspec();
}
bool GameFormSpec::handleEmptyFormspec(const std::string &formspec, const std::string &formname)
{
if (formspec.empty()) {
if (m_formspec && (formname.empty() || formname == m_formname)) {
m_formspec->quitMenu();
GUIModalMenu *menu = g_menumgr.tryGetTopMenu();
if (menu && (formname.empty() || formname == menu->getName())) {
// `m_formspec` will be fixed up in `GameFormSpec::update()`
menu->quitMenu();
}
return true;
}
@ -243,10 +247,11 @@ void GameFormSpec::showFormSpec(const std::string &formspec, const std::string &
TextDestPlayerInventory *txt_dst =
new TextDestPlayerInventory(m_client, formname);
m_formname = formname;
// Replace the currently open formspec
GUIFormSpecMenu::create(m_formspec, m_client, m_rendering_engine->get_gui_env(),
&m_input->joystick, fs_src, txt_dst, m_client->getFormspecPrepend(),
m_client->getSoundManager());
m_formspec->setName(formname);
}
void GameFormSpec::showCSMFormSpec(const std::string &formspec, const std::string &formname)
@ -258,10 +263,10 @@ void GameFormSpec::showCSMFormSpec(const std::string &formspec, const std::strin
LocalScriptingFormspecHandler *txt_dst =
new LocalScriptingFormspecHandler(formname, m_client->getScript());
m_formname = formname;
GUIFormSpecMenu::create(m_formspec, m_client, m_rendering_engine->get_gui_env(),
&m_input->joystick, fs_src, txt_dst, m_client->getFormspecPrepend(),
m_client->getSoundManager());
m_formspec->setName(formname);
}
void GameFormSpec::showPauseMenuFormSpec(const std::string &formspec, const std::string &formname)
@ -270,20 +275,25 @@ void GameFormSpec::showPauseMenuFormSpec(const std::string &formspec, const std:
// the in-game settings formspec.
// Neither CSM nor the server must be allowed to mess with it.
if (handleEmptyFormspec(formspec, formname))
// If we send updated formspec contents, we can either (1) recycle the old
// GUIFormSpecMenu or (2) close the old and open a new one. This is option 2.
(void)handleEmptyFormspec("", formname);
if (formspec.empty())
return;
FormspecFormSource *fs_src = new FormspecFormSource(formspec);
LocalScriptingFormspecHandler *txt_dst =
new LocalScriptingFormspecHandler(formname, m_pause_script.get());
m_formname = formname;
GUIFormSpecMenu::create(m_formspec, m_client, m_rendering_engine->get_gui_env(),
GUIFormSpecMenu *fs = nullptr;
GUIFormSpecMenu::create(fs, m_client, m_rendering_engine->get_gui_env(),
// Ignore formspec prepend.
&m_input->joystick, fs_src, txt_dst, "",
m_client->getSoundManager());
m_formspec->doPause = true;
fs->setName(formname);
fs->doPause = true;
fs->drop(); // 1 reference held by `g_menumgr`
}
void GameFormSpec::showNodeFormspec(const std::string &formspec, const v3s16 &nodepos)
@ -297,7 +307,6 @@ void GameFormSpec::showNodeFormspec(const std::string &formspec, const v3s16 &no
&m_client->getEnv().getClientMap(), nodepos);
TextDest *txt_dst = new TextDestNodeMetadata(nodepos, m_client);
m_formname = "";
GUIFormSpecMenu::create(m_formspec, m_client, m_rendering_engine->get_gui_env(),
&m_input->joystick, fs_src, txt_dst, m_client->getFormspecPrepend(),
m_client->getSoundManager());
@ -334,7 +343,7 @@ void GameFormSpec::showPlayerInventory()
}
TextDest *txt_dst = new TextDestPlayerInventory(m_client);
m_formname = "";
GUIFormSpecMenu::create(m_formspec, m_client, m_rendering_engine->get_gui_env(),
&m_input->joystick, fs_src, txt_dst, m_client->getFormspecPrepend(),
m_client->getSoundManager());
@ -535,12 +544,6 @@ bool GameFormSpec::handleCallbacks()
g_gamecallback->changevolume_requested = false;
}
if (g_gamecallback->keyconfig_requested) {
(void)make_irr<GUIKeyChangeMenu>(guienv, guiroot, -1,
&g_menumgr, texture_src);
g_gamecallback->keyconfig_requested = false;
}
if (g_gamecallback->touchscreenlayout_requested) {
(new GUITouchscreenLayout(guienv, guiroot, -1,
&g_menumgr, texture_src))->drop();
@ -553,11 +556,6 @@ bool GameFormSpec::handleCallbacks()
g_gamecallback->show_open_url_dialog.clear();
}
if (g_gamecallback->keyconfig_changed) {
m_input->reloadKeybindings(); // update the cache with new settings
g_gamecallback->keyconfig_changed = false;
}
return true;
}

View file

@ -26,7 +26,7 @@ struct GameFormSpec
{
void init(Client *client, RenderingEngine *rendering_engine, InputHandler *input);
~GameFormSpec();
~GameFormSpec() { reset(); }
void showFormSpec(const std::string &formspec, const std::string &formname);
void showCSMFormSpec(const std::string &formspec, const std::string &formname);
@ -43,6 +43,7 @@ struct GameFormSpec
void disableDebugView();
bool handleCallbacks();
void reset();
#ifdef __ANDROID__
// Returns false if no formspec open
@ -55,9 +56,9 @@ private:
InputHandler *m_input;
std::unique_ptr<PauseMenuScripting> m_pause_script;
// Default: "". If other than "": Empty show_formspec packets will only
// close the formspec when the formname matches
std::string m_formname;
/// The currently open formspec that is not a submenu of the pause menu
/// FIXME: Layering is already managed by `GUIModalMenu` (`g_menumgr`), hence this
/// variable should be removed in long-term.
GUIFormSpecMenu *m_formspec = nullptr;
bool handleEmptyFormspec(const std::string &formspec, const std::string &formname);

View file

@ -229,8 +229,8 @@ static video::SColor imageAverageColorInline(const video::IImage *src)
// limit runtime cost
const u32 stepx = std::max(1U, dim.Width / 16),
stepy = std::max(1U, dim.Height / 16);
for (u32 x = 0; x < dim.Width; x += stepx) {
for (u32 y = 0; y < dim.Height; y += stepy) {
for (u32 y = 0; y < dim.Height; y += stepy) {
for (u32 x = 0; x < dim.Width; x += stepx) {
video::SColor c = get_pixel(x, y);
if (c.getAlpha() > 0) {
total++;
@ -261,15 +261,15 @@ video::SColor imageAverageColor(const video::IImage *img)
void imageScaleNNAA(video::IImage *src, const core::rect<s32> &srcrect, video::IImage *dest)
{
double sx, sy, minsx, maxsx, minsy, maxsy, area, ra, ga, ba, aa, pw, ph, pa;
f32 sx, sy, minsx, maxsx, minsy, maxsy, area, ra, ga, ba, aa, pw, ph, pa;
u32 dy, dx;
video::SColor pxl;
// Cache rectangle boundaries.
double sox = srcrect.UpperLeftCorner.X * 1.0;
double soy = srcrect.UpperLeftCorner.Y * 1.0;
double sw = srcrect.getWidth() * 1.0;
double sh = srcrect.getHeight() * 1.0;
const f32 sox = srcrect.UpperLeftCorner.X;
const f32 soy = srcrect.UpperLeftCorner.Y;
const f32 sw = srcrect.getWidth();
const f32 sh = srcrect.getHeight();
// Walk each destination image pixel.
// Note: loop y around x for better cache locality.
@ -302,8 +302,8 @@ void imageScaleNNAA(video::IImage *src, const core::rect<s32> &srcrect, video::I
aa = 0;
// Loop over the integral pixel positions described by those bounds.
for (sy = floor(minsy); sy < maxsy; sy++)
for (sx = floor(minsx); sx < maxsx; sx++) {
for (sy = std::floor(minsy); sy < maxsy; sy++)
for (sx = std::floor(minsx); sx < maxsx; sx++) {
// Calculate width, height, then area of dest pixel
// that's covered by this source pixel.
@ -331,10 +331,10 @@ void imageScaleNNAA(video::IImage *src, const core::rect<s32> &srcrect, video::I
// Set the destination image pixel to the average color.
if (area > 0) {
pxl.setRed(ra / area + 0.5);
pxl.setGreen(ga / area + 0.5);
pxl.setBlue(ba / area + 0.5);
pxl.setAlpha(aa / area + 0.5);
pxl.setRed(ra / area + 0.5f);
pxl.setGreen(ga / area + 0.5f);
pxl.setBlue(ba / area + 0.5f);
pxl.setAlpha(aa / area + 0.5f);
} else {
pxl.setRed(0);
pxl.setGreen(0);
@ -344,38 +344,3 @@ void imageScaleNNAA(video::IImage *src, const core::rect<s32> &srcrect, video::I
dest->setPixel(dx, dy, pxl);
}
}
/* Check and align image to npot2 if required by hardware
* @param image image to check for npot2 alignment
* @param driver driver to use for image operations
* @return image or copy of image aligned to npot2
*/
video::IImage *Align2Npot2(video::IImage *image, video::IVideoDriver *driver)
{
if (image == nullptr)
return image;
if (driver->queryFeature(video::EVDF_TEXTURE_NPOT))
return image;
core::dimension2d<u32> dim = image->getDimension();
unsigned int height = npot2(dim.Height);
unsigned int width = npot2(dim.Width);
if (dim.Height == height && dim.Width == width)
return image;
if (dim.Height > height)
height *= 2;
if (dim.Width > width)
width *= 2;
video::IImage *targetimage =
driver->createImage(video::ECF_A8R8G8B8,
core::dimension2d<u32>(width, height));
if (targetimage != nullptr)
image->copyToScaling(targetimage);
image->drop();
return targetimage;
}

View file

@ -39,11 +39,3 @@ video::SColor imageAverageColor(const video::IImage *img);
* and downscaling.
*/
void imageScaleNNAA(video::IImage *src, const core::rect<s32> &srcrect, video::IImage *dest);
/* Check and align image to npot2 if required by hardware
* @param image image to check for npot2 alignment
* @param driver driver to use for image operations
* @return image or copy of image aligned to npot2
*/
video::IImage *Align2Npot2(video::IImage *image, video::IVideoDriver *driver);

View file

@ -601,7 +601,7 @@ static void apply_hue_saturation(video::IImage *dst, v2u32 dst_pos, v2u32 size,
}
// Apply the specified HSL adjustments
hsl.Hue = fmod(hsl.Hue + hue, 360);
hsl.Hue = fmodf(hsl.Hue + hue, 360);
if (hsl.Hue < 0)
hsl.Hue += 360;
@ -632,19 +632,19 @@ static void apply_overlay(video::IImage *blend, video::IImage *dst,
video::SColor blend_c =
blend_layer->getPixel(x + blend_layer_pos.X, y + blend_layer_pos.Y);
video::SColor base_c = base_layer->getPixel(base_x, base_y);
double blend_r = blend_c.getRed() / 255.0;
double blend_g = blend_c.getGreen() / 255.0;
double blend_b = blend_c.getBlue() / 255.0;
double base_r = base_c.getRed() / 255.0;
double base_g = base_c.getGreen() / 255.0;
double base_b = base_c.getBlue() / 255.0;
f32 blend_r = blend_c.getRed() / 255.0f;
f32 blend_g = blend_c.getGreen() / 255.0f;
f32 blend_b = blend_c.getBlue() / 255.0f;
f32 base_r = base_c.getRed() / 255.0f;
f32 base_g = base_c.getGreen() / 255.0f;
f32 base_b = base_c.getBlue() / 255.0f;
base_c.set(
base_c.getAlpha(),
// Do a Multiply blend if less that 0.5, otherwise do a Screen blend
(u32)((base_r < 0.5 ? 2 * base_r * blend_r : 1 - 2 * (1 - base_r) * (1 - blend_r)) * 255),
(u32)((base_g < 0.5 ? 2 * base_g * blend_g : 1 - 2 * (1 - base_g) * (1 - blend_g)) * 255),
(u32)((base_b < 0.5 ? 2 * base_b * blend_b : 1 - 2 * (1 - base_b) * (1 - blend_b)) * 255)
(u32)((base_r < 0.5f ? 2 * base_r * blend_r : 1 - 2 * (1 - base_r) * (1 - blend_r)) * 255),
(u32)((base_g < 0.5f ? 2 * base_g * blend_g : 1 - 2 * (1 - base_g) * (1 - blend_g)) * 255),
(u32)((base_b < 0.5f ? 2 * base_b * blend_b : 1 - 2 * (1 - base_b) * (1 - blend_b)) * 255)
);
dst->setPixel(base_x, base_y, base_c);
}
@ -659,38 +659,38 @@ static void apply_overlay(video::IImage *blend, video::IImage *dst,
static void apply_brightness_contrast(video::IImage *dst, v2u32 dst_pos, v2u32 size,
s32 brightness, s32 contrast)
{
video::SColor dst_c;
// Only allow normalized contrast to get as high as 127/128 to avoid infinite slope.
// (we could technically allow -128/128 here as that would just result in 0 slope)
double norm_c = core::clamp(contrast, -127, 127) / 128.0;
double norm_b = core::clamp(brightness, -127, 127) / 127.0;
f32 norm_c = core::clamp(contrast, -127, 127) / 128.0f;
f32 norm_b = core::clamp(brightness, -127, 127) / 127.0f;
// Scale brightness so its range is -127.5 to 127.5, otherwise brightness
// adjustments will outputs values from 0.5 to 254.5 instead of 0 to 255.
double scaled_b = brightness * 127.5 / 127;
f32 scaled_b = brightness * 127.5f / 127;
// Calculate a contrast slope such that that no colors will get clamped due
// to the brightness setting.
// This allows the texture modifier to used as a brightness modifier without
// the user having to calculate a contrast to avoid clipping at that brightness.
double slope = 1 - fabs(norm_b);
f32 slope = 1 - std::fabs(norm_b);
// Apply the user's contrast adjustment to the calculated slope, such that
// -127 will make it near-vertical and +127 will make it horizontal
double angle = atan(slope);
f32 angle = std::atan(slope);
angle += norm_c <= 0
? norm_c * angle // allow contrast slope to be lowered to 0
: norm_c * (M_PI_2 - angle); // allow contrast slope to be raised almost vert.
slope = tan(angle);
slope = std::tan(angle);
double c = slope <= 1
? -slope * 127.5 + 127.5 + scaled_b // shift up/down when slope is horiz.
: -slope * (127.5 - scaled_b) + 127.5; // shift left/right when slope is vert.
f32 c = slope <= 1
? -slope * 127.5f + 127.5f + scaled_b // shift up/down when slope is horiz.
: -slope * (127.5f - scaled_b) + 127.5f; // shift left/right when slope is vert.
// add 0.5 to c so that when the final result is cast to int, it is effectively
// rounded rather than trunc'd.
c += 0.5;
c += 0.5f;
video::SColor dst_c;
for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
dst_c = dst->getPixel(x, y);
@ -805,7 +805,7 @@ static void draw_crack(video::IImage *crack, video::IImage *dst,
static void brighten(video::IImage *image)
{
if (image == NULL)
if (!image)
return;
core::dimension2d<u32> dim = image->getDimension();
@ -814,9 +814,9 @@ static void brighten(video::IImage *image)
for (u32 x=0; x<dim.Width; x++)
{
video::SColor c = image->getPixel(x,y);
c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
c.setRed(127.5f + 0.5f * c.getRed());
c.setGreen(127.5f + 0.5f * c.getGreen());
c.setBlue(127.5f + 0.5f * c.getBlue());
image->setPixel(x,y,c);
}
}
@ -881,7 +881,7 @@ static core::dimension2du imageTransformDimension(u32 transform, core::dimension
static void imageTransform(u32 transform, video::IImage *src, video::IImage *dst)
{
if (src == NULL || dst == NULL)
if (!src || !dst)
return;
core::dimension2d<u32> dstdim = dst->getDimension();
@ -1280,7 +1280,7 @@ bool ImageSource::generateImagePart(std::string_view part_of_name,
video::IImage *img_left = generateImage(imagename_left, source_image_names);
video::IImage *img_right = generateImage(imagename_right, source_image_names);
if (img_top == NULL || img_left == NULL || img_right == NULL) {
if (!img_top || !img_left || !img_right) {
errorstream << "generateImagePart(): Failed to create textures"
<< " for inventorycube \"" << part_of_name << "\""
<< std::endl;
@ -1479,21 +1479,28 @@ bool ImageSource::generateImagePart(std::string_view part_of_name,
/* Upscale textures to user's requested minimum size. This is a trick to make
* filters look as good on low-res textures as on high-res ones, by making
* low-res textures BECOME high-res ones. This is helpful for worlds that
* low-res textures BECOME high-res ones. This is helpful for worlds that
* mix high- and low-res textures, or for mods with least-common-denominator
* textures that don't have the resources to offer high-res alternatives.
*
* Q: why not just enable/disable filtering depending on texture size?
* A: large texture resolutions apparently allow getting rid of the Moire
* effect way better than anisotropic filtering alone could.
* see <https://github.com/luanti-org/luanti/issues/15604> and related
* linked discussions.
*/
const bool filter = m_setting_trilinear_filter || m_setting_bilinear_filter;
const s32 scaleto = filter ? g_settings->getU16("texture_min_size") : 1;
if (scaleto > 1) {
const core::dimension2d<u32> dim = baseimg->getDimension();
if (filter) {
const f32 scaleto = rangelim(g_settings->getU16("texture_min_size"),
TEXTURE_FILTER_MIN_SIZE, 16384);
/* Calculate scaling needed to make the shortest texture dimension
* equal to the target minimum. If e.g. this is a vertical frames
* animation, the short dimension will be the real size.
/* Calculate integer-scaling needed to make the shortest texture
* dimension equal to the target minimum. If this is e.g. a
* vertical frames animation, the short dimension will be the real size.
*/
u32 xscale = scaleto / dim.Width;
u32 yscale = scaleto / dim.Height;
const auto &dim = baseimg->getDimension();
u32 xscale = std::ceil(scaleto / dim.Width);
u32 yscale = std::ceil(scaleto / dim.Height);
const s32 scale = std::max(xscale, yscale);
// Never downscale; only scale up by 2x or more.
@ -1889,7 +1896,7 @@ video::IImage* ImageSource::generateImage(std::string_view name,
}
// If no resulting image, print a warning
if (baseimg == NULL) {
if (!baseimg) {
errorstream << "generateImage(): baseimg is NULL (attempted to"
" create texture \"" << name << "\")" << std::endl;
} else if (baseimg->getDimension().Width == 0 ||

View file

@ -14,6 +14,8 @@
void MyEventReceiver::reloadKeybindings()
{
clearKeyCache();
keybindings[KeyType::FORWARD] = getKeySetting("keymap_forward");
keybindings[KeyType::BACKWARD] = getKeySetting("keymap_backward");
keybindings[KeyType::LEFT] = getKeySetting("keymap_left");
@ -148,6 +150,10 @@ bool MyEventReceiver::OnEvent(const SEvent &event)
}
fullscreen_is_down = event.KeyInput.PressedDown;
return true;
} else if (keyCode == EscapeKey &&
event.KeyInput.PressedDown && event.KeyInput.Shift) {
g_gamecallback->disconnect();
return true;
}
}

View file

@ -12,6 +12,8 @@
#include <set>
#include <unordered_map>
#include "keycode.h"
#include "settings.h"
#include "util/string.h"
class InputHandler;
@ -132,6 +134,13 @@ private:
class InputHandler
{
public:
InputHandler()
{
for (const auto &name: Settings::getLayer(SL_DEFAULTS)->getNames())
if (str_starts_with(name, "keymap_"))
g_settings->registerChangedCallback(name, &settingChangedCallback, this);
}
virtual ~InputHandler() = default;
virtual bool isRandom() const
@ -163,6 +172,11 @@ public:
virtual void clear() {}
virtual void releaseAllKeys() {}
static void settingChangedCallback(const std::string &name, void *data)
{
static_cast<InputHandler *>(data)->reloadKeybindings();
}
JoystickController joystick;
};

View file

@ -7,6 +7,7 @@
#include <string>
#include <map>
#include <thread>
#include <unordered_map>
#include "wieldmesh.h" // ItemMesh
#include "util/basic_macros.h"

View file

@ -3,6 +3,8 @@
// Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
#include "mesh.h"
#include "IMeshBuffer.h"
#include "SSkinMeshBuffer.h"
#include "debug.h"
#include "log.h"
#include <cmath>
@ -102,6 +104,21 @@ scene::IAnimatedMesh* createCubeMesh(v3f scale)
return anim_mesh;
}
template<typename F>
inline static void transformMeshBuffer(scene::IMeshBuffer *buf,
const F &transform_vertex)
{
const u32 stride = getVertexPitchFromType(buf->getVertexType());
u32 vertex_count = buf->getVertexCount();
u8 *vertices = (u8 *)buf->getVertices();
for (u32 i = 0; i < vertex_count; i++) {
auto *vertex = (video::S3DVertex *)(vertices + i * stride);
transform_vertex(vertex);
}
buf->setDirty(scene::EBT_VERTEX);
buf->recalculateBoundingBox();
}
void scaleMesh(scene::IMesh *mesh, v3f scale)
{
if (mesh == NULL)
@ -112,14 +129,9 @@ void scaleMesh(scene::IMesh *mesh, v3f scale)
u32 mc = mesh->getMeshBufferCount();
for (u32 j = 0; j < mc; j++) {
scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
const u32 stride = getVertexPitchFromType(buf->getVertexType());
u32 vertex_count = buf->getVertexCount();
u8 *vertices = (u8 *)buf->getVertices();
for (u32 i = 0; i < vertex_count; i++)
((video::S3DVertex *)(vertices + i * stride))->Pos *= scale;
buf->setDirty(scene::EBT_VERTEX);
buf->recalculateBoundingBox();
transformMeshBuffer(buf, [scale](video::S3DVertex *vertex) {
vertex->Pos *= scale;
});
// calculate total bounding box
if (j == 0)
@ -140,14 +152,9 @@ void translateMesh(scene::IMesh *mesh, v3f vec)
u32 mc = mesh->getMeshBufferCount();
for (u32 j = 0; j < mc; j++) {
scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
const u32 stride = getVertexPitchFromType(buf->getVertexType());
u32 vertex_count = buf->getVertexCount();
u8 *vertices = (u8 *)buf->getVertices();
for (u32 i = 0; i < vertex_count; i++)
((video::S3DVertex *)(vertices + i * stride))->Pos += vec;
buf->setDirty(scene::EBT_VERTEX);
buf->recalculateBoundingBox();
transformMeshBuffer(buf, [vec](video::S3DVertex *vertex) {
vertex->Pos += vec;
});
// calculate total bounding box
if (j == 0)
@ -330,44 +337,40 @@ bool checkMeshNormals(scene::IMesh *mesh)
return true;
}
template<class VertexType, class SMeshBufferType>
static scene::IMeshBuffer *cloneMeshBuffer(scene::IMeshBuffer *mesh_buffer)
{
auto *v = static_cast<VertexType *>(mesh_buffer->getVertices());
u16 *indices = mesh_buffer->getIndices();
auto *cloned_buffer = new SMeshBufferType();
cloned_buffer->append(v, mesh_buffer->getVertexCount(), indices,
mesh_buffer->getIndexCount());
// Rigidly animated meshes may have transformation matrices that need to be applied
if (auto *sbuf = dynamic_cast<scene::SSkinMeshBuffer *>(mesh_buffer)) {
transformMeshBuffer(cloned_buffer, [sbuf](video::S3DVertex *vertex) {
sbuf->Transformation.transformVect(vertex->Pos);
vertex->Normal = sbuf->Transformation.rotateAndScaleVect(vertex->Normal);
vertex->Normal.normalize();
});
}
return cloned_buffer;
}
scene::IMeshBuffer* cloneMeshBuffer(scene::IMeshBuffer *mesh_buffer)
{
switch (mesh_buffer->getVertexType()) {
case video::EVT_STANDARD: {
video::S3DVertex *v = (video::S3DVertex *) mesh_buffer->getVertices();
u16 *indices = mesh_buffer->getIndices();
scene::SMeshBuffer *cloned_buffer = new scene::SMeshBuffer();
cloned_buffer->append(v, mesh_buffer->getVertexCount(), indices,
mesh_buffer->getIndexCount());
return cloned_buffer;
case video::EVT_STANDARD:
return cloneMeshBuffer<video::S3DVertex, scene::SMeshBuffer>(mesh_buffer);
case video::EVT_2TCOORDS:
return cloneMeshBuffer<video::S3DVertex2TCoords, scene::SMeshBufferLightMap>(mesh_buffer);
case video::EVT_TANGENTS:
return cloneMeshBuffer<video::S3DVertexTangents, scene::SMeshBufferTangents>(mesh_buffer);
}
case video::EVT_2TCOORDS: {
video::S3DVertex2TCoords *v =
(video::S3DVertex2TCoords *) mesh_buffer->getVertices();
u16 *indices = mesh_buffer->getIndices();
scene::SMeshBufferLightMap *cloned_buffer =
new scene::SMeshBufferLightMap();
cloned_buffer->append(v, mesh_buffer->getVertexCount(), indices,
mesh_buffer->getIndexCount());
return cloned_buffer;
}
case video::EVT_TANGENTS: {
video::S3DVertexTangents *v =
(video::S3DVertexTangents *) mesh_buffer->getVertices();
u16 *indices = mesh_buffer->getIndices();
scene::SMeshBufferTangents *cloned_buffer =
new scene::SMeshBufferTangents();
cloned_buffer->append(v, mesh_buffer->getVertexCount(), indices,
mesh_buffer->getIndexCount());
return cloned_buffer;
}
}
// This should not happen.
sanity_check(false);
return NULL;
}
scene::SMesh* cloneMesh(scene::IMesh *src_mesh)
scene::SMesh* cloneStaticMesh(scene::IMesh *src_mesh)
{
scene::SMesh* dst_mesh = new scene::SMesh();
for (u16 j = 0; j < src_mesh->getMeshBufferCount(); j++) {

View file

@ -93,10 +93,8 @@ void rotateMeshYZby (scene::IMesh *mesh, f64 degrees);
*/
scene::IMeshBuffer* cloneMeshBuffer(scene::IMeshBuffer *mesh_buffer);
/*
Clone the mesh.
*/
scene::SMesh* cloneMesh(scene::IMesh *src_mesh);
/// Clone a mesh. For an animated mesh, this will clone the static pose.
scene::SMesh* cloneStaticMesh(scene::IMesh *src_mesh);
/*
Convert nodeboxes to mesh. Each tile goes into a different buffer.

View file

@ -704,8 +704,8 @@ void Minimap::updateActiveMarkers()
continue;
}
m_active_markers.emplace_back(((float)pos.X / (float)MINIMAP_MAX_SX) - 0.5,
(1.0 - (float)pos.Z / (float)MINIMAP_MAX_SY) - 0.5);
m_active_markers.emplace_back(((float)pos.X / (float)MINIMAP_MAX_SX) - 0.5f,
(1.0f - (float)pos.Z / (float)MINIMAP_MAX_SY) - 0.5f);
}
}

View file

@ -67,14 +67,14 @@ void FpsControl::limit(IrrlichtDevice *device, f32 *dtime)
last_time = time;
}
class FogShaderConstantSetter : public IShaderConstantSetter
class FogShaderUniformSetter : public IShaderUniformSetter
{
CachedPixelShaderSetting<float, 4> m_fog_color{"fogColor"};
CachedPixelShaderSetting<float> m_fog_distance{"fogDistance"};
CachedPixelShaderSetting<float> m_fog_shading_parameter{"fogShadingParameter"};
public:
void onSetConstants(video::IMaterialRendererServices *services) override
void onSetUniforms(video::IMaterialRendererServices *services) override
{
auto *driver = services->getVideoDriver();
assert(driver);
@ -101,9 +101,9 @@ public:
}
};
IShaderConstantSetter *FogShaderConstantSetterFactory::create()
IShaderUniformSetter *FogShaderUniformSetterFactory::create()
{
return new FogShaderConstantSetter();
return new FogShaderUniformSetter();
}
/* Other helpers */

View file

@ -54,11 +54,11 @@ struct FpsControl {
};
// Populates fogColor, fogDistance, fogShadingParameter with values from Irrlicht
class FogShaderConstantSetterFactory : public IShaderConstantSetterFactory
class FogShaderUniformSetterFactory : public IShaderUniformSetterFactory
{
public:
FogShaderConstantSetterFactory() {};
virtual IShaderConstantSetter *create();
FogShaderUniformSetterFactory() {};
virtual IShaderUniformSetter *create();
};
/* Rendering engine class */

View file

@ -159,7 +159,7 @@ private:
class ShaderCallback : public video::IShaderConstantSetCallBack
{
std::vector<std::unique_ptr<IShaderConstantSetter>> m_setters;
std::vector<std::unique_ptr<IShaderUniformSetter>> m_setters;
public:
template <typename Factories>
@ -175,7 +175,7 @@ public:
virtual void OnSetConstants(video::IMaterialRendererServices *services, s32 userData) override
{
for (auto &&setter : m_setters)
setter->onSetConstants(services);
setter->onSetUniforms(services);
}
virtual void OnSetMaterial(const video::SMaterial& material) override
@ -187,11 +187,78 @@ public:
/*
MainShaderConstantSetter: Set basic constants required for almost everything
MainShaderConstantSetter: Sets some random general constants
*/
class MainShaderConstantSetter : public IShaderConstantSetter
{
public:
MainShaderConstantSetter() = default;
~MainShaderConstantSetter() = default;
void onGenerate(const std::string &name, ShaderConstants &constants) override
{
constants["ENABLE_TONE_MAPPING"] = g_settings->getBool("tone_mapping") ? 1 : 0;
if (g_settings->getBool("enable_dynamic_shadows")) {
constants["ENABLE_DYNAMIC_SHADOWS"] = 1;
if (g_settings->getBool("shadow_map_color"))
constants["COLORED_SHADOWS"] = 1;
if (g_settings->getBool("shadow_poisson_filter"))
constants["POISSON_FILTER"] = 1;
if (g_settings->getBool("enable_water_reflections"))
constants["ENABLE_WATER_REFLECTIONS"] = 1;
if (g_settings->getBool("enable_translucent_foliage"))
constants["ENABLE_TRANSLUCENT_FOLIAGE"] = 1;
// FIXME: The node specular effect is currently disabled due to mixed in-game
// results. This shader should not be applied to all nodes equally. See #15898
if (false)
constants["ENABLE_NODE_SPECULAR"] = 1;
s32 shadow_filter = g_settings->getS32("shadow_filters");
constants["SHADOW_FILTER"] = shadow_filter;
float shadow_soft_radius = std::max(1.f,
g_settings->getFloat("shadow_soft_radius"));
constants["SOFTSHADOWRADIUS"] = shadow_soft_radius;
}
if (g_settings->getBool("enable_bloom")) {
constants["ENABLE_BLOOM"] = 1;
if (g_settings->getBool("enable_bloom_debug"))
constants["ENABLE_BLOOM_DEBUG"] = 1;
}
if (g_settings->getBool("enable_auto_exposure"))
constants["ENABLE_AUTO_EXPOSURE"] = 1;
if (g_settings->get("antialiasing") == "ssaa") {
constants["ENABLE_SSAA"] = 1;
u16 ssaa_scale = std::max<u16>(2, g_settings->getU16("fsaa"));
constants["SSAA_SCALE"] = ssaa_scale;
}
if (g_settings->getBool("debanding"))
constants["ENABLE_DITHERING"] = 1;
if (g_settings->getBool("enable_volumetric_lighting"))
constants["VOLUMETRIC_LIGHT"] = 1;
}
};
/*
MainShaderUniformSetter: Set basic uniforms required for almost everything
*/
class MainShaderUniformSetter : public IShaderUniformSetter
{
using SamplerLayer_t = s32;
CachedVertexShaderSetting<f32, 16> m_world_view_proj{"mWorldViewProj"};
CachedVertexShaderSetting<f32, 16> m_world{"mWorld"};
@ -200,19 +267,24 @@ class MainShaderConstantSetter : public IShaderConstantSetter
// Texture matrix
CachedVertexShaderSetting<float, 16> m_texture{"mTexture"};
CachedPixelShaderSetting<SamplerLayer_t> m_texture0{"texture0"};
CachedPixelShaderSetting<SamplerLayer_t> m_texture1{"texture1"};
CachedPixelShaderSetting<SamplerLayer_t> m_texture2{"texture2"};
CachedPixelShaderSetting<SamplerLayer_t> m_texture3{"texture3"};
// commonly used way to pass material color to shader
video::SColor m_material_color;
CachedPixelShaderSetting<float, 4> m_material_color_setting{"materialColor"};
public:
~MainShaderConstantSetter() = default;
~MainShaderUniformSetter() = default;
virtual void onSetMaterial(const video::SMaterial& material) override
{
m_material_color = material.ColorParam;
}
virtual void onSetConstants(video::IMaterialRendererServices *services) override
virtual void onSetUniforms(video::IMaterialRendererServices *services) override
{
video::IVideoDriver *driver = services->getVideoDriver();
assert(driver);
@ -237,17 +309,27 @@ public:
m_texture.set(texture, services);
}
SamplerLayer_t tex_id;
tex_id = 0;
m_texture0.set(&tex_id, services);
tex_id = 1;
m_texture1.set(&tex_id, services);
tex_id = 2;
m_texture2.set(&tex_id, services);
tex_id = 3;
m_texture3.set(&tex_id, services);
video::SColorf colorf(m_material_color);
m_material_color_setting.set(colorf, services);
}
};
class MainShaderConstantSetterFactory : public IShaderConstantSetterFactory
class MainShaderUniformSetterFactory : public IShaderUniformSetterFactory
{
public:
virtual IShaderConstantSetter* create()
{ return new MainShaderConstantSetter(); }
virtual IShaderUniformSetter* create()
{ return new MainShaderUniformSetter(); }
};
@ -262,14 +344,13 @@ public:
~ShaderSource() override;
/*
- If shader material specified by name is found from cache,
return the cached id.
- If shader material is found from cache, return the cached id.
- Otherwise generate the shader material, add to cache and return id.
The id 0 points to a null shader. Its material is EMT_SOLID.
*/
u32 getShaderIdDirect(const std::string &name,
MaterialType material_type, NodeDrawType drawtype);
u32 getShaderIdDirect(const std::string &name, const ShaderConstants &input_const,
video::E_MATERIAL_TYPE base_mat);
/*
If shader specified by the name pointed by the id doesn't
@ -279,19 +360,10 @@ public:
and not found in cache, the call is queued to the main thread
for processing.
*/
u32 getShader(const std::string &name,
MaterialType material_type, NodeDrawType drawtype) override;
u32 getShader(const std::string &name, const ShaderConstants &input_const,
video::E_MATERIAL_TYPE base_mat) override;
u32 getShaderRaw(const std::string &name, bool blendAlpha) override
{
// TODO: the shader system should be refactored to be much more generic.
// Just let callers pass arbitrary constants, this would also deal with
// runtime changes cleanly.
return getShader(name, blendAlpha ? TILE_MATERIAL_ALPHA : TILE_MATERIAL_BASIC,
NodeDrawType_END);
}
ShaderInfo getShaderInfo(u32 id) override;
const ShaderInfo &getShaderInfo(u32 id) override;
// Processes queued shader requests from other threads.
// Shall be called from the main thread.
@ -306,9 +378,14 @@ public:
// Shall be called from the main thread.
void rebuildShaders() override;
void addShaderConstantSetterFactory(IShaderConstantSetterFactory *setter) override
void addShaderConstantSetter(IShaderConstantSetter *setter) override
{
m_setter_factories.emplace_back(setter);
m_constant_setters.emplace_back(setter);
}
void addShaderUniformSetterFactory(IShaderUniformSetterFactory *setter) override
{
m_uniform_factories.emplace_back(setter);
}
private:
@ -332,11 +409,23 @@ private:
#endif
// Global constant setter factories
std::vector<std::unique_ptr<IShaderConstantSetterFactory>> m_setter_factories;
std::vector<std::unique_ptr<IShaderConstantSetter>> m_constant_setters;
// Global uniform setter factories
std::vector<std::unique_ptr<IShaderUniformSetterFactory>> m_uniform_factories;
// Generate shader given the shader name.
ShaderInfo generateShader(const std::string &name,
MaterialType material_type, NodeDrawType drawtype);
const ShaderConstants &input_const, video::E_MATERIAL_TYPE base_mat);
/// @brief outputs a constant to an ostream
inline void putConstant(std::ostream &os, const ShaderConstants::mapped_type &it)
{
if (auto *ival = std::get_if<int>(&it); ival)
os << *ival;
else
os << std::get<float>(it);
}
};
IWritableShaderSource *createShaderSource()
@ -351,8 +440,9 @@ ShaderSource::ShaderSource()
// Add a dummy ShaderInfo as the first index, named ""
m_shaderinfo_cache.emplace_back();
// Add main global constant setter
addShaderConstantSetterFactory(new MainShaderConstantSetterFactory());
// Add global stuff
addShaderConstantSetter(new MainShaderConstantSetter());
addShaderUniformSetterFactory(new MainShaderUniformSetterFactory());
}
ShaderSource::~ShaderSource()
@ -362,22 +452,27 @@ ShaderSource::~ShaderSource()
// Delete materials
auto *gpu = RenderingEngine::get_video_driver()->getGPUProgrammingServices();
assert(gpu);
u32 n = 0;
for (ShaderInfo &i : m_shaderinfo_cache) {
if (!i.name.empty())
if (!i.name.empty()) {
gpu->deleteShaderMaterial(i.material);
n++;
}
}
m_shaderinfo_cache.clear();
infostream << "~ShaderSource() cleaned up " << n << " materials" << std::endl;
}
u32 ShaderSource::getShader(const std::string &name,
MaterialType material_type, NodeDrawType drawtype)
const ShaderConstants &input_const, video::E_MATERIAL_TYPE base_mat)
{
/*
Get shader
*/
if (std::this_thread::get_id() == m_main_thread) {
return getShaderIdDirect(name, material_type, drawtype);
return getShaderIdDirect(name, input_const, base_mat);
}
errorstream << "ShaderSource::getShader(): getting from "
@ -415,7 +510,7 @@ u32 ShaderSource::getShader(const std::string &name,
This method generates all the shaders
*/
u32 ShaderSource::getShaderIdDirect(const std::string &name,
MaterialType material_type, NodeDrawType drawtype)
const ShaderConstants &input_const, video::E_MATERIAL_TYPE base_mat)
{
// Empty name means shader 0
if (name.empty()) {
@ -424,10 +519,10 @@ u32 ShaderSource::getShaderIdDirect(const std::string &name,
}
// Check if already have such instance
for(u32 i=0; i<m_shaderinfo_cache.size(); i++){
ShaderInfo *info = &m_shaderinfo_cache[i];
if(info->name == name && info->material_type == material_type &&
info->drawtype == drawtype)
for (u32 i = 0; i < m_shaderinfo_cache.size(); i++) {
auto &info = m_shaderinfo_cache[i];
if (info.name == name && info.base_material == base_mat &&
info.input_constants == input_const)
return i;
}
@ -440,7 +535,7 @@ u32 ShaderSource::getShaderIdDirect(const std::string &name,
return 0;
}
ShaderInfo info = generateShader(name, material_type, drawtype);
ShaderInfo info = generateShader(name, input_const, base_mat);
/*
Add shader to caches (add dummy shaders too)
@ -449,19 +544,19 @@ u32 ShaderSource::getShaderIdDirect(const std::string &name,
MutexAutoLock lock(m_shaderinfo_cache_mutex);
u32 id = m_shaderinfo_cache.size();
m_shaderinfo_cache.push_back(info);
m_shaderinfo_cache.push_back(std::move(info));
return id;
}
ShaderInfo ShaderSource::getShaderInfo(u32 id)
const ShaderInfo &ShaderSource::getShaderInfo(u32 id)
{
MutexAutoLock lock(m_shaderinfo_cache_mutex);
if(id >= m_shaderinfo_cache.size())
return ShaderInfo();
if (id >= m_shaderinfo_cache.size()) {
static ShaderInfo empty;
return empty;
}
return m_shaderinfo_cache[id];
}
@ -497,46 +592,31 @@ void ShaderSource::rebuildShaders()
}
}
infostream << "ShaderSource: recreating " << m_shaderinfo_cache.size()
<< " shaders" << std::endl;
// Recreate shaders
for (ShaderInfo &i : m_shaderinfo_cache) {
ShaderInfo *info = &i;
if (!info->name.empty()) {
*info = generateShader(info->name, info->material_type, info->drawtype);
*info = generateShader(info->name, info->input_constants, info->base_material);
}
}
}
ShaderInfo ShaderSource::generateShader(const std::string &name,
MaterialType material_type, NodeDrawType drawtype)
const ShaderConstants &input_const, video::E_MATERIAL_TYPE base_mat)
{
ShaderInfo shaderinfo;
shaderinfo.name = name;
shaderinfo.material_type = material_type;
shaderinfo.drawtype = drawtype;
switch (material_type) {
case TILE_MATERIAL_OPAQUE:
case TILE_MATERIAL_LIQUID_OPAQUE:
case TILE_MATERIAL_WAVING_LIQUID_OPAQUE:
shaderinfo.base_material = video::EMT_SOLID;
break;
case TILE_MATERIAL_ALPHA:
case TILE_MATERIAL_PLAIN_ALPHA:
case TILE_MATERIAL_LIQUID_TRANSPARENT:
case TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT:
shaderinfo.base_material = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
break;
case TILE_MATERIAL_BASIC:
case TILE_MATERIAL_PLAIN:
case TILE_MATERIAL_WAVING_LEAVES:
case TILE_MATERIAL_WAVING_PLANTS:
case TILE_MATERIAL_WAVING_LIQUID_BASIC:
shaderinfo.base_material = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
break;
}
shaderinfo.input_constants = input_const;
// fixed pipeline materials don't make sense here
assert(base_mat != video::EMT_TRANSPARENT_VERTEX_ALPHA && base_mat != video::EMT_ONETEXTURE_BLEND);
shaderinfo.base_material = base_mat;
shaderinfo.material = shaderinfo.base_material;
video::IVideoDriver *driver = RenderingEngine::get_video_driver();
auto *driver = RenderingEngine::get_video_driver();
// The null driver doesn't support shaders (duh), but we can pretend it does.
if (driver->getDriverType() == video::EDT_NULL)
return shaderinfo;
@ -612,13 +692,17 @@ ShaderInfo ShaderSource::generateShader(const std::string &name,
/// Unique name of this shader, for debug/logging
std::string log_name = name;
for (auto &it : input_const) {
if (log_name.size() > 60) { // it shouldn't be too long
log_name.append("...");
break;
}
std::ostringstream oss;
putConstant(oss, it.second);
log_name.append(" ").append(it.first).append("=").append(oss.str());
}
/* Define constants for node and object shaders */
const bool node_shader = drawtype != NodeDrawType_END;
if (node_shader) {
log_name.append(" mat=").append(itos(material_type))
.append(" draw=").append(itos(drawtype));
ShaderConstants constants = input_const;
bool use_discard = fully_programmable;
if (!use_discard) {
@ -629,142 +713,22 @@ ShaderInfo ShaderSource::generateShader(const std::string &name,
}
if (use_discard) {
if (shaderinfo.base_material == video::EMT_TRANSPARENT_ALPHA_CHANNEL)
shaders_header << "#define USE_DISCARD 1\n";
constants["USE_DISCARD"] = 1;
else if (shaderinfo.base_material == video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF)
shaders_header << "#define USE_DISCARD_REF 1\n";
constants["USE_DISCARD_REF"] = 1;
}
#define PROVIDE(constant) shaders_header << "#define " #constant " " << (int)constant << "\n"
PROVIDE(NDT_NORMAL);
PROVIDE(NDT_AIRLIKE);
PROVIDE(NDT_LIQUID);
PROVIDE(NDT_FLOWINGLIQUID);
PROVIDE(NDT_GLASSLIKE);
PROVIDE(NDT_ALLFACES);
PROVIDE(NDT_ALLFACES_OPTIONAL);
PROVIDE(NDT_TORCHLIKE);
PROVIDE(NDT_SIGNLIKE);
PROVIDE(NDT_PLANTLIKE);
PROVIDE(NDT_FENCELIKE);
PROVIDE(NDT_RAILLIKE);
PROVIDE(NDT_NODEBOX);
PROVIDE(NDT_GLASSLIKE_FRAMED);
PROVIDE(NDT_FIRELIKE);
PROVIDE(NDT_GLASSLIKE_FRAMED_OPTIONAL);
PROVIDE(NDT_PLANTLIKE_ROOTED);
PROVIDE(TILE_MATERIAL_BASIC);
PROVIDE(TILE_MATERIAL_ALPHA);
PROVIDE(TILE_MATERIAL_LIQUID_TRANSPARENT);
PROVIDE(TILE_MATERIAL_LIQUID_OPAQUE);
PROVIDE(TILE_MATERIAL_WAVING_LEAVES);
PROVIDE(TILE_MATERIAL_WAVING_PLANTS);
PROVIDE(TILE_MATERIAL_OPAQUE);
PROVIDE(TILE_MATERIAL_WAVING_LIQUID_BASIC);
PROVIDE(TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT);
PROVIDE(TILE_MATERIAL_WAVING_LIQUID_OPAQUE);
PROVIDE(TILE_MATERIAL_PLAIN);
PROVIDE(TILE_MATERIAL_PLAIN_ALPHA);
#undef PROVIDE
shaders_header << "#define MATERIAL_TYPE " << (int)material_type << "\n";
shaders_header << "#define DRAW_TYPE " << (int)drawtype << "\n";
bool enable_waving_water = g_settings->getBool("enable_waving_water");
shaders_header << "#define ENABLE_WAVING_WATER " << enable_waving_water << "\n";
shaders_header << "#define WATER_WAVE_HEIGHT " << g_settings->getFloat("water_wave_height") << "\n";
shaders_header << "#define WATER_WAVE_LENGTH " << g_settings->getFloat("water_wave_length") << "\n";
shaders_header << "#define WATER_WAVE_SPEED " << g_settings->getFloat("water_wave_speed") << "\n";
switch (material_type) {
case TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT:
case TILE_MATERIAL_WAVING_LIQUID_OPAQUE:
case TILE_MATERIAL_WAVING_LIQUID_BASIC:
shaders_header << "#define MATERIAL_WAVING_LIQUID 1\n";
break;
default:
shaders_header << "#define MATERIAL_WAVING_LIQUID 0\n";
break;
}
switch (material_type) {
case TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT:
case TILE_MATERIAL_WAVING_LIQUID_OPAQUE:
case TILE_MATERIAL_WAVING_LIQUID_BASIC:
case TILE_MATERIAL_LIQUID_TRANSPARENT:
shaders_header << "#define MATERIAL_WATER_REFLECTIONS 1\n";
break;
default:
shaders_header << "#define MATERIAL_WATER_REFLECTIONS 0\n";
break;
/* Let the constant setters do their job and emit constants */
for (auto &setter : m_constant_setters) {
setter->onGenerate(name, constants);
}
shaders_header << "#define ENABLE_WAVING_LEAVES " << g_settings->getBool("enable_waving_leaves") << "\n";
shaders_header << "#define ENABLE_WAVING_PLANTS " << g_settings->getBool("enable_waving_plants") << "\n";
}
/* Other constants */
shaders_header << "#define ENABLE_TONE_MAPPING " << g_settings->getBool("tone_mapping") << "\n";
if (g_settings->getBool("enable_tinted_fog"))
shaders_header << "#define ENABLE_TINTED_FOG 1\n";
if (g_settings->getBool("enable_dynamic_shadows")) {
shaders_header << "#define ENABLE_DYNAMIC_SHADOWS 1\n";
if (g_settings->getBool("shadow_map_color"))
shaders_header << "#define COLORED_SHADOWS 1\n";
if (g_settings->getBool("shadow_poisson_filter"))
shaders_header << "#define POISSON_FILTER 1\n";
if (g_settings->getBool("enable_water_reflections"))
shaders_header << "#define ENABLE_WATER_REFLECTIONS 1\n";
if (g_settings->getBool("enable_translucent_foliage"))
shaders_header << "#define ENABLE_TRANSLUCENT_FOLIAGE 1\n";
if (g_settings->getBool("enable_node_specular"))
shaders_header << "#define ENABLE_NODE_SPECULAR 1\n";
s32 shadow_filter = g_settings->getS32("shadow_filters");
shaders_header << "#define SHADOW_FILTER " << shadow_filter << "\n";
float shadow_soft_radius = g_settings->getFloat("shadow_soft_radius");
if (shadow_soft_radius < 1.0f)
shadow_soft_radius = 1.0f;
shaders_header << "#define SOFTSHADOWRADIUS " << shadow_soft_radius << "\n";
if (g_settings->getBool("enable_sun_tint"))
shaders_header << "#define ENABLE_TINTED_SUNLIGHT 1\n";
}
if (g_settings->getBool("enable_bloom")) {
shaders_header << "#define ENABLE_BLOOM 1\n";
if (g_settings->getBool("enable_bloom_debug"))
shaders_header << "#define ENABLE_BLOOM_DEBUG 1\n";
}
if (g_settings->getBool("enable_auto_exposure"))
shaders_header << "#define ENABLE_AUTO_EXPOSURE 1\n";
if (g_settings->getBool("enable_color_grading"))
shaders_header << "#define ENABLE_COLOR_GRADING 1\n";
if (g_settings->get("antialiasing") == "ssaa") {
shaders_header << "#define ENABLE_SSAA 1\n";
u16 ssaa_scale = MYMAX(2, g_settings->getU16("fsaa"));
shaders_header << "#define SSAA_SCALE " << ssaa_scale << ".\n";
}
if (g_settings->getBool("debanding"))
shaders_header << "#define ENABLE_DITHERING 1\n";
if (g_settings->getBool("enable_volumetric_lighting")) {
shaders_header << "#define VOLUMETRIC_LIGHT 1\n";
for (auto &it : constants) {
// spaces could cause duplicates
assert(trim(it.first) == it.first);
shaders_header << "#define " << it.first << ' ';
putConstant(shaders_header, it.second);
shaders_header << '\n';
}
if (g_settings->getBool("enable_volumetric_depth_attenuation")) {
@ -786,7 +750,7 @@ ShaderInfo ShaderSource::generateShader(const std::string &name,
geometry_shader_ptr = geometry_shader.c_str();
}
auto cb = make_irr<ShaderCallback>(m_setter_factories);
auto cb = make_irr<ShaderCallback>(m_uniform_factories);
infostream << "Compiling high level shaders for " << log_name << std::endl;
s32 shadermat = gpu->addHighLevelShaderMaterial(
vertex_shader.c_str(), fragment_shader.c_str(), geometry_shader_ptr,
@ -808,6 +772,39 @@ ShaderInfo ShaderSource::generateShader(const std::string &name,
return shaderinfo;
}
/*
Other functions and helpers
*/
u32 IShaderSource::getShader(const std::string &name,
MaterialType material_type, NodeDrawType drawtype)
{
ShaderConstants input_const;
input_const["MATERIAL_TYPE"] = (int)material_type;
input_const["DRAWTYPE"] = (int)drawtype;
video::E_MATERIAL_TYPE base_mat = video::EMT_SOLID;
switch (material_type) {
case TILE_MATERIAL_ALPHA:
case TILE_MATERIAL_PLAIN_ALPHA:
case TILE_MATERIAL_LIQUID_TRANSPARENT:
case TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT:
base_mat = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
break;
case TILE_MATERIAL_BASIC:
case TILE_MATERIAL_PLAIN:
case TILE_MATERIAL_WAVING_LEAVES:
case TILE_MATERIAL_WAVING_PLANTS:
case TILE_MATERIAL_WAVING_LIQUID_BASIC:
base_mat = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
break;
default:
break;
}
return getShader(name, input_const, base_mat);
}
void dumpShaderProgram(std::ostream &output_stream,
const std::string &program_type, std::string_view program)
{

View file

@ -8,10 +8,10 @@
#include "irrlichttypes_bloated.h"
#include <IMaterialRendererServices.h>
#include <string>
#include <map>
#include <variant>
#include "nodedef.h"
class IGameDef;
/*
shader.{h,cpp}: Shader handling stuff.
*/
@ -28,19 +28,27 @@ class IGameDef;
std::string getShaderPath(const std::string &name_of_shader,
const std::string &filename);
struct ShaderInfo {
std::string name = "";
video::E_MATERIAL_TYPE base_material = video::EMT_SOLID;
video::E_MATERIAL_TYPE material = video::EMT_SOLID;
NodeDrawType drawtype = NDT_NORMAL;
MaterialType material_type = TILE_MATERIAL_BASIC;
/*
Abstraction for pushing constants (or what we pretend is) into
shaders. These end up as `#define` prepended to the shader source.
*/
ShaderInfo() = default;
virtual ~ShaderInfo() = default;
// Shader constants are either an int or a float in GLSL
typedef std::map<std::string, std::variant<int, float>> ShaderConstants;
class IShaderConstantSetter {
public:
virtual ~IShaderConstantSetter() = default;
/**
* Called when the final shader source is being generated
* @param name name of the shader
* @param constants current set of constants, free to modify
*/
virtual void onGenerate(const std::string &name, ShaderConstants &constants) = 0;
};
/*
Setter of constants for shaders
Abstraction for updating uniforms used by shaders
*/
namespace irr::video {
@ -48,19 +56,19 @@ namespace irr::video {
}
class IShaderConstantSetter {
class IShaderUniformSetter {
public:
virtual ~IShaderConstantSetter() = default;
virtual void onSetConstants(video::IMaterialRendererServices *services) = 0;
virtual ~IShaderUniformSetter() = default;
virtual void onSetUniforms(video::IMaterialRendererServices *services) = 0;
virtual void onSetMaterial(const video::SMaterial& material)
{ }
};
class IShaderConstantSetterFactory {
class IShaderUniformSetterFactory {
public:
virtual ~IShaderConstantSetterFactory() = default;
virtual IShaderConstantSetter* create() = 0;
virtual ~IShaderUniformSetterFactory() = default;
virtual IShaderUniformSetter* create() = 0;
};
@ -199,7 +207,18 @@ using CachedStructPixelShaderSetting = CachedStructShaderSetting<T, count, cache
A "shader" could more precisely be called a "shader material" and comprises
a vertex, fragment and optional geometry shader.
It is uniquely identified by a name, base material and the input constants.
*/
struct ShaderInfo {
std::string name;
video::E_MATERIAL_TYPE base_material = video::EMT_SOLID;
// Material ID the shader has received from Irrlicht
video::E_MATERIAL_TYPE material = video::EMT_SOLID;
// Input constants
ShaderConstants input_constants;
};
class IShaderSource {
public:
IShaderSource() = default;
@ -210,19 +229,38 @@ public:
*
* Use this to get the material ID to plug into `video::SMaterial`.
*/
virtual ShaderInfo getShaderInfo(u32 id) = 0;
/// @brief Generates or gets a shader suitable for nodes and entities
virtual u32 getShader(const std::string &name,
MaterialType material_type, NodeDrawType drawtype = NDT_NORMAL) = 0;
virtual const ShaderInfo &getShaderInfo(u32 id) = 0;
/**
* Generates or gets a shader for general use.
* Generates or gets a shader.
*
* Note that the input constants are not for passing the entire world into
* the shader. Use `IShaderConstantSetter` to handle user settings.
* @param name name of the shader (directory on disk)
* @param input_const primary key constants for this shader
* @param base_mat base material to use
* @return shader ID
* @note `base_material` only controls alpha behavior
*/
virtual u32 getShader(const std::string &name,
const ShaderConstants &input_const, video::E_MATERIAL_TYPE base_mat) = 0;
/// @brief Helper: Generates or gets a shader suitable for nodes and entities
u32 getShader(const std::string &name,
MaterialType material_type, NodeDrawType drawtype = NDT_NORMAL);
/**
* Helper: Generates or gets a shader for common, general use.
* @param name name of the shader
* @param blendAlpha enable alpha blending for this material?
* @return shader ID
*/
virtual u32 getShaderRaw(const std::string &name, bool blendAlpha = false) = 0;
inline u32 getShaderRaw(const std::string &name, bool blendAlpha = false)
{
auto base_mat = blendAlpha ? video::EMT_TRANSPARENT_ALPHA_CHANNEL :
video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
return getShader(name, ShaderConstants(), base_mat);
}
};
class IWritableShaderSource : public IShaderSource {
@ -236,7 +274,10 @@ public:
virtual void rebuildShaders()=0;
/// @note Takes ownership of @p setter.
virtual void addShaderConstantSetterFactory(IShaderConstantSetterFactory *setter) = 0;
virtual void addShaderConstantSetter(IShaderConstantSetter *setter) = 0;
/// @note Takes ownership of @p setter.
virtual void addShaderUniformSetterFactory(IShaderUniformSetterFactory *setter) = 0;
};
IWritableShaderSource *createShaderSource();

View file

@ -36,7 +36,7 @@ ShadowRenderer::ShadowRenderer(IrrlichtDevice *device, Client *client) :
m_shadow_map_max_distance = g_settings->getFloat("shadow_map_max_distance");
m_shadow_map_texture_size = g_settings->getFloat("shadow_map_texture_size");
m_shadow_map_texture_size = g_settings->getU32("shadow_map_texture_size");
m_shadow_map_texture_32bit = g_settings->getBool("shadow_map_texture_32bit");
m_shadow_map_colored = g_settings->getBool("shadow_map_color");
@ -107,7 +107,7 @@ void ShadowRenderer::disable()
void ShadowRenderer::preInit(IWritableShaderSource *shsrc)
{
if (g_settings->getBool("enable_dynamic_shadows")) {
shsrc->addShaderConstantSetterFactory(new ShadowConstantSetterFactory());
shsrc->addShaderUniformSetterFactory(new ShadowUniformSetterFactory());
}
}
@ -273,7 +273,7 @@ void ShadowRenderer::updateSMTextures()
// Static shader values.
for (auto cb : {m_shadow_depth_cb, m_shadow_depth_entity_cb, m_shadow_depth_trans_cb}) {
if (cb) {
cb->MapRes = (f32)m_shadow_map_texture_size;
cb->MapRes = (u32)m_shadow_map_texture_size;
cb->MaxFar = (f32)m_shadow_map_max_distance * BS;
cb->PerspectiveBiasXY = getPerspectiveBiasXY();
cb->PerspectiveBiasZ = getPerspectiveBiasZ();
@ -514,6 +514,9 @@ void ShadowRenderer::mixShadowsQuad()
* Shaders system with custom IShaderConstantSetCallBack without messing up the
* code too much. If anyone knows how to integrate this with the standard MT
* shaders, please feel free to change it.
*
* TODO: as of now (2025) it should be possible to hook these up to the normal
* shader system.
*/
void ShadowRenderer::createShaders()

View file

@ -124,7 +124,7 @@ private:
video::SColor m_shadow_tint;
float m_shadow_strength_gamma;
float m_shadow_map_max_distance;
float m_shadow_map_texture_size;
u32 m_shadow_map_texture_size;
float m_time_day;
int m_shadow_samples;
bool m_shadow_map_texture_32bit;

View file

@ -5,7 +5,7 @@
#include "client/shadows/shadowsshadercallbacks.h"
#include "client/renderingengine.h"
void ShadowConstantSetter::onSetConstants(video::IMaterialRendererServices *services)
void ShadowUniformSetter::onSetUniforms(video::IMaterialRendererServices *services)
{
auto *shadow = RenderingEngine::get_shadow_renderer();
if (!shadow)

View file

@ -9,7 +9,7 @@
// Used by main game rendering
class ShadowConstantSetter : public IShaderConstantSetter
class ShadowUniformSetter : public IShaderUniformSetter
{
CachedPixelShaderSetting<f32, 16> m_shadow_view_proj{"m_ShadowViewProj"};
CachedPixelShaderSetting<f32, 3> m_light_direction{"v_LightDirection"};
@ -33,17 +33,17 @@ class ShadowConstantSetter : public IShaderConstantSetter
CachedPixelShaderSetting<f32> m_perspective_zbias_pixel{"zPerspectiveBias"};
public:
ShadowConstantSetter() = default;
~ShadowConstantSetter() = default;
ShadowUniformSetter() = default;
~ShadowUniformSetter() = default;
virtual void onSetConstants(video::IMaterialRendererServices *services) override;
virtual void onSetUniforms(video::IMaterialRendererServices *services) override;
};
class ShadowConstantSetterFactory : public IShaderConstantSetterFactory
class ShadowUniformSetterFactory : public IShaderUniformSetterFactory
{
public:
virtual IShaderConstantSetter *create() {
return new ShadowConstantSetter();
virtual IShaderUniformSetter *create() {
return new ShadowUniformSetter();
}
};

View file

@ -24,6 +24,13 @@ struct TextureInfo
std::set<std::string> sourceImages{};
};
// Stores internal information about a texture image.
struct ImageInfo
{
video::IImage *image = nullptr;
std::set<std::string> sourceImages;
};
// TextureSource
class TextureSource final : public IWritableTextureSource
{
@ -123,7 +130,13 @@ public:
video::SColor getTextureAverageColor(const std::string &name);
void setImageCaching(bool enabled);
private:
// Gets or generates an image for a texture string
// Caller needs to drop the returned image
video::IImage *getOrGenerateImage(const std::string &name,
std::set<std::string> &source_image_names);
// The id of the thread that is allowed to use irrlicht directly
std::thread::id m_main_thread;
@ -132,6 +145,12 @@ private:
// This should be only accessed from the main thread
ImageSource m_imagesource;
// Is the image cache enabled?
bool m_image_cache_enabled = false;
// Caches finished texture images before they are uploaded to the GPU
// (main thread use only)
std::unordered_map<std::string, ImageInfo> m_image_cache;
// Rebuild images and textures from the current set of source images
// Shall be called from the main thread.
// You ARE expected to be holding m_textureinfo_cache_mutex
@ -147,7 +166,7 @@ private:
// The first position contains a NULL texture.
std::vector<TextureInfo> m_textureinfo_cache;
// Maps a texture name to an index in the former.
std::map<std::string, u32> m_name_to_id;
std::unordered_map<std::string, u32> m_name_to_id;
// The two former containers are behind this mutex
std::mutex m_textureinfo_cache_mutex;
@ -191,18 +210,20 @@ TextureSource::TextureSource()
TextureSource::~TextureSource()
{
video::IVideoDriver *driver = RenderingEngine::get_video_driver();
u32 textures_before = driver->getTextureCount();
unsigned int textures_before = driver->getTextureCount();
for (const auto &it : m_image_cache) {
assert(it.second.image);
it.second.image->drop();
}
for (const auto &iter : m_textureinfo_cache) {
// cleanup texture
if (iter.texture)
driver->removeTexture(iter.texture);
}
m_textureinfo_cache.clear();
for (auto t : m_texture_trash) {
// cleanup trashed texture
driver->removeTexture(t);
}
@ -210,6 +231,26 @@ TextureSource::~TextureSource()
<< " after: " << driver->getTextureCount() << std::endl;
}
video::IImage *TextureSource::getOrGenerateImage(const std::string &name,
std::set<std::string> &source_image_names)
{
auto it = m_image_cache.find(name);
if (it != m_image_cache.end()) {
source_image_names = it->second.sourceImages;
it->second.image->grab();
return it->second.image;
}
std::set<std::string> tmp;
auto *img = m_imagesource.generateImage(name, tmp);
if (img && m_image_cache_enabled) {
img->grab();
m_image_cache[name] = {img, tmp};
}
source_image_names = std::move(tmp);
return img;
}
u32 TextureSource::getTextureId(const std::string &name)
{
{ // See if texture already exists
@ -281,12 +322,11 @@ u32 TextureSource::generateTexture(const std::string &name)
// passed into texture info for dynamic media tracking
std::set<std::string> source_image_names;
video::IImage *img = m_imagesource.generateImage(name, source_image_names);
video::IImage *img = getOrGenerateImage(name, source_image_names);
video::ITexture *tex = nullptr;
if (img) {
img = Align2Npot2(img, driver);
// Create texture from resulting image
tex = driver->addTexture(name.c_str(), img);
guiScalingCache(io::path(name.c_str()), driver, img);
@ -358,7 +398,7 @@ Palette* TextureSource::getPalette(const std::string &name)
if (it == m_palettes.end()) {
// Create palette
std::set<std::string> source_image_names; // unused, sadly.
video::IImage *img = m_imagesource.generateImage(name, source_image_names);
video::IImage *img = getOrGenerateImage(name, source_image_names);
if (!img) {
warningstream << "TextureSource::getPalette(): palette \"" << name
<< "\" could not be loaded." << std::endl;
@ -447,18 +487,29 @@ void TextureSource::rebuildImagesAndTextures()
{
MutexAutoLock lock(m_textureinfo_cache_mutex);
/*
* Note: While it may become useful in the future, it's not clear what the
* current purpose of this function is. The client loads all media into a
* freshly created texture source, so the only two textures that will ever be
* rebuilt are 'progress_bar.png' and 'progress_bar_bg.png'.
*/
video::IVideoDriver *driver = RenderingEngine::get_video_driver();
sanity_check(driver);
infostream << "TextureSource: recreating " << m_textureinfo_cache.size()
<< " textures" << std::endl;
assert(!m_image_cache_enabled || m_image_cache.empty());
// Recreate textures
for (TextureInfo &ti : m_textureinfo_cache) {
if (ti.name.empty())
continue; // Skip dummy entry
rebuildTexture(driver, ti);
}
// FIXME: we should rebuild palettes too
}
void TextureSource::rebuildTexture(video::IVideoDriver *driver, TextureInfo &ti)
@ -466,37 +517,47 @@ void TextureSource::rebuildTexture(video::IVideoDriver *driver, TextureInfo &ti)
assert(!ti.name.empty());
sanity_check(std::this_thread::get_id() == m_main_thread);
// Replaces the previous sourceImages.
// Shouldn't really need to be done, but can't hurt.
std::set<std::string> source_image_names;
video::IImage *img = m_imagesource.generateImage(ti.name, source_image_names);
img = Align2Npot2(img, driver);
video::IImage *img = getOrGenerateImage(ti.name, source_image_names);
// Create texture from resulting image
video::ITexture *t = nullptr;
if (img) {
video::ITexture *t = nullptr, *t_old = ti.texture;
if (!img) {
// new texture becomes null
} else if (t_old && t_old->getColorFormat() == img->getColorFormat() && t_old->getSize() == img->getDimension()) {
// can replace texture in-place
std::swap(t, t_old);
void *ptr = t->lock(video::ETLM_WRITE_ONLY);
if (ptr) {
memcpy(ptr, img->getData(), img->getImageDataSizeInBytes());
t->unlock();
t->regenerateMipMapLevels();
} else {
warningstream << "TextureSource::rebuildTexture(): lock failed for \""
<< ti.name << "\"" << std::endl;
}
} else {
// create new one
t = driver->addTexture(ti.name.c_str(), img);
guiScalingCache(io::path(ti.name.c_str()), driver, img);
img->drop();
}
video::ITexture *t_old = ti.texture;
// Replace texture
if (img)
guiScalingCache(io::path(ti.name.c_str()), driver, img);
// Replace texture info
if (img)
img->drop();
ti.texture = t;
ti.sourceImages = std::move(source_image_names);
if (t_old)
m_texture_trash.push_back(t_old);
}
video::SColor TextureSource::getTextureAverageColor(const std::string &name)
{
video::IVideoDriver *driver = RenderingEngine::get_video_driver();
video::ITexture *texture = getTexture(name);
if (!texture)
return {0, 0, 0, 0};
// Note: this downloads the texture back from the GPU, which is pointless
video::IImage *image = driver->createImage(texture,
core::position2d<s32>(0, 0),
texture->getOriginalSize());
assert(std::this_thread::get_id() == m_main_thread);
std::set<std::string> unused;
auto *image = getOrGenerateImage(name, unused);
if (!image)
return {0, 0, 0, 0};
@ -505,3 +566,15 @@ video::SColor TextureSource::getTextureAverageColor(const std::string &name)
return c;
}
void TextureSource::setImageCaching(bool enabled)
{
m_image_cache_enabled = enabled;
if (!enabled) {
for (const auto &it : m_image_cache) {
assert(it.second.image);
it.second.image->drop();
}
m_image_cache.clear();
}
}

View file

@ -25,10 +25,10 @@ class ISimpleTextureSource
{
public:
ISimpleTextureSource() = default;
virtual ~ISimpleTextureSource() = default;
virtual video::ITexture* getTexture(
/// @brief Generates and gets a texture
virtual video::ITexture *getTexture(
const std::string &name, u32 *id = nullptr) = 0;
};
@ -36,45 +36,73 @@ class ITextureSource : public ISimpleTextureSource
{
public:
ITextureSource() = default;
virtual ~ITextureSource() = default;
using ISimpleTextureSource::getTexture;
/// @brief Generates and gets ID of a texture
virtual u32 getTextureId(const std::string &name)=0;
/// @brief Returns name of existing texture by ID
virtual std::string getTextureName(u32 id)=0;
virtual video::ITexture* getTexture(u32 id)=0;
virtual video::ITexture* getTexture(
const std::string &name, u32 *id = nullptr)=0;
virtual video::ITexture* getTextureForMesh(
/// @brief Returns existing texture by ID
virtual video::ITexture *getTexture(u32 id)=0;
/**
* @brief Generates and gets a texture
* Filters will be applied to make the texture suitable for mipmapping and
* linear filtering during rendering.
*/
virtual video::ITexture *getTextureForMesh(
const std::string &name, u32 *id = nullptr) = 0;
/*!
/**
* Returns a palette from the given texture name.
* The pointer is valid until the texture source is
* destructed.
* Should be called from the main thread.
* Must be called from the main thread.
*/
virtual Palette* getPalette(const std::string &name) = 0;
virtual Palette *getPalette(const std::string &name) = 0;
/// @brief Check if given image name exists
virtual bool isKnownSourceImage(const std::string &name)=0;
/// @brief Return average color of a texture string
virtual video::SColor getTextureAverageColor(const std::string &name)=0;
// Note: this method is here because caching is the decision of the
// API user, even if his access is read-only.
/**
* Enables or disables the caching of finished texture images.
* This can be useful if you want to call getTextureAverageColor without
* duplicating work.
* @note Disabling caching will flush the cache.
*/
virtual void setImageCaching(bool enabled) {};
};
class IWritableTextureSource : public ITextureSource
{
public:
IWritableTextureSource() = default;
virtual ~IWritableTextureSource() = default;
virtual u32 getTextureId(const std::string &name)=0;
virtual std::string getTextureName(u32 id)=0;
virtual video::ITexture* getTexture(u32 id)=0;
virtual video::ITexture* getTexture(
const std::string &name, u32 *id = nullptr)=0;
virtual bool isKnownSourceImage(const std::string &name)=0;
/// @brief Fulfil texture requests from other threads
virtual void processQueue()=0;
/**
* @brief Inserts a source image. Must be called from the main thread.
* Takes ownership of @p img
*/
virtual void insertSourceImage(const std::string &name, video::IImage *img)=0;
/**
* Rebuilds all textures (in case-source images have changed)
* @note This won't invalidate old ITexture's, but may or may not reuse them.
* So you have to re-get all textures anyway.
*/
virtual void rebuildImagesAndTextures()=0;
virtual video::SColor getTextureAverageColor(const std::string &name)=0;
};
IWritableTextureSource *createTextureSource();

View file

@ -255,7 +255,7 @@ void WieldMeshSceneNode::setExtruded(const std::string &imagename,
dim = core::dimension2d<u32>(dim.Width, frame_height);
}
scene::IMesh *original = g_extrusion_mesh_cache->create(dim);
scene::SMesh *mesh = cloneMesh(original);
scene::SMesh *mesh = cloneStaticMesh(original);
original->drop();
//set texture
mesh->getMeshBuffer(0)->getMaterial().setTexture(0,
@ -281,12 +281,11 @@ void WieldMeshSceneNode::setExtruded(const std::string &imagename,
material.MaterialType = m_material_type;
material.MaterialTypeParam = 0.5f;
material.BackfaceCulling = true;
// Enable bi/trilinear filtering only for high resolution textures
bool bilinear_filter = dim.Width > 32 && m_bilinear_filter;
bool trilinear_filter = dim.Width > 32 && m_trilinear_filter;
// don't filter low-res textures, makes them look blurry
bool f_ok = std::min(dim.Width, dim.Height) >= TEXTURE_FILTER_MIN_SIZE;
material.forEachTexture([=] (auto &tex) {
setMaterialFilters(tex, bilinear_filter, trilinear_filter,
m_anisotropic_filter);
setMaterialFilters(tex, m_bilinear_filter && f_ok,
m_trilinear_filter && f_ok, m_anisotropic_filter);
});
// mipmaps cause "thin black line" artifacts
material.UseMipMaps = false;
@ -640,7 +639,7 @@ scene::SMesh *getExtrudedMesh(ITextureSource *tsrc,
// get mesh
core::dimension2d<u32> dim = texture->getSize();
scene::IMesh *original = g_extrusion_mesh_cache->create(dim);
scene::SMesh *mesh = cloneMesh(original);
scene::SMesh *mesh = cloneStaticMesh(original);
original->drop();
//set texture

View file

@ -99,3 +99,9 @@
#define SCREENSHOT_MAX_SERIAL_TRIES 1000
#define TTF_DEFAULT_FONT_SIZE (16)
// Minimum texture size enforced/checked for enabling linear filtering
// This serves as the minimum for `texture_min_size`.
// The intent is to ensure that the rendering doesn't turn terribly blurry
// when filtering is enabled.
#define TEXTURE_FILTER_MIN_SIZE 192U

View file

@ -164,9 +164,9 @@ void MapDatabasePostgreSQL::createDatabase()
{
createTableIfNotExists("blocks",
"CREATE TABLE blocks ("
"posX INT NOT NULL,"
"posY INT NOT NULL,"
"posZ INT NOT NULL,"
"posX smallint NOT NULL,"
"posY smallint NOT NULL,"
"posZ smallint NOT NULL,"
"data BYTEA,"
"PRIMARY KEY (posX,posY,posZ)"
");"

View file

@ -246,7 +246,7 @@ void set_default_settings()
settings->setDefault("undersampling", "1");
settings->setDefault("world_aligned_mode", "enable");
settings->setDefault("autoscale_mode", "disable");
settings->setDefault("texture_min_size", "64");
settings->setDefault("texture_min_size", std::to_string(TEXTURE_FILTER_MIN_SIZE));
settings->setDefault("enable_fog", "true");
settings->setDefault("fog_start", "0.4");
settings->setDefault("3d_mode", "none");

View file

@ -5,6 +5,7 @@ set(gui_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/guiButton.cpp
${CMAKE_CURRENT_SOURCE_DIR}/guiButtonImage.cpp
${CMAKE_CURRENT_SOURCE_DIR}/guiButtonItemImage.cpp
${CMAKE_CURRENT_SOURCE_DIR}/guiButtonKey.cpp
${CMAKE_CURRENT_SOURCE_DIR}/guiChatConsole.cpp
${CMAKE_CURRENT_SOURCE_DIR}/guiEditBox.cpp
${CMAKE_CURRENT_SOURCE_DIR}/guiEditBoxWithScrollbar.cpp
@ -12,7 +13,6 @@ set(gui_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/guiFormSpecMenu.cpp
${CMAKE_CURRENT_SOURCE_DIR}/guiInventoryList.cpp
${CMAKE_CURRENT_SOURCE_DIR}/guiItemImage.cpp
${CMAKE_CURRENT_SOURCE_DIR}/guiKeyChangeMenu.cpp
${CMAKE_CURRENT_SOURCE_DIR}/guiOpenURL.cpp
${CMAKE_CURRENT_SOURCE_DIR}/guiPasswordChange.cpp
${CMAKE_CURRENT_SOURCE_DIR}/guiPathSelectMenu.cpp

141
src/gui/guiButtonKey.cpp Normal file
View file

@ -0,0 +1,141 @@
// Luanti
// SPDX-License-Identifier: LGPL-2.1-or-later
#include "guiButtonKey.h"
using namespace irr::gui;
GUIButtonKey *GUIButtonKey::addButton(IGUIEnvironment *environment,
const core::rect<s32> &rectangle, ISimpleTextureSource *tsrc,
IGUIElement *parent, s32 id, const wchar_t *text,
const wchar_t *tooltiptext)
{
auto button = make_irr<GUIButtonKey>(environment,
parent ? parent : environment->getRootGUIElement(), id, rectangle, tsrc);
if (text)
button->setText(text);
if (tooltiptext)
button->setToolTipText(tooltiptext);
return button.get();
}
void GUIButtonKey::setKey(KeyPress kp)
{
key_value = kp;
keysym = utf8_to_wide(kp.sym());
super::setText(wstrgettext(kp.name()).c_str());
}
void GUIButtonKey::sendKey()
{
if (Parent) {
SEvent e;
e.EventType = EET_GUI_EVENT;
e.GUIEvent.Caller = this;
e.GUIEvent.Element = nullptr;
e.GUIEvent.EventType = EGET_BUTTON_CLICKED;
Parent->OnEvent(e);
}
}
bool GUIButtonKey::OnEvent(const SEvent & event)
{
switch(event.EventType)
{
case EET_KEY_INPUT_EVENT:
if (!event.KeyInput.PressedDown) {
bool wasPressed = isPressed();
setPressed(false);
if (capturing) {
cancelCapture();
if (event.KeyInput.Key != KEY_ESCAPE)
sendKey();
return true;
} else if (wasPressed && (event.KeyInput.Key == KEY_RETURN || event.KeyInput.Key == KEY_SPACE)) {
startCapture();
return true;
}
break;
} else if (capturing) {
if (event.KeyInput.Key != KEY_ESCAPE) {
setPressed(true);
setKey(KeyPress(event.KeyInput));
}
return true;
} else if (event.KeyInput.Key == KEY_RETURN || event.KeyInput.Key == KEY_SPACE) {
setPressed(true);
return true;
}
break;
case EET_MOUSE_INPUT_EVENT: {
auto in_rect = AbsoluteClippingRect.isPointInside(
core::position2d<s32>(event.MouseInput.X, event.MouseInput.Y));
switch (event.MouseInput.Event)
{
case EMIE_LMOUSE_LEFT_UP:
if (!capturing && in_rect) {
setPressed(false);
startCapture();
return true;
}
[[fallthrough]];
case EMIE_MMOUSE_LEFT_UP: [[fallthrough]];
case EMIE_RMOUSE_LEFT_UP:
setPressed(false);
if (capturing) {
cancelCapture();
sendKey();
return true;
}
break;
case EMIE_LMOUSE_PRESSED_DOWN:
if (capturing) {
if (event.MouseInput.Simulated) {
cancelCapture(true);
if (in_rect)
return true;
} else {
setPressed(true);
setKey(LMBKey);
return true;
}
} else if (in_rect) {
Environment->setFocus(this);
setPressed(true);
return true;
}
break;
case EMIE_MMOUSE_PRESSED_DOWN:
if (capturing) {
setPressed(true);
setKey(MMBKey);
return true;
}
break;
case EMIE_RMOUSE_PRESSED_DOWN:
if (capturing) {
setPressed(true);
setKey(RMBKey);
return true;
}
break;
default:
break;
}
break;
}
case EET_GUI_EVENT:
if (event.GUIEvent.EventType == EGET_ELEMENT_FOCUS_LOST) {
if (capturing)
return true;
else
nostart = false; // lift nostart restriction if "mouse" (finger) is released outside the button
}
default:
break;
}
return Parent ? Parent->OnEvent(event) : false;
}

75
src/gui/guiButtonKey.h Normal file
View file

@ -0,0 +1,75 @@
// Luanti
// SPDX-License-Identifier: LGPL-2.1-or-later
#pragma once
#include "guiButton.h"
#include "client/keycode.h"
#include "util/string.h"
#include "gettext.h"
using namespace irr;
class GUIButtonKey : public GUIButton
{
using super = GUIButton;
public:
//! Constructor
GUIButtonKey(gui::IGUIEnvironment *environment, gui::IGUIElement *parent,
s32 id, core::rect<s32> rectangle, ISimpleTextureSource *tsrc,
bool noclip = false)
: GUIButton(environment, parent, id, rectangle, tsrc, noclip) {}
//! Sets the text for the key field
virtual void setText(const wchar_t *text) override
{
setKey(wide_to_utf8(text));
}
//! Gets the value for the key field
virtual const wchar_t *getText() const override
{
return keysym.c_str();
}
//! Do not drop returned handle
static GUIButtonKey *addButton(gui::IGUIEnvironment *environment,
const core::rect<s32> &rectangle, ISimpleTextureSource *tsrc,
IGUIElement *parent, s32 id, const wchar_t *text = L"",
const wchar_t *tooltiptext = L"");
//! Called if an event happened
virtual bool OnEvent(const SEvent &event) override;
private:
void sendKey();
//! Start key capture
void startCapture()
{
if (nostart) {
nostart = false;
return;
}
capturing = true;
super::setText(wstrgettext("Press Button").c_str());
}
//! Cancel key capture
// inhibit_restart: whether the next call to startCapture should be inhibited
void cancelCapture(bool inhibit_restart = false)
{
capturing = false;
nostart |= inhibit_restart;
super::setText(wstrgettext(key_value.name()).c_str());
}
//! Sets the captured key and stop capturing
void setKey(KeyPress key);
bool capturing = false;
bool nostart = false;
KeyPress key_value = {};
std::wstring keysym;
};

View file

@ -64,7 +64,7 @@ MenuTextureSource::~MenuTextureSource()
video::ITexture *MenuTextureSource::getTexture(const std::string &name, u32 *id)
{
if (id)
*id = 0;
*id = 1;
if (name.empty())
return NULL;
@ -78,7 +78,6 @@ video::ITexture *MenuTextureSource::getTexture(const std::string &name, u32 *id)
if (!image)
return NULL;
image = Align2Npot2(image, m_driver);
retval = m_driver->addTexture(name.c_str(), image);
image->drop();
@ -118,6 +117,10 @@ GUIEngine::GUIEngine(JoystickController *joystick,
m_data(data),
m_kill(kill)
{
// Go back to our mainmenu fonts
// Delayed until mainmenu initialization because of #15883
g_fontengine->clearMediaFonts();
// initialize texture pointers
for (image_definition &texture : m_textures) {
texture.texture = NULL;
@ -168,7 +171,7 @@ GUIEngine::GUIEngine(JoystickController *joystick,
"",
false);
m_menu->allowClose(false);
m_menu->defaultAllowClose(false);
m_menu->lockSize(true,v2u32(800,600));
// Initialize scripting

View file

@ -10,9 +10,11 @@
#include <limits>
#include <sstream>
#include "guiFormSpecMenu.h"
#include "EGUIElementTypes.h"
#include "constants.h"
#include "gamedef.h"
#include "client/keycode.h"
#include "gui/guiTable.h"
#include "util/strfnd.h"
#include <IGUIButton.h>
#include <IGUICheckBox.h>
@ -47,6 +49,7 @@
#include "guiButton.h"
#include "guiButtonImage.h"
#include "guiButtonItemImage.h"
#include "guiButtonKey.h"
#include "guiEditBoxWithScrollbar.h"
#include "guiInventoryList.h"
#include "guiItemImage.h"
@ -105,7 +108,6 @@ GUIFormSpecMenu::GUIFormSpecMenu(JoystickController *joystick,
current_keys_pending.key_down = false;
current_keys_pending.key_up = false;
current_keys_pending.key_enter = false;
current_keys_pending.key_escape = false;
m_tooltip_show_delay = (u32)g_settings->getS32("tooltip_show_delay");
m_tooltip_append_itemname = g_settings->getBool("tooltip_append_itemname");
@ -198,7 +200,7 @@ void GUIFormSpecMenu::setInitialFocus()
// 3. first table
for (gui::IGUIElement *it : children) {
if (it->getTypeName() == std::string("GUITable")) {
if (it->getType() == gui::EGUIET_TABLE) {
Environment->setFocus(it);
return;
}
@ -1026,8 +1028,16 @@ void GUIFormSpecMenu::parseButton(parserData* data, const std::string &element)
if (data->type == "button_url" || data->type == "button_url_exit")
spec.url = url;
GUIButton *e = GUIButton::addButton(Environment, rect, m_tsrc,
data->current_parent, spec.fid, spec.flabel.c_str());
GUIButton *e;
if (data->type == "button_key") {
spec.ftype = f_Unknown;
e = GUIButtonKey::addButton(Environment, rect, m_tsrc,
data->current_parent, spec.fid, spec.flabel.c_str());
} else {
e = GUIButton::addButton(Environment, rect, m_tsrc,
data->current_parent, spec.fid, spec.flabel.c_str());
}
auto style = getStyleForElement(data->type, name, (data->type != "button") ? "button" : "");
@ -1760,12 +1770,19 @@ void GUIFormSpecMenu::parseHyperText(parserData *data, const std::string &elemen
void GUIFormSpecMenu::parseLabel(parserData* data, const std::string &element)
{
std::vector<std::string> parts;
if (!precheckElement("label", element, 2, 2, parts))
if (!precheckElement("label", element, 2, data->real_coordinates ? 3 : 2, parts))
return;
std::vector<std::string> v_pos = split(parts[0],',');
std::vector<std::string> v_pos = split(parts[0], ',');
MY_CHECKPOS("label", 0);
MY_CHECKPOS("label",0);
bool has_size = parts.size() >= 3;
v2s32 geom;
if (has_size) {
std::vector<std::string> v_geom = split(parts[1], ',');
MY_CHECKGEOM("label", 1);
geom = getRealCoordinateGeometry(v_geom);
}
if(!data->explicit_size)
warningstream<<"invalid use of label without a size[] element"<<std::endl;
@ -1775,53 +1792,8 @@ void GUIFormSpecMenu::parseLabel(parserData* data, const std::string &element)
if (!font)
font = m_font;
EnrichedString str(unescape_string(utf8_to_wide(parts[1])));
size_t str_pos = 0;
for (size_t i = 0; str_pos < str.size(); ++i) {
EnrichedString line = str.getNextLine(&str_pos);
core::rect<s32> rect;
if (data->real_coordinates) {
// Lines are spaced at the distance of 1/2 imgsize.
// This alows lines that line up with the new elements
// easily without sacrificing good line distance. If
// it was one whole imgsize, it would have too much
// spacing.
v2s32 pos = getRealCoordinateBasePos(v_pos);
// Labels are positioned by their center, not their top.
pos.Y += (((float) imgsize.Y) / -2) + (((float) imgsize.Y) * i / 2);
rect = core::rect<s32>(
pos.X, pos.Y,
pos.X + font->getDimension(line.c_str()).Width,
pos.Y + imgsize.Y);
} else {
// Lines are spaced at the nominal distance of
// 2/5 inventory slot, even if the font doesn't
// quite match that. This provides consistent
// form layout, at the expense of sometimes
// having sub-optimal spacing for the font.
// We multiply by 2 and then divide by 5, rather
// than multiply by 0.4, to get exact results
// in the integer cases: 0.4 is not exactly
// representable in binary floating point.
v2s32 pos = getElementBasePos(nullptr);
pos.X += stof(v_pos[0]) * spacing.X;
pos.Y += (stof(v_pos[1]) + 7.0f / 30.0f) * spacing.Y;
pos.Y += ((float) i) * spacing.Y * 2.0 / 5.0;
rect = core::rect<s32>(
pos.X, pos.Y - m_btn_height,
pos.X + font->getDimension(line.c_str()).Width,
pos.Y + m_btn_height);
}
auto add_label = [&](core::rect<s32> rect, const EnrichedString &text,
EGUI_ALIGNMENT align_h, EGUI_ALIGNMENT align_v, bool word_wrap) {
FieldSpec spec(
"",
L"",
@ -1830,9 +1802,10 @@ void GUIFormSpecMenu::parseLabel(parserData* data, const std::string &element)
4
);
gui::IGUIStaticText *e = gui::StaticText::add(Environment,
line, rect, false, false, data->current_parent,
text, rect, false, false, data->current_parent,
spec.fid);
e->setTextAlignment(gui::EGUIA_UPPERLEFT, gui::EGUIA_CENTER);
e->setTextAlignment(align_h, align_v);
e->setWordWrap(word_wrap);
e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
e->setOverrideColor(style.getColor(StyleSpec::TEXTCOLOR, video::SColor(0xFFFFFFFF)));
@ -1843,6 +1816,67 @@ void GUIFormSpecMenu::parseLabel(parserData* data, const std::string &element)
// labels should let events through
e->grab();
m_clickthrough_elements.push_back(e);
};
EnrichedString str(unescape_string(utf8_to_wide(parts[has_size ? 2 : 1])));
if (geom == v2s32()) {
size_t str_pos = 0;
for (size_t i = 0; str_pos < str.size(); ++i) {
EnrichedString line = str.getNextLine(&str_pos);
core::rect<s32> rect;
if (data->real_coordinates) {
// Lines are spaced at the distance of 1/2 imgsize.
// This alows lines that line up with the new elements
// easily without sacrificing good line distance. If
// it was one whole imgsize, it would have too much
// spacing.
v2s32 pos = getRealCoordinateBasePos(v_pos);
// Labels are positioned by their center, not their top.
pos.Y += (((float) imgsize.Y) / -2) + (((float) imgsize.Y) * i / 2);
rect = core::rect<s32>(
pos.X, pos.Y,
pos.X + font->getDimension(line.c_str()).Width,
pos.Y + imgsize.Y);
} else {
// Lines are spaced at the nominal distance of
// 2/5 inventory slot, even if the font doesn't
// quite match that. This provides consistent
// form layout, at the expense of sometimes
// having sub-optimal spacing for the font.
// We multiply by 2 and then divide by 5, rather
// than multiply by 0.4, to get exact results
// in the integer cases: 0.4 is not exactly
// representable in binary floating point.
v2s32 pos = getElementBasePos(nullptr);
pos.X += stof(v_pos[0]) * spacing.X;
pos.Y += (stof(v_pos[1]) + 7.0f / 30.0f) * spacing.Y;
pos.Y += ((float) i) * spacing.Y * 2.0 / 5.0;
rect = core::rect<s32>(
pos.X, pos.Y - m_btn_height,
pos.X + font->getDimension(line.c_str()).Width,
pos.Y + m_btn_height);
}
add_label(rect, line, gui::EGUIA_UPPERLEFT, gui::EGUIA_CENTER, false);
}
} else {
v2s32 pos = getRealCoordinateBasePos(v_pos);
core::rect<s32> rect(
pos.X, pos.Y,
pos.X + geom.X,
pos.Y + geom.Y);
add_label(rect, str, gui::EGUIA_UPPERLEFT, gui::EGUIA_UPPERLEFT, true);
}
}
@ -2833,6 +2867,11 @@ void GUIFormSpecMenu::parseModel(parserData *data, const std::string &element)
m_fields.push_back(spec);
}
void GUIFormSpecMenu::parseAllowClose(parserData *data, const std::string &element)
{
m_allowclose = is_yes(element);
}
void GUIFormSpecMenu::removeAll()
{
// Remove children
@ -2869,6 +2908,7 @@ const std::unordered_map<std::string, std::function<void(GUIFormSpecMenu*, GUIFo
{"button_exit", &GUIFormSpecMenu::parseButton},
{"button_url", &GUIFormSpecMenu::parseButton},
{"button_url_exit", &GUIFormSpecMenu::parseButton},
{"button_key", &GUIFormSpecMenu::parseButton},
{"background", &GUIFormSpecMenu::parseBackground},
{"background9", &GUIFormSpecMenu::parseBackground},
{"tableoptions", &GUIFormSpecMenu::parseTableOptions},
@ -2901,6 +2941,7 @@ const std::unordered_map<std::string, std::function<void(GUIFormSpecMenu*, GUIFo
{"scroll_container_end", &GUIFormSpecMenu::parseScrollContainerEnd},
{"set_focus", &GUIFormSpecMenu::parseSetFocus},
{"model", &GUIFormSpecMenu::parseModel},
{"allow_close", &GUIFormSpecMenu::parseAllowClose},
};
@ -3003,6 +3044,7 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
field_close_on_enter.clear();
m_dropdown_index_event.clear();
m_allowclose = m_default_allowclose;
m_bgnonfullscreen = true;
m_bgfullscreen = false;
@ -3788,12 +3830,12 @@ void GUIFormSpecMenu::acceptInput(FormspecQuitMode quitmode)
if (quitmode == quit_mode_accept) {
fields["quit"] = "true";
}
if (quitmode == quit_mode_cancel) {
} else if (quitmode == quit_mode_cancel) {
fields["quit"] = "true";
m_text_dst->gotText(fields);
return;
} else if (quitmode == quit_mode_try) {
fields["try_quit"] = "true";
}
if (current_keys_pending.key_down) {
@ -3816,11 +3858,6 @@ void GUIFormSpecMenu::acceptInput(FormspecQuitMode quitmode)
current_field_enter_pending.clear();
}
if (current_keys_pending.key_escape) {
fields["key_escape"] = "true";
current_keys_pending.key_escape = false;
}
for (const GUIFormSpecMenu::FieldSpec &s : m_fields) {
if (s.send) {
std::string name = s.fname;
@ -3997,6 +4034,8 @@ bool GUIFormSpecMenu::preprocessEvent(const SEvent& event)
if (m_allowclose) {
acceptInput(quit_mode_accept);
quitMenu();
} else {
acceptInput(quit_mode_try);
}
}
}
@ -4013,7 +4052,7 @@ void GUIFormSpecMenu::tryClose()
acceptInput(quit_mode_cancel);
quitMenu();
} else {
m_text_dst->gotText(L"MenuQuit");
acceptInput(quit_mode_try);
}
}
@ -4059,9 +4098,13 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
FATAL_ERROR("Reached a source line that can't ever been reached");
break;
}
if (current_keys_pending.key_enter && m_allowclose) {
acceptInput(quit_mode_accept);
quitMenu();
if (current_keys_pending.key_enter) {
if (m_allowclose) {
acceptInput(quit_mode_accept);
quitMenu();
} else {
acceptInput(quit_mode_try);
}
} else {
acceptInput();
}
@ -4806,14 +4849,9 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
s32 caller_id = event.GUIEvent.Caller->getID();
if (caller_id == 257) {
if (m_allowclose) {
acceptInput(quit_mode_accept);
quitMenu();
} else {
acceptInput();
m_text_dst->gotText(L"ExitButton");
}
// quitMenu deallocates menu
acceptInput(quit_mode_accept);
m_text_dst->gotText(L"ExitButton");
quitMenu();
return true;
}
@ -4842,12 +4880,9 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
}
if (s.is_exit) {
if (m_allowclose) {
acceptInput(quit_mode_accept);
quitMenu();
} else {
m_text_dst->gotText(L"ExitButton");
}
acceptInput(quit_mode_accept);
m_text_dst->gotText(L"ExitButton");
quitMenu();
return true;
}
@ -4910,15 +4945,18 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
}
}
if (m_allowclose && close_on_enter) {
current_keys_pending.key_enter = true;
acceptInput(quit_mode_accept);
quitMenu();
current_keys_pending.key_enter = true;
if (close_on_enter) {
if (m_allowclose) {
acceptInput(quit_mode_accept);
quitMenu();
} else {
acceptInput(quit_mode_try);
}
} else {
current_keys_pending.key_enter = true;
acceptInput();
}
// quitMenu deallocates menu
return true;
}
}

View file

@ -47,7 +47,8 @@ enum FormspecFieldType {
enum FormspecQuitMode {
quit_mode_no,
quit_mode_accept,
quit_mode_cancel
quit_mode_cancel,
quit_mode_try,
};
enum ButtonEventType : u8
@ -203,8 +204,11 @@ public:
m_text_dst = text_dst;
}
void allowClose(bool value)
void defaultAllowClose(bool value)
{
// Also set m_allowclose here in order to have the correct value if
// escape is pressed before regenerateGui() is called.
m_default_allowclose = value;
m_allowclose = value;
}
@ -363,6 +367,7 @@ protected:
u64 m_hovered_time = 0;
s32 m_old_tooltip_id = -1;
bool m_default_allowclose = true;
bool m_allowclose = true;
bool m_lock = false;
v2u32 m_lockscreensize;
@ -422,7 +427,6 @@ private:
bool key_up;
bool key_down;
bool key_enter;
bool key_escape;
};
fs_key_pending current_keys_pending;
@ -484,6 +488,7 @@ private:
void parseStyle(parserData *data, const std::string &element);
void parseSetFocus(parserData *, const std::string &element);
void parseModel(parserData *data, const std::string &element);
void parseAllowClose(parserData *data, const std::string &element);
bool parseMiddleRect(const std::string &value, core::rect<s32> *parsed_rect);

View file

@ -1,401 +0,0 @@
// Luanti
// SPDX-License-Identifier: LGPL-2.1-or-later
// Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
// Copyright (C) 2013 Ciaran Gultnieks <ciaran@ciarang.com>
// Copyright (C) 2013 teddydestodes <derkomtur@schattengang.net>
#include "guiKeyChangeMenu.h"
#include "debug.h"
#include "guiButton.h"
#include <string>
#include <IGUICheckBox.h>
#include <IGUIEditBox.h>
#include <IGUIButton.h>
#include <IGUIStaticText.h>
#include <IGUIFont.h>
#include <IVideoDriver.h>
#include "settings.h"
#include "gettext.h"
#include "mainmenumanager.h" // for g_gamecallback
#define KMaxButtonPerColumns 12
extern MainGameCallback *g_gamecallback;
enum
{
GUI_ID_BACK_BUTTON = 101, GUI_ID_ABORT_BUTTON, GUI_ID_SCROLL_BAR,
// buttons
GUI_ID_KEY_FORWARD_BUTTON,
GUI_ID_KEY_BACKWARD_BUTTON,
GUI_ID_KEY_LEFT_BUTTON,
GUI_ID_KEY_RIGHT_BUTTON,
GUI_ID_KEY_AUX1_BUTTON,
GUI_ID_KEY_FLY_BUTTON,
GUI_ID_KEY_FAST_BUTTON,
GUI_ID_KEY_JUMP_BUTTON,
GUI_ID_KEY_NOCLIP_BUTTON,
GUI_ID_KEY_PITCH_MOVE,
GUI_ID_KEY_CHAT_BUTTON,
GUI_ID_KEY_CMD_BUTTON,
GUI_ID_KEY_CMD_LOCAL_BUTTON,
GUI_ID_KEY_CONSOLE_BUTTON,
GUI_ID_KEY_SNEAK_BUTTON,
GUI_ID_KEY_DROP_BUTTON,
GUI_ID_KEY_INVENTORY_BUTTON,
GUI_ID_KEY_HOTBAR_PREV_BUTTON,
GUI_ID_KEY_HOTBAR_NEXT_BUTTON,
GUI_ID_KEY_MUTE_BUTTON,
GUI_ID_KEY_DEC_VOLUME_BUTTON,
GUI_ID_KEY_INC_VOLUME_BUTTON,
GUI_ID_KEY_RANGE_BUTTON,
GUI_ID_KEY_ZOOM_BUTTON,
GUI_ID_KEY_CAMERA_BUTTON,
GUI_ID_KEY_MINIMAP_BUTTON,
GUI_ID_KEY_SCREENSHOT_BUTTON,
GUI_ID_KEY_CHATLOG_BUTTON,
GUI_ID_KEY_BLOCK_BOUNDS_BUTTON,
GUI_ID_KEY_HUD_BUTTON,
GUI_ID_KEY_FOG_BUTTON,
GUI_ID_KEY_DEC_RANGE_BUTTON,
GUI_ID_KEY_INC_RANGE_BUTTON,
GUI_ID_KEY_AUTOFWD_BUTTON,
// other
GUI_ID_CB_AUX1_DESCENDS,
GUI_ID_CB_DOUBLETAP_JUMP,
GUI_ID_CB_AUTOJUMP,
};
GUIKeyChangeMenu::GUIKeyChangeMenu(gui::IGUIEnvironment* env,
gui::IGUIElement* parent, s32 id, IMenuManager *menumgr,
ISimpleTextureSource *tsrc) :
GUIModalMenu(env, parent, id, menumgr),
m_tsrc(tsrc)
{
init_keys();
}
GUIKeyChangeMenu::~GUIKeyChangeMenu()
{
removeAllChildren();
key_used_text = nullptr;
for (key_setting *ks : key_settings) {
delete ks;
}
key_settings.clear();
}
void GUIKeyChangeMenu::regenerateGui(v2u32 screensize)
{
removeAllChildren();
key_used_text = nullptr;
ScalingInfo info = getScalingInfo(screensize, v2u32(835, 430));
const float s = info.scale;
DesiredRect = info.rect;
recalculateAbsolutePosition(false);
v2s32 size = DesiredRect.getSize();
v2s32 topleft(0, 0);
{
core::rect<s32> rect(0, 0, 600 * s, 40 * s);
rect += topleft + v2s32(25 * s, 3 * s);
//gui::IGUIStaticText *t =
gui::StaticText::add(Environment, wstrgettext("Keybindings."), rect,
false, true, this, -1);
//t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT);
}
// Build buttons
v2s32 offset(25 * s, 60 * s);
for(size_t i = 0; i < key_settings.size(); i++)
{
key_setting *k = key_settings.at(i);
{
core::rect<s32> rect(0, 0, 150 * s, 20 * s);
rect += topleft + v2s32(offset.X, offset.Y);
gui::StaticText::add(Environment, k->button_name, rect,
false, true, this, -1);
}
{
core::rect<s32> rect(0, 0, 100 * s, 30 * s);
rect += topleft + v2s32(offset.X + 150 * s, offset.Y - 5 * s);
k->button = GUIButton::addButton(Environment, rect, m_tsrc, this, k->id,
wstrgettext(k->key.name()).c_str());
}
if ((i + 1) % KMaxButtonPerColumns == 0) {
offset.X += 260 * s;
offset.Y = 60 * s;
} else {
offset += v2s32(0, 25 * s);
}
}
{
s32 option_x = offset.X;
s32 option_y = offset.Y + 5 * s;
u32 option_w = 180 * s;
{
core::rect<s32> rect(0, 0, option_w, 30 * s);
rect += topleft + v2s32(option_x, option_y);
Environment->addCheckBox(g_settings->getBool("aux1_descends"), rect, this,
GUI_ID_CB_AUX1_DESCENDS, wstrgettext("\"Aux1\" = climb down").c_str());
}
offset += v2s32(0, 25 * s);
}
{
s32 option_x = offset.X;
s32 option_y = offset.Y + 5 * s;
u32 option_w = 280 * s;
{
core::rect<s32> rect(0, 0, option_w, 30 * s);
rect += topleft + v2s32(option_x, option_y);
Environment->addCheckBox(g_settings->getBool("doubletap_jump"), rect, this,
GUI_ID_CB_DOUBLETAP_JUMP, wstrgettext("Double tap \"jump\" to toggle fly").c_str());
}
offset += v2s32(0, 25 * s);
}
{
s32 option_x = offset.X;
s32 option_y = offset.Y + 5 * s;
u32 option_w = 280;
{
core::rect<s32> rect(0, 0, option_w, 30 * s);
rect += topleft + v2s32(option_x, option_y);
Environment->addCheckBox(g_settings->getBool("autojump"), rect, this,
GUI_ID_CB_AUTOJUMP, wstrgettext("Automatic jumping").c_str());
}
offset += v2s32(0, 25);
}
{
core::rect<s32> rect(0, 0, 100 * s, 30 * s);
rect += topleft + v2s32(size.X / 2 - 105 * s, size.Y - 40 * s);
GUIButton::addButton(Environment, rect, m_tsrc, this, GUI_ID_BACK_BUTTON,
wstrgettext("Save").c_str());
}
{
core::rect<s32> rect(0, 0, 100 * s, 30 * s);
rect += topleft + v2s32(size.X / 2 + 5 * s, size.Y - 40 * s);
GUIButton::addButton(Environment, rect, m_tsrc, this, GUI_ID_ABORT_BUTTON,
wstrgettext("Cancel").c_str());
}
}
void GUIKeyChangeMenu::drawMenu()
{
gui::IGUISkin* skin = Environment->getSkin();
if (!skin)
return;
video::IVideoDriver* driver = Environment->getVideoDriver();
video::SColor bgcolor(140, 0, 0, 0);
driver->draw2DRectangle(bgcolor, AbsoluteRect, &AbsoluteClippingRect);
gui::IGUIElement::draw();
}
bool GUIKeyChangeMenu::acceptInput()
{
clearKeyCache();
for (key_setting *k : key_settings) {
std::string default_key;
Settings::getLayer(SL_DEFAULTS)->getNoEx(k->setting_name, default_key);
if (k->key.sym() != default_key)
g_settings->set(k->setting_name, k->key.sym());
else
g_settings->remove(k->setting_name);
}
{
gui::IGUIElement *e = getElementFromId(GUI_ID_CB_AUX1_DESCENDS);
if(e && e->getType() == gui::EGUIET_CHECK_BOX)
g_settings->setBool("aux1_descends", ((gui::IGUICheckBox*)e)->isChecked());
}
{
gui::IGUIElement *e = getElementFromId(GUI_ID_CB_DOUBLETAP_JUMP);
if(e && e->getType() == gui::EGUIET_CHECK_BOX)
g_settings->setBool("doubletap_jump", ((gui::IGUICheckBox*)e)->isChecked());
}
{
gui::IGUIElement *e = getElementFromId(GUI_ID_CB_AUTOJUMP);
if(e && e->getType() == gui::EGUIET_CHECK_BOX)
g_settings->setBool("autojump", ((gui::IGUICheckBox*)e)->isChecked());
}
g_gamecallback->signalKeyConfigChange();
return true;
}
bool GUIKeyChangeMenu::resetMenu()
{
if (active_key) {
active_key->button->setText(wstrgettext(active_key->key.name()).c_str());
active_key = nullptr;
return false;
}
return true;
}
bool GUIKeyChangeMenu::OnEvent(const SEvent& event)
{
if (event.EventType == EET_KEY_INPUT_EVENT && active_key
&& event.KeyInput.PressedDown) {
KeyPress kp(event.KeyInput);
if (event.KeyInput.Key == irr::KEY_DELETE)
kp = KeyPress(""); // To erase key settings
else if (event.KeyInput.Key == irr::KEY_ESCAPE)
kp = active_key->key; // Cancel
bool shift_went_down = false;
if(!shift_down &&
(event.KeyInput.Key == irr::KEY_SHIFT ||
event.KeyInput.Key == irr::KEY_LSHIFT ||
event.KeyInput.Key == irr::KEY_RSHIFT))
shift_went_down = true;
// Display Key already in use message
bool key_in_use = false;
if (kp) {
for (key_setting *ks : key_settings) {
if (ks != active_key && ks->key == kp) {
key_in_use = true;
break;
}
}
}
if (key_in_use && !this->key_used_text) {
core::rect<s32> rect(0, 0, 600, 40);
rect += v2s32(0, 0) + v2s32(25, 30);
this->key_used_text = gui::StaticText::add(Environment,
wstrgettext("Key already in use"),
rect, false, true, this, -1);
} else if (!key_in_use && this->key_used_text) {
this->key_used_text->remove();
this->key_used_text = nullptr;
}
// But go on
{
active_key->key = kp;
active_key->button->setText(wstrgettext(kp.name()).c_str());
// Allow characters made with shift
if (shift_went_down){
shift_down = true;
return false;
}
active_key = nullptr;
return true;
}
} else if (event.EventType == EET_KEY_INPUT_EVENT && !active_key
&& event.KeyInput.PressedDown
&& event.KeyInput.Key == irr::KEY_ESCAPE) {
quitMenu();
return true;
} else if (event.EventType == EET_GUI_EVENT) {
if (event.GUIEvent.EventType == gui::EGET_ELEMENT_FOCUS_LOST
&& isVisible())
{
if (!canTakeFocus(event.GUIEvent.Element))
{
infostream << "GUIKeyChangeMenu: Not allowing focus change."
<< std::endl;
// Returning true disables focus change
return true;
}
}
if (event.GUIEvent.EventType == gui::EGET_BUTTON_CLICKED)
{
switch (event.GUIEvent.Caller->getID())
{
case GUI_ID_BACK_BUTTON: //back
acceptInput();
quitMenu();
return true;
case GUI_ID_ABORT_BUTTON: //abort
quitMenu();
return true;
default:
resetMenu();
for (key_setting *ks : key_settings) {
if (ks->id == event.GUIEvent.Caller->getID()) {
active_key = ks;
break;
}
}
FATAL_ERROR_IF(!active_key, "Key setting not found");
shift_down = false;
active_key->button->setText(wstrgettext("press key").c_str());
break;
}
Environment->setFocus(this);
}
}
return Parent ? Parent->OnEvent(event) : false;
}
void GUIKeyChangeMenu::add_key(int id, std::wstring button_name, const std::string &setting_name)
{
key_setting *k = new key_setting;
k->id = id;
k->button_name = std::move(button_name);
k->setting_name = setting_name;
k->key = getKeySetting(k->setting_name.c_str());
key_settings.push_back(k);
}
// compare with button_titles in touchcontrols.cpp
void GUIKeyChangeMenu::init_keys()
{
this->add_key(GUI_ID_KEY_FORWARD_BUTTON, wstrgettext("Forward"), "keymap_forward");
this->add_key(GUI_ID_KEY_BACKWARD_BUTTON, wstrgettext("Backward"), "keymap_backward");
this->add_key(GUI_ID_KEY_LEFT_BUTTON, wstrgettext("Left"), "keymap_left");
this->add_key(GUI_ID_KEY_RIGHT_BUTTON, wstrgettext("Right"), "keymap_right");
this->add_key(GUI_ID_KEY_AUX1_BUTTON, wstrgettext("Aux1"), "keymap_aux1");
this->add_key(GUI_ID_KEY_JUMP_BUTTON, wstrgettext("Jump"), "keymap_jump");
this->add_key(GUI_ID_KEY_SNEAK_BUTTON, wstrgettext("Sneak"), "keymap_sneak");
this->add_key(GUI_ID_KEY_DROP_BUTTON, wstrgettext("Drop"), "keymap_drop");
this->add_key(GUI_ID_KEY_INVENTORY_BUTTON, wstrgettext("Inventory"), "keymap_inventory");
this->add_key(GUI_ID_KEY_HOTBAR_PREV_BUTTON, wstrgettext("Prev. item"), "keymap_hotbar_previous");
this->add_key(GUI_ID_KEY_HOTBAR_NEXT_BUTTON, wstrgettext("Next item"), "keymap_hotbar_next");
this->add_key(GUI_ID_KEY_ZOOM_BUTTON, wstrgettext("Zoom"), "keymap_zoom");
this->add_key(GUI_ID_KEY_CAMERA_BUTTON, wstrgettext("Change camera"), "keymap_camera_mode");
this->add_key(GUI_ID_KEY_MINIMAP_BUTTON, wstrgettext("Toggle minimap"), "keymap_minimap");
this->add_key(GUI_ID_KEY_FLY_BUTTON, wstrgettext("Toggle fly"), "keymap_freemove");
this->add_key(GUI_ID_KEY_PITCH_MOVE, wstrgettext("Toggle pitchmove"), "keymap_pitchmove");
this->add_key(GUI_ID_KEY_FAST_BUTTON, wstrgettext("Toggle fast"), "keymap_fastmove");
this->add_key(GUI_ID_KEY_NOCLIP_BUTTON, wstrgettext("Toggle noclip"), "keymap_noclip");
this->add_key(GUI_ID_KEY_MUTE_BUTTON, wstrgettext("Mute"), "keymap_mute");
this->add_key(GUI_ID_KEY_DEC_VOLUME_BUTTON, wstrgettext("Dec. volume"), "keymap_decrease_volume");
this->add_key(GUI_ID_KEY_INC_VOLUME_BUTTON, wstrgettext("Inc. volume"), "keymap_increase_volume");
this->add_key(GUI_ID_KEY_AUTOFWD_BUTTON, wstrgettext("Autoforward"), "keymap_autoforward");
this->add_key(GUI_ID_KEY_CHAT_BUTTON, wstrgettext("Chat"), "keymap_chat");
this->add_key(GUI_ID_KEY_SCREENSHOT_BUTTON, wstrgettext("Screenshot"), "keymap_screenshot");
this->add_key(GUI_ID_KEY_RANGE_BUTTON, wstrgettext("Range select"), "keymap_rangeselect");
this->add_key(GUI_ID_KEY_DEC_RANGE_BUTTON, wstrgettext("Dec. range"), "keymap_decrease_viewing_range_min");
this->add_key(GUI_ID_KEY_INC_RANGE_BUTTON, wstrgettext("Inc. range"), "keymap_increase_viewing_range_min");
this->add_key(GUI_ID_KEY_CONSOLE_BUTTON, wstrgettext("Console"), "keymap_console");
this->add_key(GUI_ID_KEY_CMD_BUTTON, wstrgettext("Command"), "keymap_cmd");
this->add_key(GUI_ID_KEY_CMD_LOCAL_BUTTON, wstrgettext("Local command"), "keymap_cmd_local");
this->add_key(GUI_ID_KEY_BLOCK_BOUNDS_BUTTON, wstrgettext("Block bounds"), "keymap_toggle_block_bounds");
this->add_key(GUI_ID_KEY_HUD_BUTTON, wstrgettext("Toggle HUD"), "keymap_toggle_hud");
this->add_key(GUI_ID_KEY_CHATLOG_BUTTON, wstrgettext("Toggle chat log"), "keymap_toggle_chat");
this->add_key(GUI_ID_KEY_FOG_BUTTON, wstrgettext("Toggle fog"), "keymap_toggle_fog");
}

View file

@ -1,63 +0,0 @@
// Luanti
// SPDX-License-Identifier: LGPL-2.1-or-later
// Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
// Copyright (C) 2013 Ciaran Gultnieks <ciaran@ciarang.com>
// Copyright (C) 2013 teddydestodes <derkomtur@schattengang.net>
#pragma once
#include "modalMenu.h"
#include "client/keycode.h"
#include <string>
#include <vector>
#include <IGUIEnvironment.h>
class ISimpleTextureSource;
struct key_setting
{
int id;
std::wstring button_name;
KeyPress key;
std::string setting_name;
gui::IGUIButton *button;
};
class GUIKeyChangeMenu : public GUIModalMenu
{
public:
GUIKeyChangeMenu(gui::IGUIEnvironment *env, gui::IGUIElement *parent, s32 id,
IMenuManager *menumgr, ISimpleTextureSource *tsrc);
~GUIKeyChangeMenu();
/*
Remove and re-add (or reposition) stuff
*/
void regenerateGui(v2u32 screensize);
void drawMenu();
bool acceptInput();
bool OnEvent(const SEvent &event);
bool pausesGame() { return true; }
protected:
std::wstring getLabelByID(s32 id) { return L""; }
std::string getNameByID(s32 id) { return ""; }
private:
void init_keys();
bool resetMenu();
void add_key(int id, std::wstring button_name, const std::string &setting_name);
bool shift_down = false;
key_setting *active_key = nullptr;
gui::IGUIStaticText *key_used_text = nullptr;
std::vector<key_setting *> key_settings;
ISimpleTextureSource *m_tsrc;
};

View file

@ -8,7 +8,6 @@
#include <IAnimatedMeshSceneNode.h>
#include <IVideoDriver.h>
#include <ISceneManager.h>
#include "IAttributes.h"
#include "porting.h"
#include "client/mesh.h"
@ -21,8 +20,6 @@ GUIScene::GUIScene(gui::IGUIEnvironment *env, scene::ISceneManager *smgr,
m_cam = m_smgr->addCameraSceneNode(0, v3f(0.f, 0.f, -100.f), v3f(0.f));
m_cam->setFOV(30.f * core::DEGTORAD);
m_smgr->getParameters()->setAttribute(scene::ALLOW_ZWRITE_ON_TRANSPARENT, true);
}
GUIScene::~GUIScene()

View file

@ -32,7 +32,7 @@ GUITable::GUITable(gui::IGUIEnvironment *env,
core::rect<s32> rectangle,
ISimpleTextureSource *tsrc
):
gui::IGUIElement(gui::EGUIET_ELEMENT, env, parent, id, rectangle),
gui::IGUIElement(gui::EGUIET_TABLE, env, parent, id, rectangle),
m_tsrc(tsrc)
{
assert(tsrc != NULL);
@ -635,11 +635,6 @@ void GUITable::setDynamicData(const DynamicData &dyndata)
m_scrollbar->setPos(dyndata.scrollpos);
}
const c8* GUITable::getTypeName() const
{
return "GUITable";
}
void GUITable::updateAbsolutePosition()
{
IGUIElement::updateAbsolutePosition();

View file

@ -118,9 +118,6 @@ public:
/* Set selection, scroll position and opened (sub)trees */
void setDynamicData(const DynamicData &dyndata);
/* Returns "GUITable" */
virtual const c8* getTypeName() const;
/* Must be called when position or size changes */
virtual void updateAbsolutePosition();

View file

@ -77,7 +77,7 @@ void GUIVolumeChange::regenerateGui(v2u32 screensize)
core::rect<s32> rect(0, 0, 100 * s, 30 * s);
rect = rect + v2s32(size.X / 2 - 100 * s / 2, size.Y / 2 + 55 * s);
GUIButton::addButton(Environment, rect, m_tsrc, this, ID_soundExitButton,
wstrgettext("Exit").c_str());
wstrgettext("Back").c_str());
}
{
core::rect<s32> rect(0, 0, 300 * s, 20 * s);

View file

@ -22,12 +22,10 @@ class IGameCallback
public:
virtual void exitToOS() = 0;
virtual void openSettings() = 0;
virtual void keyConfig() = 0;
virtual void disconnect() = 0;
virtual void changePassword() = 0;
virtual void changeVolume() = 0;
virtual void showOpenURLDialog(const std::string &url) = 0;
virtual void signalKeyConfigChange() = 0;
virtual void touchscreenLayout() = 0;
};
@ -61,6 +59,8 @@ public:
if(!m_stack.empty()) {
m_stack.back()->setVisible(true);
guienv->setFocus(m_stack.back());
} else {
guienv->removeFocus(menu);
}
}
@ -78,6 +78,13 @@ public:
return m_stack.size();
}
GUIModalMenu *tryGetTopMenu() const
{
if (m_stack.empty())
return nullptr;
return dynamic_cast<GUIModalMenu *>(m_stack.back());
}
void deleteFront()
{
m_stack.front()->setVisible(false);
@ -136,16 +143,6 @@ public:
changevolume_requested = true;
}
void keyConfig() override
{
keyconfig_requested = true;
}
void signalKeyConfigChange() override
{
keyconfig_changed = true;
}
void touchscreenLayout() override
{
touchscreenlayout_requested = true;
@ -160,10 +157,8 @@ public:
bool settings_requested = false;
bool changepassword_requested = false;
bool changevolume_requested = false;
bool keyconfig_requested = false;
bool touchscreenlayout_requested = false;
bool shutdown_requested = false;
bool keyconfig_changed = false;
std::string show_open_url_dialog = "";
};

View file

@ -275,8 +275,35 @@ HTTPFetchOngoing::HTTPFetchOngoing(const HTTPFetchRequest &request_,
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &result.data);
}
// Configure the method
switch (request.method) {
default:
assert(false);
case HTTP_GET:
curl_easy_setopt(curl, CURLOPT_HTTPGET, 1);
break;
case HTTP_HEAD:
// This is kinda pointless right now, since we don't return response headers (TODO?)
curl_easy_setopt(curl, CURLOPT_NOBODY, 1);
break;
case HTTP_POST:
curl_easy_setopt(curl, CURLOPT_POST, 1);
break;
case HTTP_PUT:
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT");
break;
case HTTP_PATCH:
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PATCH");
break;
case HTTP_DELETE:
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE");
break;
}
const bool has_request_body = request.method != HTTP_GET && request.method != HTTP_HEAD;
// Set data from fields or raw_data
if (request.multipart) {
assert(has_request_body);
multipart_mime = curl_mime_init(curl);
for (auto &it : request.fields) {
curl_mimepart *part = curl_mime_addpart(multipart_mime);
@ -284,46 +311,31 @@ HTTPFetchOngoing::HTTPFetchOngoing(const HTTPFetchRequest &request_,
curl_mime_data(part, it.second.c_str(), it.second.size());
}
curl_easy_setopt(curl, CURLOPT_MIMEPOST, multipart_mime);
} else {
switch (request.method) {
case HTTP_GET:
curl_easy_setopt(curl, CURLOPT_HTTPGET, 1);
break;
case HTTP_POST:
curl_easy_setopt(curl, CURLOPT_POST, 1);
break;
case HTTP_PUT:
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT");
break;
case HTTP_DELETE:
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE");
break;
}
if (request.method != HTTP_GET) {
if (!request.raw_data.empty()) {
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE,
request.raw_data.size());
curl_easy_setopt(curl, CURLOPT_POSTFIELDS,
request.raw_data.c_str());
} else if (!request.fields.empty()) {
std::string str;
for (auto &field : request.fields) {
if (!str.empty())
str += "&";
str += urlencode(field.first);
str += "=";
str += urlencode(field.second);
}
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE,
str.size());
curl_easy_setopt(curl, CURLOPT_COPYPOSTFIELDS,
str.c_str());
} else if (has_request_body) {
if (request.fields.empty()) {
// Note that we need to set this to an empty buffer (not NULL)
// even if no data is to be sent.
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE,
request.raw_data.size());
curl_easy_setopt(curl, CURLOPT_POSTFIELDS,
request.raw_data.c_str());
} else {
std::string str;
for (auto &field : request.fields) {
if (!str.empty())
str += "&";
str += urlencode(field.first);
str += "=";
str += urlencode(field.second);
}
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, str.size());
curl_easy_setopt(curl, CURLOPT_COPYPOSTFIELDS, str.c_str());
}
}
// Set additional HTTP headers
for (const std::string &extra_header : request.extra_headers) {
http_header = curl_slist_append(http_header, extra_header.c_str());
for (const auto &s : request.extra_headers) {
http_header = curl_slist_append(http_header, s.c_str());
}
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, http_header);

View file

@ -31,8 +31,10 @@ namespace {
enum HttpMethod : u8
{
HTTP_GET,
HTTP_HEAD,
HTTP_POST,
HTTP_PUT,
HTTP_PATCH,
HTTP_DELETE,
};
@ -55,23 +57,22 @@ struct HTTPFetchRequest
long connect_timeout;
// Indicates if this is multipart/form-data or
// application/x-www-form-urlencoded. POST-only.
// application/x-www-form-urlencoded. Not allowed for GET.
bool multipart = false;
// The Method to use default = GET
// Avaible methods GET, POST, PUT, DELETE
// Method to use
HttpMethod method = HTTP_GET;
// Fields of the request
StringMap fields;
// Raw data of the request overrides fields
// Raw data of the request (instead of fields, ignored if multipart)
std::string raw_data;
// If not empty, should contain entries such as "Accept: text/html"
std::vector<std::string> extra_headers;
// useragent to use
// User agent to send
std::string useragent;
HTTPFetchRequest();

View file

@ -736,9 +736,8 @@ static void uninit_common()
static void startup_message()
{
print_version(infostream);
infostream << "SER_FMT_VER_HIGHEST_READ=" <<
TOSTRING(SER_FMT_VER_HIGHEST_READ) <<
" LATEST_PROTOCOL_VERSION=" << LATEST_PROTOCOL_VERSION
infostream << "SER_FMT_VER_HIGHEST_READ=" << (int)SER_FMT_VER_HIGHEST_READ
<< " LATEST_PROTOCOL_VERSION=" << (int)LATEST_PROTOCOL_VERSION
<< std::endl;
}
@ -778,10 +777,13 @@ static bool read_config_file(const Settings &cmd_args)
}
// If no path found, use the first one (menu creates the file)
if (g_settings_path.empty())
if (g_settings_path.empty()) {
g_settings_path = filenames[0];
g_first_run = true;
}
}
infostream << "Global configuration file: " << g_settings_path << std::endl;
infostream << "Global configuration file: " << g_settings_path
<< (g_first_run ? " (first run)" : "") << std::endl;
return true;
}
@ -1280,6 +1282,7 @@ static bool recompress_map_database(const GameParams &game_params, const Setting
u64 last_update_time = 0;
bool &kill = *porting::signal_handler_killstatus();
const u8 serialize_as_ver = SER_FMT_VER_HIGHEST_WRITE;
const s16 map_compression_level = rangelim(g_settings->getS16("map_compression_level_disk"), -1, 9);
// This is ok because the server doesn't actually run
std::vector<v3s16> blocks;
@ -1307,7 +1310,7 @@ static bool recompress_map_database(const GameParams &game_params, const Setting
oss.str("");
oss.clear();
writeU8(oss, serialize_as_ver);
mb.serialize(oss, serialize_as_ver, true, -1);
mb.serialize(oss, serialize_as_ver, true, map_compression_level);
}
db->saveBlock(*it, oss.str());

View file

@ -47,6 +47,9 @@ struct MapEditEvent
MapNode n = CONTENT_AIR;
std::vector<v3s16> modified_blocks; // Represents a set
bool is_private_change = false;
// Setting low_priority to true allows the server
// to send this change to clients with some delay.
bool low_priority = false;
MapEditEvent() = default;

View file

@ -24,6 +24,55 @@
#include "util/serialize.h"
#include "util/basic_macros.h"
// Like a std::unordered_map<content_t, content_t>, but faster.
//
// Unassigned entries are marked with 0xFFFF.
//
// The static memory requires about 65535 * 2 bytes RAM in order to be
// sure we can handle all content ids.
class IdIdMapping
{
static_assert(sizeof(content_t) == 2, "content_t must be 16-bit");
private:
std::unique_ptr<content_t[]> m_mapping;
std::vector<content_t> m_dirty;
public:
IdIdMapping()
{
m_mapping = std::make_unique<content_t[]>(CONTENT_MAX + 1);
memset(m_mapping.get(), 0xFF, (CONTENT_MAX + 1) * sizeof(content_t));
}
DISABLE_CLASS_COPY(IdIdMapping)
content_t get(content_t k) const
{
return m_mapping[k];
}
void set(content_t k, content_t v)
{
m_mapping[k] = v;
m_dirty.push_back(k);
}
void clear()
{
for (auto k : m_dirty)
m_mapping[k] = 0xFFFF;
m_dirty.clear();
}
static IdIdMapping &giveClearedThreadLocalInstance()
{
static thread_local IdIdMapping tl_ididmapping;
tl_ididmapping.clear();
return tl_ididmapping;
}
};
static const char *modified_reason_strings[] = {
"reallocate or initial",
"setIsUnderground",
@ -212,16 +261,7 @@ void MapBlock::expireIsAirCache()
static void getBlockNodeIdMapping(NameIdMapping *nimap, MapNode *nodes,
const NodeDefManager *nodedef)
{
// The static memory requires about 65535 * 2 bytes RAM in order to be
// sure we can handle all content ids. But it's absolutely worth it as it's
// a speedup of 4 for one of the major time consuming functions on storing
// mapblocks.
thread_local std::unique_ptr<content_t[]> mapping;
static_assert(sizeof(content_t) == 2, "content_t must be 16-bit");
if (!mapping)
mapping = std::make_unique<content_t[]>(CONTENT_MAX + 1);
memset(mapping.get(), 0xFF, (CONTENT_MAX + 1) * sizeof(content_t));
IdIdMapping &mapping = IdIdMapping::giveClearedThreadLocalInstance();
content_t id_counter = 0;
for (u32 i = 0; i < MapBlock::nodecount; i++) {
@ -229,12 +269,12 @@ static void getBlockNodeIdMapping(NameIdMapping *nimap, MapNode *nodes,
content_t id = CONTENT_IGNORE;
// Try to find an existing mapping
if (mapping[global_id] != 0xFFFF) {
id = mapping[global_id];
if (auto found = mapping.get(global_id); found != 0xFFFF) {
id = found;
} else {
// We have to assign a new mapping
id = id_counter++;
mapping[global_id] = id;
mapping.set(global_id, id);
const auto &name = nodedef->get(global_id).name;
nimap->set(id, name);
@ -259,25 +299,20 @@ static void correctBlockNodeIds(const NameIdMapping *nimap, MapNode *nodes,
std::unordered_set<content_t> unnamed_contents;
std::unordered_set<std::string> unallocatable_contents;
bool previous_exists = false;
content_t previous_local_id = CONTENT_IGNORE;
content_t previous_global_id = CONTENT_IGNORE;
// Used to cache local to global id lookup.
IdIdMapping &mapping_cache = IdIdMapping::giveClearedThreadLocalInstance();
for (u32 i = 0; i < MapBlock::nodecount; i++) {
content_t local_id = nodes[i].getContent();
// If previous node local_id was found and same than before, don't lookup maps
// apply directly previous resolved id
// This permits to massively improve loading performance when nodes are similar
// example: default:air, default:stone are massively present
if (previous_exists && local_id == previous_local_id) {
nodes[i].setContent(previous_global_id);
if (auto found = mapping_cache.get(local_id); found != 0xFFFF) {
nodes[i].setContent(found);
continue;
}
std::string name;
if (!nimap->getName(local_id, name)) {
unnamed_contents.insert(local_id);
previous_exists = false;
continue;
}
@ -286,16 +321,13 @@ static void correctBlockNodeIds(const NameIdMapping *nimap, MapNode *nodes,
global_id = gamedef->allocateUnknownNodeId(name);
if (global_id == CONTENT_IGNORE) {
unallocatable_contents.insert(name);
previous_exists = false;
continue;
}
}
nodes[i].setContent(global_id);
// Save previous node local_id & global_id result
previous_local_id = local_id;
previous_global_id = global_id;
previous_exists = true;
mapping_cache.set(local_id, global_id);
}
for (const content_t c: unnamed_contents) {

View file

@ -163,7 +163,7 @@ void Decoration::placeDeco(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax)
deco_count = deco_count_f;
} else if (deco_count_f > 0.0f) {
// For very low density calculate a chance for 1 decoration
if (ps.range(1000) <= deco_count_f * 1000.0f)
if (ps.next() <= deco_count_f * static_cast<float>(PcgRandom::RANDOM_RANGE))
deco_count = 1;
}
}

View file

@ -72,4 +72,4 @@
const u16 LATEST_PROTOCOL_VERSION = 48;
// See also formspec [Version History] in doc/lua_api.md
const u16 FORMSPEC_API_VERSION = 8;
const u16 FORMSPEC_API_VERSION = 9;

View file

@ -4,6 +4,7 @@
#include "nodedef.h"
#include "SAnimatedMesh.h"
#include "itemdef.h"
#if CHECK_CLIENT_BUILD()
#include "client/mesh.h"
@ -13,6 +14,7 @@
#include "client/texturesource.h"
#include "client/tile.h"
#include <IMeshManipulator.h>
#include <SMesh.h>
#include <SkinnedMesh.h>
#endif
#include "log.h"
@ -272,7 +274,8 @@ void TextureSettings::readSettings()
connected_glass = g_settings->getBool("connected_glass");
translucent_liquids = g_settings->getBool("translucent_liquids");
enable_minimap = g_settings->getBool("enable_minimap");
node_texture_size = std::max<u16>(g_settings->getU16("texture_min_size"), 1);
node_texture_size = rangelim(g_settings->getU16("texture_min_size"),
TEXTURE_FILTER_MIN_SIZE, 16384);
std::string leaves_style_str = g_settings->get("leaves_style");
std::string world_aligned_mode_str = g_settings->get("world_aligned_mode");
std::string autoscale_mode_str = g_settings->get("autoscale_mode");
@ -760,10 +763,6 @@ static bool isWorldAligned(AlignStyle style, WorldAlignMode mode, NodeDrawType d
void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc,
scene::IMeshManipulator *meshmanip, Client *client, const TextureSettings &tsettings)
{
// minimap pixel color - the average color of a texture
if (tsettings.enable_minimap && !tiledef[0].name.empty())
minimap_color = tsrc->getTextureAverageColor(tiledef[0].name);
// Figure out the actual tiles to use
TileDef tdef[6];
for (u32 j = 0; j < 6; j++) {
@ -908,7 +907,12 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc
u32 overlay_shader = shdsrc->getShader("nodes_shader", overlay_material, drawtype);
// minimap pixel color = average color of top tile
if (tsettings.enable_minimap && !tdef[0].name.empty() && drawtype != NDT_AIRLIKE)
minimap_color = tsrc->getTextureAverageColor(tdef[0].name);
// Tiles (fill in f->tiles[])
bool any_polygon_offset = false;
for (u16 j = 0; j < 6; j++) {
tiles[j].world_aligned = isWorldAligned(tdef[j].align_style,
tsettings.world_aligned_mode, drawtype);
@ -921,6 +925,17 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc
tdef[j].backface_culling, tsettings);
tiles[j].layers[0].need_polygon_offset = !tiles[j].layers[1].empty();
any_polygon_offset |= tiles[j].layers[0].need_polygon_offset;
}
if (drawtype == NDT_MESH && any_polygon_offset) {
// Our per-tile polygon offset enablement workaround works fine for normal
// nodes and anything else, where we know that different tiles are different
// faces that couldn't possibly conflict with each other.
// We can't assume this for mesh nodes, so apply it to all tiles (= materials)
// then.
for (u16 j = 0; j < 6; j++)
tiles[j].layers[0].need_polygon_offset = true;
}
MaterialType special_material = material_type;
@ -946,23 +961,44 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc
palette = tsrc->getPalette(palette_name);
if (drawtype == NDT_MESH && !mesh.empty()) {
// Read the mesh and apply scale
mesh_ptr = client->getMesh(mesh);
if (mesh_ptr) {
v3f scale = v3f(BS) * visual_scale;
scaleMesh(mesh_ptr, scale);
// Note: By freshly reading, we get an unencumbered mesh.
if (scene::IMesh *src_mesh = client->getMesh(mesh)) {
bool apply_bs = false;
// For frame-animated meshes, always get the first frame,
// which holds a model for which we can eventually get the static pose.
while (auto *src_meshes = dynamic_cast<scene::SAnimatedMesh *>(src_mesh)) {
src_mesh = src_meshes->getMesh(0.0f);
src_mesh->grab();
src_meshes->drop();
}
if (auto *skinned_mesh = dynamic_cast<scene::SkinnedMesh *>(src_mesh)) {
// Compatibility: Animated meshes, as well as static gltf meshes, are not scaled by BS.
// See https://github.com/luanti-org/luanti/pull/16112#issuecomment-2881860329
bool is_gltf = skinned_mesh->getSourceFormat() ==
scene::SkinnedMesh::SourceFormat::GLTF;
apply_bs = skinned_mesh->isStatic() && !is_gltf;
// Nodes do not support mesh animation, so we clone the static pose.
// This simplifies working with the mesh: We can just scale the vertices
// as transformations have already been applied.
mesh_ptr = cloneStaticMesh(src_mesh);
src_mesh->drop();
} else {
auto *static_mesh = dynamic_cast<scene::SMesh *>(src_mesh);
assert(static_mesh);
mesh_ptr = static_mesh;
// Compatibility: Apply BS scaling to static meshes (.obj). See #15811.
apply_bs = true;
}
scaleMesh(mesh_ptr, v3f((apply_bs ? BS : 1.0f) * visual_scale));
recalculateBoundingBox(mesh_ptr);
if (!checkMeshNormals(mesh_ptr)) {
// TODO this should be done consistently when the mesh is loaded
infostream << "ContentFeatures: recalculating normals for mesh "
<< mesh << std::endl;
meshmanip->recalculateNormals(mesh_ptr, true, false);
} else {
// Animation is not supported, but we need to reset it to
// default state if it is animated.
// Note: recalculateNormals() also does this hence the else-block
if (mesh_ptr->getMeshType() == scene::EAMT_SKINNED)
((scene::SkinnedMesh*) mesh_ptr)->resetAnimation();
}
} else {
mesh_ptr = nullptr;
}
}
}
@ -1444,13 +1480,16 @@ void NodeDefManager::updateTextures(IGameDef *gamedef, void *progress_callback_a
TextureSettings tsettings;
tsettings.readSettings();
u32 size = m_content_features.size();
tsrc->setImageCaching(true);
u32 size = m_content_features.size();
for (u32 i = 0; i < size; i++) {
ContentFeatures *f = &(m_content_features[i]);
f->updateTextures(tsrc, shdsrc, meshmanip, client, tsettings);
client->showUpdateProgressTexture(progress_callback_args, i, size);
}
tsrc->setImageCaching(false);
#endif
}

View file

@ -342,7 +342,7 @@ struct ContentFeatures
enum NodeDrawType drawtype;
std::string mesh;
#if CHECK_CLIENT_BUILD()
scene::IMesh *mesh_ptr; // mesh in case of mesh node
scene::SMesh *mesh_ptr; // mesh in case of mesh node
video::SColor minimap_color;
#endif
float visual_scale; // Misc. scale parameter

View file

@ -187,6 +187,18 @@ void shareFileAndroid(const std::string &path)
jnienv->CallVoidMethod(activity, url_open, jurl);
}
void setPlayingNowNotification(bool show)
{
jmethodID play_notification = jnienv->GetMethodID(activityClass,
"setPlayingNowNotification", "(Z)V");
FATAL_ERROR_IF(play_notification == nullptr,
"porting::setPlayingNowNotification unable to find Java setPlayingNowNotification method");
jboolean jshow = show;
jnienv->CallVoidMethod(activity, play_notification, jshow);
}
AndroidDialogType getLastInputDialogType()
{
jmethodID lastdialogtype = jnienv->GetMethodID(activityClass,

View file

@ -36,6 +36,13 @@ void showComboBoxDialog(const std::string *optionList, s32 listSize, s32 selecte
*/
void shareFileAndroid(const std::string &path);
/**
* Shows/hides notification that the game is running
*
* @param show whether to show/hide the notification
*/
void setPlayingNowNotification(bool show);
/*
* Types of Android input dialog:
* 1. Text input (single/multi-line text and password field)

View file

@ -104,7 +104,7 @@ void read_item_definition(lua_State* L, int index,
} else if (lua_isstring(L, -1)) {
video::SColor color;
read_color(L, -1, &color);
def.wear_bar_params = WearBarParams({{0.0, color}},
def.wear_bar_params = WearBarParams({{0.0f, color}},
WearBarParams::BLEND_MODE_CONSTANT);
}

View file

@ -24,6 +24,11 @@ extern "C" {
#endif
#include "lua_api/l_base.h"
// if a job is waiting for this duration, an additional thread will be spawned
static constexpr int AUTOSCALE_DELAY_MS = 1000;
// if jobs are waiting for this duration, a warning is printed
static constexpr int STUCK_DELAY_MS = 11500;
/******************************************************************************/
AsyncEngine::~AsyncEngine()
{
@ -156,6 +161,7 @@ void AsyncEngine::step(lua_State *L)
{
stepJobResults(L);
stepAutoscale();
stepStuckWarning();
}
void AsyncEngine::stepJobResults(lua_State *L)
@ -203,11 +209,9 @@ void AsyncEngine::stepAutoscale()
if (autoscaleTimer && porting::getTimeMs() >= autoscaleTimer) {
autoscaleTimer = 0;
// Determine overlap with previous snapshot
unsigned int n = 0;
for (const auto &it : jobQueue)
n += autoscaleSeenJobs.count(it.id);
autoscaleSeenJobs.clear();
infostream << "AsyncEngine: " << n << " jobs were still waiting after 1s" << std::endl;
size_t n = compareJobs(autoscaleSeenJobs);
infostream << "AsyncEngine: " << n << " jobs were still waiting after "
<< AUTOSCALE_DELAY_MS << "ms, adding more threads." << std::endl;
// Start this many new threads
while (workerThreads.size() < autoscaleMaxWorkers && n > 0) {
addWorkerThread();
@ -216,13 +220,34 @@ void AsyncEngine::stepAutoscale()
return;
}
// 1) Check if there's anything in the queue
// 1) Check queue contents
if (!autoscaleTimer && !jobQueue.empty()) {
// Take a snapshot of all jobs we have seen
for (const auto &it : jobQueue)
autoscaleSeenJobs.emplace(it.id);
// and set a timer for 1 second
autoscaleTimer = porting::getTimeMs() + 1000;
autoscaleSeenJobs.clear();
snapshotJobs(autoscaleSeenJobs);
autoscaleTimer = porting::getTimeMs() + AUTOSCALE_DELAY_MS;
}
}
void AsyncEngine::stepStuckWarning()
{
MutexAutoLock autolock(jobQueueMutex);
// 2) If the timer elapsed, check again
if (stuckTimer && porting::getTimeMs() >= stuckTimer) {
stuckTimer = 0;
size_t n = compareJobs(stuckSeenJobs);
if (n > 0) {
warningstream << "AsyncEngine: " << n << " jobs seem to be stuck in queue"
" (" << workerThreads.size() << " workers active)" << std::endl;
}
// fallthrough
}
// 1) Check queue contents
if (!stuckTimer && !jobQueue.empty()) {
stuckSeenJobs.clear();
snapshotJobs(stuckSeenJobs);
stuckTimer = porting::getTimeMs() + STUCK_DELAY_MS;
}
}

View file

@ -138,6 +138,11 @@ protected:
*/
void stepAutoscale();
/**
* Print warning message if too many jobs are stuck
*/
void stepStuckWarning();
/**
* Initialize environment with current registred functions
* this function adds all functions registred by registerFunction to the
@ -149,6 +154,21 @@ protected:
bool prepareEnvironment(lua_State* L, int top);
private:
template <typename T>
inline void snapshotJobs(T &to)
{
for (const auto &it : jobQueue)
to.emplace(it.id);
}
template <typename T>
inline size_t compareJobs(const T &from)
{
size_t overlap = 0;
for (const auto &it : jobQueue)
overlap += from.count(it.id);
return overlap;
}
// Variable locking the engine against further modification
bool initDone = false;
@ -158,6 +178,9 @@ private:
u64 autoscaleTimer = 0;
std::unordered_set<u32> autoscaleSeenJobs;
u64 stuckTimer = 0;
std::unordered_set<u32> stuckSeenJobs;
// Only set for the server async environment (duh)
Server *server = nullptr;

View file

@ -5,6 +5,7 @@
#include "cpp_api/s_base.h"
#include "cpp_api/s_internal.h"
#include "cpp_api/s_security.h"
#include "debug.h"
#include "lua_api/l_object.h"
#include "common/c_converter.h"
#include "server/player_sao.h"
@ -467,12 +468,8 @@ void ScriptApiBase::objectrefGetOrCreate(lua_State *L, ServerActiveObject *cobj)
if (!cobj) {
ObjectRef::create(L, nullptr); // dummy reference
} else if (cobj->getId() == 0) {
// TODO after 5.10.0: convert this to a FATAL_ERROR
errorstream << "ScriptApiBase::objectrefGetOrCreate(): "
<< "Pushing orphan ObjectRef. Please open a bug report for this."
<< std::endl;
assert(0);
ObjectRef::create(L, cobj);
FATAL_ERROR("ScriptApiBase::objectrefGetOrCreate(): "
"Pushing orphan ObjectRef. Please open a bug report for this.");
} else {
push_objectRef(L, cobj->getId());
if (cobj->isGone())

View file

@ -34,7 +34,7 @@ void ScriptApiMainMenu::handleMainMenuEvent(const std::string &text)
lua_getfield(L, -1, "event_handler");
lua_remove(L, -2); // Remove core
if (lua_isnil(L, -1)) {
lua_pop(L, 1); // Pop event_handler
lua_pop(L, 2); // Pop event_handler, error handler
return;
}
luaL_checktype(L, -1, LUA_TFUNCTION);
@ -56,7 +56,7 @@ void ScriptApiMainMenu::handleMainMenuButtons(const StringMap &fields)
lua_getfield(L, -1, "button_handler");
lua_remove(L, -2); // Remove core
if (lua_isnil(L, -1)) {
lua_pop(L, 1); // Pop button handler
lua_pop(L, 2); // Pop button handler, error handler
return;
}
luaL_checktype(L, -1, LUA_TFUNCTION);
@ -76,3 +76,25 @@ void ScriptApiMainMenu::handleMainMenuButtons(const StringMap &fields)
PCALL_RES(lua_pcall(L, 1, 0, error_handler));
lua_pop(L, 1); // Pop error handler
}
void ScriptApiMainMenu::beforeClose()
{
SCRIPTAPI_PRECHECKHEADER
int error_handler = PUSH_ERROR_HANDLER(L);
// Get handler function
lua_getglobal(L, "core");
lua_getfield(L, -1, "on_before_close");
lua_remove(L, -2); // Remove core
if (lua_isnil(L, -1)) {
lua_pop(L, 2); // Pop callback, error handler
return;
}
luaL_checktype(L, -1, LUA_TFUNCTION);
// Call it
PCALL_RES(lua_pcall(L, 0, 0, error_handler));
lua_pop(L, 1); // Pop error handler
}

View file

@ -27,4 +27,9 @@ public:
* @param fields data in field format
*/
void handleMainMenuButtons(const StringMap &fields);
/**
* Called before the menu is closed, either to exit or to join a game
*/
void beforeClose();
};

View file

@ -330,7 +330,7 @@ void LuaAreaStore::Register(lua_State *L)
{"__gc", gc_object},
{0, 0}
};
registerClass(L, className, methods, metamethods);
registerClass<LuaAreaStore>(L, methods, metamethods);
// Can be created from Lua (AreaStore())
lua_register(L, className, create_object);

View file

@ -90,29 +90,6 @@ bool ModApiBase::registerFunction(lua_State *L, const char *name,
return true;
}
void ModApiBase::registerClass(lua_State *L, const char *name,
const luaL_Reg *methods,
const luaL_Reg *metamethods)
{
luaL_newmetatable(L, name);
luaL_register(L, NULL, metamethods);
int metatable = lua_gettop(L);
lua_newtable(L);
luaL_register(L, NULL, methods);
int methodtable = lua_gettop(L);
lua_pushvalue(L, methodtable);
lua_setfield(L, metatable, "__index");
// Protect the real metatable.
lua_pushvalue(L, methodtable);
lua_setfield(L, metatable, "__metatable");
// Pop methodtable and metatable.
lua_pop(L, 2);
}
int ModApiBase::l_deprecated_function(lua_State *L, const char *good, const char *bad, lua_CFunction func)
{
thread_local std::vector<u64> deprecated_logged;
@ -123,14 +100,18 @@ int ModApiBase::l_deprecated_function(lua_State *L, const char *good, const char
u64 start_time = porting::getTimeUs();
lua_Debug ar;
std::string backtrace;
// Get caller name with line and script backtrace
FATAL_ERROR_IF(!lua_getstack(L, 1, &ar), "lua_getstack() failed");
FATAL_ERROR_IF(!lua_getinfo(L, "Sl", &ar), "lua_getinfo() failed");
if (lua_getstack(L, 1, &ar) && lua_getinfo(L, "Sl", &ar)) {
// Get backtrace and hash it to reduce the warning flood
backtrace = ar.short_src;
backtrace.append(":").append(std::to_string(ar.currentline));
} else {
backtrace = "<tail call optimized coroutine> ";
backtrace.append(script_get_backtrace(L));
}
// Get backtrace and hash it to reduce the warning flood
std::string backtrace = ar.short_src;
backtrace.append(":").append(std::to_string(ar.currentline));
u64 hash = murmur_hash_64_ua(backtrace.data(), backtrace.length(), 0xBADBABE);
if (std::find(deprecated_logged.begin(), deprecated_logged.end(), hash)

View file

@ -60,9 +60,37 @@ public:
lua_CFunction func,
int top);
static void registerClass(lua_State *L, const char *name,
template<typename T>
static void registerClass(lua_State *L,
const luaL_Reg *methods,
const luaL_Reg *metamethods);
const luaL_Reg *metamethods)
{
luaL_newmetatable(L, T::className);
luaL_register(L, NULL, metamethods);
int metatable = lua_gettop(L);
lua_newtable(L);
luaL_register(L, NULL, methods);
int methodtable = lua_gettop(L);
lua_pushvalue(L, methodtable);
lua_setfield(L, metatable, "__index");
lua_getfield(L, metatable, "__tostring");
bool default_tostring = lua_isnil(L, -1);
lua_pop(L, 1);
if (default_tostring) {
lua_pushcfunction(L, ModApiBase::defaultToString<T>);
lua_setfield(L, metatable, "__tostring");
}
// Protect the real metatable.
lua_pushvalue(L, methodtable);
lua_setfield(L, metatable, "__metatable");
// Pop methodtable and metatable.
lua_pop(L, 2);
}
template<typename T>
static inline T *checkObject(lua_State *L, int narg)
@ -84,4 +112,14 @@ public:
* @return value from `func`
*/
static int l_deprecated_function(lua_State *L, const char *good, const char *bad, lua_CFunction func);
private:
template<typename T>
static int defaultToString(lua_State *L)
{
auto *t = checkObject<T>(L, 1);
lua_pushfstring(L, "%s: %p", T::className, t);
return 1;
}
};

View file

@ -175,7 +175,7 @@ void LuaCamera::Register(lua_State *L)
{"__gc", gc_object},
{0, 0}
};
registerClass(L, className, methods, metamethods);
registerClass<LuaCamera>(L, methods, metamethods);
}
const char LuaCamera::className[] = "Camera";

View file

@ -271,66 +271,52 @@ int ModApiCraft::l_clear_craft(lua_State *L)
luaL_checktype(L, 1, LUA_TTABLE);
int table = 1;
// Get the writable craft definition manager from the server
IWritableCraftDefManager *craftdef =
getServer(L)->getWritableCraftDefManager();
std::string output = getstringfield_default(L, table, "output", "");
std::string type = getstringfield_default(L, table, "type", "shaped");
CraftOutput c_output(output, 0);
if (!output.empty()) {
CraftOutput c_output(output, 0);
if (craftdef->clearCraftsByOutput(c_output, getServer(L))) {
lua_pushboolean(L, true);
return 1;
}
warningstream << "No craft recipe known for output" << std::endl;
warningstream << "No craft recipe known for output '" << output
<< "'" << std::endl;
lua_pushboolean(L, false);
return 1;
}
std::vector<std::string> recipe;
int width = 0;
CraftMethod method = CRAFT_METHOD_NORMAL;
/*
CraftDefinitionShaped
*/
if (type == "shaped") {
lua_getfield(L, table, "recipe");
if (lua_isnil(L, -1))
throw LuaError("Either output or recipe has to be defined");
if (!readCraftRecipeShaped(L, -1, width, recipe))
throw LuaError("Invalid crafting recipe");
}
/*
CraftDefinitionShapeless
*/
else if (type == "shapeless") {
} else if (type == "shapeless") {
lua_getfield(L, table, "recipe");
if (lua_isnil(L, -1))
throw LuaError("Either output or recipe has to be defined");
if (!readCraftRecipeShapeless(L, -1, recipe))
throw LuaError("Invalid crafting recipe");
}
/*
CraftDefinitionCooking
*/
else if (type == "cooking") {
} else if (type == "cooking") {
method = CRAFT_METHOD_COOKING;
std::string rec = getstringfield_default(L, table, "recipe", "");
if (rec.empty())
throw LuaError("Crafting definition (cooking)"
" is missing a recipe");
throw LuaError("Crafting definition (cooking) is missing a recipe");
recipe.push_back(rec);
}
/*
CraftDefinitionFuel
*/
else if (type == "fuel") {
} else if (type == "fuel") {
method = CRAFT_METHOD_FUEL;
std::string rec = getstringfield_default(L, table, "recipe", "");
if (rec.empty())
throw LuaError("Crafting definition (fuel)"
" is missing a recipe");
throw LuaError("Crafting definition (fuel) is missing a recipe");
recipe.push_back(rec);
} else {
throw LuaError("Unknown crafting definition type: \"" + type + "\"");
@ -338,12 +324,20 @@ int ModApiCraft::l_clear_craft(lua_State *L)
std::vector<ItemStack> items;
items.reserve(recipe.size());
for (const auto &item : recipe)
for (const auto &item : recipe) {
items.emplace_back(item, 1, 0, getServer(L)->idef());
}
CraftInput input(method, width, items);
if (!craftdef->clearCraftsByInput(input, getServer(L))) {
warningstream << "No craft recipe matches input" << std::endl;
warningstream << "No craft recipe matches input (type: " << type
<< ", items: [";
for (size_t i = 0; i < items.size(); ++i) {
warningstream << "'" << items[i].name << "'";
if (i != items.size() - 1)
warningstream << ", ";
}
warningstream << "])" << std::endl;
lua_pushboolean(L, false);
return 1;
}

View file

@ -124,7 +124,7 @@ void LuaRaycast::Register(lua_State *L)
{"__gc", gc_object},
{0, 0}
};
registerClass(L, className, methods, metamethods);
registerClass<LuaRaycast>(L, methods, metamethods);
lua_register(L, className, create_object);
}

View file

@ -7,6 +7,7 @@
#include "common/c_content.h"
#include "lua_api/l_http.h"
#include "cpp_api/s_security.h"
#include "util/enum_string.h"
#include "httpfetch.h"
#include "settings.h"
#include "debug.h"
@ -19,6 +20,16 @@
lua_pushcfunction(L, l_http_##name); \
lua_settable(L, -3);
const static EnumString es_HttpMethod[] = {
{HTTP_GET, "GET"},
{HTTP_HEAD, "HEAD"},
{HTTP_POST, "POST"},
{HTTP_PUT, "PUT"},
{HTTP_PATCH, "PATCH"},
{HTTP_DELETE, "DELETE"},
{0, nullptr}
};
#if USE_CURL
void ModApiHttp::read_http_fetch_request(lua_State *L, HTTPFetchRequest &req)
{
@ -32,17 +43,8 @@ void ModApiHttp::read_http_fetch_request(lua_State *L, HTTPFetchRequest &req)
req.timeout *= 1000;
lua_getfield(L, 1, "method");
if (lua_isstring(L, -1)) {
std::string mth = getstringfield_default(L, 1, "method", "");
if (mth == "GET")
req.method = HTTP_GET;
else if (mth == "POST")
req.method = HTTP_POST;
else if (mth == "PUT")
req.method = HTTP_PUT;
else if (mth == "DELETE")
req.method = HTTP_DELETE;
}
if (lua_isstring(L, -1))
string_to_enum(es_HttpMethod, req.method, lua_tostring(L, -1));
lua_pop(L, 1);
// post_data: if table, post form data, otherwise raw data DEPRECATED use data and method instead
@ -50,8 +52,7 @@ void ModApiHttp::read_http_fetch_request(lua_State *L, HTTPFetchRequest &req)
if (lua_isnil(L, 2)) {
lua_pop(L, 1);
lua_getfield(L, 1, "data");
}
else {
} else {
req.method = HTTP_POST;
}

View file

@ -410,7 +410,7 @@ void InvRef::Register(lua_State *L)
{"__gc", gc_object},
{0, 0}
};
registerClass(L, className, methods, metamethods);
registerClass<InvRef>(L, methods, metamethods);
// Cannot be created from Lua
//lua_register(L, className, create_object);

View file

@ -524,7 +524,7 @@ void LuaItemStack::Register(lua_State *L)
{"__eq", l_equals},
{0, 0}
};
registerClass(L, className, methods, metamethods);
registerClass<LuaItemStack>(L, methods, metamethods);
// Can be created from Lua (ItemStack(itemstack or itemstring or table or nil))
lua_register(L, className, create_object);

View file

@ -81,7 +81,7 @@ void ItemStackMetaRef::create(lua_State *L, LuaItemStack *istack)
void ItemStackMetaRef::Register(lua_State *L)
{
registerMetadataClass(L, className, methods);
registerMetadataClass<ItemStackMetaRef>(L, methods);
}
const char ItemStackMetaRef::className[] = "ItemStackMetaRef";

View file

@ -464,7 +464,7 @@ void LuaLocalPlayer::Register(lua_State *L)
{"__gc", gc_object},
{0, 0}
};
registerClass(L, className, methods, metamethods);
registerClass<LuaLocalPlayer>(L, methods, metamethods);
}
const char LuaLocalPlayer::className[] = "LocalPlayer";

View file

@ -9,7 +9,6 @@
#include "scripting_mainmenu.h"
#include "gui/guiEngine.h"
#include "gui/guiMainMenu.h"
#include "gui/guiKeyChangeMenu.h"
#include "gui/guiPathSelectMenu.h"
#include "gui/touchscreeneditor.h"
#include "version.h"
@ -538,22 +537,6 @@ int ModApiMainMenu::l_get_content_translation(lua_State *L)
return 1;
}
/******************************************************************************/
int ModApiMainMenu::l_show_keys_menu(lua_State *L)
{
GUIEngine *engine = getGuiEngine(L);
sanity_check(engine != NULL);
GUIKeyChangeMenu *kmenu = new GUIKeyChangeMenu(
engine->m_rendering_engine->get_gui_env(),
engine->m_parent,
-1,
engine->m_menumanager,
engine->m_texture_source.get());
kmenu->drop();
return 0;
}
/******************************************************************************/
int ModApiMainMenu::l_show_touchscreen_layout(lua_State *L)
{
@ -945,8 +928,9 @@ int ModApiMainMenu::l_get_active_renderer(lua_State *L)
/******************************************************************************/
int ModApiMainMenu::l_get_active_irrlicht_device(lua_State *L)
{
const char *device_name = [] {
switch (RenderingEngine::get_raw_device()->getType()) {
auto device = RenderingEngine::get_raw_device();
std::string device_name = [device] {
switch (device->getType()) {
case EIDT_WIN32: return "WIN32";
case EIDT_X11: return "X11";
case EIDT_OSX: return "OSX";
@ -955,7 +939,9 @@ int ModApiMainMenu::l_get_active_irrlicht_device(lua_State *L)
default: return "Unknown";
}
}();
lua_pushstring(L, device_name);
if (auto version = device->getVersionString(); !version.empty())
device_name.append(" " + version);
lua_pushstring(L, device_name.c_str());
return 1;
}
@ -1067,7 +1053,6 @@ void ModApiMainMenu::Initialize(lua_State *L, int top)
API_FCT(get_content_translation);
API_FCT(start);
API_FCT(close);
API_FCT(show_keys_menu);
API_FCT(show_touchscreen_layout);
API_FCT(create_world);
API_FCT(delete_world);
@ -1104,6 +1089,9 @@ void ModApiMainMenu::Initialize(lua_State *L, int top)
API_FCT(open_dir);
API_FCT(share_file);
API_FCT(do_async_callback);
lua_pushboolean(L, g_first_run);
lua_setfield(L, top, "is_first_run");
}
/******************************************************************************/

View file

@ -65,8 +65,6 @@ private:
//gui
static int l_show_keys_menu(lua_State *L);
static int l_show_touchscreen_layout(lua_State *L);
static int l_show_path_select_dialog(lua_State *L);

View file

@ -35,11 +35,20 @@ int ModApiMenuCommon::l_irrlicht_device_supports_touch(lua_State *L)
}
int ModApiMenuCommon::l_normalize_keycode(lua_State *L)
{
auto keystr = luaL_checkstring(L, 1);
lua_pushstring(L, KeyPress(keystr).sym().c_str());
return 1;
}
void ModApiMenuCommon::Initialize(lua_State *L, int top)
{
API_FCT(gettext);
API_FCT(get_active_driver);
API_FCT(irrlicht_device_supports_touch);
API_FCT(normalize_keycode);
}

View file

@ -13,6 +13,7 @@ private:
static int l_gettext(lua_State *L);
static int l_get_active_driver(lua_State *L);
static int l_irrlicht_device_supports_touch(lua_State *L);
static int l_normalize_keycode(lua_State *L);
public:
static void Initialize(lua_State *L, int top);

View file

@ -10,6 +10,7 @@
#include "map.h"
#include "server.h"
#include "util/basic_macros.h"
#include "util/string.h"
MetaDataRef *MetaDataRef::checkAnyMetadata(lua_State *L, int narg)
{
@ -166,9 +167,9 @@ int MetaDataRef::l_get_float(lua_State *L)
std::string str_;
const std::string &str = meta->getString(name, &str_);
// Convert with Lua, as is done in set_float.
lua_pushlstring(L, str.data(), str.size());
lua_pushnumber(L, lua_tonumber(L, -1));
// TODO this silently produces 0.0 if conversion fails, which is a footgun
f64 number = my_string_to_double(str).value_or(0.0);
lua_pushnumber(L, number);
return 1;
}
@ -179,12 +180,11 @@ int MetaDataRef::l_set_float(lua_State *L)
MetaDataRef *ref = checkAnyMetadata(L, 1);
std::string name = luaL_checkstring(L, 2);
luaL_checknumber(L, 3);
// Convert number to string with Lua as it gives good precision.
std::string str = readParam<std::string>(L, 3);
f64 number = luaL_checknumber(L, 3);
IMetadata *meta = ref->getmeta(true);
if (meta != NULL && meta->setString(name, str))
// Note: Do not use Lua's tostring for the conversion - it rounds.
if (meta != NULL && meta->setString(name, my_double_to_string(number)))
ref->reportMetadataChange(&name);
return 0;
}
@ -289,8 +289,11 @@ bool MetaDataRef::handleFromTable(lua_State *L, int table, IMetadata *meta)
while (lua_next(L, fieldstable) != 0) {
// key at index -2 and value at index -1
std::string name = readParam<std::string>(L, -2);
auto value = readParam<std::string_view>(L, -1);
meta->setString(name, value);
if (lua_type(L, -1) == LUA_TNUMBER) {
log_deprecated(L, "Passing `fields` with number values "
"is deprecated and may result in loss of precision.");
}
meta->setString(name, readParam<std::string_view>(L, -1));
lua_pop(L, 1); // Remove value, keep key for next iteration
}
lua_pop(L, 1);
@ -312,20 +315,3 @@ int MetaDataRef::l_equals(lua_State *L)
lua_pushboolean(L, *data1 == *data2);
return 1;
}
void MetaDataRef::registerMetadataClass(lua_State *L, const char *name,
const luaL_Reg *methods)
{
const luaL_Reg metamethods[] = {
{"__eq", l_equals},
{"__gc", gc_object},
{0, 0}
};
registerClass(L, name, methods, metamethods);
// Set metadata_class in the metatable for MetaDataRef::checkAnyMetadata.
luaL_getmetatable(L, name);
lua_pushstring(L, name);
lua_setfield(L, -2, "metadata_class");
lua_pop(L, 1);
}

View file

@ -29,7 +29,22 @@ protected:
virtual void handleToTable(lua_State *L, IMetadata *meta);
virtual bool handleFromTable(lua_State *L, int table, IMetadata *meta);
static void registerMetadataClass(lua_State *L, const char *name, const luaL_Reg *methods);
template<class T>
static void registerMetadataClass(lua_State *L, const luaL_Reg *methods)
{
const luaL_Reg metamethods[] = {
{"__eq", l_equals},
{"__gc", gc_object},
{0, 0}
};
registerClass<T>(L, methods, metamethods);
// Set metadata_class in the metatable for MetaDataRef::checkAnyMetadata.
luaL_getmetatable(L, T::className);
lua_pushstring(L, T::className);
lua_setfield(L, -2, "metadata_class");
lua_pop(L, 1);
}
// Exported functions

View file

@ -160,7 +160,7 @@ void LuaMinimap::Register(lua_State *L)
{"__gc", gc_object},
{0, 0}
};
registerClass(L, className, methods, metamethods);
registerClass<LuaMinimap>(L, methods, metamethods);
}
const char LuaMinimap::className[] = "Minimap";

View file

@ -77,7 +77,7 @@ void ModChannelRef::Register(lua_State *L)
{"__gc", gc_object},
{0, 0}
};
registerClass(L, className, methods, metamethods);
registerClass<ModChannelRef>(L, methods, metamethods);
}
void ModChannelRef::create(lua_State *L, const std::string &channel)

View file

@ -186,7 +186,7 @@ const char NodeMetaRef::className[] = "NodeMetaRef";
void NodeMetaRef::Register(lua_State *L)
{
registerMetadataClass(L, className, methodsServer);
registerMetadataClass<NodeMetaRef>(L, methodsServer);
}
@ -211,7 +211,7 @@ const luaL_Reg NodeMetaRef::methodsServer[] = {
void NodeMetaRef::RegisterClient(lua_State *L)
{
registerMetadataClass(L, className, methodsClient);
registerMetadataClass<NodeMetaRef>(L, methodsClient);
}

View file

@ -84,7 +84,7 @@ void NodeTimerRef::Register(lua_State *L)
{"__gc", gc_object},
{0, 0}
};
registerClass(L, className, methods, metamethods);
registerClass<NodeTimerRef>(L, methods, metamethods);
// Cannot be created from Lua
//lua_register(L, className, create_object);

View file

@ -101,7 +101,7 @@ void LuaValueNoise::Register(lua_State *L)
{"__gc", gc_object},
{0, 0}
};
registerClass(L, className, methods, metamethods);
registerClass<LuaValueNoise>(L, methods, metamethods);
lua_register(L, className, create_object);
@ -360,7 +360,7 @@ void LuaValueNoiseMap::Register(lua_State *L)
{"__gc", gc_object},
{0, 0}
};
registerClass(L, className, methods, metamethods);
registerClass<LuaValueNoiseMap>(L, methods, metamethods);
lua_register(L, className, create_object);
@ -449,7 +449,7 @@ void LuaPseudoRandom::Register(lua_State *L)
{"__gc", gc_object},
{0, 0}
};
registerClass(L, className, methods, metamethods);
registerClass<LuaPseudoRandom>(L, methods, metamethods);
lua_register(L, className, create_object);
}
@ -562,7 +562,7 @@ void LuaPcgRandom::Register(lua_State *L)
{"__gc", gc_object},
{0, 0}
};
registerClass(L, className, methods, metamethods);
registerClass<LuaPcgRandom>(L, methods, metamethods);
lua_register(L, className, create_object);
}
@ -652,7 +652,7 @@ void LuaSecureRandom::Register(lua_State *L)
{"__gc", gc_object},
{0, 0}
};
registerClass(L, className, methods, metamethods);
registerClass<LuaSecureRandom>(L, methods, metamethods);
lua_register(L, className, create_object);
}

View file

@ -69,8 +69,27 @@ RemotePlayer *ObjectRef::getplayer(ObjectRef *ref)
// Exported functions
int ObjectRef::mt_tostring(lua_State *L)
{
auto *ref = checkObject<ObjectRef>(L, 1);
if (getobject(ref)) {
if (auto *player = getplayer(ref)) {
lua_pushfstring(L, "ObjectRef (player): %s", player->getName().c_str());
} else if (auto *entitysao = getluaobject(ref)) {
lua_pushfstring(L, "ObjectRef (entity): %s (id: %d)",
entitysao->getName().c_str(), entitysao->getId());
} else {
lua_pushfstring(L, "ObjectRef (?): %p", ref);
}
} else {
lua_pushfstring(L, "ObjectRef (invalid): %p", ref);
}
return 1;
}
// garbage collector
int ObjectRef::gc_object(lua_State *L) {
int ObjectRef::gc_object(lua_State *L)
{
ObjectRef *obj = *(ObjectRef **)(lua_touserdata(L, 1));
delete obj;
return 0;
@ -2870,9 +2889,10 @@ void ObjectRef::Register(lua_State *L)
{
static const luaL_Reg metamethods[] = {
{"__gc", gc_object},
{"__tostring", mt_tostring},
{0, 0}
};
registerClass(L, className, methods, metamethods);
registerClass<ObjectRef>(L, methods, metamethods);
}
const char ObjectRef::className[] = "ObjectRef";

View file

@ -6,6 +6,7 @@
#include "lua_api/l_base.h"
#include "irrlichttypes.h"
#include <lua.h>
class ServerActiveObject;
class LuaEntitySAO;
@ -48,6 +49,9 @@ private:
// Exported functions
// __tostring metamethod
static int mt_tostring(lua_State *L);
// garbage collector
static int gc_object(lua_State *L);

View file

@ -3,18 +3,12 @@
// Copyright (C) 2025 grorp
#include "l_pause_menu.h"
#include "client/keycode.h"
#include "gui/mainmenumanager.h"
#include "lua_api/l_internal.h"
#include "client/client.h"
int ModApiPauseMenu::l_show_keys_menu(lua_State *L)
{
g_gamecallback->keyConfig();
return 0;
}
int ModApiPauseMenu::l_show_touchscreen_layout(lua_State *L)
{
g_gamecallback->touchscreenLayout();
@ -31,7 +25,6 @@ int ModApiPauseMenu::l_is_internal_server(lua_State *L)
void ModApiPauseMenu::Initialize(lua_State *L, int top)
{
API_FCT(show_keys_menu);
API_FCT(show_touchscreen_layout);
API_FCT(is_internal_server);
}

View file

@ -9,7 +9,6 @@
class ModApiPauseMenu: public ModApiBase
{
private:
static int l_show_keys_menu(lua_State *L);
static int l_show_touchscreen_layout(lua_State *L);
static int l_is_internal_server(lua_State *L);

View file

@ -44,7 +44,7 @@ void PlayerMetaRef::create(lua_State *L, ServerEnvironment *env, std::string_vie
void PlayerMetaRef::Register(lua_State *L)
{
registerMetadataClass(L, className, methods);
registerMetadataClass<PlayerMetaRef>(L, methods);
}
const char PlayerMetaRef::className[] = "PlayerMetaRef";

View file

@ -362,7 +362,7 @@ void LuaSettings::Register(lua_State* L)
{"__gc", gc_object},
{0, 0}
};
registerClass(L, className, methods, metamethods);
registerClass<LuaSettings>(L, methods, metamethods);
// Can be created from Lua (Settings(filename))
lua_register(L, className, create_object);

View file

@ -36,7 +36,7 @@ void StorageRef::create(lua_State *L, const std::string &mod_name, ModStorageDat
void StorageRef::Register(lua_State *L)
{
registerMetadataClass(L, className, methods);
registerMetadataClass<StorageRef>(L, methods);
}
IMetadata* StorageRef::getmeta(bool auto_create)

View file

@ -425,7 +425,7 @@ void LuaVoxelManip::Register(lua_State *L)
{"__gc", gc_object},
{0, 0}
};
registerClass(L, className, methods, metamethods);
registerClass<LuaVoxelManip>(L, methods, metamethods);
// Can be created from Lua (VoxelManip())
lua_register(L, className, create_object);

View file

@ -114,20 +114,6 @@ bool MainMenuScripting::checkPathAccess(const std::string &abs_path, bool write_
return !write_required;
}
void MainMenuScripting::beforeClose()
{
SCRIPTAPI_PRECHECKHEADER
int error_handler = PUSH_ERROR_HANDLER(L);
lua_getglobal(L, "core");
lua_getfield(L, -1, "on_before_close");
PCALL_RES(lua_pcall(L, 0, 0, error_handler));
lua_pop(L, 2); // Pop core, error handler
}
void MainMenuScripting::step()
{
asyncEngine.step(getStack());

View file

@ -24,9 +24,6 @@ public:
// Global step handler to pass back async events
void step();
// Calls core.on_before_close()
void beforeClose();
// Pass async events from engine to async threads
u32 queueAsync(std::string &&serialized_func,
std::string &&serialized_param);

View file

@ -284,12 +284,12 @@ Server::Server(
throw ServerError("Supplied invalid gamespec");
#if USE_PROMETHEUS
if (!simple_singleplayer_mode)
m_metrics_backend = std::unique_ptr<MetricsBackend>(createPrometheusMetricsBackend());
else
#else
if (true)
if (!simple_singleplayer_mode) {
// Note: may return null
m_metrics_backend.reset(createPrometheusMetricsBackend());
}
#endif
if (!m_metrics_backend)
m_metrics_backend = std::make_unique<MetricsBackend>();
m_uptime_counter = m_metrics_backend->addCounter("minetest_core_server_uptime", "Server uptime (in seconds)");
@ -755,6 +755,7 @@ void Server::AsyncRunStep(float dtime, bool initial_step)
if (!modified_blocks.empty()) {
MapEditEvent event;
event.type = MEET_OTHER;
event.low_priority = true;
event.setModifiedBlocks(modified_blocks);
m_env->getMap().dispatchEvent(event);
}
@ -1006,7 +1007,7 @@ void Server::AsyncRunStep(float dtime, bool initial_step)
}
case MEET_OTHER:
prof.add("MEET_OTHER", 1);
m_clients.markBlocksNotSent(event->modified_blocks);
m_clients.markBlocksNotSent(event->modified_blocks, event->low_priority);
break;
default:
prof.add("unknown", 1);
@ -1022,7 +1023,7 @@ void Server::AsyncRunStep(float dtime, bool initial_step)
*/
for (const u16 far_player : far_players) {
if (RemoteClient *client = getClient(far_player))
client->SetBlocksNotSent(event->modified_blocks);
client->SetBlocksNotSent(event->modified_blocks, event->low_priority);
}
delete event;

View file

@ -8,6 +8,7 @@
#include <vector>
#include <map>
#include <unordered_set>
#include <unordered_map>
#include "irr_v3d.h"
#include "mapnode.h"

View file

@ -195,13 +195,6 @@ void RemoteClient::GetNextBlocks (
m_last_camera_dir = camera_dir;
m_map_send_completion_timer = 0.0f;
}
if (m_nearest_unsent_d > 0) {
// make sure any blocks modified since the last time we sent blocks are resent
for (const v3s16 &p : m_blocks_modified) {
m_nearest_unsent_d = std::min(m_nearest_unsent_d, center.getDistanceFrom(p));
}
}
m_blocks_modified.clear();
s16 d_start = m_nearest_unsent_d;
@ -241,7 +234,6 @@ void RemoteClient::GetNextBlocks (
camera_fov = camera_fov / (1 + dot / 300.0f);
s32 nearest_emerged_d = -1;
s32 nearest_emergefull_d = -1;
s32 nearest_sent_d = -1;
//bool queue_is_full = false;
@ -364,17 +356,12 @@ void RemoteClient::GetNextBlocks (
Add inexistent block to emerge queue.
*/
if (want_emerge) {
if (emerge->enqueueBlockEmerge(peer_id, p, generate)) {
if (nearest_emerged_d == -1)
nearest_emerged_d = d;
} else {
if (nearest_emergefull_d == -1)
nearest_emergefull_d = d;
if (nearest_emerged_d == -1)
nearest_emerged_d = d;
if (emerge->enqueueBlockEmerge(peer_id, p, generate))
continue;
else
goto queue_full_break;
}
// get next one.
continue;
}
if (nearest_sent_d == -1)
@ -383,9 +370,7 @@ void RemoteClient::GetNextBlocks (
/*
Add block to send queue
*/
PrioritySortedBlockTransfer q((float)dist, p, peer_id);
dest.push_back(q);
dest.emplace_back((float)dist, p, peer_id);
num_blocks_selected += 1;
}
@ -396,8 +381,6 @@ queue_full_break:
// emerging, continue next time browsing from here
if (nearest_emerged_d != -1) {
new_nearest_unsent_d = nearest_emerged_d;
} else if (nearest_emergefull_d != -1) {
new_nearest_unsent_d = nearest_emergefull_d;
} else {
if (d > full_d_max) {
new_nearest_unsent_d = 0;
@ -423,8 +406,7 @@ queue_full_break:
void RemoteClient::GotBlock(v3s16 p)
{
if (m_blocks_sending.find(p) != m_blocks_sending.end()) {
m_blocks_sending.erase(p);
if (m_blocks_sending.erase(p) > 0) {
// only add to sent blocks if it actually was sending
// (it might have been modified since)
m_blocks_sent.insert(p);
@ -435,32 +417,39 @@ void RemoteClient::GotBlock(v3s16 p)
void RemoteClient::SentBlock(v3s16 p)
{
if (m_blocks_sending.find(p) == m_blocks_sending.end())
m_blocks_sending[p] = 0.0f;
else
if (!m_blocks_sending.insert(p).second)
infostream<<"RemoteClient::SentBlock(): Sent block"
" already in m_blocks_sending"<<std::endl;
}
void RemoteClient::SetBlockNotSent(v3s16 p)
void RemoteClient::SetBlockNotSent(v3s16 p, bool low_priority)
{
m_nothing_to_send_pause_timer = 0;
// remove the block from sending and sent sets,
// and mark as modified if found
if (m_blocks_sending.erase(p) + m_blocks_sent.erase(p) > 0)
m_blocks_modified.insert(p);
// and reset the scan loop if found
if (m_blocks_sending.erase(p) + m_blocks_sent.erase(p) > 0) {
// If this is a low priority event, do not reset m_nearest_unsent_d.
// Instead, the send loop will get to the block in the next full loop iteration.
if (!low_priority) {
// Note that we do NOT use the euclidean distance here.
// getNextBlocks builds successive cube-surfaces in the send loop.
// This resets the distance to the maximum cube size that
// still guarantees that this block will be scanned again right away.
//
// Using m_last_center is OK, as a change in center
// will reset m_nearest_unsent_d to 0 anyway (see getNextBlocks).
p -= m_last_center;
s16 this_d = std::max({std::abs(p.X), std::abs(p.Y), std::abs(p.Z)});
m_nearest_unsent_d = std::min(m_nearest_unsent_d, this_d);
}
}
}
void RemoteClient::SetBlocksNotSent(const std::vector<v3s16> &blocks)
void RemoteClient::SetBlocksNotSent(const std::vector<v3s16> &blocks, bool low_priority)
{
m_nothing_to_send_pause_timer = 0;
for (v3s16 p : blocks) {
// remove the block from sending and sent sets,
// and mark as modified if found
if (m_blocks_sending.erase(p) + m_blocks_sent.erase(p) > 0)
m_blocks_modified.insert(p);
SetBlockNotSent(p, low_priority);
}
}
@ -679,12 +668,12 @@ std::vector<session_t> ClientInterface::getClientIDs(ClientState min_state)
return reply;
}
void ClientInterface::markBlocksNotSent(const std::vector<v3s16> &positions)
void ClientInterface::markBlocksNotSent(const std::vector<v3s16> &positions, bool low_priority)
{
RecursiveMutexAutoLock clientslock(m_clients_mutex);
for (const auto &client : m_clients) {
if (client.second->getState() >= CS_Active)
client.second->SetBlocksNotSent(positions);
client.second->SetBlocksNotSent(positions, low_priority);
}
}

View file

@ -251,8 +251,8 @@ public:
void SentBlock(v3s16 p);
void SetBlockNotSent(v3s16 p);
void SetBlocksNotSent(const std::vector<v3s16> &blocks);
void SetBlockNotSent(v3s16 p, bool low_priority = false);
void SetBlocksNotSent(const std::vector<v3s16> &blocks, bool low_priority = false);
/**
* tell client about this block being modified right now.
@ -388,19 +388,8 @@ private:
- The size of this list is limited to some value
Block is added when it is sent with BLOCKDATA.
Block is removed when GOTBLOCKS is received.
Value is time from sending. (not used at the moment)
*/
std::unordered_map<v3s16, float> m_blocks_sending;
/*
Blocks that have been modified since blocks were
sent to the client last (getNextBlocks()).
This is used to reset the unsent distance, so that
modified blocks are resent to the client.
List of block positions.
*/
std::unordered_set<v3s16> m_blocks_modified;
std::unordered_set<v3s16> m_blocks_sending;
/*
Count of excess GotBlocks().
@ -454,7 +443,7 @@ public:
std::vector<session_t> getClientIDs(ClientState min_state=CS_Active);
/* mark blocks as not sent on all active clients */
void markBlocksNotSent(const std::vector<v3s16> &positions);
void markBlocksNotSent(const std::vector<v3s16> &positions, bool low_priority = false);
/* verify is server user limit was reached */
bool isUserLimitReached();

View file

@ -128,8 +128,9 @@ void UnitSAO::setAttachment(const object_t new_parent, const std::string &bone,
};
// Do checks to avoid circular references
// See similar check in `GenericCAO::setAttachment` (but with different types).
{
auto *obj = new_parent ? m_env->getActiveObject(new_parent) : nullptr;
auto *obj = m_env->getActiveObject(new_parent);
if (obj == this) {
assert(false);
return;
@ -145,7 +146,8 @@ void UnitSAO::setAttachment(const object_t new_parent, const std::string &bone,
}
}
if (problem) {
warningstream << "Mod bug: Attempted to attach object " << m_id << " to parent "
warningstream << "Mod bug: "
<< "Attempted to attach object " << m_id << " to parent "
<< new_parent << " but former is an (in)direct parent of latter." << std::endl;
return;
}

View file

@ -55,15 +55,13 @@ static void fillRadiusBlock(v3s16 p0, s16 r, std::set<v3s16> &list)
{
v3s16 p;
for(p.X=p0.X-r; p.X<=p0.X+r; p.X++)
for(p.Y=p0.Y-r; p.Y<=p0.Y+r; p.Y++)
for(p.Z=p0.Z-r; p.Z<=p0.Z+r; p.Z++)
{
// limit to a sphere
if (p.getDistanceFrom(p0) <= r) {
// Set in list
list.insert(p);
}
}
for(p.Y=p0.Y-r; p.Y<=p0.Y+r; p.Y++)
for(p.Z=p0.Z-r; p.Z<=p0.Z+r; p.Z++) {
// limit to a sphere
if (p.getDistanceFrom(p0) <= r) {
list.insert(p);
}
}
}
static void fillViewConeBlock(v3s16 p0,
@ -119,30 +117,23 @@ void ActiveBlockList::update(std::vector<PlayerSAO*> &active_players,
m_abm_list = newlist;
/*
Find out which blocks on the new list are not on the old list
*/
// 1. Find out which blocks on the new list are not on the old list
std::set_difference(newlist.begin(), newlist.end(), m_list.begin(), m_list.end(),
std::inserter(blocks_added, blocks_added.end()));
// 2. remove duplicate blocks from the extra list
for (v3s16 p : newlist) {
// also remove duplicate blocks from the extra list
extralist.erase(p);
// If not on old list, it's been added
if (m_list.find(p) == m_list.end())
blocks_added.insert(p);
}
/*
Find out which blocks on the extra list are not on the old list
*/
for (v3s16 p : extralist) {
// also make sure newlist has all blocks
newlist.insert(p);
// If not on old list, it's been added
if (m_list.find(p) == m_list.end())
extra_blocks_added.insert(p);
}
/*
Find out which blocks on the old list are not on the new + extra list
*/
// 3. Find out which blocks on the extra list are not on the old list
std::set_difference(extralist.begin(), extralist.end(), m_list.begin(), m_list.end(),
std::inserter(extra_blocks_added, extra_blocks_added.end()));
// 4. make sure newlist has all new block
newlist.insert(extralist.begin(), extralist.end());
// 5. Find out which blocks on the old list are not on the new + extra list
std::set_difference(m_list.begin(), m_list.end(), newlist.begin(), newlist.end(),
std::inserter(blocks_removed, blocks_removed.end()));
@ -167,9 +158,7 @@ void ActiveBlockList::update(std::vector<PlayerSAO*> &active_players,
assert(m_list.count(*blocks_removed.begin()) > 0);
}
/*
Update m_list
*/
// Update m_list
m_list = std::move(newlist);
}

Some files were not shown because too many files have changed in this diff Show more