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

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

This commit is contained in:
Gefüllte Taubenbrust 2024-12-22 19:08:52 +01:00
commit e7c7441429
285 changed files with 8894 additions and 4654 deletions

View file

@ -249,6 +249,19 @@ if(ENABLE_SPATIAL)
endif(SPATIAL_LIBRARY AND SPATIAL_INCLUDE_DIR)
endif(ENABLE_SPATIAL)
option(ENABLE_OPENSSL "Use OpenSSL's libcrypto for faster SHA implementations" TRUE)
set(USE_OPENSSL FALSE)
if(ENABLE_OPENSSL)
find_package(OpenSSL 3.0)
if(OPENSSL_FOUND)
set(USE_OPENSSL TRUE)
message(STATUS "OpenSSL's libcrypto SHA enabled.")
else()
message(STATUS "OpenSSL not found!")
endif()
endif(ENABLE_OPENSSL)
find_package(ZLIB REQUIRED)
find_package(Zstd REQUIRED)
@ -260,12 +273,11 @@ endif()
# Haiku endian support
if(HAIKU)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_BSD_SOURCE")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_BSD_SOURCE")
add_compile_definitions(_BSD_SOURCE)
endif()
# Use cmake_config.h
add_definitions(-DUSE_CMAKE_CONFIG_H)
add_compile_definitions(USE_CMAKE_CONFIG_H)
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)
@ -274,15 +286,19 @@ set(PLATFORM_LIBS Threads::Threads)
if(WIN32)
# Windows
if(MSVC) # MSVC Specifics
set(PLATFORM_LIBS dbghelp.lib ${PLATFORM_LIBS})
list(APPEND PLATFORM_LIBS dbghelp.lib)
# Surpress some useless warnings
add_definitions ( /D "_CRT_SECURE_NO_DEPRECATE" /W1 )
# Get M_PI to work
add_definitions(/D "_USE_MATH_DEFINES")
# Don't define min/max macros in minwindef.h
add_definitions(/D "NOMINMAX")
add_compile_options(/W1)
add_compile_definitions(
# Suppress some useless warnings
_CRT_SECURE_NO_DEPRECATE
# Get M_PI to work
_USE_MATH_DEFINES
# Don't define min/max macros in minwindef.h
NOMINMAX
)
endif()
set(PLATFORM_LIBS ws2_32.lib version.lib shlwapi.lib winmm.lib ${PLATFORM_LIBS})
list(APPEND PLATFORM_LIBS ws2_32.lib version.lib shlwapi.lib winmm.lib)
set(EXTRA_DLL "" CACHE FILEPATH "Optional paths to additional DLLs that should be packaged")
@ -309,28 +325,28 @@ if(WIN32)
endif()
else()
# Unix probably
set(PLATFORM_LIBS ${PLATFORM_LIBS} ${CMAKE_DL_LIBS})
list(APPEND PLATFORM_LIBS ${CMAKE_DL_LIBS})
if(APPLE)
set(PLATFORM_LIBS "-framework CoreFoundation" ${PLATFORM_LIBS})
list(APPEND PLATFORM_LIBS "-framework CoreFoundation")
else()
check_library_exists(rt clock_gettime "" HAVE_LIBRT)
if (HAVE_LIBRT)
set(PLATFORM_LIBS -lrt ${PLATFORM_LIBS})
list(APPEND PLATFORM_LIBS -lrt)
endif(HAVE_LIBRT)
endif(APPLE)
find_library(ICONV_LIBRARY iconv)
mark_as_advanced(ICONV_LIBRARY)
if (ICONV_LIBRARY)
set(PLATFORM_LIBS ${PLATFORM_LIBS} ${ICONV_LIBRARY})
list(APPEND PLATFORM_LIBS ${ICONV_LIBRARY})
endif()
if (HAIKU)
set(PLATFORM_LIBS ${PLATFORM_LIBS} network)
list(APPEND PLATFORM_LIBS network)
endif()
if (ANDROID)
set(PLATFORM_LIBS ${PLATFORM_LIBS} android log)
list(APPEND PLATFORM_LIBS android log)
endif()
endif()
@ -344,7 +360,7 @@ if(NOT CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
check_c_source_compiles("int main(){}" HAVE_LINK_ATOMIC)
set(CMAKE_REQUIRED_LIBRARIES "")
if(HAVE_LINK_ATOMIC)
set(PLATFORM_LIBS ${PLATFORM_LIBS} "-latomic")
list(APPEND PLATFORM_LIBS "-latomic")
endif()
endif()
@ -468,21 +484,21 @@ set(common_SRCS
)
if(ANDROID)
set(common_SRCS ${common_SRCS} porting_android.cpp)
list(APPEND common_SRCS porting_android.cpp)
endif()
if(BUILD_UNITTESTS)
add_subdirectory(unittest)
set(common_SRCS ${common_SRCS} ${UNITTEST_SRCS})
list(APPEND common_SRCS ${UNITTEST_SRCS})
endif()
if(BUILD_BENCHMARKS)
add_subdirectory(benchmark)
set(common_SRCS ${common_SRCS} ${BENCHMARK_SRCS})
list(APPEND common_SRCS ${BENCHMARK_SRCS})
endif()
if(BUILD_UNITTESTS OR BUILD_BENCHMARKS)
set(common_SRCS ${common_SRCS} catch.cpp)
list(APPEND common_SRCS catch.cpp)
endif()
# This gives us the icon and file version information
@ -513,8 +529,7 @@ if (BUILD_CLIENT)
add_subdirectory(irrlicht_changes)
endif(BUILD_CLIENT)
set(client_SRCS
${client_SRCS}
list(APPEND client_SRCS
${common_SRCS}
${gui_SRCS}
${client_network_SRCS}
@ -523,11 +538,11 @@ set(client_SRCS
)
if(BUILD_UNITTESTS)
set(client_SRCS ${client_SRCS} ${UNITTEST_CLIENT_SRCS})
list(APPEND client_SRCS ${UNITTEST_CLIENT_SRCS})
endif()
if(BUILD_BENCHMARKS)
set(client_SRCS ${client_SRCS} ${BENCHMARK_CLIENT_SRCS})
list(APPEND client_SRCS ${BENCHMARK_CLIENT_SRCS})
endif()
# Server sources
@ -591,6 +606,9 @@ add_dependencies(EngineCommon GenerateVersion)
target_link_libraries(EngineCommon
sha256
)
if(USE_OPENSSL)
target_link_libraries(EngineCommon OpenSSL::Crypto)
endif()
get_target_property(
IRRLICHT_INCLUDES IrrlichtMt::IrrlichtMt INTERFACE_INCLUDE_DIRECTORIES)
target_include_directories(EngineCommon PRIVATE ${IRRLICHT_INCLUDES})
@ -724,6 +742,9 @@ if(BUILD_CLIENT)
if (USE_SPATIAL)
target_link_libraries(${PROJECT_NAME} ${SPATIAL_LIBRARY})
endif()
if (USE_OPENSSL)
target_link_libraries(${PROJECT_NAME} OpenSSL::Crypto)
endif()
if(BUILD_UNITTESTS OR BUILD_BENCHMARKS)
target_link_libraries(${PROJECT_NAME} Catch2::Catch2)
endif()
@ -793,6 +814,9 @@ if(BUILD_SERVER)
if (USE_SPATIAL)
target_link_libraries(${PROJECT_NAME}server ${SPATIAL_LIBRARY})
endif()
if (USE_OPENSSL)
target_link_libraries(${PROJECT_NAME}server OpenSSL::Crypto)
endif()
if(USE_CURL)
target_link_libraries(
${PROJECT_NAME}server
@ -871,7 +895,7 @@ endif()
if(MSVC)
# Visual Studio
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /D _WIN32_WINNT=0x0601 /D WIN32_LEAN_AND_MEAN /D _CRT_SECURE_NO_WARNINGS")
add_compile_definitions(_WIN32_WINNT=0x0601 WIN32_LEAN_AND_MEAN _CRT_SECURE_NO_WARNINGS)
# EHa enables SEH exceptions (used for catching segfaults)
set(CMAKE_CXX_FLAGS_RELEASE "/EHa /Ox /MD /GS- /Zi /fp:fast /D NDEBUG /D _HAS_ITERATOR_DEBUGGING=0")
if(CMAKE_SIZEOF_VOID_P EQUAL 4)
@ -893,12 +917,12 @@ if(MSVC)
# Flags that cannot be shared between cl and clang-cl
# https://clang.llvm.org/docs/UsersManual.html#clang-cl
if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fuse-ld=lld")
add_compile_options(-fuse-ld=lld)
# Disable pragma-pack warning
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -Wno-pragma-pack")
else()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP")
add_compile_options(/MP)
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /TP /FD /GL")
set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /LTCG")
endif()
@ -915,7 +939,7 @@ else()
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
set(OTHER_FLAGS "${OTHER_FLAGS} -mthreads")
endif()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_WIN32_WINNT=0x0601 -DWIN32_LEAN_AND_MEAN")
add_compile_definitions(_WIN32_WINNT=0x0601 WIN32_LEAN_AND_MEAN)
endif()
# Use a safe subset of flags to speed up math calculations:
@ -1103,7 +1127,7 @@ elseif (USE_GETTEXT)
COMMENT "mo-update [${LOCALE}]: Creating mo file."
)
set(MO_FILES ${MO_FILES} ${MO_FILE_PATH})
list(APPEND MO_FILES ${MO_FILE_PATH})
endforeach()
add_custom_target(translations ALL COMMENT "mo update" DEPENDS ${MO_FILES})

View file

@ -5,6 +5,7 @@ set (BENCHMARK_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/benchmark_serialize.cpp
${CMAKE_CURRENT_SOURCE_DIR}/benchmark_mapblock.cpp
${CMAKE_CURRENT_SOURCE_DIR}/benchmark_mapmodify.cpp
${CMAKE_CURRENT_SOURCE_DIR}/benchmark_sha.cpp
PARENT_SCOPE)
set (BENCHMARK_CLIENT_SRCS

View file

@ -0,0 +1,23 @@
// SPDX-FileCopyrightText: 2024 Luanti Contributors
//
// SPDX-License-Identifier: LGPL-2.1-or-later
#include "catch.h"
#include "util/hashing.h"
#include <string>
TEST_CASE("benchmark_sha")
{
std::string input;
input.resize(100000);
BENCHMARK("sha1_input100000", i) {
input[0] = (char)i;
return hashing::sha1(input);
};
BENCHMARK("sha256_input100000", i) {
input[0] = (char)i;
return hashing::sha256(input);
};
}

View file

@ -1,7 +1,7 @@
set(sound_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/sound.cpp)
if(USE_SOUND)
set(sound_SRCS ${sound_SRCS}
list(APPEND sound_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/sound/al_extensions.cpp
${CMAKE_CURRENT_SOURCE_DIR}/sound/al_helpers.cpp
${CMAKE_CURRENT_SOURCE_DIR}/sound/ogg_file.cpp
@ -51,6 +51,7 @@ set(client_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/fontengine.cpp
${CMAKE_CURRENT_SOURCE_DIR}/game.cpp
${CMAKE_CURRENT_SOURCE_DIR}/gameui.cpp
${CMAKE_CURRENT_SOURCE_DIR}/game_formspec.cpp
${CMAKE_CURRENT_SOURCE_DIR}/guiscalingfilter.cpp
${CMAKE_CURRENT_SOURCE_DIR}/hud.cpp
${CMAKE_CURRENT_SOURCE_DIR}/imagefilters.cpp

View file

@ -48,7 +48,7 @@ struct Nametag
return bgcolor.value();
else if (!use_fallback)
return video::SColor(0, 0, 0, 0);
else if (textcolor.getLuminance() > 186)
else if (textcolor.getBrightness() > 186)
// Dark background for light text
return video::SColor(50, 50, 50, 50);
else

View file

@ -14,7 +14,6 @@
#include "network/networkpacket.h"
#include "threading/mutex_auto_lock.h"
#include "client/clientevent.h"
#include "client/gameui.h"
#include "client/renderingengine.h"
#include "client/sound.h"
#include "client/texturepaths.h"
@ -94,7 +93,6 @@ Client::Client(
ISoundManager *sound,
MtEventManager *event,
RenderingEngine *rendering_engine,
GameUI *game_ui,
ELoginRegister allow_login_or_register
):
m_tsrc(tsrc),
@ -117,7 +115,6 @@ Client::Client(
m_chosen_auth_mech(AUTH_MECHANISM_NONE),
m_media_downloader(new ClientMediaDownloader()),
m_state(LC_Created),
m_game_ui(game_ui),
m_modchannel_mgr(new ModChannelMgr())
{
// Add local player
@ -1128,7 +1125,7 @@ void Client::sendInit(const std::string &playerName)
{
NetworkPacket pkt(TOSERVER_INIT, 1 + 2 + 2 + (1 + playerName.size()));
pkt << (u8) SER_FMT_VER_HIGHEST_READ << (u16) 0;
pkt << SER_FMT_VER_HIGHEST_READ << (u16) 0 /* unused */;
pkt << CLIENT_PROTOCOL_VERSION_MIN << LATEST_PROTOCOL_VERSION;
pkt << playerName;

View file

@ -99,7 +99,6 @@ private:
};
class ClientScripting;
class GameUI;
class Client : public con::PeerHandler, public InventoryManager, public IGameDef
{
@ -119,7 +118,6 @@ public:
ISoundManager *sound,
MtEventManager *event,
RenderingEngine *rendering_engine,
GameUI *game_ui,
ELoginRegister allow_login_or_register
);
@ -572,8 +570,6 @@ private:
// own state
LocalClientState m_state;
GameUI *m_game_ui;
// Used for saving server map to disk client-side
MapDatabase *m_localdb = nullptr;
IntervalLimiter m_localdb_save_interval;

View file

@ -25,19 +25,10 @@ namespace {
// A helper struct
struct MeshBufListMaps
{
struct MaterialHash
{
size_t operator()(const video::SMaterial &m) const noexcept
{
// Only hash first texture. Simple and fast.
return std::hash<video::ITexture *>{}(m.TextureLayers[0].Texture);
}
};
using MeshBufListMap = std::unordered_map<
video::SMaterial,
std::vector<std::pair<v3s16, scene::IMeshBuffer *>>,
MaterialHash>;
std::vector<std::pair<v3s16, scene::IMeshBuffer *>>
>;
std::array<MeshBufListMap, MAX_TILE_LAYERS> maps;
@ -73,6 +64,7 @@ static const std::string ClientMap_settings[] = {
"trilinear_filter",
"bilinear_filter",
"anisotropic_filter",
"transparency_sorting_group_by_buffers",
"transparency_sorting_distance",
"occlusion_culler",
"enable_raytraced_culling",
@ -115,6 +107,9 @@ void ClientMap::onSettingChanged(std::string_view name, bool all)
m_cache_bilinear_filter = g_settings->getBool("bilinear_filter");
if (all || name == "anisotropic_filter")
m_cache_anistropic_filter = g_settings->getBool("anisotropic_filter");
if (all || name == "transparency_sorting_group_by_buffers")
m_cache_transparency_sorting_group_by_buffers =
g_settings->getBool("transparency_sorting_group_by_buffers");
if (all || name == "transparency_sorting_distance")
m_cache_transparency_sorting_distance = g_settings->getU16("transparency_sorting_distance");
if (all || name == "occlusion_culler")
@ -1337,7 +1332,8 @@ void ClientMap::updateTransparentMeshBuffers()
}
if (do_sort_block) {
blockmesh->updateTransparentBuffers(m_camera_position, block->getPos());
blockmesh->updateTransparentBuffers(m_camera_position, block->getPos(),
m_cache_transparency_sorting_group_by_buffers);
++sorted_blocks;
} else {
blockmesh->consolidateTransparentBuffers();

View file

@ -182,6 +182,7 @@ private:
bool m_cache_trilinear_filter;
bool m_cache_bilinear_filter;
bool m_cache_anistropic_filter;
bool m_cache_transparency_sorting_group_by_buffers;
u16 m_cache_transparency_sorting_distance;
bool m_loops_occlusion_culler;

View file

@ -13,7 +13,7 @@
#include "settings.h"
#include "util/hex.h"
#include "util/serialize.h"
#include "util/sha1.h"
#include "util/hashing.h"
#include "util/string.h"
#include <sstream>
@ -537,12 +537,7 @@ bool IClientMediaDownloader::checkAndLoad(
std::string sha1_hex = hex_encode(sha1);
// Compute actual checksum of data
std::string data_sha1;
{
SHA1 ctx;
ctx.addBytes(data);
data_sha1 = ctx.getDigest();
}
std::string data_sha1 = hashing::sha1(data);
// Check that received file matches announced checksum
if (data_sha1 != sha1) {

View file

@ -37,7 +37,7 @@ Clouds::Clouds(scene::ISceneManager* mgr, IShaderSource *ssrc,
m_material.FogEnable = true;
m_material.AntiAliasing = video::EAAM_SIMPLE;
{
auto sid = ssrc->getShader("cloud_shader", TILE_MATERIAL_ALPHA);
auto sid = ssrc->getShaderRaw("cloud_shader", true);
m_material.MaterialType = ssrc->getShaderInfo(sid).material;
}

View file

@ -1663,11 +1663,6 @@ void GenericCAO::processMessage(const std::string &data)
bool is_end_position = readU8(is);
float update_interval = readF32(is);
// Place us a bit higher if we're physical, to not sink into
// the ground due to sucky collision detection...
if(m_prop.physical)
m_position += v3f(0,0.002,0);
if(getParent() != NULL) // Just in case
return;

View file

@ -1644,7 +1644,6 @@ void MapblockMeshGenerator::drawMeshNode()
{
u8 facedir = 0;
scene::IMesh* mesh;
bool private_mesh; // as a grab/drop pair is not thread-safe
int degrotate = 0;
if (cur_node.f->param_type_2 == CPT2_FACEDIR ||
@ -1664,32 +1663,35 @@ void MapblockMeshGenerator::drawMeshNode()
if (cur_node.f->mesh_ptr) {
// clone and rotate mesh
private_mesh = true;
mesh = cloneMesh(cur_node.f->mesh_ptr);
bool modified = true;
if (facedir)
rotateMeshBy6dFacedir(mesh, facedir);
else if (degrotate)
rotateMeshXZby(mesh, 1.5f * degrotate);
recalculateBoundingBox(mesh);
meshmanip->recalculateNormals(mesh, true, false);
else
modified = false;
if (modified) {
recalculateBoundingBox(mesh);
}
} else {
warningstream << "drawMeshNode(): missing mesh" << std::endl;
return;
}
int mesh_buffer_count = mesh->getMeshBufferCount();
for (int j = 0; j < mesh_buffer_count; j++) {
for (u32 j = 0; j < mesh->getMeshBufferCount(); j++) {
// Only up to 6 tiles are supported
const auto tile = mesh->getTextureSlot(j);
const u32 tile = mesh->getTextureSlot(j);
useTile(MYMIN(tile, 5));
scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
video::S3DVertex *vertices = (video::S3DVertex *)buf->getVertices();
int vertex_count = buf->getVertexCount();
u32 vertex_count = buf->getVertexCount();
if (data->m_smooth_lighting) {
// Mesh is always private here. So the lighting is applied to each
// vertex right here.
for (int k = 0; k < vertex_count; k++) {
for (u32 k = 0; k < vertex_count; k++) {
video::S3DVertex &vertex = vertices[k];
vertex.Color = blendLightColor(vertex.Pos, vertex.Normal);
vertex.Pos += cur_node.origin;
@ -1697,15 +1699,13 @@ void MapblockMeshGenerator::drawMeshNode()
collector->append(cur_node.tile, vertices, vertex_count,
buf->getIndices(), buf->getIndexCount());
} else {
// Don't modify the mesh, it may not be private here.
// Instead, let the collector process colors, etc.
// Let the collector process colors, etc.
collector->append(cur_node.tile, vertices, vertex_count,
buf->getIndices(), buf->getIndexCount(), cur_node.origin,
cur_node.color, cur_node.f->light_source);
}
}
if (private_mesh)
mesh->drop();
mesh->drop();
}
// also called when the drawtype is known but should have been pre-converted

View file

@ -11,6 +11,7 @@
#include "client.h"
#include "client/clientevent.h"
#include "client/gameui.h"
#include "client/game_formspec.h"
#include "client/inputhandler.h"
#include "client/texturepaths.h"
#include "client/keys.h"
@ -32,11 +33,7 @@
#include "gameparams.h"
#include "gettext.h"
#include "gui/guiChatConsole.h"
#include "gui/guiFormSpecMenu.h"
#include "gui/guiKeyChangeMenu.h"
#include "gui/guiPasswordChange.h"
#include "gui/guiOpenURL.h"
#include "gui/guiVolumeChange.h"
#include "texturesource.h"
#include "gui/mainmenumanager.h"
#include "gui/profilergraph.h"
#include "minimap.h"
@ -68,171 +65,6 @@
#include "client/sound/sound_openal.h"
#endif
/*
Text input system
*/
struct TextDestNodeMetadata : public TextDest
{
TextDestNodeMetadata(v3s16 p, Client *client)
{
m_p = p;
m_client = client;
}
// This is deprecated I guess? -celeron55
void gotText(const std::wstring &text)
{
std::string ntext = wide_to_utf8(text);
infostream << "Submitting 'text' field of node at (" << m_p.X << ","
<< m_p.Y << "," << m_p.Z << "): " << ntext << std::endl;
StringMap fields;
fields["text"] = ntext;
m_client->sendNodemetaFields(m_p, "", fields);
}
void gotText(const StringMap &fields)
{
m_client->sendNodemetaFields(m_p, "", fields);
}
v3s16 m_p;
Client *m_client;
};
struct TextDestPlayerInventory : public TextDest
{
TextDestPlayerInventory(Client *client)
{
m_client = client;
m_formname.clear();
}
TextDestPlayerInventory(Client *client, const std::string &formname)
{
m_client = client;
m_formname = formname;
}
void gotText(const StringMap &fields)
{
m_client->sendInventoryFields(m_formname, fields);
}
Client *m_client;
};
struct LocalFormspecHandler : public TextDest
{
LocalFormspecHandler(const std::string &formname)
{
m_formname = formname;
}
LocalFormspecHandler(const std::string &formname, Client *client):
m_client(client)
{
m_formname = formname;
}
void gotText(const StringMap &fields)
{
if (m_formname == "MT_PAUSE_MENU") {
if (fields.find("btn_sound") != fields.end()) {
g_gamecallback->changeVolume();
return;
}
if (fields.find("btn_key_config") != fields.end()) {
g_gamecallback->keyConfig();
return;
}
if (fields.find("btn_exit_menu") != fields.end()) {
g_gamecallback->disconnect();
return;
}
if (fields.find("btn_exit_os") != fields.end()) {
g_gamecallback->exitToOS();
#ifndef __ANDROID__
RenderingEngine::get_raw_device()->closeDevice();
#endif
return;
}
if (fields.find("btn_change_password") != fields.end()) {
g_gamecallback->changePassword();
return;
}
return;
}
if (m_formname == "MT_DEATH_SCREEN") {
assert(m_client != nullptr);
if (fields.find("quit") != fields.end())
m_client->sendRespawnLegacy();
return;
}
if (m_client->modsLoaded())
m_client->getScript()->on_formspec_input(m_formname, fields);
}
Client *m_client = nullptr;
};
/* Form update callback */
class NodeMetadataFormSource: public IFormSource
{
public:
NodeMetadataFormSource(ClientMap *map, v3s16 p):
m_map(map),
m_p(p)
{
}
const std::string &getForm() const
{
static const std::string empty_string = "";
NodeMetadata *meta = m_map->getNodeMetadata(m_p);
if (!meta)
return empty_string;
return meta->getString("formspec");
}
virtual std::string resolveText(const std::string &str)
{
NodeMetadata *meta = m_map->getNodeMetadata(m_p);
if (!meta)
return str;
return meta->resolveString(str);
}
ClientMap *m_map;
v3s16 m_p;
};
class PlayerInventoryFormSource: public IFormSource
{
public:
PlayerInventoryFormSource(Client *client):
m_client(client)
{
}
const std::string &getForm() const
{
LocalPlayer *player = m_client->getEnv().getLocalPlayer();
return player->inventory_formspec;
}
Client *m_client;
};
class NodeDugEvent : public MtEvent
{
public:
@ -636,8 +468,6 @@ public:
}
};
#define SIZE_TAG "size[11,5.5,true]" // Fixed size (ignored in touchscreen mode)
/****************************************************************************
****************************************************************************/
@ -739,7 +569,6 @@ protected:
void updateInteractTimers(f32 dtime);
bool checkConnection();
bool handleCallbacks();
void processQueues();
void updateProfilers(const RunStats &stats, const FpsControl &draw_times, f32 dtime);
void updateDebugState();
@ -753,7 +582,6 @@ protected:
bool shouldShowTouchControls();
void dropSelectedItem(bool single_item = false);
void openInventory();
void openConsole(float scale, const wchar_t *line=NULL);
void toggleFreeMove();
void toggleFreeMoveAlt();
@ -855,9 +683,6 @@ private:
bool disable_camera_update = false;
};
void showDeathFormspecLegacy();
void showPauseMenu();
void pauseAnimation();
void resumeAnimation();
@ -922,6 +747,7 @@ private:
irr_ptr<Sky> sky;
Hud *hud = nullptr;
Minimap *mapper = nullptr;
GameFormSpec m_game_formspec;
// Map server hud ids to client hud ids
std::unordered_map<u32, u32> m_hud_server_to_client;
@ -1129,6 +955,8 @@ bool Game::startup(bool *kill,
m_rendering_engine->initialize(client, hud);
m_game_formspec.init(client, m_rendering_engine, input);
return true;
}
@ -1199,7 +1027,7 @@ void Game::run()
if (!checkConnection())
break;
if (!handleCallbacks())
if (!m_game_formspec.handleCallbacks())
break;
processQueues();
@ -1231,7 +1059,7 @@ void Game::run()
updateProfilerGraphs(&graph);
if (m_does_lost_focus_pause_game && !device->isWindowFocused() && !isMenuActive()) {
showPauseMenu();
m_game_formspec.showPauseMenu();
}
}
@ -1243,10 +1071,6 @@ void Game::run()
void Game::shutdown()
{
auto formspec = m_game_ui->getFormspecGUI();
if (formspec)
formspec->quitMenu();
// Clear text when exiting.
m_game_ui->clearText();
@ -1268,8 +1092,6 @@ void Game::shutdown()
g_menumgr.deleteFront();
}
m_game_ui->deleteFormspec();
chat_backend->addMessage(L"", L"# Disconnected.");
chat_backend->addMessage(L"", L"");
@ -1654,7 +1476,7 @@ bool Game::connectToServer(const GameStartData &start_data,
start_data.password,
*draw_control, texture_src, shader_src,
itemdef_manager, nodedef_manager, sound_manager.get(), eventmgr,
m_rendering_engine, m_game_ui.get(),
m_rendering_engine,
start_data.allow_login_or_register);
} catch (const BaseException &e) {
*error_message = fmtgettext("Error creating client: %s", e.what());
@ -1862,49 +1684,6 @@ inline bool Game::checkConnection()
return true;
}
/* returns false if game should exit, otherwise true
*/
inline bool Game::handleCallbacks()
{
if (g_gamecallback->disconnect_requested) {
g_gamecallback->disconnect_requested = false;
return false;
}
if (g_gamecallback->changepassword_requested) {
(void)make_irr<GUIPasswordChange>(guienv, guiroot, -1,
&g_menumgr, client, texture_src);
g_gamecallback->changepassword_requested = false;
}
if (g_gamecallback->changevolume_requested) {
(void)make_irr<GUIVolumeChange>(guienv, guiroot, -1,
&g_menumgr, texture_src);
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->show_open_url_dialog.empty()) {
(void)make_irr<GUIOpenURLMenu>(guienv, guiroot, -1,
&g_menumgr, texture_src, g_gamecallback->show_open_url_dialog);
g_gamecallback->show_open_url_dialog.clear();
}
if (g_gamecallback->keyconfig_changed) {
input->keycache.populate(); // update the cache with new settings
g_gamecallback->keyconfig_changed = false;
}
return true;
}
void Game::processQueues()
{
texture_src->processQueue();
@ -1931,10 +1710,7 @@ void Game::updateDebugState()
if (!has_debug) {
draw_control->show_wireframe = false;
m_flags.disable_camera_update = false;
auto formspec = m_game_ui->getFormspecGUI();
if (formspec) {
formspec->setDebugView(false);
}
m_game_formspec.disableDebugView();
}
// noclip
@ -2078,10 +1854,7 @@ void Game::processUserInput(f32 dtime)
input->step(dtime);
#ifdef __ANDROID__
auto formspec = m_game_ui->getFormspecGUI();
if (formspec)
formspec->getAndroidUIInput();
else
if (!m_game_formspec.handleAndroidUIInput())
handleAndroidChatInput();
#endif
@ -2106,13 +1879,13 @@ void Game::processKeyInput()
if (g_settings->getBool("continuous_forward"))
toggleAutoforward();
} else if (wasKeyDown(KeyType::INVENTORY)) {
openInventory();
m_game_formspec.showPlayerInventory();
} else if (input->cancelPressed()) {
#ifdef __ANDROID__
m_android_chat_open = false;
#endif
if (!gui_chat_console->isOpenInhibited()) {
showPauseMenu();
m_game_formspec.showPauseMenu();
}
} else if (wasKeyDown(KeyType::CHAT)) {
openConsole(0.2, L"");
@ -2280,45 +2053,6 @@ void Game::dropSelectedItem(bool single_item)
client->inventoryAction(a);
}
void Game::openInventory()
{
/*
* Don't permit to open inventory is CAO or player doesn't exists.
* This prevent showing an empty inventory at player load
*/
LocalPlayer *player = client->getEnv().getLocalPlayer();
if (!player || !player->getCAO())
return;
infostream << "Game: Launching inventory" << std::endl;
PlayerInventoryFormSource *fs_src = new PlayerInventoryFormSource(client);
InventoryLocation inventoryloc;
inventoryloc.setCurrentPlayer();
if (client->modsLoaded() && client->getScript()->on_inventory_open(fs_src->m_client->getInventory(inventoryloc))) {
delete fs_src;
return;
}
if (fs_src->getForm().empty()) {
delete fs_src;
return;
}
TextDest *txt_dst = new TextDestPlayerInventory(client);
auto *&formspec = m_game_ui->updateFormspec("");
GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
&input->joystick, fs_src, txt_dst, client->getFormspecPrepend(),
sound_manager.get());
formspec->setFormSpec(fs_src->getForm(), inventoryloc);
}
void Game::openConsole(float scale, const wchar_t *line)
{
assert(scale > 0.0f && scale <= 1.0f);
@ -2920,28 +2654,13 @@ void Game::handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientati
void Game::handleClientEvent_DeathscreenLegacy(ClientEvent *event, CameraOrientation *cam)
{
showDeathFormspecLegacy();
m_game_formspec.showDeathFormspecLegacy();
}
void Game::handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam)
{
if (event->show_formspec.formspec->empty()) {
auto formspec = m_game_ui->getFormspecGUI();
if (formspec && (event->show_formspec.formname->empty()
|| *(event->show_formspec.formname) == m_game_ui->getFormspecName())) {
formspec->quitMenu();
}
} else {
FormspecFormSource *fs_src =
new FormspecFormSource(*(event->show_formspec.formspec));
TextDestPlayerInventory *txt_dst =
new TextDestPlayerInventory(client, *(event->show_formspec.formname));
auto *&formspec = m_game_ui->updateFormspec(*(event->show_formspec.formname));
GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
&input->joystick, fs_src, txt_dst, client->getFormspecPrepend(),
sound_manager.get());
}
m_game_formspec.showFormSpec(*event->show_formspec.formspec,
*event->show_formspec.formname);
delete event->show_formspec.formspec;
delete event->show_formspec.formname;
@ -2949,11 +2668,8 @@ void Game::handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation
void Game::handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam)
{
FormspecFormSource *fs_src = new FormspecFormSource(*event->show_formspec.formspec);
LocalFormspecHandler *txt_dst =
new LocalFormspecHandler(*event->show_formspec.formname, client);
GUIFormSpecMenu::create(m_game_ui->getFormspecGUI(), client, m_rendering_engine->get_gui_env(),
&input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound_manager.get());
m_game_formspec.showLocalFormSpec(*event->show_formspec.formspec,
*event->show_formspec.formname);
delete event->show_formspec.formspec;
delete event->show_formspec.formname;
@ -3691,21 +3407,7 @@ bool Game::nodePlacement(const ItemDefinition &selected_def,
if (nodedef_manager->get(map.getNode(nodepos)).rightclickable)
client->interact(INTERACT_PLACE, pointed);
infostream << "Launching custom inventory view" << std::endl;
InventoryLocation inventoryloc;
inventoryloc.setNodeMeta(nodepos);
NodeMetadataFormSource *fs_src = new NodeMetadataFormSource(
&client->getEnv().getClientMap(), nodepos);
TextDest *txt_dst = new TextDestNodeMetadata(nodepos, client);
auto *&formspec = m_game_ui->updateFormspec("");
GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
&input->joystick, fs_src, txt_dst, client->getFormspecPrepend(),
sound_manager.get());
formspec->setFormSpec(meta->getString("formspec"), inventoryloc);
m_game_formspec.showNodeFormspec(meta->getString("formspec"), nodepos);
return false;
}
@ -4241,34 +3943,7 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
m_game_ui->update(*stats, client, draw_control, cam, runData.pointed_old,
gui_chat_console.get(), dtime);
/*
make sure menu is on top
1. Delete formspec menu reference if menu was removed
2. Else, make sure formspec menu is on top
*/
auto formspec = m_game_ui->getFormspecGUI();
do { // breakable. only runs for one iteration
if (!formspec)
break;
if (formspec->getReferenceCount() == 1) {
// See GUIFormSpecMenu::create what refcnt = 1 means
m_game_ui->deleteFormspec();
break;
}
auto &loc = formspec->getFormspecLocation();
if (loc.type == InventoryLocation::NODEMETA) {
NodeMetadata *meta = client->getEnv().getClientMap().getNodeMetadata(loc.p);
if (!meta || meta->getString("formspec").empty()) {
formspec->quitMenu();
break;
}
}
if (isMenuActive())
guiroot->bringToFront(formspec);
} while (false);
m_game_formspec.update();
/*
==================== Drawing begins ====================
@ -4485,140 +4160,6 @@ void Game::readSettings()
m_does_lost_focus_pause_game = g_settings->getBool("pause_on_lost_focus");
}
/****************************************************************************/
/****************************************************************************
Shutdown / cleanup
****************************************************************************/
/****************************************************************************/
void Game::showDeathFormspecLegacy()
{
static std::string formspec_str =
std::string("formspec_version[1]") +
SIZE_TAG
"bgcolor[#320000b4;true]"
"label[4.85,1.35;" + gettext("You died") + "]"
"button_exit[4,3;3,0.5;btn_respawn;" + gettext("Respawn") + "]"
;
/* Create menu */
/* Note: FormspecFormSource and LocalFormspecHandler *
* are deleted by guiFormSpecMenu */
FormspecFormSource *fs_src = new FormspecFormSource(formspec_str);
LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_DEATH_SCREEN", client);
auto *&formspec = m_game_ui->getFormspecGUI();
GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
&input->joystick, fs_src, txt_dst, client->getFormspecPrepend(),
sound_manager.get());
formspec->setFocus("btn_respawn");
}
#define GET_KEY_NAME(KEY) gettext(getKeySetting(#KEY).name())
void Game::showPauseMenu()
{
std::string control_text;
if (g_touchcontrols) {
control_text = strgettext("Controls:\n"
"No menu open:\n"
"- slide finger: look around\n"
"- tap: place/punch/use (default)\n"
"- long tap: dig/use (default)\n"
"Menu/inventory open:\n"
"- double tap (outside):\n"
" --> close\n"
"- touch stack, touch slot:\n"
" --> move stack\n"
"- touch&drag, tap 2nd finger\n"
" --> place single item to slot\n"
);
}
float ypos = simple_singleplayer_mode ? 0.7f : 0.1f;
std::ostringstream os;
os << "formspec_version[1]" << SIZE_TAG
<< "button_exit[4," << (ypos++) << ";3,0.5;btn_continue;"
<< strgettext("Continue") << "]";
if (!simple_singleplayer_mode) {
os << "button_exit[4," << (ypos++) << ";3,0.5;btn_change_password;"
<< strgettext("Change Password") << "]";
} else {
os << "field[4.95,0;5,1.5;;" << strgettext("Game paused") << ";]";
}
#ifndef __ANDROID__
#if USE_SOUND
if (g_settings->getBool("enable_sound")) {
os << "button_exit[4," << (ypos++) << ";3,0.5;btn_sound;"
<< strgettext("Sound Volume") << "]";
}
#endif
os << "button_exit[4," << (ypos++) << ";3,0.5;btn_key_config;"
<< strgettext("Controls") << "]";
#endif
os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_menu;"
<< strgettext("Exit to Menu") << "]";
os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;"
<< strgettext("Exit to OS") << "]";
if (!control_text.empty()) {
os << "textarea[7.5,0.25;3.9,6.25;;" << control_text << ";]";
}
os << "textarea[0.4,0.25;3.9,6.25;;" << PROJECT_NAME_C " " VERSION_STRING "\n"
<< "\n"
<< strgettext("Game info:") << "\n";
const std::string &address = client->getAddressName();
os << strgettext("- Mode: ");
if (!simple_singleplayer_mode) {
if (address.empty())
os << strgettext("Hosting server");
else
os << strgettext("Remote server");
} else {
os << strgettext("Singleplayer");
}
os << "\n";
if (simple_singleplayer_mode || address.empty()) {
static const std::string on = strgettext("On");
static const std::string off = strgettext("Off");
// Note: Status of enable_damage and creative_mode settings is intentionally
// NOT shown here because the game might roll its own damage system and/or do
// a per-player Creative Mode, in which case writing it here would mislead.
bool damage = g_settings->getBool("enable_damage");
const std::string &announced = g_settings->getBool("server_announce") ? on : off;
if (!simple_singleplayer_mode) {
if (damage) {
const std::string &pvp = g_settings->getBool("enable_pvp") ? on : off;
//~ PvP = Player versus Player
os << strgettext("- PvP: ") << pvp << "\n";
}
os << strgettext("- Public: ") << announced << "\n";
std::string server_name = g_settings->get("server_name");
str_formspec_escape(server_name);
if (announced == on && !server_name.empty())
os << strgettext("- Server Name: ") << server_name;
}
}
os << ";]";
/* Create menu */
/* Note: FormspecFormSource and LocalFormspecHandler *
* are deleted by guiFormSpecMenu */
FormspecFormSource *fs_src = new FormspecFormSource(os.str());
LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_PAUSE_MENU");
auto *&formspec = m_game_ui->getFormspecGUI();
GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
&input->joystick, fs_src, txt_dst, client->getFormspecPrepend(),
sound_manager.get());
formspec->setFocus("btn_continue");
// game will be paused in next step, if in singleplayer (see m_is_paused)
formspec->doPause = true;
}
/****************************************************************************/
/****************************************************************************
extern function for launching the game

View file

@ -0,0 +1,524 @@
// Luanti
// SPDX-License-Identifier: LGPL-2.1-or-later
// Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
#include "game_formspec.h"
#include "gettext.h"
#include "nodemetadata.h"
#include "renderingengine.h"
#include "client.h"
#include "scripting_client.h"
#include "clientmap.h"
#include "gui/guiFormSpecMenu.h"
#include "gui/mainmenumanager.h"
#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"
/*
Text input system
*/
struct TextDestNodeMetadata : public TextDest
{
TextDestNodeMetadata(v3s16 p, Client *client)
{
m_p = p;
m_client = client;
}
// This is deprecated I guess? -celeron55
void gotText(const std::wstring &text)
{
std::string ntext = wide_to_utf8(text);
infostream << "Submitting 'text' field of node at (" << m_p.X << ","
<< m_p.Y << "," << m_p.Z << "): " << ntext << std::endl;
StringMap fields;
fields["text"] = ntext;
m_client->sendNodemetaFields(m_p, "", fields);
}
void gotText(const StringMap &fields)
{
m_client->sendNodemetaFields(m_p, "", fields);
}
v3s16 m_p;
Client *m_client;
};
struct TextDestPlayerInventory : public TextDest
{
TextDestPlayerInventory(Client *client)
{
m_client = client;
m_formname.clear();
}
TextDestPlayerInventory(Client *client, const std::string &formname)
{
m_client = client;
m_formname = formname;
}
void gotText(const StringMap &fields)
{
m_client->sendInventoryFields(m_formname, fields);
}
Client *m_client;
};
struct LocalFormspecHandler : public TextDest
{
LocalFormspecHandler(const std::string &formname)
{
m_formname = formname;
}
LocalFormspecHandler(const std::string &formname, Client *client):
m_client(client)
{
m_formname = formname;
}
void gotText(const StringMap &fields)
{
if (m_formname == "MT_PAUSE_MENU") {
if (fields.find("btn_sound") != fields.end()) {
g_gamecallback->changeVolume();
return;
}
if (fields.find("btn_key_config") != fields.end()) {
g_gamecallback->keyConfig();
return;
}
if (fields.find("btn_touchscreen_layout") != fields.end()) {
g_gamecallback->touchscreenLayout();
return;
}
if (fields.find("btn_exit_menu") != fields.end()) {
g_gamecallback->disconnect();
return;
}
if (fields.find("btn_exit_os") != fields.end()) {
g_gamecallback->exitToOS();
#ifndef __ANDROID__
RenderingEngine::get_raw_device()->closeDevice();
#endif
return;
}
if (fields.find("btn_change_password") != fields.end()) {
g_gamecallback->changePassword();
return;
}
return;
}
if (m_formname == "MT_DEATH_SCREEN") {
assert(m_client != nullptr);
if (fields.find("quit") != fields.end())
m_client->sendRespawnLegacy();
return;
}
if (m_client->modsLoaded())
m_client->getScript()->on_formspec_input(m_formname, fields);
}
Client *m_client = nullptr;
};
/* Form update callback */
class NodeMetadataFormSource: public IFormSource
{
public:
NodeMetadataFormSource(ClientMap *map, v3s16 p):
m_map(map),
m_p(p)
{
}
const std::string &getForm() const
{
static const std::string empty_string = "";
NodeMetadata *meta = m_map->getNodeMetadata(m_p);
if (!meta)
return empty_string;
return meta->getString("formspec");
}
virtual std::string resolveText(const std::string &str)
{
NodeMetadata *meta = m_map->getNodeMetadata(m_p);
if (!meta)
return str;
return meta->resolveString(str);
}
ClientMap *m_map;
v3s16 m_p;
};
class PlayerInventoryFormSource: public IFormSource
{
public:
PlayerInventoryFormSource(Client *client):
m_client(client)
{
}
const std::string &getForm() const
{
LocalPlayer *player = m_client->getEnv().getLocalPlayer();
return player->inventory_formspec;
}
Client *m_client;
};
//// GameFormSpec
void GameFormSpec::deleteFormspec()
{
if (m_formspec) {
m_formspec->drop();
m_formspec = nullptr;
}
m_formname.clear();
}
GameFormSpec::~GameFormSpec() {
if (m_formspec)
m_formspec->quitMenu();
this->deleteFormspec();
}
void GameFormSpec::showFormSpec(const std::string &formspec, const std::string &formname)
{
if (formspec.empty()) {
if (m_formspec && (formname.empty() || formname == m_formname)) {
m_formspec->quitMenu();
}
} else {
FormspecFormSource *fs_src =
new FormspecFormSource(formspec);
TextDestPlayerInventory *txt_dst =
new TextDestPlayerInventory(m_client, formname);
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());
}
}
void GameFormSpec::showLocalFormSpec(const std::string &formspec, const std::string &formname)
{
FormspecFormSource *fs_src = new FormspecFormSource(formspec);
LocalFormspecHandler *txt_dst =
new LocalFormspecHandler(formname, m_client);
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());
}
void GameFormSpec::showNodeFormspec(const std::string &formspec, const v3s16 &nodepos)
{
infostream << "Launching custom inventory view" << std::endl;
InventoryLocation inventoryloc;
inventoryloc.setNodeMeta(nodepos);
NodeMetadataFormSource *fs_src = new NodeMetadataFormSource(
&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());
m_formspec->setFormSpec(formspec, inventoryloc);
}
void GameFormSpec::showPlayerInventory()
{
/*
* Don't permit to open inventory is CAO or player doesn't exists.
* This prevent showing an empty inventory at player load
*/
LocalPlayer *player = m_client->getEnv().getLocalPlayer();
if (!player || !player->getCAO())
return;
infostream << "Game: Launching inventory" << std::endl;
PlayerInventoryFormSource *fs_src = new PlayerInventoryFormSource(m_client);
InventoryLocation inventoryloc;
inventoryloc.setCurrentPlayer();
if (m_client->modsLoaded() && m_client->getScript()->on_inventory_open(m_client->getInventory(inventoryloc))) {
delete fs_src;
return;
}
if (fs_src->getForm().empty()) {
delete fs_src;
return;
}
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());
m_formspec->setFormSpec(fs_src->getForm(), inventoryloc);
}
#define SIZE_TAG "size[11,5.5,true]" // Fixed size (ignored in touchscreen mode)
void GameFormSpec::showPauseMenu()
{
std::string control_text;
if (g_touchcontrols) {
control_text = strgettext("Controls:\n"
"No menu open:\n"
"- slide finger: look around\n"
"- tap: place/punch/use (default)\n"
"- long tap: dig/use (default)\n"
"Menu/inventory open:\n"
"- double tap (outside):\n"
" --> close\n"
"- touch stack, touch slot:\n"
" --> move stack\n"
"- touch&drag, tap 2nd finger\n"
" --> place single item to slot\n"
);
}
auto simple_singleplayer_mode = m_client->m_simple_singleplayer_mode;
float ypos = simple_singleplayer_mode ? 0.7f : 0.1f;
std::ostringstream os;
os << "formspec_version[1]" << SIZE_TAG
<< "button_exit[4," << (ypos++) << ";3,0.5;btn_continue;"
<< strgettext("Continue") << "]";
if (!simple_singleplayer_mode) {
os << "button_exit[4," << (ypos++) << ";3,0.5;btn_change_password;"
<< strgettext("Change Password") << "]";
} else {
os << "field[4.95,0;5,1.5;;" << strgettext("Game paused") << ";]";
}
#ifndef __ANDROID__
#if USE_SOUND
if (g_settings->getBool("enable_sound")) {
os << "button_exit[4," << (ypos++) << ";3,0.5;btn_sound;"
<< strgettext("Sound Volume") << "]";
}
#endif
#endif
if (g_touchcontrols) {
os << "button_exit[4," << (ypos++) << ";3,0.5;btn_touchscreen_layout;"
<< strgettext("Touchscreen Layout") << "]";
} else {
os << "button_exit[4," << (ypos++) << ";3,0.5;btn_key_config;"
<< strgettext("Controls") << "]";
}
os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_menu;"
<< strgettext("Exit to Menu") << "]";
os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;"
<< strgettext("Exit to OS") << "]";
if (!control_text.empty()) {
os << "textarea[7.5,0.25;3.9,6.25;;" << control_text << ";]";
}
os << "textarea[0.4,0.25;3.9,6.25;;" << PROJECT_NAME_C " " VERSION_STRING "\n"
<< "\n"
<< strgettext("Game info:") << "\n";
const std::string &address = m_client->getAddressName();
os << strgettext("- Mode: ");
if (!simple_singleplayer_mode) {
if (address.empty())
os << strgettext("Hosting server");
else
os << strgettext("Remote server");
} else {
os << strgettext("Singleplayer");
}
os << "\n";
if (simple_singleplayer_mode || address.empty()) {
static const std::string on = strgettext("On");
static const std::string off = strgettext("Off");
// Note: Status of enable_damage and creative_mode settings is intentionally
// NOT shown here because the game might roll its own damage system and/or do
// a per-player Creative Mode, in which case writing it here would mislead.
bool damage = g_settings->getBool("enable_damage");
const std::string &announced = g_settings->getBool("server_announce") ? on : off;
if (!simple_singleplayer_mode) {
if (damage) {
const std::string &pvp = g_settings->getBool("enable_pvp") ? on : off;
//~ PvP = Player versus Player
os << strgettext("- PvP: ") << pvp << "\n";
}
os << strgettext("- Public: ") << announced << "\n";
std::string server_name = g_settings->get("server_name");
str_formspec_escape(server_name);
if (announced == on && !server_name.empty())
os << strgettext("- Server Name: ") << server_name;
}
}
os << ";]";
/* Create menu */
/* Note: FormspecFormSource and LocalFormspecHandler *
* are deleted by guiFormSpecMenu */
FormspecFormSource *fs_src = new FormspecFormSource(os.str());
LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_PAUSE_MENU");
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->setFocus("btn_continue");
// game will be paused in next step, if in singleplayer (see m_is_paused)
m_formspec->doPause = true;
}
void GameFormSpec::showDeathFormspecLegacy()
{
static std::string formspec_str =
std::string("formspec_version[1]") +
SIZE_TAG
"bgcolor[#320000b4;true]"
"label[4.85,1.35;" + gettext("You died") + "]"
"button_exit[4,3;3,0.5;btn_respawn;" + gettext("Respawn") + "]"
;
/* Create menu */
/* Note: FormspecFormSource and LocalFormspecHandler *
* are deleted by guiFormSpecMenu */
FormspecFormSource *fs_src = new FormspecFormSource(formspec_str);
LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_DEATH_SCREEN", m_client);
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->setFocus("btn_respawn");
}
void GameFormSpec::update()
{
/*
make sure menu is on top
1. Delete formspec menu reference if menu was removed
2. Else, make sure formspec menu is on top
*/
if (!m_formspec)
return;
if (m_formspec->getReferenceCount() == 1) {
// See GUIFormSpecMenu::create what refcnt = 1 means
this->deleteFormspec();
return;
}
auto &loc = m_formspec->getFormspecLocation();
if (loc.type == InventoryLocation::NODEMETA) {
NodeMetadata *meta = m_client->getEnv().getClientMap().getNodeMetadata(loc.p);
if (!meta || meta->getString("formspec").empty()) {
m_formspec->quitMenu();
return;
}
}
if (isMenuActive())
guiroot->bringToFront(m_formspec);
}
void GameFormSpec::disableDebugView()
{
if (m_formspec) {
m_formspec->setDebugView(false);
}
}
/* returns false if game should exit, otherwise true
*/
bool GameFormSpec::handleCallbacks()
{
auto texture_src = m_client->getTextureSource();
if (g_gamecallback->disconnect_requested) {
g_gamecallback->disconnect_requested = false;
return false;
}
if (g_gamecallback->changepassword_requested) {
(void)make_irr<GUIPasswordChange>(guienv, guiroot, -1,
&g_menumgr, m_client, texture_src);
g_gamecallback->changepassword_requested = false;
}
if (g_gamecallback->changevolume_requested) {
(void)make_irr<GUIVolumeChange>(guienv, guiroot, -1,
&g_menumgr, texture_src);
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();
g_gamecallback->touchscreenlayout_requested = false;
}
if (!g_gamecallback->show_open_url_dialog.empty()) {
(void)make_irr<GUIOpenURLMenu>(guienv, guiroot, -1,
&g_menumgr, texture_src, g_gamecallback->show_open_url_dialog);
g_gamecallback->show_open_url_dialog.clear();
}
if (g_gamecallback->keyconfig_changed) {
m_input->keycache.populate(); // update the cache with new settings
g_gamecallback->keyconfig_changed = false;
}
return true;
}
#ifdef __ANDROID__
bool GameFormSpec::handleAndroidUIInput()
{
if (m_formspec) {
m_formspec->getAndroidUIInput();
return true;
}
return false;
}
#endif

View file

@ -0,0 +1,62 @@
// Luanti
// SPDX-License-Identifier: LGPL-2.1-or-later
// Copyright (C) 2024 cx384
#pragma once
#include <string>
#include "irr_v3d.h"
class Client;
class RenderingEngine;
class InputHandler;
class ISoundManager;
class GUIFormSpecMenu;
/*
This object intend to contain the core fromspec functionality.
It includes:
- methods to show specific formspec menus
- storing the opened fromspec
- handling fromspec related callbacks
*/
struct GameFormSpec
{
void init(Client *client, RenderingEngine *rendering_engine, InputHandler *input)
{
m_client = client;
m_rendering_engine = rendering_engine;
m_input = input;
}
~GameFormSpec();
void showFormSpec(const std::string &formspec, const std::string &formname);
void showLocalFormSpec(const std::string &formspec, const std::string &formname);
void showNodeFormspec(const std::string &formspec, const v3s16 &nodepos);
void showPlayerInventory();
void showDeathFormspecLegacy();
void showPauseMenu();
void update();
void disableDebugView();
bool handleCallbacks();
#ifdef __ANDROID__
// Returns false if no formspec open
bool handleAndroidUIInput();
#endif
private:
Client *m_client;
RenderingEngine *m_rendering_engine;
InputHandler *m_input;
// Default: "". If other than "": Empty show_formspec packets will only
// close the formspec when the formname matches
std::string m_formname;
GUIFormSpecMenu *m_formspec = nullptr;
void deleteFormspec();
};

View file

@ -8,7 +8,6 @@
#include <gettext.h>
#include "gui/mainmenumanager.h"
#include "gui/guiChatConsole.h"
#include "gui/guiFormSpecMenu.h"
#include "gui/touchcontrols.h"
#include "util/enriched_string.h"
#include "util/pointedthing.h"
@ -201,10 +200,10 @@ void GameUI::update(const RunStats &stats, Client *client, MapDrawControl *draw_
status_y - status_height, status_x + status_width, status_y));
// Fade out
video::SColor final_color = m_statustext_initial_color;
final_color.setAlpha(0);
video::SColor fade_color = m_statustext_initial_color.getInterpolated_quadratic(
m_statustext_initial_color, final_color, m_statustext_time / statustext_time_max);
video::SColor fade_color = m_statustext_initial_color;
f32 d = m_statustext_time / statustext_time_max;
fade_color.setAlpha(static_cast<u32>(
fade_color.getAlpha() * (1.0f - d * d)));
guitext_status->setOverrideColor(fade_color);
guitext_status->enableOverrideColor(true);
}
@ -319,17 +318,6 @@ void GameUI::toggleProfiler()
}
}
void GameUI::deleteFormspec()
{
if (m_formspec) {
m_formspec->drop();
m_formspec = nullptr;
}
m_formname.clear();
}
void GameUI::clearText()
{
if (m_guitext_chat) {

View file

@ -13,7 +13,6 @@ using namespace irr;
class Client;
class EnrichedString;
class GUIChatConsole;
class GUIFormSpecMenu;
struct MapDrawControl;
struct PointedThing;
@ -79,15 +78,6 @@ public:
void toggleHud();
void toggleProfiler();
GUIFormSpecMenu *&updateFormspec(const std::string &formname)
{
m_formname = formname;
return m_formspec;
}
const std::string &getFormspecName() { return m_formname; }
GUIFormSpecMenu *&getFormspecGUI() { return m_formspec; }
void deleteFormspec();
void clearText();
private:
@ -113,9 +103,4 @@ private:
gui::IGUIStaticText *m_guitext_profiler = nullptr; // Profiler text
u8 m_profiler_current_page = 0;
const u8 m_profiler_max_page = 3;
// Default: "". If other than "": Empty show_formspec packets will only
// close the formspec when the formname matches
std::string m_formname;
GUIFormSpecMenu *m_formspec = nullptr;
};

View file

@ -24,6 +24,7 @@
#include "wieldmesh.h"
#include "client/renderingengine.h"
#include "client/minimap.h"
#include "client/texturesource.h"
#include "gui/touchcontrols.h"
#include "util/enriched_string.h"
#include "irrlicht_changes/CGUITTFont.h"
@ -54,14 +55,14 @@ Hud::Hud(Client *client, LocalPlayer *player,
tsrc = client->getTextureSource();
v3f crosshair_color = g_settings->getV3F("crosshair_color");
v3f crosshair_color = g_settings->getV3F("crosshair_color").value_or(v3f());
u32 cross_r = rangelim(myround(crosshair_color.X), 0, 255);
u32 cross_g = rangelim(myround(crosshair_color.Y), 0, 255);
u32 cross_b = rangelim(myround(crosshair_color.Z), 0, 255);
u32 cross_a = rangelim(g_settings->getS32("crosshair_alpha"), 0, 255);
crosshair_argb = video::SColor(cross_a, cross_r, cross_g, cross_b);
v3f selectionbox_color = g_settings->getV3F("selectionbox_color");
v3f selectionbox_color = g_settings->getV3F("selectionbox_color").value_or(v3f());
u32 sbox_r = rangelim(myround(selectionbox_color.X), 0, 255);
u32 sbox_g = rangelim(myround(selectionbox_color.Y), 0, 255);
u32 sbox_b = rangelim(myround(selectionbox_color.Z), 0, 255);
@ -85,10 +86,11 @@ Hud::Hud(Client *client, LocalPlayer *player,
// Initialize m_selection_material
IShaderSource *shdrsrc = client->getShaderSource();
{
auto shader_id = shdrsrc->getShader(
m_mode == HIGHLIGHT_HALO ? "selection_shader" : "default_shader", TILE_MATERIAL_ALPHA);
if (m_mode == HIGHLIGHT_HALO) {
auto shader_id = shdrsrc->getShaderRaw("selection_shader", true);
m_selection_material.MaterialType = shdrsrc->getShaderInfo(shader_id).material;
} else {
m_selection_material.MaterialType = video::EMT_SOLID;
}
if (m_mode == HIGHLIGHT_BOX) {
@ -102,10 +104,7 @@ Hud::Hud(Client *client, LocalPlayer *player,
}
// Initialize m_block_bounds_material
{
auto shader_id = shdrsrc->getShader("default_shader", TILE_MATERIAL_ALPHA);
m_block_bounds_material.MaterialType = shdrsrc->getShaderInfo(shader_id).material;
}
m_block_bounds_material.MaterialType = video::EMT_SOLID;
m_block_bounds_material.Thickness =
rangelim(g_settings->getS16("selectionbox_width"), 1, 5);

View file

@ -566,7 +566,7 @@ static void apply_hue_saturation(video::IImage *dst, v2u32 dst_pos, v2u32 size,
for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
if (colorize) {
f32 lum = dst->getPixel(x, y).getLuminance() / 255.0f;
f32 lum = dst->getPixel(x, y).getBrightness() / 255.0f;
if (norm_l < 0) {
lum *= norm_l + 1.0f;
@ -1511,10 +1511,18 @@ bool ImageSource::generateImagePart(std::string_view part_of_name,
CHECK_BASEIMG();
// Apply the "clean transparent" filter, if needed
/* Apply the "clean transparent" filter, if necessary
* This is needed since filtering will sample parts of the image
* that are transparent and PNG optimizers often discard the color
* information in those parts. */
if (m_setting_mipmap || m_setting_bilinear_filter ||
m_setting_trilinear_filter || m_setting_anisotropic_filter)
imageCleanTransparent(baseimg, 127);
m_setting_trilinear_filter || m_setting_anisotropic_filter) {
/* Note: in theory we should pass either 0 or 127 depending on
* if the texture is used with an ALPHA or ALPHA_REF material,
* however we don't have this information here.
* It doesn't matter in practice. */
imageCleanTransparent(baseimg, 0);
}
/* 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

View file

@ -3,6 +3,7 @@
// Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
#include "mapblock_mesh.h"
#include "CMeshBuffer.h"
#include "client.h"
#include "mapblock.h"
#include "map.h"
@ -818,7 +819,8 @@ bool MapBlockMesh::animate(bool faraway, float time, int crack,
return true;
}
void MapBlockMesh::updateTransparentBuffers(v3f camera_pos, v3s16 block_pos)
void MapBlockMesh::updateTransparentBuffers(v3f camera_pos, v3s16 block_pos,
bool group_by_buffers)
{
// nothing to do if the entire block is opaque
if (m_transparent_triangles.empty())
@ -834,24 +836,56 @@ void MapBlockMesh::updateTransparentBuffers(v3f camera_pos, v3s16 block_pos)
m_transparent_buffers_consolidated = false;
m_transparent_buffers.clear();
std::vector<std::pair<scene::SMeshBuffer *, std::vector<u16>>> ordered_strains;
std::unordered_map<scene::SMeshBuffer *, size_t> strain_idxs;
if (group_by_buffers) {
// find (reversed) order for strains, by iterating front-to-back
// (if a buffer A has a triangle nearer than all triangles of another
// buffer B, A should be drawn in front of (=after) B)
scene::SMeshBuffer *current_buffer = nullptr;
for (auto it = triangle_refs.rbegin(); it != triangle_refs.rend(); ++it) {
const auto &t = m_transparent_triangles[*it];
if (current_buffer == t.buffer)
continue;
current_buffer = t.buffer;
auto [_it2, is_new] =
strain_idxs.emplace(current_buffer, ordered_strains.size());
if (is_new)
ordered_strains.emplace_back(current_buffer, std::vector<u16>{});
}
}
// find order for triangles, by iterating back-to-front
scene::SMeshBuffer *current_buffer = nullptr;
std::vector<u16> current_strain;
std::vector<u16> *current_strain = nullptr;
for (auto i : triangle_refs) {
const auto &t = m_transparent_triangles[i];
if (current_buffer != t.buffer) {
if (current_buffer) {
m_transparent_buffers.emplace_back(current_buffer, std::move(current_strain));
current_strain.clear();
}
current_buffer = t.buffer;
if (group_by_buffers) {
auto it = strain_idxs.find(current_buffer);
assert(it != strain_idxs.end());
current_strain = &ordered_strains[it->second].second;
} else {
ordered_strains.emplace_back(current_buffer, std::vector<u16>{});
current_strain = &ordered_strains.back().second;
}
}
current_strain.push_back(t.p1);
current_strain.push_back(t.p2);
current_strain.push_back(t.p3);
current_strain->push_back(t.p1);
current_strain->push_back(t.p2);
current_strain->push_back(t.p3);
}
if (!current_strain.empty())
m_transparent_buffers.emplace_back(current_buffer, std::move(current_strain));
m_transparent_buffers.reserve(ordered_strains.size());
if (group_by_buffers) {
// the order was reversed
for (auto it = ordered_strains.rbegin(); it != ordered_strains.rend(); ++it)
m_transparent_buffers.emplace_back(it->first, std::move(it->second));
} else {
for (auto it = ordered_strains.begin(); it != ordered_strains.end(); ++it)
m_transparent_buffers.emplace_back(it->first, std::move(it->second));
}
}
void MapBlockMesh::consolidateTransparentBuffers()

View file

@ -209,8 +209,14 @@ public:
/// Center of the bounding-sphere, in BS-space, relative to block pos.
v3f getBoundingSphereCenter() const { return m_bounding_sphere_center; }
/// update transparent buffers to render towards the camera
void updateTransparentBuffers(v3f camera_pos, v3s16 block_pos);
/** Update transparent buffers to render towards the camera.
* @param group_by_buffers If true, triangles in the same buffer are batched
* into the same PartialMeshBuffer, resulting in fewer draw calls, but
* wrong order. Triangles within a single buffer are still ordered, and
* buffers are ordered relative to each other (with respect to their nearest
* triangle).
*/
void updateTransparentBuffers(v3f camera_pos, v3s16 block_pos, bool group_by_buffers);
void consolidateTransparentBuffers();
/// get the list of transparent buffers

View file

@ -249,10 +249,14 @@ static void rotateMesh(scene::IMesh *mesh, float degrees)
float c = std::cos(degrees);
float s = std::sin(degrees);
auto rotator = [c, s] (video::S3DVertex *vertex) {
float u = vertex->Pos.*U;
float v = vertex->Pos.*V;
vertex->Pos.*U = c * u - s * v;
vertex->Pos.*V = s * u + c * v;
auto rotate_vec = [c, s] (v3f &vec) {
float u = vec.*U;
float v = vec.*V;
vec.*U = c * u - s * v;
vec.*V = s * u + c * v;
};
rotate_vec(vertex->Pos);
rotate_vec(vertex->Normal);
};
applyToMesh(mesh, rotator);
}
@ -272,9 +276,9 @@ void rotateMeshYZby(scene::IMesh *mesh, f64 degrees)
rotateMesh<&v3f::Y, &v3f::Z>(mesh, degrees);
}
void rotateMeshBy6dFacedir(scene::IMesh *mesh, int facedir)
void rotateMeshBy6dFacedir(scene::IMesh *mesh, u8 facedir)
{
int axisdir = facedir >> 2;
u8 axisdir = facedir >> 2;
facedir &= 0x03;
switch (facedir) {
case 1: rotateMeshXZby(mesh, -90); break;

View file

@ -68,7 +68,7 @@ void setMeshColorByNormal(scene::IMesh *mesh, const v3f &normal,
Rotate the mesh by 6d facedir value.
Method only for meshnodes, not suitable for entities.
*/
void rotateMeshBy6dFacedir(scene::IMesh *mesh, int facedir);
void rotateMeshBy6dFacedir(scene::IMesh *mesh, u8 facedir);
/*
Rotate the mesh around the axis and given angle in degrees.

View file

@ -599,7 +599,7 @@ void Minimap::drawMinimap(core::rect<s32> rect)
material.TextureLayers[1].Texture = data->heightmap_texture;
if (data->mode.type == MINIMAP_TYPE_SURFACE) {
auto sid = m_shdrsrc->getShader("minimap_shader", TILE_MATERIAL_ALPHA);
auto sid = m_shdrsrc->getShaderRaw("minimap_shader", true);
material.MaterialType = m_shdrsrc->getShaderInfo(sid).material;
} else {
material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;

View file

@ -22,6 +22,8 @@
#include "settings.h"
#include "profiler.h"
using BlendMode = ParticleParamTypes::BlendMode;
ClientParticleTexture::ClientParticleTexture(const ServerParticleTexture& p, ITextureSource *tsrc)
{
tex = p;
@ -268,6 +270,7 @@ ParticleSpawner::ParticleSpawner(
}
size_t max_particles = 0; // maximum number of particles likely to be visible at any given time
assert(p.time >= 0);
if (p.time != 0) {
auto maxGenerations = p.time / std::min(p.exptime.start.min, p.exptime.end.min);
max_particles = p.amount / maxGenerations;
@ -603,8 +606,11 @@ video::S3DVertex *ParticleBuffer::getVertices(u16 index)
void ParticleBuffer::OnRegisterSceneNode()
{
if (IsVisible)
SceneManager->registerNodeForRendering(this, scene::ESNRP_TRANSPARENT_EFFECT);
if (IsVisible) {
SceneManager->registerNodeForRendering(this,
m_mesh_buffer->getMaterial().MaterialType == video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF
? scene::ESNRP_SOLID : scene::ESNRP_TRANSPARENT_EFFECT);
}
scene::ISceneNode::OnRegisterSceneNode();
}
@ -906,6 +912,9 @@ void ParticleManager::addNodeParticle(IGameDef *gamedef,
if (!getNodeParticleParams(n, f, p, &ref, texpos, texsize, &color))
return;
p.texture.blendmode = f.alpha == ALPHAMODE_BLEND
? BlendMode::alpha : BlendMode::clip;
p.expirationtime = myrand_range(0, 100) / 100.0f;
// Physics
@ -940,40 +949,47 @@ void ParticleManager::reserveParticleSpace(size_t max_estimate)
m_particles.reserve(m_particles.size() + max_estimate);
}
video::SMaterial ParticleManager::getMaterialForParticle(const ClientParticleTexRef &texture)
static void setBlendMode(video::SMaterial &material, BlendMode blendmode)
{
// translate blend modes to GL blend functions
video::E_BLEND_FACTOR bfsrc, bfdst;
video::E_BLEND_OPERATION blendop;
const auto blendmode = texture.tex ? texture.tex->blendmode :
ParticleParamTypes::BlendMode::alpha;
switch (blendmode) {
case ParticleParamTypes::BlendMode::add:
case BlendMode::add:
bfsrc = video::EBF_SRC_ALPHA;
bfdst = video::EBF_DST_ALPHA;
blendop = video::EBO_ADD;
break;
case ParticleParamTypes::BlendMode::sub:
case BlendMode::sub:
bfsrc = video::EBF_SRC_ALPHA;
bfdst = video::EBF_DST_ALPHA;
blendop = video::EBO_REVSUBTRACT;
break;
case ParticleParamTypes::BlendMode::screen:
case BlendMode::screen:
bfsrc = video::EBF_ONE;
bfdst = video::EBF_ONE_MINUS_SRC_COLOR;
blendop = video::EBO_ADD;
break;
default: // includes ParticleParamTypes::BlendMode::alpha
default: // includes BlendMode::alpha
bfsrc = video::EBF_SRC_ALPHA;
bfdst = video::EBF_ONE_MINUS_SRC_ALPHA;
blendop = video::EBO_ADD;
break;
}
material.MaterialTypeParam = video::pack_textureBlendFunc(
bfsrc, bfdst,
video::EMFN_MODULATE_1X,
video::EAS_TEXTURE | video::EAS_VERTEX_COLOR);
material.BlendOperation = blendop;
}
video::SMaterial ParticleManager::getMaterialForParticle(const Particle *particle)
{
const ClientParticleTexRef &texture = particle->getTextureRef();
video::SMaterial material;
// Texture
@ -984,17 +1000,18 @@ video::SMaterial ParticleManager::getMaterialForParticle(const ClientParticleTex
tex.MagFilter = video::ETMAGF_NEAREST;
});
// We don't have working transparency sorting. Disable Z-Write for
// correct results for clipped-alpha at least.
material.ZWriteEnable = video::EZW_OFF;
// enable alpha blending and set blend mode
material.MaterialType = video::EMT_ONETEXTURE_BLEND;
material.MaterialTypeParam = video::pack_textureBlendFunc(
bfsrc, bfdst,
video::EMFN_MODULATE_1X,
video::EAS_TEXTURE | video::EAS_VERTEX_COLOR);
material.BlendOperation = blendop;
const auto blendmode = particle->getBlendMode();
if (blendmode == BlendMode::clip) {
material.ZWriteEnable = video::EZW_ON;
material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
material.MaterialTypeParam = 0.5f;
} else {
// We don't have working transparency sorting. Disable Z-Write for
// correct results for clipped-alpha at least.
material.ZWriteEnable = video::EZW_OFF;
material.MaterialType = video::EMT_ONETEXTURE_BLEND;
setBlendMode(material, blendmode);
}
material.setTexture(0, texture.ref);
return material;
@ -1004,7 +1021,7 @@ bool ParticleManager::addParticle(std::unique_ptr<Particle> toadd)
{
MutexAutoLock lock(m_particle_list_lock);
auto material = getMaterialForParticle(toadd->getTextureRef());
auto material = getMaterialForParticle(toadd.get());
ParticleBuffer *found = nullptr;
// simple shortcut when multiple particles of the same type get added

View file

@ -77,6 +77,9 @@ public:
const ClientParticleTexRef &getTextureRef() const { return m_texture; }
ParticleParamTypes::BlendMode getBlendMode() const
{ return m_texture.tex ? m_texture.tex->blendmode : m_p.texture.blendmode; }
ParticleBuffer *getBuffer() const { return m_buffer; }
bool attachToBuffer(ParticleBuffer *buffer);
@ -231,7 +234,7 @@ protected:
ParticleParameters &p, video::ITexture **texture, v2f &texpos,
v2f &texsize, video::SColor *color, u8 tilenum = 0);
static video::SMaterial getMaterialForParticle(const ClientParticleTexRef &texture);
static video::SMaterial getMaterialForParticle(const Particle *texture);
bool addParticle(std::unique_ptr<Particle> toadd);

View file

@ -21,8 +21,7 @@ void SetColorMaskStep::run(PipelineContext &context)
mat.Material.ColorMask = color_mask;
mat.EnableProps = video::EMP_COLOR_MASK;
mat.EnablePasses = scene::ESNRP_SKY_BOX | scene::ESNRP_SOLID |
scene::ESNRP_TRANSPARENT | scene::ESNRP_TRANSPARENT_EFFECT |
scene::ESNRP_SHADOW;
scene::ESNRP_TRANSPARENT | scene::ESNRP_TRANSPARENT_EFFECT;
}
/// ClearDepthBufferTarget

View file

@ -66,10 +66,12 @@ void populateInterlacedPipeline(RenderPipeline *pipeline, Client *client)
}
pipeline->addStep<OffsetCameraStep>(0.0f);
IShaderSource *s = client->getShaderSource();
u32 shader = s->getShader("3d_interlaced_merge", TILE_MATERIAL_BASIC);
auto shader = s->getShaderRaw("3d_interlaced_merge");
video::E_MATERIAL_TYPE material = s->getShaderInfo(shader).material;
auto texture_map = { TEXTURE_LEFT, TEXTURE_RIGHT, TEXTURE_MASK };
auto merge = pipeline->addStep<PostProcessingStep>(material, texture_map);
merge->setRenderSource(buffer);
merge->setRenderTarget(pipeline->createOwned<ScreenTarget>());

View file

@ -6,6 +6,7 @@
#include "client/client.h"
#include "client/hud.h"
#include "IRenderTarget.h"
#include "SColor.h"
#include <vector>
#include <memory>
@ -26,7 +27,7 @@ video::ITexture *TextureBuffer::getTexture(u8 index)
}
void TextureBuffer::setTexture(u8 index, core::dimension2du size, const std::string &name, video::ECOLOR_FORMAT format, bool clear)
void TextureBuffer::setTexture(u8 index, core::dimension2du size, const std::string &name, video::ECOLOR_FORMAT format, bool clear, u8 msaa)
{
assert(index != NO_DEPTH_TEXTURE);
@ -41,9 +42,10 @@ void TextureBuffer::setTexture(u8 index, core::dimension2du size, const std::str
definition.name = name;
definition.format = format;
definition.clear = clear;
definition.msaa = msaa;
}
void TextureBuffer::setTexture(u8 index, v2f scale_factor, const std::string &name, video::ECOLOR_FORMAT format, bool clear)
void TextureBuffer::setTexture(u8 index, v2f scale_factor, const std::string &name, video::ECOLOR_FORMAT format, bool clear, u8 msaa)
{
assert(index != NO_DEPTH_TEXTURE);
@ -58,6 +60,7 @@ void TextureBuffer::setTexture(u8 index, v2f scale_factor, const std::string &na
definition.name = name;
definition.format = format;
definition.clear = clear;
definition.msaa = msaa;
}
void TextureBuffer::reset(PipelineContext &context)
@ -119,23 +122,41 @@ bool TextureBuffer::ensureTexture(video::ITexture **texture, const TextureDefini
if (!modify)
return false;
if (*texture)
if (*texture) {
m_driver->removeTexture(*texture);
*texture = nullptr;
}
if (definition.valid) {
if (!m_driver->queryTextureFormat(definition.format)) {
errorstream << "Failed to create texture \"" << definition.name
<< "\": unsupported format " << video::ColorFormatNames[definition.format]
<< std::endl;
return false;
}
if (definition.clear) {
// We're not able to clear a render target texture
// We're not able to create a normal texture with MSAA
// (could be solved by more refactoring in Irrlicht, but not needed for now)
sanity_check(definition.msaa < 1);
video::IImage *image = m_driver->createImage(definition.format, size);
// Cannot use image->fill because it's not implemented for all formats.
std::memset(image->getData(), 0, image->getDataSizeFromFormat(definition.format, size.Width, size.Height));
*texture = m_driver->addTexture(definition.name.c_str(), image);
image->drop();
}
else {
} else if (definition.msaa > 0) {
*texture = m_driver->addRenderTargetTextureMs(size, definition.msaa, definition.name.c_str(), definition.format);
} else {
*texture = m_driver->addRenderTargetTexture(size, definition.name.c_str(), definition.format);
}
}
else {
*texture = nullptr;
if (!*texture) {
errorstream << "Failed to create texture \"" << definition.name
<< "\"" << std::endl;
return false;
}
}
return true;
@ -188,6 +209,12 @@ void TextureBufferOutput::activate(PipelineContext &context)
RenderTarget::activate(context);
}
video::IRenderTarget *TextureBufferOutput::getIrrRenderTarget(PipelineContext &context)
{
activate(context); // Needed to make sure that render_target is set up.
return render_target;
}
u8 DynamicSource::getTextureCount()
{
assert(isConfigured());

View file

@ -117,7 +117,7 @@ public:
* @param name unique name of the texture
* @param format color format
*/
void setTexture(u8 index, core::dimension2du size, const std::string& name, video::ECOLOR_FORMAT format, bool clear = false);
void setTexture(u8 index, core::dimension2du size, const std::string& name, video::ECOLOR_FORMAT format, bool clear = false, u8 msaa = 0);
/**
* Configure relative-size texture for the specific index
@ -127,7 +127,7 @@ public:
* @param name unique name of the texture
* @param format color format
*/
void setTexture(u8 index, v2f scale_factor, const std::string& name, video::ECOLOR_FORMAT format, bool clear = false);
void setTexture(u8 index, v2f scale_factor, const std::string& name, video::ECOLOR_FORMAT format, bool clear = false, u8 msaa = 0);
virtual u8 getTextureCount() override { return m_textures.size(); }
virtual video::ITexture *getTexture(u8 index) override;
@ -147,6 +147,7 @@ private:
core::dimension2du size;
std::string name;
video::ECOLOR_FORMAT format;
u8 msaa;
};
/**
@ -175,6 +176,9 @@ public:
TextureBufferOutput(TextureBuffer *buffer, const std::vector<u8> &texture_map, u8 depth_stencil);
virtual ~TextureBufferOutput() override;
void activate(PipelineContext &context) override;
video::IRenderTarget *getIrrRenderTarget(PipelineContext &context);
private:
static const u8 NO_DEPTH_TEXTURE = 255;

View file

@ -155,8 +155,11 @@ void populatePlainPipeline(RenderPipeline *pipeline, Client *client)
video::ECOLOR_FORMAT selectColorFormat(video::IVideoDriver *driver)
{
if (driver->queryTextureFormat(video::ECF_A16B16G16R16F))
u32 bits = g_settings->getU32("post_processing_texture_bits");
if (bits >= 16 && driver->queryTextureFormat(video::ECF_A16B16G16R16F))
return video::ECF_A16B16G16R16F;
if (bits >= 10 && driver->queryTextureFormat(video::ECF_A2R10G10B10))
return video::ECF_A2R10G10B10;
return video::ECF_A8R8G8B8;
}

View file

@ -9,6 +9,7 @@
#include "client/shader.h"
#include "client/tile.h"
#include "settings.h"
#include "mt_opengl.h"
PostProcessingStep::PostProcessingStep(u32 _shader_id, const std::vector<u8> &_texture_map) :
shader_id(_shader_id), texture_map(_texture_map)
@ -102,21 +103,45 @@ RenderStep *addPostProcessing(RenderPipeline *pipeline, RenderStep *previousStep
static const u8 TEXTURE_EXPOSURE_2 = 4;
static const u8 TEXTURE_FXAA = 5;
static const u8 TEXTURE_VOLUME = 6;
static const u8 TEXTURE_MSAA_COLOR = 7;
static const u8 TEXTURE_MSAA_DEPTH = 8;
static const u8 TEXTURE_SCALE_DOWN = 10;
static const u8 TEXTURE_SCALE_UP = 20;
// Super-sampling is simply rendering into a larger texture.
// Downscaling is done by the final step when rendering to the screen.
const std::string antialiasing = g_settings->get("antialiasing");
const bool enable_bloom = g_settings->getBool("enable_bloom");
const bool enable_volumetric_light = g_settings->getBool("enable_volumetric_lighting") && enable_bloom;
const bool enable_auto_exposure = g_settings->getBool("enable_auto_exposure");
const std::string antialiasing = g_settings->get("antialiasing");
const u16 antialiasing_scale = MYMAX(2, g_settings->getU16("fsaa"));
// This code only deals with MSAA in combination with post-processing. MSAA without
// post-processing works via a flag at OpenGL context creation instead.
// To make MSAA work with post-processing, we need multisample texture support,
// which has higher OpenGL (ES) version requirements.
// Note: This is not about renderbuffer objects, but about textures,
// since that's what we use and what Irrlicht allows us to use.
const bool msaa_available = driver->queryFeature(video::EVDF_TEXTURE_MULTISAMPLE);
const bool enable_msaa = antialiasing == "fsaa" && msaa_available;
if (antialiasing == "fsaa" && !msaa_available)
warningstream << "Ignoring configured FSAA. FSAA is not supported in "
<< "combination with post-processing by the current video driver." << std::endl;
const bool enable_ssaa = antialiasing == "ssaa";
const bool enable_fxaa = antialiasing == "fxaa";
const bool enable_volumetric_light = g_settings->getBool("enable_volumetric_lighting") && enable_bloom;
// Super-sampling is simply rendering into a larger texture.
// Downscaling is done by the final step when rendering to the screen.
if (enable_ssaa) {
u16 ssaa_scale = MYMAX(2, g_settings->getU16("fsaa"));
scale *= ssaa_scale;
scale *= antialiasing_scale;
}
if (enable_msaa) {
buffer->setTexture(TEXTURE_MSAA_COLOR, scale, "3d_render_msaa", color_format, false, antialiasing_scale);
buffer->setTexture(TEXTURE_MSAA_DEPTH, scale, "3d_depthmap_msaa", depth_format, false, antialiasing_scale);
}
buffer->setTexture(TEXTURE_COLOR, scale, "3d_render", color_format);
@ -125,7 +150,14 @@ RenderStep *addPostProcessing(RenderPipeline *pipeline, RenderStep *previousStep
buffer->setTexture(TEXTURE_DEPTH, scale, "3d_depthmap", depth_format);
// attach buffer to the previous step
previousStep->setRenderTarget(pipeline->createOwned<TextureBufferOutput>(buffer, std::vector<u8> { TEXTURE_COLOR }, TEXTURE_DEPTH));
if (enable_msaa) {
TextureBufferOutput *msaa = pipeline->createOwned<TextureBufferOutput>(buffer, std::vector<u8> { TEXTURE_MSAA_COLOR }, TEXTURE_MSAA_DEPTH);
previousStep->setRenderTarget(msaa);
TextureBufferOutput *normal = pipeline->createOwned<TextureBufferOutput>(buffer, std::vector<u8> { TEXTURE_COLOR }, TEXTURE_DEPTH);
pipeline->addStep<ResolveMSAAStep>(msaa, normal);
} else {
previousStep->setRenderTarget(pipeline->createOwned<TextureBufferOutput>(buffer, std::vector<u8> { TEXTURE_COLOR }, TEXTURE_DEPTH));
}
// shared variables
u32 shader_id;
@ -133,6 +165,9 @@ RenderStep *addPostProcessing(RenderPipeline *pipeline, RenderStep *previousStep
// Number of mipmap levels of the bloom downsampling texture
const u8 MIPMAP_LEVELS = 4;
// color_format can be a normalized integer format, but bloom requires
// values outside of [0,1] so this needs to be a different one.
const auto bloom_format = video::ECF_A16B16G16R16F;
// post-processing stage
@ -141,19 +176,19 @@ RenderStep *addPostProcessing(RenderPipeline *pipeline, RenderStep *previousStep
// common downsampling step for bloom or autoexposure
if (enable_bloom || enable_auto_exposure) {
v2f downscale = scale * 0.5;
v2f downscale = scale * 0.5f;
for (u8 i = 0; i < MIPMAP_LEVELS; i++) {
buffer->setTexture(TEXTURE_SCALE_DOWN + i, downscale, std::string("downsample") + std::to_string(i), color_format);
buffer->setTexture(TEXTURE_SCALE_DOWN + i, downscale, std::string("downsample") + std::to_string(i), bloom_format);
if (enable_bloom)
buffer->setTexture(TEXTURE_SCALE_UP + i, downscale, std::string("upsample") + std::to_string(i), color_format);
downscale *= 0.5;
buffer->setTexture(TEXTURE_SCALE_UP + i, downscale, std::string("upsample") + std::to_string(i), bloom_format);
downscale *= 0.5f;
}
if (enable_bloom) {
buffer->setTexture(TEXTURE_BLOOM, scale, "bloom", color_format);
buffer->setTexture(TEXTURE_BLOOM, scale, "bloom", bloom_format);
// get bright spots
u32 shader_id = client->getShaderSource()->getShader("extract_bloom", TILE_MATERIAL_PLAIN, NDT_MESH);
u32 shader_id = client->getShaderSource()->getShaderRaw("extract_bloom");
RenderStep *extract_bloom = pipeline->addStep<PostProcessingStep>(shader_id, std::vector<u8> { source, TEXTURE_EXPOSURE_1 });
extract_bloom->setRenderSource(buffer);
extract_bloom->setRenderTarget(pipeline->createOwned<TextureBufferOutput>(buffer, TEXTURE_BLOOM));
@ -163,7 +198,7 @@ RenderStep *addPostProcessing(RenderPipeline *pipeline, RenderStep *previousStep
if (enable_volumetric_light) {
buffer->setTexture(TEXTURE_VOLUME, scale, "volume", color_format);
shader_id = client->getShaderSource()->getShader("volumetric_light", TILE_MATERIAL_PLAIN, NDT_MESH);
shader_id = client->getShaderSource()->getShaderRaw("volumetric_light");
auto volume = pipeline->addStep<PostProcessingStep>(shader_id, std::vector<u8> { source, TEXTURE_DEPTH });
volume->setRenderSource(buffer);
volume->setRenderTarget(pipeline->createOwned<TextureBufferOutput>(buffer, TEXTURE_VOLUME));
@ -171,7 +206,7 @@ RenderStep *addPostProcessing(RenderPipeline *pipeline, RenderStep *previousStep
}
// downsample
shader_id = client->getShaderSource()->getShader("bloom_downsample", TILE_MATERIAL_PLAIN, NDT_MESH);
shader_id = client->getShaderSource()->getShaderRaw("bloom_downsample");
for (u8 i = 0; i < MIPMAP_LEVELS; i++) {
auto step = pipeline->addStep<PostProcessingStep>(shader_id, std::vector<u8> { source });
step->setRenderSource(buffer);
@ -184,7 +219,7 @@ RenderStep *addPostProcessing(RenderPipeline *pipeline, RenderStep *previousStep
// Bloom pt 2
if (enable_bloom) {
// upsample
shader_id = client->getShaderSource()->getShader("bloom_upsample", TILE_MATERIAL_PLAIN, NDT_MESH);
shader_id = client->getShaderSource()->getShaderRaw("bloom_upsample");
for (u8 i = MIPMAP_LEVELS - 1; i > 0; i--) {
auto step = pipeline->addStep<PostProcessingStep>(shader_id, std::vector<u8> { u8(TEXTURE_SCALE_DOWN + i - 1), source });
step->setRenderSource(buffer);
@ -197,7 +232,7 @@ RenderStep *addPostProcessing(RenderPipeline *pipeline, RenderStep *previousStep
// Dynamic Exposure pt2
if (enable_auto_exposure) {
shader_id = client->getShaderSource()->getShader("update_exposure", TILE_MATERIAL_PLAIN, NDT_MESH);
shader_id = client->getShaderSource()->getShaderRaw("update_exposure");
auto update_exposure = pipeline->addStep<PostProcessingStep>(shader_id, std::vector<u8> { TEXTURE_EXPOSURE_1, u8(TEXTURE_SCALE_DOWN + MIPMAP_LEVELS - 1) });
update_exposure->setBilinearFilter(1, true);
update_exposure->setRenderSource(buffer);
@ -211,7 +246,7 @@ RenderStep *addPostProcessing(RenderPipeline *pipeline, RenderStep *previousStep
final_stage_source = TEXTURE_FXAA;
buffer->setTexture(TEXTURE_FXAA, scale, "fxaa", color_format);
shader_id = client->getShaderSource()->getShader("fxaa", TILE_MATERIAL_PLAIN);
shader_id = client->getShaderSource()->getShaderRaw("fxaa");
PostProcessingStep *effect = pipeline->createOwned<PostProcessingStep>(shader_id, std::vector<u8> { TEXTURE_COLOR });
pipeline->addStep(effect);
effect->setBilinearFilter(0, true);
@ -220,7 +255,7 @@ RenderStep *addPostProcessing(RenderPipeline *pipeline, RenderStep *previousStep
}
// final merge
shader_id = client->getShaderSource()->getShader("second_stage", TILE_MATERIAL_PLAIN, NDT_MESH);
shader_id = client->getShaderSource()->getShaderRaw("second_stage");
PostProcessingStep *effect = pipeline->createOwned<PostProcessingStep>(shader_id, std::vector<u8> { final_stage_source, TEXTURE_SCALE_UP, TEXTURE_EXPOSURE_2 });
pipeline->addStep(effect);
if (enable_ssaa)
@ -234,3 +269,9 @@ RenderStep *addPostProcessing(RenderPipeline *pipeline, RenderStep *previousStep
return effect;
}
void ResolveMSAAStep::run(PipelineContext &context)
{
context.device->getVideoDriver()->blitRenderTarget(msaa_fbo->getIrrRenderTarget(context),
target_fbo->getIrrRenderTarget(context));
}

View file

@ -44,4 +44,18 @@ private:
void configureMaterial();
};
class ResolveMSAAStep : public TrivialRenderStep
{
public:
ResolveMSAAStep(TextureBufferOutput *_msaa_fbo, TextureBufferOutput *_target_fbo) :
msaa_fbo(_msaa_fbo), target_fbo(_target_fbo) {};
void run(PipelineContext &context) override;
private:
TextureBufferOutput *msaa_fbo;
TextureBufferOutput *target_fbo;
};
RenderStep *addPostProcessing(RenderPipeline *pipeline, RenderStep *previousStep, v2f scale, Client *client);

View file

@ -179,7 +179,10 @@ RenderingEngine::RenderingEngine(MyEventReceiver *receiver)
// bpp, fsaa, vsync
bool vsync = g_settings->getBool("vsync");
bool enable_fsaa = g_settings->get("antialiasing") == "fsaa";
// Don't enable MSAA in OpenGL context creation if post-processing is enabled,
// the post-processing pipeline handles it.
bool enable_fsaa = g_settings->get("antialiasing") == "fsaa" &&
!g_settings->getBool("enable_post_processing");
u16 fsaa = enable_fsaa ? MYMAX(2, g_settings->getU16("fsaa")) : 0;
// Determine driver

View file

@ -271,7 +271,7 @@ public:
The id 0 points to a null shader. Its material is EMT_SOLID.
*/
u32 getShaderIdDirect(const std::string &name,
MaterialType material_type, NodeDrawType drawtype) override;
MaterialType material_type, NodeDrawType drawtype);
/*
If shader specified by the name pointed by the id doesn't
@ -281,10 +281,18 @@ 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 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;
// Processes queued shader requests from other threads.
@ -607,11 +615,17 @@ ShaderInfo ShaderSource::generateShader(const std::string &name,
#define textureFlags texture2
)";
/* Define constants for node and object shaders */
const bool node_shader = drawtype != NodeDrawType_END;
if (node_shader) {
bool use_discard = fully_programmable;
// For renderers that should use discard instead of GL_ALPHA_TEST
const char *renderer = reinterpret_cast<const char*>(GL.GetString(GL.RENDERER));
if (strstr(renderer, "GC7000"))
use_discard = true;
if (!use_discard) {
// workaround for a certain OpenGL implementation lacking GL_ALPHA_TEST
const char *renderer = reinterpret_cast<const char*>(GL.GetString(GL.RENDERER));
if (strstr(renderer, "GC7000"))
use_discard = true;
}
if (use_discard) {
if (shaderinfo.base_material == video::EMT_TRANSPARENT_ALPHA_CHANNEL)
shaders_header << "#define USE_DISCARD 1\n";
@ -660,12 +674,30 @@ ShaderInfo ShaderSource::generateShader(const std::string &name,
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";
if (enable_waving_water) {
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:
case TILE_MATERIAL_LIQUID_TRANSPARENT:
shaders_header << "#define MATERIAL_WAVING_LIQUID 1\n";
break;
default:
shaders_header << "#define MATERIAL_WAVING_LIQUID 0\n";
break;
}
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"))

View file

@ -196,18 +196,33 @@ using CachedStructPixelShaderSetting = CachedStructShaderSetting<T, count, cache
/*
ShaderSource creates and caches shaders.
*/
A "shader" could more precisely be called a "shader material" and comprises
a vertex, fragment and optional geometry shader.
*/
class IShaderSource {
public:
IShaderSource() = default;
virtual ~IShaderSource() = default;
virtual u32 getShaderIdDirect(const std::string &name,
MaterialType material_type, NodeDrawType drawtype = NDT_NORMAL){return 0;}
virtual ShaderInfo getShaderInfo(u32 id){return ShaderInfo();}
/**
* @brief returns information about an existing shader
*
* 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){return 0;}
MaterialType material_type, NodeDrawType drawtype = NDT_NORMAL) = 0;
/**
* Generates or gets a shader for general use.
* @param name name of the shader (directory on disk)
* @param blendAlpha enable alpha blending for this material?
* @return shader ID
*/
virtual u32 getShaderRaw(const std::string &name, bool blendAlpha = false) = 0;
};
class IWritableShaderSource : public IShaderSource {

View file

@ -62,7 +62,7 @@ Sky::Sky(s32 id, RenderingEngine *rendering_engine, ITextureSource *tsrc, IShade
m_materials[0] = baseMaterial();
m_materials[0].MaterialType =
ssrc->getShaderInfo(ssrc->getShader("stars_shader", TILE_MATERIAL_ALPHA)).material;
ssrc->getShaderInfo(ssrc->getShaderRaw("stars_shader", true)).material;
m_materials[1] = baseMaterial();
m_materials[1].MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
@ -661,7 +661,9 @@ void Sky::draw_stars(video::IVideoDriver * driver, float wicked_time_of_day)
return;
m_materials[0].ColorParam = color.toSColor();
auto sky_rotation = core::matrix4().setRotationAxisRadians(2.0f * M_PI * (wicked_time_of_day - 0.25f), v3f(0.0f, 0.0f, 1.0f));
auto day_rotation = core::matrix4().setRotationAxisRadians(2.0f * M_PI * (wicked_time_of_day - 0.25f), v3f(0.0f, 0.0f, 1.0f));
auto orbit_rotation = core::matrix4().setRotationAxisRadians(m_sky_params.body_orbit_tilt * M_PI / 180.0, v3f(1.0f, 0.0f, 0.0f));
auto sky_rotation = orbit_rotation * day_rotation;
auto world_matrix = driver->getTransform(video::ETS_WORLD);
driver->setTransform(video::ETS_WORLD, world_matrix * sky_rotation);
driver->setMaterial(m_materials[0]);
@ -695,13 +697,11 @@ void Sky::place_sky_body(
* day_position: turn the body around the Z axis, to place it depending of the time of the day
*/
{
v3f centrum = getSkyBodyPosition(horizon_position, day_position, m_sky_params.body_orbit_tilt);
v3f untilted_centrum = getSkyBodyPosition(horizon_position, day_position, 0.f);
for (video::S3DVertex &vertex : vertices) {
// Body is directed to -Z (south) by default
vertex.Pos.rotateXZBy(horizon_position);
vertex.Pos.rotateXYBy(day_position);
vertex.Pos += centrum - untilted_centrum;
vertex.Pos.rotateYZBy(m_sky_params.body_orbit_tilt);
}
}

View file

@ -20,6 +20,7 @@ enum MaterialType{
TILE_MATERIAL_WAVING_LIQUID_BASIC,
TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT,
TILE_MATERIAL_WAVING_LIQUID_OPAQUE,
// Note: PLAIN isn't a material actually used by tiles, rather just entities.
TILE_MATERIAL_PLAIN,
TILE_MATERIAL_PLAIN_ALPHA
};

View file

@ -29,6 +29,7 @@
#cmakedefine01 USE_SYSTEM_GMP
#cmakedefine01 USE_SYSTEM_JSONCPP
#cmakedefine01 USE_REDIS
#cmakedefine01 USE_OPENSSL
#cmakedefine01 HAVE_ENDIAN_H
#cmakedefine01 HAVE_STRLCPY
#cmakedefine01 HAVE_MALLOC_TRIM

View file

@ -43,7 +43,7 @@ void PlayerDatabaseFiles::deSerialize(RemotePlayer *p, std::istream &is,
}
try {
sao->setBasePosition(args.getV3F("position"));
sao->setBasePosition(args.getV3F("position").value_or(v3f()));
} catch (SettingNotFoundException &e) {}
try {

View file

@ -78,7 +78,7 @@ void set_default_settings()
settings->setDefault("language", "");
settings->setDefault("name", "");
settings->setDefault("bind_address", "");
settings->setDefault("serverlist_url", "servers.luanti.org");
settings->setDefault("serverlist_url", "https://servers.luanti.org");
// Client
settings->setDefault("address", "");
@ -302,6 +302,7 @@ void set_default_settings()
settings->setDefault("arm_inertia", "true");
settings->setDefault("show_nametag_backgrounds", "true");
settings->setDefault("show_block_bounds_radius_near", "4");
settings->setDefault("transparency_sorting_group_by_buffers", "true");
settings->setDefault("transparency_sorting_distance", "16");
settings->setDefault("enable_minimap", "true");
@ -310,6 +311,7 @@ void set_default_settings()
// Effects
settings->setDefault("enable_post_processing", "true");
settings->setDefault("post_processing_texture_bits", "16");
settings->setDefault("directional_colored_fog", "true");
settings->setDefault("inventory_items_animations", "false");
settings->setDefault("mip_map", "false");
@ -547,6 +549,7 @@ void set_default_settings()
settings->setDefault("keymap_sneak", "KEY_SHIFT");
#endif
settings->setDefault("touch_layout", "");
settings->setDefault("touchscreen_sensitivity", "0.2");
settings->setDefault("touchscreen_threshold", "20");
settings->setDefault("touch_long_tap_delay", "400");
@ -570,8 +573,12 @@ void set_default_settings()
settings->setDefault("active_block_range", "2");
settings->setDefault("viewing_range", "50");
settings->setDefault("leaves_style", "simple");
// Note: OpenGL ES 2.0 is not guaranteed to provide depth textures,
// which we would need for PP.
settings->setDefault("enable_post_processing", "false");
// still set these two settings in case someone wants to enable it
settings->setDefault("debanding", "false");
settings->setDefault("post_processing_texture_bits", "8");
settings->setDefault("curl_verify_cert", "false");
// Apply settings according to screen size

View file

@ -93,7 +93,7 @@ public:
MapEditEventAreaIgnorer(VoxelArea *ignorevariable, const VoxelArea &a):
m_ignorevariable(ignorevariable)
{
if (m_ignorevariable->getVolume() == 0)
if (m_ignorevariable->hasEmptyExtent())
*m_ignorevariable = a;
else
m_ignorevariable = nullptr;
@ -102,7 +102,7 @@ public:
~MapEditEventAreaIgnorer()
{
if (m_ignorevariable) {
assert(m_ignorevariable->getVolume() != 0);
assert(!m_ignorevariable->hasEmptyExtent());
*m_ignorevariable = VoxelArea();
}
}

View file

@ -833,16 +833,51 @@ std::string RemoveRelativePathComponents(std::string path)
std::string AbsolutePath(const std::string &path)
{
#ifdef _WIN32
// handle behavior differences on windows
if (path.empty())
return "";
else if (!PathExists(path))
return "";
char *abs_path = _fullpath(NULL, path.c_str(), MAX_PATH);
#else
char *abs_path = realpath(path.c_str(), NULL);
#endif
if (!abs_path) return "";
if (!abs_path)
return "";
std::string abs_path_str(abs_path);
free(abs_path);
return abs_path_str;
}
std::string AbsolutePathPartial(const std::string &path)
{
if (path.empty())
return "";
// Try to determine absolute path
std::string abs_path = fs::AbsolutePath(path);
if (!abs_path.empty())
return abs_path;
// Remove components until it works
std::string cur_path = path;
std::string removed;
while (abs_path.empty() && !cur_path.empty()) {
std::string component;
cur_path = RemoveLastPathComponent(cur_path, &component);
removed = component + (removed.empty() ? "" : DIR_DELIM + removed);
abs_path = AbsolutePath(cur_path);
}
// If we had a relative path that does not exist, it needs to be joined with cwd
if (cur_path.empty() && !IsPathAbsolute(path))
abs_path = AbsolutePath(".");
// or there's an error
if (abs_path.empty())
return "";
// Put them back together and resolve the remaining relative components
if (!removed.empty())
abs_path.append(DIR_DELIM).append(removed);
return RemoveRelativePathComponents(abs_path);
}
const char *GetFilenameFromPath(const char *path)
{
const char *filename = strrchr(path, DIR_DELIM_CHAR);

View file

@ -131,6 +131,12 @@ std::string RemoveRelativePathComponents(std::string path);
// components and symlinks removed. Returns "" on error.
std::string AbsolutePath(const std::string &path);
// This is a combination of RemoveRelativePathComponents() and AbsolutePath()
// It will resolve symlinks for the leading path components that exist and
// still remove "." and ".." in the rest of the path.
// Returns "" on error.
std::string AbsolutePathPartial(const std::string &path);
// Returns the filename from a path or the entire path if no directory
// delimiter is found.
const char *GetFilenameFromPath(const char *path);

View file

@ -90,16 +90,16 @@ class TernaryOperation: public GettextPluralForm
};
typedef std::pair<GettextPluralForm::Ptr, std::wstring_view> ParserResult;
typedef ParserResult (*Parser)(const size_t, const std::wstring_view &);
typedef ParserResult (*Parser)(const size_t, std::wstring_view);
static ParserResult parse_expr(const size_t nplurals, const std::wstring_view &str);
static ParserResult parse_expr(const size_t nplurals, std::wstring_view str);
template<Parser Parser, template<typename> typename Operator>
static ParserResult reduce_ltr(const size_t nplurals, const ParserResult &res, const wchar_t* pattern)
{
if (!str_starts_with(res.second, pattern))
return ParserResult(nullptr, res.second);
auto next = Parser(nplurals, res.second.substr(std::char_traits<wchar_t>::length(pattern)));
auto next = Parser(nplurals, trim(res.second.substr(std::char_traits<wchar_t>::length(pattern))));
if (!next.first)
return next;
next.first = GettextPluralForm::Ptr(new BinaryOperation<Operator>(res.first, next.first));
@ -123,7 +123,7 @@ static ParserResult reduce_ltr(const size_t nplurals, const ParserResult &res, c
}
template<Parser Parser, template<typename> typename Operator, template<typename> typename... Operators>
static ParserResult parse_ltr(const size_t nplurals, const std::wstring_view &str, const wchar_t** patterns)
static ParserResult parse_ltr(const size_t nplurals, std::wstring_view str, const wchar_t** patterns)
{
auto &&pres = Parser(nplurals, str);
if (!pres.first)
@ -139,7 +139,7 @@ static ParserResult parse_ltr(const size_t nplurals, const std::wstring_view &st
return pres;
}
static ParserResult parse_atomic(const size_t nplurals, const std::wstring_view &str)
static ParserResult parse_atomic(const size_t nplurals, std::wstring_view str)
{
if (str.empty())
return ParserResult(nullptr, str);
@ -151,7 +151,7 @@ static ParserResult parse_atomic(const size_t nplurals, const std::wstring_view
return ParserResult(new ConstValue(nplurals, val), trim(str.substr(endp-str.data())));
}
static ParserResult parse_parenthesized(const size_t nplurals, const std::wstring_view &str)
static ParserResult parse_parenthesized(const size_t nplurals, std::wstring_view str)
{
if (str.empty())
return ParserResult(nullptr, str);
@ -167,7 +167,7 @@ static ParserResult parse_parenthesized(const size_t nplurals, const std::wstrin
return result;
}
static ParserResult parse_negation(const size_t nplurals, const std::wstring_view &str)
static ParserResult parse_negation(const size_t nplurals, std::wstring_view str)
{
if (str.empty())
return ParserResult(nullptr, str);
@ -179,43 +179,43 @@ static ParserResult parse_negation(const size_t nplurals, const std::wstring_vie
return result;
}
static ParserResult parse_multiplicative(const size_t nplurals, const std::wstring_view &str)
static ParserResult parse_multiplicative(const size_t nplurals, std::wstring_view str)
{
static const wchar_t *patterns[] = { L"*", L"/", L"%" };
return parse_ltr<parse_negation, std::multiplies, std::divides, std::modulus>(nplurals, str, patterns);
}
static ParserResult parse_additive(const size_t nplurals, const std::wstring_view &str)
static ParserResult parse_additive(const size_t nplurals, std::wstring_view str)
{
static const wchar_t *patterns[] = { L"+", L"-" };
return parse_ltr<parse_multiplicative, std::plus, std::minus>(nplurals, str, patterns);
}
static ParserResult parse_comparison(const size_t nplurals, const std::wstring_view &str)
static ParserResult parse_comparison(const size_t nplurals, std::wstring_view str)
{
static const wchar_t *patterns[] = { L"<=", L">=", L"<", L">" };
return parse_ltr<parse_additive, std::less_equal, std::greater_equal, std::less, std::greater>(nplurals, str, patterns);
}
static ParserResult parse_equality(const size_t nplurals, const std::wstring_view &str)
static ParserResult parse_equality(const size_t nplurals, std::wstring_view str)
{
static const wchar_t *patterns[] = { L"==", L"!=" };
return parse_ltr<parse_comparison, std::equal_to, std::not_equal_to>(nplurals, str, patterns);
}
static ParserResult parse_conjunction(const size_t nplurals, const std::wstring_view &str)
static ParserResult parse_conjunction(const size_t nplurals, std::wstring_view str)
{
static const wchar_t *and_pattern[] = { L"&&" };
return parse_ltr<parse_equality, std::logical_and>(nplurals, str, and_pattern);
}
static ParserResult parse_disjunction(const size_t nplurals, const std::wstring_view &str)
static ParserResult parse_disjunction(const size_t nplurals, std::wstring_view str)
{
static const wchar_t *or_pattern[] = { L"||" };
return parse_ltr<parse_conjunction, std::logical_or>(nplurals, str, or_pattern);
}
static ParserResult parse_ternary(const size_t nplurals, const std::wstring_view &str)
static ParserResult parse_ternary(const size_t nplurals, std::wstring_view str)
{
auto pres = parse_disjunction(nplurals, str);
if (pres.second.empty() || pres.second[0] != '?') // no ? :
@ -229,12 +229,12 @@ static ParserResult parse_ternary(const size_t nplurals, const std::wstring_view
return ParserResult(new TernaryOperation(cond, val, pres.first), pres.second);
}
static ParserResult parse_expr(const size_t nplurals, const std::wstring_view &str)
static ParserResult parse_expr(const size_t nplurals, std::wstring_view str)
{
return parse_ternary(nplurals, trim(str));
}
GettextPluralForm::Ptr GettextPluralForm::parse(const size_t nplurals, const std::wstring_view &str)
GettextPluralForm::Ptr GettextPluralForm::parse(const size_t nplurals, std::wstring_view str)
{
if (nplurals == 0)
return nullptr;
@ -244,7 +244,7 @@ GettextPluralForm::Ptr GettextPluralForm::parse(const size_t nplurals, const std
return result.first;
}
GettextPluralForm::Ptr GettextPluralForm::parseHeaderLine(const std::wstring_view &str)
GettextPluralForm::Ptr GettextPluralForm::parseHeaderLine(std::wstring_view str)
{
if (!str_starts_with(str, L"Plural-Forms: nplurals=") || !str_ends_with(str, L";"))
return nullptr;

View file

@ -24,8 +24,8 @@ public:
}
virtual ~GettextPluralForm() {};
static GettextPluralForm::Ptr parse(const size_t nplurals, const std::wstring_view &str);
static GettextPluralForm::Ptr parseHeaderLine(const std::wstring_view &str);
static GettextPluralForm::Ptr parse(const size_t nplurals, std::wstring_view str);
static GettextPluralForm::Ptr parseHeaderLine(std::wstring_view str);
protected:
GettextPluralForm(size_t nplurals): nplurals(nplurals) {};
private:

View file

@ -25,5 +25,7 @@ set(gui_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/modalMenu.cpp
${CMAKE_CURRENT_SOURCE_DIR}/profilergraph.cpp
${CMAKE_CURRENT_SOURCE_DIR}/touchcontrols.cpp
${CMAKE_CURRENT_SOURCE_DIR}/touchscreenlayout.cpp
${CMAKE_CURRENT_SOURCE_DIR}/touchscreeneditor.cpp
PARENT_SCOPE
)

View file

@ -55,7 +55,7 @@ GUIChatConsole::GUIChatConsole(
m_background_color.setGreen(255);
m_background_color.setBlue(255);
} else {
v3f console_color = g_settings->getV3F("console_color");
v3f console_color = g_settings->getV3F("console_color").value_or(v3f());
m_background_color.setRed(clamp_u8(myround(console_color.X)));
m_background_color.setGreen(clamp_u8(myround(console_color.Y)));
m_background_color.setBlue(clamp_u8(myround(console_color.Z)));

View file

@ -30,10 +30,6 @@ GUIEditBoxWithScrollBar::GUIEditBoxWithScrollBar(const wchar_t* text, bool borde
: GUIEditBox(environment, parent, id, rectangle, border, writable),
m_background(true), m_bg_color_used(false), m_tsrc(tsrc)
{
#ifdef _DEBUG
setDebugName("GUIEditBoxWithScrollBar");
#endif
Text = text;

View file

@ -3010,7 +3010,7 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
m_tabheader_upper_edge = 0;
{
v3f formspec_bgcolor = g_settings->getV3F("formspec_fullscreen_bg_color");
v3f formspec_bgcolor = g_settings->getV3F("formspec_fullscreen_bg_color").value_or(v3f());
m_fullscreen_bgcolor = video::SColor(
(u8) clamp_u8(g_settings->getS32("formspec_fullscreen_bg_opacity")),
clamp_u8(myround(formspec_bgcolor.X)),
@ -3610,10 +3610,25 @@ void GUIFormSpecMenu::showTooltip(const std::wstring &text,
// Calculate and set the tooltip position
s32 tooltip_x = m_pointer.X + tooltip_offset_x;
s32 tooltip_y = m_pointer.Y + tooltip_offset_y;
if (tooltip_x + tooltip_width > (s32)screenSize.X)
tooltip_x = (s32)screenSize.X - tooltip_width - m_btn_height;
if (tooltip_y + tooltip_height > (s32)screenSize.Y)
tooltip_y = (s32)screenSize.Y - tooltip_height - m_btn_height;
// Bottom/Left limited positions (if the tooltip is too far out)
s32 tooltip_x_alt = (s32)screenSize.X - tooltip_width - m_btn_height;
s32 tooltip_y_alt = (s32)screenSize.Y - tooltip_height - m_btn_height;
int collision = (tooltip_x_alt < tooltip_x) + 2 * (tooltip_y_alt < tooltip_y);
switch (collision) {
case 1: // x
tooltip_x = tooltip_x_alt;
break;
case 2: // y
tooltip_y = tooltip_y_alt;
break;
case 3: // both
tooltip_x = tooltip_x_alt;
tooltip_y = (s32)screenSize.Y - 2 * tooltip_height - m_btn_height;
break;
default: // OK
break;
}
m_tooltip_element->setRelativePosition(
core::rect<s32>(

View file

@ -1015,10 +1015,6 @@ GUIHyperText::GUIHyperText(const wchar_t *text, IGUIEnvironment *environment,
m_drawer(text, client, environment, tsrc), m_text_scrollpos(0, 0)
{
#ifdef _DEBUG
setDebugName("GUIHyperText");
#endif
IGUISkin *skin = 0;
if (Environment)
skin = Environment->getSkin();

View file

@ -21,6 +21,7 @@ public:
virtual void changeVolume() = 0;
virtual void showOpenURLDialog(const std::string &url) = 0;
virtual void signalKeyConfigChange() = 0;
virtual void touchscreenLayout() = 0;
};
extern gui::IGUIEnvironment *guienv;
@ -133,6 +134,11 @@ public:
keyconfig_changed = true;
}
void touchscreenLayout() override
{
touchscreenlayout_requested = true;
}
void showOpenURLDialog(const std::string &url) override
{
show_open_url_dialog = url;
@ -142,6 +148,7 @@ public:
bool changepassword_requested = false;
bool changevolume_requested = false;
bool keyconfig_requested = false;
bool touchscreenlayout_requested = false;
bool shutdown_requested = false;
bool keyconfig_changed = false;
std::string show_open_url_dialog = "";

View file

@ -5,6 +5,7 @@
// Copyright (C) 2024 grorp, Gregor Parzefall
#include "touchcontrols.h"
#include "touchscreenlayout.h"
#include "gettime.h"
#include "irr_v2d.h"
@ -14,8 +15,10 @@
#include "client/guiscalingfilter.h"
#include "client/keycode.h"
#include "client/renderingengine.h"
#include "client/texturesource.h"
#include "util/numeric.h"
#include "gettext.h"
#include "irr_gui_ptr.h"
#include "IGUIImage.h"
#include "IGUIStaticText.h"
#include "IGUIFont.h"
#include <IrrlichtDevice.h>
@ -26,59 +29,6 @@
TouchControls *g_touchcontrols;
static const char *button_image_names[] = {
"jump_btn.png",
"down.png",
"zoom.png",
"aux1_btn.png",
"overflow_btn.png",
"fly_btn.png",
"noclip_btn.png",
"fast_btn.png",
"debug_btn.png",
"camera_btn.png",
"rangeview_btn.png",
"minimap_btn.png",
"",
"chat_btn.png",
"inventory_btn.png",
"drop_btn.png",
"exit_btn.png",
"joystick_off.png",
"joystick_bg.png",
"joystick_center.png",
};
// compare with GUIKeyChangeMenu::init_keys
static const char *button_titles[] = {
N_("Jump"),
N_("Sneak"),
N_("Zoom"),
N_("Aux1"),
N_("Overflow menu"),
N_("Toggle fly"),
N_("Toggle noclip"),
N_("Toggle fast"),
N_("Toggle debug"),
N_("Change camera"),
N_("Range select"),
N_("Toggle minimap"),
N_("Toggle chat log"),
N_("Chat"),
N_("Inventory"),
N_("Drop"),
N_("Exit"),
N_("Joystick"),
N_("Joystick"),
N_("Joystick"),
};
static void load_button_texture(IGUIImage *gui_button, const std::string &path,
const recti &button_rect, ISimpleTextureSource *tsrc, video::IVideoDriver *driver)
{
@ -268,10 +218,22 @@ TouchControls::TouchControls(IrrlichtDevice *device, ISimpleTextureSource *tsrc)
m_long_tap_delay = g_settings->getU16("touch_long_tap_delay");
m_fixed_joystick = g_settings->getBool("fixed_virtual_joystick");
m_joystick_triggers_aux1 = g_settings->getBool("virtual_joystick_triggers_aux1");
m_screensize = m_device->getVideoDriver()->getScreenSize();
m_button_size = MYMIN(m_screensize.Y / 4.5f,
RenderingEngine::getDisplayDensity() * 65.0f *
g_settings->getFloat("hud_scaling"));
m_button_size = ButtonLayout::getButtonSize(m_screensize);
applyLayout(ButtonLayout::loadFromSettings());
}
void TouchControls::applyLayout(const ButtonLayout &layout)
{
m_layout = layout;
m_buttons.clear();
m_overflow_btn = nullptr;
m_overflow_bg = nullptr;
m_overflow_buttons.clear();
m_overflow_button_titles.clear();
m_overflow_button_rects.clear();
// Initialize joystick display "button".
// Joystick is placed on the bottom left of screen.
@ -298,47 +260,21 @@ TouchControls::TouchControls(IrrlichtDevice *device, ISimpleTextureSource *tsrc)
m_joystick_btn_center = grab_gui_element<IGUIImage>(makeButtonDirect(joystick_center_id,
recti(0, 0, m_button_size, m_button_size), false));
// init jump button
addButton(m_buttons, jump_id, button_image_names[jump_id],
recti(m_screensize.X - 1.75f * m_button_size,
m_screensize.Y - m_button_size,
m_screensize.X - 0.25f * m_button_size,
m_screensize.Y));
for (const auto &[id, meta] : m_layout.layout) {
if (!mayAddButton(id))
continue;
// init sneak button
addButton(m_buttons, sneak_id, button_image_names[sneak_id],
recti(m_screensize.X - 3.25f * m_button_size,
m_screensize.Y - m_button_size,
m_screensize.X - 1.75f * m_button_size,
m_screensize.Y));
// init zoom button
addButton(m_buttons, zoom_id, button_image_names[zoom_id],
recti(m_screensize.X - 1.25f * m_button_size,
m_screensize.Y - 4 * m_button_size,
m_screensize.X - 0.25f * m_button_size,
m_screensize.Y - 3 * m_button_size));
// init aux1 button
if (!m_joystick_triggers_aux1)
addButton(m_buttons, aux1_id, button_image_names[aux1_id],
recti(m_screensize.X - 1.25f * m_button_size,
m_screensize.Y - 2.5f * m_button_size,
m_screensize.X - 0.25f * m_button_size,
m_screensize.Y - 1.5f * m_button_size));
// init overflow button
m_overflow_btn = grab_gui_element<IGUIImage>(makeButtonDirect(overflow_id,
recti(m_screensize.X - 1.25f * m_button_size,
m_screensize.Y - 5.5f * m_button_size,
m_screensize.X - 0.25f * m_button_size,
m_screensize.Y - 4.5f * m_button_size), true));
const static touch_gui_button_id overflow_buttons[] {
chat_id, inventory_id, drop_id, exit_id,
fly_id, noclip_id, fast_id, debug_id, camera_id, range_id, minimap_id,
toggle_chat_id,
};
recti rect = m_layout.getRect(id, m_screensize, m_button_size, m_texturesource);
if (id == toggle_chat_id)
// Chat is shown by default, so chat_hide_btn.png is shown first.
addToggleButton(m_buttons, id, "chat_hide_btn.png",
"chat_show_btn.png", rect, true);
else if (id == overflow_id)
m_overflow_btn = grab_gui_element<IGUIImage>(
makeButtonDirect(id, rect, true));
else
addButton(m_buttons, id, button_image_names[id], rect, true);
}
IGUIStaticText *background = m_guienv->addStaticText(L"",
recti(v2s32(0, 0), dimension2du(m_screensize)));
@ -346,32 +282,17 @@ TouchControls::TouchControls(IrrlichtDevice *device, ISimpleTextureSource *tsrc)
background->setVisible(false);
m_overflow_bg = grab_gui_element<IGUIStaticText>(background);
s32 cols = 4;
s32 rows = 3;
f32 screen_aspect = (f32)m_screensize.X / (f32)m_screensize.Y;
while ((s32)ARRLEN(overflow_buttons) > cols * rows) {
f32 aspect = (f32)cols / (f32)rows;
if (aspect > screen_aspect)
rows++;
else
cols++;
}
v2s32 size(m_button_size, m_button_size);
v2s32 spacing(m_screensize.X / (cols + 1), m_screensize.Y / (rows + 1));
v2s32 pos(spacing);
for (auto id : overflow_buttons) {
if (id_to_keycode(id) == KEY_UNKNOWN)
continue;
recti rect(pos - size / 2, dimension2du(size.X, size.Y));
if (rect.LowerRightCorner.X > (s32)m_screensize.X) {
pos.X = spacing.X;
pos.Y += spacing.Y;
rect = recti(pos - size / 2, dimension2du(size.X, size.Y));
}
auto overflow_buttons = m_layout.getMissingButtons();
overflow_buttons.erase(std::remove_if(
overflow_buttons.begin(), overflow_buttons.end(),
[&](touch_gui_button_id id) {
// There's no sense in adding the overflow button to the overflow
// menu (also, it's impossible since it doesn't have a keycode).
return !mayAddButton(id) || id == overflow_id;
}), overflow_buttons.end());
layout_button_grid(m_screensize, m_texturesource, overflow_buttons,
[&] (touch_gui_button_id id, v2s32 pos, recti rect) {
if (id == toggle_chat_id)
// Chat is shown by default, so chat_hide_btn.png is shown first.
addToggleButton(m_overflow_buttons, id, "chat_hide_btn.png",
@ -379,27 +300,23 @@ TouchControls::TouchControls(IrrlichtDevice *device, ISimpleTextureSource *tsrc)
else
addButton(m_overflow_buttons, id, button_image_names[id], rect, false);
std::wstring str = wstrgettext(button_titles[id]);
IGUIStaticText *text = m_guienv->addStaticText(str.c_str(), recti());
IGUIFont *font = text->getActiveFont();
dimension2du dim = font->getDimension(str.c_str());
dim = dimension2du(dim.Width * 1.25f, dim.Height * 1.25f); // avoid clipping
text->setRelativePosition(recti(pos.X - dim.Width / 2, pos.Y + size.Y / 2,
pos.X + dim.Width / 2, pos.Y + size.Y / 2 + dim.Height));
text->setTextAlignment(EGUIA_CENTER, EGUIA_UPPERLEFT);
IGUIStaticText *text = m_guienv->addStaticText(L"", recti());
make_button_grid_title(text, id, pos, rect);
text->setVisible(false);
m_overflow_button_titles.push_back(grab_gui_element<IGUIStaticText>(text));
rect.addInternalPoint(text->getRelativePosition().UpperLeftCorner);
rect.addInternalPoint(text->getRelativePosition().LowerRightCorner);
m_overflow_button_rects.push_back(rect);
pos.X += spacing.X;
}
});
m_status_text = grab_gui_element<IGUIStaticText>(
m_guienv->addStaticText(L"", recti(), false, false));
m_status_text->setVisible(false);
// applyLayout can be called at any time, also e.g. while the overflow menu
// is open, so this is necessary to restore correct visibility.
updateVisibility();
}
TouchControls::~TouchControls()
@ -407,6 +324,17 @@ TouchControls::~TouchControls()
releaseAll();
}
bool TouchControls::mayAddButton(touch_gui_button_id id)
{
if (!ButtonLayout::isButtonAllowed(id))
return false;
if (id == aux1_id && m_joystick_triggers_aux1)
return false;
if (id != overflow_id && id_to_keycode(id) == KEY_UNKNOWN)
return false;
return true;
}
void TouchControls::addButton(std::vector<button_info> &buttons, touch_gui_button_id id,
const std::string &image, const recti &rect, bool visible)
{
@ -701,6 +629,15 @@ void TouchControls::applyJoystickStatus()
void TouchControls::step(float dtime)
{
v2u32 screensize = m_device->getVideoDriver()->getScreenSize();
s32 button_size = ButtonLayout::getButtonSize(screensize);
if (m_screensize != screensize || m_button_size != button_size) {
m_screensize = screensize;
m_button_size = button_size;
applyLayout(m_layout);
}
// simulate keyboard repeats
buttons_step(m_buttons, dtime, m_device->getVideoDriver(), m_receiver, m_texturesource);
buttons_step(m_overflow_buttons, dtime, m_device->getVideoDriver(), m_receiver, m_texturesource);

View file

@ -5,11 +5,8 @@
#pragma once
#include "IGUIStaticText.h"
#include "irrlichttypes.h"
#include <IEventReceiver.h>
#include <IGUIImage.h>
#include <IGUIEnvironment.h>
#include "IEventReceiver.h"
#include <memory>
#include <optional>
@ -17,41 +14,29 @@
#include <vector>
#include "itemdef.h"
#include "client/game.h"
#include "touchscreenlayout.h"
#include "util/basic_macros.h"
#include "client/texturesource.h"
namespace irr
{
class IrrlichtDevice;
namespace gui
{
class IGUIEnvironment;
class IGUIImage;
class IGUIStaticText;
}
namespace video
{
class IVideoDriver;
}
}
class ISimpleTextureSource;
using namespace irr::core;
using namespace irr::gui;
// We cannot use irr_ptr for Irrlicht GUI elements we own.
// Option 1: Pass IGUIElement* returned by IGUIEnvironment::add* into irr_ptr
// constructor.
// -> We steal the reference owned by IGUIEnvironment and drop it later,
// causing the IGUIElement to be deleted while IGUIEnvironment still
// references it.
// Option 2: Pass IGUIElement* returned by IGUIEnvironment::add* into irr_ptr::grab.
// -> We add another reference and drop it later, but since IGUIEnvironment
// still references the IGUIElement, it is never deleted.
// To make IGUIEnvironment drop its reference to the IGUIElement, we have to call
// IGUIElement::remove, so that's what we'll do.
template <typename T>
std::shared_ptr<T> grab_gui_element(T *element)
{
static_assert(std::is_base_of_v<IGUIElement, T>,
"grab_gui_element only works for IGUIElement");
return std::shared_ptr<T>(element, [](T *e) {
e->remove();
});
}
enum class TapState
{
None,
@ -59,36 +44,6 @@ enum class TapState
LongTap,
};
enum touch_gui_button_id
{
jump_id = 0,
sneak_id,
zoom_id,
aux1_id,
overflow_id,
// usually in the "settings bar"
fly_id,
noclip_id,
fast_id,
debug_id,
camera_id,
range_id,
minimap_id,
toggle_chat_id,
// usually in the "rare controls bar"
chat_id,
inventory_id,
drop_id,
exit_id,
// the joystick
joystick_off_id,
joystick_bg_id,
joystick_center_id,
};
#define BUTTON_REPEAT_DELAY 0.5f
#define BUTTON_REPEAT_INTERVAL 0.333f
@ -168,6 +123,9 @@ public:
bool isStatusTextOverriden() { return m_overflow_open; }
IGUIStaticText *getStatusText() { return m_status_text.get(); }
ButtonLayout getLayout() { return m_layout; }
void applyLayout(const ButtonLayout &layout);
private:
IrrlichtDevice *m_device = nullptr;
IGUIEnvironment *m_guienv = nullptr;
@ -233,13 +191,14 @@ private:
void releaseAll();
// initialize a button
bool mayAddButton(touch_gui_button_id id);
void addButton(std::vector<button_info> &buttons,
touch_gui_button_id id, const std::string &image,
const recti &rect, bool visible=true);
const recti &rect, bool visible);
void addToggleButton(std::vector<button_info> &buttons,
touch_gui_button_id id,
const std::string &image_1, const std::string &image_2,
const recti &rect, bool visible=true);
const recti &rect, bool visible);
IGUIImage *makeButtonDirect(touch_gui_button_id id,
const recti &rect, bool visible);
@ -268,6 +227,8 @@ private:
bool m_place_pressed = false;
u64 m_place_pressed_until = 0;
ButtonLayout m_layout;
};
extern TouchControls *g_touchcontrols;

View file

@ -0,0 +1,404 @@
// Luanti
// SPDX-License-Identifier: LGPL-2.1-or-later
// Copyright (C) 2024 grorp, Gregor Parzefall <grorp@posteo.de>
#include "touchscreeneditor.h"
#include "touchcontrols.h"
#include "touchscreenlayout.h"
#include "client/renderingengine.h"
#include "gettext.h"
#include "irr_gui_ptr.h"
#include "settings.h"
#include "IGUIButton.h"
#include "IGUIFont.h"
#include "IGUIImage.h"
#include "IGUIStaticText.h"
GUITouchscreenLayout::GUITouchscreenLayout(gui::IGUIEnvironment* env,
gui::IGUIElement* parent, s32 id,
IMenuManager *menumgr, ISimpleTextureSource *tsrc
):
GUIModalMenu(env, parent, id, menumgr),
m_tsrc(tsrc)
{
if (g_touchcontrols)
m_layout = g_touchcontrols->getLayout();
else
m_layout = ButtonLayout::loadFromSettings();
m_gui_help_text = grab_gui_element<IGUIStaticText>(Environment->addStaticText(
L"", core::recti(), false, false, this, -1));
m_gui_help_text->setTextAlignment(EGUIA_CENTER, EGUIA_CENTER);
m_gui_add_btn = grab_gui_element<IGUIButton>(Environment->addButton(
core::recti(), this, -1, wstrgettext("Add button").c_str()));
m_gui_reset_btn = grab_gui_element<IGUIButton>(Environment->addButton(
core::recti(), this, -1, wstrgettext("Reset").c_str()));
m_gui_done_btn = grab_gui_element<IGUIButton>(Environment->addButton(
core::recti(), this, -1, wstrgettext("Done").c_str()));
m_gui_remove_btn = grab_gui_element<IGUIButton>(Environment->addButton(
core::recti(), this, -1, wstrgettext("Remove").c_str()));
}
GUITouchscreenLayout::~GUITouchscreenLayout()
{
ButtonLayout::clearTextureCache();
}
void GUITouchscreenLayout::regenerateGui(v2u32 screensize)
{
DesiredRect = core::recti(0, 0, screensize.X, screensize.Y);
recalculateAbsolutePosition(false);
s32 button_size = ButtonLayout::getButtonSize(screensize);
if (m_last_screensize != screensize || m_button_size != button_size) {
m_last_screensize = screensize;
m_button_size = button_size;
// Prevent interpolation when the layout scales.
clearGUIImages();
}
// Discard invalid selection. May happen when...
// 1. a button is removed.
// 2. adding a button fails and it disappears from the layout again.
if (m_selected_btn != touch_gui_button_id_END &&
m_layout.layout.count(m_selected_btn) == 0)
m_selected_btn = touch_gui_button_id_END;
if (m_mode == Mode::Add)
regenerateGUIImagesAddMode(screensize);
else
regenerateGUIImagesRegular(screensize);
regenerateMenu(screensize);
}
void GUITouchscreenLayout::clearGUIImages()
{
m_gui_images.clear();
m_gui_images_target_pos.clear();
m_add_layout.layout.clear();
m_add_button_titles.clear();
}
void GUITouchscreenLayout::regenerateGUIImagesRegular(v2u32 screensize)
{
assert(m_mode != Mode::Add);
auto old_gui_images = m_gui_images;
clearGUIImages();
for (const auto &[btn, meta] : m_layout.layout) {
core::recti rect = m_layout.getRect(btn, screensize, m_button_size, m_tsrc);
std::shared_ptr<IGUIImage> img;
if (old_gui_images.count(btn) > 0) {
img = old_gui_images.at(btn);
// Update size, keep position. Position is interpolated in interpolateGUIImages.
img->setRelativePosition(core::recti(
img->getRelativePosition().UpperLeftCorner, rect.getSize()));
} else {
img = grab_gui_element<IGUIImage>(Environment->addImage(rect, this, -1));
img->setImage(ButtonLayout::getTexture(btn, m_tsrc));
img->setScaleImage(true);
}
m_gui_images[btn] = img;
m_gui_images_target_pos[btn] = rect.UpperLeftCorner;
}
}
void GUITouchscreenLayout::regenerateGUIImagesAddMode(v2u32 screensize)
{
assert(m_mode == Mode::Add);
clearGUIImages();
auto missing_buttons = m_layout.getMissingButtons();
layout_button_grid(screensize, m_tsrc, missing_buttons,
[&] (touch_gui_button_id btn, v2s32 pos, core::recti rect) {
auto img = grab_gui_element<IGUIImage>(Environment->addImage(rect, this, -1));
img->setImage(ButtonLayout::getTexture(btn, m_tsrc));
img->setScaleImage(true);
m_gui_images[btn] = img;
ButtonMeta meta;
meta.setPos(pos, screensize, m_button_size);
m_add_layout.layout[btn] = meta;
IGUIStaticText *text = Environment->addStaticText(L"", core::recti(),
false, false,this, -1);
make_button_grid_title(text, btn, pos, rect);
m_add_button_titles.push_back(grab_gui_element<IGUIStaticText>(text));
});
}
void GUITouchscreenLayout::interpolateGUIImages()
{
if (m_mode == Mode::Add)
return;
for (auto &[btn, gui_image] : m_gui_images) {
bool interpolate = m_mode != Mode::Dragging || m_selected_btn != btn;
v2s32 cur_pos_int = gui_image->getRelativePosition().UpperLeftCorner;
v2s32 tgt_pos_int = m_gui_images_target_pos.at(btn);
v2f cur_pos(cur_pos_int.X, cur_pos_int.Y);
v2f tgt_pos(tgt_pos_int.X, tgt_pos_int.Y);
if (interpolate && cur_pos.getDistanceFrom(tgt_pos) > 2.0f) {
v2f pos = cur_pos.getInterpolated(tgt_pos, 0.5f);
gui_image->setRelativePosition(v2s32(core::round32(pos.X), core::round32(pos.Y)));
} else {
gui_image->setRelativePosition(tgt_pos_int);
}
}
}
static void layout_menu_row(v2u32 screensize,
const std::vector<std::shared_ptr<IGUIButton>> &row,
const std::vector<std::shared_ptr<IGUIButton>> &full_row, bool bottom)
{
s32 spacing = RenderingEngine::getDisplayDensity() * 4.0f;
s32 btn_w = 0;
s32 btn_h = 0;
for (const auto &btn : full_row) {
IGUIFont *font = btn->getActiveFont();
core::dimension2du dim = font->getDimension(btn->getText());
btn_w = std::max(btn_w, (s32)(dim.Width * 1.5f));
btn_h = std::max(btn_h, (s32)(dim.Height * 2.5f));
}
const s32 row_width = ((btn_w + spacing) * row.size()) - spacing;
s32 x = screensize.X / 2 - row_width / 2;
const s32 y = bottom ? screensize.Y - spacing - btn_h : spacing;
for (const auto &btn : row) {
btn->setRelativePosition(core::recti(
v2s32(x, y), core::dimension2du(btn_w, btn_h)));
x += btn_w + spacing;
}
}
void GUITouchscreenLayout::regenerateMenu(v2u32 screensize)
{
bool have_selection = m_selected_btn != touch_gui_button_id_END;
if (m_mode == Mode::Add)
m_gui_help_text->setText(wstrgettext("Start dragging a button to add. Tap outside to cancel.").c_str());
else if (!have_selection)
m_gui_help_text->setText(wstrgettext("Tap a button to select it. Drag a button to move it.").c_str());
else
m_gui_help_text->setText(wstrgettext("Tap outside to deselect.").c_str());
IGUIFont *font = m_gui_help_text->getActiveFont();
core::dimension2du dim = font->getDimension(m_gui_help_text->getText());
s32 height = dim.Height * 2.5f;
s32 pos_y = (m_mode == Mode::Add || have_selection) ? 0 : screensize.Y - height;
m_gui_help_text->setRelativePosition(core::recti(
v2s32(0, pos_y),
core::dimension2du(screensize.X, height)));
bool normal_buttons_visible = m_mode != Mode::Add && !have_selection;
bool add_visible = normal_buttons_visible && !m_layout.getMissingButtons().empty();
m_gui_add_btn->setVisible(add_visible);
m_gui_reset_btn->setVisible(normal_buttons_visible);
m_gui_done_btn->setVisible(normal_buttons_visible);
if (normal_buttons_visible) {
std::vector row1{m_gui_add_btn, m_gui_reset_btn, m_gui_done_btn};
if (add_visible) {
layout_menu_row(screensize, row1, row1, false);
} else {
std::vector row1_reduced{m_gui_reset_btn, m_gui_done_btn};
layout_menu_row(screensize, row1_reduced, row1, false);
}
}
bool remove_visible = m_mode != Mode::Add && have_selection &&
!ButtonLayout::isButtonRequired(m_selected_btn);
m_gui_remove_btn->setVisible(remove_visible);
if (remove_visible) {
std::vector row2{m_gui_remove_btn};
layout_menu_row(screensize, row2, row2, true);
}
}
void GUITouchscreenLayout::drawMenu()
{
video::IVideoDriver *driver = Environment->getVideoDriver();
video::SColor bgcolor(140, 0, 0, 0);
video::SColor selection_color(255, 128, 128, 128);
video::SColor error_color(255, 255, 0, 0);
driver->draw2DRectangle(bgcolor, AbsoluteRect, &AbsoluteClippingRect);
// Done here instead of in OnPostRender to avoid drag&drop lagging behind
// input by one frame.
// Must be done before drawing selection rectangle.
interpolateGUIImages();
bool draw_selection = m_gui_images.count(m_selected_btn) > 0;
if (draw_selection)
driver->draw2DRectangle(selection_color,
m_gui_images.at(m_selected_btn)->getAbsolutePosition(),
&AbsoluteClippingRect);
if (m_mode == Mode::Dragging) {
for (const auto &rect : m_error_rects)
driver->draw2DRectangle(error_color, rect, &AbsoluteClippingRect);
}
IGUIElement::draw();
}
void GUITouchscreenLayout::updateDragState(v2u32 screensize, v2s32 mouse_movement)
{
assert(m_mode == Mode::Dragging);
core::recti rect = m_layout.getRect(m_selected_btn, screensize, m_button_size, m_tsrc);
rect += mouse_movement;
rect.constrainTo(core::recti(v2s32(0, 0), core::dimension2du(screensize)));
ButtonMeta &meta = m_layout.layout.at(m_selected_btn);
meta.setPos(rect.getCenter(), screensize, m_button_size);
rect = m_layout.getRect(m_selected_btn, screensize, m_button_size, m_tsrc);
m_error_rects.clear();
for (const auto &[other_btn, other_meta] : m_layout.layout) {
if (other_btn == m_selected_btn)
continue;
core::recti other_rect = m_layout.getRect(other_btn, screensize, m_button_size, m_tsrc);
if (other_rect.isRectCollided(rect))
m_error_rects.push_back(other_rect);
}
if (m_error_rects.empty())
m_last_good_layout = m_layout;
}
bool GUITouchscreenLayout::OnEvent(const SEvent& event)
{
if (event.EventType == EET_KEY_INPUT_EVENT) {
if (event.KeyInput.Key == KEY_ESCAPE && event.KeyInput.PressedDown) {
quitMenu();
return true;
}
}
core::dimension2du screensize = Environment->getVideoDriver()->getScreenSize();
if (event.EventType == EET_MOUSE_INPUT_EVENT) {
v2s32 mouse_pos = v2s32(event.MouseInput.X, event.MouseInput.Y);
switch (event.MouseInput.Event) {
case EMIE_LMOUSE_PRESSED_DOWN: {
m_mouse_down = true;
m_last_mouse_pos = mouse_pos;
IGUIElement *el = Environment->getRootGUIElement()->getElementFromPoint(mouse_pos);
// Clicking on nothing deselects.
m_selected_btn = touch_gui_button_id_END;
for (const auto &[btn, gui_image] : m_gui_images) {
if (el == gui_image.get()) {
m_selected_btn = btn;
break;
}
}
if (m_mode == Mode::Add) {
if (m_selected_btn != touch_gui_button_id_END) {
m_mode = Mode::Dragging;
m_last_good_layout = m_layout;
m_layout.layout[m_selected_btn] = m_add_layout.layout.at(m_selected_btn);
updateDragState(screensize, v2s32(0, 0));
} else {
// Clicking on nothing quits add mode without adding a button.
m_mode = Mode::Default;
}
}
regenerateGui(screensize);
return true;
}
case EMIE_MOUSE_MOVED: {
if (m_mouse_down && m_selected_btn != touch_gui_button_id_END) {
if (m_mode != Mode::Dragging) {
m_mode = Mode::Dragging;
m_last_good_layout = m_layout;
}
updateDragState(screensize, mouse_pos - m_last_mouse_pos);
regenerateGui(screensize);
}
m_last_mouse_pos = mouse_pos;
return true;
}
case EMIE_LMOUSE_LEFT_UP: {
m_mouse_down = false;
if (m_mode == Mode::Dragging) {
m_mode = Mode::Default;
if (!m_error_rects.empty())
m_layout = m_last_good_layout;
regenerateGui(screensize);
}
return true;
}
default:
break;
}
}
if (event.EventType == EET_GUI_EVENT) {
switch (event.GUIEvent.EventType) {
case EGET_BUTTON_CLICKED: {
if (event.GUIEvent.Caller == m_gui_add_btn.get()) {
m_mode = Mode::Add;
regenerateGui(screensize);
return true;
}
if (event.GUIEvent.Caller == m_gui_reset_btn.get()) {
m_layout = ButtonLayout::predefined;
regenerateGui(screensize);
return true;
}
if (event.GUIEvent.Caller == m_gui_done_btn.get()) {
if (g_touchcontrols)
g_touchcontrols->applyLayout(m_layout);
std::ostringstream oss;
m_layout.serializeJson(oss);
g_settings->set("touch_layout", oss.str());
quitMenu();
return true;
}
if (event.GUIEvent.Caller == m_gui_remove_btn.get()) {
m_layout.layout.erase(m_selected_btn);
regenerateGui(screensize);
return true;
}
break;
}
default:
break;
}
}
return Parent ? Parent->OnEvent(event) : false;
}

View file

@ -0,0 +1,81 @@
// Luanti
// SPDX-License-Identifier: LGPL-2.1-or-later
// Copyright (C) 2024 grorp, Gregor Parzefall <grorp@posteo.de>
#pragma once
#include "touchscreenlayout.h"
#include "modalMenu.h"
#include <memory>
#include <unordered_map>
class ISimpleTextureSource;
namespace irr::gui
{
class IGUIImage;
}
class GUITouchscreenLayout : public GUIModalMenu
{
public:
GUITouchscreenLayout(gui::IGUIEnvironment* env,
gui::IGUIElement* parent, s32 id,
IMenuManager *menumgr, ISimpleTextureSource *tsrc);
~GUITouchscreenLayout();
void regenerateGui(v2u32 screensize);
void drawMenu();
bool OnEvent(const SEvent& event);
protected:
std::wstring getLabelByID(s32 id) { return L""; }
std::string getNameByID(s32 id) { return ""; }
private:
ISimpleTextureSource *m_tsrc;
ButtonLayout m_layout;
v2u32 m_last_screensize;
s32 m_button_size;
enum class Mode {
Default,
Dragging,
Add,
};
Mode m_mode = Mode::Default;
std::unordered_map<touch_gui_button_id, std::shared_ptr<gui::IGUIImage>> m_gui_images;
// unused if m_mode == Mode::Add
std::unordered_map<touch_gui_button_id, v2s32> m_gui_images_target_pos;
void clearGUIImages();
void regenerateGUIImagesRegular(v2u32 screensize);
void regenerateGUIImagesAddMode(v2u32 screensize);
void interpolateGUIImages();
// interaction state
bool m_mouse_down = false;
v2s32 m_last_mouse_pos;
touch_gui_button_id m_selected_btn = touch_gui_button_id_END;
// dragging
ButtonLayout m_last_good_layout;
std::vector<core::recti> m_error_rects;
void updateDragState(v2u32 screensize, v2s32 mouse_movement);
// add mode
ButtonLayout m_add_layout;
std::vector<std::shared_ptr<gui::IGUIStaticText>> m_add_button_titles;
// Menu GUI elements
std::shared_ptr<gui::IGUIStaticText> m_gui_help_text;
std::shared_ptr<gui::IGUIButton> m_gui_add_btn;
std::shared_ptr<gui::IGUIButton> m_gui_reset_btn;
std::shared_ptr<gui::IGUIButton> m_gui_done_btn;
std::shared_ptr<gui::IGUIButton> m_gui_remove_btn;
void regenerateMenu(v2u32 screensize);
};

View file

@ -0,0 +1,345 @@
// Luanti
// SPDX-License-Identifier: LGPL-2.1-or-later
// Copyright (C) 2024 grorp, Gregor Parzefall <grorp@posteo.de>
#include "touchscreenlayout.h"
#include "client/renderingengine.h"
#include "client/texturesource.h"
#include "convert_json.h"
#include "gettext.h"
#include "settings.h"
#include <json/json.h>
#include "IGUIFont.h"
#include "IGUIStaticText.h"
const char *button_names[] = {
"jump",
"sneak",
"zoom",
"aux1",
"overflow",
"chat",
"inventory",
"drop",
"exit",
"fly",
"fast",
"noclip",
"debug",
"camera",
"range",
"minimap",
"toggle_chat",
"joystick_off",
"joystick_bg",
"joystick_center",
};
// compare with GUIKeyChangeMenu::init_keys
const char *button_titles[] = {
N_("Jump"),
N_("Sneak"),
N_("Zoom"),
N_("Aux1"),
N_("Overflow menu"),
N_("Chat"),
N_("Inventory"),
N_("Drop"),
N_("Exit"),
N_("Toggle fly"),
N_("Toggle fast"),
N_("Toggle noclip"),
N_("Toggle debug"),
N_("Change camera"),
N_("Range select"),
N_("Toggle minimap"),
N_("Toggle chat log"),
N_("Joystick"),
N_("Joystick"),
N_("Joystick"),
};
const char *button_image_names[] = {
"jump_btn.png",
"down.png",
"zoom.png",
"aux1_btn.png",
"overflow_btn.png",
"chat_btn.png",
"inventory_btn.png",
"drop_btn.png",
"exit_btn.png",
"fly_btn.png",
"fast_btn.png",
"noclip_btn.png",
"debug_btn.png",
"camera_btn.png",
"rangeview_btn.png",
"minimap_btn.png",
// toggle button: switches between "chat_hide_btn.png" and "chat_show_btn.png"
"chat_hide_btn.png",
"joystick_off.png",
"joystick_bg.png",
"joystick_center.png",
};
v2s32 ButtonMeta::getPos(v2u32 screensize, s32 button_size) const
{
return v2s32((position.X * screensize.X) + (offset.X * button_size),
(position.Y * screensize.Y) + (offset.Y * button_size));
}
void ButtonMeta::setPos(v2s32 pos, v2u32 screensize, s32 button_size)
{
v2s32 third(screensize.X / 3, screensize.Y / 3);
if (pos.X < third.X)
position.X = 0.0f;
else if (pos.X < 2 * third.X)
position.X = 0.5f;
else
position.X = 1.0f;
if (pos.Y < third.Y)
position.Y = 0.0f;
else if (pos.Y < 2 * third.Y)
position.Y = 0.5f;
else
position.Y = 1.0f;
offset.X = (pos.X - (position.X * screensize.X)) / button_size;
offset.Y = (pos.Y - (position.Y * screensize.Y)) / button_size;
}
bool ButtonLayout::isButtonAllowed(touch_gui_button_id id)
{
return id != joystick_off_id && id != joystick_bg_id && id != joystick_center_id &&
id != touch_gui_button_id_END;
}
bool ButtonLayout::isButtonRequired(touch_gui_button_id id)
{
return id == overflow_id;
}
s32 ButtonLayout::getButtonSize(v2u32 screensize)
{
return std::min(screensize.Y / 4.5f,
RenderingEngine::getDisplayDensity() * 65.0f *
g_settings->getFloat("hud_scaling"));
}
const ButtonLayout ButtonLayout::predefined {{
{jump_id, {
v2f(1.0f, 1.0f),
v2f(-1.0f, -0.5f),
}},
{sneak_id, {
v2f(1.0f, 1.0f),
v2f(-2.5f, -0.5f),
}},
{zoom_id, {
v2f(1.0f, 1.0f),
v2f(-0.75f, -3.5f),
}},
{aux1_id, {
v2f(1.0f, 1.0f),
v2f(-0.75f, -2.0f),
}},
{overflow_id, {
v2f(1.0f, 1.0f),
v2f(-0.75f, -5.0f),
}},
}};
ButtonLayout ButtonLayout::loadFromSettings()
{
bool restored = false;
ButtonLayout layout;
std::string str = g_settings->get("touch_layout");
if (!str.empty()) {
std::istringstream iss(str);
try {
layout.deserializeJson(iss);
restored = true;
} catch (const Json::Exception &e) {
warningstream << "Could not parse touchscreen layout: " << e.what() << std::endl;
}
}
if (!restored)
return predefined;
return layout;
}
std::unordered_map<touch_gui_button_id, irr_ptr<video::ITexture>> ButtonLayout::texture_cache;
video::ITexture *ButtonLayout::getTexture(touch_gui_button_id btn, ISimpleTextureSource *tsrc)
{
if (texture_cache.count(btn) > 0)
return texture_cache.at(btn).get();
video::ITexture *tex = tsrc->getTexture(button_image_names[btn]);
if (!tex)
// necessary in the mainmenu
tex = tsrc->getTexture(porting::path_share + "/textures/base/pack/" +
button_image_names[btn]);
irr_ptr<video::ITexture> ptr;
ptr.grab(tex);
texture_cache[btn] = ptr;
return tex;
}
void ButtonLayout::clearTextureCache()
{
texture_cache.clear();
}
core::recti ButtonLayout::getRect(touch_gui_button_id btn,
v2u32 screensize, s32 button_size, ISimpleTextureSource *tsrc)
{
const ButtonMeta &meta = layout.at(btn);
v2s32 pos = meta.getPos(screensize, button_size);
v2u32 orig_size = getTexture(btn, tsrc)->getOriginalSize();
v2s32 size((button_size * orig_size.X) / orig_size.Y, button_size);
return core::recti(pos - size / 2, core::dimension2di(size));
}
std::vector<touch_gui_button_id> ButtonLayout::getMissingButtons()
{
std::vector<touch_gui_button_id> missing_buttons;
for (u8 i = 0; i < touch_gui_button_id_END; i++) {
touch_gui_button_id btn = (touch_gui_button_id)i;
if (isButtonAllowed(btn) && layout.count(btn) == 0)
missing_buttons.push_back(btn);
}
return missing_buttons;
}
void ButtonLayout::serializeJson(std::ostream &os) const
{
Json::Value root = Json::objectValue;
root["layout"] = Json::objectValue;
for (const auto &[id, meta] : layout) {
Json::Value button = Json::objectValue;
button["position_x"] = meta.position.X;
button["position_y"] = meta.position.Y;
button["offset_x"] = meta.offset.X;
button["offset_y"] = meta.offset.Y;
root["layout"][button_names[id]] = button;
}
fastWriteJson(root, os);
}
static touch_gui_button_id button_name_to_id(const std::string &name)
{
for (u8 i = 0; i < touch_gui_button_id_END; i++) {
if (name == button_names[i])
return (touch_gui_button_id)i;
}
return touch_gui_button_id_END;
}
void ButtonLayout::deserializeJson(std::istream &is)
{
layout.clear();
Json::Value root;
is >> root;
if (!root["layout"].isObject())
throw Json::RuntimeError("invalid type for layout");
Json::Value &obj = root["layout"];
Json::ValueIterator iter;
for (iter = obj.begin(); iter != obj.end(); iter++) {
touch_gui_button_id id = button_name_to_id(iter.name());
if (!isButtonAllowed(id))
throw Json::RuntimeError("invalid button name");
Json::Value &value = *iter;
if (!value.isObject())
throw Json::RuntimeError("invalid type for button metadata");
ButtonMeta meta;
if (!value["position_x"].isNumeric() || !value["position_y"].isNumeric())
throw Json::RuntimeError("invalid type for position_x or position_y in button metadata");
meta.position.X = value["position_x"].asFloat();
meta.position.Y = value["position_y"].asFloat();
if (!value["offset_x"].isNumeric() || !value["offset_y"].isNumeric())
throw Json::RuntimeError("invalid type for offset_x or offset_y in button metadata");
meta.offset.X = value["offset_x"].asFloat();
meta.offset.Y = value["offset_y"].asFloat();
layout.emplace(id, meta);
}
}
void layout_button_grid(v2u32 screensize, ISimpleTextureSource *tsrc,
const std::vector<touch_gui_button_id> &buttons,
// pos refers to the center of the button
const std::function<void(touch_gui_button_id btn, v2s32 pos, core::recti rect)> &callback)
{
s32 cols = 4;
s32 rows = 3;
f32 screen_aspect = (f32)screensize.X / (f32)screensize.Y;
while ((s32)buttons.size() > cols * rows) {
f32 aspect = (f32)cols / (f32)rows;
if (aspect > screen_aspect)
rows++;
else
cols++;
}
s32 button_size = ButtonLayout::getButtonSize(screensize);
v2s32 spacing(screensize.X / (cols + 1), screensize.Y / (rows + 1));
v2s32 pos(spacing);
for (touch_gui_button_id btn : buttons) {
v2u32 orig_size = ButtonLayout::getTexture(btn, tsrc)->getOriginalSize();
v2s32 size((button_size * orig_size.X) / orig_size.Y, button_size);
core::recti rect(pos - size / 2, core::dimension2di(size));
if (rect.LowerRightCorner.X > (s32)screensize.X) {
pos.X = spacing.X;
pos.Y += spacing.Y;
rect = core::recti(pos - size / 2, core::dimension2di(size));
}
callback(btn, pos, rect);
pos.X += spacing.X;
}
}
void make_button_grid_title(gui::IGUIStaticText *text, touch_gui_button_id btn, v2s32 pos, core::recti rect)
{
std::wstring str = wstrgettext(button_titles[btn]);
text->setText(str.c_str());
gui::IGUIFont *font = text->getActiveFont();
core::dimension2du dim = font->getDimension(str.c_str());
dim = core::dimension2du(dim.Width * 1.25f, dim.Height * 1.25f); // avoid clipping
text->setRelativePosition(core::recti(pos.X - dim.Width / 2, rect.LowerRightCorner.Y,
pos.X + dim.Width / 2, rect.LowerRightCorner.Y + dim.Height));
text->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT);
}

104
src/gui/touchscreenlayout.h Normal file
View file

@ -0,0 +1,104 @@
// Luanti
// SPDX-License-Identifier: LGPL-2.1-or-later
// Copyright (C) 2024 grorp, Gregor Parzefall <grorp@posteo.de>
#pragma once
#include "irr_ptr.h"
#include "irrlichttypes_bloated.h"
#include "rect.h"
#include <iostream>
#include <unordered_map>
class ISimpleTextureSource;
namespace irr::gui
{
class IGUIStaticText;
}
namespace irr::video
{
class ITexture;
}
enum touch_gui_button_id : u8
{
jump_id = 0,
sneak_id,
zoom_id,
aux1_id,
overflow_id,
// formerly "rare controls bar"
chat_id,
inventory_id,
drop_id,
exit_id,
// formerly "settings bar"
fly_id,
fast_id,
noclip_id,
debug_id,
camera_id,
range_id,
minimap_id,
toggle_chat_id,
// the joystick
joystick_off_id,
joystick_bg_id,
joystick_center_id,
touch_gui_button_id_END,
};
extern const char *button_names[];
extern const char *button_titles[];
extern const char *button_image_names[];
struct ButtonMeta {
// Position, specified as a percentage of the screensize in the range [0,1].
// The editor currently writes the values 0, 0.5 and 1.
v2f position;
// Offset, multiplied by the global button size before it is applied.
// Together, position and offset define the position of the button's center.
v2f offset;
// Returns the button's effective center position in pixels.
v2s32 getPos(v2u32 screensize, s32 button_size) const;
// Sets the button's effective center position in pixels.
void setPos(v2s32 pos, v2u32 screensize, s32 button_size);
};
struct ButtonLayout {
static bool isButtonAllowed(touch_gui_button_id id);
static bool isButtonRequired(touch_gui_button_id id);
static s32 getButtonSize(v2u32 screensize);
static ButtonLayout loadFromSettings();
static video::ITexture *getTexture(touch_gui_button_id btn, ISimpleTextureSource *tsrc);
static void clearTextureCache();
std::unordered_map<touch_gui_button_id, ButtonMeta> layout;
core::recti getRect(touch_gui_button_id btn,
v2u32 screensize, s32 button_size, ISimpleTextureSource *tsrc);
std::vector<touch_gui_button_id> getMissingButtons();
void serializeJson(std::ostream &os) const;
void deserializeJson(std::istream &is);
static const ButtonLayout predefined;
private:
static std::unordered_map<touch_gui_button_id, irr_ptr<video::ITexture>> texture_cache;
};
void layout_button_grid(v2u32 screensize, ISimpleTextureSource *tsrc,
const std::vector<touch_gui_button_id> &buttons,
// pos refers to the center of the button.
const std::function<void(touch_gui_button_id btn, v2s32 pos, core::recti rect)> &callback);
void make_button_grid_title(gui::IGUIStaticText *text,
touch_gui_button_id btn,v2s32 pos, core::recti rect);

View file

@ -295,10 +295,8 @@ std::string ItemStack::getWieldOverlay(const IItemDefManager *itemdef) const
v3f ItemStack::getWieldScale(const IItemDefManager *itemdef) const
{
std::string scale = metadata.getString("wield_scale");
if (scale.empty())
return getDefinition(itemdef).wield_scale;
return str_to_v3f(scale);
return str_to_v3f(scale).value_or(getDefinition(itemdef).wield_scale);
}
ItemStack ItemStack::addItem(ItemStack newitem, IItemDefManager *itemdef)

28
src/irr_gui_ptr.h Normal file
View file

@ -0,0 +1,28 @@
// Luanti
// SPDX-License-Identifier: LGPL-2.1-or-later
// Copyright (C) 2024 grorp, Gregor Parzefall <grorp@posteo.de>
#pragma once
#include <memory>
#include "IGUIElement.h"
// We cannot use irr_ptr for Irrlicht GUI elements we own.
// Option 1: Pass IGUIElement* returned by IGUIEnvironment::add* into irr_ptr
// constructor.
// -> We steal the reference owned by IGUIEnvironment and drop it later,
// causing the IGUIElement to be deleted while IGUIEnvironment still
// references it.
// Option 2: Pass IGUIElement* returned by IGUIEnvironment::add* into irr_ptr::grab.
// -> We add another reference and drop it later, but since IGUIEnvironment
// still references the IGUIElement, it is never deleted.
// To make IGUIEnvironment drop its reference to the IGUIElement, we have to call
// IGUIElement::remove, so that's what we'll do.
template <typename T>
std::shared_ptr<T> grab_gui_element(T *element)
{
static_assert(std::is_base_of_v<irr::gui::IGUIElement, T>,
"grab_gui_element only works for IGUIElement");
return std::shared_ptr<T>(element, [](T *e) {
e->remove();
});
}

View file

@ -254,9 +254,6 @@ CGUITTFont::CGUITTFont(IGUIEnvironment *env)
batch_load_size(1), Device(0), Environment(env), Driver(0), GlobalKerningWidth(0), GlobalKerningHeight(0),
shadow_offset(0), shadow_alpha(0), fallback(0)
{
#ifdef _DEBUG
setDebugName("CGUITTFont");
#endif
if (Environment)
{

View file

@ -30,10 +30,6 @@ StaticText::StaticText(const EnrichedString &text, bool border,
RestrainTextInside(true), RightToLeft(false),
OverrideFont(0), LastBreakFont(0)
{
#ifdef _DEBUG
setDebugName("StaticText");
#endif
setText(text);
}

View file

@ -24,6 +24,7 @@
#include "config.h"
#include "player.h"
#include "porting.h"
#include "serialization.h" // SER_FMT_VER_HIGHEST_*
#include "network/socket.h"
#include "mapblock.h"
#if USE_CURSES

View file

@ -766,7 +766,7 @@ void MMVManip::initialEmerge(v3s16 blockpos_min, v3s16 blockpos_max,
VoxelArea block_area_nodes
(p_min*MAP_BLOCKSIZE, (p_max+1)*MAP_BLOCKSIZE-v3s16(1,1,1));
u32 size_MB = block_area_nodes.getVolume()*4/1000000;
u32 size_MB = block_area_nodes.getVolume() * sizeof(MapNode) / 1000000U;
if(size_MB >= 1)
{
infostream<<"initialEmerge: area: ";
@ -855,7 +855,7 @@ MMVManip *MMVManip::clone() const
{
MMVManip *ret = new MMVManip();
const s32 size = m_area.getVolume();
const u32 size = m_area.getVolume();
ret->m_area = m_area;
if (m_data) {
ret->m_data = new MapNode[size];

View file

@ -79,7 +79,7 @@ struct MapEditEvent
VoxelArea a;
for (v3s16 p : modified_blocks) {
v3s16 np1 = p*MAP_BLOCKSIZE;
v3s16 np2 = np1 + v3s16(1,1,1)*MAP_BLOCKSIZE - v3s16(1,1,1);
v3s16 np2 = np1 + v3s16(MAP_BLOCKSIZE-1);
a.addPoint(np1);
a.addPoint(np2);
}

View file

@ -307,11 +307,9 @@ static void correctBlockNodeIds(const NameIdMapping *nimap, MapNode *nodes,
void MapBlock::serialize(std::ostream &os_compressed, u8 version, bool disk, int compression_level)
{
if(!ser_ver_supported(version))
if (!ser_ver_supported_write(version))
throw VersionMismatchException("ERROR: MapBlock format not supported");
FATAL_ERROR_IF(version < SER_FMT_VER_LOWEST_WRITE, "Serialization version error");
std::ostringstream os_raw(std::ios_base::binary);
std::ostream &os = version >= 29 ? os_raw : os_compressed;
@ -423,7 +421,7 @@ void MapBlock::serializeNetworkSpecific(std::ostream &os)
void MapBlock::deSerialize(std::istream &in_compressed, u8 version, bool disk)
{
if(!ser_ver_supported(version))
if (!ser_ver_supported_read(version))
throw VersionMismatchException("ERROR: MapBlock format not supported");
TRACESTREAM(<<"MapBlock::deSerialize "<<getPos()<<std::endl);

View file

@ -64,7 +64,7 @@ void CavesNoiseIntersection::generateCaves(MMVManip *vm,
noise_cave1->perlinMap3D(nmin.X, nmin.Y - 1, nmin.Z);
noise_cave2->perlinMap3D(nmin.X, nmin.Y - 1, nmin.Z);
const v3s16 &em = vm->m_area.getExtent();
const v3s32 &em = vm->m_area.getExtent();
u32 index2d = 0; // Biomemap index
for (s16 z = nmin.Z; z <= nmax.Z; z++)
@ -84,7 +84,7 @@ void CavesNoiseIntersection::generateCaves(MMVManip *vm,
u16 depth_riverbed = biome->depth_riverbed;
u16 nplaced = 0;
s16 biome_y_min = m_bmgn->getNextTransitionY(nmax.Y);
s16 biome_y_next = m_bmgn->getNextTransitionY(nmax.Y);
// Don't excavate the overgenerated stone at nmax.Y + 1,
// this creates a 'roof' over the tunnel, preventing light in
@ -94,13 +94,13 @@ void CavesNoiseIntersection::generateCaves(MMVManip *vm,
index3d -= m_ystride,
VoxelArea::add_y(em, vi, -1)) {
// We need this check to make sure that biomes don't generate too far down
if (y < biome_y_min) {
if (y <= biome_y_next) {
biome = m_bmgn->getBiomeAtIndex(index2d, v3s16(x, y, z));
biome_y_min = m_bmgn->getNextTransitionY(y);
biome_y_next = m_bmgn->getNextTransitionY(y);
if (x == nmin.X && z == nmin.Z && false) {
dstream << "cavegen: biome at " << y << " is " << biome->name
<< ", next at " << biome_y_min << std::endl;
<< ", next at " << biome_y_next << std::endl;
}
}
@ -230,7 +230,7 @@ bool CavernsNoise::generateCaverns(MMVManip *vm, v3s16 nmin, v3s16 nmax)
//// Place nodes
bool near_cavern = false;
const v3s16 &em = vm->m_area.getExtent();
const v3s32 &em = vm->m_area.getExtent();
u32 index2d = 0;
for (s16 z = nmin.Z; z <= nmax.Z; z++)

View file

@ -127,7 +127,7 @@ void DungeonGen::generate(MMVManip *vm, u32 bseed, v3s16 nmin, v3s16 nmax)
void DungeonGen::makeDungeon(v3s16 start_padding)
{
const v3s16 &areasize = vm->m_area.getExtent();
const v3s32 &areasize = vm->m_area.getExtent();
v3s16 roomsize;
v3s16 roomplace;

View file

@ -240,7 +240,7 @@ u32 Mapgen::getBlockSeed2(v3s16 p, s32 seed)
// Returns -MAX_MAP_GENERATION_LIMIT if not found
s16 Mapgen::findGroundLevel(v2s16 p2d, s16 ymin, s16 ymax)
{
const v3s16 &em = vm->m_area.getExtent();
const v3s32 &em = vm->m_area.getExtent();
u32 i = vm->m_area.index(p2d.X, ymax, p2d.Y);
s16 y;
@ -258,7 +258,7 @@ s16 Mapgen::findGroundLevel(v2s16 p2d, s16 ymin, s16 ymax)
// Returns -MAX_MAP_GENERATION_LIMIT if not found or if ground is found first
s16 Mapgen::findLiquidSurface(v2s16 p2d, s16 ymin, s16 ymax)
{
const v3s16 &em = vm->m_area.getExtent();
const v3s32 &em = vm->m_area.getExtent();
u32 i = vm->m_area.index(p2d.X, ymax, p2d.Y);
s16 y;
@ -296,7 +296,7 @@ void Mapgen::updateHeightmap(v3s16 nmin, v3s16 nmax)
void Mapgen::getSurfaces(v2s16 p2d, s16 ymin, s16 ymax,
std::vector<s16> &floors, std::vector<s16> &ceilings)
{
const v3s16 &em = vm->m_area.getExtent();
const v3s32 &em = vm->m_area.getExtent();
bool is_walkable = false;
u32 vi = vm->m_area.index(p2d.X, ymax, p2d.Y);
@ -320,7 +320,7 @@ void Mapgen::getSurfaces(v2s16 p2d, s16 ymin, s16 ymax,
}
inline bool Mapgen::isLiquidHorizontallyFlowable(u32 vi, v3s16 em)
inline bool Mapgen::isLiquidHorizontallyFlowable(u32 vi, v3s32 em)
{
u32 vi_neg_x = vi;
VoxelArea::add_x(em, vi_neg_x, -1);
@ -357,7 +357,7 @@ void Mapgen::updateLiquid(UniqueQueue<v3s16> *trans_liquid, v3s16 nmin, v3s16 nm
{
bool isignored, isliquid, wasignored, wasliquid, waschecked, waspushed;
content_t was_n;
const v3s16 &em = vm->m_area.getExtent();
const v3s32 &em = vm->m_area.getExtent();
isignored = true;
isliquid = false;
@ -481,7 +481,7 @@ void Mapgen::propagateSunlight(v3s16 nmin, v3s16 nmax, bool propagate_shadow)
//TimeTaker t("propagateSunlight");
VoxelArea a(nmin, nmax);
bool block_is_underground = (water_level >= nmax.Y);
const v3s16 &em = vm->m_area.getExtent();
const v3s32 &em = vm->m_area.getExtent();
// NOTE: Direct access to the low 4 bits of param1 is okay here because,
// by definition, sunlight will never be in the night lightbank.
@ -629,7 +629,7 @@ void MapgenBasic::generateBiomes()
assert(biomegen);
assert(biomemap);
const v3s16 &em = vm->m_area.getExtent();
const v3s32 &em = vm->m_area.getExtent();
u32 index = 0;
noise_filler_depth->perlinMap2D(node_min.X, node_min.Z);
@ -644,7 +644,7 @@ void MapgenBasic::generateBiomes()
u16 depth_riverbed = 0;
u32 vi = vm->m_area.index(x, node_max.Y, z);
s16 biome_y_min = biomegen->getNextTransitionY(node_max.Y);
s16 biome_y_next = biomegen->getNextTransitionY(node_max.Y);
// Check node at base of mapchunk above, either a node of a previously
// generated mapchunk or if not, a node of overgenerated base terrain.
@ -661,23 +661,29 @@ void MapgenBasic::generateBiomes()
for (s16 y = node_max.Y; y >= node_min.Y; y--) {
content_t c = vm->m_data[vi].getContent();
const bool biome_outdated = !biome || y <= biome_y_next;
// Biome is (re)calculated:
// 1. At the surface of stone below air or water.
// 2. At the surface of water below air.
// 3. When stone or water is detected but biome has not yet been calculated.
// 4. When stone or water is detected just below a biome's lower limit.
bool is_stone_surface = (c == c_stone) &&
(air_above || water_above || !biome || y < biome_y_min); // 1, 3, 4
(air_above || water_above || biome_outdated); // 1, 3, 4
bool is_water_surface =
(c == c_water_source || c == c_river_water_source) &&
(air_above || !biome || y < biome_y_min); // 2, 3, 4
(air_above || biome_outdated); // 2, 3, 4
if (is_stone_surface || is_water_surface) {
if (!biome || y < biome_y_min) {
if (biome_outdated) {
// (Re)calculate biome
biome = biomegen->getBiomeAtIndex(index, v3s16(x, y, z));
biome_y_min = biomegen->getNextTransitionY(y);
biome_y_next = biomegen->getNextTransitionY(y);
if (x == node_min.X && z == node_min.Z && false) {
dstream << "biomegen: biome at " << y << " is " << biome->name
<< ", next at " << biome_y_next << std::endl;
}
}
// Add biome to biomemap at first stone surface detected
@ -768,7 +774,7 @@ void MapgenBasic::dustTopNodes()
if (node_max.Y < water_level)
return;
const v3s16 &em = vm->m_area.getExtent();
const v3s32 &em = vm->m_area.getExtent();
u32 index = 0;
for (s16 z = node_min.Z; z <= node_max.Z; z++)

View file

@ -261,7 +261,7 @@ private:
// isLiquidHorizontallyFlowable() is a helper function for updateLiquid()
// that checks whether there are floodable nodes without liquid beneath
// the node at index vi.
inline bool isLiquidHorizontallyFlowable(u32 vi, v3s16 em);
inline bool isLiquidHorizontallyFlowable(u32 vi, v3s32 em);
};
/*

View file

@ -445,7 +445,7 @@ int MapgenCarpathian::generateTerrain()
noise_rivers->perlinMap2D(node_min.X, node_min.Z);
//// Place nodes
const v3s16 &em = vm->m_area.getExtent();
const v3s32 &em = vm->m_area.getExtent();
s16 stone_surface_max_y = -MAX_MAP_GENERATION_LIMIT;
u32 index2d = 0;

View file

@ -278,7 +278,7 @@ s16 MapgenFlat::generateTerrain()
MapNode n_stone(c_stone);
MapNode n_water(c_water_source);
const v3s16 &em = vm->m_area.getExtent();
const v3s32 &em = vm->m_area.getExtent();
s16 stone_surface_max_y = -MAX_MAP_GENERATION_LIMIT;
u32 ni2d = 0;

View file

@ -103,8 +103,17 @@ void MapgenFractalParams::readParams(const Settings *settings)
settings->getS16NoEx("mgfractal_dungeon_ymax", dungeon_ymax);
settings->getU16NoEx("mgfractal_fractal", fractal);
settings->getU16NoEx("mgfractal_iterations", iterations);
settings->getV3FNoEx("mgfractal_scale", scale);
settings->getV3FNoEx("mgfractal_offset", offset);
std::optional<v3f> mgfractal_scale;
if (settings->getV3FNoEx("mgfractal_scale", mgfractal_scale) && mgfractal_scale.has_value()) {
scale = *mgfractal_scale;
}
std::optional<v3f> mgfractal_offset;
if (settings->getV3FNoEx("mgfractal_offset", mgfractal_offset) && mgfractal_offset.has_value()) {
offset = *mgfractal_offset;
}
settings->getFloatNoEx("mgfractal_slice_w", slice_w);
settings->getFloatNoEx("mgfractal_julia_x", julia_x);
settings->getFloatNoEx("mgfractal_julia_y", julia_y);

View file

@ -222,7 +222,7 @@ void MapgenV6Params::setDefaultSettings(Settings *settings)
// Returns Y one under area minimum if not found
s16 MapgenV6::find_stone_level(v2s16 p2d)
{
const v3s16 &em = vm->m_area.getExtent();
const v3s32 &em = vm->m_area.getExtent();
s16 y_nodes_max = vm->m_area.MaxEdge.Y;
s16 y_nodes_min = vm->m_area.MinEdge.Y;
u32 i = vm->m_area.index(p2d.X, y_nodes_max, p2d.Y);
@ -670,7 +670,7 @@ int MapgenV6::generateGround()
BiomeV6Type bt = getBiome(v2s16(x, z));
// Fill ground with stone
const v3s16 &em = vm->m_area.getExtent();
const v3s32 &em = vm->m_area.getExtent();
u32 i = vm->m_area.index(x, node_min.Y, z);
for (s16 y = node_min.Y; y <= node_max.Y; y++) {
if (vm->m_data[i].getContent() == CONTENT_IGNORE) {
@ -739,7 +739,7 @@ void MapgenV6::addMud()
// Add mud on ground
s16 mudcount = 0;
const v3s16 &em = vm->m_area.getExtent();
const v3s32 &em = vm->m_area.getExtent();
s16 y_start = surface_y + 1;
u32 i = vm->m_area.index(x, y_start, z);
for (s16 y = y_start; y <= node_max.Y; y++) {
@ -757,7 +757,7 @@ void MapgenV6::addMud()
void MapgenV6::flowMud(s16 &mudflow_minpos, s16 &mudflow_maxpos)
{
const v3s16 &em = vm->m_area.getExtent();
const v3s32 &em = vm->m_area.getExtent();
static const v3s16 dirs4[4] = {
v3s16(0, 0, 1), // Back
v3s16(1, 0, 0), // Right
@ -870,7 +870,7 @@ void MapgenV6::flowMud(s16 &mudflow_minpos, s16 &mudflow_maxpos)
void MapgenV6::moveMud(u32 remove_index, u32 place_index,
u32 above_remove_index, v2s16 pos, v3s16 em)
u32 above_remove_index, v2s16 pos, v3s32 em)
{
MapNode n_air(CONTENT_AIR);
// Copy mud from old place to new place
@ -920,7 +920,7 @@ void MapgenV6::placeTreesAndJungleGrass()
if (c_junglegrass == CONTENT_IGNORE)
c_junglegrass = CONTENT_AIR;
MapNode n_junglegrass(c_junglegrass);
const v3s16 &em = vm->m_area.getExtent();
const v3s32 &em = vm->m_area.getExtent();
// Divide area into parts
s16 div = 8;
@ -1027,7 +1027,7 @@ void MapgenV6::growGrass() // Add surface nodes
MapNode n_dirt_with_grass(c_dirt_with_grass);
MapNode n_dirt_with_snow(c_dirt_with_snow);
MapNode n_snowblock(c_snowblock);
const v3s16 &em = vm->m_area.getExtent();
const v3s32 &em = vm->m_area.getExtent();
u32 index = 0;
for (s16 z = full_node_min.Z; z <= full_node_max.Z; z++)

View file

@ -150,7 +150,7 @@ public:
void addMud();
void flowMud(s16 &mudflow_minpos, s16 &mudflow_maxpos);
void moveMud(u32 remove_index, u32 place_index,
u32 above_remove_index, v2s16 pos, v3s16 em);
u32 above_remove_index, v2s16 pos, v3s32 em);
void growGrass();
void placeTreesAndJungleGrass();
virtual void generateCaves(int max_stone_y);

View file

@ -523,7 +523,7 @@ int MapgenV7::generateTerrain()
}
//// Place nodes
const v3s16 &em = vm->m_area.getExtent();
const v3s32 &em = vm->m_area.getExtent();
s16 stone_surface_max_y = -MAX_MAP_GENERATION_LIMIT;
u32 index2d = 0;

View file

@ -344,7 +344,7 @@ int MapgenValleys::generateTerrain()
noise_inter_valley_fill->perlinMap3D(node_min.X, node_min.Y - 1, node_min.Z);
const v3s16 &em = vm->m_area.getExtent();
const v3s32 &em = vm->m_area.getExtent();
s16 surface_max_y = -MAX_MAP_GENERATION_LIMIT;
u32 index_2d = 0;

View file

@ -25,21 +25,7 @@ BiomeManager::BiomeManager(Server *server) :
// Create default biome to be used in case none exist
Biome *b = new Biome;
b->name = "default";
b->flags = 0;
b->depth_top = 0;
b->depth_filler = -MAX_MAP_GENERATION_LIMIT;
b->depth_water_top = 0;
b->depth_riverbed = 0;
b->min_pos = v3s16(-MAX_MAP_GENERATION_LIMIT,
-MAX_MAP_GENERATION_LIMIT, -MAX_MAP_GENERATION_LIMIT);
b->max_pos = v3s16(MAX_MAP_GENERATION_LIMIT,
MAX_MAP_GENERATION_LIMIT, MAX_MAP_GENERATION_LIMIT);
b->heat_point = 0.0;
b->humidity_point = 0.0;
b->vertical_blend = 0;
b->weight = 1.0f;
b->m_nodenames.emplace_back("mapgen_stone");
b->m_nodenames.emplace_back("mapgen_stone");
@ -64,11 +50,13 @@ void BiomeManager::clear()
{
EmergeManager *emerge = m_server->getEmergeManager();
// Remove all dangling references in Decorations
DecorationManager *decomgr = emerge->getWritableDecorationManager();
for (size_t i = 0; i != decomgr->getNumObjects(); i++) {
Decoration *deco = (Decoration *)decomgr->getRaw(i);
deco->biomes.clear();
if (emerge) {
// Remove all dangling references in Decorations
DecorationManager *decomgr = emerge->getWritableDecorationManager();
for (size_t i = 0; i != decomgr->getNumObjects(); i++) {
Decoration *deco = (Decoration *)decomgr->getRaw(i);
deco->biomes.clear();
}
}
// Don't delete the first biome
@ -141,7 +129,10 @@ BiomeGenOriginal::BiomeGenOriginal(BiomeManager *biomemgr,
for (size_t i = 0; i < m_bmgr->getNumObjects(); i++) {
Biome *b = (Biome *)m_bmgr->getRaw(i);
values.push_back(b->max_pos.Y);
values.push_back(b->min_pos.Y);
// We scan for biomes from high Y to low Y (top to bottom). Hence,
// biomes effectively transition at (min_pos.Y - 1).
if (b->min_pos.Y > -MAX_MAP_GENERATION_LIMIT)
values.push_back(b->min_pos.Y - 1);
}
std::sort(values.begin(), values.end(), std::greater<>());
@ -299,8 +290,6 @@ ObjDef *Biome::clone() const
ObjDef::cloneTo(obj);
NodeResolver::cloneTo(obj);
obj->flags = flags;
obj->c_top = c_top;
obj->c_filler = c_filler;
obj->c_stone = c_stone;

View file

@ -19,6 +19,12 @@ class BiomeManager;
typedef u16 biome_t;
constexpr v3s16 MAX_MAP_GENERATION_LIMIT_V3(
MAX_MAP_GENERATION_LIMIT,
MAX_MAP_GENERATION_LIMIT,
MAX_MAP_GENERATION_LIMIT
);
#define BIOME_NONE ((biome_t)0)
enum BiomeType {
@ -29,32 +35,32 @@ class Biome : public ObjDef, public NodeResolver {
public:
ObjDef *clone() const;
u32 flags;
content_t c_top;
content_t c_filler;
content_t c_stone;
content_t c_water_top;
content_t c_water;
content_t c_river_water;
content_t c_riverbed;
content_t c_dust;
content_t
c_top = CONTENT_IGNORE,
c_filler = CONTENT_IGNORE,
c_stone = CONTENT_IGNORE,
c_water_top = CONTENT_IGNORE,
c_water = CONTENT_IGNORE,
c_river_water = CONTENT_IGNORE,
c_riverbed = CONTENT_IGNORE,
c_dust = CONTENT_IGNORE;
std::vector<content_t> c_cave_liquid;
content_t c_dungeon;
content_t c_dungeon_alt;
content_t c_dungeon_stair;
content_t
c_dungeon = CONTENT_IGNORE,
c_dungeon_alt = CONTENT_IGNORE,
c_dungeon_stair = CONTENT_IGNORE;
s16 depth_top;
s16 depth_filler;
s16 depth_water_top;
s16 depth_riverbed;
s16 depth_top = 0;
s16 depth_filler = -MAX_MAP_GENERATION_LIMIT;
s16 depth_water_top = 0;
s16 depth_riverbed = 0;
v3s16 min_pos;
v3s16 max_pos;
float heat_point;
float humidity_point;
s16 vertical_blend;
float weight;
v3s16 min_pos = -MAX_MAP_GENERATION_LIMIT_V3;
v3s16 max_pos = MAX_MAP_GENERATION_LIMIT_V3;
float heat_point = 0.0f;
float humidity_point = 0.0f;
s16 vertical_blend = 0;
float weight = 1.0f;
virtual void resolveNodeNames();
};
@ -191,7 +197,8 @@ private:
Noise *noise_heat_blend;
Noise *noise_humidity_blend;
// ordered descending
/// Y values at which biomes may transition.
/// This array may only be used for downwards scanning!
std::vector<s16> m_transitions_y;
};

View file

@ -346,7 +346,7 @@ size_t DecoSimple::generate(MMVManip *vm, PcgRandom *pr, v3s16 p, bool ceiling)
pr->range(deco_param2, deco_param2_max) : deco_param2;
bool force_placement = (flags & DECO_FORCE_PLACEMENT);
const v3s16 &em = vm->m_area.getExtent();
const v3s32 &em = vm->m_area.getExtent();
u32 vi = vm->m_area.index(p);
if (ceiling) {

View file

@ -23,8 +23,7 @@ void TreeDef::resolveNodeNames()
getIdFromNrBacklog(&leavesnode.param0, "", CONTENT_IGNORE);
if (leaves2_chance)
getIdFromNrBacklog(&leaves2node.param0, "", CONTENT_IGNORE);
if (fruit_chance)
getIdFromNrBacklog(&fruitnode.param0, "", CONTENT_IGNORE);
getIdFromNrBacklog(&fruitnode.param0, "", CONTENT_IGNORE);
}
/*
@ -78,7 +77,7 @@ void make_tree(MMVManip &vmanip, v3s16 p0, bool is_apple_tree,
VoxelArea leaves_a(v3s16(-2, -1, -2), v3s16(2, 2, 2));
Buffer<u8> leaves_d(leaves_a.getVolume());
for (s32 i = 0; i < leaves_a.getVolume(); i++)
for (u32 i = 0; i < leaves_d.getSize(); i++)
leaves_d[i] = 0;
// Force leaves at near the end of the trunk
@ -698,9 +697,8 @@ void make_jungletree(MMVManip &vmanip, v3s16 p0, const NodeDefManager *ndef,
p1.Y -= 1;
VoxelArea leaves_a(v3s16(-3, -2, -3), v3s16(3, 2, 3));
//SharedPtr<u8> leaves_d(new u8[leaves_a.getVolume()]);
Buffer<u8> leaves_d(leaves_a.getVolume());
for (s32 i = 0; i < leaves_a.getVolume(); i++)
for (u32 i = 0; i < leaves_d.getSize(); i++)
leaves_d[i] = 0;
// Force leaves at near the end of the trunk
@ -789,7 +787,7 @@ void make_pine_tree(MMVManip &vmanip, v3s16 p0, const NodeDefManager *ndef,
VoxelArea leaves_a(v3s16(-3, -6, -3), v3s16(3, 3, 3));
Buffer<u8> leaves_d(leaves_a.getVolume());
for (s32 i = 0; i < leaves_a.getVolume(); i++)
for (u32 i = 0; i < leaves_d.getSize(); i++)
leaves_d[i] = 0;
// Upper branches
@ -876,4 +874,15 @@ void make_pine_tree(MMVManip &vmanip, v3s16 p0, const NodeDefManager *ndef,
}
}
std::string error_to_string(error e)
{
switch (e) {
case SUCCESS:
return "success";
case UNBALANCED_BRACKETS:
return "closing ']' has no matching opening bracket";
}
return "unknown error";
}
}; // namespace treegen

View file

@ -68,4 +68,7 @@ namespace treegen {
treegen::error make_ltree(MMVManip &vmanip, v3s16 p0, const TreeDef &def);
// Helper to spawn it directly on map
treegen::error spawn_ltree(ServerMap *map, v3s16 p0, const TreeDef &def);
// Helper to get a string from the error message
std::string error_to_string(error e);
}; // namespace treegen

View file

@ -8,7 +8,7 @@
#include "nodedef.h"
#include "map.h"
#include "content_mapnode.h" // For mapnode_translate_*_internal
#include "serialization.h" // For ser_ver_supported
#include "serialization.h" // For ser_ver_supported_*
#include "util/serialize.h"
#include "log.h"
#include "util/directiontables.h"
@ -620,7 +620,7 @@ s8 MapNode::addLevel(const NodeDefManager *nodemgr, s16 add)
u32 MapNode::serializedLength(u8 version)
{
if(!ser_ver_supported(version))
if (!ser_ver_supported_read(version))
throw VersionMismatchException("ERROR: MapNode format not supported");
if (version == 0)
@ -636,7 +636,7 @@ u32 MapNode::serializedLength(u8 version)
}
void MapNode::serialize(u8 *dest, u8 version) const
{
if(!ser_ver_supported(version))
if (!ser_ver_supported_write(version))
throw VersionMismatchException("ERROR: MapNode format not supported");
// Can't do this anymore; we have 16-bit dynamically allocated node IDs
@ -651,7 +651,7 @@ void MapNode::serialize(u8 *dest, u8 version) const
}
void MapNode::deSerialize(u8 *source, u8 version)
{
if(!ser_ver_supported(version))
if (!ser_ver_supported_read(version))
throw VersionMismatchException("ERROR: MapNode format not supported");
if(version <= 21)
@ -679,18 +679,12 @@ Buffer<u8> MapNode::serializeBulk(int version,
const MapNode *nodes, u32 nodecount,
u8 content_width, u8 params_width)
{
if (!ser_ver_supported(version))
if (!ser_ver_supported_write(version))
throw VersionMismatchException("ERROR: MapNode format not supported");
sanity_check(content_width == 2);
sanity_check(params_width == 2);
// Can't do this anymore; we have 16-bit dynamically allocated node IDs
// in memory; conversion just won't work in this direction.
if (version < 24)
throw SerializationError("MapNode::serializeBulk: serialization to "
"version < 24 not possible");
Buffer<u8> databuf(nodecount * (content_width + params_width));
// Writing to the buffer linearly is faster
@ -712,13 +706,13 @@ void MapNode::deSerializeBulk(std::istream &is, int version,
MapNode *nodes, u32 nodecount,
u8 content_width, u8 params_width)
{
if(!ser_ver_supported(version))
if (!ser_ver_supported_read(version))
throw VersionMismatchException("ERROR: MapNode format not supported");
if (version < 22
|| (content_width != 1 && content_width != 2)
|| params_width != 2)
FATAL_ERROR("Deserialize bulk node data error");
throw SerializationError("Deserialize bulk node data error");
// read data
const u32 len = nodecount * (content_width + params_width);

View file

@ -4,6 +4,7 @@
#include "client/client.h"
#include "exceptions.h"
#include "irr_v2d.h"
#include "util/base64.h"
#include "client/camera.h"
@ -27,7 +28,7 @@
#include "script/scripting_client.h"
#include "util/serialize.h"
#include "util/srp.h"
#include "util/sha1.h"
#include "util/hashing.h"
#include "tileanimation.h"
#include "gettext.h"
#include "skyparams.h"
@ -62,7 +63,7 @@ void Client::handleCommand_Hello(NetworkPacket* pkt)
if (pkt->getSize() < 1)
return;
u8 serialization_ver;
u8 serialization_ver; // negotiated value
u16 proto_ver;
u16 unused_compression_mode;
u32 auth_mechs;
@ -79,9 +80,9 @@ void Client::handleCommand_Hello(NetworkPacket* pkt)
<< ", proto_ver=" << proto_ver
<< ". Doing auth with mech " << chosen_auth_mechanism << std::endl;
if (!ser_ver_supported(serialization_ver)) {
if (!ser_ver_supported_read(serialization_ver)) {
infostream << "Client: TOCLIENT_HELLO: Server sent "
<< "unsupported ser_fmt_ver"<< std::endl;
<< "unsupported ser_fmt_ver=" << (int)serialization_ver << std::endl;
return;
}
@ -1007,6 +1008,8 @@ void Client::handleCommand_AddParticleSpawner(NetworkPacket* pkt)
p.amount = readU16(is);
p.time = readF32(is);
if (p.time < 0)
throw SerializationError("particle spawner time < 0");
bool missing_end_values = false;
if (m_proto_ver >= 42) {
@ -1642,12 +1645,7 @@ void Client::handleCommand_MediaPush(NetworkPacket *pkt)
if (!filedata.empty()) {
// LEGACY CODEPATH
// Compute and check checksum of data
std::string computed_hash;
{
SHA1 ctx;
ctx.addBytes(filedata);
computed_hash = ctx.getDigest();
}
std::string computed_hash = hashing::sha1(filedata);
if (raw_hash != computed_hash) {
verbosestream << "Hash of file data mismatches, ignoring." << std::endl;
return;

View file

@ -60,9 +60,12 @@
Support float animation frame numbers in TOCLIENT_LOCAL_PLAYER_ANIMATIONS
Add beta_r0, vignette, specular intensity, foliage translucency and cdl parameters to Lighting packets
[scheduled bump for 5.10.0]
PROTOCOL VERSION 47
Add particle blend mode "clip"
[scheduled bump for 5.11.0]
*/
const u16 LATEST_PROTOCOL_VERSION = 46;
const u16 LATEST_PROTOCOL_VERSION = 47;
// See also formspec [Version History] in doc/lua_api.md
const u16 FORMSPEC_API_VERSION = 8;

View file

@ -12,6 +12,7 @@
#include "remoteplayer.h"
#include "rollback_interface.h"
#include "scripting_server.h"
#include "serialization.h"
#include "settings.h"
#include "tool.h"
#include "version.h"
@ -83,34 +84,27 @@ void Server::handleCommand_Init(NetworkPacket* pkt)
if (denyIfBanned(peer_id))
return;
// First byte after command is maximum supported
// serialization version
u8 client_max;
u8 max_ser_ver; // SER_FMT_VER_HIGHEST_READ (of client)
u16 unused;
u16 min_net_proto_version = 0;
u16 min_net_proto_version;
u16 max_net_proto_version;
std::string playerName;
*pkt >> client_max >> unused >> min_net_proto_version
>> max_net_proto_version >> playerName;
*pkt >> max_ser_ver >> unused
>> min_net_proto_version >> max_net_proto_version
>> playerName;
u8 our_max = SER_FMT_VER_HIGHEST_READ;
// Use the highest version supported by both
u8 depl_serial_v = std::min(client_max, our_max);
// If it's lower than the lowest supported, give up.
#if SER_FMT_VER_LOWEST_READ > 0
if (depl_serial_v < SER_FMT_VER_LOWEST_READ)
depl_serial_v = SER_FMT_VER_INVALID;
#endif
const u8 serialization_ver = std::min(max_ser_ver, SER_FMT_VER_HIGHEST_WRITE);
if (depl_serial_v == SER_FMT_VER_INVALID) {
if (!ser_ver_supported_write(serialization_ver)) {
actionstream << "Server: A mismatched client tried to connect from " <<
addr_s << " ser_fmt_max=" << (int)client_max << std::endl;
addr_s << " ser_fmt_max=" << (int)serialization_ver << std::endl;
DenyAccess(peer_id, SERVER_ACCESSDENIED_WRONG_VERSION);
return;
}
client->setPendingSerializationVersion(depl_serial_v);
client->setPendingSerializationVersion(serialization_ver);
/*
Read and check network protocol version
@ -263,8 +257,9 @@ void Server::handleCommand_Init(NetworkPacket* pkt)
NetworkPacket resp_pkt(TOCLIENT_HELLO, 0, peer_id);
resp_pkt << depl_serial_v << u16(0) << net_proto_version
<< auth_mechs << std::string_view();
resp_pkt << serialization_ver << u16(0) /* unused */
<< net_proto_version
<< auth_mechs << std::string_view() /* unused */;
Send(&resp_pkt);

View file

@ -13,6 +13,7 @@
#include "client/texturesource.h"
#include "client/tile.h"
#include <IMeshManipulator.h>
#include <SkinnedMesh.h>
#endif
#include "log.h"
#include "settings.h"
@ -943,14 +944,23 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc
palette = tsrc->getPalette(palette_name);
if (drawtype == NDT_MESH && !mesh.empty()) {
// Meshnode drawtype
// Read the mesh and apply scale
mesh_ptr = client->getMesh(mesh);
if (mesh_ptr) {
v3f scale = v3f(BS) * visual_scale;
scaleMesh(mesh_ptr, scale);
recalculateBoundingBox(mesh_ptr);
meshmanip->recalculateNormals(mesh_ptr, true, false);
if (!checkMeshNormals(mesh_ptr)) {
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();
}
}
}
}

View file

@ -222,6 +222,7 @@ enum NodeDrawType : u8
NDT_MESH,
// Combined plantlike-on-solid
NDT_PLANTLIKE_ROOTED,
// Dummy for validity check
NodeDrawType_END
};

View file

@ -190,8 +190,10 @@ void ServerParticleTexture::serialize(std::ostream &os, u16 protocol_ver,
FlagT flags = 0;
if (animated)
flags |= FlagT(ParticleTextureFlags::animated);
if (blendmode != BlendMode::alpha)
flags |= FlagT(blendmode) << 1;
// Default to `blend = "alpha"` for older clients that don't support `blend = "clip"`
auto sent_blendmode = (protocol_ver < 47 && blendmode == BlendMode::clip)
? BlendMode::alpha : blendmode;
flags |= FlagT(sent_blendmode) << 1;
serializeParameterValue(os, flags);
alpha.serialize(os);
@ -215,6 +217,8 @@ void ServerParticleTexture::deSerialize(std::istream &is, u16 protocol_ver,
animated = !!(flags & FlagT(ParticleTextureFlags::animated));
blendmode = BlendMode((flags & FlagT(ParticleTextureFlags::blend)) >> 1);
if (blendmode >= BlendMode::BlendMode_END)
throw SerializationError("invalid blend mode");
alpha.deSerialize(is);
scale.deSerialize(is);

View file

@ -233,7 +233,8 @@ namespace ParticleParamTypes
}
enum class AttractorKind : u8 { none, point, line, plane };
enum class BlendMode : u8 { alpha, add, sub, screen };
// Note: Allows at most 8 enum members (due to how this is serialized)
enum class BlendMode : u8 { alpha, add, sub, screen, clip, BlendMode_END };
// these are consistently-named convenience aliases to make code more readable without `using ParticleParamTypes` declarations
using v3fRange = RangedParameter<v3fParameter>;

View file

@ -688,6 +688,10 @@ void initializePaths()
#endif // RUN_IN_PLACE
assert(!path_share.empty());
assert(!path_user.empty());
assert(!path_cache.empty());
infostream << "Detected share path: " << path_share << std::endl;
infostream << "Detected user path: " << path_user << std::endl;
infostream << "Detected cache path: " << path_cache << std::endl;

View file

@ -109,8 +109,7 @@ extern std::string path_cache;
bool getCurrentExecPath(char *buf, size_t len);
/*
Get full path of stuff in data directory.
Example: "stone.png" -> "../data/stone.png"
Concatenate subpath to path_share.
*/
std::string getDataPath(const char *subpath);

View file

@ -2032,12 +2032,11 @@ bool read_tree_def(lua_State *L, int idx, const NodeDefManager *ndef,
getstringfield(L, idx, "trunk_type", tree_def.trunk_type);
getboolfield(L, idx, "thin_branches", tree_def.thin_branches);
tree_def.fruit_chance = 0;
fruit = "air";
getstringfield(L, idx, "fruit", fruit);
if (!fruit.empty()) {
if (!fruit.empty())
getintfield(L, idx, "fruit_chance", tree_def.fruit_chance);
if (tree_def.fruit_chance)
tree_def.m_nodenames.push_back(fruit);
}
tree_def.m_nodenames.push_back(fruit);
tree_def.explicit_seed = getintfield(L, idx, "seed", tree_def.seed);
// Resolves the node IDs for trunk, leaves, leaves2 and fruit at runtime,

View file

@ -566,38 +566,23 @@ bool ScriptApiSecurity::checkPath(lua_State *L, const char *path,
if (write_allowed)
*write_allowed = false;
std::string abs_path = fs::AbsolutePath(path);
// If we couldn't find the absolute path (path doesn't exist) then
// try removing the last components until it works (to allow
// non-existent files/folders for mkdir).
std::string cur_path = path;
std::string removed;
while (abs_path.empty() && !cur_path.empty()) {
std::string component;
cur_path = fs::RemoveLastPathComponent(cur_path, &component);
if (component == "..") {
// Parent components can't be allowed or we could allow something like
// /home/user/minetest/worlds/foo/noexist/../../../../../../etc/passwd.
// If we have previous non-relative elements in the path we might be
// able to remove them so that things like worlds/foo/noexist/../auth.txt
// could be allowed, but those paths will be interpreted as nonexistent
// by the operating system anyways.
return false;
}
removed = component + (removed.empty() ? "" : DIR_DELIM + removed);
abs_path = fs::AbsolutePath(cur_path);
}
if (abs_path.empty())
return false;
// Add the removed parts back so that you can e.g. create a
// directory in worldmods if worldmods doesn't exist.
if (!removed.empty())
abs_path += DIR_DELIM + removed;
// We can't use AbsolutePath() here since we want to allow creating paths that
// do not yet exist. But RemoveRelativePathComponents() would also be incorrect
// since that wouldn't normalize subpaths that *do* exist.
// This is required so that comparisons with other normalized paths work correctly.
std::string abs_path = fs::AbsolutePathPartial(path);
tracestream << "ScriptApiSecurity: path \"" << path << "\" resolved to \""
<< abs_path << "\"" << std::endl;
if (abs_path.empty())
return false;
// Note: abs_path can be a valid path while path isn't, e.g.
// abs_path = "/home/user/.luanti"
// path = "/home/user/.luanti/noexist/.."
// Letting this through the sandbox isn't a concern as any actual attempts to
// use the path would fail.
// Ask the environment-specific implementation
auto *sec = ModApiBase::getScriptApi<ScriptApiSecurity>(L);
return sec->checkPathInternal(abs_path, write_required, write_allowed);
@ -617,9 +602,11 @@ bool ScriptApiSecurity::checkPathWithGamedef(lua_State *L,
if (!gamedef)
return false;
if (!abs_path.empty()) {
assert(!abs_path.empty());
if (!g_settings_path.empty()) {
// Don't allow accessing the settings file
str = fs::AbsolutePath(g_settings_path);
str = fs::AbsolutePathPartial(g_settings_path);
if (str == abs_path)
return false;
}

View file

@ -1307,11 +1307,7 @@ int ModApiEnv::l_spawn_tree(lua_State *L)
ServerMap *map = &env->getServerMap();
treegen::error e;
if ((e = treegen::spawn_ltree (map, p0, tree_def)) != treegen::SUCCESS) {
if (e == treegen::UNBALANCED_BRACKETS) {
luaL_error(L, "spawn_tree(): closing ']' has no matching opening bracket");
} else {
luaL_error(L, "spawn_tree(): unknown error");
}
throw LuaError("spawn_tree(): " + treegen::error_to_string(e));
}
lua_pushboolean(L, true);
@ -1614,11 +1610,7 @@ int ModApiEnvVM::l_spawn_tree(lua_State *L)
treegen::error e;
if ((e = treegen::make_ltree(*vm, p0, tree_def)) != treegen::SUCCESS) {
if (e == treegen::UNBALANCED_BRACKETS) {
throw LuaError("spawn_tree(): closing ']' has no matching opening bracket");
} else {
throw LuaError("spawn_tree(): unknown error");
}
throw LuaError("spawn_tree(): " + treegen::error_to_string(e));
}
lua_pushboolean(L, true);

View file

@ -11,6 +11,7 @@
#include "gui/guiMainMenu.h"
#include "gui/guiKeyChangeMenu.h"
#include "gui/guiPathSelectMenu.h"
#include "gui/touchscreeneditor.h"
#include "version.h"
#include "porting.h"
#include "filesys.h"
@ -535,6 +536,22 @@ int ModApiMainMenu::l_show_keys_menu(lua_State *L)
return 0;
}
/******************************************************************************/
int ModApiMainMenu::l_show_touchscreen_layout(lua_State *L)
{
GUIEngine *engine = getGuiEngine(L);
sanity_check(engine != NULL);
GUITouchscreenLayout *gui = new GUITouchscreenLayout(
engine->m_rendering_engine->get_gui_env(),
engine->m_parent,
-1,
engine->m_menumanager,
engine->m_texture_source.get());
gui->drop();
return 0;
}
/******************************************************************************/
int ModApiMainMenu::l_create_world(lua_State *L)
{
@ -1080,6 +1097,7 @@ void ModApiMainMenu::Initialize(lua_State *L, int top)
API_FCT(start);
API_FCT(close);
API_FCT(show_keys_menu);
API_FCT(show_touchscreen_layout);
API_FCT(create_world);
API_FCT(delete_world);
API_FCT(set_background);

View file

@ -69,6 +69,8 @@ private:
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);
static int l_set_topleft_text(lua_State *L);

View file

@ -366,22 +366,19 @@ Biome *read_biome_def(lua_State *L, int index, const NodeDefManager *ndef)
ModApiMapgen::es_BiomeTerrainType, BIOMETYPE_NORMAL);
Biome *b = BiomeManager::create(biometype);
b->name = getstringfield_default(L, index, "name", "");
b->depth_top = getintfield_default(L, index, "depth_top", 0);
b->depth_filler = getintfield_default(L, index, "depth_filler", -31000);
b->depth_water_top = getintfield_default(L, index, "depth_water_top", 0);
b->depth_riverbed = getintfield_default(L, index, "depth_riverbed", 0);
b->heat_point = getfloatfield_default(L, index, "heat_point", 0.f);
b->humidity_point = getfloatfield_default(L, index, "humidity_point", 0.f);
b->vertical_blend = getintfield_default(L, index, "vertical_blend", 0);
b->weight = getfloatfield_default(L, index, "weight", 1.f);
b->flags = 0; // reserved
getstringfield(L, index, "name", b->name);
getintfield(L, index, "depth_top", b->depth_top);
getintfield(L, index, "depth_filler", b->depth_filler);
getintfield(L, index, "depth_water_top", b->depth_water_top);
getintfield(L, index, "depth_riverbed", b->depth_riverbed);
getfloatfield(L, index, "heat_point", b->heat_point);
getfloatfield(L, index, "humidity_point", b->humidity_point);
getintfield(L, index, "vertical_blend", b->vertical_blend);
getfloatfield(L, index, "weight", b->weight);
b->min_pos = getv3s16field_default(
L, index, "min_pos", v3s16(-31000, -31000, -31000));
b->min_pos = getv3s16field_default(L, index, "min_pos", b->min_pos);
getintfield(L, index, "y_min", b->min_pos.Y);
b->max_pos = getv3s16field_default(
L, index, "max_pos", v3s16(31000, 31000, 31000));
b->max_pos = getv3s16field_default(L, index, "max_pos", b->max_pos);
getintfield(L, index, "y_max", b->max_pos.Y);
std::vector<std::string> &nn = b->m_nodenames;
@ -1773,6 +1770,27 @@ int ModApiMapgen::l_place_schematic_on_vmanip(lua_State *L)
return 1;
}
// spawn_tree_on_vmanip(vmanip, pos, treedef)
int ModApiMapgen::l_spawn_tree_on_vmanip(lua_State *L)
{
NO_MAP_LOCK_REQUIRED;
MMVManip *vm = checkObject<LuaVoxelManip>(L, 1)->vm;
v3s16 p0 = read_v3s16(L, 2);
treegen::TreeDef tree_def;
const NodeDefManager *ndef = getGameDef(L)->ndef();
if (!read_tree_def(L, 3, ndef, tree_def))
return 0;
treegen::error e = treegen::make_ltree(*vm, p0, tree_def);
if (e != treegen::SUCCESS) {
throw LuaError("spawn_tree_on_vmanip(): " + treegen::error_to_string(e));
}
lua_pushboolean(L, true);
return 1;
}
// serialize_schematic(schematic, format, options={...})
int ModApiMapgen::l_serialize_schematic(lua_State *L)
@ -2023,6 +2041,7 @@ void ModApiMapgen::Initialize(lua_State *L, int top)
API_FCT(create_schematic);
API_FCT(place_schematic);
API_FCT(place_schematic_on_vmanip);
API_FCT(spawn_tree_on_vmanip);
API_FCT(serialize_schematic);
API_FCT(read_schematic);
}
@ -2049,6 +2068,7 @@ void ModApiMapgen::InitializeEmerge(lua_State *L, int top)
API_FCT(generate_ores);
API_FCT(generate_decorations);
API_FCT(place_schematic_on_vmanip);
API_FCT(spawn_tree_on_vmanip);
API_FCT(serialize_schematic);
API_FCT(read_schematic);
}

View file

@ -131,6 +131,9 @@ private:
// replacements, force_placement, flagstring)
static int l_place_schematic_on_vmanip(lua_State *L);
// spawn_tree_on_vmanip(vmanip, pos, treedef)
static int l_spawn_tree_on_vmanip(lua_State *L);
// serialize_schematic(schematic, format, options={...})
static int l_serialize_schematic(lua_State *L);

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