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:
commit
e05544b84e
340 changed files with 73244 additions and 25967 deletions
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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 ||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include <string>
|
||||
#include <map>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include "wieldmesh.h" // ItemMesh
|
||||
#include "util/basic_macros.h"
|
||||
|
||||
|
|
|
@ -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++) {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)"
|
||||
");"
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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
141
src/gui/guiButtonKey.cpp
Normal 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
75
src/gui/guiButtonKey.h
Normal 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;
|
||||
};
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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");
|
||||
}
|
|
@ -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;
|
||||
};
|
|
@ -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()
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 = "";
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
15
src/main.cpp
15
src/main.cpp
|
@ -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());
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include <vector>
|
||||
#include <map>
|
||||
#include <unordered_set>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "irr_v3d.h"
|
||||
#include "mapnode.h"
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue