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

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

This commit is contained in:
Gefüllte Taubenbrust 2025-04-13 11:33:37 +02:00
commit fa212d19f7
572 changed files with 71629 additions and 67352 deletions

View file

@ -39,7 +39,7 @@ public:
for (auto &it : m_active_objects.iter()) {
if (!it.second)
continue;
m_active_objects.remove(it.first);
removeObject(it.first);
}
} while (!m_active_objects.empty());
}

View file

@ -105,7 +105,11 @@ void benchGetObjectsInArea(Catch::Benchmark::Chronometer &meter)
TEST_CASE("ActiveObjectMgr") {
BENCH_INSIDE_RADIUS(200)
BENCH_INSIDE_RADIUS(1450)
BENCH_INSIDE_RADIUS(10000)
BENCH_IN_AREA(200)
BENCH_IN_AREA(1450)
BENCH_IN_AREA(10000)
}
// TODO benchmark active object manager update costs

View file

@ -56,6 +56,7 @@ set(client_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/hud.cpp
${CMAKE_CURRENT_SOURCE_DIR}/imagefilters.cpp
${CMAKE_CURRENT_SOURCE_DIR}/inputhandler.cpp
${CMAKE_CURRENT_SOURCE_DIR}/item_visuals_manager.cpp
${CMAKE_CURRENT_SOURCE_DIR}/joystick_controller.cpp
${CMAKE_CURRENT_SOURCE_DIR}/keycode.cpp
${CMAKE_CURRENT_SOURCE_DIR}/localplayer.cpp

View file

@ -26,12 +26,18 @@
#include <IGUIFont.h>
#include <IVideoDriver.h>
#define CAMERA_OFFSET_STEP 200
static constexpr f32 CAMERA_OFFSET_STEP = 200;
#define WIELDMESH_OFFSET_X 55.0f
#define WIELDMESH_OFFSET_Y -35.0f
#define WIELDMESH_AMPLITUDE_X 7.0f
#define WIELDMESH_AMPLITUDE_Y 10.0f
static const char *setting_names[] = {
"view_bobbing_amount", "fov", "arm_inertia",
"show_nametag_backgrounds",
};
Camera::Camera(MapDrawControl &draw_control, Client *client, RenderingEngine *rendering_engine):
m_draw_control(draw_control),
m_client(client),
@ -53,27 +59,36 @@ Camera::Camera(MapDrawControl &draw_control, Client *client, RenderingEngine *re
m_wieldnode->setItem(ItemStack(), m_client);
m_wieldnode->drop(); // m_wieldmgr grabbed it
/* TODO: Add a callback function so these can be updated when a setting
* changes. At this point in time it doesn't matter (e.g. /set
* is documented to change server settings only)
*
* TODO: Local caching of settings is not optimal and should at some stage
m_nametags.clear();
readSettings();
for (auto name : setting_names)
g_settings->registerChangedCallback(name, settingChangedCallback, this);
}
void Camera::settingChangedCallback(const std::string &name, void *data)
{
static_cast<Camera *>(data)->readSettings();
}
void Camera::readSettings()
{
/* TODO: Local caching of settings is not optimal and should at some stage
* be updated to use a global settings object for getting thse values
* (as opposed to the this local caching). This can be addressed in
* a later release.
*/
m_cache_fall_bobbing_amount = g_settings->getFloat("fall_bobbing_amount", 0.0f, 100.0f);
m_cache_view_bobbing_amount = g_settings->getFloat("view_bobbing_amount", 0.0f, 7.9f);
// 45 degrees is the lowest FOV that doesn't cause the server to treat this
// as a zoom FOV and load world beyond the set server limits.
m_cache_fov = g_settings->getFloat("fov", 45.0f, 160.0f);
m_arm_inertia = g_settings->getBool("arm_inertia");
m_nametags.clear();
m_show_nametag_backgrounds = g_settings->getBool("show_nametag_backgrounds");
}
Camera::~Camera()
{
g_settings->deregisterAllChangedCallbacks(this);
m_wieldmgr->drop();
}
@ -114,19 +129,13 @@ inline f32 my_modf(f32 x)
void Camera::step(f32 dtime)
{
if(m_view_bobbing_fall > 0)
{
m_view_bobbing_fall -= 3 * dtime;
if(m_view_bobbing_fall <= 0)
m_view_bobbing_fall = -1; // Mark the effect as finished
}
bool was_under_zero = m_wield_change_timer < 0;
m_wield_change_timer = MYMIN(m_wield_change_timer + dtime, 0.125);
if (m_wield_change_timer >= 0 && was_under_zero) {
m_wieldnode->setItem(m_wield_item_next, m_client);
m_wieldnode->setNodeLightColor(m_player_light_color);
m_wieldnode->setLightColorAndAnimation(m_player_light_color,
m_client->getAnimationTime());
}
if (m_view_bobbing_state != 0)
@ -281,6 +290,20 @@ void Camera::addArmInertia(f32 player_yaw)
}
}
void Camera::updateOffset()
{
v3f cp = m_camera_position / BS;
// Update offset if too far away from the center of the map
m_camera_offset = v3s16(
floorf(cp.X / CAMERA_OFFSET_STEP) * CAMERA_OFFSET_STEP,
floorf(cp.Y / CAMERA_OFFSET_STEP) * CAMERA_OFFSET_STEP,
floorf(cp.Z / CAMERA_OFFSET_STEP) * CAMERA_OFFSET_STEP
);
// No need to update m_cameranode as that will be done before the next render.
}
void Camera::update(LocalPlayer* player, f32 frametime, f32 tool_reload_ratio)
{
// Get player position
@ -321,30 +344,13 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 tool_reload_ratio)
// Get camera tilt timer (hurt animation)
float cameratilt = fabs(fabs(player->hurt_tilt_timer-0.75)-0.75);
// Fall bobbing animation
float fall_bobbing = 0;
if(player->camera_impact >= 1 && m_camera_mode < CAMERA_MODE_THIRD)
{
if(m_view_bobbing_fall == -1) // Effect took place and has finished
player->camera_impact = m_view_bobbing_fall = 0;
else if(m_view_bobbing_fall == 0) // Initialize effect
m_view_bobbing_fall = 1;
// Convert 0 -> 1 to 0 -> 1 -> 0
fall_bobbing = m_view_bobbing_fall < 0.5 ? m_view_bobbing_fall * 2 : -(m_view_bobbing_fall - 0.5) * 2 + 1;
// Smoothen and invert the above
fall_bobbing = sin(fall_bobbing * 0.5 * M_PI) * -1;
// Amplify according to the intensity of the impact
if (player->camera_impact > 0.0f)
fall_bobbing *= (1 - rangelim(50 / player->camera_impact, 0, 1)) * 5;
fall_bobbing *= m_cache_fall_bobbing_amount;
}
// Calculate and translate the head SceneNode offsets
{
v3f eye_offset = player->getEyeOffset();
switch(m_camera_mode) {
case CAMERA_MODE_ANY:
assert(false);
break;
case CAMERA_MODE_FIRST:
eye_offset += player->eye_offset_first;
break;
@ -359,13 +365,14 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 tool_reload_ratio)
}
// Set head node transformation
eye_offset.Y += cameratilt * -player->hurt_tilt_strength + fall_bobbing;
eye_offset.Y += cameratilt * -player->hurt_tilt_strength;
m_headnode->setPosition(eye_offset);
m_headnode->setRotation(v3f(pitch, 0,
cameratilt * player->hurt_tilt_strength));
m_headnode->updateAbsolutePosition();
}
// Compute relative camera position and target
v3f rel_cam_pos = v3f(0,0,0);
v3f rel_cam_target = v3f(0,0,1);
@ -397,12 +404,11 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 tool_reload_ratio)
v3f abs_cam_up = m_headnode->getAbsoluteTransformation()
.rotateAndScaleVect(rel_cam_up);
// Separate camera position for calculation
v3f my_cp = m_camera_position;
// Reposition the camera for third person view
if (m_camera_mode > CAMERA_MODE_FIRST)
{
v3f my_cp = m_camera_position;
if (m_camera_mode == CAMERA_MODE_THIRD_FRONT)
m_camera_direction *= -1;
@ -434,27 +440,19 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 tool_reload_ratio)
// If node blocks camera position don't move y to heigh
if (abort && my_cp.Y > player_position.Y+BS*2)
my_cp.Y = player_position.Y+BS*2;
// update the camera position in third-person mode to render blocks behind player
// and correctly apply liquid post FX.
m_camera_position = my_cp;
}
// Update offset if too far away from the center of the map
m_camera_offset.X += CAMERA_OFFSET_STEP*
(((s16)(my_cp.X/BS) - m_camera_offset.X)/CAMERA_OFFSET_STEP);
m_camera_offset.Y += CAMERA_OFFSET_STEP*
(((s16)(my_cp.Y/BS) - m_camera_offset.Y)/CAMERA_OFFSET_STEP);
m_camera_offset.Z += CAMERA_OFFSET_STEP*
(((s16)(my_cp.Z/BS) - m_camera_offset.Z)/CAMERA_OFFSET_STEP);
// Set camera node transformation
m_cameranode->setPosition(my_cp-intToFloat(m_camera_offset, BS));
m_cameranode->updateAbsolutePosition();
m_cameranode->setPosition(m_camera_position - intToFloat(m_camera_offset, BS));
m_cameranode->setUpVector(abs_cam_up);
// *100.0 helps in large map coordinates
m_cameranode->setTarget(my_cp-intToFloat(m_camera_offset, BS) + 100 * m_camera_direction);
// update the camera position in third-person mode to render blocks behind player
// and correctly apply liquid post FX.
if (m_camera_mode != CAMERA_MODE_FIRST)
m_camera_position = my_cp;
m_cameranode->updateAbsolutePosition();
// *100 helps in large map coordinates
m_cameranode->setTarget(m_camera_position - intToFloat(m_camera_offset, BS)
+ 100 * m_camera_direction);
/*
* Apply server-sent FOV, instantaneous or smooth transition.
@ -540,7 +538,8 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 tool_reload_ratio)
m_wieldnode->setRotation(wield_rotation);
m_player_light_color = player->light_color;
m_wieldnode->setNodeLightColor(m_player_light_color);
m_wieldnode->setLightColorAndAnimation(m_player_light_color,
m_client->getAnimationTime());
// Set render distance
updateViewingRange();

View file

@ -57,8 +57,6 @@ struct Nametag
}
};
enum CameraMode {CAMERA_MODE_FIRST, CAMERA_MODE_THIRD, CAMERA_MODE_THIRD_FRONT};
/*
Client camera class, manages the player and camera scene nodes, the viewing distance
and performs view bobbing etc. It also displays the wielded tool in front of the
@ -70,6 +68,9 @@ public:
Camera(MapDrawControl &draw_control, Client *client, RenderingEngine *rendering_engine);
~Camera();
static void settingChangedCallback(const std::string &name, void *data);
void readSettings();
// Get camera scene node.
// It has the eye transformation, pitch and view bobbing applied.
inline scene::ICameraSceneNode* getCameraNode() const
@ -147,6 +148,9 @@ public:
// Update the camera from the local player's position.
void update(LocalPlayer* player, f32 frametime, f32 tool_reload_ratio);
// Adjust the camera offset when needed
void updateOffset();
// Update render distance
void updateViewingRange();
@ -163,7 +167,8 @@ public:
void drawWieldedTool(irr::core::matrix4* translation=NULL);
// Toggle the current camera mode
void toggleCameraMode() {
void toggleCameraMode()
{
if (m_camera_mode == CAMERA_MODE_FIRST)
m_camera_mode = CAMERA_MODE_THIRD;
else if (m_camera_mode == CAMERA_MODE_THIRD)
@ -179,7 +184,7 @@ public:
}
//read the current camera mode
inline CameraMode getCameraMode()
inline CameraMode getCameraMode() const
{
return m_camera_mode;
}
@ -251,8 +256,6 @@ private:
s32 m_view_bobbing_state = 0;
// Speed of view bobbing animation
f32 m_view_bobbing_speed = 0.0f;
// Fall view bobbing
f32 m_view_bobbing_fall = 0.0f;
// Digging animation frame (0 <= m_digging_anim < 1)
f32 m_digging_anim = 0.0f;
@ -267,7 +270,6 @@ private:
CameraMode m_camera_mode = CAMERA_MODE_FIRST;
f32 m_cache_fall_bobbing_amount;
f32 m_cache_view_bobbing_amount;
bool m_arm_inertia;

View file

@ -9,6 +9,7 @@
#include <IFileSystem.h>
#include <json/json.h>
#include "client.h"
#include "client/fontengine.h"
#include "network/clientopcodes.h"
#include "network/connection.h"
#include "network/networkpacket.h"
@ -52,6 +53,7 @@
#include "translation.h"
#include "content/mod_configuration.h"
#include "mapnode.h"
#include "item_visuals_manager.h"
extern gui::IGUIEnvironment* guienv;
@ -94,6 +96,7 @@ Client::Client(
ISoundManager *sound,
MtEventManager *event,
RenderingEngine *rendering_engine,
ItemVisualsManager *item_visuals_manager,
ELoginRegister allow_login_or_register
):
m_tsrc(tsrc),
@ -103,6 +106,7 @@ Client::Client(
m_sound(sound),
m_event(event),
m_rendering_engine(rendering_engine),
m_item_visuals_manager(item_visuals_manager),
m_mesh_update_manager(std::make_unique<MeshUpdateManager>(this)),
m_env(
make_irr<ClientMap>(this, rendering_engine, control, 666),
@ -345,6 +349,8 @@ Client::~Client()
// cleanup 3d model meshes on client shutdown
m_rendering_engine->cleanupMeshCache();
m_item_visuals_manager->clear();
guiScalingCacheClear();
delete m_minimap;
@ -361,10 +367,12 @@ Client::~Client()
for (auto &csp : m_sounds_client_to_server)
m_sound->freeId(csp.first);
m_sounds_client_to_server.clear();
// Go back to our mainmenu fonts
g_fontengine->clearMediaFonts();
}
void Client::connect(const Address &address, const std::string &address_name,
bool is_local_server)
void Client::connect(const Address &address, const std::string &address_name)
{
if (m_con) {
// can't do this if the connection has entered auth phase
@ -385,7 +393,7 @@ void Client::connect(const Address &address, const std::string &address_name,
m_con->Connect(address);
initLocalMapSaving(address, m_address_name, is_local_server);
initLocalMapSaving(address, m_address_name);
}
void Client::step(float dtime)
@ -394,11 +402,7 @@ void Client::step(float dtime)
if (dtime > DTIME_LIMIT)
dtime = DTIME_LIMIT;
m_animation_time += dtime;
if(m_animation_time > 60.0)
m_animation_time -= 60.0;
m_time_of_day_update_timer += dtime;
m_animation_time = fmodf(m_animation_time + dtime, 60.0f);
ReceiveAll();
@ -446,20 +450,43 @@ void Client::step(float dtime)
/*
Run Map's timers and unload unused data
*/
const float map_timer_and_unload_dtime = 5.25;
constexpr float map_timer_and_unload_dtime = 5.25f;
constexpr s32 mapblock_limit_enforce_distance = 200;
if(m_map_timer_and_unload_interval.step(dtime, map_timer_and_unload_dtime)) {
std::vector<v3s16> deleted_blocks;
// Determine actual block limit to use
const s32 configured_limit = g_settings->getS32("client_mapblock_limit");
s32 mapblock_limit;
if (configured_limit < 0) {
mapblock_limit = -1;
} else {
s32 view_range = g_settings->getS16("viewing_range");
// Up to a certain limit we want to guarantee that the client can keep
// a full 360° view loaded in memory without blocks vanishing behind
// the players back.
// We use a sphere volume to approximate this. In practice far less
// blocks will be needed due to occlusion/culling.
float blocks_range = ceilf(std::min(mapblock_limit_enforce_distance, view_range)
/ (float) MAP_BLOCKSIZE);
mapblock_limit = (4.f/3.f) * M_PI * powf(blocks_range, 3);
assert(mapblock_limit > 0);
mapblock_limit = std::max(mapblock_limit, configured_limit);
if (mapblock_limit > std::max(configured_limit, m_mapblock_limit_logged)) {
infostream << "Client: using block limit of " << mapblock_limit
<< " rather than configured " << configured_limit
<< " due to view range." << std::endl;
m_mapblock_limit_logged = mapblock_limit;
}
}
m_env.getMap().timerUpdate(map_timer_and_unload_dtime,
std::max(g_settings->getFloat("client_unload_unused_data_timeout"), 0.0f),
g_settings->getS32("client_mapblock_limit"),
&deleted_blocks);
mapblock_limit, &deleted_blocks);
/*
Send info to server
NOTE: This loop is intentionally iterated the way it is.
*/
// Send info to server
std::vector<v3s16>::iterator i = deleted_blocks.begin();
auto i = deleted_blocks.begin();
std::vector<v3s16> sendlist;
for(;;) {
if(sendlist.size() == 255 || i == deleted_blocks.end()) {
@ -564,7 +591,8 @@ void Client::step(float dtime)
std::vector<MinimapMapblock*> minimap_mapblocks;
bool do_mapper_update = true;
MapSector *sector = m_env.getMap().emergeSector(v2s16(r.p.X, r.p.Z));
ClientMap &map = m_env.getClientMap();
MapSector *sector = map.emergeSector(v2s16(r.p.X, r.p.Z));
MapBlock *block = sector->getBlockNoCreateNoEx(r.p.Y);
@ -576,6 +604,8 @@ void Client::step(float dtime)
if (block) {
// Delete the old mesh
if (block->mesh)
map.invalidateMapBlockMesh(block->mesh);
delete block->mesh;
block->mesh = nullptr;
block->solid_sides = r.solid_sides;
@ -590,9 +620,9 @@ void Client::step(float dtime)
if (r.mesh->getMesh(l)->getMeshBufferCount() != 0)
is_empty = false;
if (is_empty)
if (is_empty) {
delete r.mesh;
else {
} else {
// Replace with the new mesh
block->mesh = r.mesh;
if (r.urgent)
@ -637,9 +667,11 @@ void Client::step(float dtime)
if (num_processed_meshes > 0)
g_profiler->graphAdd("num_processed_meshes", num_processed_meshes);
auto shadow_renderer = RenderingEngine::get_shadow_renderer();
if (shadow_renderer && force_update_shadows)
shadow_renderer->setForceUpdateShadowMap();
if (force_update_shadows && !g_settings->getFlag("performance_tradeoffs")) {
auto shadow = RenderingEngine::get_shadow_renderer();
if (shadow)
shadow->setForceUpdateShadowMap();
};
}
/*
@ -834,6 +866,15 @@ bool Client::loadMedia(const std::string &data, const std::string &filename,
return true;
}
const char *font_ext[] = {".ttf", ".woff", NULL};
name = removeStringEnd(filename, font_ext);
if (!name.empty()) {
verbosestream<<"Client: Loading file as font: \""
<< filename << "\"" << std::endl;
g_fontengine->setMediaFont(name, data);
return true;
}
errorstream << "Client: Don't know how to load file \""
<< filename << "\"" << std::endl;
return false;
@ -859,14 +900,6 @@ void Client::deletingPeer(con::IPeer *peer, bool timeout)
m_access_denied_reason = gettext("Connection aborted (protocol error?).");
}
/*
u16 command
u16 number of files requested
for each file {
u16 length of name
string name
}
*/
void Client::request_media(const std::vector<std::string> &file_requests)
{
std::ostringstream os(std::ios_base::binary);
@ -875,7 +908,6 @@ void Client::request_media(const std::vector<std::string> &file_requests)
FATAL_ERROR_IF(file_requests_size > 0xFFFF, "Unsupported number of file requests");
// Packet dynamicly resized
NetworkPacket pkt(TOSERVER_REQUEST_MEDIA, 2 + 0);
pkt << (u16) (file_requests_size & 0xFFFF);
@ -891,11 +923,9 @@ void Client::request_media(const std::vector<std::string> &file_requests)
<< pkt.getSize() << ")" << std::endl;
}
void Client::initLocalMapSaving(const Address &address,
const std::string &hostname,
bool is_local_server)
void Client::initLocalMapSaving(const Address &address, const std::string &hostname)
{
if (!g_settings->getBool("enable_local_map_saving") || is_local_server) {
if (!g_settings->getBool("enable_local_map_saving") || m_internal_server) {
return;
}
if (m_localdb) {
@ -1017,8 +1047,8 @@ void Client::Send(NetworkPacket* pkt)
// Will fill up 12 + 12 + 4 + 4 + 4 + 1 + 1 + 1 + 4 + 4 bytes
void writePlayerPos(LocalPlayer *myplayer, ClientMap *clientMap, NetworkPacket *pkt, bool camera_inverted)
{
v3f pf = myplayer->getPosition() * 100;
v3f sf = myplayer->getSpeed() * 100;
v3s32 position = v3s32::from(myplayer->getPosition() * 100);
v3s32 speed = v3s32::from(myplayer->getSpeed() * 100);
s32 pitch = myplayer->getPitch() * 100;
s32 yaw = myplayer->getYaw() * 100;
u32 keyPressed = myplayer->control.getKeysPressed();
@ -1029,9 +1059,6 @@ void writePlayerPos(LocalPlayer *myplayer, ClientMap *clientMap, NetworkPacket *
f32 movement_speed = myplayer->control.movement_speed;
f32 movement_dir = myplayer->control.movement_direction;
v3s32 position(pf.X, pf.Y, pf.Z);
v3s32 speed(sf.X, sf.Y, sf.Z);
/*
Format:
[0] v3s32 position*100
@ -1619,11 +1646,6 @@ void Client::inventoryAction(InventoryAction *a)
delete a;
}
float Client::getAnimationTime()
{
return m_animation_time;
}
int Client::getCrackLevel()
{
return m_crack_level;
@ -1728,12 +1750,7 @@ void Client::addUpdateMeshTaskWithEdge(v3s16 blockpos, bool ack_to_server, bool
void Client::addUpdateMeshTaskForNode(v3s16 nodepos, bool ack_to_server, bool urgent)
{
{
v3s16 p = nodepos;
infostream<<"Client::addUpdateMeshTaskForNode(): "
<<"("<<p.X<<","<<p.Y<<","<<p.Z<<")"
<<std::endl;
}
infostream << "Client::addUpdateMeshTaskForNode(): " << nodepos << std::endl;
v3s16 blockpos = getNodeBlockPos(nodepos);
v3s16 blockpos_relative = blockpos * MAP_BLOCKSIZE;
@ -1747,11 +1764,6 @@ void Client::addUpdateMeshTaskForNode(v3s16 nodepos, bool ack_to_server, bool ur
addUpdateMeshTask(blockpos + v3s16(0, 0, -1), false, urgent);
}
void Client::updateCameraOffset(v3s16 camera_offset)
{
m_mesh_update_manager->m_camera_offset = camera_offset;
}
ClientEvent *Client::getClientEvent()
{
FATAL_ERROR_IF(m_client_event_queue.empty(),

View file

@ -55,6 +55,7 @@ struct MeshMakeData;
struct MinimapMapblock;
struct PlayerControl;
struct PointedThing;
struct ItemVisualsManager;
namespace con {
class IConnection;
@ -118,6 +119,7 @@ public:
ISoundManager *sound,
MtEventManager *event,
RenderingEngine *rendering_engine,
ItemVisualsManager *item_visuals,
ELoginRegister allow_login_or_register
);
@ -140,8 +142,7 @@ public:
bool isShutdown();
void connect(const Address &address, const std::string &address_name,
bool is_local_server);
void connect(const Address &address, const std::string &address_name);
/*
Stuff that references the environment is valid only as
@ -217,6 +218,7 @@ public:
void handleCommand_MediaPush(NetworkPacket *pkt);
void handleCommand_MinimapModes(NetworkPacket *pkt);
void handleCommand_SetLighting(NetworkPacket *pkt);
void handleCommand_Camera(NetworkPacket* pkt);
void ProcessData(NetworkPacket *pkt);
@ -276,7 +278,10 @@ public:
return m_env.getPlayerNames();
}
float getAnimationTime();
float getAnimationTime() const
{
return m_animation_time;
}
int getCrackLevel();
v3s16 getCrackPos();
@ -293,15 +298,13 @@ public:
bool getChatMessage(std::wstring &message);
void typeChatMessage(const std::wstring& message);
u64 getMapSeed(){ return m_map_seed; }
u64 getMapSeed() const { return m_map_seed; }
void addUpdateMeshTask(v3s16 blockpos, bool ack_to_server=false, bool urgent=false);
// Including blocks at appropriate edges
void addUpdateMeshTaskWithEdge(v3s16 blockpos, bool ack_to_server=false, bool urgent=false);
void addUpdateMeshTaskForNode(v3s16 nodepos, bool ack_to_server=false, bool urgent=false);
void updateCameraOffset(v3s16 camera_offset);
bool hasClientEvents() const { return !m_client_event_queue.empty(); }
// Get event from queue. If queue is empty, it triggers an assertion failure.
ClientEvent * getClientEvent();
@ -336,8 +339,17 @@ public:
u16 getProtoVersion() const
{ return m_proto_ver; }
// Whether the server is in "simple singleplayer mode".
// This implies "m_internal_server = true".
bool m_simple_singleplayer_mode;
// Whether the server is hosted by the same Luanti instance and singletons
// like g_settings are shared between client and server.
//
// This is intentionally *not* true if we're just connecting to a localhost
// server hosted by a different Luanti instance.
bool m_internal_server;
float mediaReceiveProgress();
void drawLoadScreen(const std::wstring &text, float dtime, int percent);
@ -373,6 +385,8 @@ public:
const std::string* getModFile(std::string filename);
ModStorageDatabase *getModStorageDatabase() override { return m_mod_storage_database; }
ItemVisualsManager *getItemVisualsManager() { return m_item_visuals_manager; }
// Migrates away old files-based mod storage if necessary
void migrateModStorage();
@ -439,9 +453,7 @@ private:
void peerAdded(con::IPeer *peer) override;
void deletingPeer(con::IPeer *peer, bool timeout) override;
void initLocalMapSaving(const Address &address,
const std::string &hostname,
bool is_local_server);
void initLocalMapSaving(const Address &address, const std::string &hostname);
void ReceiveAll();
@ -472,6 +484,7 @@ private:
ISoundManager *m_sound;
MtEventManager *m_event;
RenderingEngine *m_rendering_engine;
ItemVisualsManager *m_item_visuals_manager;
std::unique_ptr<MeshUpdateManager> m_mesh_update_manager;
@ -487,23 +500,18 @@ private:
u8 m_server_ser_ver;
// Used version of the protocol with server
// Values smaller than 25 only mean they are smaller than 25,
// and aren't accurate. We simply just don't know, because
// the server didn't send the version back then.
// If 0, server init hasn't been received yet.
u16 m_proto_ver = 0;
bool m_update_wielded_item = false;
Inventory *m_inventory_from_server = nullptr;
float m_inventory_from_server_age = 0.0f;
s32 m_mapblock_limit_logged = 0;
PacketCounter m_packetcounter;
// Block mesh animation parameters
float m_animation_time = 0.0f;
int m_crack_level = -1;
v3s16 m_crack_pos;
// 0 <= m_daynight_i < DAYNIGHT_CACHE_COUNT
//s32 m_daynight_i;
//u32 m_daynight_ratio;
std::queue<std::wstring> m_out_chat_queue;
u32 m_last_chat_message_sent;
float m_chat_message_allowance = 5.0f;
@ -539,11 +547,6 @@ private:
// Pending downloads of dynamic media (key: token)
std::vector<std::pair<u32, std::shared_ptr<SingleMediaDownloader>>> m_pending_media_downloads;
// time_of_day speed approximation for old protocol
bool m_time_of_day_set = false;
float m_last_time_of_day_f = -1.0f;
float m_time_of_day_update_timer = 0.0f;
// An interval for generally sending object positions and stuff
float m_recommended_send_interval = 0.1f;

View file

@ -441,8 +441,8 @@ void ClientEnvironment::getSelectedActiveObjects(
GenericCAO* gcao = dynamic_cast<GenericCAO*>(obj);
if (gcao != nullptr && gcao->getProperties().rotate_selectionbox) {
gcao->getSceneNode()->updateAbsolutePosition();
const v3f deg = obj->getSceneNode()->getAbsoluteTransformation().getRotationDegrees();
collision = boxLineCollision(selection_box, deg,
const v3f rad = obj->getSceneNode()->getAbsoluteTransformation().getRotationRadians();
collision = boxLineCollision(selection_box, rad,
rel_pos, line_vector, &current_intersection, &current_normal, &current_raw_normal);
} else {
collision = boxLineCollision(selection_box, rel_pos, line_vector,

View file

@ -22,7 +22,8 @@ enum ClientEventType : u8
CE_PLAYER_FORCE_MOVE,
CE_DEATHSCREEN_LEGACY,
CE_SHOW_FORMSPEC,
CE_SHOW_LOCAL_FORMSPEC,
CE_SHOW_CSM_FORMSPEC,
CE_SHOW_PAUSE_MENU_FORMSPEC,
CE_SPAWN_PARTICLE,
CE_ADD_PARTICLESPAWNER,
CE_DELETE_PARTICLESPAWNER,
@ -35,6 +36,7 @@ enum ClientEventType : u8
CE_SET_STARS,
CE_OVERRIDE_DAY_NIGHT_RATIO,
CE_CLOUD_PARAMS,
CE_UPDATE_CAMERA,
CLIENTEVENT_MAX,
};
@ -65,11 +67,14 @@ struct ClientEventHudChange
struct ClientEvent
{
// TODO: should get rid of this ctor
ClientEvent() : type(CE_NONE) {}
ClientEvent(ClientEventType type) : type(type) {}
ClientEventType type;
union
{
// struct{
//} none;
struct
{
u16 amount;
@ -85,8 +90,6 @@ struct ClientEvent
std::string *formspec;
std::string *formname;
} show_formspec;
// struct{
//} textures_updated;
ParticleParameters *spawn_particle;
struct
{

View file

@ -122,6 +122,13 @@ namespace {
}
}
void CachedMeshBuffer::drop()
{
for (auto *it : buf)
it->drop();
buf.clear();
}
/*
ClientMap
*/
@ -191,10 +198,14 @@ void ClientMap::onSettingChanged(std::string_view name, bool all)
ClientMap::~ClientMap()
{
g_settings->deregisterAllChangedCallbacks(this);
for (auto &it : m_dynamic_buffers)
it.second.drop();
}
void ClientMap::updateCamera(v3f pos, v3f dir, f32 fov, v3s16 offset, video::SColor light_color)
{
v3s16 previous_camera_offset = m_camera_offset;
v3s16 previous_node = floatToInt(m_camera_position, BS) + m_camera_offset;
v3s16 previous_block = getContainerPos(previous_node, MAP_BLOCKSIZE);
@ -214,6 +225,13 @@ void ClientMap::updateCamera(v3f pos, v3f dir, f32 fov, v3s16 offset, video::SCo
// reorder transparent meshes when camera crosses node boundary
if (previous_node != current_node)
m_needs_update_transparent_meshes = true;
// drop merged mesh cache when camera offset changes
if (previous_camera_offset != m_camera_offset) {
for (auto &it : m_dynamic_buffers)
it.second.drop();
m_dynamic_buffers.clear();
}
}
MapSector * ClientMap::emergeSector(v2s16 p2d)
@ -788,27 +806,24 @@ void MeshBufListMaps::addFromBlock(v3s16 block_pos, MapBlockMesh *block_mesh,
* @param src buffer list
* @param dst draw order
* @param get_world_pos returns translation for a buffer
* @param buffer_trash output container for temporary mesh buffers
* @param dynamic_buffers cache structure for merged buffers
* @return number of buffers that were merged
*/
template <typename F, typename C>
template <typename F>
static u32 transformBuffersToDrawOrder(
const MeshBufListMaps::MeshBufList &src, DrawDescriptorList &draw_order,
F get_world_pos, C &buffer_trash)
F get_world_pos, CachedMeshBuffers &dynamic_buffers)
{
/**
* This is a tradeoff between time spent merging buffers and time spent
* due to excess drawcalls.
* Testing has shown that the ideal value is in the low hundreds, as extra
* CPU work quickly eats up the benefits.
* CPU work quickly eats up the benefits (though alleviated by a cache).
* In MTG landscape scenes this was found to save around 20-40% of drawcalls.
*
* NOTE: if you attempt to test this with quicktune, it won't give you valid
* results since HW buffers stick around and Irrlicht handles large amounts
* inefficiently.
*
* TODO: as a next step we should cache merged meshes, so they do not need
* to be re-built *and* can be kept in GPU memory.
*/
const u32 target_min_vertices = g_settings->getU32("mesh_buffer_min_vertices");
@ -826,23 +841,8 @@ static u32 transformBuffersToDrawOrder(
}
}
scene::SMeshBuffer *tmp = nullptr;
const auto &finish_buf = [&] () {
if (tmp) {
draw_order.emplace_back(v3f(0), tmp);
total_vtx = subtract_or_zero(total_vtx, tmp->getVertexCount());
total_idx = subtract_or_zero(total_idx, tmp->getIndexCount());
// Upload buffer here explicitly to give the driver some
// extra time to get it ready before drawing.
tmp->setHardwareMappingHint(scene::EHM_STREAM);
driver->updateHardwareBuffer(tmp->getVertexBuffer());
driver->updateHardwareBuffer(tmp->getIndexBuffer());
}
tmp = nullptr;
};
// iterate in reverse to get closest blocks first
std::vector<std::pair<v3f, scene::IMeshBuffer*>> to_merge;
for (auto it = src.rbegin(); it != src.rend(); ++it) {
v3f translate = get_world_pos(it->first);
auto *buf = it->second;
@ -850,25 +850,83 @@ static u32 transformBuffersToDrawOrder(
draw_order.emplace_back(translate, buf);
continue;
}
bool new_buffer = false;
if (!tmp)
new_buffer = true;
else if (tmp->getVertexCount() + buf->getVertexCount() > U16_MAX)
new_buffer = true;
if (new_buffer) {
finish_buf();
tmp = new scene::SMeshBuffer();
buffer_trash.push_back(tmp);
assert(tmp->getPrimitiveType() == buf->getPrimitiveType());
tmp->Material = buf->getMaterial();
// preallocate
tmp->Vertices->Data.reserve(total_vtx);
tmp->Indices->Data.reserve(total_idx);
}
appendToMeshBuffer(tmp, buf, translate);
to_merge.emplace_back(translate, buf);
}
/*
* Tracking buffers, their contents and modifications would be quite complicated
* so we opt for something simple here: We identify buffers by their location
* in memory.
* This imposes the following assumptions:
* - buffers don't move in memory
* - vertex and index data is immutable
* - we know when to invalidate (invalidateMapBlockMesh does this)
*/
std::sort(to_merge.begin(), to_merge.end(), [] (const auto &l, const auto &r) {
return static_cast<void*>(l.second) < static_cast<void*>(r.second);
});
// cache key is a string of sorted raw pointers
std::string key;
key.reserve(sizeof(void*) * to_merge.size());
for (auto &it : to_merge)
key.append(reinterpret_cast<const char*>(&it.second), sizeof(void*));
// try to take from cache
auto it2 = dynamic_buffers.find(key);
if (it2 != dynamic_buffers.end()) {
g_profiler->avg("CM::transformBuffersToDO: cache hit rate", 1);
const auto &use_mat = to_merge.front().second->getMaterial();
assert(!it2->second.buf.empty());
for (auto *buf : it2->second.buf) {
// material is not part of the cache key, so make sure it still matches
buf->getMaterial() = use_mat;
draw_order.emplace_back(v3f(0), buf);
}
it2->second.age = 0;
} else if (!key.empty()) {
g_profiler->avg("CM::transformBuffersToDO: cache hit rate", 0);
// merge and save to cache
auto &put_buffers = dynamic_buffers[key];
scene::SMeshBuffer *tmp = nullptr;
const auto &finish_buf = [&] () {
if (tmp) {
draw_order.emplace_back(v3f(0), tmp);
total_vtx = subtract_or_zero(total_vtx, tmp->getVertexCount());
total_idx = subtract_or_zero(total_idx, tmp->getIndexCount());
// Upload buffer here explicitly to give the driver some
// extra time to get it ready before drawing.
tmp->setHardwareMappingHint(scene::EHM_STREAM);
driver->updateHardwareBuffer(tmp->getVertexBuffer());
driver->updateHardwareBuffer(tmp->getIndexBuffer());
}
tmp = nullptr;
};
for (auto &it : to_merge) {
v3f translate = it.first;
auto *buf = it.second;
bool new_buffer = false;
if (!tmp)
new_buffer = true;
else if (tmp->getVertexCount() + buf->getVertexCount() > U16_MAX)
new_buffer = true;
if (new_buffer) {
finish_buf();
tmp = new scene::SMeshBuffer();
put_buffers.buf.push_back(tmp);
assert(tmp->getPrimitiveType() == buf->getPrimitiveType());
tmp->Material = buf->getMaterial();
// preallocate approximately
tmp->Vertices->Data.reserve(MYMIN(U16_MAX, total_vtx));
tmp->Indices->Data.reserve(total_idx);
}
appendToMeshBuffer(tmp, buf, translate);
}
finish_buf();
assert(!put_buffers.buf.empty());
}
finish_buf();
// first call needs to set the material
if (draw_order.size() > draw_order_pre)
@ -921,7 +979,6 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
TimeTaker tt_collect("");
MeshBufListMaps grouped_buffers;
std::vector<scene::IMeshBuffer*> buffer_trash;
DrawDescriptorList draw_order;
auto is_frustum_culled = m_client->getCamera()->getFrustumCuller();
@ -979,7 +1036,7 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
for (auto &map : grouped_buffers.maps) {
for (auto &list : map) {
merged_count += transformBuffersToDrawOrder(
list.second, draw_order, get_block_wpos, buffer_trash);
list.second, draw_order, get_block_wpos, m_dynamic_buffers);
}
}
@ -1036,6 +1093,20 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
if (pass == scene::ESNRP_SOLID) {
g_profiler->avg("renderMap(): animated meshes [#]", mesh_animate_count);
g_profiler->avg(prefix + "merged buffers [#]", merged_count);
u32 cached_count = 0;
for (auto it = m_dynamic_buffers.begin(); it != m_dynamic_buffers.end(); ) {
// prune aggressively since every new/changed block or camera
// rotation can have big effects
if (++it->second.age > 1) {
it->second.drop();
it = m_dynamic_buffers.erase(it);
} else {
cached_count += it->second.buf.size();
it++;
}
}
g_profiler->avg(prefix + "merged buffers in cache [#]", cached_count);
}
if (pass == scene::ESNRP_TRANSPARENT) {
@ -1045,9 +1116,51 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
g_profiler->avg(prefix + "vertices drawn [#]", vertex_count);
g_profiler->avg(prefix + "drawcalls [#]", drawcall_count);
g_profiler->avg(prefix + "material swaps [#]", material_swaps);
}
for (auto &x : buffer_trash)
x->drop();
void ClientMap::invalidateMapBlockMesh(MapBlockMesh *mesh)
{
// find all buffers for this block
MeshBufListMaps tmp;
tmp.addFromBlock(v3s16(), mesh, getSceneManager()->getVideoDriver());
std::vector<void*> to_delete;
void *maxp = 0;
for (auto &it : tmp.maps) {
for (auto &it2 : it) {
for (auto &it3 : it2.second) {
void *const p = it3.second; // explicit downcast
to_delete.push_back(p);
maxp = std::max(maxp, p);
}
}
}
if (to_delete.empty())
return;
// we know which buffers were used to produce a merged buffer
// so go through the cache and drop any entries that match
const auto &match_any = [&] (const std::string &key) {
assert(key.size() % sizeof(void*) == 0);
void *v;
for (size_t off = 0; off < key.size(); off += sizeof(void*)) {
// no alignment guarantee so *(void**)&key[off] is not allowed!
memcpy(&v, &key[off], sizeof(void*));
if (v > maxp) // early exit, since it's sorted
break;
if (CONTAINS(to_delete, v))
return true;
}
return false;
};
for (auto it = m_dynamic_buffers.begin(); it != m_dynamic_buffers.end(); ) {
if (match_any(it->first)) {
it->second.drop();
it = m_dynamic_buffers.erase(it);
} else {
it++;
}
}
}
static bool getVisibleBrightness(Map *map, const v3f &p0, v3f dir, float step,
@ -1263,7 +1376,6 @@ void ClientMap::renderMapShadows(video::IVideoDriver *driver,
};
MeshBufListMaps grouped_buffers;
std::vector<scene::IMeshBuffer*> buffer_trash;
DrawDescriptorList draw_order;
std::size_t count = 0;
@ -1308,7 +1420,7 @@ void ClientMap::renderMapShadows(video::IVideoDriver *driver,
for (auto &map : grouped_buffers.maps) {
for (auto &list : map) {
transformBuffersToDrawOrder(
list.second, draw_order, get_block_wpos, buffer_trash);
list.second, draw_order, get_block_wpos, m_dynamic_buffers);
}
}
@ -1373,9 +1485,6 @@ void ClientMap::renderMapShadows(video::IVideoDriver *driver,
g_profiler->avg(prefix + "vertices drawn [#]", vertex_count);
g_profiler->avg(prefix + "drawcalls [#]", drawcall_count);
g_profiler->avg(prefix + "material swaps [#]", material_swaps);
for (auto &x : buffer_trash)
x->drop();
}
/*

View file

@ -36,6 +36,16 @@ namespace irr::video
class IVideoDriver;
}
struct CachedMeshBuffer {
std::vector<scene::IMeshBuffer*> buf;
u8 age = 0;
void drop();
};
using CachedMeshBuffers = std::unordered_map<std::string, CachedMeshBuffer>;
/*
ClientMap
@ -95,6 +105,8 @@ public:
void renderPostFx(CameraMode cam_mode);
void invalidateMapBlockMesh(MapBlockMesh *mesh);
// For debug printing
void PrintInfo(std::ostream &out) override;
@ -151,6 +163,7 @@ private:
std::vector<MapBlock*> m_keeplist;
std::map<v3s16, MapBlock*> m_drawlist_shadow;
bool m_needs_update_drawlist;
CachedMeshBuffers m_dynamic_buffers;
bool m_cache_trilinear_filter;
bool m_cache_bilinear_filter;

View file

@ -154,7 +154,7 @@ void ClientMediaDownloader::step(Client *client)
startRemoteMediaTransfers();
// Did all remote transfers end and no new ones can be started?
// If so, request still missing files from the minetest server
// If so, request still missing files from the server
// (Or report that we have all files.)
if (m_httpfetch_active == 0) {
if (m_uncached_received_count < m_uncached_count) {
@ -168,6 +168,15 @@ void ClientMediaDownloader::step(Client *client)
}
}
std::string ClientMediaDownloader::makeReferer(Client *client)
{
std::string addr = client->getAddressName();
if (addr.find(':') != std::string::npos)
addr = '[' + addr + ']';
return std::string("minetest://") + addr + ':' +
std::to_string(client->getServerAddress().getPort());
}
void ClientMediaDownloader::initialStep(Client *client)
{
std::wstring loading_text = wstrgettext("Media...");
@ -227,9 +236,14 @@ void ClientMediaDownloader::initialStep(Client *client)
m_httpfetch_active_limit = g_settings->getS32("curl_parallel_limit");
m_httpfetch_active_limit = MYMAX(m_httpfetch_active_limit, 84);
// Write a list of hashes that we need. This will be POSTed
// to the server using Content-Type: application/octet-stream
std::string required_hash_set = serializeRequiredHashSet();
// Note: we used to use a POST request that contained the set of
// hashes we needed here, but this use was discontinued in 5.12.0 as
// it's not CDN/static hosting-friendly.
// Even with a large repository of media (think 60k files), you would be
// looking at only 1.1 MB for the index file.
// If it becomes a problem we can always still introduce a v2 of the
// hash set format and truncate the hashes to 6 bytes -- at the cost of
// a false-positive rate of 2^-48 -- which is 70% less space.
// minor fixme: this loop ignores m_httpfetch_active_limit
@ -251,19 +265,8 @@ void ClientMediaDownloader::initialStep(Client *client)
remote->baseurl + MTHASHSET_FILE_NAME;
fetch_request.caller = m_httpfetch_caller;
fetch_request.request_id = m_httpfetch_next_id; // == i
fetch_request.method = HTTP_POST;
fetch_request.raw_data = required_hash_set;
fetch_request.extra_headers.emplace_back(
"Content-Type: application/octet-stream");
// Encapsulate possible IPv6 plain address in []
std::string addr = client->getAddressName();
if (addr.find(':', 0) != std::string::npos)
addr = '[' + addr + ']';
fetch_request.extra_headers.emplace_back(
std::string("Referer: minetest://") +
addr + ":" +
std::to_string(client->getServerAddress().getPort()));
"Referer: " + makeReferer(client));
httpfetch_async(fetch_request);
@ -585,25 +588,6 @@ bool IClientMediaDownloader::checkAndLoad(
1 - Initial version
*/
std::string ClientMediaDownloader::serializeRequiredHashSet()
{
std::ostringstream os(std::ios::binary);
writeU32(os, MTHASHSET_FILE_SIGNATURE); // signature
writeU16(os, 1); // version
// Write list of hashes of files that have not been
// received (found in cache) yet
for (const auto &it : m_files) {
if (!it.second->received) {
FATAL_ERROR_IF(it.second->sha1.size() != 20, "Invalid SHA1 size");
os << it.second->sha1;
}
}
return os.str();
}
void ClientMediaDownloader::deSerializeHashSet(const std::string &data,
std::set<std::string> &result)
{

View file

@ -119,6 +119,8 @@ protected:
bool loadMedia(Client *client, const std::string &data,
const std::string &name) override;
static std::string makeReferer(Client *client);
private:
struct FileStatus {
bool received;
@ -142,7 +144,6 @@ private:
static void deSerializeHashSet(const std::string &data,
std::set<std::string> &result);
std::string serializeRequiredHashSet();
// Maps filename to file status
std::map<std::string, FileStatus*> m_files;

View file

@ -75,8 +75,8 @@ public:
Client *client, ClientEnvironment *env);
// If returns true, punch will not be sent to the server
virtual bool directReportPunch(v3f dir, const ItemStack *punchitem = nullptr,
float time_from_last_punch = 1000000) { return false; }
virtual bool directReportPunch(v3f dir, const ItemStack *punchitem,
const ItemStack *hand_item, float time_from_last_punch = 1000000) { return false; }
protected:
// Used for creating objects based on type

View file

@ -52,6 +52,13 @@ Clouds::Clouds(scene::ISceneManager* mgr, IShaderSource *ssrc,
updateBox();
// Neither EAC_BOX (the default) nor EAC_FRUSTUM_BOX will correctly cull
// the clouds.
// And yes, the bounding box is correct. You can check using the #if 0'd
// code in render() and see for yourself.
// So I give up and let's disable culling.
setAutomaticCulling(scene::EAC_OFF);
m_meshbuffer.reset(new scene::SMeshBuffer());
m_meshbuffer->setHardwareMappingHint(scene::EHM_DYNAMIC);
}
@ -366,6 +373,19 @@ void Clouds::render()
if (SceneManager->getSceneNodeRenderPass() != scene::ESNRP_TRANSPARENT)
return;
#if 0
{
video::SMaterial tmp;
tmp.Thickness = 1.f;
driver->setTransform(video::ETS_WORLD, core::IdentityMatrix);
driver->setMaterial(tmp);
aabb3f tmpbox = m_box;
tmpbox.MinEdge.X = tmpbox.MinEdge.Z = -1000 * BS;
tmpbox.MaxEdge.X = tmpbox.MaxEdge.Z = 1000 * BS;
driver->draw3DBox(tmpbox, video::SColor(255, 255, 0x4d, 0));
}
#endif
updateMesh();
// Update position
@ -425,14 +445,14 @@ void Clouds::update(const v3f &camera_p, const video::SColorf &color_diffuse)
// is the camera inside the cloud mesh?
m_camera_pos = camera_p;
m_camera_inside_cloud = false; // default
m_camera_inside_cloud = false;
if (is3D()) {
float camera_height = camera_p.Y - BS * m_camera_offset.Y;
if (camera_height >= m_box.MinEdge.Y &&
camera_height <= m_box.MaxEdge.Y) {
v2f camera_in_noise;
camera_in_noise.X = floor((camera_p.X - m_origin.X) / cloud_size + 0.5);
camera_in_noise.Y = floor((camera_p.Z - m_origin.Y) / cloud_size + 0.5);
camera_in_noise.X = floorf((camera_p.X - m_origin.X) / cloud_size + 0.5f);
camera_in_noise.Y = floorf((camera_p.Z - m_origin.Y) / cloud_size + 0.5f);
bool filled = gridFilled(camera_in_noise.X, camera_in_noise.Y);
m_camera_inside_cloud = filled;
}
@ -454,7 +474,7 @@ void Clouds::readSettings()
bool Clouds::gridFilled(int x, int y) const
{
float cloud_size_noise = cloud_size / (BS * 200.f);
float noise = noise2d_perlin(
float noise = noise2d_fractal(
(float)x * cloud_size_noise,
(float)y * cloud_size_noise,
m_seed, 3, 0.5);

View file

@ -134,8 +134,11 @@ private:
{
float height_bs = m_params.height * BS;
float thickness_bs = m_params.thickness * BS;
m_box = aabb3f(-BS * 1000000.0f, height_bs, -BS * 1000000.0f,
BS * 1000000.0f, height_bs + thickness_bs, BS * 1000000.0f);
float far_bs = 1000000.0f * BS;
m_box = aabb3f(-far_bs, height_bs, -far_bs,
far_bs, height_bs + thickness_bs, far_bs);
m_box.MinEdge -= v3f::from(m_camera_offset) * BS;
m_box.MaxEdge -= v3f::from(m_camera_offset) * BS;
}
void updateMesh();

View file

@ -12,6 +12,8 @@
#include "client/sound.h"
#include "client/texturesource.h"
#include "client/mapblock_mesh.h"
#include "client/content_mapblock.h"
#include "client/meshgen/collector.h"
#include "util/basic_macros.h"
#include "util/numeric.h"
#include "util/serialize.h"
@ -33,6 +35,9 @@
#include "client/shader.h"
#include "client/minimap.h"
#include <quaternion.h>
#include <SMesh.h>
#include <IMeshBuffer.h>
#include <SMeshBuffer.h>
class Settings;
struct ToolCapabilities;
@ -177,137 +182,53 @@ static void setColorParam(scene::ISceneNode *node, video::SColor color)
node->getMaterial(i).ColorParam = color;
}
/*
TestCAO
*/
class TestCAO : public ClientActiveObject
static scene::SMesh *generateNodeMesh(Client *client, MapNode n,
std::vector<MeshAnimationInfo> &animation)
{
public:
TestCAO(Client *client, ClientEnvironment *env);
virtual ~TestCAO() = default;
auto *ndef = client->ndef();
auto *shdsrc = client->getShaderSource();
ActiveObjectType getType() const
MeshCollector collector(v3f(0), v3f());
{
return ACTIVEOBJECT_TYPE_TEST;
MeshMakeData mmd(ndef, 1, MeshGrid{1});
n.setParam1(0xff);
mmd.fillSingleNode(n);
MapblockMeshGenerator(&mmd, &collector).generate();
}
static std::unique_ptr<ClientActiveObject> create(Client *client, ClientEnvironment *env);
auto mesh = make_irr<scene::SMesh>();
animation.clear();
for (int layer = 0; layer < MAX_TILE_LAYERS; layer++) {
for (PreMeshBuffer &p : collector.prebuffers[layer]) {
// reset the pre-computed light data stored in the vertex color,
// since we do that ourselves via updateLight().
for (auto &v : p.vertices)
v.Color.set(0xFFFFFFFF);
// but still apply the tile color
p.applyTileColor();
void addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr);
void removeFromScene(bool permanent);
void updateLight(u32 day_night_ratio);
void updateNodePos();
if (p.layer.material_flags & MATERIAL_FLAG_ANIMATION) {
const FrameSpec &frame = (*p.layer.frames)[0];
p.layer.texture = frame.texture;
void step(float dtime, ClientEnvironment *env);
animation.emplace_back(MeshAnimationInfo{mesh->getMeshBufferCount(), 0, p.layer});
}
void processMessage(const std::string &data);
auto buf = make_irr<scene::SMeshBuffer>();
buf->append(&p.vertices[0], p.vertices.size(),
&p.indices[0], p.indices.size());
bool getCollisionBox(aabb3f *toset) const { return false; }
private:
scene::IMeshSceneNode *m_node;
v3f m_position;
};
// Set up material
auto &mat = buf->Material;
u32 shader_id = shdsrc->getShader("object_shader", p.layer.material_type, NDT_NORMAL);
mat.MaterialType = shdsrc->getShaderInfo(shader_id).material;
p.layer.applyMaterialOptions(mat, layer);
// Prototype
static TestCAO proto_TestCAO(nullptr, nullptr);
TestCAO::TestCAO(Client *client, ClientEnvironment *env):
ClientActiveObject(0, client, env),
m_node(NULL),
m_position(v3f(0,10*BS,0))
{
if (!client)
ClientActiveObject::registerType(getType(), create);
}
std::unique_ptr<ClientActiveObject> TestCAO::create(Client *client, ClientEnvironment *env)
{
return std::make_unique<TestCAO>(client, env);
}
void TestCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr)
{
if(m_node != NULL)
return;
//video::IVideoDriver* driver = smgr->getVideoDriver();
scene::SMesh *mesh = new scene::SMesh();
scene::IMeshBuffer *buf = new scene::SMeshBuffer();
video::SColor c(255,255,255,255);
video::S3DVertex vertices[4] =
{
video::S3DVertex(-BS/2,-BS/4,0, 0,0,0, c, 0,1),
video::S3DVertex(BS/2,-BS/4,0, 0,0,0, c, 1,1),
video::S3DVertex(BS/2,BS/4,0, 0,0,0, c, 1,0),
video::S3DVertex(-BS/2,BS/4,0, 0,0,0, c, 0,0),
};
u16 indices[] = {0,1,2,2,3,0};
buf->append(vertices, 4, indices, 6);
// Set material
buf->getMaterial().BackfaceCulling = false;
buf->getMaterial().TextureLayers[0].Texture = tsrc->getTextureForMesh("rat.png");
buf->getMaterial().TextureLayers[0].MinFilter = video::ETMINF_NEAREST_MIPMAP_NEAREST;
buf->getMaterial().TextureLayers[0].MagFilter = video::ETMAGF_NEAREST;
buf->getMaterial().FogEnable = true;
buf->getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
// Add to mesh
mesh->addMeshBuffer(buf);
buf->drop();
m_node = smgr->addMeshSceneNode(mesh, NULL);
mesh->drop();
updateNodePos();
}
void TestCAO::removeFromScene(bool permanent)
{
if (!m_node)
return;
m_node->remove();
m_node = NULL;
}
void TestCAO::updateLight(u32 day_night_ratio)
{
}
void TestCAO::updateNodePos()
{
if (!m_node)
return;
m_node->setPosition(m_position);
//m_node->setRotation(v3f(0, 45, 0));
}
void TestCAO::step(float dtime, ClientEnvironment *env)
{
if(m_node)
{
v3f rot = m_node->getRotation();
//infostream<<"dtime="<<dtime<<", rot.Y="<<rot.Y<<std::endl;
rot.Y += dtime * 180;
m_node->setRotation(rot);
}
}
void TestCAO::processMessage(const std::string &data)
{
infostream<<"TestCAO: Got data: "<<data<<std::endl;
std::istringstream is(data, std::ios::binary);
u16 cmd;
is>>cmd;
if(cmd == 0)
{
v3f newpos;
is>>newpos.X;
is>>newpos.Y;
is>>newpos.Z;
m_position = newpos;
updateNodePos();
mesh->addMeshBuffer(buf.get());
}
}
mesh->recalculateBoundingBox();
return mesh.release();
}
/*
@ -569,6 +490,8 @@ void GenericCAO::removeFromScene(bool permanent)
m_spritenode = nullptr;
}
m_meshnode_animation.clear();
if (m_matrixnode) {
m_matrixnode->remove();
m_matrixnode->drop();
@ -597,10 +520,12 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr)
if (!m_prop.is_visible)
return;
infostream << "GenericCAO::addToScene(): " << m_prop.visual << std::endl;
m_material_type_param = 0.5f; // May cut off alpha < 128 depending on m_material_type
infostream << "GenericCAO::addToScene(): " <<
enum_to_string(es_ObjectVisual, m_prop.visual)<< std::endl;
if (m_prop.visual != OBJECTVISUAL_NODE &&
m_prop.visual != OBJECTVISUAL_WIELDITEM &&
m_prop.visual != OBJECTVISUAL_ITEM)
{
IShaderSource *shader_source = m_client->getShaderSource();
MaterialType material_type;
@ -614,15 +539,17 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr)
u32 shader_id = shader_source->getShader("object_shader", material_type, NDT_NORMAL);
m_material_type = shader_source->getShaderInfo(shader_id).material;
} else {
// Not used, so make sure it's not valid
m_material_type = EMT_INVALID;
}
auto grabMatrixNode = [this] {
m_matrixnode = m_smgr->addDummyTransformationSceneNode();
m_matrixnode->grab();
};
m_matrixnode = m_smgr->addDummyTransformationSceneNode();
m_matrixnode->grab();
auto setMaterial = [this] (video::SMaterial &mat) {
mat.MaterialType = m_material_type;
if (m_material_type != EMT_INVALID)
mat.MaterialType = m_material_type;
mat.FogEnable = true;
mat.forEachTexture([] (auto &tex) {
tex.MinFilter = video::ETMINF_NEAREST_MIPMAP_NEAREST;
@ -634,28 +561,8 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr)
node->forEachMaterial(setMaterial);
};
if (m_prop.visual == "sprite") {
grabMatrixNode();
m_spritenode = m_smgr->addBillboardSceneNode(
m_matrixnode, v2f(1, 1), v3f(0,0,0), -1);
m_spritenode->grab();
video::ITexture *tex = tsrc->getTextureForMesh("no_texture.png");
m_spritenode->forEachMaterial([tex] (auto &mat) {
mat.setTexture(0, tex);
});
setSceneNodeMaterials(m_spritenode);
m_spritenode->setSize(v2f(m_prop.visual_size.X,
m_prop.visual_size.Y) * BS);
{
const float txs = 1.0 / 1;
const float tys = 1.0 / 1;
setBillboardTextureMatrix(m_spritenode,
txs, tys, 0, 0);
}
} else if (m_prop.visual == "upright_sprite") {
grabMatrixNode();
switch(m_prop.visual) {
case OBJECTVISUAL_UPRIGHT_SPRITE: {
auto mesh = make_irr<scene::SMesh>();
f32 dx = BS * m_prop.visual_size.X / 2;
f32 dy = BS * m_prop.visual_size.Y / 2;
@ -696,8 +603,8 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr)
mesh->recalculateBoundingBox();
m_meshnode = m_smgr->addMeshSceneNode(mesh.get(), m_matrixnode);
m_meshnode->grab();
} else if (m_prop.visual == "cube") {
grabMatrixNode();
break;
} case OBJECTVISUAL_CUBE: {
scene::IMesh *mesh = createCubeMesh(v3f(BS,BS,BS));
m_meshnode = m_smgr->addMeshSceneNode(mesh, m_matrixnode);
m_meshnode->grab();
@ -710,8 +617,8 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr)
m_meshnode->forEachMaterial([this] (auto &mat) {
mat.BackfaceCulling = m_prop.backface_culling;
});
} else if (m_prop.visual == "mesh") {
grabMatrixNode();
break;
} case OBJECTVISUAL_MESH: {
scene::IAnimatedMesh *mesh = m_client->getMesh(m_prop.mesh, true);
if (mesh) {
if (!checkMeshNormals(mesh)) {
@ -737,8 +644,10 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr)
});
} else
errorstream<<"GenericCAO::addToScene(): Could not load mesh "<<m_prop.mesh<<std::endl;
} else if (m_prop.visual == "wielditem" || m_prop.visual == "item") {
grabMatrixNode();
break;
}
case OBJECTVISUAL_WIELDITEM:
case OBJECTVISUAL_ITEM: {
ItemStack item;
if (m_prop.wield_item.empty()) {
// Old format, only textures are specified.
@ -755,19 +664,46 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr)
}
m_wield_meshnode = new WieldMeshSceneNode(m_smgr, -1);
m_wield_meshnode->setItem(item, m_client,
(m_prop.visual == "wielditem"));
(m_prop.visual == OBJECTVISUAL_WIELDITEM));
m_wield_meshnode->setScale(m_prop.visual_size / 2.0f);
} else {
infostream<<"GenericCAO::addToScene(): \""<<m_prop.visual
<<"\" not supported"<<std::endl;
break;
} case OBJECTVISUAL_NODE: {
auto *mesh = generateNodeMesh(m_client, m_prop.node, m_meshnode_animation);
assert(mesh);
m_meshnode = m_smgr->addMeshSceneNode(mesh, m_matrixnode);
m_meshnode->setSharedMaterials(true);
m_meshnode->grab();
mesh->drop();
m_meshnode->setScale(m_prop.visual_size);
setSceneNodeMaterials(m_meshnode);
break;
} default:
m_spritenode = m_smgr->addBillboardSceneNode(m_matrixnode);
m_spritenode->grab();
setSceneNodeMaterials(m_spritenode);
m_spritenode->setSize(v2f(m_prop.visual_size.X,
m_prop.visual_size.Y) * BS);
setBillboardTextureMatrix(m_spritenode, 1, 1, 0, 0);
// This also serves as fallback for unknown visual types
if (m_prop.visual != OBJECTVISUAL_SPRITE) {
m_spritenode->getMaterial(0).setTexture(0,
tsrc->getTextureForMesh("unknown_object.png"));
}
break;
}
/* Set VBO hint */
// wieldmesh sets its own hint, no need to handle it
if (m_meshnode || m_animated_meshnode) {
// sprite uses vertex animation
if (m_meshnode && m_prop.visual != "upright_sprite")
if (m_meshnode && m_prop.visual != OBJECTVISUAL_UPRIGHT_SPRITE)
m_meshnode->getMesh()->setHardwareMappingHint(scene::EHM_STATIC);
if (m_animated_meshnode) {
@ -786,8 +722,7 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr)
updateTextures(m_current_texture_modifier);
if (scene::ISceneNode *node = getSceneNode()) {
if (m_matrixnode)
node->setParent(m_matrixnode);
node->setParent(m_matrixnode);
if (auto shadow = RenderingEngine::get_shadow_renderer())
shadow->addNodeToShadowList(node);
@ -858,8 +793,7 @@ void GenericCAO::updateLight(u32 day_night_ratio)
if (!pos_ok)
light_at_pos = LIGHT_SUN;
// Initialize with full alpha, otherwise entity won't be visible
video::SColor light{0xFFFFFFFF};
video::SColor light;
// Encode light into color, adding a small boost
// based on the entity glow.
@ -873,9 +807,10 @@ void GenericCAO::updateLight(u32 day_night_ratio)
void GenericCAO::setNodeLight(const video::SColor &light_color)
{
if (m_prop.visual == "wielditem" || m_prop.visual == "item") {
if (m_prop.visual == OBJECTVISUAL_WIELDITEM || m_prop.visual == OBJECTVISUAL_ITEM) {
if (m_wield_meshnode)
m_wield_meshnode->setNodeLightColor(light_color);
m_wield_meshnode->setLightColorAndAnimation(light_color,
m_client->getAnimationTime());
return;
}
@ -962,6 +897,7 @@ void GenericCAO::updateNodePos()
scene::ISceneNode *node = getSceneNode();
if (node) {
assert(m_matrixnode);
v3s16 camera_offset = m_env->getCameraOffset();
v3f pos = pos_translator.val_current -
intToFloat(camera_offset, BS);
@ -1150,7 +1086,7 @@ void GenericCAO::step(float dtime, ClientEnvironment *env)
m_anim_frame = 0;
}
updateTexturePos();
updateTextureAnim();
if(m_reset_textures_timer >= 0)
{
@ -1211,7 +1147,7 @@ static void setMeshBufferTextureCoords(scene::IMeshBuffer *buf, const v2f *uv, u
buf->setDirty(scene::EBT_VERTEX);
}
void GenericCAO::updateTexturePos()
void GenericCAO::updateTextureAnim()
{
if(m_spritenode)
{
@ -1259,7 +1195,7 @@ void GenericCAO::updateTexturePos()
}
else if (m_meshnode) {
if (m_prop.visual == "upright_sprite") {
if (m_prop.visual == OBJECTVISUAL_UPRIGHT_SPRITE) {
int row = m_tx_basepos.Y;
int col = m_tx_basepos.X;
@ -1276,6 +1212,23 @@ void GenericCAO::updateTexturePos()
auto mesh = m_meshnode->getMesh();
setMeshBufferTextureCoords(mesh->getMeshBuffer(0), t, 4);
setMeshBufferTextureCoords(mesh->getMeshBuffer(1), t, 4);
} else if (m_prop.visual == OBJECTVISUAL_NODE) {
// same calculation as MapBlockMesh::animate() with a global timer
const float time = m_client->getAnimationTime();
for (auto &it : m_meshnode_animation) {
const TileLayer &tile = it.tile;
int frameno = (int)(time * 1000 / tile.animation_frame_length_ms)
% tile.animation_frame_count;
if (frameno == it.frame)
continue;
it.frame = frameno;
auto *buf = m_meshnode->getMesh()->getMeshBuffer(it.i);
const FrameSpec &frame = (*tile.frames)[frameno];
buf->getMaterial().setTexture(0, frame.texture);
}
}
}
}
@ -1293,7 +1246,7 @@ void GenericCAO::updateTextures(std::string mod)
m_current_texture_modifier = mod;
if (m_spritenode) {
if (m_prop.visual == "sprite") {
if (m_prop.visual == OBJECTVISUAL_SPRITE) {
std::string texturestring = "no_texture.png";
if (!m_prop.textures.empty())
texturestring = m_prop.textures[0];
@ -1301,7 +1254,6 @@ void GenericCAO::updateTextures(std::string mod)
video::SMaterial &material = m_spritenode->getMaterial(0);
material.MaterialType = m_material_type;
material.MaterialTypeParam = m_material_type_param;
material.setTexture(0, tsrc->getTextureForMesh(texturestring));
material.forEachTexture([=] (auto &tex) {
@ -1312,7 +1264,7 @@ void GenericCAO::updateTextures(std::string mod)
}
else if (m_animated_meshnode) {
if (m_prop.visual == "mesh") {
if (m_prop.visual == OBJECTVISUAL_MESH) {
for (u32 i = 0; i < m_animated_meshnode->getMaterialCount(); ++i) {
const auto texture_idx = m_animated_meshnode->getMesh()->getTextureSlot(i);
if (texture_idx >= m_prop.textures.size())
@ -1330,7 +1282,6 @@ void GenericCAO::updateTextures(std::string mod)
// Set material flags and texture
video::SMaterial &material = m_animated_meshnode->getMaterial(i);
material.MaterialType = m_material_type;
material.MaterialTypeParam = m_material_type_param;
material.TextureLayers[0].Texture = texture;
material.BackfaceCulling = m_prop.backface_culling;
@ -1350,7 +1301,7 @@ void GenericCAO::updateTextures(std::string mod)
}
else if (m_meshnode) {
if(m_prop.visual == "cube")
if(m_prop.visual == OBJECTVISUAL_CUBE)
{
for (u32 i = 0; i < 6; ++i)
{
@ -1362,7 +1313,6 @@ void GenericCAO::updateTextures(std::string mod)
// Set material flags and texture
video::SMaterial &material = m_meshnode->getMaterial(i);
material.MaterialType = m_material_type;
material.MaterialTypeParam = m_material_type_param;
material.setTexture(0, tsrc->getTextureForMesh(texturestring));
material.getTextureMatrix(0).makeIdentity();
@ -1371,7 +1321,7 @@ void GenericCAO::updateTextures(std::string mod)
use_anisotropic_filter);
});
}
} else if (m_prop.visual == "upright_sprite") {
} else if (m_prop.visual == OBJECTVISUAL_UPRIGHT_SPRITE) {
scene::IMesh *mesh = m_meshnode->getMesh();
{
std::string tname = "no_texture.png";
@ -1404,8 +1354,8 @@ void GenericCAO::updateTextures(std::string mod)
});
}
// Set mesh color (only if lighting is disabled)
if (!m_prop.colors.empty() && m_prop.glow < 0)
setMeshColor(mesh, m_prop.colors[0]);
if (m_prop.glow < 0)
setMeshColor(mesh, {255, 255, 255, 255});
}
}
// Prevent showing the player after changing texture
@ -1459,34 +1409,6 @@ void GenericCAO::updateBones(f32 dtime)
bone->setScale(props.getScale(bone->getScale()));
}
// search through bones to find mistakenly rotated bones due to bug in Irrlicht
for (u32 i = 0; i < m_animated_meshnode->getJointCount(); ++i) {
scene::IBoneSceneNode *bone = m_animated_meshnode->getJointNode(i);
if (!bone)
continue;
//If bone is manually positioned there is no need to perform the bug check
bool skip = false;
for (auto &it : m_bone_override) {
if (it.first == bone->getName()) {
skip = true;
break;
}
}
if (skip)
continue;
// Workaround for Irrlicht bug
// We check each bone to see if it has been rotated ~180deg from its expected position due to a bug in Irricht
// when using EJUOR_CONTROL joint control. If the bug is detected we update the bone to the proper position
// and update the bones transformation.
v3f bone_rot = bone->getRelativeTransformation().getRotationDegrees();
float offset = fabsf(bone_rot.X - bone->getRotation().X);
if (offset > 179.9f && offset < 180.1f) {
bone->setRotation(bone_rot);
bone->updateAbsolutePosition();
}
}
// The following is needed for set_bone_pos to propagate to
// attached objects correctly.
// Irrlicht ought to do this, but doesn't when using EJUOR_CONTROL.
@ -1557,19 +1479,20 @@ bool GenericCAO::visualExpiryRequired(const ObjectProperties &new_) const
/* Visuals do not need to be expired for:
* - nametag props: handled by updateNametag()
* - textures: handled by updateTextures()
* - sprite props: handled by updateTexturePos()
* - sprite props: handled by updateTextureAnim()
* - glow: handled by updateLight()
* - any other properties that do not change appearance
*/
bool uses_legacy_texture = new_.wield_item.empty() &&
(new_.visual == "wielditem" || new_.visual == "item");
(new_.visual == OBJECTVISUAL_WIELDITEM || new_.visual == OBJECTVISUAL_ITEM);
// Ordered to compare primitive types before std::vectors
return old.backface_culling != new_.backface_culling ||
old.is_visible != new_.is_visible ||
old.mesh != new_.mesh ||
old.shaded != new_.shaded ||
old.use_texture_alpha != new_.use_texture_alpha ||
old.node != new_.node ||
old.mesh != new_.mesh ||
old.visual != new_.visual ||
old.visual_size != new_.visual_size ||
old.wield_item != new_.wield_item ||
@ -1680,7 +1603,7 @@ void GenericCAO::processMessage(const std::string &data)
m_anim_framelength = framelength;
m_tx_select_horiz_by_yawpitch = select_horiz_by_yawpitch;
updateTexturePos();
updateTextureAnim();
} else if (cmd == AO_CMD_SET_PHYSICS_OVERRIDE) {
float override_speed = readF32(is);
float override_jump = readF32(is);
@ -1889,11 +1812,11 @@ void GenericCAO::processMessage(const std::string &data)
/* \pre punchitem != NULL
*/
bool GenericCAO::directReportPunch(v3f dir, const ItemStack *punchitem,
float time_from_last_punch)
const ItemStack *hand_item, float time_from_last_punch)
{
assert(punchitem); // pre-condition
const ToolCapabilities *toolcap =
&punchitem->getToolCapabilities(m_client->idef());
&punchitem->getToolCapabilities(m_client->idef(), hand_item);
PunchDamageResult result = getPunchDamage(
m_armor_groups,
toolcap,
@ -1954,7 +1877,7 @@ void GenericCAO::updateMeshCulling()
if (!node)
return;
if (m_prop.visual == "upright_sprite") {
if (m_prop.visual == OBJECTVISUAL_UPRIGHT_SPRITE) {
// upright sprite has no backface culling
node->forEachMaterial([=] (auto &mat) {
mat.FrontfaceCulling = hidden;

View file

@ -12,6 +12,7 @@
#include "clientobject.h"
#include "constants.h"
#include "itemgroup.h"
#include "client/tile.h"
#include <cassert>
#include <map>
#include <memory>
@ -27,7 +28,7 @@ struct Nametag;
struct MinimapMarker;
/*
SmoothTranslator
SmoothTranslator and other helpers
*/
template<typename T>
@ -60,9 +61,21 @@ struct SmoothTranslatorWrappedv3f : SmoothTranslator<v3f>
void translate(f32 dtime);
};
struct MeshAnimationInfo {
u32 i; /// index of mesh buffer
int frame; /// last animation frame
TileLayer tile;
};
/*
GenericCAO
*/
class GenericCAO : public ClientActiveObject
{
private:
static constexpr auto EMT_INVALID = video::EMT_FORCE_32BIT;
// Only set at initialization
std::string m_name = "";
bool m_is_player = false;
@ -73,6 +86,8 @@ private:
scene::ISceneManager *m_smgr = nullptr;
Client *m_client = nullptr;
aabb3f m_selection_box = aabb3f(-BS/3.,-BS/3.,-BS/3., BS/3.,BS/3.,BS/3.);
// Visuals
scene::IMeshSceneNode *m_meshnode = nullptr;
scene::IAnimatedMeshSceneNode *m_animated_meshnode = nullptr;
WieldMeshSceneNode *m_wield_meshnode = nullptr;
@ -80,6 +95,15 @@ private:
scene::IDummyTransformationSceneNode *m_matrixnode = nullptr;
Nametag *m_nametag = nullptr;
MinimapMarker *m_marker = nullptr;
bool m_visuals_expired = false;
video::SColor m_last_light = video::SColor(0xFFFFFFFF);
bool m_is_visible = false;
std::vector<MeshAnimationInfo> m_meshnode_animation;
// Material
video::E_MATERIAL_TYPE m_material_type = EMT_INVALID;
// Movement
v3f m_position = v3f(0.0f, 10.0f * BS, 0);
v3f m_velocity;
v3f m_acceleration;
@ -87,18 +111,25 @@ private:
u16 m_hp = 1;
SmoothTranslator<v3f> pos_translator;
SmoothTranslatorWrappedv3f rot_translator;
// Spritesheet/animation stuff
// Spritesheet stuff
v2f m_tx_size = v2f(1,1);
v2s16 m_tx_basepos;
bool m_initial_tx_basepos_set = false;
bool m_tx_select_horiz_by_yawpitch = false;
bool m_animation_loop = true;
v2f m_animation_range;
float m_animation_speed = 15.0f;
float m_animation_blend = 0.0f;
bool m_animation_loop = true;
int m_anim_frame = 0;
int m_anim_num_frames = 1;
float m_anim_framelength = 0.2f;
float m_anim_timer = 0.0f;
// stores position and rotation for each bone name
BoneOverrideMap m_bone_override;
// Attachments
object_t m_attachment_parent_id = 0;
std::unordered_set<object_t> m_attachment_child_ids;
std::string m_attachment_bone = "";
@ -107,23 +138,13 @@ private:
bool m_attached_to_local = false;
bool m_force_visible = false;
int m_anim_frame = 0;
int m_anim_num_frames = 1;
float m_anim_framelength = 0.2f;
float m_anim_timer = 0.0f;
ItemGroupList m_armor_groups;
float m_reset_textures_timer = -1.0f;
// stores texture modifier before punch update
std::string m_previous_texture_modifier = "";
// last applied texture modifier
std::string m_current_texture_modifier = "";
bool m_visuals_expired = false;
float m_step_distance_counter = 0.0f;
video::SColor m_last_light = video::SColor(0xFFFFFFFF);
bool m_is_visible = false;
// Material
video::E_MATERIAL_TYPE m_material_type;
f32 m_material_type_param;
bool visualExpiryRequired(const ObjectProperties &newprops) const;
@ -255,7 +276,7 @@ public:
void step(float dtime, ClientEnvironment *env) override;
void updateTexturePos();
void updateTextureAnim();
// ffs this HAS TO BE a string copy! See #5739 if you think otherwise
// Reason: updateTextures(m_previous_texture_modifier);
@ -269,8 +290,8 @@ public:
void processMessage(const std::string &data) override;
bool directReportPunch(v3f dir, const ItemStack *punchitem=NULL,
float time_from_last_punch=1000000) override;
bool directReportPunch(v3f dir, const ItemStack *punchitem,
const ItemStack *hand_item, float time_from_last_punch=1000000) override;
std::string debugInfoText() override;

View file

@ -39,7 +39,7 @@ public:
MapNode n = env->getMap().getNode(floatToInt(pos, BS), &pos_ok);
light = pos_ok ? decode_light(n.getLightBlend(env->getDayNightRatio(),
env->getGameDef()->ndef()->getLightingFlags(n)))
: 64;
: 64;
video::SColor color(255,light,light,light);
m_spritenode->setColor(color);
}

View file

@ -17,6 +17,8 @@
#include "client/renderingengine.h"
#include "client.h"
#include "noise.h"
#include <SMesh.h>
#include <IMeshBuffer.h>
// Distance of light extrapolation (for oversized nodes)
// After this distance, it gives up and considers light level constant
@ -101,7 +103,7 @@ void MapblockMeshGenerator::getSpecialTile(int index, TileSpec *tile_ret, bool a
for (auto &layernum : tile_ret->layers) {
TileLayer *layer = &layernum;
if (layer->texture_id == 0)
if (layer->empty())
continue;
top_layer = layer;
if (!layer->has_color)
@ -119,7 +121,7 @@ void MapblockMeshGenerator::drawQuad(const TileSpec &tile, v3f *coords, const v3
v2f(1.0, vertical_tiling), v2f(0.0, vertical_tiling)};
video::S3DVertex vertices[4];
bool shade_face = !cur_node.f->light_source && (normal != v3s16(0, 0, 0));
v3f normal2(normal.X, normal.Y, normal.Z);
v3f normal2 = v3f::from(normal);
for (int j = 0; j < 4; j++) {
vertices[j].Pos = coords[j] + cur_node.origin;
vertices[j].Normal = normal2;

View file

@ -33,7 +33,7 @@ public:
void put(MtEvent *e) override
{
std::map<MtEvent::Type, Dest>::iterator i = m_dest.find(e->getType());
auto i = m_dest.find(e->getType());
if (i != m_dest.end()) {
std::list<FuncSpec> &funcs = i->second.funcs;
for (FuncSpec &func : funcs) {
@ -44,7 +44,7 @@ public:
}
void reg(MtEvent::Type type, event_receive_func f, void *data) override
{
std::map<MtEvent::Type, Dest>::iterator i = m_dest.find(type);
auto i = m_dest.find(type);
if (i != m_dest.end()) {
i->second.funcs.emplace_back(f, data);
} else {

View file

@ -3,25 +3,24 @@
// Copyright (C) 2010-2014 sapier <sapier at gmx dot net>
#include "fontengine.h"
#include <cmath>
#include "client/renderingengine.h"
#include "config.h"
#include "porting.h"
#include "filesys.h"
#include "gettext.h"
#include "settings.h"
#include "irrlicht_changes/CGUITTFont.h"
#include "util/numeric.h" // rangelim
#include <IGUIEnvironment.h>
#include <IGUIFont.h>
#include <cmath>
#include <cstring>
#include <unordered_set>
/** reference to access font engine, has to be initialized by main */
FontEngine *g_fontengine = nullptr;
/** callback to be used on change of font size setting */
static void font_setting_changed(const std::string &name, void *userdata)
void FontEngine::fontSettingChanged(const std::string &name, void *userdata)
{
static_cast<FontEngine *>(userdata)->readSettings();
((FontEngine *)userdata)->m_needs_reload = true;
}
static const char *settings[] = {
@ -35,7 +34,6 @@ static const char *settings[] = {
"dpi_change_notifier", "display_density_factor", "gui_scaling",
};
/******************************************************************************/
FontEngine::FontEngine(gui::IGUIEnvironment* env) :
m_env(env)
{
@ -50,19 +48,17 @@ FontEngine::FontEngine(gui::IGUIEnvironment* env) :
readSettings();
for (auto name : settings)
g_settings->registerChangedCallback(name, font_setting_changed, this);
g_settings->registerChangedCallback(name, fontSettingChanged, this);
}
/******************************************************************************/
FontEngine::~FontEngine()
{
g_settings->deregisterAllChangedCallbacks(this);
cleanCache();
clearCache();
}
/******************************************************************************/
void FontEngine::cleanCache()
void FontEngine::clearCache()
{
RecursiveMutexAutoLock l(m_font_mutex);
@ -76,7 +72,6 @@ void FontEngine::cleanCache()
}
}
/******************************************************************************/
irr::gui::IGUIFont *FontEngine::getFont(FontSpec spec)
{
return getFont(spec, false);
@ -85,7 +80,7 @@ irr::gui::IGUIFont *FontEngine::getFont(FontSpec spec)
irr::gui::IGUIFont *FontEngine::getFont(FontSpec spec, bool may_fail)
{
if (spec.mode == FM_Unspecified) {
spec.mode = m_currentMode;
spec.mode = s_default_font_mode;
} else if (spec.mode == _FM_Fallback) {
// Fallback font doesn't support these
spec.bold = false;
@ -118,7 +113,6 @@ irr::gui::IGUIFont *FontEngine::getFont(FontSpec spec, bool may_fail)
return font;
}
/******************************************************************************/
unsigned int FontEngine::getTextHeight(const FontSpec &spec)
{
gui::IGUIFont *font = getFont(spec);
@ -126,7 +120,6 @@ unsigned int FontEngine::getTextHeight(const FontSpec &spec)
return font->getDimension(L"Some unimportant example String").Height;
}
/******************************************************************************/
unsigned int FontEngine::getTextWidth(const std::wstring &text, const FontSpec &spec)
{
gui::IGUIFont *font = getFont(spec);
@ -143,10 +136,9 @@ unsigned int FontEngine::getLineHeight(const FontSpec &spec)
+ font->getKerning(L'S').Y;
}
/******************************************************************************/
unsigned int FontEngine::getDefaultFontSize()
{
return m_default_size[m_currentMode];
return m_default_size[s_default_font_mode];
}
unsigned int FontEngine::getFontSize(FontMode mode)
@ -157,7 +149,6 @@ unsigned int FontEngine::getFontSize(FontMode mode)
return m_default_size[mode];
}
/******************************************************************************/
void FontEngine::readSettings()
{
m_default_size[FM_Standard] = rangelim(g_settings->getU16("font_size"), 5, 72);
@ -167,12 +158,18 @@ void FontEngine::readSettings()
m_default_bold = g_settings->getBool("font_bold");
m_default_italic = g_settings->getBool("font_italic");
cleanCache();
updateFontCache();
updateSkin();
refresh();
}
void FontEngine::handleReload()
{
if (!m_needs_reload)
return;
m_needs_reload = false;
readSettings();
}
/******************************************************************************/
void FontEngine::updateSkin()
{
gui::IGUIFont *font = getFont();
@ -181,20 +178,58 @@ void FontEngine::updateSkin()
m_env->getSkin()->setFont(font);
}
/******************************************************************************/
void FontEngine::updateFontCache()
void FontEngine::updateCache()
{
/* the only font to be initialized is default one,
* all others are re-initialized on demand */
getFont(FONT_SIZE_UNSPECIFIED, FM_Unspecified);
}
/******************************************************************************/
gui::IGUIFont *FontEngine::initFont(const FontSpec &spec)
void FontEngine::refresh() {
clearCache();
updateCache();
updateSkin();
}
void FontEngine::setMediaFont(const std::string &name, const std::string &data)
{
static std::unordered_set<std::string> valid_names {
"regular", "bold", "italic", "bold_italic",
"mono", "mono_bold", "mono_italic", "mono_bold_italic",
};
if (!valid_names.count(name)) {
warningstream << "Ignoring unrecognized media font: " << name << std::endl;
return;
}
constexpr char TTF_MAGIC[5] = {0, 1, 0, 0, 0};
if (data.size() < 5 || (memcmp(data.data(), "wOFF", 4) &&
memcmp(data.data(), TTF_MAGIC, 5))) {
warningstream << "Rejecting media font with unrecognized magic" << std::endl;
return;
}
std::string copy = data;
irr_ptr<gui::SGUITTFace> face(gui::SGUITTFace::createFace(std::move(copy)));
m_media_faces.emplace(name, face);
refresh();
}
void FontEngine::clearMediaFonts()
{
RecursiveMutexAutoLock l(m_font_mutex);
m_media_faces.clear();
refresh();
}
gui::IGUIFont *FontEngine::initFont(FontSpec spec)
{
assert(spec.mode != FM_Unspecified);
assert(spec.size != FONT_SIZE_UNSPECIFIED);
if (spec.mode == _FM_Fallback)
spec.allow_server_media = false;
std::string setting_prefix = "";
if (spec.mode == FM_Mono)
setting_prefix = "mono_";
@ -224,6 +259,42 @@ gui::IGUIFont *FontEngine::initFont(const FontSpec &spec)
g_settings->getU16NoEx(setting_prefix + "font_shadow_alpha",
font_shadow_alpha);
auto createFont = [&](gui::SGUITTFace *face) -> gui::CGUITTFont* {
auto *font = gui::CGUITTFont::createTTFont(m_env,
face, size, true, true, font_shadow,
font_shadow_alpha);
if (!font)
return nullptr;
if (spec.mode != _FM_Fallback) {
FontSpec spec2(spec);
spec2.mode = _FM_Fallback;
font->setFallback(getFont(spec2, true));
}
return font;
};
// Use the server-provided font media (if available)
if (spec.allow_server_media) {
std::string media_name = spec.mode == FM_Mono
? "mono" + setting_suffix
: (setting_suffix.empty() ? "" : setting_suffix.substr(1));
if (media_name.empty())
media_name = "regular";
auto it = m_media_faces.find(media_name);
if (it != m_media_faces.end()) {
auto *face = it->second.get();
if (auto *font = createFont(face))
return font;
errorstream << "FontEngine: Cannot load media font '" << media_name <<
"'. Falling back to client settings." << std::endl;
}
}
// Use the local font files specified by the settings
std::string path_setting;
if (spec.mode == _FM_Fallback)
path_setting = "fallback_font_path";
@ -234,24 +305,19 @@ gui::IGUIFont *FontEngine::initFont(const FontSpec &spec)
g_settings->get(path_setting),
Settings::getLayer(SL_DEFAULTS)->get(path_setting)
};
for (const std::string &font_path : fallback_settings) {
gui::CGUITTFont *font = gui::CGUITTFont::createTTFont(m_env,
font_path.c_str(), size, true, true, font_shadow,
font_shadow_alpha);
infostream << "Creating new font: " << font_path.c_str()
<< " " << size << "pt" << std::endl;
if (!font) {
errorstream << "FontEngine: Cannot load '" << font_path <<
"'. Trying to fall back to another path." << std::endl;
continue;
// Grab the face.
if (auto *face = irr::gui::SGUITTFace::loadFace(font_path)) {
auto *font = createFont(face);
face->drop();
return font;
}
if (spec.mode != _FM_Fallback) {
FontSpec spec2(spec);
spec2.mode = _FM_Fallback;
font->setFallback(getFont(spec2, true));
}
return font;
errorstream << "FontEngine: Cannot load '" << font_path <<
"'. Trying to fall back to another path." << std::endl;
}
return nullptr;
}

View file

@ -5,6 +5,9 @@
#pragma once
#include <map>
#include <unordered_map>
#include "irr_ptr.h"
#include "irrlicht_changes/CGUITTFont.h"
#include "util/basic_macros.h"
#include "irrlichttypes.h"
#include "irrString.h" // utf8_to_wide
@ -20,10 +23,22 @@ namespace irr {
#define FONT_SIZE_UNSPECIFIED 0xFFFFFFFF
enum FontMode : u8 {
/// Regular font (settings "font_path*", overwritable)
FM_Standard = 0,
/// Monospace font (settings "mono_font*", overwritable)
FM_Mono,
_FM_Fallback, // do not use directly
/// Use only in `FontEngine`. Fallback font to render glyphs that are not present
/// in the originally requested font (setting "fallback_font_path")
_FM_Fallback,
/// Sum of all font modes
FM_MaxMode,
// ----------------------------
/// Request the defult font specified by `s_default_font_mode`
FM_Unspecified
};
@ -34,15 +49,22 @@ struct FontSpec {
bold(bold),
italic(italic) {}
static const unsigned VARIANT_BITS = 3;
static const size_t MAX_VARIANTS = FM_MaxMode << VARIANT_BITS;
u16 getHash() const
{
return (mode << 2) | (static_cast<u8>(bold) << 1) | static_cast<u8>(italic);
return (mode << VARIANT_BITS)
| (static_cast<u8>(allow_server_media) << 2)
| (static_cast<u8>(bold) << 1)
| static_cast<u8>(italic);
}
unsigned int size;
FontMode mode;
bool bold;
bool italic;
bool allow_server_media = true;
};
class FontEngine
@ -66,7 +88,7 @@ public:
/** get text height for a specific font */
unsigned int getTextHeight(const FontSpec &spec);
/** get text width if a text for a specific font */
/** get text width of a text for a specific font */
unsigned int getTextHeight(
unsigned int font_size=FONT_SIZE_UNSPECIFIED,
FontMode mode=FM_Unspecified)
@ -77,7 +99,7 @@ public:
unsigned int getTextWidth(const std::wstring &text, const FontSpec &spec);
/** get text width if a text for a specific font */
/** get text width of a text for a specific font */
unsigned int getTextWidth(const std::wstring& text,
unsigned int font_size=FONT_SIZE_UNSPECIFIED,
FontMode mode=FM_Unspecified)
@ -118,20 +140,32 @@ public:
/** update internal parameters from settings */
void readSettings();
/** reload fonts if settings were changed */
void handleReload();
void setMediaFont(const std::string &name, const std::string &data);
void clearMediaFonts();
private:
irr::gui::IGUIFont *getFont(FontSpec spec, bool may_fail);
/** update content of font cache in case of a setting change made it invalid */
void updateFontCache();
void updateCache();
/** initialize a new TTF font */
gui::IGUIFont *initFont(const FontSpec &spec);
gui::IGUIFont *initFont(FontSpec spec);
/** update current minetest skin with font changes */
void updateSkin();
/** clean cache */
void cleanCache();
void clearCache();
/** refresh after fonts have been changed */
void refresh();
/** callback to be used on change of font size setting */
static void fontSettingChanged(const std::string &name, void *userdata);
/** pointer to irrlicht gui environment */
gui::IGUIEnvironment* m_env = nullptr;
@ -140,7 +174,10 @@ private:
std::recursive_mutex m_font_mutex;
/** internal storage for caching fonts of different size */
std::map<unsigned int, irr::gui::IGUIFont*> m_font_cache[FM_MaxMode << 2];
std::map<unsigned int, irr::gui::IGUIFont*> m_font_cache[FontSpec::MAX_VARIANTS];
/** media-provided faces, indexed by filename (without extension) */
std::unordered_map<std::string, irr_ptr<gui::SGUITTFace>> m_media_faces;
/** default font size to use */
unsigned int m_default_size[FM_MaxMode];
@ -150,7 +187,9 @@ private:
bool m_default_italic = false;
/** default font engine mode (fixed) */
static const FontMode m_currentMode = FM_Standard;
static const FontMode s_default_font_mode = FM_Standard;
bool m_needs_reload = false;
DISABLE_CLASS_COPY(FontEngine);
};

View file

@ -60,6 +60,7 @@
#include "clientdynamicinfo.h"
#include <IAnimatedMeshSceneNode.h>
#include "util/tracy_wrapper.h"
#include "item_visuals_manager.h"
#if USE_SOUND
#include "client/sound/sound_openal.h"
@ -553,7 +554,7 @@ protected:
bool init(const std::string &map_dir, const std::string &address,
u16 port, const SubgameSpec &gamespec);
bool initSound();
bool createSingleplayerServer(const std::string &map_dir,
bool createServer(const std::string &map_dir,
const SubgameSpec &gamespec, u16 port);
void copyServerClientCache();
@ -605,10 +606,13 @@ protected:
void updateCameraDirection(CameraOrientation *cam, float dtime);
void updateCameraOrientation(CameraOrientation *cam, float dtime);
bool getTogglableKeyState(GameKeyType key, bool toggling_enabled, bool prev_key_state);
void updatePlayerControl(const CameraOrientation &cam);
void updatePauseState();
void step(f32 dtime);
void processClientEvents(CameraOrientation *cam);
void updateCameraMode(); // call after changing it
void updateCameraOffset();
void updateCamera(f32 dtime);
void updateSound(f32 dtime);
void processPlayerInteraction(f32 dtime, bool show_hud);
@ -634,7 +638,7 @@ protected:
void handlePointingAtNode(const PointedThing &pointed,
const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime);
void handlePointingAtObject(const PointedThing &pointed, const ItemStack &playeritem,
const v3f &player_position, bool show_debug);
const ItemStack &hand_item, const v3f &player_position, bool show_debug);
void handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime);
void updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
@ -695,7 +699,8 @@ private:
void handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam);
void handleClientEvent_DeathscreenLegacy(ClientEvent *event, CameraOrientation *cam);
void handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam);
void handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam);
void handleClientEvent_ShowCSMFormSpec(ClientEvent *event, CameraOrientation *cam);
void handleClientEvent_ShowPauseMenuFormSpec(ClientEvent *event, CameraOrientation *cam);
void handleClientEvent_HandleParticleEvent(ClientEvent *event,
CameraOrientation *cam);
void handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam);
@ -708,6 +713,7 @@ private:
void handleClientEvent_OverrideDayNigthRatio(ClientEvent *event,
CameraOrientation *cam);
void handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam);
void handleClientEvent_UpdateCamera(ClientEvent *event, CameraOrientation *cam);
void updateChat(f32 dtime);
@ -732,6 +738,7 @@ private:
// When created, these will be filled with data received from the server
IWritableItemDefManager *itemdef_manager = nullptr;
NodeDefManager *nodedef_manager = nullptr;
std::unique_ptr<ItemVisualsManager> m_item_visuals_manager;
std::unique_ptr<ISoundManager> sound_manager;
SoundMaker *soundmaker = nullptr;
@ -787,6 +794,8 @@ private:
* a later release.
*/
bool m_cache_doubletap_jump;
bool m_cache_toggle_sneak_key;
bool m_cache_toggle_aux1_key;
bool m_cache_enable_joysticks;
bool m_cache_enable_fog;
bool m_cache_enable_noclip;
@ -812,9 +821,10 @@ private:
bool m_is_paused = false;
bool m_touch_simulate_aux1 = false;
bool m_touch_use_crosshair;
inline bool isTouchCrosshairDisabled() {
return !m_touch_use_crosshair && camera->getCameraMode() == CAMERA_MODE_FIRST;
inline bool isTouchShootlineUsed()
{
return g_touchcontrols && g_touchcontrols->isShootlineAvailable() &&
camera->getCameraMode() == CAMERA_MODE_FIRST;
}
#ifdef __ANDROID__
bool m_android_chat_open;
@ -831,6 +841,10 @@ Game::Game() :
&settingChangedCallback, this);
g_settings->registerChangedCallback("doubletap_jump",
&settingChangedCallback, this);
g_settings->registerChangedCallback("toggle_sneak_key",
&settingChangedCallback, this);
g_settings->registerChangedCallback("toggle_aux1_key",
&settingChangedCallback, this);
g_settings->registerChangedCallback("enable_joysticks",
&settingChangedCallback, this);
g_settings->registerChangedCallback("enable_fog",
@ -918,7 +932,7 @@ bool Game::startup(bool *kill,
this->chat_backend = chat_backend;
simple_singleplayer_mode = start_data.isSinglePlayer();
input->keycache.populate();
input->reloadKeybindings();
driver = device->getVideoDriver();
smgr = m_rendering_engine->get_scene_manager();
@ -939,11 +953,8 @@ bool Game::startup(bool *kill,
m_first_loop_after_window_activation = true;
m_touch_use_crosshair = g_settings->getBool("touch_use_crosshair");
g_client_translations->clear();
// address can change if simple_singleplayer_mode
if (!init(start_data.world_spec.path, start_data.address,
start_data.socket_port, start_data.game_spec))
return false;
@ -1001,10 +1012,12 @@ void Game::run()
// Calculate dtime =
// m_rendering_engine->run() from this iteration
// + Sleep time until the wanted FPS are reached
draw_times.limit(device, &dtime, g_menumgr.pausesGame());
draw_times.limit(device, &dtime);
framemarker.start();
g_fontengine->handleReload();
const auto current_dynamic_info = ClientDynamicInfo::getCurrent();
if (!current_dynamic_info.equal(client_display_info)) {
client_display_info = current_dynamic_info;
@ -1033,6 +1046,12 @@ void Game::run()
m_game_ui->clearInfoText();
updateProfilers(stats, draw_times, dtime);
// Update camera offset once before doing anything.
// In contrast to other updates the latency of this doesn't matter,
// since it's invisible to the user. But it needs to be consistent.
updateCameraOffset();
processUserInput(dtime);
// Update camera before player movement to avoid camera lag of one frame
updateCameraDirection(&cam_view_target, dtime);
@ -1050,6 +1069,7 @@ void Game::run()
processClientEvents(&cam_view_target);
updateDebugState();
// Update camera here so it is in-sync with CAO position
updateCamera(dtime);
updateSound(dtime);
processPlayerInteraction(dtime, m_game_ui->m_flags.show_hud);
@ -1152,6 +1172,8 @@ bool Game::init(
itemdef_manager = createItemDefManager();
nodedef_manager = createNodeDefManager();
m_item_visuals_manager = std::make_unique<ItemVisualsManager>();
eventmgr = new EventManager();
quicktune = new QuicktuneShortcutter();
@ -1164,7 +1186,7 @@ bool Game::init(
// Create a server if not connecting to an existing one
if (address.empty()) {
if (!createSingleplayerServer(map_dir, gamespec, port))
if (!createServer(map_dir, gamespec, port))
return false;
}
@ -1199,7 +1221,7 @@ bool Game::initSound()
return true;
}
bool Game::createSingleplayerServer(const std::string &map_dir,
bool Game::createServer(const std::string &map_dir,
const SubgameSpec &gamespec, u16 port)
{
showOverlayMessage(N_("Creating server..."), 0, 5);
@ -1404,10 +1426,8 @@ bool Game::initGui()
gui_chat_console = make_irr<GUIChatConsole>(guienv, guienv->getRootGUIElement(),
-1, chat_backend, client, &g_menumgr);
if (shouldShowTouchControls()) {
if (shouldShowTouchControls())
g_touchcontrols = new TouchControls(device, texture_src);
g_touchcontrols->setUseCrosshair(!isTouchCrosshairDisabled());
}
return true;
}
@ -1417,7 +1437,6 @@ bool Game::connectToServer(const GameStartData &start_data,
{
*connect_ok = false; // Let's not be overly optimistic
*connection_aborted = false;
bool local_server_mode = false;
const auto &address_name = start_data.address;
showOverlayMessage(N_("Resolving address..."), 0, 15);
@ -1437,7 +1456,6 @@ bool Game::connectToServer(const GameStartData &start_data,
} else {
connect_address.setAddress(127, 0, 0, 1);
}
local_server_mode = true;
}
} catch (ResolveError &e) {
*error_message = fmtgettext("Couldn't resolve address: %s", e.what());
@ -1474,6 +1492,7 @@ bool Game::connectToServer(const GameStartData &start_data,
*draw_control, texture_src, shader_src,
itemdef_manager, nodedef_manager, sound_manager.get(), eventmgr,
m_rendering_engine,
m_item_visuals_manager.get(),
start_data.allow_login_or_register);
} catch (const BaseException &e) {
*error_message = fmtgettext("Error creating client: %s", e.what());
@ -1483,13 +1502,13 @@ bool Game::connectToServer(const GameStartData &start_data,
client->migrateModStorage();
client->m_simple_singleplayer_mode = simple_singleplayer_mode;
client->m_internal_server = !!server;
/*
Wait for server to accept connection
*/
client->connect(connect_address, address_name,
simple_singleplayer_mode || local_server_mode);
client->connect(connect_address, address_name);
try {
input->clear();
@ -1536,12 +1555,11 @@ bool Game::connectToServer(const GameStartData &start_data,
}
wait_time += dtime;
if (local_server_mode) {
if (server) {
// never time out
} else if (wait_time > GAME_FALLBACK_TIMEOUT && !did_fallback) {
if (!client->hasServerReplied() && fallback_address.isValid()) {
client->connect(fallback_address, address_name,
simple_singleplayer_mode || local_server_mode);
client->connect(fallback_address, address_name);
}
did_fallback = true;
} else if (wait_time > GAME_CONNECTION_TIMEOUT) {
@ -1947,6 +1965,9 @@ void Game::processKeyInput()
toggleFog();
} else if (wasKeyDown(KeyType::TOGGLE_UPDATE_CAMERA)) {
toggleUpdateCamera();
} else if (wasKeyPressed(KeyType::CAMERA_MODE)) {
camera->toggleCameraMode();
updateCameraMode();
} else if (wasKeyPressed(KeyType::TOGGLE_DEBUG)) {
toggleDebug();
} else if (wasKeyPressed(KeyType::TOGGLE_PROFILER)) {
@ -2432,8 +2453,10 @@ f32 Game::getSensitivityScaleFactor() const
void Game::updateCameraOrientation(CameraOrientation *cam, float dtime)
{
if (g_touchcontrols) {
cam->camera_yaw += g_touchcontrols->getYawChange();
cam->camera_pitch += g_touchcontrols->getPitchChange();
// User setting is already applied by TouchControls.
f32 sens_scale = getSensitivityScaleFactor();
cam->camera_yaw += g_touchcontrols->getYawChange() * sens_scale;
cam->camera_pitch += g_touchcontrols->getPitchChange() * sens_scale;
} else {
v2s32 center(driver->getScreenSize().Width / 2, driver->getScreenSize().Height / 2);
v2s32 dist = input->getMousePos() - center;
@ -2457,7 +2480,17 @@ void Game::updateCameraOrientation(CameraOrientation *cam, float dtime)
cam->camera_pitch += input->joystick.getAxisWithoutDead(JA_FRUSTUM_VERTICAL) * c;
}
cam->camera_pitch = rangelim(cam->camera_pitch, -89.5, 89.5);
cam->camera_pitch = rangelim(cam->camera_pitch, -90, 90);
}
// Get the state of an optionally togglable key
bool Game::getTogglableKeyState(GameKeyType key, bool toggling_enabled, bool prev_key_state)
{
if (!toggling_enabled)
return isKeyDown(key);
else
return prev_key_state ^ wasKeyPressed(key);
}
@ -2465,6 +2498,11 @@ void Game::updatePlayerControl(const CameraOrientation &cam)
{
LocalPlayer *player = client->getEnv().getLocalPlayer();
// In free move (fly), the "toggle_sneak_key" setting would prevent precise
// up/down movements. Hence, enable the feature only during 'normal' movement.
const bool allow_sneak_toggle = m_cache_toggle_sneak_key &&
!player->getPlayerSettings().free_move;
//TimeTaker tt("update player control", NULL, PRECISION_NANO);
PlayerControl control(
@ -2473,8 +2511,8 @@ void Game::updatePlayerControl(const CameraOrientation &cam)
isKeyDown(KeyType::LEFT),
isKeyDown(KeyType::RIGHT),
isKeyDown(KeyType::JUMP) || player->getAutojump(),
isKeyDown(KeyType::AUX1),
isKeyDown(KeyType::SNEAK),
getTogglableKeyState(KeyType::AUX1, m_cache_toggle_aux1_key, player->control.aux1),
getTogglableKeyState(KeyType::SNEAK, allow_sneak_toggle, player->control.sneak),
isKeyDown(KeyType::ZOOM),
isKeyDown(KeyType::DIG),
isKeyDown(KeyType::PLACE),
@ -2528,7 +2566,7 @@ inline void Game::step(f32 dtime)
ZoneScoped;
if (server) {
float fps_max = (!device->isWindowFocused() || g_menumgr.pausesGame()) ?
float fps_max = !device->isWindowFocused() && simple_singleplayer_mode ?
g_settings->getFloat("fps_max_unfocused") :
g_settings->getFloat("fps_max");
fps_max = std::max(fps_max, 1.0f);
@ -2585,7 +2623,8 @@ const ClientEventHandler Game::clientEventHandler[CLIENTEVENT_MAX] = {
{&Game::handleClientEvent_PlayerForceMove},
{&Game::handleClientEvent_DeathscreenLegacy},
{&Game::handleClientEvent_ShowFormSpec},
{&Game::handleClientEvent_ShowLocalFormSpec},
{&Game::handleClientEvent_ShowCSMFormSpec},
{&Game::handleClientEvent_ShowPauseMenuFormSpec},
{&Game::handleClientEvent_HandleParticleEvent},
{&Game::handleClientEvent_HandleParticleEvent},
{&Game::handleClientEvent_HandleParticleEvent},
@ -2598,6 +2637,7 @@ const ClientEventHandler Game::clientEventHandler[CLIENTEVENT_MAX] = {
{&Game::handleClientEvent_SetStars},
{&Game::handleClientEvent_OverrideDayNigthRatio},
{&Game::handleClientEvent_CloudParams},
{&Game::handleClientEvent_UpdateCamera},
};
void Game::handleClientEvent_None(ClientEvent *event, CameraOrientation *cam)
@ -2653,9 +2693,18 @@ void Game::handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation
delete event->show_formspec.formname;
}
void Game::handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam)
void Game::handleClientEvent_ShowCSMFormSpec(ClientEvent *event, CameraOrientation *cam)
{
m_game_formspec.showLocalFormSpec(*event->show_formspec.formspec,
m_game_formspec.showCSMFormSpec(*event->show_formspec.formspec,
*event->show_formspec.formname);
delete event->show_formspec.formspec;
delete event->show_formspec.formname;
}
void Game::handleClientEvent_ShowPauseMenuFormSpec(ClientEvent *event, CameraOrientation *cam)
{
m_game_formspec.showPauseMenuFormSpec(*event->show_formspec.formspec,
*event->show_formspec.formname);
delete event->show_formspec.formspec;
@ -2893,6 +2942,13 @@ void Game::handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *
clouds->setSpeed(v2f(event->cloud_params.speed_x, event->cloud_params.speed_y));
}
void Game::handleClientEvent_UpdateCamera(ClientEvent *event, CameraOrientation *cam)
{
// no parameters to update here, this just makes sure the camera is in the
// state it should be after something was changed.
updateCameraMode();
}
void Game::processClientEvents(CameraOrientation *cam)
{
while (client->hasClientEvents()) {
@ -2946,70 +3002,81 @@ void Game::updateChat(f32 dtime)
void Game::updateCamera(f32 dtime)
{
LocalPlayer *player = client->getEnv().getLocalPlayer();
ClientEnvironment &env = client->getEnv();
LocalPlayer *player = env.getLocalPlayer();
/*
For interaction purposes, get info about the held item
- What item is it?
- Is it a usable item?
- Can it point to liquids?
*/
ItemStack playeritem;
// For interaction purposes, get info about the held item
ItemStack playeritem, hand;
{
ItemStack selected, hand;
ItemStack selected;
playeritem = player->getWieldedItem(&selected, &hand);
}
ToolCapabilities playeritem_toolcap =
playeritem.getToolCapabilities(itemdef_manager);
v3s16 old_camera_offset = camera->getOffset();
if (wasKeyPressed(KeyType::CAMERA_MODE)) {
GenericCAO *playercao = player->getCAO();
// If playercao not loaded, don't change camera
if (!playercao)
return;
camera->toggleCameraMode();
if (g_touchcontrols)
g_touchcontrols->setUseCrosshair(!isTouchCrosshairDisabled());
// Make the player visible depending on camera mode.
playercao->updateMeshCulling();
playercao->setChildrenVisible(camera->getCameraMode() > CAMERA_MODE_FIRST);
}
playeritem.getToolCapabilities(itemdef_manager, &hand);
float full_punch_interval = playeritem_toolcap.full_punch_interval;
float tool_reload_ratio = runData.time_from_last_punch / full_punch_interval;
tool_reload_ratio = MYMIN(tool_reload_ratio, 1.0);
tool_reload_ratio = std::min(tool_reload_ratio, 1.0f);
camera->update(player, dtime, tool_reload_ratio);
camera->step(dtime);
f32 camera_fov = camera->getFovMax();
v3s16 camera_offset = camera->getOffset();
m_camera_offset_changed = (camera_offset != old_camera_offset);
if (!m_flags.disable_camera_update) {
v3f camera_position = camera->getPosition();
v3f camera_direction = camera->getDirection();
client->getEnv().getClientMap().updateCamera(camera_position,
camera_direction, camera_fov, camera_offset, player->light_color);
if (m_camera_offset_changed) {
client->updateCameraOffset(camera_offset);
client->getEnv().updateCameraOffset(camera_offset);
clouds->updateCameraOffset(camera_offset);
}
client->getEnv().getClientMap().updateCamera(camera->getPosition(),
camera->getDirection(), camera->getFovMax(), camera->getOffset(),
player->light_color);
}
}
void Game::updateCameraMode()
{
LocalPlayer *player = client->getEnv().getLocalPlayer();
// Obey server choice
if (player->allowed_camera_mode != CAMERA_MODE_ANY)
camera->setCameraMode(player->allowed_camera_mode);
GenericCAO *playercao = player->getCAO();
if (playercao) {
// Make the player visible depending on camera mode.
playercao->updateMeshCulling();
playercao->setChildrenVisible(camera->getCameraMode() > CAMERA_MODE_FIRST);
}
}
void Game::updateCameraOffset()
{
ClientEnvironment &env = client->getEnv();
v3s16 old_camera_offset = camera->getOffset();
camera->updateOffset();
v3s16 camera_offset = camera->getOffset();
m_camera_offset_changed = camera_offset != old_camera_offset;
if (!m_camera_offset_changed)
return;
if (!m_flags.disable_camera_update) {
auto *shadow = RenderingEngine::get_shadow_renderer();
if (shadow) {
shadow->getDirectionalLight().updateCameraOffset(camera);
// FIXME: I bet we can be smarter about this and don't need to redraw
// the shadow map at all, but this is for someone else to figure out.
if (!g_settings->getFlag("performance_tradeoffs"))
shadow->setForceUpdateShadowMap();
}
env.getClientMap().updateCamera(camera->getPosition(),
camera->getDirection(), camera->getFovMax(), camera_offset,
env.getLocalPlayer()->light_color);
env.updateCameraOffset(camera_offset);
clouds->updateCameraOffset(camera_offset);
}
}
void Game::updateSound(f32 dtime)
{
@ -3053,12 +3120,15 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud)
ItemStack selected_item, hand_item;
const ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
const ItemDefinition &selected_def = selected_item.getDefinition(itemdef_manager);
f32 d = getToolRange(selected_item, hand_item, itemdef_manager);
const ItemDefinition &selected_def = tool_item.getDefinition(itemdef_manager);
f32 d = getToolRange(tool_item, hand_item, itemdef_manager);
core::line3d<f32> shootline;
switch (camera->getCameraMode()) {
case CAMERA_MODE_ANY:
assert(false);
break;
case CAMERA_MODE_FIRST:
// Shoot from camera position, with bobbing
shootline.start = camera->getPosition();
@ -3075,7 +3145,7 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud)
}
shootline.end = shootline.start + camera_direction * BS * d;
if (g_touchcontrols && isTouchCrosshairDisabled()) {
if (isTouchShootlineUsed()) {
shootline = g_touchcontrols->getShootline();
// Scale shootline to the acual distance the player can reach
shootline.end = shootline.start +
@ -3168,7 +3238,7 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud)
} else if (pointed.type == POINTEDTHING_OBJECT) {
v3f player_position = player->getPosition();
bool basic_debug_allowed = client->checkPrivilege("debug") || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
handlePointingAtObject(pointed, tool_item, player_position,
handlePointingAtObject(pointed, tool_item, hand_item, player_position,
m_game_ui->m_flags.show_basic_debug && basic_debug_allowed);
} else if (isKeyDown(KeyType::DIG)) {
// When button is held down in air, show continuous animation
@ -3234,9 +3304,10 @@ PointedThing Game::updatePointedThing(
hud->setSelectionPos(pos, camera_offset);
GenericCAO* gcao = dynamic_cast<GenericCAO*>(runData.selected_object);
if (gcao != nullptr && gcao->getProperties().rotate_selectionbox)
hud->setSelectionRotation(gcao->getSceneNode()->getAbsoluteTransformation().getRotationDegrees());
hud->setSelectionRotationRadians(gcao->getSceneNode()
->getAbsoluteTransformation().getRotationRadians());
else
hud->setSelectionRotation(v3f());
hud->setSelectionRotationRadians(v3f());
}
hud->setSelectedFaceNormal(result.raw_intersection_normal);
} else if (result.type == POINTEDTHING_NODE) {
@ -3246,17 +3317,15 @@ PointedThing Game::updatePointedThing(
n.getSelectionBoxes(nodedef, &boxes,
n.getNeighbors(result.node_undersurface, &map));
f32 d = 0.002 * BS;
for (std::vector<aabb3f>::const_iterator i = boxes.begin();
i != boxes.end(); ++i) {
aabb3f box = *i;
f32 d = 0.002f * BS;
for (aabb3f box : boxes) {
box.MinEdge -= v3f(d, d, d);
box.MaxEdge += v3f(d, d, d);
selectionboxes->push_back(box);
}
hud->setSelectionPos(intToFloat(result.node_undersurface, BS),
camera_offset);
hud->setSelectionRotation(v3f());
hud->setSelectionRotationRadians(v3f());
hud->setSelectedFaceNormal(result.intersection_normal);
}
@ -3451,9 +3520,8 @@ bool Game::nodePlacement(const ItemDefinition &selected_def,
u8 predicted_param2 = dir.Y < 0 ? 1 : 0;
if (selected_def.wallmounted_rotate_vertical) {
bool rotate90 = false;
v3f fnodepos = v3f(neighborpos.X, neighborpos.Y, neighborpos.Z);
v3f ppos = client->getEnv().getLocalPlayer()->getPosition() / BS;
v3f pdir = fnodepos - ppos;
v3f pdir = v3f::from(neighborpos) - ppos;
switch (predicted_f.drawtype) {
case NDT_TORCHLIKE: {
rotate90 = !((pdir.X < 0 && pdir.Z > 0) ||
@ -3584,8 +3652,8 @@ bool Game::nodePlacement(const ItemDefinition &selected_def,
}
}
void Game::handlePointingAtObject(const PointedThing &pointed,
const ItemStack &tool_item, const v3f &player_position, bool show_debug)
void Game::handlePointingAtObject(const PointedThing &pointed, const ItemStack &tool_item,
const ItemStack &hand_item, const v3f &player_position, bool show_debug)
{
std::wstring infotext = unescape_translate(
utf8_to_wide(runData.selected_object->infoText()));
@ -3615,6 +3683,7 @@ void Game::handlePointingAtObject(const PointedThing &pointed,
if (do_punch) {
infostream << "Punched object" << std::endl;
runData.punching = true;
runData.nodig_delay_timer = std::max(0.15f, m_repeat_dig_time);
}
if (do_punch_damage) {
@ -3623,7 +3692,7 @@ void Game::handlePointingAtObject(const PointedThing &pointed,
v3f dir = (objpos - player_position).normalize();
bool disable_send = runData.selected_object->directReportPunch(
dir, &tool_item, runData.time_from_last_punch);
dir, &tool_item, &hand_item, runData.time_from_last_punch);
runData.time_from_last_punch = 0;
if (!disable_send)
@ -3644,13 +3713,14 @@ void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
ClientMap &map = client->getEnv().getClientMap();
MapNode n = map.getNode(nodepos);
const auto &features = nodedef_manager->get(n);
const ItemStack &tool_item = selected_item.name.empty() ? hand_item : selected_item;
// NOTE: Similar piece of code exists on the server side for
// cheat detection.
// Get digging parameters
DigParams params = getDigParams(features.groups,
&selected_item.getToolCapabilities(itemdef_manager),
selected_item.wear);
&tool_item.getToolCapabilities(itemdef_manager, &hand_item),
tool_item.wear);
// If can't dig, try hand
if (!params.diggable) {
@ -3811,8 +3881,8 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
float old_brightness = sky->getBrightness();
direct_brightness = client->getEnv().getClientMap()
.getBackgroundBrightness(MYMIN(runData.fog_range * 1.2, 60 * BS),
daynight_ratio, (int)(old_brightness * 255.5), &sunlight_seen)
/ 255.0;
daynight_ratio, (int)(old_brightness * 255.5), &sunlight_seen)
/ 255.0;
}
float time_of_day_smooth = runData.time_of_day_smooth;
@ -3894,7 +3964,8 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
runData.update_draw_list_timer += dtime;
runData.touch_blocks_timer += dtime;
float update_draw_list_delta = 0.2f;
constexpr float update_draw_list_delta = 0.2f;
constexpr float touch_mapblock_delta = 4.0f;
v3f camera_direction = camera->getDirection();
@ -3907,7 +3978,7 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
runData.update_draw_list_timer = 0;
client->getEnv().getClientMap().updateDrawList();
runData.update_draw_list_last_cam_dir = camera_direction;
} else if (runData.touch_blocks_timer > update_draw_list_delta) {
} else if (runData.touch_blocks_timer > touch_mapblock_delta) {
client->getEnv().getClientMap().touchMapBlocks();
runData.touch_blocks_timer = 0;
} else if (RenderingEngine::get_shadow_renderer()) {
@ -3996,11 +4067,10 @@ void Game::updateShadows()
v3f light = is_day ? sky->getSunDirection() : sky->getMoonDirection();
v3f sun_pos = light * offset_constant;
shadow->getDirectionalLight().setDirection(sun_pos);
shadow->setTimeOfDay(in_timeofday);
shadow->getDirectionalLight().update_frustum(camera, client, m_camera_offset_changed);
shadow->getDirectionalLight().updateFrustum(camera, client);
}
void Game::drawScene(ProfilerGraph *graph, RunStats *stats)
@ -4049,7 +4119,7 @@ void Game::drawScene(ProfilerGraph *graph, RunStats *stats)
(player->hud_flags & HUD_FLAG_CROSSHAIR_VISIBLE) &&
(this->camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT));
if (g_touchcontrols && isTouchCrosshairDisabled())
if (isTouchShootlineUsed())
draw_crosshair = false;
this->m_rendering_engine->draw_scene(sky_color, this->m_game_ui->m_flags.show_hud,
@ -4105,6 +4175,8 @@ void Game::readSettings()
m_chat_log_buf.setLogLevel(chat_log_level);
m_cache_doubletap_jump = g_settings->getBool("doubletap_jump");
m_cache_toggle_sneak_key = g_settings->getBool("toggle_sneak_key");
m_cache_toggle_aux1_key = g_settings->getBool("toggle_aux1_key");
m_cache_enable_joysticks = g_settings->getBool("enable_joysticks");
m_cache_enable_fog = g_settings->getBool("enable_fog");
m_cache_mouse_sensitivity = g_settings->getFloat("mouse_sensitivity", 0.001f, 10.0f);

View file

@ -9,6 +9,7 @@
#include "renderingengine.h"
#include "client.h"
#include "scripting_client.h"
#include "cpp_api/s_client_common.h"
#include "clientmap.h"
#include "gui/guiFormSpecMenu.h"
#include "gui/mainmenumanager.h"
@ -70,69 +71,73 @@ struct TextDestPlayerInventory : public TextDest
Client *m_client;
};
struct LocalFormspecHandler : public TextDest
struct LocalScriptingFormspecHandler : public TextDest
{
LocalFormspecHandler(const std::string &formname)
{
m_formname = formname;
}
LocalFormspecHandler(const std::string &formname, Client *client):
m_client(client)
LocalScriptingFormspecHandler(const std::string &formname, ScriptApiClientCommon *script)
{
m_formname = formname;
m_script = script;
}
void gotText(const StringMap &fields)
{
if (m_formname == "MT_PAUSE_MENU") {
if (fields.find("btn_sound") != fields.end()) {
g_gamecallback->changeVolume();
return;
}
m_script->on_formspec_input(m_formname, fields);
}
if (fields.find("btn_key_config") != fields.end()) {
g_gamecallback->keyConfig();
return;
}
ScriptApiClientCommon *m_script = nullptr;
};
if (fields.find("btn_touchscreen_layout") != fields.end()) {
g_gamecallback->touchscreenLayout();
return;
}
struct HardcodedPauseFormspecHandler : public TextDest
{
HardcodedPauseFormspecHandler()
{
m_formname = "MT_PAUSE_MENU";
}
if (fields.find("btn_exit_menu") != fields.end()) {
g_gamecallback->disconnect();
return;
}
void gotText(const StringMap &fields)
{
if (fields.find("btn_settings") != fields.end()) {
g_gamecallback->openSettings();
return;
}
if (fields.find("btn_exit_os") != fields.end()) {
g_gamecallback->exitToOS();
if (fields.find("btn_sound") != fields.end()) {
g_gamecallback->changeVolume();
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();
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();
if (fields.find("btn_change_password") != fields.end()) {
g_gamecallback->changePassword();
return;
}
}
};
if (m_client->modsLoaded())
m_client->getScript()->on_formspec_input(m_formname, fields);
struct LegacyDeathFormspecHandler : public TextDest
{
LegacyDeathFormspecHandler(Client *client)
{
m_formname = "MT_DEATH_SCREEN";
m_client = client;
}
void gotText(const StringMap &fields)
{
if (fields.find("quit") != fields.end())
m_client->sendRespawnLegacy();
}
Client *m_client = nullptr;
@ -193,6 +198,15 @@ public:
//// GameFormSpec
void GameFormSpec::init(Client *client, RenderingEngine *rendering_engine, InputHandler *input)
{
m_client = client;
m_rendering_engine = rendering_engine;
m_input = input;
m_pause_script = std::make_unique<PauseMenuScripting>(client);
m_pause_script->loadBuiltin();
}
void GameFormSpec::deleteFormspec()
{
if (m_formspec) {
@ -208,35 +222,70 @@ GameFormSpec::~GameFormSpec() {
this->deleteFormspec();
}
void GameFormSpec::showFormSpec(const std::string &formspec, const std::string &formname)
bool GameFormSpec::handleEmptyFormspec(const std::string &formspec, const std::string &formname)
{
if (formspec.empty()) {
if (m_formspec && (formname.empty() || formname == m_formname)) {
m_formspec->quitMenu();
}
} 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());
return true;
}
return false;
}
void GameFormSpec::showLocalFormSpec(const std::string &formspec, const std::string &formname)
void GameFormSpec::showFormSpec(const std::string &formspec, const std::string &formname)
{
if (handleEmptyFormspec(formspec, formname))
return;
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::showCSMFormSpec(const std::string &formspec, const std::string &formname)
{
if (handleEmptyFormspec(formspec, formname))
return;
FormspecFormSource *fs_src = new FormspecFormSource(formspec);
LocalFormspecHandler *txt_dst =
new LocalFormspecHandler(formname, m_client);
LocalScriptingFormspecHandler *txt_dst =
new LocalScriptingFormspecHandler(formname, m_client->getScript());
m_formname = formname;
GUIFormSpecMenu::create(m_formspec, m_client, m_rendering_engine->get_gui_env(),
&m_input->joystick, fs_src, txt_dst, m_client->getFormspecPrepend(),
m_client->getSoundManager());
}
void GameFormSpec::showPauseMenuFormSpec(const std::string &formspec, const std::string &formname)
{
// The pause menu env is a trusted context like the mainmenu env and provides
// the in-game settings formspec.
// Neither CSM nor the server must be allowed to mess with it.
if (handleEmptyFormspec(formspec, formname))
return;
FormspecFormSource *fs_src = new FormspecFormSource(formspec);
LocalScriptingFormspecHandler *txt_dst =
new LocalScriptingFormspecHandler(formname, m_pause_script.get());
m_formname = formname;
GUIFormSpecMenu::create(m_formspec, m_client, m_rendering_engine->get_gui_env(),
// Ignore formspec prepend.
&m_input->joystick, fs_src, txt_dst, "",
m_client->getSoundManager());
m_formspec->doPause = true;
}
void GameFormSpec::showNodeFormspec(const std::string &formspec, const v3s16 &nodepos)
{
infostream << "Launching custom inventory view" << std::endl;
@ -325,26 +374,22 @@ void GameFormSpec::showPauseMenu()
<< strgettext("Continue") << "]";
if (!simple_singleplayer_mode) {
os << "button_exit[4," << (ypos++) << ";3,0.5;btn_change_password;"
os << "button[4," << (ypos++) << ";3,0.5;btn_change_password;"
<< strgettext("Change Password") << "]";
} else {
os << "field[4.95,0;5,1.5;;" << strgettext("Game paused") << ";]";
}
os << "button[4," << (ypos++) << ";3,0.5;btn_settings;"
<< strgettext("Settings") << "]";
#ifndef __ANDROID__
#if USE_SOUND
os << "button_exit[4," << (ypos++) << ";3,0.5;btn_sound;"
os << "button[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;"
@ -394,13 +439,13 @@ void GameFormSpec::showPauseMenu()
/* Note: FormspecFormSource and LocalFormspecHandler *
* are deleted by guiFormSpecMenu */
FormspecFormSource *fs_src = new FormspecFormSource(os.str());
LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_PAUSE_MENU");
HardcodedPauseFormspecHandler *txt_dst = new HardcodedPauseFormspecHandler();
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)
// game will be paused in next step, if in singleplayer (see Game::m_is_paused)
m_formspec->doPause = true;
}
@ -418,7 +463,7 @@ void GameFormSpec::showDeathFormspecLegacy()
/* 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);
LegacyDeathFormspecHandler *txt_dst = new LegacyDeathFormspecHandler(m_client);
GUIFormSpecMenu::create(m_formspec, m_client, m_rendering_engine->get_gui_env(),
&m_input->joystick, fs_src, txt_dst, m_client->getFormspecPrepend(),
@ -473,6 +518,11 @@ bool GameFormSpec::handleCallbacks()
return false;
}
if (g_gamecallback->settings_requested) {
m_pause_script->open_settings();
g_gamecallback->settings_requested = false;
}
if (g_gamecallback->changepassword_requested) {
(void)make_irr<GUIPasswordChange>(guienv, guiroot, -1,
&g_menumgr, m_client, texture_src);
@ -504,7 +554,7 @@ bool GameFormSpec::handleCallbacks()
}
if (g_gamecallback->keyconfig_changed) {
m_input->keycache.populate(); // update the cache with new settings
m_input->reloadKeybindings(); // update the cache with new settings
g_gamecallback->keyconfig_changed = false;
}

View file

@ -4,8 +4,10 @@
#pragma once
#include <memory>
#include <string>
#include "irr_v3d.h"
#include "scripting_pause_menu.h"
class Client;
class RenderingEngine;
@ -22,20 +24,19 @@ It includes:
*/
struct GameFormSpec
{
void init(Client *client, RenderingEngine *rendering_engine, InputHandler *input)
{
m_client = client;
m_rendering_engine = rendering_engine;
m_input = input;
}
void init(Client *client, RenderingEngine *rendering_engine, InputHandler *input);
~GameFormSpec();
void showFormSpec(const std::string &formspec, const std::string &formname);
void showLocalFormSpec(const std::string &formspec, const std::string &formname);
void showCSMFormSpec(const std::string &formspec, const std::string &formname);
// Used by the Lua pause menu environment to show formspecs.
// Currently only used for the in-game settings menu.
void showPauseMenuFormSpec(const std::string &formspec, const std::string &formname);
void showNodeFormspec(const std::string &formspec, const v3s16 &nodepos);
void showPlayerInventory();
void showDeathFormspecLegacy();
// Shows the hardcoded "main" pause menu.
void showPauseMenu();
void update();
@ -52,11 +53,14 @@ private:
Client *m_client;
RenderingEngine *m_rendering_engine;
InputHandler *m_input;
std::unique_ptr<PauseMenuScripting> m_pause_script;
// Default: "". If other than "": Empty show_formspec packets will only
// close the formspec when the formname matches
std::string m_formname;
GUIFormSpecMenu *m_formspec = nullptr;
bool handleEmptyFormspec(const std::string &formspec, const std::string &formname);
void deleteFormspec();
};

View file

@ -17,17 +17,16 @@
#include "client/tile.h"
#include "localplayer.h"
#include "camera.h"
#include "porting.h"
#include "fontengine.h"
#include "guiscalingfilter.h"
#include "mesh.h"
#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"
#include "gui/drawItemStack.h"
#define OBJECT_CROSSHAIR_LINE_SIZE 8
#define CROSSHAIR_LINE_SIZE 10
@ -880,7 +879,7 @@ void Hud::drawSelectionMesh()
core::matrix4 translate;
translate.setTranslation(m_selection_pos_with_offset);
core::matrix4 rotation;
rotation.setRotationDegrees(m_selection_rotation);
rotation.setRotationRadians(m_selection_rotation_radians);
driver->setTransform(video::ETS_WORLD, translate * rotation);
if (m_mode == HIGHLIGHT_BOX) {
@ -965,8 +964,8 @@ void Hud::drawBlockBounds()
v3f pmax = v3f(x, y, 1 + radius) * MAP_BLOCKSIZE * BS;
driver->draw3DLine(
base_corner + v3f(pmin.X, pmin.Y, pmin.Z),
base_corner + v3f(pmax.X, pmax.Y, pmax.Z),
base_corner + pmin,
base_corner + pmax,
choose_color(block_pos.X, block_pos.Y)
);
driver->draw3DLine(
@ -1039,295 +1038,3 @@ void Hud::resizeHotbar() {
m_displaycenter = v2s32(m_screensize.X/2,m_screensize.Y/2);
}
}
struct MeshTimeInfo {
u64 time;
scene::IMesh *mesh = nullptr;
};
void drawItemStack(
video::IVideoDriver *driver,
gui::IGUIFont *font,
const ItemStack &item,
const core::rect<s32> &rect,
const core::rect<s32> *clip,
Client *client,
ItemRotationKind rotation_kind,
const v3s16 &angle,
const v3s16 &rotation_speed)
{
static MeshTimeInfo rotation_time_infos[IT_ROT_NONE];
if (item.empty()) {
if (rotation_kind < IT_ROT_NONE && rotation_kind != IT_ROT_OTHER) {
rotation_time_infos[rotation_kind].mesh = NULL;
}
return;
}
const bool enable_animations = g_settings->getBool("inventory_items_animations");
auto *idef = client->idef();
const ItemDefinition &def = item.getDefinition(idef);
bool draw_overlay = false;
const std::string inventory_image = item.getInventoryImage(idef);
const std::string inventory_overlay = item.getInventoryOverlay(idef);
bool has_mesh = false;
ItemMesh *imesh;
core::rect<s32> viewrect = rect;
if (clip != nullptr)
viewrect.clipAgainst(*clip);
// Render as mesh if animated or no inventory image
if ((enable_animations && rotation_kind < IT_ROT_NONE) || inventory_image.empty()) {
imesh = idef->getWieldMesh(item, client);
has_mesh = imesh && imesh->mesh;
}
if (has_mesh) {
scene::IMesh *mesh = imesh->mesh;
driver->clearBuffers(video::ECBF_DEPTH);
s32 delta = 0;
if (rotation_kind < IT_ROT_NONE) {
MeshTimeInfo &ti = rotation_time_infos[rotation_kind];
if (mesh != ti.mesh && rotation_kind != IT_ROT_OTHER) {
ti.mesh = mesh;
ti.time = porting::getTimeMs();
} else {
delta = porting::getDeltaMs(ti.time, porting::getTimeMs()) % 100000;
}
}
core::rect<s32> oldViewPort = driver->getViewPort();
core::matrix4 oldProjMat = driver->getTransform(video::ETS_PROJECTION);
core::matrix4 oldViewMat = driver->getTransform(video::ETS_VIEW);
core::matrix4 ProjMatrix;
ProjMatrix.buildProjectionMatrixOrthoLH(2.0f, 2.0f, -1.0f, 100.0f);
core::matrix4 ViewMatrix;
ViewMatrix.buildProjectionMatrixOrthoLH(
2.0f * viewrect.getWidth() / rect.getWidth(),
2.0f * viewrect.getHeight() / rect.getHeight(),
-1.0f,
100.0f);
ViewMatrix.setTranslation(core::vector3df(
1.0f * (rect.LowerRightCorner.X + rect.UpperLeftCorner.X -
viewrect.LowerRightCorner.X - viewrect.UpperLeftCorner.X) /
viewrect.getWidth(),
1.0f * (viewrect.LowerRightCorner.Y + viewrect.UpperLeftCorner.Y -
rect.LowerRightCorner.Y - rect.UpperLeftCorner.Y) /
viewrect.getHeight(),
0.0f));
driver->setTransform(video::ETS_PROJECTION, ProjMatrix);
driver->setTransform(video::ETS_VIEW, ViewMatrix);
core::matrix4 matrix;
matrix.makeIdentity();
if (enable_animations) {
float timer_f = (float) delta / 5000.f;
matrix.setRotationDegrees(v3f(
angle.X + rotation_speed.X * 3.60f * timer_f,
angle.Y + rotation_speed.Y * 3.60f * timer_f,
angle.Z + rotation_speed.Z * 3.60f * timer_f)
);
}
driver->setTransform(video::ETS_WORLD, matrix);
driver->setViewPort(viewrect);
video::SColor basecolor =
client->idef()->getItemstackColor(item, client);
const u32 mc = mesh->getMeshBufferCount();
if (mc > imesh->buffer_colors.size())
imesh->buffer_colors.resize(mc);
for (u32 j = 0; j < mc; ++j) {
scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
video::SColor c = basecolor;
auto &p = imesh->buffer_colors[j];
p.applyOverride(c);
// TODO: could be moved to a shader
if (p.needColorize(c)) {
buf->setDirty(scene::EBT_VERTEX);
if (imesh->needs_shading)
colorizeMeshBuffer(buf, &c);
else
setMeshBufferColor(buf, c);
}
video::SMaterial &material = buf->getMaterial();
material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
driver->setMaterial(material);
driver->drawMeshBuffer(buf);
}
driver->setTransform(video::ETS_VIEW, oldViewMat);
driver->setTransform(video::ETS_PROJECTION, oldProjMat);
driver->setViewPort(oldViewPort);
draw_overlay = def.type == ITEM_NODE && inventory_image.empty();
} else { // Otherwise just draw as 2D
video::ITexture *texture = client->idef()->getInventoryTexture(item, client);
video::SColor color;
if (texture) {
color = client->idef()->getItemstackColor(item, client);
} else {
color = video::SColor(255, 255, 255, 255);
ITextureSource *tsrc = client->getTextureSource();
texture = tsrc->getTexture("no_texture.png");
if (!texture)
return;
}
const video::SColor colors[] = { color, color, color, color };
draw2DImageFilterScaled(driver, texture, rect,
core::rect<s32>({0, 0}, core::dimension2di(texture->getOriginalSize())),
clip, colors, true);
draw_overlay = true;
}
// draw the inventory_overlay
if (!inventory_overlay.empty() && draw_overlay) {
ITextureSource *tsrc = client->getTextureSource();
video::ITexture *overlay_texture = tsrc->getTexture(inventory_overlay);
core::dimension2d<u32> dimens = overlay_texture->getOriginalSize();
core::rect<s32> srcrect(0, 0, dimens.Width, dimens.Height);
draw2DImageFilterScaled(driver, overlay_texture, rect, srcrect, clip, 0, true);
}
if (def.type == ITEM_TOOL && item.wear != 0) {
// Draw a progressbar
float barheight = static_cast<float>(rect.getHeight()) / 16;
float barpad_x = static_cast<float>(rect.getWidth()) / 16;
float barpad_y = static_cast<float>(rect.getHeight()) / 16;
core::rect<s32> progressrect(
rect.UpperLeftCorner.X + barpad_x,
rect.LowerRightCorner.Y - barpad_y - barheight,
rect.LowerRightCorner.X - barpad_x,
rect.LowerRightCorner.Y - barpad_y);
// Shrink progressrect by amount of tool damage
float wear = item.wear / 65535.0f;
int progressmid =
wear * progressrect.UpperLeftCorner.X +
(1 - wear) * progressrect.LowerRightCorner.X;
// Compute progressbar color
// default scheme:
// wear = 0.0: green
// wear = 0.5: yellow
// wear = 1.0: red
video::SColor color;
auto barParams = item.getWearBarParams(client->idef());
if (barParams.has_value()) {
f32 durabilityPercent = 1.0 - wear;
color = barParams->getWearBarColor(durabilityPercent);
} else {
color = video::SColor(255, 255, 255, 255);
int wear_i = MYMIN(std::floor(wear * 600), 511);
wear_i = MYMIN(wear_i + 10, 511);
if (wear_i <= 255)
color.set(255, wear_i, 255, 0);
else
color.set(255, 255, 511 - wear_i, 0);
}
core::rect<s32> progressrect2 = progressrect;
progressrect2.LowerRightCorner.X = progressmid;
driver->draw2DRectangle(color, progressrect2, clip);
color = video::SColor(255, 0, 0, 0);
progressrect2 = progressrect;
progressrect2.UpperLeftCorner.X = progressmid;
driver->draw2DRectangle(color, progressrect2, clip);
}
const std::string &count_text = item.metadata.getString("count_meta");
if (font != nullptr && (item.count >= 2 || !count_text.empty())) {
// Get the item count as a string
std::string text = count_text.empty() ? itos(item.count) : count_text;
v2u32 dim = font->getDimension(utf8_to_wide(unescape_enriched(text)).c_str());
v2s32 sdim(dim.X, dim.Y);
core::rect<s32> rect2(
rect.LowerRightCorner - sdim,
rect.LowerRightCorner
);
// get the count alignment
s32 count_alignment = stoi(item.metadata.getString("count_alignment"));
if (count_alignment != 0) {
s32 a_x = count_alignment & 3;
s32 a_y = (count_alignment >> 2) & 3;
s32 x1, x2, y1, y2;
switch (a_x) {
case 1: // left
x1 = rect.UpperLeftCorner.X;
x2 = x1 + sdim.X;
break;
case 2: // middle
x1 = (rect.UpperLeftCorner.X + rect.LowerRightCorner.X - sdim.X) / 2;
x2 = x1 + sdim.X;
break;
case 3: // right
x2 = rect.LowerRightCorner.X;
x1 = x2 - sdim.X;
break;
default: // 0 = default
x1 = rect2.UpperLeftCorner.X;
x2 = rect2.LowerRightCorner.X;
break;
}
switch (a_y) {
case 1: // up
y1 = rect.UpperLeftCorner.Y;
y2 = y1 + sdim.Y;
break;
case 2: // middle
y1 = (rect.UpperLeftCorner.Y + rect.LowerRightCorner.Y - sdim.Y) / 2;
y2 = y1 + sdim.Y;
break;
case 3: // down
y2 = rect.LowerRightCorner.Y;
y1 = y2 - sdim.Y;
break;
default: // 0 = default
y1 = rect2.UpperLeftCorner.Y;
y2 = rect2.LowerRightCorner.Y;
break;
}
rect2 = core::rect<s32>(x1, y1, x2, y2);
}
video::SColor color(255, 255, 255, 255);
font->draw(utf8_to_wide(text).c_str(), rect2, color, false, false, &viewrect);
}
}
void drawItemStack(
video::IVideoDriver *driver,
gui::IGUIFont *font,
const ItemStack &item,
const core::rect<s32> &rect,
const core::rect<s32> *clip,
Client *client,
ItemRotationKind rotation_kind)
{
drawItemStack(driver, font, item, rect, clip, client, rotation_kind,
v3s16(0, 0, 0), v3s16(0, 100, 0));
}

View file

@ -74,9 +74,15 @@ public:
v3f getSelectionPos() const { return m_selection_pos; }
void setSelectionRotation(v3f rotation) { m_selection_rotation = rotation; }
void setSelectionRotationRadians(v3f rotation)
{
m_selection_rotation_radians = rotation;
}
v3f getSelectionRotation() const { return m_selection_rotation; }
v3f getSelectionRotationRadians() const
{
return m_selection_rotation_radians;
}
void setSelectionMeshColor(const video::SColor &color)
{
@ -129,7 +135,7 @@ private:
std::vector<aabb3f> m_halo_boxes;
v3f m_selection_pos;
v3f m_selection_pos_with_offset;
v3f m_selection_rotation;
v3f m_selection_rotation_radians;
scene::IMesh *m_selection_mesh = nullptr;
video::SColor m_selection_mesh_color;
@ -147,32 +153,3 @@ private:
HIGHLIGHT_NONE
} m_mode;
};
enum ItemRotationKind
{
IT_ROT_SELECTED,
IT_ROT_HOVERED,
IT_ROT_DRAGGED,
IT_ROT_OTHER,
IT_ROT_NONE, // Must be last, also serves as number
};
void drawItemStack(video::IVideoDriver *driver,
gui::IGUIFont *font,
const ItemStack &item,
const core::rect<s32> &rect,
const core::rect<s32> *clip,
Client *client,
ItemRotationKind rotation_kind);
void drawItemStack(
video::IVideoDriver *driver,
gui::IGUIFont *font,
const ItemStack &item,
const core::rect<s32> &rect,
const core::rect<s32> *clip,
Client *client,
ItemRotationKind rotation_kind,
const v3s16 &angle,
const v3s16 &rotation_speed);

View file

@ -285,13 +285,13 @@ void imageScaleNNAA(video::IImage *src, const core::rect<s32> &srcrect, video::I
maxsx = minsx + sw / dim.Width;
maxsx = rangelim(maxsx, 0, sox + sw);
if (minsx > maxsx)
SWAP(double, minsx, maxsx);
std::swap(minsx, maxsx);
minsy = soy + (dy * sh / dim.Height);
minsy = rangelim(minsy, 0, soy + sh);
maxsy = minsy + sh / dim.Height;
maxsy = rangelim(maxsy, 0, soy + sh);
if (minsy > maxsy)
SWAP(double, minsy, maxsy);
std::swap(minsy, maxsy);
// Total area, and integral of r, g, b values over that area,
// initialized to zero, to be summed up in next loops.

View file

@ -949,9 +949,10 @@ static void imageTransform(u32 transform, video::IImage *src, video::IImage *dst
#define CHECK_DIM(w, h) \
do { \
if ((w) <= 0 || (h) <= 0 || (w) >= 0xffff || (h) >= 0xffff) { \
COMPLAIN_INVALID("width or height"); \
} \
if ((w) <= 0 || (w) > MAX_IMAGE_DIMENSION) \
COMPLAIN_INVALID("width"); \
if ((h) <= 0 || (h) > MAX_IMAGE_DIMENSION) \
COMPLAIN_INVALID("height"); \
} while(0)
bool ImageSource::generateImagePart(std::string_view part_of_name,
@ -1350,6 +1351,8 @@ bool ImageSource::generateImagePart(std::string_view part_of_name,
v2u32 frame_size = baseimg->getDimension();
frame_size.Y /= frame_count;
if (frame_size.Y == 0)
frame_size.Y = 1;
video::IImage *img = driver->createImage(video::ECF_A8R8G8B8,
frame_size);
@ -1498,11 +1501,13 @@ bool ImageSource::generateImagePart(std::string_view part_of_name,
u32 w = scale * dim.Width;
u32 h = scale * dim.Height;
const core::dimension2d<u32> newdim(w, h);
video::IImage *newimg = driver->createImage(
baseimg->getColorFormat(), newdim);
baseimg->copyToScaling(newimg);
baseimg->drop();
baseimg = newimg;
if (w <= MAX_IMAGE_DIMENSION && h <= MAX_IMAGE_DIMENSION) {
video::IImage *newimg = driver->createImage(
baseimg->getColorFormat(), newdim);
baseimg->copyToScaling(newimg);
baseimg->drop();
baseimg = newimg;
}
}
}
}

View file

@ -45,6 +45,12 @@ struct ImageSource {
// Insert a source image into the cache without touching the filesystem.
void insertSourceImage(const std::string &name, video::IImage *img, bool prefer_local);
// This was picked so that the image buffer size fits in an s32 (assuming 32bpp).
// The exact value is 23170 but this provides some leeway.
// In theory something like 33333x123 could be allowed, but there is no strong
// need or argument. Irrlicht also has the same limit.
static constexpr int MAX_IMAGE_DIMENSION = 23000;
private:
// Generate image based on a string like "stone.png" or "[crack:1:0".

View file

@ -12,75 +12,98 @@
#include "log_internal.h"
#include "client/renderingengine.h"
void KeyCache::populate_nonchanging()
void MyEventReceiver::reloadKeybindings()
{
key[KeyType::ESC] = EscapeKey;
}
keybindings[KeyType::FORWARD] = getKeySetting("keymap_forward");
keybindings[KeyType::BACKWARD] = getKeySetting("keymap_backward");
keybindings[KeyType::LEFT] = getKeySetting("keymap_left");
keybindings[KeyType::RIGHT] = getKeySetting("keymap_right");
keybindings[KeyType::JUMP] = getKeySetting("keymap_jump");
keybindings[KeyType::AUX1] = getKeySetting("keymap_aux1");
keybindings[KeyType::SNEAK] = getKeySetting("keymap_sneak");
keybindings[KeyType::DIG] = getKeySetting("keymap_dig");
keybindings[KeyType::PLACE] = getKeySetting("keymap_place");
void KeyCache::populate()
{
key[KeyType::FORWARD] = getKeySetting("keymap_forward");
key[KeyType::BACKWARD] = getKeySetting("keymap_backward");
key[KeyType::LEFT] = getKeySetting("keymap_left");
key[KeyType::RIGHT] = getKeySetting("keymap_right");
key[KeyType::JUMP] = getKeySetting("keymap_jump");
key[KeyType::AUX1] = getKeySetting("keymap_aux1");
key[KeyType::SNEAK] = getKeySetting("keymap_sneak");
key[KeyType::DIG] = getKeySetting("keymap_dig");
key[KeyType::PLACE] = getKeySetting("keymap_place");
keybindings[KeyType::ESC] = EscapeKey;
key[KeyType::AUTOFORWARD] = getKeySetting("keymap_autoforward");
keybindings[KeyType::AUTOFORWARD] = getKeySetting("keymap_autoforward");
key[KeyType::DROP] = getKeySetting("keymap_drop");
key[KeyType::INVENTORY] = getKeySetting("keymap_inventory");
key[KeyType::CHAT] = getKeySetting("keymap_chat");
key[KeyType::CMD] = getKeySetting("keymap_cmd");
key[KeyType::CMD_LOCAL] = getKeySetting("keymap_cmd_local");
key[KeyType::CONSOLE] = getKeySetting("keymap_console");
key[KeyType::MINIMAP] = getKeySetting("keymap_minimap");
key[KeyType::FREEMOVE] = getKeySetting("keymap_freemove");
key[KeyType::PITCHMOVE] = getKeySetting("keymap_pitchmove");
key[KeyType::FASTMOVE] = getKeySetting("keymap_fastmove");
key[KeyType::NOCLIP] = getKeySetting("keymap_noclip");
key[KeyType::HOTBAR_PREV] = getKeySetting("keymap_hotbar_previous");
key[KeyType::HOTBAR_NEXT] = getKeySetting("keymap_hotbar_next");
key[KeyType::MUTE] = getKeySetting("keymap_mute");
key[KeyType::INC_VOLUME] = getKeySetting("keymap_increase_volume");
key[KeyType::DEC_VOLUME] = getKeySetting("keymap_decrease_volume");
key[KeyType::CINEMATIC] = getKeySetting("keymap_cinematic");
key[KeyType::SCREENSHOT] = getKeySetting("keymap_screenshot");
key[KeyType::TOGGLE_BLOCK_BOUNDS] = getKeySetting("keymap_toggle_block_bounds");
key[KeyType::TOGGLE_HUD] = getKeySetting("keymap_toggle_hud");
key[KeyType::TOGGLE_CHAT] = getKeySetting("keymap_toggle_chat");
key[KeyType::TOGGLE_FOG] = getKeySetting("keymap_toggle_fog");
key[KeyType::TOGGLE_UPDATE_CAMERA] = getKeySetting("keymap_toggle_update_camera");
key[KeyType::TOGGLE_DEBUG] = getKeySetting("keymap_toggle_debug");
key[KeyType::TOGGLE_PROFILER] = getKeySetting("keymap_toggle_profiler");
key[KeyType::CAMERA_MODE] = getKeySetting("keymap_camera_mode");
key[KeyType::INCREASE_VIEWING_RANGE] =
keybindings[KeyType::DROP] = getKeySetting("keymap_drop");
keybindings[KeyType::INVENTORY] = getKeySetting("keymap_inventory");
keybindings[KeyType::CHAT] = getKeySetting("keymap_chat");
keybindings[KeyType::CMD] = getKeySetting("keymap_cmd");
keybindings[KeyType::CMD_LOCAL] = getKeySetting("keymap_cmd_local");
keybindings[KeyType::CONSOLE] = getKeySetting("keymap_console");
keybindings[KeyType::MINIMAP] = getKeySetting("keymap_minimap");
keybindings[KeyType::FREEMOVE] = getKeySetting("keymap_freemove");
keybindings[KeyType::PITCHMOVE] = getKeySetting("keymap_pitchmove");
keybindings[KeyType::FASTMOVE] = getKeySetting("keymap_fastmove");
keybindings[KeyType::NOCLIP] = getKeySetting("keymap_noclip");
keybindings[KeyType::HOTBAR_PREV] = getKeySetting("keymap_hotbar_previous");
keybindings[KeyType::HOTBAR_NEXT] = getKeySetting("keymap_hotbar_next");
keybindings[KeyType::MUTE] = getKeySetting("keymap_mute");
keybindings[KeyType::INC_VOLUME] = getKeySetting("keymap_increase_volume");
keybindings[KeyType::DEC_VOLUME] = getKeySetting("keymap_decrease_volume");
keybindings[KeyType::CINEMATIC] = getKeySetting("keymap_cinematic");
keybindings[KeyType::SCREENSHOT] = getKeySetting("keymap_screenshot");
keybindings[KeyType::TOGGLE_BLOCK_BOUNDS] = getKeySetting("keymap_toggle_block_bounds");
keybindings[KeyType::TOGGLE_HUD] = getKeySetting("keymap_toggle_hud");
keybindings[KeyType::TOGGLE_CHAT] = getKeySetting("keymap_toggle_chat");
keybindings[KeyType::TOGGLE_FOG] = getKeySetting("keymap_toggle_fog");
keybindings[KeyType::TOGGLE_UPDATE_CAMERA] = getKeySetting("keymap_toggle_update_camera");
keybindings[KeyType::TOGGLE_DEBUG] = getKeySetting("keymap_toggle_debug");
keybindings[KeyType::TOGGLE_PROFILER] = getKeySetting("keymap_toggle_profiler");
keybindings[KeyType::CAMERA_MODE] = getKeySetting("keymap_camera_mode");
keybindings[KeyType::INCREASE_VIEWING_RANGE] =
getKeySetting("keymap_increase_viewing_range_min");
key[KeyType::DECREASE_VIEWING_RANGE] =
keybindings[KeyType::DECREASE_VIEWING_RANGE] =
getKeySetting("keymap_decrease_viewing_range_min");
key[KeyType::RANGESELECT] = getKeySetting("keymap_rangeselect");
key[KeyType::ZOOM] = getKeySetting("keymap_zoom");
keybindings[KeyType::RANGESELECT] = getKeySetting("keymap_rangeselect");
keybindings[KeyType::ZOOM] = getKeySetting("keymap_zoom");
key[KeyType::QUICKTUNE_NEXT] = getKeySetting("keymap_quicktune_next");
key[KeyType::QUICKTUNE_PREV] = getKeySetting("keymap_quicktune_prev");
key[KeyType::QUICKTUNE_INC] = getKeySetting("keymap_quicktune_inc");
key[KeyType::QUICKTUNE_DEC] = getKeySetting("keymap_quicktune_dec");
keybindings[KeyType::QUICKTUNE_NEXT] = getKeySetting("keymap_quicktune_next");
keybindings[KeyType::QUICKTUNE_PREV] = getKeySetting("keymap_quicktune_prev");
keybindings[KeyType::QUICKTUNE_INC] = getKeySetting("keymap_quicktune_inc");
keybindings[KeyType::QUICKTUNE_DEC] = getKeySetting("keymap_quicktune_dec");
for (int i = 0; i < HUD_HOTBAR_ITEMCOUNT_MAX; i++) {
std::string slot_key_name = "keymap_slot" + std::to_string(i + 1);
key[KeyType::SLOT_1 + i] = getKeySetting(slot_key_name.c_str());
keybindings[KeyType::SLOT_1 + i] = getKeySetting(slot_key_name.c_str());
}
if (handler) {
// First clear all keys, then re-add the ones we listen for
handler->dontListenForKeys();
for (const KeyPress &k : key) {
handler->listenForKey(k);
}
handler->listenForKey(EscapeKey);
// First clear all keys, then re-add the ones we listen for
keysListenedFor.clear();
for (int i = 0; i < KeyType::INTERNAL_ENUM_COUNT; i++) {
listenForKey(keybindings[i], static_cast<GameKeyType>(i));
}
}
bool MyEventReceiver::setKeyDown(KeyPress keyCode, bool is_down)
{
if (keysListenedFor.find(keyCode) == keysListenedFor.end()) // ignore irrelevant key input
return false;
auto action = keysListenedFor[keyCode];
if (is_down) {
physicalKeyDown.insert(keyCode);
setKeyDown(action, true);
} else {
physicalKeyDown.erase(keyCode);
setKeyDown(action, false);
}
return true;
}
void MyEventReceiver::setKeyDown(GameKeyType action, bool is_down)
{
if (is_down) {
if (!IsKeyDown(action))
keyWasPressed.set(action);
keyIsDown.set(action);
keyWasDown.set(action);
} else {
if (IsKeyDown(action))
keyWasReleased.set(action);
keyIsDown.reset(action);
}
}
@ -111,7 +134,7 @@ bool MyEventReceiver::OnEvent(const SEvent &event)
// This is separate from other keyboard handling so that it also works in menus.
if (event.EventType == EET_KEY_INPUT_EVENT) {
const KeyPress keyCode(event.KeyInput);
KeyPress keyCode(event.KeyInput);
if (keyCode == getKeySetting("keymap_fullscreen")) {
if (event.KeyInput.PressedDown && !fullscreen_is_down) {
IrrlichtDevice *device = RenderingEngine::get_raw_device();
@ -142,24 +165,9 @@ bool MyEventReceiver::OnEvent(const SEvent &event)
// Remember whether each key is down or up
if (event.EventType == irr::EET_KEY_INPUT_EVENT) {
const KeyPress keyCode(event.KeyInput);
if (keysListenedFor[keyCode]) {
if (event.KeyInput.PressedDown) {
if (!IsKeyDown(keyCode))
keyWasPressed.set(keyCode);
keyIsDown.set(keyCode);
keyWasDown.set(keyCode);
} else {
if (IsKeyDown(keyCode))
keyWasReleased.set(keyCode);
keyIsDown.unset(keyCode);
}
KeyPress keyCode(event.KeyInput);
if (setKeyDown(keyCode, event.KeyInput.PressedDown))
return true;
}
} else if (g_touchcontrols && event.EventType == irr::EET_TOUCH_INPUT_EVENT) {
// In case of touchcontrols, we have to handle different events
g_touchcontrols->translateEvent(event);
@ -171,31 +179,22 @@ bool MyEventReceiver::OnEvent(const SEvent &event)
// Handle mouse events
switch (event.MouseInput.Event) {
case EMIE_LMOUSE_PRESSED_DOWN:
keyIsDown.set(LMBKey);
keyWasDown.set(LMBKey);
keyWasPressed.set(LMBKey);
setKeyDown(LMBKey, true);
break;
case EMIE_MMOUSE_PRESSED_DOWN:
keyIsDown.set(MMBKey);
keyWasDown.set(MMBKey);
keyWasPressed.set(MMBKey);
setKeyDown(MMBKey, true);
break;
case EMIE_RMOUSE_PRESSED_DOWN:
keyIsDown.set(RMBKey);
keyWasDown.set(RMBKey);
keyWasPressed.set(RMBKey);
setKeyDown(RMBKey, true);
break;
case EMIE_LMOUSE_LEFT_UP:
keyIsDown.unset(LMBKey);
keyWasReleased.set(LMBKey);
setKeyDown(LMBKey, false);
break;
case EMIE_MMOUSE_LEFT_UP:
keyIsDown.unset(MMBKey);
keyWasReleased.set(MMBKey);
setKeyDown(MMBKey, false);
break;
case EMIE_RMOUSE_LEFT_UP:
keyIsDown.unset(RMBKey);
keyWasReleased.set(RMBKey);
setKeyDown(RMBKey, false);
break;
case EMIE_MOUSE_WHEEL:
mouse_wheel += event.MouseInput.Wheel;
@ -257,7 +256,7 @@ s32 RandomInputHandler::Rand(s32 min, s32 max)
}
struct RandomInputHandlerSimData {
std::string key;
GameKeyType key;
float counter;
int time_max;
};
@ -265,19 +264,19 @@ struct RandomInputHandlerSimData {
void RandomInputHandler::step(float dtime)
{
static RandomInputHandlerSimData rnd_data[] = {
{ "keymap_jump", 0.0f, 40 },
{ "keymap_aux1", 0.0f, 40 },
{ "keymap_forward", 0.0f, 40 },
{ "keymap_left", 0.0f, 40 },
{ "keymap_dig", 0.0f, 30 },
{ "keymap_place", 0.0f, 15 }
{ KeyType::JUMP, 0.0f, 40 },
{ KeyType::AUX1, 0.0f, 40 },
{ KeyType::FORWARD, 0.0f, 40 },
{ KeyType::LEFT, 0.0f, 40 },
{ KeyType::DIG, 0.0f, 30 },
{ KeyType::PLACE, 0.0f, 15 }
};
for (auto &i : rnd_data) {
i.counter -= dtime;
if (i.counter < 0.0) {
i.counter = 0.1 * Rand(1, i.time_max);
keydown.toggle(getKeySetting(i.key.c_str()));
keydown.flip(i.key);
}
}
{

View file

@ -7,7 +7,10 @@
#include "irrlichttypes.h"
#include "irr_v2d.h"
#include "joystick_controller.h"
#include <array>
#include <list>
#include <set>
#include <unordered_map>
#include "keycode.h"
class InputHandler;
@ -17,142 +20,32 @@ enum class PointerType {
Touch,
};
/****************************************************************************
Fast key cache for main game loop
****************************************************************************/
/* This is faster than using getKeySetting with the tradeoff that functions
* using it must make sure that it's initialised before using it and there is
* no error handling (for example bounds checking). This is really intended for
* use only in the main running loop of the client (the_game()) where the faster
* (up to 10x faster) key lookup is an asset. Other parts of the codebase
* (e.g. formspecs) should continue using getKeySetting().
*/
struct KeyCache
{
KeyCache()
{
handler = NULL;
populate();
populate_nonchanging();
}
void populate();
// Keys that are not settings dependent
void populate_nonchanging();
KeyPress key[KeyType::INTERNAL_ENUM_COUNT];
InputHandler *handler;
};
class KeyList : private std::list<KeyPress>
{
typedef std::list<KeyPress> super;
typedef super::iterator iterator;
typedef super::const_iterator const_iterator;
virtual const_iterator find(const KeyPress &key) const
{
const_iterator f(begin());
const_iterator e(end());
while (f != e) {
if (*f == key)
return f;
++f;
}
return e;
}
virtual iterator find(const KeyPress &key)
{
iterator f(begin());
iterator e(end());
while (f != e) {
if (*f == key)
return f;
++f;
}
return e;
}
public:
void clear() { super::clear(); }
void set(const KeyPress &key)
{
if (find(key) == end())
push_back(key);
}
void unset(const KeyPress &key)
{
iterator p(find(key));
if (p != end())
erase(p);
}
void toggle(const KeyPress &key)
{
iterator p(this->find(key));
if (p != end())
erase(p);
else
push_back(key);
}
void append(const KeyList &other)
{
for (const KeyPress &key : other) {
set(key);
}
}
bool operator[](const KeyPress &key) const { return find(key) != end(); }
};
class MyEventReceiver : public IEventReceiver
{
public:
// This is the one method that we have to implement
virtual bool OnEvent(const SEvent &event);
bool IsKeyDown(const KeyPress &keyCode) const { return keyIsDown[keyCode]; }
bool IsKeyDown(GameKeyType key) const { return keyIsDown[key]; }
// Checks whether a key was down and resets the state
bool WasKeyDown(const KeyPress &keyCode)
bool WasKeyDown(GameKeyType key)
{
bool b = keyWasDown[keyCode];
bool b = keyWasDown[key];
if (b)
keyWasDown.unset(keyCode);
keyWasDown.reset(key);
return b;
}
// Checks whether a key was just pressed. State will be cleared
// in the subsequent iteration of Game::processPlayerInteraction
bool WasKeyPressed(const KeyPress &keycode) const { return keyWasPressed[keycode]; }
bool WasKeyPressed(GameKeyType key) const { return keyWasPressed[key]; }
// Checks whether a key was just released. State will be cleared
// in the subsequent iteration of Game::processPlayerInteraction
bool WasKeyReleased(const KeyPress &keycode) const { return keyWasReleased[keycode]; }
bool WasKeyReleased(GameKeyType key) const { return keyWasReleased[key]; }
void listenForKey(const KeyPress &keyCode)
{
keysListenedFor.set(keyCode);
}
void dontListenForKeys()
{
keysListenedFor.clear();
}
void reloadKeybindings();
s32 getMouseWheel()
{
@ -163,28 +56,30 @@ public:
void clearInput()
{
keyIsDown.clear();
keyWasDown.clear();
keyWasPressed.clear();
keyWasReleased.clear();
physicalKeyDown.clear();
keyIsDown.reset();
keyWasDown.reset();
keyWasPressed.reset();
keyWasReleased.reset();
mouse_wheel = 0;
}
void releaseAllKeys()
{
keyWasReleased.append(keyIsDown);
keyIsDown.clear();
physicalKeyDown.clear();
keyWasReleased |= keyIsDown;
keyIsDown.reset();
}
void clearWasKeyPressed()
{
keyWasPressed.clear();
keyWasPressed.reset();
}
void clearWasKeyReleased()
{
keyWasReleased.clear();
keyWasReleased.reset();
}
JoystickController *joystick = nullptr;
@ -192,26 +87,41 @@ public:
PointerType getLastPointerType() { return last_pointer_type; }
private:
void listenForKey(KeyPress keyCode, GameKeyType action)
{
if (keyCode)
keysListenedFor[keyCode] = action;
}
bool setKeyDown(KeyPress keyCode, bool is_down);
void setKeyDown(GameKeyType action, bool is_down);
/* This is faster than using getKeySetting with the tradeoff that functions
* using it must make sure that it's initialised before using it and there is
* no error handling (for example bounds checking). This is useful here as the
* faster (up to 10x faster) key lookup is an asset.
*/
std::array<KeyPress, KeyType::INTERNAL_ENUM_COUNT> keybindings;
s32 mouse_wheel = 0;
// The current state of physical keys.
std::set<KeyPress> physicalKeyDown;
// The current state of keys
KeyList keyIsDown;
std::bitset<GameKeyType::INTERNAL_ENUM_COUNT> keyIsDown;
// Like keyIsDown but only reset when that key is read
KeyList keyWasDown;
std::bitset<GameKeyType::INTERNAL_ENUM_COUNT> keyWasDown;
// Whether a key has just been pressed
KeyList keyWasPressed;
std::bitset<GameKeyType::INTERNAL_ENUM_COUNT> keyWasPressed;
// Whether a key has just been released
KeyList keyWasReleased;
std::bitset<GameKeyType::INTERNAL_ENUM_COUNT> keyWasReleased;
// List of keys we listen for
// TODO perhaps the type of this is not really
// performant as KeyList is designed for few but
// often changing keys, and keysListenedFor is expected
// to change seldomly but contain lots of keys.
KeyList keysListenedFor;
std::unordered_map<KeyPress, GameKeyType> keysListenedFor;
// Intentionally not reset by clearInput/releaseAllKeys.
bool fullscreen_is_down = false;
@ -222,12 +132,6 @@ private:
class InputHandler
{
public:
InputHandler()
{
keycache.handler = this;
keycache.populate();
}
virtual ~InputHandler() = default;
virtual bool isRandom() const
@ -247,8 +151,7 @@ public:
virtual void clearWasKeyPressed() {}
virtual void clearWasKeyReleased() {}
virtual void listenForKey(const KeyPress &keyCode) {}
virtual void dontListenForKeys() {}
virtual void reloadKeybindings() {}
virtual v2s32 getMousePos() = 0;
virtual void setMousePos(s32 x, s32 y) = 0;
@ -261,7 +164,6 @@ public:
virtual void releaseAllKeys() {}
JoystickController joystick;
KeyCache keycache;
};
/*
@ -274,6 +176,7 @@ public:
RealInputHandler(MyEventReceiver *receiver) : m_receiver(receiver)
{
m_receiver->joystick = &joystick;
m_receiver->reloadKeybindings();
}
virtual ~RealInputHandler()
@ -283,19 +186,19 @@ public:
virtual bool isKeyDown(GameKeyType k)
{
return m_receiver->IsKeyDown(keycache.key[k]) || joystick.isKeyDown(k);
return m_receiver->IsKeyDown(k) || joystick.isKeyDown(k);
}
virtual bool wasKeyDown(GameKeyType k)
{
return m_receiver->WasKeyDown(keycache.key[k]) || joystick.wasKeyDown(k);
return m_receiver->WasKeyDown(k) || joystick.wasKeyDown(k);
}
virtual bool wasKeyPressed(GameKeyType k)
{
return m_receiver->WasKeyPressed(keycache.key[k]) || joystick.wasKeyPressed(k);
return m_receiver->WasKeyPressed(k) || joystick.wasKeyPressed(k);
}
virtual bool wasKeyReleased(GameKeyType k)
{
return m_receiver->WasKeyReleased(keycache.key[k]) || joystick.wasKeyReleased(k);
return m_receiver->WasKeyReleased(k) || joystick.wasKeyReleased(k);
}
virtual float getJoystickSpeed();
@ -316,13 +219,9 @@ public:
m_receiver->clearWasKeyReleased();
}
virtual void listenForKey(const KeyPress &keyCode)
virtual void reloadKeybindings()
{
m_receiver->listenForKey(keyCode);
}
virtual void dontListenForKeys()
{
m_receiver->dontListenForKeys();
m_receiver->reloadKeybindings();
}
virtual v2s32 getMousePos();
@ -360,7 +259,7 @@ public:
return true;
}
virtual bool isKeyDown(GameKeyType k) { return keydown[keycache.key[k]]; }
virtual bool isKeyDown(GameKeyType k) { return keydown[k]; }
virtual bool wasKeyDown(GameKeyType k) { return false; }
virtual bool wasKeyPressed(GameKeyType k) { return false; }
virtual bool wasKeyReleased(GameKeyType k) { return false; }
@ -377,7 +276,7 @@ public:
s32 Rand(s32 min, s32 max);
private:
KeyList keydown;
std::bitset<GameKeyType::INTERNAL_ENUM_COUNT> keydown;
v2s32 mousepos;
v2s32 mousespeed;
float joystickSpeed;

View file

@ -0,0 +1,102 @@
// Luanti
// SPDX-License-Identifier: LGPL-2.1-or-later
// Copyright (C) 2025 cx384
#include "item_visuals_manager.h"
#include "mesh.h"
#include "client.h"
#include "texturesource.h"
#include "itemdef.h"
#include "inventory.h"
ItemVisualsManager::ItemVisuals::~ItemVisuals() {
if (wield_mesh.mesh)
wield_mesh.mesh->drop();
}
ItemVisualsManager::ItemVisuals *ItemVisualsManager::createItemVisuals( const ItemStack &item,
Client *client) const
{
// This is not thread-safe
sanity_check(std::this_thread::get_id() == m_main_thread);
IItemDefManager *idef = client->idef();
const ItemDefinition &def = item.getDefinition(idef);
std::string inventory_image = item.getInventoryImage(idef);
std::string inventory_overlay = item.getInventoryOverlay(idef);
std::string cache_key = def.name;
if (!inventory_image.empty())
cache_key += "/" + inventory_image;
if (!inventory_overlay.empty())
cache_key += ":" + inventory_overlay;
// Skip if already in cache
auto it = m_cached_item_visuals.find(cache_key);
if (it != m_cached_item_visuals.end())
return it->second.get();
infostream << "Lazily creating item texture and mesh for \""
<< cache_key << "\"" << std::endl;
ITextureSource *tsrc = client->getTextureSource();
// Create new ItemVisuals
auto cc = std::make_unique<ItemVisuals>();
cc->inventory_texture = NULL;
if (!inventory_image.empty())
cc->inventory_texture = tsrc->getTexture(inventory_image);
getItemMesh(client, item, &(cc->wield_mesh));
cc->palette = tsrc->getPalette(def.palette_image);
// Put in cache
ItemVisuals *ptr = cc.get();
m_cached_item_visuals[cache_key] = std::move(cc);
return ptr;
}
video::ITexture* ItemVisualsManager::getInventoryTexture(const ItemStack &item,
Client *client) const
{
ItemVisuals *iv = createItemVisuals(item, client);
if (!iv)
return nullptr;
return iv->inventory_texture;
}
ItemMesh* ItemVisualsManager::getWieldMesh(const ItemStack &item, Client *client) const
{
ItemVisuals *iv = createItemVisuals(item, client);
if (!iv)
return nullptr;
return &(iv->wield_mesh);
}
Palette* ItemVisualsManager::getPalette(const ItemStack &item, Client *client) const
{
ItemVisuals *iv = createItemVisuals(item, client);
if (!iv)
return nullptr;
return iv->palette;
}
video::SColor ItemVisualsManager::getItemstackColor(const ItemStack &stack,
Client *client) const
{
// Look for direct color definition
const std::string &colorstring = stack.metadata.getString("color", 0);
video::SColor directcolor;
if (!colorstring.empty() && parseColorString(colorstring, directcolor, true))
return directcolor;
// See if there is a palette
Palette *palette = getPalette(stack, client);
const std::string &index = stack.metadata.getString("palette_index", 0);
if (palette && !index.empty())
return (*palette)[mystoi(index, 0, 255)];
// Fallback color
return client->idef()->get(stack.name).color;
}

View file

@ -0,0 +1,68 @@
// Luanti
// SPDX-License-Identifier: LGPL-2.1-or-later
// Copyright (C) 2025 cx384
#pragma once
#include <string>
#include <map>
#include <thread>
#include "wieldmesh.h" // ItemMesh
#include "util/basic_macros.h"
class Client;
struct ItemStack;
typedef std::vector<video::SColor> Palette; // copied from src/client/texturesource.h
namespace irr::video { class ITexture; }
// Caches data needed to draw an itemstack
struct ItemVisualsManager
{
ItemVisualsManager()
{
m_main_thread = std::this_thread::get_id();
}
void clear() {
m_cached_item_visuals.clear();
}
// Get item inventory texture
video::ITexture* getInventoryTexture(const ItemStack &item, Client *client) const;
// Get item wield mesh
// Once said to return nullptr if there is an inventory image, but this is wrong
ItemMesh* getWieldMesh(const ItemStack &item, Client *client) const;
// Get item palette
Palette* getPalette(const ItemStack &item, Client *client) const;
// Returns the base color of an item stack: the color of all
// tiles that do not define their own color.
video::SColor getItemstackColor(const ItemStack &stack, Client *client) const;
private:
struct ItemVisuals
{
video::ITexture *inventory_texture;
ItemMesh wield_mesh;
Palette *palette;
ItemVisuals():
inventory_texture(nullptr),
palette(nullptr)
{}
~ItemVisuals();
DISABLE_CLASS_COPY(ItemVisuals);
};
// The id of the thread that is allowed to use irrlicht directly
std::thread::id m_main_thread;
// Cached textures and meshes
mutable std::unordered_map<std::string, std::unique_ptr<ItemVisuals>> m_cached_item_visuals;
ItemVisuals* createItemVisuals(const ItemStack &item, Client *client) const;
};

View file

@ -3,7 +3,6 @@
// Copyright (C) 2016 est31, <MTest31@outlook.com>
#include "joystick_controller.h"
#include "irrlichttypes_extrabloated.h"
#include "keys.h"
#include "settings.h"
#include "gettime.h"

View file

@ -6,15 +6,18 @@
#include "settings.h"
#include "log.h"
#include "debug.h"
#include "renderingengine.h"
#include "util/hex.h"
#include "util/string.h"
#include "util/basic_macros.h"
#include <unordered_map>
#include <vector>
struct table_key {
const char *Name;
std::string Name; // An EKEY_CODE 'symbol' name as a string
irr::EKEY_CODE Key;
wchar_t Char; // L'\0' means no character assigned
const char *LangName; // NULL means it doesn't have a human description
std::string LangName; // empty string means it doesn't have a human description
};
#define DEFINEKEY1(x, lang) /* Irrlicht key without character */ \
@ -30,7 +33,7 @@ struct table_key {
#define N_(text) text
static const struct table_key table[] = {
static std::vector<table_key> table = {
// Keys that can be reliably mapped between Char and Key
DEFINEKEY3(0)
DEFINEKEY3(1)
@ -126,7 +129,7 @@ static const struct table_key table[] = {
DEFINEKEY1(KEY_ADD, N_("Numpad +"))
DEFINEKEY1(KEY_SEPARATOR, N_("Numpad ."))
DEFINEKEY1(KEY_SUBTRACT, N_("Numpad -"))
DEFINEKEY1(KEY_DECIMAL, NULL)
DEFINEKEY1(KEY_DECIMAL, N_("Numpad ."))
DEFINEKEY1(KEY_DIVIDE, N_("Numpad /"))
DEFINEKEY4(1)
DEFINEKEY4(2)
@ -221,122 +224,156 @@ static const struct table_key table[] = {
DEFINEKEY5("_")
};
static const table_key invalid_key = {"", irr::KEY_UNKNOWN, L'\0', ""};
#undef N_
static const table_key &lookup_keyname(const char *name)
{
for (const auto &table_key : table) {
if (strcmp(table_key.Name, name) == 0)
return table_key;
}
throw UnknownKeycode(name);
}
static const table_key &lookup_keykey(irr::EKEY_CODE key)
{
for (const auto &table_key : table) {
if (table_key.Key == key)
return table_key;
}
std::ostringstream os;
os << "<Keycode " << (int) key << ">";
throw UnknownKeycode(os.str().c_str());
}
static const table_key &lookup_keychar(wchar_t Char)
{
if (Char == L'\0')
return invalid_key;
for (const auto &table_key : table) {
if (table_key.Char == Char)
return table_key;
}
std::ostringstream os;
os << "<Char " << hex_encode((char*) &Char, sizeof(wchar_t)) << ">";
throw UnknownKeycode(os.str().c_str());
// Create a new entry in the lookup table if one is not available.
auto newsym = wide_to_utf8(std::wstring_view(&Char, 1));
table_key new_key {newsym, irr::KEY_KEY_CODES_COUNT, Char, newsym};
return table.emplace_back(std::move(new_key));
}
KeyPress::KeyPress(const char *name)
static const table_key &lookup_keykey(irr::EKEY_CODE key)
{
if (strlen(name) == 0) {
Key = irr::KEY_KEY_CODES_COUNT;
Char = L'\0';
m_name = "";
if (!Keycode::isValid(key))
return invalid_key;
for (const auto &table_key : table) {
if (table_key.Key == key)
return table_key;
}
return invalid_key;
}
static const table_key &lookup_keyname(std::string_view name)
{
if (name.empty())
return invalid_key;
for (const auto &table_key : table) {
if (table_key.Name == name)
return table_key;
}
auto wname = utf8_to_wide(name);
if (wname.empty())
return invalid_key;
return lookup_keychar(wname[0]);
}
static const table_key &lookup_scancode(const u32 scancode)
{
auto key = RenderingEngine::get_raw_device()->getKeyFromScancode(scancode);
return std::holds_alternative<EKEY_CODE>(key) ?
lookup_keykey(std::get<irr::EKEY_CODE>(key)) :
lookup_keychar(std::get<wchar_t>(key));
}
static const table_key &lookup_scancode(const std::variant<u32, irr::EKEY_CODE> &scancode)
{
return std::holds_alternative<irr::EKEY_CODE>(scancode) ?
lookup_keykey(std::get<irr::EKEY_CODE>(scancode)) :
lookup_scancode(std::get<u32>(scancode));
}
void KeyPress::loadFromKey(irr::EKEY_CODE keycode, wchar_t keychar)
{
scancode = RenderingEngine::get_raw_device()->getScancodeFromKey(Keycode(keycode, keychar));
}
KeyPress::KeyPress(const std::string &name)
{
if (loadFromScancode(name))
return;
}
if (strlen(name) <= 4) {
// Lookup by resulting character
int chars_read = mbtowc(&Char, name, 1);
FATAL_ERROR_IF(chars_read != 1, "Unexpected multibyte character");
try {
auto &k = lookup_keychar(Char);
m_name = k.Name;
Key = k.Key;
return;
} catch (UnknownKeycode &e) {};
} else {
// Lookup by name
m_name = name;
try {
auto &k = lookup_keyname(name);
Key = k.Key;
Char = k.Char;
return;
} catch (UnknownKeycode &e) {};
}
// It's not a known key, complain and try to do something
Key = irr::KEY_KEY_CODES_COUNT;
int chars_read = mbtowc(&Char, name, 1);
FATAL_ERROR_IF(chars_read != 1, "Unexpected multibyte character");
m_name = "";
warningstream << "KeyPress: Unknown key '" << name
<< "', falling back to first char." << std::endl;
const auto &key = lookup_keyname(name);
loadFromKey(key.Key, key.Char);
}
KeyPress::KeyPress(const irr::SEvent::SKeyInput &in, bool prefer_character)
KeyPress::KeyPress(const irr::SEvent::SKeyInput &in)
{
if (prefer_character)
Key = irr::KEY_KEY_CODES_COUNT;
else
Key = in.Key;
Char = in.Char;
try {
if (valid_kcode(Key))
m_name = lookup_keykey(Key).Name;
if (USE_SDL2) {
if (in.SystemKeyCode)
scancode.emplace<u32>(in.SystemKeyCode);
else
m_name = lookup_keychar(Char).Name;
} catch (UnknownKeycode &e) {
m_name.clear();
};
scancode.emplace<irr::EKEY_CODE>(in.Key);
} else {
loadFromKey(in.Key, in.Char);
}
}
const char *KeyPress::sym() const
std::string KeyPress::formatScancode() const
{
return m_name.c_str();
if (USE_SDL2) {
if (auto pv = std::get_if<u32>(&scancode))
return *pv == 0 ? "" : "SYSTEM_SCANCODE_" + std::to_string(*pv);
}
return "";
}
const char *KeyPress::name() const
std::string KeyPress::sym() const
{
if (m_name.empty())
return "";
const char *ret;
if (valid_kcode(Key))
ret = lookup_keykey(Key).LangName;
else
ret = lookup_keychar(Char).LangName;
return ret ? ret : "<Unnamed key>";
std::string name = lookup_scancode(scancode).Name;
if (USE_SDL2 || name.empty())
if (auto newname = formatScancode(); !newname.empty())
return newname;
return name;
}
const KeyPress EscapeKey("KEY_ESCAPE");
std::string KeyPress::name() const
{
const auto &name = lookup_scancode(scancode).LangName;
if (!name.empty())
return name;
return formatScancode();
}
const KeyPress LMBKey("KEY_LBUTTON");
const KeyPress MMBKey("KEY_MBUTTON");
const KeyPress RMBKey("KEY_RBUTTON");
irr::EKEY_CODE KeyPress::getKeycode() const
{
return lookup_scancode(scancode).Key;
}
wchar_t KeyPress::getKeychar() const
{
return lookup_scancode(scancode).Char;
}
bool KeyPress::loadFromScancode(const std::string &name)
{
if (USE_SDL2) {
if (!str_starts_with(name, "SYSTEM_SCANCODE_"))
return false;
char *p;
const auto code = strtoul(name.c_str()+16, &p, 10);
if (p != name.c_str() + name.size())
return false;
scancode.emplace<u32>(code);
return true;
} else {
return false;
}
}
std::unordered_map<std::string, KeyPress> specialKeyCache;
KeyPress KeyPress::getSpecialKey(const std::string &name)
{
auto &key = specialKeyCache[name];
if (!key)
key = KeyPress(name);
return key;
}
/*
Key config
@ -345,14 +382,18 @@ const KeyPress RMBKey("KEY_RBUTTON");
// A simple cache for quicker lookup
static std::unordered_map<std::string, KeyPress> g_key_setting_cache;
const KeyPress &getKeySetting(const char *settingname)
KeyPress getKeySetting(const std::string &settingname)
{
auto n = g_key_setting_cache.find(settingname);
if (n != g_key_setting_cache.end())
return n->second;
auto keysym = g_settings->get(settingname);
auto &ref = g_key_setting_cache[settingname];
ref = g_settings->get(settingname).c_str();
ref = KeyPress(keysym);
if (!keysym.empty() && !ref) {
warningstream << "Invalid key '" << keysym << "' for '" << settingname << "'." << std::endl;
}
return ref;
}
@ -360,8 +401,3 @@ void clearKeyCache()
{
g_key_setting_cache.clear();
}
irr::EKEY_CODE keyname_to_keycode(const char *name)
{
return lookup_keyname(name).Key;
}

View file

@ -4,62 +4,95 @@
#pragma once
#include "exceptions.h"
#include "irrlichttypes.h"
#include <Keycodes.h>
#include <IEventReceiver.h>
#include <string>
#include <variant>
class UnknownKeycode : public BaseException
{
public:
UnknownKeycode(const char *s) :
BaseException(s) {};
};
/* A key press, consisting of either an Irrlicht keycode
or an actual char */
/* A key press, consisting of a scancode or a keycode.
* This fits into 64 bits, so prefer passing this by value.
*/
class KeyPress
{
public:
KeyPress() = default;
KeyPress(const char *name);
KeyPress(const std::string &name);
KeyPress(const irr::SEvent::SKeyInput &in, bool prefer_character = false);
KeyPress(const irr::SEvent::SKeyInput &in);
bool operator==(const KeyPress &o) const
// Get a string representation that is suitable for use in minetest.conf
std::string sym() const;
// Get a human-readable string representation
std::string name() const;
// Get the corresponding keycode or KEY_UNKNOWN if one is not available
irr::EKEY_CODE getKeycode() const;
// Get the corresponding keychar or '\0' if one is not available
wchar_t getKeychar() const;
// Get the scancode or 0 is one is not available
u32 getScancode() const
{
return (Char > 0 && Char == o.Char) || (valid_kcode(Key) && Key == o.Key);
if (auto pv = std::get_if<u32>(&scancode))
return *pv;
return 0;
}
const char *sym() const;
const char *name() const;
protected:
static bool valid_kcode(irr::EKEY_CODE k)
{
return k > 0 && k < irr::KEY_KEY_CODES_COUNT;
bool operator==(KeyPress o) const {
return scancode == o.scancode;
}
bool operator!=(KeyPress o) const {
return !(*this == o);
}
irr::EKEY_CODE Key = irr::KEY_KEY_CODES_COUNT;
wchar_t Char = L'\0';
std::string m_name = "";
// Used for e.g. std::set
bool operator<(KeyPress o) const {
return scancode < o.scancode;
}
// Check whether the keypress is valid
operator bool() const
{
return std::holds_alternative<irr::EKEY_CODE>(scancode) ?
Keycode::isValid(std::get<irr::EKEY_CODE>(scancode)) :
std::get<u32>(scancode) != 0;
}
static KeyPress getSpecialKey(const std::string &name);
private:
using value_type = std::variant<u32, irr::EKEY_CODE>;
bool loadFromScancode(const std::string &name);
void loadFromKey(irr::EKEY_CODE keycode, wchar_t keychar);
std::string formatScancode() const;
value_type scancode = irr::KEY_UNKNOWN;
friend std::hash<KeyPress>;
};
template <>
struct std::hash<KeyPress>
{
size_t operator()(KeyPress kp) const noexcept {
return std::hash<KeyPress::value_type>{}(kp.scancode);
}
};
// Global defines for convenience
extern const KeyPress EscapeKey;
extern const KeyPress LMBKey;
extern const KeyPress MMBKey; // Middle Mouse Button
extern const KeyPress RMBKey;
// This implementation defers creation of the objects to make sure that the
// IrrlichtDevice is initialized.
#define EscapeKey KeyPress::getSpecialKey("KEY_ESCAPE")
#define LMBKey KeyPress::getSpecialKey("KEY_LBUTTON")
#define MMBKey KeyPress::getSpecialKey("KEY_MBUTTON") // Middle Mouse Button
#define RMBKey KeyPress::getSpecialKey("KEY_RBUTTON")
// Key configuration getter
const KeyPress &getKeySetting(const char *settingname);
KeyPress getKeySetting(const std::string &settingname);
// Clear fast lookup cache
void clearKeyCache();
irr::EKEY_CODE keyname_to_keycode(const char *name);

View file

@ -21,6 +21,9 @@
#include <algorithm>
#include <cmath>
#include "client/texturesource.h"
#include <SMesh.h>
#include <IMeshBuffer.h>
#include <SMeshBuffer.h>
/*
MeshMakeData
@ -344,7 +347,7 @@ void getNodeTileN(MapNode mn, const v3s16 &p, u8 tileindex, MeshMakeData *data,
tile = f.tiles[tileindex];
bool has_crack = p == data->m_crack_pos_relative;
for (TileLayer &layer : tile.layers) {
if (layer.texture_id == 0)
if (layer.empty())
continue;
if (!layer.has_color)
mn.getColor(f, &(layer.color));
@ -423,20 +426,6 @@ void getNodeTile(MapNode mn, const v3s16 &p, const v3s16 &dir, MeshMakeData *dat
tile.rotation = tile.world_aligned ? TileRotation::None : dir_to_tile[facedir][dir_i].rotation;
}
static void applyTileColor(PreMeshBuffer &pmb)
{
video::SColor tc = pmb.layer.color;
if (tc == video::SColor(0xFFFFFFFF))
return;
for (video::S3DVertex &vertex : pmb.vertices) {
video::SColor *c = &vertex.Color;
c->set(c->getAlpha(),
c->getRed() * tc.getRed() / 255,
c->getGreen() * tc.getGreen() / 255,
c->getBlue() * tc.getBlue() / 255);
}
}
/*
MapBlockBspTree
*/
@ -608,7 +597,7 @@ void PartialMeshBuffer::draw(video::IVideoDriver *driver) const
MapBlockMesh
*/
MapBlockMesh::MapBlockMesh(Client *client, MeshMakeData *data, v3s16 camera_offset):
MapBlockMesh::MapBlockMesh(Client *client, MeshMakeData *data):
m_tsrc(client->getTextureSource()),
m_shdrsrc(client->getShaderSource()),
m_bounding_sphere_center((data->m_side_length * 0.5f - 0.5f) * BS),
@ -665,7 +654,7 @@ MapBlockMesh::MapBlockMesh(Client *client, MeshMakeData *data, v3s16 camera_offs
{
PreMeshBuffer &p = collector.prebuffers[layer][i];
applyTileColor(p);
p.applyTileColor();
// Generate animation data
// - Cracks
@ -689,38 +678,23 @@ MapBlockMesh::MapBlockMesh(Client *client, MeshMakeData *data, v3s16 camera_offs
// - Texture animation
if (p.layer.material_flags & MATERIAL_FLAG_ANIMATION) {
// Add to MapBlockMesh in order to animate these tiles
auto &info = m_animation_info[{layer, i}];
info.tile = p.layer;
info.frame = 0;
info.frame_offset = 0;
m_animation_info.emplace(std::make_pair(layer, i), AnimationInfo(p.layer));
// Replace tile texture with the first animation frame
p.layer.texture = (*p.layer.frames)[0].texture;
}
// Create material
video::SMaterial material;
material.BackfaceCulling = true;
material.FogEnable = true;
material.setTexture(0, p.layer.texture);
material.forEachTexture([] (auto &tex) {
tex.MinFilter = video::ETMINF_NEAREST_MIPMAP_NEAREST;
tex.MagFilter = video::ETMAGF_NEAREST;
});
/*
* The second layer is for overlays, but uses the same vertex positions
* as the first, which quickly leads to z-fighting.
* To fix this we can offset the polygons in the direction of the camera.
* This only affects the depth buffer and leads to no visual gaps in geometry.
*/
if (layer == 1) {
material.PolygonOffsetSlopeScale = -1;
material.PolygonOffsetDepthBias = -1;
}
{
material.MaterialType = m_shdrsrc->getShaderInfo(
p.layer.shader_id).material;
p.layer.applyMaterialOptionsWithShaders(material);
p.layer.applyMaterialOptions(material, layer);
}
scene::SMeshBuffer *buf = new scene::SMeshBuffer();
@ -787,6 +761,12 @@ bool MapBlockMesh::animate(bool faraway, float time, int crack,
// Cracks
if (crack != m_last_crack) {
for (auto &crack_material : m_crack_materials) {
// TODO crack on animated tiles does not work
auto anim_it = m_animation_info.find(crack_material.first);
if (anim_it != m_animation_info.end())
continue;
scene::IMeshBuffer *buf = m_mesh[crack_material.first.first]->
getMeshBuffer(crack_material.first.second);
@ -796,16 +776,6 @@ bool MapBlockMesh::animate(bool faraway, float time, int crack,
video::ITexture *new_texture =
m_tsrc->getTextureForMesh(s, &new_texture_id);
buf->getMaterial().setTexture(0, new_texture);
// If the current material is also animated, update animation info
auto anim_it = m_animation_info.find(crack_material.first);
if (anim_it != m_animation_info.end()) {
TileLayer &tile = anim_it->second.tile;
tile.texture = new_texture;
tile.texture_id = new_texture_id;
// force animation update
anim_it->second.frame = -1;
}
}
m_last_crack = crack;
@ -813,20 +783,9 @@ bool MapBlockMesh::animate(bool faraway, float time, int crack,
// Texture animation
for (auto &it : m_animation_info) {
const TileLayer &tile = it.second.tile;
// Figure out current frame
int frameno = (int)(time * 1000 / tile.animation_frame_length_ms
+ it.second.frame_offset) % tile.animation_frame_count;
// If frame doesn't change, skip
if (frameno == it.second.frame)
continue;
it.second.frame = frameno;
scene::IMeshBuffer *buf = m_mesh[it.first.first]->getMeshBuffer(it.first.second);
const FrameSpec &frame = (*tile.frames)[frameno];
buf->getMaterial().setTexture(0, frame.texture);
video::SMaterial &material = buf->getMaterial();
it.second.updateTexture(material, time);
}
return true;

View file

@ -170,19 +170,17 @@ private:
/*
Holds a mesh for a mapblock.
Besides the SMesh*, this contains information used for animating
the vertex positions, colors and texture coordinates of the mesh.
Besides the SMesh*, this contains information used fortransparency sorting
and texture animation.
For example:
- cracks [implemented]
- day/night transitions [implemented]
- animated flowing liquids [not implemented]
- animating vertex positions for e.g. axles [not implemented]
- cracks
- day/night transitions
*/
class MapBlockMesh
{
public:
// Builds the mesh given
MapBlockMesh(Client *client, MeshMakeData *data, v3s16 camera_offset);
MapBlockMesh(Client *client, MeshMakeData *data);
~MapBlockMesh();
// Main animation function, parameters:
@ -193,13 +191,17 @@ public:
// Returns true if anything has been changed.
bool animate(bool faraway, float time, int crack, u32 daynight_ratio);
/// @warning ClientMap requires that the vertex and index data is not modified
scene::IMesh *getMesh()
{
return m_mesh[0].get();
}
/// @param layer layer index
/// @warning ClientMap requires that the vertex and index data is not modified
scene::IMesh *getMesh(u8 layer)
{
assert(layer < MAX_TILE_LAYERS);
return m_mesh[layer].get();
}
@ -244,11 +246,6 @@ public:
}
private:
struct AnimationInfo {
int frame; // last animation frame
int frame_offset;
TileLayer tile;
};
irr_ptr<scene::IMesh> m_mesh[MAX_TILE_LAYERS];
std::vector<MinimapMapblock*> m_minimap_mapblocks;

View file

@ -145,8 +145,7 @@ QueuedMeshUpdate *MeshUpdateQueue::pop()
MutexAutoLock lock(m_mutex);
bool must_be_urgent = !m_urgents.empty();
for (std::vector<QueuedMeshUpdate*>::iterator i = m_queue.begin();
i != m_queue.end(); ++i) {
for (auto i = m_queue.begin(); i != m_queue.end(); ++i) {
QueuedMeshUpdate *q = *i;
if (must_be_urgent && m_urgents.count(q->p) == 0)
continue;
@ -202,8 +201,8 @@ void MeshUpdateQueue::fillDataFromMapBlocks(QueuedMeshUpdate *q)
MeshUpdateWorkerThread
*/
MeshUpdateWorkerThread::MeshUpdateWorkerThread(Client *client, MeshUpdateQueue *queue_in, MeshUpdateManager *manager, v3s16 *camera_offset) :
UpdateThread("Mesh"), m_client(client), m_queue_in(queue_in), m_manager(manager), m_camera_offset(camera_offset)
MeshUpdateWorkerThread::MeshUpdateWorkerThread(Client *client, MeshUpdateQueue *queue_in, MeshUpdateManager *manager) :
UpdateThread("Mesh"), m_client(client), m_queue_in(queue_in), m_manager(manager)
{
m_generation_interval = g_settings->getU16("mesh_generation_interval");
m_generation_interval = rangelim(m_generation_interval, 0, 50);
@ -220,7 +219,7 @@ void MeshUpdateWorkerThread::doUpdate()
ScopeProfiler sp(g_profiler, "Client: Mesh making (sum)");
MapBlockMesh *mesh_new = new MapBlockMesh(m_client, q->data, *m_camera_offset);
MapBlockMesh *mesh_new = new MapBlockMesh(m_client, q->data);
MeshUpdateResult r;
r.p = q->p;
@ -254,7 +253,7 @@ MeshUpdateManager::MeshUpdateManager(Client *client):
infostream << "MeshUpdateManager: using " << number_of_threads << " threads" << std::endl;
for (int i = 0; i < number_of_threads; i++)
m_workers.push_back(std::make_unique<MeshUpdateWorkerThread>(client, &m_queue_in, this, &m_camera_offset));
m_workers.push_back(std::make_unique<MeshUpdateWorkerThread>(client, &m_queue_in, this));
}
void MeshUpdateManager::updateBlock(Map *map, v3s16 p, bool ack_block_to_server,
@ -264,8 +263,8 @@ void MeshUpdateManager::updateBlock(Map *map, v3s16 p, bool ack_block_to_server,
g_settings->getBool("smooth_lighting")
&& !g_settings->getFlag("performance_tradeoffs");
if (!m_queue_in.addBlock(map, p, ack_block_to_server, urgent)) {
warningstream << "Update requested for non-existent block at ("
<< p.X << ", " << p.Y << ", " << p.Z << ")" << std::endl;
warningstream << "Update requested for non-existent block at "
<< p << std::endl;
return;
}
if (update_neighbors) {

View file

@ -93,7 +93,7 @@ class MeshUpdateManager;
class MeshUpdateWorkerThread : public UpdateThread
{
public:
MeshUpdateWorkerThread(Client *client, MeshUpdateQueue *queue_in, MeshUpdateManager *manager, v3s16 *camera_offset);
MeshUpdateWorkerThread(Client *client, MeshUpdateQueue *queue_in, MeshUpdateManager *manager);
protected:
virtual void doUpdate();
@ -102,7 +102,6 @@ private:
Client *m_client;
MeshUpdateQueue *m_queue_in;
MeshUpdateManager *m_manager;
v3s16 *m_camera_offset;
// TODO: Add callback to update these when g_settings changes
int m_generation_interval;
@ -121,8 +120,6 @@ public:
bool getNextResult(MeshUpdateResult &r);
v3s16 m_camera_offset;
void start();
void stop();
void wait();

View file

@ -12,7 +12,7 @@ void MeshCollector::append(const TileSpec &tile, const video::S3DVertex *vertice
{
for (int layernum = 0; layernum < MAX_TILE_LAYERS; layernum++) {
const TileLayer *layer = &tile.layers[layernum];
if (layer->texture_id == 0)
if (layer->empty())
continue;
append(*layer, vertices, numVertices, indices, numIndices, layernum,
tile.world_aligned);

View file

@ -18,6 +18,21 @@ struct PreMeshBuffer
PreMeshBuffer() = default;
explicit PreMeshBuffer(const TileLayer &layer) : layer(layer) {}
/// @brief Colorizes vertices as indicated by tile layer
void applyTileColor()
{
video::SColor tc = layer.color;
if (tc == video::SColor(0xFFFFFFFF))
return;
for (auto &vertex : vertices) {
video::SColor *c = &vertex.Color;
c->set(c->getAlpha(),
c->getRed() * tc.getRed() / 255U,
c->getGreen() * tc.getGreen() / 255U,
c->getBlue() * tc.getBlue() / 255U);
}
}
};
struct MeshCollector

View file

@ -78,15 +78,13 @@ void MinimapUpdateThread::doUpdate()
while (popBlockUpdate(&update)) {
if (update.data) {
// Swap two values in the map using single lookup
std::pair<std::map<v3s16, MinimapMapblock*>::iterator, bool>
result = m_blocks_cache.insert(std::make_pair(update.pos, update.data));
auto result = m_blocks_cache.insert(std::make_pair(update.pos, update.data));
if (!result.second) {
delete result.first->second;
result.first->second = update.data;
}
} else {
std::map<v3s16, MinimapMapblock *>::iterator it;
it = m_blocks_cache.find(update.pos);
auto it = m_blocks_cache.find(update.pos);
if (it != m_blocks_cache.end()) {
delete it->second;
m_blocks_cache.erase(it);
@ -124,8 +122,7 @@ void MinimapUpdateThread::getMap(v3s16 pos, s16 size, s16 height)
for (blockpos.Z = blockpos_min.Z; blockpos.Z <= blockpos_max.Z; ++blockpos.Z)
for (blockpos.Y = blockpos_min.Y; blockpos.Y <= blockpos_max.Y; ++blockpos.Y)
for (blockpos.X = blockpos_min.X; blockpos.X <= blockpos_max.X; ++blockpos.X) {
std::map<v3s16, MinimapMapblock *>::const_iterator pblock =
m_blocks_cache.find(blockpos);
auto pblock = m_blocks_cache.find(blockpos);
if (pblock == m_blocks_cache.end())
continue;
const MinimapMapblock &block = *pblock->second;
@ -647,8 +644,7 @@ void Minimap::drawMinimap(core::rect<s32> rect)
f32 sin_angle = std::sin(m_angle * core::DEGTORAD);
f32 cos_angle = std::cos(m_angle * core::DEGTORAD);
s32 marker_size2 = 0.025 * (float)rect.getWidth();;
for (std::list<v2f>::const_iterator
i = m_active_markers.begin();
for (auto i = m_active_markers.begin();
i != m_active_markers.end(); ++i) {
v2f posf = *i;
if (data->minimap_shape_round) {

View file

@ -193,7 +193,7 @@ void Particle::updateVertices(ClientEnvironment *env, video::SColor color)
video::S3DVertex *vertices = m_buffer->getVertices(m_index);
if (m_texture.tex != nullptr)
scale = m_texture.tex -> scale.blend(m_time / (m_expiration+0.1));
scale = m_texture.tex -> scale.blend(m_time / (m_expiration+0.1f));
else
scale = v2f(1.f, 1.f);
@ -203,7 +203,7 @@ void Particle::updateVertices(ClientEnvironment *env, video::SColor color)
v2u32 framesize;
texcoord = m_p.animation.getTextureCoords(texsize, m_animation_frame);
m_p.animation.determineParams(texsize, NULL, NULL, &framesize);
framesize_f = v2f(framesize.X / (float) texsize.X, framesize.Y / (float) texsize.Y);
framesize_f = v2f::from(framesize) / v2f::from(texsize);
tx0 = m_texpos.X + texcoord.X;
tx1 = m_texpos.X + texcoord.X + framesize_f.X * m_texsize.X;
@ -392,7 +392,7 @@ void ParticleSpawner::spawnParticle(ClientEnvironment *env, float radius,
}
case ParticleParamTypes::AttractorKind::line: {
// https://github.com/minetest/minetest/issues/11505#issuecomment-915612700
// <https://github.com/luanti-org/luanti/issues/11505#issuecomment-915612700>
const auto& lorigin = attractor_origin;
v3f ldir = attractor_direction;
ldir.normalize();
@ -408,7 +408,7 @@ void ParticleSpawner::spawnParticle(ClientEnvironment *env, float radius,
}
case ParticleParamTypes::AttractorKind::plane: {
// https://github.com/minetest/minetest/issues/11505#issuecomment-915612700
// <https://github.com/luanti-org/luanti/issues/11505#issuecomment-915612700>
const v3f& porigin = attractor_origin;
v3f normal = attractor_direction;
normal.normalize();

View file

@ -11,6 +11,7 @@
#include "SMeshBuffer.h"
#include <mutex>
#include <memory>
#include <vector>
#include <unordered_map>
#include "../particles.h"

View file

@ -6,7 +6,7 @@
#include "anaglyph.h"
#include "client/camera.h"
#include <IrrlichtDevice.h>
#include <ISceneManager.h>
/// SetColorMaskStep step

View file

@ -4,7 +4,9 @@
// Copyright (C) 2017 numzero, Lobachevskiy Vitaliy <numzer0@yandex.ru>
#pragma once
#include "irrlichttypes_extrabloated.h"
#include "irr_v2d.h"
#include <SColor.h>
namespace irr
{

View file

@ -3,7 +3,7 @@
// Copyright (C) 2022 x2048, Dmitry Kostenko <codeforsmile@gmail.com>
#pragma once
#include "irrlichttypes_extrabloated.h"
#include "irrlichttypes_bloated.h"
#include <IrrlichtDevice.h> // used in all render/*.cpp
#include <IVideoDriver.h> // used in all render/*.cpp

View file

@ -11,6 +11,7 @@
#include "client/hud.h"
#include "client/minimap.h"
#include "client/shadows/dynamicshadowsrender.h"
#include <IGUIEnvironment.h>
/// Draw3D pipeline step
void Draw3D::run(PipelineContext &context)

View file

@ -10,6 +10,7 @@
#include "client/tile.h"
#include "settings.h"
#include "mt_opengl.h"
#include <ISceneManager.h>
PostProcessingStep::PostProcessingStep(u32 _shader_id, const std::vector<u8> &_texture_map) :
shader_id(_shader_id), texture_map(_texture_map)

View file

@ -7,6 +7,7 @@
#include "client/client.h"
#include "client/hud.h"
#include "client/camera.h"
#include <ISceneManager.h>
DrawImageStep::DrawImageStep(u8 texture_index, v2f _offset) :
texture_index(texture_index), offset(_offset)
@ -82,4 +83,4 @@ void populateSideBySidePipeline(RenderPipeline *pipeline, Client *client, bool h
step->setRenderSource(buffer);
step->setRenderTarget(screen);
}
}
}

View file

@ -34,9 +34,9 @@ void FpsControl::reset()
last_time = porting::getTimeUs();
}
void FpsControl::limit(IrrlichtDevice *device, f32 *dtime, bool assume_paused)
void FpsControl::limit(IrrlichtDevice *device, f32 *dtime)
{
const float fps_limit = (device->isWindowFocused() && !assume_paused)
const float fps_limit = device->isWindowFocused()
? g_settings->getFloat("fps_max")
: g_settings->getFloat("fps_max_unfocused");
const u64 frametime_min = 1000000.0f / std::max(fps_limit, 1.0f);
@ -375,8 +375,8 @@ std::vector<video::E_DRIVER_TYPE> RenderingEngine::getSupportedVideoDrivers()
// Only check these drivers. We do not support software and D3D in any capacity.
// ordered by preference (best first)
static const video::E_DRIVER_TYPE glDrivers[] = {
video::EDT_OPENGL3,
video::EDT_OPENGL,
video::EDT_OPENGL3,
video::EDT_OGLES2,
video::EDT_NULL,
};

View file

@ -9,7 +9,6 @@
#include <memory>
#include <string>
#include "client/inputhandler.h"
#include "irrlichttypes_extrabloated.h"
#include "debug.h"
#include "config.h"
#include "client/shader.h"
@ -46,7 +45,7 @@ struct FpsControl {
void reset();
void limit(IrrlichtDevice *device, f32 *dtime, bool assume_paused = false);
void limit(IrrlichtDevice *device, f32 *dtime);
u32 getBusyMs() const { return busy_time / 1000; }

View file

@ -6,7 +6,6 @@
#include <fstream>
#include <iterator>
#include "shader.h"
#include "irrlichttypes_extrabloated.h"
#include "irr_ptr.h"
#include "debug.h"
#include "filesys.h"
@ -452,9 +451,6 @@ u32 ShaderSource::getShaderIdDirect(const std::string &name,
u32 id = m_shaderinfo_cache.size();
m_shaderinfo_cache.push_back(info);
infostream<<"getShaderIdDirect(): "
<<"Returning id="<<id<<" for name \""<<name<<"\""<<std::endl;
return id;
}
@ -803,7 +799,7 @@ ShaderInfo ShaderSource::generateShader(const std::string &name,
dumpShaderProgram(warningstream, "Fragment", fragment_shader);
dumpShaderProgram(warningstream, "Geometry", geometry_shader);
throw ShaderException(
fmtgettext("Failed to compile the \"%s\" shader.", name.c_str()) +
fmtgettext("Failed to compile the \"%s\" shader.", log_name.c_str()) +
strgettext("\nCheck debug.txt for details."));
}

View file

@ -32,11 +32,12 @@ void DirectionalLight::createSplitMatrices(const Camera *cam)
// adjusted frustum boundaries
float sfNear = future_frustum.zNear;
float sfFar = adjustDist(future_frustum.zFar, cam->getFovY());
assert(sfFar - sfNear > 0);
// adjusted camera positions
v3f cam_pos_world = cam->getPosition();
// if world position is less than 1 block away from the captured
// if world position is less than 1 node away from the captured
// world position then stick to the captured value, otherwise recapture.
if (cam_pos_world.getDistanceFromSQ(last_cam_pos_world) < BS * BS)
cam_pos_world = last_cam_pos_world;
@ -74,7 +75,6 @@ void DirectionalLight::createSplitMatrices(const Camera *cam)
future_frustum.ViewMat.buildCameraLookAtMatrixLH(eye, center_scene, v3f(0.0f, 1.0f, 0.0f));
future_frustum.ProjOrthMat.buildProjectionMatrixOrthoLH(radius, radius,
0.0f, length, false);
future_frustum.camera_offset = cam->getOffset();
}
DirectionalLight::DirectionalLight(const u32 shadowMapResolution,
@ -84,9 +84,18 @@ DirectionalLight::DirectionalLight(const u32 shadowMapResolution,
farPlane(farValue), mapRes(shadowMapResolution), pos(position)
{}
void DirectionalLight::update_frustum(const Camera *cam, Client *client, bool force)
void DirectionalLight::updateCameraOffset(const Camera *cam)
{
if (dirty && !force)
if (future_frustum.zFar == 0.0f) // not initialized
return;
createSplitMatrices(cam);
should_update_map_shadow = true;
dirty = true;
}
void DirectionalLight::updateFrustum(const Camera *cam, Client *client)
{
if (dirty)
return;
float zNear = cam->getCameraNode()->getNearValue();
@ -106,16 +115,6 @@ void DirectionalLight::update_frustum(const Camera *cam, Client *client, bool fo
getPosition(), getDirection(), future_frustum.radius, future_frustum.length);
should_update_map_shadow = true;
dirty = true;
// when camera offset changes, adjust the current frustum view matrix to avoid flicker
v3s16 cam_offset = cam->getOffset();
if (cam_offset != shadow_frustum.camera_offset) {
v3f rotated_offset = shadow_frustum.ViewMat.rotateAndScaleVect(
intToFloat(cam_offset - shadow_frustum.camera_offset, BS));
shadow_frustum.ViewMat.setTranslation(shadow_frustum.ViewMat.getTranslation() + rotated_offset);
shadow_frustum.player += intToFloat(shadow_frustum.camera_offset - cam->getOffset(), BS);
shadow_frustum.camera_offset = cam_offset;
}
}
void DirectionalLight::commitFrustum()

View file

@ -22,7 +22,6 @@ struct shadowFrustum
core::matrix4 ViewMat;
v3f position;
v3f player;
v3s16 camera_offset;
};
class DirectionalLight
@ -34,9 +33,9 @@ public:
f32 farValue = 100.0f);
~DirectionalLight() = default;
//DISABLE_CLASS_COPY(DirectionalLight)
void updateCameraOffset(const Camera *cam);
void update_frustum(const Camera *cam, Client *client, bool force = false);
void updateFrustum(const Camera *cam, Client *client);
// when set direction is updated to negative normalized(direction)
void setDirection(v3f dir);
@ -85,6 +84,7 @@ public:
return mapRes;
}
/// If true, shadow map needs to be invalidated due to frustum change
bool should_update_map_shadow{true};
void commitFrustum();

View file

@ -177,14 +177,15 @@ void ShadowRenderer::removeNodeFromShadowList(scene::ISceneNode *node)
node->forEachMaterial([] (auto &mat) {
mat.setTexture(TEXTURE_LAYER_SHADOW, nullptr);
});
for (auto it = m_shadow_node_array.begin(); it != m_shadow_node_array.end();) {
if (it->node == node) {
it = m_shadow_node_array.erase(it);
break;
} else {
++it;
}
auto it = std::find(m_shadow_node_array.begin(), m_shadow_node_array.end(), node);
if (it == m_shadow_node_array.end()) {
infostream << "removeNodeFromShadowList: " << node << " not found" << std::endl;
return;
}
// swap with last, then remove
*it = m_shadow_node_array.back();
m_shadow_node_array.pop_back();
}
void ShadowRenderer::updateSMTextures()
@ -254,15 +255,15 @@ void ShadowRenderer::updateSMTextures()
if (!m_shadow_node_array.empty()) {
bool reset_sm_texture = false;
// detect if SM should be regenerated
// clear texture if requested
for (DirectionalLight &light : m_light_list) {
if (light.should_update_map_shadow || m_force_update_shadow_map) {
light.should_update_map_shadow = false;
m_current_frame = 0;
reset_sm_texture = true;
}
reset_sm_texture |= light.should_update_map_shadow;
light.should_update_map_shadow = false;
}
if (reset_sm_texture || m_force_update_shadow_map)
m_current_frame = 0;
video::ITexture* shadowMapTargetTexture = shadowMapClientMapFuture;
if (shadowMapTargetTexture == nullptr)
shadowMapTargetTexture = shadowMapClientMap;
@ -270,7 +271,7 @@ void ShadowRenderer::updateSMTextures()
// Update SM incrementally:
for (DirectionalLight &light : m_light_list) {
// Static shader values.
for (auto cb : {m_shadow_depth_cb, m_shadow_depth_entity_cb, m_shadow_depth_trans_cb})
for (auto cb : {m_shadow_depth_cb, m_shadow_depth_entity_cb, m_shadow_depth_trans_cb}) {
if (cb) {
cb->MapRes = (f32)m_shadow_map_texture_size;
cb->MaxFar = (f32)m_shadow_map_max_distance * BS;
@ -278,12 +279,9 @@ void ShadowRenderer::updateSMTextures()
cb->PerspectiveBiasZ = getPerspectiveBiasZ();
cb->CameraPos = light.getFuturePlayerPos();
}
}
// set the Render Target
// right now we can only render in usual RTT, not
// Depth texture is available in irrlicth maybe we
// should put some gl* fn here
// Note that force_update means we're drawing everything one go.
if (m_current_frame < m_map_shadow_update_frames || m_force_update_shadow_map) {
m_driver->setRenderTarget(shadowMapTargetTexture, reset_sm_texture, true,
@ -688,21 +686,19 @@ std::string ShadowRenderer::readShaderFile(const std::string &path)
ShadowRenderer *createShadowRenderer(IrrlichtDevice *device, Client *client)
{
if (!g_settings->getBool("enable_dynamic_shadows"))
return nullptr;
// disable if unsupported
if (g_settings->getBool("enable_dynamic_shadows")) {
// See also checks in builtin/mainmenu/settings/dlg_settings.lua
const video::E_DRIVER_TYPE type = device->getVideoDriver()->getDriverType();
if (type != video::EDT_OPENGL && type != video::EDT_OPENGL3) {
warningstream << "Shadows: disabled dynamic shadows due to being unsupported" << std::endl;
g_settings->setBool("enable_dynamic_shadows", false);
}
// See also checks in builtin/mainmenu/settings/dlg_settings.lua
const video::E_DRIVER_TYPE type = device->getVideoDriver()->getDriverType();
if (type != video::EDT_OPENGL && type != video::EDT_OPENGL3) {
warningstream << "Shadows: disabled dynamic shadows due to being unsupported" << std::endl;
g_settings->setBool("enable_dynamic_shadows", false);
return nullptr;
}
if (g_settings->getBool("enable_dynamic_shadows")) {
ShadowRenderer *shadow_renderer = new ShadowRenderer(device, client);
shadow_renderer->initialize();
return shadow_renderer;
}
return nullptr;
ShadowRenderer *shadow_renderer = new ShadowRenderer(device, client);
shadow_renderer->initialize();
return shadow_renderer;
}

View file

@ -7,8 +7,9 @@
#include <string>
#include <vector>
#include <IrrlichtDevice.h>
#include "irrlichttypes_extrabloated.h"
#include "client/shadows/dynamicshadows.h"
#include <ISceneNode.h>
#include <ISceneManager.h>
class ShadowDepthShaderCB;
class shadowScreenQuad;
@ -27,7 +28,8 @@ struct NodeToApply
E_SHADOW_MODE m = E_SHADOW_MODE::ESM_BOTH) :
node(n),
shadowMode(m){};
bool operator<(const NodeToApply &other) const { return node < other.node; };
bool operator==(scene::ISceneNode *n) const { return node == n; }
scene::ISceneNode *node;
@ -67,6 +69,7 @@ public:
void removeNodeFromShadowList(scene::ISceneNode *node);
void update(video::ITexture *outputTarget = nullptr);
/// Force shadow map to be re-drawn in one go next frame
void setForceUpdateShadowMap() { m_force_update_shadow_map = true; }
void drawDebug();

View file

@ -3,7 +3,6 @@
// Copyright (C) 2021 Liso <anlismon@gmail.com>
#pragma once
#include "irrlichttypes_extrabloated.h"
#include <IMaterialRendererServices.h>
#include <IShaderConstantSetCallBack.h>
#include "client/shader.h"

View file

@ -3,7 +3,6 @@
// Copyright (C) 2021 Liso <anlismon@gmail.com>
#pragma once
#include "irrlichttypes_extrabloated.h"
#include <IMaterialRendererServices.h>
#include <IShaderConstantSetCallBack.h>
#include "client/shader.h"

View file

@ -3,42 +3,24 @@
// Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
#include "tile.h"
#include <cassert>
// Sets everything else except the texture in the material
void TileLayer::applyMaterialOptions(video::SMaterial &material) const
void AnimationInfo::updateTexture(video::SMaterial &material, float animation_time)
{
switch (material_type) {
case TILE_MATERIAL_OPAQUE:
case TILE_MATERIAL_LIQUID_OPAQUE:
case TILE_MATERIAL_WAVING_LIQUID_OPAQUE:
material.MaterialType = video::EMT_SOLID;
break;
case TILE_MATERIAL_BASIC:
case TILE_MATERIAL_WAVING_LEAVES:
case TILE_MATERIAL_WAVING_PLANTS:
case TILE_MATERIAL_WAVING_LIQUID_BASIC:
material.MaterialTypeParam = 0.5;
material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
break;
case TILE_MATERIAL_ALPHA:
case TILE_MATERIAL_LIQUID_TRANSPARENT:
case TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT:
material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
break;
default:
break;
// Figure out current frame
u16 frame = (u16)(animation_time * 1000 / m_frame_length_ms) % m_frame_count;
// Only adjust if frame changed
if (frame != m_frame) {
m_frame = frame;
assert(m_frame < m_frames->size());
material.setTexture(0, (*m_frames)[m_frame].texture);
}
material.BackfaceCulling = (material_flags & MATERIAL_FLAG_BACKFACE_CULLING) != 0;
if (!(material_flags & MATERIAL_FLAG_TILEABLE_HORIZONTAL)) {
material.TextureLayers[0].TextureWrapU = video::ETC_CLAMP_TO_EDGE;
}
if (!(material_flags & MATERIAL_FLAG_TILEABLE_VERTICAL)) {
material.TextureLayers[0].TextureWrapV = video::ETC_CLAMP_TO_EDGE;
}
}
};
void TileLayer::applyMaterialOptionsWithShaders(video::SMaterial &material) const
void TileLayer::applyMaterialOptions(video::SMaterial &material, int layer) const
{
material.setTexture(0, texture);
material.BackfaceCulling = (material_flags & MATERIAL_FLAG_BACKFACE_CULLING) != 0;
if (!(material_flags & MATERIAL_FLAG_TILEABLE_HORIZONTAL)) {
material.TextureLayers[0].TextureWrapU = video::ETC_CLAMP_TO_EDGE;
@ -48,4 +30,20 @@ void TileLayer::applyMaterialOptionsWithShaders(video::SMaterial &material) cons
material.TextureLayers[0].TextureWrapV = video::ETC_CLAMP_TO_EDGE;
material.TextureLayers[1].TextureWrapV = video::ETC_CLAMP_TO_EDGE;
}
/*
* The second layer is for overlays, but uses the same vertex positions
* as the first, which easily leads to Z-fighting.
* To fix this we offset the polygons of the *first layer* away from the camera.
* This only affects the depth buffer and leads to no visual gaps in geometry.
*
* However, doing so intrudes the "Z space" of the overlay of the next node
* so that leads to inconsistent Z-sorting again. :(
* HACK: For lack of a better approach we restrict this to cases where
* an overlay is actually present.
*/
if (need_polygon_offset) {
material.PolygonOffsetSlopeScale = 1;
material.PolygonOffsetDepthBias = 1;
}
}

View file

@ -9,7 +9,7 @@
#include <vector>
#include <SMaterial.h>
enum MaterialType{
enum MaterialType : u8 {
TILE_MATERIAL_BASIC,
TILE_MATERIAL_ALPHA,
TILE_MATERIAL_LIQUID_TRANSPARENT,
@ -73,7 +73,8 @@ struct TileLayer
material_flags == other.material_flags &&
has_color == other.has_color &&
color == other.color &&
scale == other.scale;
scale == other.scale &&
need_polygon_offset == other.need_polygon_offset;
}
/*!
@ -84,9 +85,19 @@ struct TileLayer
return !(*this == other);
}
void applyMaterialOptions(video::SMaterial &material) const;
/**
* Set some material parameters accordingly.
* @note does not set `MaterialType`
* @param material material to mody
* @param layer index of this layer in the `TileSpec`
*/
void applyMaterialOptions(video::SMaterial &material, int layer) const;
void applyMaterialOptionsWithShaders(video::SMaterial &material) const;
/// @return is this layer uninitalized?
bool empty() const
{
return !shader_id && !texture_id;
}
/// @return is this layer semi-transparent?
bool isTransparent() const
@ -98,8 +109,9 @@ struct TileLayer
case TILE_MATERIAL_LIQUID_TRANSPARENT:
case TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT:
return true;
default:
return false;
}
return false;
}
// Ordered for size, please do not reorder
@ -113,16 +125,20 @@ struct TileLayer
u16 animation_frame_length_ms = 0;
u16 animation_frame_count = 1;
u8 material_type = TILE_MATERIAL_BASIC;
MaterialType material_type = TILE_MATERIAL_BASIC;
u8 material_flags =
//0 // <- DEBUG, Use the one below
MATERIAL_FLAG_BACKFACE_CULLING |
MATERIAL_FLAG_TILEABLE_HORIZONTAL|
MATERIAL_FLAG_TILEABLE_VERTICAL;
//! If true, the tile has its own color.
bool has_color = false;
u8 scale = 1;
/// does this tile need to have a positive polygon offset set?
/// @see TileLayer::applyMaterialOptions
bool need_polygon_offset = false;
/// @note not owned by this struct
std::vector<FrameSpec> *frames = nullptr;
/*!
@ -131,7 +147,30 @@ struct TileLayer
*/
video::SColor color = video::SColor(0, 0, 0, 0);
u8 scale = 1;
//! If true, the tile has its own color.
bool has_color = false;
};
// Stores information for drawing an animated tile
struct AnimationInfo {
AnimationInfo() = default;
AnimationInfo(const TileLayer &tile) :
m_frame_length_ms(tile.animation_frame_length_ms),
m_frame_count(tile.animation_frame_count),
m_frames(tile.frames)
{};
void updateTexture(video::SMaterial &material, float animation_time);
private:
u16 m_frame = 0; // last animation frame
u16 m_frame_length_ms = 0;
u16 m_frame_count = 1;
/// @note not owned by this struct
std::vector<FrameSpec> *m_frames = nullptr;
};
enum class TileRotation: u8 {

View file

@ -20,6 +20,10 @@
#include <map>
#include <IMeshManipulator.h>
#include "client/renderingengine.h"
#include <SMesh.h>
#include <IMeshBuffer.h>
#include <SMeshBuffer.h>
#include "item_visuals_manager.h"
#define WIELD_SCALE_FACTOR 30.0f
#define WIELD_SCALE_FACTOR_EXTRUDED 40.0f
@ -27,6 +31,14 @@
#define MIN_EXTRUSION_MESH_RESOLUTION 16
#define MAX_EXTRUSION_MESH_RESOLUTION 512
ItemMeshBufferInfo::ItemMeshBufferInfo(const TileLayer &layer) :
override_color(layer.color),
override_color_set(layer.has_color),
animation_info((layer.material_flags & MATERIAL_FLAG_ANIMATION) ?
std::make_unique<AnimationInfo>(layer) :
nullptr)
{}
static scene::IMesh *createExtrusionMesh(int resolution_x, int resolution_y)
{
const f32 r = 0.5;
@ -151,8 +163,7 @@ public:
int maxdim = MYMAX(dim.Width, dim.Height);
std::map<int, scene::IMesh*>::iterator
it = m_extrusion_meshes.lower_bound(maxdim);
auto it = m_extrusion_meshes.lower_bound(maxdim);
if (it == m_extrusion_meshes.end()) {
// no viable resolution found; use largest one
@ -201,7 +212,6 @@ WieldMeshSceneNode::WieldMeshSceneNode(scene::ISceneManager *mgr, s32 id):
// Create the child scene node
scene::IMesh *dummymesh = g_extrusion_mesh_cache->createCube();
m_meshnode = SceneManager->addMeshSceneNode(dummymesh, this, -1);
m_meshnode->setReadOnlyMaterials(false);
m_meshnode->setVisible(false);
dummymesh->drop(); // m_meshnode grabbed it
@ -284,7 +294,7 @@ void WieldMeshSceneNode::setExtruded(const std::string &imagename,
}
static scene::SMesh *createGenericNodeMesh(Client *client, MapNode n,
std::vector<ItemPartColor> *colors, const ContentFeatures &f)
std::vector<ItemMeshBufferInfo> *buffer_info, const ContentFeatures &f)
{
n.setParam1(0xff);
if (n.getParam2()) {
@ -308,7 +318,7 @@ static scene::SMesh *createGenericNodeMesh(Client *client, MapNode n,
MapblockMeshGenerator(&mmd, &collector).generate();
}
colors->clear();
buffer_info->clear();
scene::SMesh *mesh = new scene::SMesh();
for (int layer = 0; layer < MAX_TILE_LAYERS; layer++) {
auto &prebuffers = collector.prebuffers[layer];
@ -324,16 +334,11 @@ static scene::SMesh *createGenericNodeMesh(Client *client, MapNode n,
buf->append(&p.vertices[0], p.vertices.size(),
&p.indices[0], p.indices.size());
// Set up material
buf->Material.setTexture(0, p.layer.texture);
if (layer == 1) {
buf->Material.PolygonOffsetSlopeScale = -1;
buf->Material.PolygonOffsetDepthBias = -1;
}
p.layer.applyMaterialOptions(buf->Material);
// note: material type is left unset, overriden later
p.layer.applyMaterialOptions(buf->Material, layer);
mesh->addMeshBuffer(buf.get());
colors->emplace_back(p.layer.has_color, p.layer.color);
buffer_info->emplace_back(p.layer);
}
}
mesh->recalculateBoundingBox();
@ -344,6 +349,7 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che
{
ITextureSource *tsrc = client->getTextureSource();
IItemDefManager *idef = client->getItemDefManager();
ItemVisualsManager *item_visuals = client->getItemVisualsManager();
IShaderSource *shdrsrc = client->getShaderSource();
const NodeDefManager *ndef = client->getNodeDefManager();
const ItemDefinition &def = item.getDefinition(idef);
@ -356,8 +362,8 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che
m_material_type = shdrsrc->getShaderInfo(shader_id).material;
// Color-related
m_colors.clear();
m_base_color = idef->getItemstackColor(item, client);
m_buffer_info.clear();
m_base_color = item_visuals->getItemstackColor(item, client);
const std::string wield_image = item.getWieldImage(idef);
const std::string wield_overlay = item.getWieldOverlay(idef);
@ -365,11 +371,10 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che
// If wield_image needs to be checked and is defined, it overrides everything else
if (!wield_image.empty() && check_wield_image) {
setExtruded(wield_image, wield_overlay, wield_scale, tsrc,
1);
m_colors.emplace_back();
setExtruded(wield_image, wield_overlay, wield_scale, tsrc, 1);
m_buffer_info.emplace_back();
// overlay is white, if present
m_colors.emplace_back(true, video::SColor(0xFFFFFFFF));
m_buffer_info.emplace_back(true, video::SColor(0xFFFFFFFF));
// initialize the color
setColor(video::SColor(0xFFFFFFFF));
return;
@ -398,8 +403,8 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che
wscale, tsrc,
l0.animation_frame_count);
// Add color
m_colors.emplace_back(l0.has_color, l0.color);
m_colors.emplace_back(l1.has_color, l1.color);
m_buffer_info.emplace_back(l0.has_color, l0.color);
m_buffer_info.emplace_back(l1.has_color, l1.color);
break;
}
case NDT_PLANTLIKE_ROOTED: {
@ -408,7 +413,7 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che
setExtruded(tsrc->getTextureName(l0.texture_id),
"", wield_scale, tsrc,
l0.animation_frame_count);
m_colors.emplace_back(l0.has_color, l0.color);
m_buffer_info.emplace_back(l0.has_color, l0.color);
break;
}
default: {
@ -417,7 +422,7 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che
if (def.place_param2)
n.setParam2(*def.place_param2);
mesh = createGenericNodeMesh(client, n, &m_colors, f);
mesh = createGenericNodeMesh(client, n, &m_buffer_info, f);
changeToMesh(mesh);
mesh->drop();
m_meshnode->setScale(
@ -430,7 +435,7 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che
u32 material_count = m_meshnode->getMaterialCount();
for (u32 i = 0; i < material_count; ++i) {
video::SMaterial &material = m_meshnode->getMaterial(i);
// FIXME: overriding this breaks different alpha modes the mesh may have
// FIXME: we should take different alpha modes of the mesh into account here
material.MaterialType = m_material_type;
material.MaterialTypeParam = 0.5f;
material.forEachTexture([this] (auto &tex) {
@ -451,9 +456,9 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che
setExtruded("no_texture.png", "", def.wield_scale, tsrc, 1);
}
m_colors.emplace_back();
m_buffer_info.emplace_back();
// overlay is white, if present
m_colors.emplace_back(true, video::SColor(0xFFFFFFFF));
m_buffer_info.emplace_back(true, video::SColor(0xFFFFFFFF));
// initialize the color
setColor(video::SColor(0xFFFFFFFF));
@ -475,33 +480,38 @@ void WieldMeshSceneNode::setColor(video::SColor c)
u8 blue = c.getBlue();
const u32 mc = mesh->getMeshBufferCount();
if (mc > m_colors.size())
m_colors.resize(mc);
if (mc > m_buffer_info.size())
m_buffer_info.resize(mc);
for (u32 j = 0; j < mc; j++) {
video::SColor bc(m_base_color);
m_colors[j].applyOverride(bc);
m_buffer_info[j].applyOverride(bc);
video::SColor buffercolor(255,
bc.getRed() * red / 255,
bc.getGreen() * green / 255,
bc.getBlue() * blue / 255);
scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
if (m_colors[j].needColorize(buffercolor)) {
if (m_buffer_info[j].needColorize(buffercolor)) {
buf->setDirty(scene::EBT_VERTEX);
setMeshBufferColor(buf, buffercolor);
}
}
}
void WieldMeshSceneNode::setNodeLightColor(video::SColor color)
void WieldMeshSceneNode::setLightColorAndAnimation(video::SColor color, float animation_time)
{
if (!m_meshnode)
return;
{
for (u32 i = 0; i < m_meshnode->getMaterialCount(); ++i) {
video::SMaterial &material = m_meshnode->getMaterial(i);
material.ColorParam = color;
for (u32 i = 0; i < m_meshnode->getMaterialCount(); ++i) {
// Color
video::SMaterial &material = m_meshnode->getMaterial(i);
material.ColorParam = color;
// Animation
const ItemMeshBufferInfo &buf_info = m_buffer_info[i];
if (buf_info.animation_info) {
buf_info.animation_info->updateTexture(material, animation_time);
}
}
}
@ -548,9 +558,9 @@ void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result)
const std::string inventory_overlay = item.getInventoryOverlay(idef);
if (!inventory_image.empty()) {
mesh = getExtrudedMesh(tsrc, inventory_image, inventory_overlay);
result->buffer_colors.emplace_back();
result->buffer_info.emplace_back();
// overlay is white, if present
result->buffer_colors.emplace_back(true, video::SColor(0xFFFFFFFF));
result->buffer_info.emplace_back(true, video::SColor(0xFFFFFFFF));
// Items with inventory images do not need shading
result->needs_shading = false;
} else if (def.type == ITEM_NODE && f.drawtype == NDT_AIRLIKE) {
@ -566,8 +576,8 @@ void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result)
tsrc->getTextureName(l0.texture_id),
tsrc->getTextureName(l1.texture_id));
// Add color
result->buffer_colors.emplace_back(l0.has_color, l0.color);
result->buffer_colors.emplace_back(l1.has_color, l1.color);
result->buffer_info.emplace_back(l0.has_color, l0.color);
result->buffer_info.emplace_back(l1.has_color, l1.color);
break;
}
case NDT_PLANTLIKE_ROOTED: {
@ -575,7 +585,7 @@ void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result)
const TileLayer &l0 = f.special_tiles[0].layers[0];
mesh = getExtrudedMesh(tsrc,
tsrc->getTextureName(l0.texture_id), "");
result->buffer_colors.emplace_back(l0.has_color, l0.color);
result->buffer_info.emplace_back(l0.has_color, l0.color);
break;
}
default: {
@ -584,7 +594,7 @@ void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result)
if (def.place_param2)
n.setParam2(*def.place_param2);
mesh = createGenericNodeMesh(client, n, &result->buffer_colors, f);
mesh = createGenericNodeMesh(client, n, &result->buffer_info, f);
scaleMesh(mesh, v3f(0.12, 0.12, 0.12));
break;
}

View file

@ -11,6 +11,8 @@
#include <EMaterialTypes.h>
#include <IMeshSceneNode.h>
#include <SColor.h>
#include <memory>
#include "tile.h"
namespace irr::scene
{
@ -28,9 +30,10 @@ struct ContentFeatures;
class ShadowRenderer;
/*
* Holds color information of an item mesh's buffer.
* Holds information of an item mesh's buffer.
* Used for coloring and animation.
*/
class ItemPartColor
class ItemMeshBufferInfo
{
/*
* Optional color that overrides the global base color.
@ -47,12 +50,14 @@ class ItemPartColor
public:
ItemPartColor() = default;
ItemMeshBufferInfo() = default;
ItemPartColor(bool override, video::SColor color) :
ItemMeshBufferInfo(bool override, video::SColor color) :
override_color(color), override_color_set(override)
{}
ItemMeshBufferInfo(const TileLayer &layer);
void applyOverride(video::SColor &dest) const {
if (override_color_set)
dest = override_color;
@ -65,15 +70,18 @@ public:
last_colorized = target;
return true;
}
// Null for no animated parts
std::unique_ptr<AnimationInfo> animation_info;
};
struct ItemMesh
{
scene::IMesh *mesh = nullptr;
/*
* Stores the color of each mesh buffer.
* Stores draw information of each mesh buffer.
*/
std::vector<ItemPartColor> buffer_colors;
std::vector<ItemMeshBufferInfo> buffer_info;
/*
* If false, all faces of the item should have the same brightness.
* Disables shading based on normal vectors.
@ -101,7 +109,7 @@ public:
// Must only be used if the constructor was called with lighting = false
void setColor(video::SColor color);
void setNodeLightColor(video::SColor color);
void setLightColorAndAnimation(video::SColor color, float animation_time);
scene::IMesh *getMesh() { return m_meshnode->getMesh(); }
@ -120,10 +128,10 @@ private:
bool m_bilinear_filter;
bool m_trilinear_filter;
/*!
* Stores the colors of the mesh's mesh buffers.
* Stores the colors and animation data of the mesh's mesh buffers.
* This does not include lighting.
*/
std::vector<ItemPartColor> m_colors;
std::vector<ItemMeshBufferInfo> m_buffer_info;
/*!
* The base color of this mesh. This is the default
* for all mesh buffers.

View file

@ -13,32 +13,32 @@
ClientDynamicInfo ClientDynamicInfo::getCurrent()
{
v2u32 screen_size = RenderingEngine::getWindowSize();
f32 density = RenderingEngine::getDisplayDensity();
f32 gui_scaling = g_settings->getFloat("gui_scaling", 0.5f, 20.0f);
f32 hud_scaling = g_settings->getFloat("hud_scaling", 0.5f, 20.0f);
f32 real_gui_scaling = gui_scaling * density;
f32 real_hud_scaling = hud_scaling * density;
bool touch_controls = g_touchcontrols;
v2u32 screen_size = RenderingEngine::getWindowSize();
f32 density = RenderingEngine::getDisplayDensity();
f32 gui_scaling = g_settings->getFloat("gui_scaling", 0.5f, 20.0f);
f32 hud_scaling = g_settings->getFloat("hud_scaling", 0.5f, 20.0f);
f32 real_gui_scaling = gui_scaling * density;
f32 real_hud_scaling = hud_scaling * density;
bool touch_controls = g_touchcontrols;
return {
screen_size, real_gui_scaling, real_hud_scaling,
ClientDynamicInfo::calculateMaxFSSize(screen_size, density, gui_scaling),
touch_controls
};
return {
screen_size, real_gui_scaling, real_hud_scaling,
ClientDynamicInfo::calculateMaxFSSize(screen_size, density, gui_scaling),
touch_controls
};
}
v2f32 ClientDynamicInfo::calculateMaxFSSize(v2u32 render_target_size, f32 density, f32 gui_scaling)
{
// must stay in sync with GUIFormSpecMenu::calculateImgsize
const double screen_dpi = density * 96;
const double screen_dpi = density * 96;
// assume padding[0,0] since max_formspec_size is used for fullscreen formspecs
// assume padding[0,0] since max_formspec_size is used for fullscreen formspecs
double prefer_imgsize = GUIFormSpecMenu::getImgsize(render_target_size,
screen_dpi, gui_scaling);
return v2f32(render_target_size.X / prefer_imgsize,
render_target_size.Y / prefer_imgsize);
screen_dpi, gui_scaling);
return v2f32(render_target_size.X / prefer_imgsize,
render_target_size.Y / prefer_imgsize);
}
#endif

View file

@ -4,10 +4,12 @@
#include "collision.h"
#include <cmath>
#include "irr_aabb3d.h"
#include "mapblock.h"
#include "map.h"
#include "nodedef.h"
#include "gamedef.h"
#include "util/numeric.h"
#if CHECK_CLIENT_BUILD()
#include "client/clientenvironment.h"
#include "client/localplayer.h"
@ -72,6 +74,14 @@ inline v3f truncate(const v3f vec, const f32 factor)
);
}
inline v3f rangelimv(const v3f vec, const f32 low, const f32 high)
{
return v3f(
rangelim(vec.X, low, high),
rangelim(vec.Y, low, high),
rangelim(vec.Z, low, high)
);
}
}
// Helper function:
@ -101,6 +111,8 @@ CollisionAxis axisAlignedCollision(
if (speed.Y) {
distance = relbox.MaxEdge.Y - relbox.MinEdge.Y;
// FIXME: The dtime calculation is inaccurate without acceleration information.
// Exact formula: `dtime = (-vel ± sqrt(vel² + 2 * acc * distance)) / acc`
*dtime = distance / std::abs(speed.Y);
time = std::max(*dtime, 0.0f);
@ -214,16 +226,50 @@ static bool add_area_node_boxes(const v3s16 min, const v3s16 max, IGameDef *game
thread_local std::vector<aabb3f> nodeboxes;
Map *map = &env->getMap();
const bool air_walkable = nodedef->get(CONTENT_AIR).walkable;
v3s16 last_bp(S16_MAX);
MapBlock *last_block = nullptr;
// Note: as the area used here is usually small, iterating entire blocks
// would actually be slower by factor of 10.
v3s16 p;
for (p.Z = min.Z; p.Z <= max.Z; p.Z++)
for (p.Y = min.Y; p.Y <= max.Y; p.Y++)
for (p.X = min.X; p.X <= max.X; p.X++) {
bool is_position_valid;
MapNode n = map->getNode(p, &is_position_valid);
v3s16 bp, relp;
getNodeBlockPosWithOffset(p, bp, relp);
if (bp != last_bp) {
last_block = map->getBlockNoCreateNoEx(bp);
last_bp = bp;
}
MapBlock *const block = last_block;
if (is_position_valid && n.getContent() != CONTENT_IGNORE) {
// Object collides into walkable nodes
if (!block) {
// Since we iterate with node precision we can only safely skip
// ahead in the "innermost" axis of the MapBlock (X).
// This still worth it as it reduces the number of nodes to look at
// and entries in `cinfo`.
v3s16 rowend(bp.X * MAP_BLOCKSIZE + MAP_BLOCKSIZE - 1, p.Y, p.Z);
aabb3f box = getNodeBox(p, BS);
box.addInternalBox(getNodeBox(rowend, BS));
// Collide with unloaded block
cinfo.emplace_back(true, 0, p, box);
p.X = rowend.X;
continue;
}
if (!air_walkable && block->isAir()) {
// Skip ahead if air, like above
any_position_valid = true;
p.X = bp.X * MAP_BLOCKSIZE + MAP_BLOCKSIZE - 1;
continue;
}
const MapNode n = block->getNodeNoCheck(relp);
if (n.getContent() != CONTENT_IGNORE) {
any_position_valid = true;
const ContentFeatures &f = nodedef->get(n);
@ -238,7 +284,6 @@ static bool add_area_node_boxes(const v3s16 min, const v3s16 max, IGameDef *game
nodeboxes.clear();
n.getCollisionBoxes(nodedef, &nodeboxes, neighbors);
// Calculate float position only once
v3f posf = intToFloat(p, BS);
for (auto box : nodeboxes) {
box.MinEdge += posf;
@ -246,12 +291,12 @@ static bool add_area_node_boxes(const v3s16 min, const v3s16 max, IGameDef *game
cinfo.emplace_back(false, n_bouncy_value, p, box);
}
} else {
// Collide with unloaded nodes (position invalid) and loaded
// CONTENT_IGNORE nodes (position valid)
// Collide with loaded CONTENT_IGNORE nodes
aabb3f box = getNodeBox(p, BS);
cinfo.emplace_back(true, 0, p, box);
}
}
return any_position_valid;
}
@ -268,13 +313,14 @@ static void add_object_boxes(Environment *env,
}
};
// Calculate distance by speed, add own extent and 1.5m of tolerance
const f32 distance = speed_f.getLength() * dtime +
box_0.getExtent().getLength() + 1.5f * BS;
constexpr f32 tolerance = 1.5f * BS;
#if CHECK_CLIENT_BUILD()
ClientEnvironment *c_env = dynamic_cast<ClientEnvironment*>(env);
if (c_env) {
// Calculate distance by speed, add own extent and tolerance
const f32 distance = speed_f.getLength() * dtime +
box_0.getExtent().getLength() + tolerance;
std::vector<DistanceSortedActiveObject> clientobjects;
c_env->getActiveObjects(pos_f, distance, clientobjects);
@ -313,9 +359,14 @@ static void add_object_boxes(Environment *env,
return false;
};
// Calculate distance by speed, add own extent and tolerance
const v3f movement = speed_f * dtime;
const v3f min = pos_f + box_0.MinEdge - v3f(tolerance) + componentwise_min(movement, v3f());
const v3f max = pos_f + box_0.MaxEdge + v3f(tolerance) + componentwise_max(movement, v3f());
// nothing is put into this vector
std::vector<ServerActiveObject*> s_objects;
s_env->getObjectsInsideRadius(s_objects, pos_f, distance, include_obj_cb);
s_env->getObjectsInArea(s_objects, aabb3f(min, max), include_obj_cb);
}
}
}
@ -335,6 +386,10 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef,
collisionMoveResult result;
// Assume no collisions when no velocity and no acceleration
if (*speed_f == v3f() && accel_f == v3f())
return result;
/*
Calculate new velocity
*/
@ -350,30 +405,19 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef,
time_notification_done = false;
}
v3f dpos_f = (*speed_f + accel_f * 0.5f * dtime) * dtime;
v3f newpos_f = *pos_f + dpos_f;
*speed_f += accel_f * dtime;
// If the object is static, there are no collisions
if (dpos_f == v3f())
return result;
// Average speed
v3f aspeed_f = *speed_f + accel_f * 0.5f * dtime;
// Limit speed for avoiding hangs
speed_f->Y = rangelim(speed_f->Y, -5000, 5000);
speed_f->X = rangelim(speed_f->X, -5000, 5000);
speed_f->Z = rangelim(speed_f->Z, -5000, 5000);
aspeed_f = truncate(rangelimv(aspeed_f, -5000.0f, 5000.0f), 10000.0f);
*speed_f = truncate(*speed_f, 10000.0f);
/*
Collect node boxes in movement range
*/
// Collect node boxes in movement range
// cached allocation
thread_local std::vector<NearbyCollisionInfo> cinfo;
cinfo.clear();
{
// Movement if no collisions
v3f newpos_f = *pos_f + aspeed_f * dtime;
v3f minpos_f(
MYMIN(pos_f->X, newpos_f.X),
MYMIN(pos_f->Y, newpos_f.Y) + 0.01f * BS, // bias rounding, player often at +/-n.5
@ -399,24 +443,14 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef,
}
}
/*
Collect object boxes in movement range
*/
// Collect object boxes in movement range
if (collide_with_objects) {
add_object_boxes(env, box_0, dtime, *pos_f, *speed_f, self, cinfo);
add_object_boxes(env, box_0, dtime, *pos_f, aspeed_f, self, cinfo);
}
/*
Collision detection
*/
// Collision detection
f32 d = 0.0f;
int loopcount = 0;
while(dtime > BS * 1e-10f) {
// Avoid infinite loop
loopcount++;
for (int loopcount = 0;; loopcount++) {
if (loopcount >= 100) {
warningstream << "collisionMoveSimple: Loop count exceeded, aborting to avoid infinite loop" << std::endl;
g_collision_problems_encountered = true;
@ -431,9 +465,7 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef,
f32 nearest_dtime = dtime;
int nearest_boxindex = -1;
/*
Go through every nodebox, find nearest collision
*/
// Go through every nodebox, find nearest collision
for (u32 boxindex = 0; boxindex < cinfo.size(); boxindex++) {
const NearbyCollisionInfo &box_info = cinfo[boxindex];
// Ignore if already stepped up this nodebox.
@ -443,8 +475,7 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef,
// Find nearest collision of the two boxes (raytracing-like)
f32 dtime_tmp = nearest_dtime;
CollisionAxis collided = axisAlignedCollision(box_info.box,
movingbox, *speed_f, &dtime_tmp);
movingbox, aspeed_f, &dtime_tmp);
if (collided == -1 || dtime_tmp >= nearest_dtime)
continue;
@ -455,95 +486,119 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef,
if (nearest_collided == COLLISION_AXIS_NONE) {
// No collision with any collision box.
*pos_f += truncate(*speed_f * dtime, 100.0f);
dtime = 0; // Set to 0 to avoid "infinite" loop due to small FP numbers
} else {
// Otherwise, a collision occurred.
NearbyCollisionInfo &nearest_info = cinfo[nearest_boxindex];
const aabb3f& cbox = nearest_info.box;
*pos_f += aspeed_f * dtime;
// Final speed:
*speed_f += accel_f * dtime;
// Limit speed for avoiding hangs
*speed_f = truncate(rangelimv(*speed_f, -5000.0f, 5000.0f), 10000.0f);
break;
}
// Otherwise, a collision occurred.
NearbyCollisionInfo &nearest_info = cinfo[nearest_boxindex];
const aabb3f& cbox = nearest_info.box;
//movingbox except moved to the horizontal position it would be after step up
//movingbox except moved to the horizontal position it would be after step up
bool step_up = false;
if (nearest_collided != COLLISION_AXIS_Y) {
aabb3f stepbox = movingbox;
stepbox.MinEdge.X += speed_f->X * dtime;
stepbox.MinEdge.Z += speed_f->Z * dtime;
stepbox.MaxEdge.X += speed_f->X * dtime;
stepbox.MaxEdge.Z += speed_f->Z * dtime;
// Look slightly ahead for checking the height when stepping
// to ensure we also check above the node we collided with
// otherwise, might allow glitches such as a stack of stairs
float extra_dtime = nearest_dtime + 0.1f * fabsf(dtime - nearest_dtime);
stepbox.MinEdge.X += aspeed_f.X * extra_dtime;
stepbox.MinEdge.Z += aspeed_f.Z * extra_dtime;
stepbox.MaxEdge.X += aspeed_f.X * extra_dtime;
stepbox.MaxEdge.Z += aspeed_f.Z * extra_dtime;
// Check for stairs.
bool step_up = (nearest_collided != COLLISION_AXIS_Y) && // must not be Y direction
(movingbox.MinEdge.Y < cbox.MaxEdge.Y) &&
(movingbox.MinEdge.Y + stepheight > cbox.MaxEdge.Y) &&
(!wouldCollideWithCeiling(cinfo, stepbox,
cbox.MaxEdge.Y - movingbox.MinEdge.Y,
d));
step_up = (movingbox.MinEdge.Y < cbox.MaxEdge.Y) &&
(movingbox.MinEdge.Y + stepheight > cbox.MaxEdge.Y) &&
(!wouldCollideWithCeiling(cinfo, stepbox,
cbox.MaxEdge.Y - movingbox.MinEdge.Y,
d));
}
// Get bounce multiplier
float bounce = -(float)nearest_info.bouncy / 100.0f;
// Get bounce multiplier
float bounce = -(float)nearest_info.bouncy / 100.0f;
// Move to the point of collision and reduce dtime by nearest_dtime
if (nearest_dtime < 0) {
// Handle negative nearest_dtime
if (!step_up) {
if (nearest_collided == COLLISION_AXIS_X)
pos_f->X += speed_f->X * nearest_dtime;
if (nearest_collided == COLLISION_AXIS_Y)
pos_f->Y += speed_f->Y * nearest_dtime;
if (nearest_collided == COLLISION_AXIS_Z)
pos_f->Z += speed_f->Z * nearest_dtime;
}
} else {
*pos_f += truncate(*speed_f * nearest_dtime, 100.0f);
dtime -= nearest_dtime;
// Move to the point of collision and reduce dtime by nearest_dtime
if (nearest_dtime < 0) {
// Handle negative nearest_dtime
// This largely means an "instant" collision, e.g., with the floor.
// We use aspeed and nearest_dtime to be consistent with above and resolve this collision
if (!step_up) {
if (nearest_collided == COLLISION_AXIS_X)
pos_f->X += aspeed_f.X * nearest_dtime;
if (nearest_collided == COLLISION_AXIS_Y)
pos_f->Y += aspeed_f.Y * nearest_dtime;
if (nearest_collided == COLLISION_AXIS_Z)
pos_f->Z += aspeed_f.Z * nearest_dtime;
}
} else if (nearest_dtime > 0) {
// updated average speed for the sub-interval up to nearest_dtime
aspeed_f = *speed_f + accel_f * 0.5f * nearest_dtime;
*pos_f += aspeed_f * nearest_dtime;
// Speed at (approximated) collision:
*speed_f += accel_f * nearest_dtime;
// Limit speed for avoiding hangs
*speed_f = truncate(rangelimv(*speed_f, -5000.0f, 5000.0f), 10000.0f);
dtime -= nearest_dtime;
}
bool is_collision = true;
if (nearest_info.is_unloaded)
is_collision = false;
v3f old_speed_f = *speed_f;
// Set the speed component that caused the collision to zero
if (step_up) {
// Special case: Handle stairs
nearest_info.is_step_up = true;
} else if (nearest_collided == COLLISION_AXIS_X) {
if (bounce < -1e-4 && fabsf(speed_f->X) > BS * 3) {
speed_f->X *= bounce;
} else {
speed_f->X = 0;
accel_f.X = 0; // avoid colliding in the next interations
}
} else if (nearest_collided == COLLISION_AXIS_Y) {
if (bounce < -1e-4 && fabsf(speed_f->Y) > BS * 3) {
speed_f->Y *= bounce;
} else {
if (speed_f->Y < 0.0f) {
// FIXME: This code is necessary until `axisAlignedCollision` takes acceleration
// into consideration for the time calculation. Otherwise, the colliding faces
// never line up, especially at high step (dtime) intervals.
result.touching_ground = true;
result.standing_on_object = nearest_info.isObject();
}
speed_f->Y = 0;
accel_f.Y = 0; // avoid colliding in the next interations
}
} else { /* nearest_collided == COLLISION_AXIS_Z */
if (bounce < -1e-4 && fabsf(speed_f->Z) > BS * 3) {
speed_f->Z *= bounce;
} else {
speed_f->Z = 0;
accel_f.Z = 0; // avoid colliding in the next interations
}
}
if (!nearest_info.is_unloaded && !step_up) {
CollisionInfo info;
if (nearest_info.isObject())
info.type = COLLISION_OBJECT;
else
info.type = COLLISION_NODE;
info.axis = nearest_collided;
info.type = nearest_info.isObject() ? COLLISION_OBJECT : COLLISION_NODE;
info.node_p = nearest_info.position;
info.object = nearest_info.obj;
info.new_pos = *pos_f;
info.old_speed = *speed_f;
// Set the speed component that caused the collision to zero
if (step_up) {
// Special case: Handle stairs
nearest_info.is_step_up = true;
is_collision = false;
} else if (nearest_collided == COLLISION_AXIS_X) {
if (fabs(speed_f->X) > BS * 3)
speed_f->X *= bounce;
else
speed_f->X = 0;
result.collides = true;
} else if (nearest_collided == COLLISION_AXIS_Y) {
if(fabs(speed_f->Y) > BS * 3)
speed_f->Y *= bounce;
else
speed_f->Y = 0;
result.collides = true;
} else if (nearest_collided == COLLISION_AXIS_Z) {
if (fabs(speed_f->Z) > BS * 3)
speed_f->Z *= bounce;
else
speed_f->Z = 0;
result.collides = true;
}
info.old_speed = old_speed_f;
info.new_speed = *speed_f;
if (info.new_speed.getDistanceFrom(info.old_speed) < 0.1f * BS)
is_collision = false;
if (is_collision) {
info.axis = nearest_collided;
result.collisions.push_back(std::move(info));
}
result.collisions.push_back(info);
}
if (dtime < BS * 1e-10f)
break;
// Speed for finding the next collision
aspeed_f = *speed_f + accel_f * 0.5f * dtime;
// Limit speed for avoiding hangs
aspeed_f = truncate(rangelimv(aspeed_f, -5000.0f, 5000.0f), 10000.0f);
}
/*
@ -573,14 +628,15 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef,
box.MaxEdge += *pos_f;
}
if (std::fabs(cbox.MaxEdge.Y - box.MinEdge.Y) < 0.05f) {
// This is code is technically only required if `box_info.is_step_up == true`.
// However, players rely on this check/condition to climb stairs faster. See PR #10587.
result.touching_ground = true;
if (box_info.isObject())
result.standing_on_object = true;
result.standing_on_object = box_info.isObject();
}
}
}
result.collides = !result.collisions.empty();
return result;
}

View file

@ -13,7 +13,7 @@
*/
/*
Connection
Network Protocol
*/
#define PEER_ID_INEXISTENT 0
@ -60,15 +60,16 @@
// Use floatToInt(p, BS) and intToFloat(p, BS).
#define BS 10.0f
// Dimension of a MapBlock
// Dimension of a MapBlock in nodes
#define MAP_BLOCKSIZE 16
// This makes mesh updates too slow, as many meshes are updated during
// the main loop (related to TempMods and day/night)
//#define MAP_BLOCKSIZE 32
// Player step height in nodes
#define PLAYER_DEFAULT_STEPHEIGHT 0.6f
// Arbitrary volume limit for working with contiguous areas (in nodes)
// needs to safely fit in the VoxelArea class; used by e.g. VManips
#define MAX_WORKING_VOLUME 150000000UL
/*
Old stuff that shouldn't be hardcoded
*/
@ -82,6 +83,10 @@
// Default maximal breath of a player
#define PLAYER_MAX_BREATH_DEFAULT 10
/*
Misc
*/
// Number of different files to try to save a player to if the first fails
// (because of a case-insensitive filesystem)
// TODO: Use case-insensitive player names instead of this hack.
@ -93,8 +98,4 @@
// the file attempting to ensure a unique filename
#define SCREENSHOT_MAX_SERIAL_TRIES 1000
/*
GUI related things
*/
#define TTF_DEFAULT_FONT_SIZE (16)

View file

@ -34,6 +34,8 @@ std::string ModConfiguration::getUnsatisfiedModsError() const
void ModConfiguration::addModsInPath(const std::string &path, const std::string &virtual_path)
{
verbosestream << "Adding mods from path " << path << " virtual=\""
<< virtual_path << "\"" << std::endl;
addMods(flattenMods(getModsInPath(path, virtual_path)));
}
@ -140,8 +142,6 @@ void ModConfiguration::addModsFromConfig(
*
* Alternative candidates for a modname are stored in `candidates`,
* and used in an error message later.
*
* If not enabled, add `load_mod_modname = false` to world.mt
*/
for (const auto &modPath : modPaths) {
std::vector<ModSpec> addon_mods_in_path = flattenMods(getModsInPath(modPath.second, modPath.first));
@ -154,7 +154,7 @@ void ModConfiguration::addModsFromConfig(
candidates[pair->first].emplace_back(mod.virtual_path);
}
} else {
conf.setBool("load_mod_" + mod.name, false);
conf.remove("load_mod_" + mod.name);
}
}
}

View file

@ -15,6 +15,7 @@
#include "porting.h"
#include "convert_json.h"
#include "script/common/c_internal.h"
#include "exceptions.h"
void ModSpec::checkAndLog() const
{
@ -174,8 +175,9 @@ std::map<std::string, ModSpec> getModsInPath(
mod_virtual_path.append(virtual_path).append("/").append(modname);
ModSpec spec(modname, mod_path, part_of_modpack, mod_virtual_path);
parseModContents(spec);
result[modname] = std::move(spec);
if (parseModContents(spec)) {
result[modname] = std::move(spec);
}
}
return result;
}

View file

@ -78,7 +78,7 @@ struct ModSpec
*
* @returns false if not a mod
*/
bool parseModContents(ModSpec &mod);
[[nodiscard]] bool parseModContents(ModSpec &mod);
/**
* Gets a list of all mods and modpacks in path

View file

@ -12,6 +12,7 @@
#include "defaultsettings.h" // for set_default_settings
#include "map_settings_manager.h"
#include "util/string.h"
#include "exceptions.h"
// The maximum number of identical world names allowed
#define MAX_WORLD_NAMES 100

View file

@ -30,15 +30,13 @@ void Database_Dummy::loadBlock(const v3s16 &pos, std::string *block)
bool Database_Dummy::deleteBlock(const v3s16 &pos)
{
m_database.erase(getBlockAsInteger(pos));
return true;
return m_database.erase(getBlockAsInteger(pos)) > 0;
}
void Database_Dummy::listAllLoadableBlocks(std::vector<v3s16> &dst)
{
dst.reserve(m_database.size());
for (std::map<s64, std::string>::const_iterator x = m_database.begin();
x != m_database.end(); ++x) {
for (auto x = m_database.begin(); x != m_database.end(); ++x) {
dst.push_back(getIntegerAsBlock(x->first));
}
}

View file

@ -234,8 +234,7 @@ void PlayerDatabaseFiles::listPlayers(std::vector<std::string> &res)
{
std::vector<fs::DirListNode> files = fs::GetDirListing(m_savedir);
// list files into players directory
for (std::vector<fs::DirListNode>::const_iterator it = files.begin(); it !=
files.end(); ++it) {
for (auto it = files.begin(); it != files.end(); ++it) {
// Ignore directories
if (it->dir)
continue;

View file

@ -97,7 +97,7 @@ void Database_PostgreSQL::ping()
bool Database_PostgreSQL::initialized() const
{
return (PQstatus(m_conn) == CONNECTION_OK);
return m_conn && PQstatus(m_conn) == CONNECTION_OK;
}
PGresult *Database_PostgreSQL::checkResults(PGresult *result, bool clear)

View file

@ -9,20 +9,20 @@
#include "database.h"
#include "util/basic_macros.h"
class Settings;
class Database_PostgreSQL: public Database
// Template class for PostgreSQL based data storage
class Database_PostgreSQL : public Database
{
public:
Database_PostgreSQL(const std::string &connect_string, const char *type);
~Database_PostgreSQL();
void beginSave();
void endSave();
void beginSave() override;
void endSave() override;
void rollback();
bool initialized() const;
bool initialized() const override;
void verifyDatabase() override;
protected:
// Conversion helpers
@ -73,7 +73,6 @@ protected:
}
void createTableIfNotExists(const std::string &table_name, const std::string &definition);
void verifyDatabase();
// Database initialization
void connectToDatabase();
@ -99,6 +98,12 @@ private:
int m_pgversion = 0;
};
// Not sure why why we have to do this. can't C++ figure it out on its own?
#define PARENT_CLASS_FUNCS \
void beginSave() { Database_PostgreSQL::beginSave(); } \
void endSave() { Database_PostgreSQL::endSave(); } \
void verifyDatabase() { Database_PostgreSQL::verifyDatabase(); }
class MapDatabasePostgreSQL : private Database_PostgreSQL, public MapDatabase
{
public:
@ -110,8 +115,7 @@ public:
bool deleteBlock(const v3s16 &pos);
void listAllLoadableBlocks(std::vector<v3s16> &dst);
void beginSave() { Database_PostgreSQL::beginSave(); }
void endSave() { Database_PostgreSQL::endSave(); }
PARENT_CLASS_FUNCS
protected:
virtual void createDatabase();
@ -129,6 +133,8 @@ public:
bool removePlayer(const std::string &name);
void listPlayers(std::vector<std::string> &res);
PARENT_CLASS_FUNCS
protected:
virtual void createDatabase();
virtual void initStatements();
@ -143,8 +149,6 @@ public:
AuthDatabasePostgreSQL(const std::string &connect_string);
virtual ~AuthDatabasePostgreSQL() = default;
virtual void verifyDatabase() { Database_PostgreSQL::verifyDatabase(); }
virtual bool getAuth(const std::string &name, AuthEntry &res);
virtual bool saveAuth(const AuthEntry &authEntry);
virtual bool createAuth(AuthEntry &authEntry);
@ -152,6 +156,8 @@ public:
virtual void listNames(std::vector<std::string> &res);
virtual void reload();
PARENT_CLASS_FUNCS
protected:
virtual void createDatabase();
virtual void initStatements();
@ -176,10 +182,11 @@ public:
bool removeModEntries(const std::string &modname);
void listMods(std::vector<std::string> *res);
void beginSave() { Database_PostgreSQL::beginSave(); }
void endSave() { Database_PostgreSQL::endSave(); }
PARENT_CLASS_FUNCS
protected:
virtual void createDatabase();
virtual void initStatements();
};
#undef PARENT_CLASS_FUNCS

View file

@ -20,7 +20,7 @@
/*
* Redis is not a good fit for Minetest and only still supported for legacy as
* well as advanced use case reasons, see:
* <https://github.com/minetest/minetest/issues/14822>
* <https://github.com/luanti-org/luanti/issues/14822>
*
* Do NOT extend this backend with any new functionality.
*/

View file

@ -2,14 +2,6 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
// Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
/*
SQLite format specification:
blocks:
(PK) INT id
BLOB data
*/
#include "database-sqlite3.h"
#include "log.h"
@ -26,23 +18,19 @@ SQLite format specification:
// When to print messages when the database is being held locked by another process
// Note: I've seen occasional delays of over 250ms while running minetestmapper.
#define BUSY_INFO_TRESHOLD 100 // Print first informational message after 100ms.
#define BUSY_WARNING_TRESHOLD 250 // Print warning message after 250ms. Lag is increased.
#define BUSY_ERROR_TRESHOLD 1000 // Print error message after 1000ms. Significant lag.
#define BUSY_FATAL_TRESHOLD 3000 // Allow SQLITE_BUSY to be returned, which will cause a minetest crash.
#define BUSY_ERROR_INTERVAL 10000 // Safety net: report again every 10 seconds
enum {
BUSY_INFO_TRESHOLD = 100, // Print first informational message.
BUSY_WARNING_TRESHOLD = 250, // Print warning message. Significant lag.
BUSY_FATAL_TRESHOLD = 3000, // Allow SQLITE_BUSY to be returned back to the caller.
BUSY_ERROR_INTERVAL = 10000, // Safety net: report again every 10 seconds
};
#define SQLRES(s, r, m) \
if ((s) != (r)) { \
throw DatabaseException(std::string(m) + ": " +\
sqlite3_errmsg(m_database)); \
}
#define SQLRES(s, r, m) sqlite3_vrfy(s, m, r);
#define SQLOK(s, m) SQLRES(s, SQLITE_OK, m)
#define PREPARE_STATEMENT(name, query) \
SQLOK(sqlite3_prepare_v2(m_database, query, -1, &m_stmt_##name, NULL),\
"Failed to prepare query '" query "'")
SQLOK(sqlite3_prepare_v2(m_database, query, -1, &m_stmt_##name, NULL), \
std::string("Failed to prepare query \"").append(query).append("\""))
#define SQLOK_ERRSTREAM(s, m) \
if ((s) != SQLITE_OK) { \
@ -50,52 +38,49 @@ SQLite format specification:
<< sqlite3_errmsg(m_database) << std::endl; \
}
#define FINALIZE_STATEMENT(statement) SQLOK_ERRSTREAM(sqlite3_finalize(statement), \
"Failed to finalize " #statement)
#define FINALIZE_STATEMENT(name) \
sqlite3_finalize(m_stmt_##name); /* if this fails who cares */ \
m_stmt_##name = nullptr;
int Database_SQLite3::busyHandler(void *data, int count)
{
s64 &first_time = reinterpret_cast<s64 *>(data)[0];
s64 &prev_time = reinterpret_cast<s64 *>(data)[1];
s64 cur_time = porting::getTimeMs();
u64 &first_time = reinterpret_cast<u64*>(data)[0];
u64 &prev_time = reinterpret_cast<u64*>(data)[1];
u64 cur_time = porting::getTimeMs();
if (count == 0) {
first_time = cur_time;
prev_time = first_time;
} else {
while (cur_time < prev_time)
cur_time += s64(1)<<32;
}
if (cur_time - first_time < BUSY_INFO_TRESHOLD) {
; // do nothing
} else if (cur_time - first_time >= BUSY_INFO_TRESHOLD &&
prev_time - first_time < BUSY_INFO_TRESHOLD) {
const auto total_diff = cur_time - first_time; // time since first call
const auto this_diff = prev_time - first_time; // time since last call
if (total_diff < BUSY_INFO_TRESHOLD) {
// do nothing
} else if (total_diff >= BUSY_INFO_TRESHOLD &&
this_diff < BUSY_INFO_TRESHOLD) {
infostream << "SQLite3 database has been locked for "
<< cur_time - first_time << " ms." << std::endl;
} else if (cur_time - first_time >= BUSY_WARNING_TRESHOLD &&
prev_time - first_time < BUSY_WARNING_TRESHOLD) {
<< total_diff << " ms." << std::endl;
} else if (total_diff >= BUSY_WARNING_TRESHOLD &&
this_diff < BUSY_WARNING_TRESHOLD) {
warningstream << "SQLite3 database has been locked for "
<< cur_time - first_time << " ms." << std::endl;
} else if (cur_time - first_time >= BUSY_ERROR_TRESHOLD &&
prev_time - first_time < BUSY_ERROR_TRESHOLD) {
<< total_diff << " ms; this causes lag." << std::endl;
} else if (total_diff >= BUSY_FATAL_TRESHOLD &&
this_diff < BUSY_FATAL_TRESHOLD) {
errorstream << "SQLite3 database has been locked for "
<< cur_time - first_time << " ms; this causes lag." << std::endl;
} else if (cur_time - first_time >= BUSY_FATAL_TRESHOLD &&
prev_time - first_time < BUSY_FATAL_TRESHOLD) {
errorstream << "SQLite3 database has been locked for "
<< cur_time - first_time << " ms - giving up!" << std::endl;
} else if ((cur_time - first_time) / BUSY_ERROR_INTERVAL !=
(prev_time - first_time) / BUSY_ERROR_INTERVAL) {
<< total_diff << " ms - giving up!" << std::endl;
} else if (total_diff / BUSY_ERROR_INTERVAL !=
this_diff / BUSY_ERROR_INTERVAL) {
// Safety net: keep reporting every BUSY_ERROR_INTERVAL
errorstream << "SQLite3 database has been locked for "
<< (cur_time - first_time) / 1000 << " seconds!" << std::endl;
<< total_diff / 1000 << " seconds!" << std::endl;
}
prev_time = cur_time;
// Make sqlite transaction fail if delay exceeds BUSY_FATAL_TRESHOLD
return cur_time - first_time < BUSY_FATAL_TRESHOLD;
return total_diff < BUSY_FATAL_TRESHOLD;
}
@ -130,7 +115,7 @@ void Database_SQLite3::openDatabase()
// Open the database connection
if (!fs::CreateAllDirs(m_savedir)) {
infostream << "Database_SQLite3: Failed to create directory \""
errorstream << "Database_SQLite3: Failed to create directory \""
<< m_savedir << "\"" << std::endl;
throw FileNotGoodException("Failed to create database "
"save directory");
@ -138,8 +123,11 @@ void Database_SQLite3::openDatabase()
bool needs_create = !fs::PathExists(dbp);
SQLOK(sqlite3_open_v2(dbp.c_str(), &m_database,
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL),
auto flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
#ifdef SQLITE_OPEN_EXRESCODE
flags |= SQLITE_OPEN_EXRESCODE;
#endif
SQLOK(sqlite3_open_v2(dbp.c_str(), &m_database, flags, NULL),
std::string("Failed to open SQLite3 database file ") + dbp);
SQLOK(sqlite3_busy_handler(m_database, Database_SQLite3::busyHandler,
@ -152,9 +140,9 @@ void Database_SQLite3::openDatabase()
std::string query_str = std::string("PRAGMA synchronous = ")
+ itos(g_settings->getU16("sqlite_synchronous"));
SQLOK(sqlite3_exec(m_database, query_str.c_str(), NULL, NULL, NULL),
"Failed to modify sqlite3 synchronous mode");
"Failed to set SQLite3 synchronous mode");
SQLOK(sqlite3_exec(m_database, "PRAGMA foreign_keys = ON", NULL, NULL, NULL),
"Failed to enable sqlite3 foreign key support");
"Failed to enable SQLite3 foreign key support");
}
void Database_SQLite3::verifyDatabase()
@ -171,10 +159,47 @@ void Database_SQLite3::verifyDatabase()
m_initialized = true;
}
bool Database_SQLite3::checkTable(const char *table)
{
assert(m_database);
// PRAGMA table_list would be cleaner here but it was only introduced in
// sqlite 3.37.0 (2021-11-27).
// So let's do this: https://stackoverflow.com/a/83195
sqlite3_stmt *m_stmt_tmp = nullptr;
PREPARE_STATEMENT(tmp, "SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?;");
str_to_sqlite(m_stmt_tmp, 1, table);
bool ret = (sqlite3_step(m_stmt_tmp) == SQLITE_ROW);
FINALIZE_STATEMENT(tmp)
return ret;
}
bool Database_SQLite3::checkColumn(const char *table, const char *column)
{
assert(m_database);
sqlite3_stmt *m_stmt_tmp = nullptr;
auto query_str = std::string("PRAGMA table_info(").append(table).append(");");
PREPARE_STATEMENT(tmp, query_str.c_str());
bool ret = false;
while (sqlite3_step(m_stmt_tmp) == SQLITE_ROW) {
ret |= sqlite_to_string_view(m_stmt_tmp, 1) == column;
if (ret)
break;
}
FINALIZE_STATEMENT(tmp)
return ret;
}
Database_SQLite3::~Database_SQLite3()
{
FINALIZE_STATEMENT(m_stmt_begin)
FINALIZE_STATEMENT(m_stmt_end)
FINALIZE_STATEMENT(begin)
FINALIZE_STATEMENT(end)
SQLOK_ERRSTREAM(sqlite3_close(m_database), "Failed to close database");
}
@ -191,40 +216,68 @@ MapDatabaseSQLite3::MapDatabaseSQLite3(const std::string &savedir):
MapDatabaseSQLite3::~MapDatabaseSQLite3()
{
FINALIZE_STATEMENT(m_stmt_read)
FINALIZE_STATEMENT(m_stmt_write)
FINALIZE_STATEMENT(m_stmt_list)
FINALIZE_STATEMENT(m_stmt_delete)
FINALIZE_STATEMENT(read)
FINALIZE_STATEMENT(write)
FINALIZE_STATEMENT(list)
FINALIZE_STATEMENT(delete)
}
void MapDatabaseSQLite3::createDatabase()
{
assert(m_database); // Pre-condition
assert(m_database);
SQLOK(sqlite3_exec(m_database,
// Note: before 5.12.0 the format was blocks(pos INT, data BLOB).
// This function only runs for newly created databases.
const char *schema =
"CREATE TABLE IF NOT EXISTS `blocks` (\n"
" `pos` INT PRIMARY KEY,\n"
" `data` BLOB\n"
");\n",
NULL, NULL, NULL),
"`x` INTEGER,"
"`y` INTEGER,"
"`z` INTEGER,"
"`data` BLOB NOT NULL,"
// Declaring a primary key automatically creates an index and the
// order largely dictates which range operations can be sped up.
// see also: <https://www.sqlite.org/optoverview.html#skipscan>
// Putting XZ before Y matches our MapSector abstraction.
"PRIMARY KEY (`x`, `z`, `y`)"
");\n"
;
SQLOK(sqlite3_exec(m_database, schema, NULL, NULL, NULL),
"Failed to create database table");
}
void MapDatabaseSQLite3::initStatements()
{
PREPARE_STATEMENT(read, "SELECT `data` FROM `blocks` WHERE `pos` = ? LIMIT 1");
PREPARE_STATEMENT(write, "REPLACE INTO `blocks` (`pos`, `data`) VALUES (?, ?)");
PREPARE_STATEMENT(delete, "DELETE FROM `blocks` WHERE `pos` = ?");
PREPARE_STATEMENT(list, "SELECT `pos` FROM `blocks`");
assert(checkTable("blocks"));
m_new_format = checkColumn("blocks", "z");
infostream << "MapDatabaseSQLite3: split column format = "
<< (m_new_format ? "yes" : "no") << std::endl;
verbosestream << "ServerMap: SQLite3 database opened." << std::endl;
if (m_new_format) {
PREPARE_STATEMENT(read, "SELECT `data` FROM `blocks` WHERE `x` = ? AND `y` = ? AND `z` = ? LIMIT 1");
PREPARE_STATEMENT(write, "REPLACE INTO `blocks` (`x`, `y`, `z`, `data`) VALUES (?, ?, ?, ?)");
PREPARE_STATEMENT(delete, "DELETE FROM `blocks` WHERE `x` = ? AND `y` = ? AND `z` = ?");
PREPARE_STATEMENT(list, "SELECT `x`, `y`, `z` FROM `blocks`");
} else {
PREPARE_STATEMENT(read, "SELECT `data` FROM `blocks` WHERE `pos` = ? LIMIT 1");
PREPARE_STATEMENT(write, "REPLACE INTO `blocks` (`pos`, `data`) VALUES (?, ?)");
PREPARE_STATEMENT(delete, "DELETE FROM `blocks` WHERE `pos` = ?");
PREPARE_STATEMENT(list, "SELECT `pos` FROM `blocks`");
}
}
inline void MapDatabaseSQLite3::bindPos(sqlite3_stmt *stmt, const v3s16 &pos, int index)
inline int MapDatabaseSQLite3::bindPos(sqlite3_stmt *stmt, v3s16 pos, int index)
{
SQLOK(sqlite3_bind_int64(stmt, index, getBlockAsInteger(pos)),
"Internal error: failed to bind query at " __FILE__ ":" TOSTRING(__LINE__));
if (m_new_format) {
int_to_sqlite(stmt, index, pos.X);
int_to_sqlite(stmt, index + 1, pos.Y);
int_to_sqlite(stmt, index + 2, pos.Z);
return index + 3;
} else {
int64_to_sqlite(stmt, index, getBlockAsInteger(pos));
return index + 1;
}
}
bool MapDatabaseSQLite3::deleteBlock(const v3s16 &pos)
@ -237,7 +290,7 @@ bool MapDatabaseSQLite3::deleteBlock(const v3s16 &pos)
sqlite3_reset(m_stmt_delete);
if (!good) {
warningstream << "deleteBlock: Block failed to delete "
warningstream << "deleteBlock: Failed to delete block "
<< pos << ": " << sqlite3_errmsg(m_database) << std::endl;
}
return good;
@ -247,9 +300,8 @@ bool MapDatabaseSQLite3::saveBlock(const v3s16 &pos, std::string_view data)
{
verifyDatabase();
bindPos(m_stmt_write, pos);
SQLOK(sqlite3_bind_blob(m_stmt_write, 2, data.data(), data.size(), NULL),
"Internal error: failed to bind query at " __FILE__ ":" TOSTRING(__LINE__));
int col = bindPos(m_stmt_write, pos);
blob_to_sqlite(m_stmt_write, col, data);
SQLRES(sqlite3_step(m_stmt_write), SQLITE_DONE, "Failed to save block")
sqlite3_reset(m_stmt_write);
@ -264,6 +316,7 @@ void MapDatabaseSQLite3::loadBlock(const v3s16 &pos, std::string *block)
bindPos(m_stmt_read, pos);
if (sqlite3_step(m_stmt_read) != SQLITE_ROW) {
block->clear();
sqlite3_reset(m_stmt_read);
return;
}
@ -271,7 +324,6 @@ void MapDatabaseSQLite3::loadBlock(const v3s16 &pos, std::string *block)
auto data = sqlite_to_blob(m_stmt_read, 0);
block->assign(data);
sqlite3_step(m_stmt_read);
// We should never get more than 1 row, so ok to reset
sqlite3_reset(m_stmt_read);
}
@ -280,8 +332,17 @@ void MapDatabaseSQLite3::listAllLoadableBlocks(std::vector<v3s16> &dst)
{
verifyDatabase();
while (sqlite3_step(m_stmt_list) == SQLITE_ROW)
dst.push_back(getIntegerAsBlock(sqlite3_column_int64(m_stmt_list, 0)));
v3s16 p;
while (sqlite3_step(m_stmt_list) == SQLITE_ROW) {
if (m_new_format) {
p.X = sqlite_to_int(m_stmt_list, 0);
p.Y = sqlite_to_int(m_stmt_list, 1);
p.Z = sqlite_to_int(m_stmt_list, 2);
} else {
p = getIntegerAsBlock(sqlite_to_int64(m_stmt_list, 0));
}
dst.push_back(p);
}
sqlite3_reset(m_stmt_list);
}
@ -298,35 +359,38 @@ PlayerDatabaseSQLite3::PlayerDatabaseSQLite3(const std::string &savedir):
PlayerDatabaseSQLite3::~PlayerDatabaseSQLite3()
{
FINALIZE_STATEMENT(m_stmt_player_load)
FINALIZE_STATEMENT(m_stmt_player_add)
FINALIZE_STATEMENT(m_stmt_player_update)
FINALIZE_STATEMENT(m_stmt_player_remove)
FINALIZE_STATEMENT(m_stmt_player_list)
FINALIZE_STATEMENT(m_stmt_player_add_inventory)
FINALIZE_STATEMENT(m_stmt_player_add_inventory_items)
FINALIZE_STATEMENT(m_stmt_player_remove_inventory)
FINALIZE_STATEMENT(m_stmt_player_remove_inventory_items)
FINALIZE_STATEMENT(m_stmt_player_load_inventory)
FINALIZE_STATEMENT(m_stmt_player_load_inventory_items)
FINALIZE_STATEMENT(m_stmt_player_metadata_load)
FINALIZE_STATEMENT(m_stmt_player_metadata_add)
FINALIZE_STATEMENT(m_stmt_player_metadata_remove)
FINALIZE_STATEMENT(player_load)
FINALIZE_STATEMENT(player_add)
FINALIZE_STATEMENT(player_update)
FINALIZE_STATEMENT(player_remove)
FINALIZE_STATEMENT(player_list)
FINALIZE_STATEMENT(player_add_inventory)
FINALIZE_STATEMENT(player_add_inventory_items)
FINALIZE_STATEMENT(player_remove_inventory)
FINALIZE_STATEMENT(player_remove_inventory_items)
FINALIZE_STATEMENT(player_load_inventory)
FINALIZE_STATEMENT(player_load_inventory_items)
FINALIZE_STATEMENT(player_metadata_load)
FINALIZE_STATEMENT(player_metadata_add)
FINALIZE_STATEMENT(player_metadata_remove)
};
void PlayerDatabaseSQLite3::createDatabase()
{
assert(m_database); // Pre-condition
assert(m_database);
// When designing the schema remember that SQLite only has 5 basic data types
// and ignores length-limited types like "VARCHAR(32)".
SQLOK(sqlite3_exec(m_database,
"CREATE TABLE IF NOT EXISTS `player` ("
"`name` VARCHAR(50) NOT NULL,"
"`pitch` NUMERIC(11, 4) NOT NULL,"
"`yaw` NUMERIC(11, 4) NOT NULL,"
"`posX` NUMERIC(11, 4) NOT NULL,"
"`posY` NUMERIC(11, 4) NOT NULL,"
"`posZ` NUMERIC(11, 4) NOT NULL,"
"`name` TEXT NOT NULL,"
"`pitch` NUMERIC NOT NULL,"
"`yaw` NUMERIC NOT NULL,"
"`posX` NUMERIC NOT NULL,"
"`posY` NUMERIC NOT NULL,"
"`posZ` NUMERIC NOT NULL,"
"`hp` INT NOT NULL,"
"`breath` INT NOT NULL,"
"`creation_date` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,"
@ -337,9 +401,9 @@ void PlayerDatabaseSQLite3::createDatabase()
SQLOK(sqlite3_exec(m_database,
"CREATE TABLE IF NOT EXISTS `player_metadata` ("
" `player` VARCHAR(50) NOT NULL,"
" `metadata` VARCHAR(256) NOT NULL,"
" `value` TEXT,"
" `player` TEXT NOT NULL,"
" `metadata` TEXT NOT NULL,"
" `value` TEXT NOT NULL,"
" PRIMARY KEY(`player`, `metadata`),"
" FOREIGN KEY (`player`) REFERENCES player (`name`) ON DELETE CASCADE );",
NULL, NULL, NULL),
@ -347,7 +411,7 @@ void PlayerDatabaseSQLite3::createDatabase()
SQLOK(sqlite3_exec(m_database,
"CREATE TABLE IF NOT EXISTS `player_inventories` ("
" `player` VARCHAR(50) NOT NULL,"
" `player` TEXT NOT NULL,"
" `inv_id` INT NOT NULL,"
" `inv_width` INT NOT NULL,"
" `inv_name` TEXT NOT NULL DEFAULT '',"
@ -359,7 +423,7 @@ void PlayerDatabaseSQLite3::createDatabase()
SQLOK(sqlite3_exec(m_database,
"CREATE TABLE `player_inventory_items` ("
" `player` VARCHAR(50) NOT NULL,"
" `player` TEXT NOT NULL,"
" `inv_id` INT NOT NULL,"
" `slot_id` INT NOT NULL,"
" `item` TEXT NOT NULL DEFAULT '',"
@ -401,7 +465,6 @@ void PlayerDatabaseSQLite3::initStatements()
"(`player`, `metadata`, `value`) VALUES (?, ?, ?)")
PREPARE_STATEMENT(player_metadata_remove, "DELETE FROM `player_metadata` "
"WHERE `player` = ?")
verbosestream << "ServerEnvironment: SQLite3 database opened (players)." << std::endl;
}
bool PlayerDatabaseSQLite3::playerDataExists(const std::string &name)
@ -588,27 +651,27 @@ AuthDatabaseSQLite3::AuthDatabaseSQLite3(const std::string &savedir) :
AuthDatabaseSQLite3::~AuthDatabaseSQLite3()
{
FINALIZE_STATEMENT(m_stmt_read)
FINALIZE_STATEMENT(m_stmt_write)
FINALIZE_STATEMENT(m_stmt_create)
FINALIZE_STATEMENT(m_stmt_delete)
FINALIZE_STATEMENT(m_stmt_list_names)
FINALIZE_STATEMENT(m_stmt_read_privs)
FINALIZE_STATEMENT(m_stmt_write_privs)
FINALIZE_STATEMENT(m_stmt_delete_privs)
FINALIZE_STATEMENT(m_stmt_last_insert_rowid)
FINALIZE_STATEMENT(read)
FINALIZE_STATEMENT(write)
FINALIZE_STATEMENT(create)
FINALIZE_STATEMENT(delete)
FINALIZE_STATEMENT(list_names)
FINALIZE_STATEMENT(read_privs)
FINALIZE_STATEMENT(write_privs)
FINALIZE_STATEMENT(delete_privs)
FINALIZE_STATEMENT(last_insert_rowid)
}
void AuthDatabaseSQLite3::createDatabase()
{
assert(m_database); // Pre-condition
assert(m_database);
SQLOK(sqlite3_exec(m_database,
"CREATE TABLE IF NOT EXISTS `auth` ("
"`id` INTEGER PRIMARY KEY AUTOINCREMENT,"
"`name` VARCHAR(32) UNIQUE,"
"`password` VARCHAR(512),"
"`last_login` INTEGER"
"`name` TEXT UNIQUE NOT NULL,"
"`password` TEXT NOT NULL,"
"`last_login` INTEGER NOT NULL DEFAULT 0"
");",
NULL, NULL, NULL),
"Failed to create auth table");
@ -616,7 +679,7 @@ void AuthDatabaseSQLite3::createDatabase()
SQLOK(sqlite3_exec(m_database,
"CREATE TABLE IF NOT EXISTS `user_privileges` ("
"`id` INTEGER,"
"`privilege` VARCHAR(32),"
"`privilege` TEXT,"
"PRIMARY KEY (id, privilege)"
"CONSTRAINT fk_id FOREIGN KEY (id) REFERENCES auth (id) ON DELETE CASCADE"
");",
@ -751,18 +814,18 @@ ModStorageDatabaseSQLite3::ModStorageDatabaseSQLite3(const std::string &savedir)
ModStorageDatabaseSQLite3::~ModStorageDatabaseSQLite3()
{
FINALIZE_STATEMENT(m_stmt_remove_all)
FINALIZE_STATEMENT(m_stmt_remove)
FINALIZE_STATEMENT(m_stmt_set)
FINALIZE_STATEMENT(m_stmt_has)
FINALIZE_STATEMENT(m_stmt_get)
FINALIZE_STATEMENT(m_stmt_get_keys)
FINALIZE_STATEMENT(m_stmt_get_all)
FINALIZE_STATEMENT(remove_all)
FINALIZE_STATEMENT(remove)
FINALIZE_STATEMENT(set)
FINALIZE_STATEMENT(has)
FINALIZE_STATEMENT(get)
FINALIZE_STATEMENT(get_keys)
FINALIZE_STATEMENT(get_all)
}
void ModStorageDatabaseSQLite3::createDatabase()
{
assert(m_database); // Pre-condition
assert(m_database);
SQLOK(sqlite3_exec(m_database,
"CREATE TABLE IF NOT EXISTS `entries` (\n"
@ -825,8 +888,7 @@ bool ModStorageDatabaseSQLite3::getModEntry(const std::string &modname,
verifyDatabase();
str_to_sqlite(m_stmt_get, 1, modname);
SQLOK(sqlite3_bind_blob(m_stmt_get, 2, key.data(), key.size(), NULL),
"Internal error: failed to bind query at " __FILE__ ":" TOSTRING(__LINE__));
blob_to_sqlite(m_stmt_get, 2, key);
bool found = sqlite3_step(m_stmt_get) == SQLITE_ROW;
if (found) {
auto sv = sqlite_to_blob(m_stmt_get, 0);
@ -845,8 +907,7 @@ bool ModStorageDatabaseSQLite3::hasModEntry(const std::string &modname,
verifyDatabase();
str_to_sqlite(m_stmt_has, 1, modname);
SQLOK(sqlite3_bind_blob(m_stmt_has, 2, key.data(), key.size(), NULL),
"Internal error: failed to bind query at " __FILE__ ":" TOSTRING(__LINE__));
blob_to_sqlite(m_stmt_has, 2, key);
bool found = sqlite3_step(m_stmt_has) == SQLITE_ROW;
if (found)
sqlite3_step(m_stmt_has);
@ -862,10 +923,8 @@ bool ModStorageDatabaseSQLite3::setModEntry(const std::string &modname,
verifyDatabase();
str_to_sqlite(m_stmt_set, 1, modname);
SQLOK(sqlite3_bind_blob(m_stmt_set, 2, key.data(), key.size(), NULL),
"Internal error: failed to bind query at " __FILE__ ":" TOSTRING(__LINE__));
SQLOK(sqlite3_bind_blob(m_stmt_set, 3, value.data(), value.size(), NULL),
"Internal error: failed to bind query at " __FILE__ ":" TOSTRING(__LINE__));
blob_to_sqlite(m_stmt_set, 2, key);
blob_to_sqlite(m_stmt_set, 3, value);
SQLRES(sqlite3_step(m_stmt_set), SQLITE_DONE, "Failed to set mod entry")
sqlite3_reset(m_stmt_set);
@ -879,8 +938,7 @@ bool ModStorageDatabaseSQLite3::removeModEntry(const std::string &modname,
verifyDatabase();
str_to_sqlite(m_stmt_remove, 1, modname);
SQLOK(sqlite3_bind_blob(m_stmt_remove, 2, key.data(), key.size(), NULL),
"Internal error: failed to bind query at " __FILE__ ":" TOSTRING(__LINE__));
blob_to_sqlite(m_stmt_remove, 2, key);
sqlite3_vrfy(sqlite3_step(m_stmt_remove), SQLITE_DONE);
int changes = sqlite3_changes(m_database);
@ -906,6 +964,7 @@ void ModStorageDatabaseSQLite3::listMods(std::vector<std::string> *res)
{
verifyDatabase();
// FIXME: please don't do this. this should be sqlite3_step like all others.
char *errmsg;
int status = sqlite3_exec(m_database,
"SELECT `modname` FROM `entries` GROUP BY `modname`;",

View file

@ -13,27 +13,41 @@ extern "C" {
#include "sqlite3.h"
}
// Template class for SQLite3 based data storage
class Database_SQLite3 : public Database
{
public:
virtual ~Database_SQLite3();
void beginSave();
void endSave();
void beginSave() override;
void endSave() override;
bool initialized() const override { return m_initialized; }
/// @note not thread-safe
void verifyDatabase() override;
bool initialized() const { return m_initialized; }
protected:
Database_SQLite3(const std::string &savedir, const std::string &dbname);
// Open and initialize the database if needed
void verifyDatabase();
// Check if a specific table exists
bool checkTable(const char *table);
// Check if a table has a specific column
bool checkColumn(const char *table, const char *column);
/* Value conversion helpers */
// Convertors
inline void str_to_sqlite(sqlite3_stmt *s, int iCol, std::string_view str) const
{
sqlite3_vrfy(sqlite3_bind_text(s, iCol, str.data(), str.size(), NULL));
}
inline void blob_to_sqlite(sqlite3_stmt *s, int iCol, std::string_view str) const
{
sqlite3_vrfy(sqlite3_bind_blob(s, iCol, str.data(), str.size(), NULL));
}
inline void int_to_sqlite(sqlite3_stmt *s, int iCol, int val) const
{
sqlite3_vrfy(sqlite3_bind_int(s, iCol, val));
@ -104,12 +118,14 @@ protected:
sqlite_to_float(s, iCol + 2));
}
// Query verifiers helpers
// Helper for verifying result of sqlite3_step() and such
inline void sqlite3_vrfy(int s, std::string_view m = "", int r = SQLITE_OK) const
{
if (s != r) {
std::string msg(m);
msg.append(": ").append(sqlite3_errmsg(m_database));
if (!msg.empty())
msg.append(": ");
msg.append(sqlite3_errmsg(m_database));
throw DatabaseException(msg);
}
}
@ -119,28 +135,37 @@ protected:
sqlite3_vrfy(s, m, r);
}
// Create the database structure
// Called after opening a fresh database file. Should create tables and indices.
virtual void createDatabase() = 0;
// Should prepare the necessary statements.
virtual void initStatements() = 0;
sqlite3 *m_database = nullptr;
private:
// Open the database
void openDatabase();
bool m_initialized = false;
std::string m_savedir = "";
std::string m_dbname = "";
const std::string m_savedir;
const std::string m_dbname;
sqlite3_stmt *m_stmt_begin = nullptr;
sqlite3_stmt *m_stmt_end = nullptr;
s64 m_busy_handler_data[2];
u64 m_busy_handler_data[2];
static int busyHandler(void *data, int count);
};
// Not sure why why we have to do this. can't C++ figure it out on its own?
#define PARENT_CLASS_FUNCS \
void beginSave() { Database_SQLite3::beginSave(); } \
void endSave() { Database_SQLite3::endSave(); } \
void verifyDatabase() { Database_SQLite3::verifyDatabase(); }
class MapDatabaseSQLite3 : private Database_SQLite3, public MapDatabase
{
public:
@ -152,16 +177,19 @@ public:
bool deleteBlock(const v3s16 &pos);
void listAllLoadableBlocks(std::vector<v3s16> &dst);
void beginSave() { Database_SQLite3::beginSave(); }
void endSave() { Database_SQLite3::endSave(); }
PARENT_CLASS_FUNCS
protected:
virtual void createDatabase();
virtual void initStatements();
private:
void bindPos(sqlite3_stmt *stmt, const v3s16 &pos, int index = 1);
/// @brief Bind block position into statement at column index
/// @return index of next column after position
int bindPos(sqlite3_stmt *stmt, v3s16 pos, int index = 1);
bool m_new_format = false;
// Map
sqlite3_stmt *m_stmt_read = nullptr;
sqlite3_stmt *m_stmt_write = nullptr;
sqlite3_stmt *m_stmt_list = nullptr;
@ -179,6 +207,8 @@ public:
bool removePlayer(const std::string &name);
void listPlayers(std::vector<std::string> &res);
PARENT_CLASS_FUNCS
protected:
virtual void createDatabase();
virtual void initStatements();
@ -216,6 +246,8 @@ public:
virtual void listNames(std::vector<std::string> &res);
virtual void reload();
PARENT_CLASS_FUNCS
protected:
virtual void createDatabase();
virtual void initStatements();
@ -251,8 +283,7 @@ public:
virtual bool removeModEntries(const std::string &modname);
virtual void listMods(std::vector<std::string> *res);
virtual void beginSave() { Database_SQLite3::beginSave(); }
virtual void endSave() { Database_SQLite3::endSave(); }
PARENT_CLASS_FUNCS
protected:
virtual void createDatabase();
@ -267,3 +298,5 @@ private:
sqlite3_stmt *m_stmt_remove = nullptr;
sqlite3_stmt *m_stmt_remove_all = nullptr;
};
#undef PARENT_CLASS_FUNCS

View file

@ -7,48 +7,23 @@
/****************
* Black magic! *
****************
* The position hashing is very messed up.
* It's a lot more complicated than it looks.
* The position encoding is a bit messed up because negative
* values were not taken into account.
* But this also maps 0,0,0 to 0, which is nice, and we mostly
* need forward encoding in Luanti.
*/
static inline s16 unsigned_to_signed(u16 i, u16 max_positive)
{
if (i < max_positive) {
return i;
}
return i - (max_positive * 2);
}
// Modulo of a negative number does not work consistently in C
static inline s64 pythonmodulo(s64 i, s16 mod)
{
if (i >= 0) {
return i % mod;
}
return mod - ((-i) % mod);
}
s64 MapDatabase::getBlockAsInteger(const v3s16 &pos)
{
return (u64) pos.Z * 0x1000000 +
(u64) pos.Y * 0x1000 +
(u64) pos.X;
return ((s64) pos.Z << 24) + ((s64) pos.Y << 12) + pos.X;
}
v3s16 MapDatabase::getIntegerAsBlock(s64 i)
{
v3s16 pos;
pos.X = unsigned_to_signed(pythonmodulo(i, 4096), 2048);
i = (i - pos.X) / 4096;
pos.Y = unsigned_to_signed(pythonmodulo(i, 4096), 2048);
i = (i - pos.Y) / 4096;
pos.Z = unsigned_to_signed(pythonmodulo(i, 4096), 2048);
return pos;
// Offset so that all negative coordinates become non-negative
i = i + 0x800800800;
// Which is now easier to decode using simple bit masks:
return { (s16)( (i & 0xFFF) - 0x800),
(s16)(((i >> 12) & 0xFFF) - 0x800),
(s16)(((i >> 24) & 0xFFF) - 0x800) };
}

View file

@ -16,7 +16,12 @@ class Database
public:
virtual void beginSave() = 0;
virtual void endSave() = 0;
/// @return true if database connection is open
virtual bool initialized() const { return true; }
/// Open and initialize the database if needed
virtual void verifyDatabase() {};
};
class MapDatabase : public Database

View file

@ -82,6 +82,7 @@ void set_default_settings()
// Client
settings->setDefault("address", "");
settings->setDefault("remote_port", "30000");
#if defined(__unix__) && !defined(__APPLE__) && !defined (__ANDROID__)
// On Linux+X11 (not Linux+Wayland or Linux+XWayland), I've encountered a bug
// where fake mouse events were generated from touch events if in relative
@ -103,7 +104,7 @@ void set_default_settings()
settings->setDefault("sound_extensions_blacklist", "");
settings->setDefault("mesh_generation_interval", "0");
settings->setDefault("mesh_generation_threads", "0");
settings->setDefault("mesh_buffer_min_vertices", "100");
settings->setDefault("mesh_buffer_min_vertices", "300");
settings->setDefault("free_move", "false");
settings->setDefault("pitch_move", "false");
settings->setDefault("fast_move", "false");
@ -112,7 +113,7 @@ void set_default_settings()
settings->setDefault("screenshot_format", "png");
settings->setDefault("screenshot_quality", "0");
settings->setDefault("client_unload_unused_data_timeout", "600");
settings->setDefault("client_mapblock_limit", "7500");
settings->setDefault("client_mapblock_limit", "7500"); // about 120 MB
settings->setDefault("enable_build_where_you_stand", "false");
settings->setDefault("curl_timeout", "20000");
settings->setDefault("curl_parallel_limit", "8");
@ -128,65 +129,74 @@ void set_default_settings()
settings->setDefault("chat_weblink_color", "#8888FF");
// Keymap
settings->setDefault("remote_port", "30000");
settings->setDefault("keymap_forward", "KEY_KEY_W");
#if USE_SDL2
#define USEKEY2(name, value, _) settings->setDefault(name, value)
#else
#define USEKEY2(name, _, value) settings->setDefault(name, value)
#endif
USEKEY2("keymap_forward", "SYSTEM_SCANCODE_26", "KEY_KEY_W");
settings->setDefault("keymap_autoforward", "");
settings->setDefault("keymap_backward", "KEY_KEY_S");
settings->setDefault("keymap_left", "KEY_KEY_A");
settings->setDefault("keymap_right", "KEY_KEY_D");
settings->setDefault("keymap_jump", "KEY_SPACE");
settings->setDefault("keymap_sneak", "KEY_LSHIFT");
USEKEY2("keymap_backward", "SYSTEM_SCANCODE_22", "KEY_KEY_S");
USEKEY2("keymap_left", "SYSTEM_SCANCODE_4", "KEY_KEY_A");
USEKEY2("keymap_right", "SYSTEM_SCANCODE_7", "KEY_KEY_D");
USEKEY2("keymap_jump", "SYSTEM_SCANCODE_44", "KEY_SPACE");
#if !USE_SDL2 && defined(__MACH__) && defined(__APPLE__)
// Altered settings for CIrrDeviceOSX
settings->setDefault("keymap_sneak", "KEY_SHIFT");
#else
USEKEY2("keymap_sneak", "SYSTEM_SCANCODE_225", "KEY_LSHIFT");
#endif
settings->setDefault("keymap_dig", "KEY_LBUTTON");
settings->setDefault("keymap_place", "KEY_RBUTTON");
settings->setDefault("keymap_drop", "KEY_KEY_Q");
settings->setDefault("keymap_zoom", "KEY_KEY_Z");
settings->setDefault("keymap_inventory", "KEY_KEY_I");
settings->setDefault("keymap_aux1", "KEY_KEY_E");
settings->setDefault("keymap_chat", "KEY_KEY_T");
settings->setDefault("keymap_cmd", "/");
settings->setDefault("keymap_cmd_local", ".");
settings->setDefault("keymap_minimap", "KEY_KEY_V");
settings->setDefault("keymap_console", "KEY_F10");
USEKEY2("keymap_drop", "SYSTEM_SCANCODE_20", "KEY_KEY_Q");
USEKEY2("keymap_zoom", "SYSTEM_SCANCODE_29", "KEY_KEY_Z");
USEKEY2("keymap_inventory", "SYSTEM_SCANCODE_12", "KEY_KEY_I");
USEKEY2("keymap_aux1", "SYSTEM_SCANCODE_8", "KEY_KEY_E");
USEKEY2("keymap_chat", "SYSTEM_SCANCODE_23", "KEY_KEY_T");
USEKEY2("keymap_cmd", "SYSTEM_SCANCODE_56", "/");
USEKEY2("keymap_cmd_local", "SYSTEM_SCANCODE_55", ".");
USEKEY2("keymap_minimap", "SYSTEM_SCANCODE_25", "KEY_KEY_V");
USEKEY2("keymap_console", "SYSTEM_SCANCODE_67", "KEY_F10");
// See https://github.com/minetest/minetest/issues/12792
settings->setDefault("keymap_rangeselect", has_touch ? "KEY_KEY_R" : "");
// see <https://github.com/luanti-org/luanti/issues/12792>
USEKEY2("keymap_rangeselect", has_touch ? "SYSTEM_SCANCODE_21" : "", has_touch ? "KEY_KEY_R" : "");
settings->setDefault("keymap_freemove", "KEY_KEY_K");
USEKEY2("keymap_freemove", "SYSTEM_SCANCODE_14", "KEY_KEY_K");
settings->setDefault("keymap_pitchmove", "");
settings->setDefault("keymap_fastmove", "KEY_KEY_J");
settings->setDefault("keymap_noclip", "KEY_KEY_H");
settings->setDefault("keymap_hotbar_next", "KEY_KEY_N");
settings->setDefault("keymap_hotbar_previous", "KEY_KEY_B");
settings->setDefault("keymap_mute", "KEY_KEY_M");
USEKEY2("keymap_fastmove", "SYSTEM_SCANCODE_13", "KEY_KEY_J");
USEKEY2("keymap_noclip", "SYSTEM_SCANCODE_11", "KEY_KEY_H");
USEKEY2("keymap_hotbar_next", "SYSTEM_SCANCODE_17", "KEY_KEY_N");
USEKEY2("keymap_hotbar_previous", "SYSTEM_SCANCODE_5", "KEY_KEY_B");
USEKEY2("keymap_mute", "SYSTEM_SCANCODE_16", "KEY_KEY_M");
settings->setDefault("keymap_increase_volume", "");
settings->setDefault("keymap_decrease_volume", "");
settings->setDefault("keymap_cinematic", "");
settings->setDefault("keymap_toggle_block_bounds", "");
settings->setDefault("keymap_toggle_hud", "KEY_F1");
settings->setDefault("keymap_toggle_chat", "KEY_F2");
settings->setDefault("keymap_toggle_fog", "KEY_F3");
USEKEY2("keymap_toggle_hud", "SYSTEM_SCANCODE_58", "KEY_F1");
USEKEY2("keymap_toggle_chat", "SYSTEM_SCANCODE_59", "KEY_F2");
USEKEY2("keymap_toggle_fog", "SYSTEM_SCANCODE_60", "KEY_F3");
#ifndef NDEBUG
settings->setDefault("keymap_toggle_update_camera", "KEY_F4");
USEKEY2("keymap_toggle_update_camera", "SYSTEM_SCANCODE_61", "KEY_F4");
#else
settings->setDefault("keymap_toggle_update_camera", "");
#endif
settings->setDefault("keymap_toggle_debug", "KEY_F5");
settings->setDefault("keymap_toggle_profiler", "KEY_F6");
settings->setDefault("keymap_camera_mode", "KEY_KEY_C");
settings->setDefault("keymap_screenshot", "KEY_F12");
settings->setDefault("keymap_fullscreen", "KEY_F11");
settings->setDefault("keymap_increase_viewing_range_min", "+");
settings->setDefault("keymap_decrease_viewing_range_min", "-");
settings->setDefault("keymap_slot1", "KEY_KEY_1");
settings->setDefault("keymap_slot2", "KEY_KEY_2");
settings->setDefault("keymap_slot3", "KEY_KEY_3");
settings->setDefault("keymap_slot4", "KEY_KEY_4");
settings->setDefault("keymap_slot5", "KEY_KEY_5");
settings->setDefault("keymap_slot6", "KEY_KEY_6");
settings->setDefault("keymap_slot7", "KEY_KEY_7");
settings->setDefault("keymap_slot8", "KEY_KEY_8");
settings->setDefault("keymap_slot9", "KEY_KEY_9");
settings->setDefault("keymap_slot10", "KEY_KEY_0");
USEKEY2("keymap_toggle_debug", "SYSTEM_SCANCODE_62", "KEY_F5");
USEKEY2("keymap_toggle_profiler", "SYSTEM_SCANCODE_63", "KEY_F6");
USEKEY2("keymap_camera_mode", "SYSTEM_SCANCODE_6", "KEY_KEY_C");
USEKEY2("keymap_screenshot", "SYSTEM_SCANCODE_69", "KEY_F12");
USEKEY2("keymap_fullscreen", "SYSTEM_SCANCODE_68", "KEY_F11");
USEKEY2("keymap_increase_viewing_range_min", "SYSTEM_SCANCODE_46", "+");
USEKEY2("keymap_decrease_viewing_range_min", "SYSTEM_SCANCODE_45", "-");
USEKEY2("keymap_slot1", "SYSTEM_SCANCODE_30", "KEY_KEY_1");
USEKEY2("keymap_slot2", "SYSTEM_SCANCODE_31", "KEY_KEY_2");
USEKEY2("keymap_slot3", "SYSTEM_SCANCODE_32", "KEY_KEY_3");
USEKEY2("keymap_slot4", "SYSTEM_SCANCODE_33", "KEY_KEY_4");
USEKEY2("keymap_slot5", "SYSTEM_SCANCODE_34", "KEY_KEY_5");
USEKEY2("keymap_slot6", "SYSTEM_SCANCODE_35", "KEY_KEY_6");
USEKEY2("keymap_slot7", "SYSTEM_SCANCODE_36", "KEY_KEY_7");
USEKEY2("keymap_slot8", "SYSTEM_SCANCODE_37", "KEY_KEY_8");
USEKEY2("keymap_slot9", "SYSTEM_SCANCODE_38", "KEY_KEY_9");
USEKEY2("keymap_slot10", "SYSTEM_SCANCODE_39", "KEY_KEY_0");
settings->setDefault("keymap_slot11", "");
settings->setDefault("keymap_slot12", "");
settings->setDefault("keymap_slot13", "");
@ -212,16 +222,17 @@ void set_default_settings()
#ifndef NDEBUG
// Default keybinds for quicktune in debug builds
settings->setDefault("keymap_quicktune_prev", "KEY_HOME");
settings->setDefault("keymap_quicktune_next", "KEY_END");
settings->setDefault("keymap_quicktune_dec", "KEY_NEXT");
settings->setDefault("keymap_quicktune_inc", "KEY_PRIOR");
USEKEY2("keymap_quicktune_prev", "SYSTEM_SCANCODE_74", "KEY_HOME");
USEKEY2("keymap_quicktune_next", "SYSTEM_SCANCODE_77", "KEY_END");
USEKEY2("keymap_quicktune_dec", "SYSTEM_SCANCODE_81", "KEY_NEXT");
USEKEY2("keymap_quicktune_inc", "SYSTEM_SCANCODE_82", "KEY_PRIOR");
#else
settings->setDefault("keymap_quicktune_prev", "");
settings->setDefault("keymap_quicktune_next", "");
settings->setDefault("keymap_quicktune_dec", "");
settings->setDefault("keymap_quicktune_inc", "");
#endif
#undef USEKEY2
// Visuals
#ifdef NDEBUG
@ -243,7 +254,7 @@ void set_default_settings()
settings->setDefault("tooltip_show_delay", "400");
settings->setDefault("tooltip_append_itemname", "false");
settings->setDefault("fps_max", "60");
settings->setDefault("fps_max_unfocused", "20");
settings->setDefault("fps_max_unfocused", "10");
settings->setDefault("viewing_range", "190");
settings->setDefault("client_mesh_chunk", "1");
settings->setDefault("screen_w", "1024");
@ -270,7 +281,6 @@ void set_default_settings()
settings->setDefault("camera_smoothing", "0.0");
settings->setDefault("cinematic_camera_smoothing", "0.7");
settings->setDefault("view_bobbing_amount", "1.0");
settings->setDefault("fall_bobbing_amount", "0.03");
settings->setDefault("enable_3d_clouds", "true");
settings->setDefault("soft_clouds", "false");
settings->setDefault("cloud_radius", "12");
@ -307,7 +317,7 @@ void set_default_settings()
// Effects
settings->setDefault("enable_post_processing", "true");
settings->setDefault("post_processing_texture_bits", "10");
settings->setDefault("post_processing_texture_bits", "16");
settings->setDefault("directional_colored_fog", "true");
settings->setDefault("inventory_items_animations", "false");
settings->setDefault("mip_map", "false");
@ -362,6 +372,8 @@ void set_default_settings()
settings->setDefault("aux1_descends", "false");
settings->setDefault("doubletap_jump", "false");
settings->setDefault("always_fly_fast", "true");
settings->setDefault("toggle_sneak_key", "false");
settings->setDefault("toggle_aux1_key", "false");
settings->setDefault("autojump", bool_to_cstr(has_touch));
settings->setDefault("continuous_forward", "false");
settings->setDefault("enable_joysticks", "false");
@ -409,7 +421,7 @@ void set_default_settings()
#endif
#if ENABLE_UPDATE_CHECKER
settings->setDefault("update_information_url", "https://www.minetest.net/release_info.json");
settings->setDefault("update_information_url", "https://www.luanti.org/release_info.json");
#else
settings->setDefault("update_information_url", "");
#endif
@ -427,7 +439,7 @@ void set_default_settings()
// Network
settings->setDefault("enable_ipv6", "true");
settings->setDefault("ipv6_server", "false");
settings->setDefault("ipv6_server", "true");
settings->setDefault("max_packets_per_iteration", "1024");
settings->setDefault("port", "30000");
settings->setDefault("strict_protocol_version_checking", "false");
@ -538,20 +550,16 @@ void set_default_settings()
settings->setDefault("display_density_factor", "1");
settings->setDefault("dpi_change_notifier", "0");
// Altered settings for CIrrDeviceOSX
#if !USE_SDL2 && defined(__MACH__) && defined(__APPLE__)
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");
settings->setDefault("touch_use_crosshair", "false");
settings->setDefault("fixed_virtual_joystick", "false");
settings->setDefault("virtual_joystick_triggers_aux1", "false");
settings->setDefault("touch_interaction_style", "tap");
settings->setDefault("touch_punch_gesture", "short_tap");
settings->setDefault("clickable_chat_weblinks", "true");
// Altered settings for Android
#ifdef __ANDROID__
settings->setDefault("screen_w", "0");
@ -563,9 +571,9 @@ void set_default_settings()
settings->setDefault("max_block_generate_distance", "5");
settings->setDefault("sqlite_synchronous", "1");
settings->setDefault("server_map_save_interval", "15");
settings->setDefault("client_mapblock_limit", "1000");
settings->setDefault("client_mapblock_limit", "1500");
settings->setDefault("active_block_range", "2");
settings->setDefault("viewing_range", "50");
settings->setDefault("viewing_range", "70");
settings->setDefault("leaves_style", "simple");
// Note: OpenGL ES 2.0 is not guaranteed to provide depth textures,
// which we would need for PP.
@ -573,6 +581,7 @@ void set_default_settings()
// still set these two settings in case someone wants to enable it
settings->setDefault("debanding", "false");
settings->setDefault("post_processing_texture_bits", "8");
// We don't have working certificate verification...
settings->setDefault("curl_verify_cert", "false");
// Apply settings according to screen size

View file

@ -106,9 +106,9 @@ EmergeManager::EmergeManager(Server *server, MetricsBackend *mb)
m_qlimit_generate = nthreads + 1;
// don't trust user input for something very important like this
m_qlimit_total = rangelim(m_qlimit_total, 1, 1000000);
m_qlimit_diskonly = rangelim(m_qlimit_diskonly, 1, 1000000);
m_qlimit_diskonly = rangelim(m_qlimit_diskonly, 2, 1000000);
m_qlimit_generate = rangelim(m_qlimit_generate, 1, 1000000);
m_qlimit_total = std::max(m_qlimit_total, std::max(m_qlimit_diskonly, m_qlimit_generate));
for (s16 i = 0; i < nthreads; i++)
m_threads.push_back(new EmergeThread(server, i));
@ -375,8 +375,7 @@ bool EmergeManager::pushBlockEmergeData(
}
}
std::pair<std::map<v3s16, BlockEmergeData>::iterator, bool> findres;
findres = m_blocks_enqueued.insert(std::make_pair(pos, BlockEmergeData()));
auto findres = m_blocks_enqueued.insert(std::make_pair(pos, BlockEmergeData()));
BlockEmergeData &bedata = findres.first->second;
*entry_already_exists = !findres.second;
@ -582,7 +581,8 @@ MapBlock *EmergeThread::finishGen(v3s16 pos, BlockMakeData *bmdata,
Perform post-processing on blocks (invalidate lighting, queue liquid
transforms, etc.) to finish block make
*/
m_map->finishBlockMake(bmdata, modified_blocks);
m_map->finishBlockMake(bmdata, modified_blocks,
m_server->m_env->getGameTime());
MapBlock *block = m_map->getBlockNoCreateNoEx(pos);
if (!block) {
@ -620,11 +620,6 @@ MapBlock *EmergeThread::finishGen(v3s16 pos, BlockMakeData *bmdata,
m_mapgen->gennotify.clearEvents();
m_mapgen->vm = nullptr;
/*
Activate the block
*/
m_server->m_env->activateBlock(block, 0);
return block;
}
@ -707,6 +702,8 @@ void *EmergeThread::run()
{
ScopeProfiler sp(g_profiler, "EmergeThread: load block - async (sum)");
MutexAutoLock dblock(m_db.mutex);
// Note: this can throw an exception, but there isn't really
// a good, safe way to handle it.
m_db.loadBlock(pos, databuf);
}
// actually load it, then decide again

View file

@ -17,11 +17,6 @@ Environment::Environment(IGameDef *gamedef):
m_day_count(0),
m_gamedef(gamedef)
{
m_cache_active_block_mgmt_interval = g_settings->getFloat("active_block_mgmt_interval");
m_cache_abm_interval = g_settings->getFloat("abm_interval");
m_cache_nodetimer_interval = g_settings->getFloat("nodetimer_interval");
m_cache_abm_time_budget = g_settings->getFloat("abm_time_budget");
m_time_of_day = g_settings->getU32("world_start_time");
m_time_of_day_f = (float)m_time_of_day / 24000.0f;
}

View file

@ -122,20 +122,6 @@ protected:
* Above: values managed by m_time_lock
*/
/* TODO: Add a callback function so these can be updated when a setting
* changes. At this point in time it doesn't matter (e.g. /set
* is documented to change server settings only)
*
* TODO: Local caching of settings is not optimal and should at some stage
* be updated to use a global settings object for getting thse values
* (as opposed to the this local caching). This can be addressed in
* a later release.
*/
float m_cache_active_block_mgmt_interval;
float m_cache_abm_interval;
float m_cache_nodetimer_interval;
float m_cache_abm_time_budget;
IGameDef *m_gamedef;
private:

View file

@ -13,7 +13,7 @@ std::mutex FacePositionCache::cache_mutex;
const std::vector<v3s16> &FacePositionCache::getFacePositions(u16 d)
{
MutexAutoLock lock(cache_mutex);
std::unordered_map<u16, std::vector<v3s16>>::const_iterator it = cache.find(d);
auto it = cache.find(d);
if (it != cache.end())
return it->second;

View file

@ -104,7 +104,7 @@ std::vector<DirListNode> GetDirListing(const std::string &pathstring)
<< " Error is " << dwError << std::endl;
listing.clear();
return listing;
}
}
}
return listing;
}
@ -136,20 +136,23 @@ bool IsDir(const std::string &path)
(attr & FILE_ATTRIBUTE_DIRECTORY));
}
bool IsFile(const std::string &path)
{
DWORD attr = GetFileAttributes(path.c_str());
return (attr != INVALID_FILE_ATTRIBUTES &&
!(attr & FILE_ATTRIBUTE_DIRECTORY));
}
bool IsExecutable(const std::string &path)
{
DWORD type;
return GetBinaryType(path.c_str(), &type) != 0;
}
bool IsDirDelimiter(char c)
{
return c == '/' || c == '\\';
}
bool RecursiveDelete(const std::string &path)
{
infostream << "Recursively deleting \"" << path << "\"" << std::endl;
assert(IsPathAbsolute(path));
if (!IsDir(path)) {
infostream << "RecursiveDelete: Deleting file " << path << std::endl;
if (!DeleteFile(path.c_str())) {
@ -181,19 +184,9 @@ bool RecursiveDelete(const std::string &path)
bool DeleteSingleFileOrEmptyDirectory(const std::string &path)
{
DWORD attr = GetFileAttributes(path.c_str());
bool is_directory = (attr != INVALID_FILE_ATTRIBUTES &&
(attr & FILE_ATTRIBUTE_DIRECTORY));
if(!is_directory)
{
bool did = DeleteFile(path.c_str());
return did;
}
else
{
bool did = RemoveDirectory(path.c_str());
return did;
}
if (!IsDir(path))
return DeleteFile(path.c_str());
return RemoveDirectory(path.c_str());
}
std::string TempPath()
@ -336,8 +329,7 @@ bool CreateDir(const std::string &path)
bool PathExists(const std::string &path)
{
struct stat st{};
return (stat(path.c_str(),&st) == 0);
return access(path.c_str(), F_OK) == 0;
}
bool IsPathAbsolute(const std::string &path)
@ -348,21 +340,29 @@ bool IsPathAbsolute(const std::string &path)
bool IsDir(const std::string &path)
{
struct stat statbuf{};
if(stat(path.c_str(), &statbuf))
if (stat(path.c_str(), &statbuf))
return false; // Actually error; but certainly not a directory
return ((statbuf.st_mode & S_IFDIR) == S_IFDIR);
}
bool IsFile(const std::string &path)
{
struct stat statbuf{};
if (stat(path.c_str(), &statbuf))
return false;
#ifdef S_IFSOCK
// sockets cannot be opened in any way, so they are not files.
if ((statbuf.st_mode & S_IFSOCK) == S_IFSOCK)
return false;
#endif
return ((statbuf.st_mode & S_IFDIR) != S_IFDIR);
}
bool IsExecutable(const std::string &path)
{
return access(path.c_str(), X_OK) == 0;
}
bool IsDirDelimiter(char c)
{
return c == '/';
}
bool RecursiveDelete(const std::string &path)
{
/*
@ -506,15 +506,10 @@ bool CopyFileContents(const std::string &source, const std::string &target)
// fallback to normal copy, but no need to reopen the files
sourcefile.reset(fdopen(srcfd, "rb"));
targetfile.reset(fdopen(tgtfd, "wb"));
goto fallback;
#endif
#else
sourcefile.reset(fopen(source.c_str(), "rb"));
targetfile.reset(fopen(target.c_str(), "wb"));
fallback:
#endif
if (!sourcefile) {
errorstream << source << ": can't open for reading: "
<< strerror(errno) << std::endl;
@ -715,7 +710,7 @@ bool PathStartsWith(const std::string &path, const std::string &prefix)
if(prefixpos == prefixsize)
return true;
// Return false if path has ended (at delimiter/EOS)
// while prefix did not.
// while prefix did not.
if(pathpos == pathsize)
return false;
}
@ -882,7 +877,7 @@ const char *GetFilenameFromPath(const char *path)
{
const char *filename = strrchr(path, DIR_DELIM_CHAR);
// Consistent with IsDirDelimiter this function handles '/' too
if (DIR_DELIM_CHAR != '/') {
if constexpr (DIR_DELIM_CHAR != '/') {
const char *tmp = strrchr(path, '/');
if (tmp && tmp > filename)
filename = tmp;

View file

@ -36,28 +36,28 @@ struct DirListNode
bool dir;
};
[[nodiscard]]
std::vector<DirListNode> GetDirListing(const std::string &path);
// Returns true if already exists
bool CreateDir(const std::string &path);
bool PathExists(const std::string &path);
[[nodiscard]] bool PathExists(const std::string &path);
bool IsPathAbsolute(const std::string &path);
[[nodiscard]] bool IsPathAbsolute(const std::string &path);
bool IsDir(const std::string &path);
[[nodiscard]] bool IsDir(const std::string &path);
bool IsExecutable(const std::string &path);
[[nodiscard]] bool IsExecutable(const std::string &path);
inline bool IsFile(const std::string &path)
[[nodiscard]] bool IsFile(const std::string &path);
[[nodiscard]] inline bool IsDirDelimiter(char c)
{
return PathExists(path) && !IsDir(path);
return c == '/' || c == DIR_DELIM_CHAR;
}
bool IsDirDelimiter(char c);
// Only pass full paths to this one. True on success.
// NOTE: The WIN32 version returns always true.
// Only pass full paths to this one. returns true on success.
bool RecursiveDelete(const std::string &path);
bool DeleteSingleFileOrEmptyDirectory(const std::string &path);
@ -65,20 +65,21 @@ bool DeleteSingleFileOrEmptyDirectory(const std::string &path);
/// Returns path to temp directory.
/// You probably don't want to use this directly, see `CreateTempFile` or `CreateTempDir`.
/// @return path or "" on error
std::string TempPath();
[[nodiscard]] std::string TempPath();
/// Returns path to securely-created temporary file (will already exist when this function returns).
/// @return path or "" on error
std::string CreateTempFile();
[[nodiscard]] std::string CreateTempFile();
/// Returns path to securely-created temporary directory (will already exist when this function returns).
/// @return path or "" on error
std::string CreateTempDir();
[[nodiscard]] std::string CreateTempDir();
/* Returns a list of subdirectories, including the path itself, but excluding
hidden directories (whose names start with . or _)
*/
void GetRecursiveDirs(std::vector<std::string> &dirs, const std::string &dir);
[[nodiscard]]
std::vector<std::string> GetRecursiveDirs(const std::string &dir);
/* Multiplatform */
@ -129,16 +130,19 @@ std::string RemoveRelativePathComponents(std::string path);
// Returns the absolute path for the passed path, with "." and ".." path
// components and symlinks removed. Returns "" on error.
[[nodiscard]]
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.
[[nodiscard]]
std::string AbsolutePathPartial(const std::string &path);
// Returns the filename from a path or the entire path if no directory
// delimiter is found.
[[nodiscard]]
const char *GetFilenameFromPath(const char *path);
// Replace the content of a file on disk in a way that is safe from
@ -181,6 +185,7 @@ bool OpenStream(std::filebuf &stream, const char *filename,
* @param mode additional mode bits (e.g. std::ios::app)
* @return file stream, will be !good in case of error
*/
[[nodiscard]]
inline std::ofstream open_ofstream(const char *name, bool log,
std::ios::openmode mode = std::ios::openmode())
{
@ -203,6 +208,7 @@ inline std::ofstream open_ofstream(const char *name, bool log,
* @param mode additional mode bits (e.g. std::ios::ate)
* @return file stream, will be !good in case of error
*/
[[nodiscard]]
inline std::ifstream open_ifstream(const char *name, bool log,
std::ios::openmode mode = std::ios::openmode())
{

View file

@ -25,6 +25,7 @@ enum class ELoginRegister {
};
// Information processed by main menu
// TODO: unify with MainMenuData
struct GameStartData : GameParams
{
GameStartData() = default;
@ -33,7 +34,11 @@ struct GameStartData : GameParams
std::string name;
std::string password;
// If empty, we're hosting a server.
// This may or may not be in "simple singleplayer mode".
std::string address;
// If true, we're hosting a server and are *not* in "simple singleplayer
// mode".
bool local_server;
ELoginRegister allow_login_or_register = ELoginRegister::Any;

View file

@ -171,8 +171,13 @@ void init_gettext(const char *path, const std::string &configured_language,
#if CHECK_CLIENT_BUILD()
// Hack to force gettext to see the right environment
if (current_language != configured_language)
MSVC_LocaleWorkaround(argc, argv);
if (current_language != configured_language) {
// Disabled when debugger is present as it can break debugging
if (!IsDebuggerPresent())
MSVC_LocaleWorkaround(argc, argv);
else
actionstream << "Debugger detected. Skipping MSVC_LocaleWorkaround." << std::endl;
}
#else
errorstream << "*******************************************************" << std::endl;
errorstream << "Can't apply locale workaround for server!" << std::endl;

View file

@ -59,6 +59,7 @@ inline std::wstring wstrgettext(const std::string &str)
* @tparam Args Template parameter for format args
* @param src Translation source string
* @param args Variable format args
* @warning No dynamic sizing! string will be cut off if longer than 255 chars.
* @return translated string
*/
template <typename ...Args>
@ -81,14 +82,19 @@ inline std::wstring fwgettext(const char *src, Args&&... args)
template <typename ...Args>
inline std::string fmtgettext(const char *format, Args&&... args)
{
std::string buf;
std::size_t buf_size = 256;
buf.resize(buf_size);
format = gettext(format);
int len = porting::mt_snprintf(&buf[0], buf_size, format, std::forward<Args>(args)...);
if (len <= 0) throw std::runtime_error("gettext format error: " + std::string(format));
std::string buf;
{
size_t default_size = strlen(format);
if (default_size < 256)
default_size = 256;
buf.resize(default_size);
}
int len = porting::mt_snprintf(&buf[0], buf.size(), format, std::forward<Args>(args)...);
if (len <= 0)
throw std::runtime_error("gettext format error: " + std::string(format));
if ((size_t)len >= buf.size()) {
buf.resize(len+1); // extra null byte
porting::mt_snprintf(&buf[0], buf.size(), format, std::forward<Args>(args)...);

View file

@ -27,5 +27,6 @@ set(gui_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/touchcontrols.cpp
${CMAKE_CURRENT_SOURCE_DIR}/touchscreenlayout.cpp
${CMAKE_CURRENT_SOURCE_DIR}/touchscreeneditor.cpp
${CMAKE_CURRENT_SOURCE_DIR}/drawItemStack.cpp
PARENT_SCOPE
)

View file

@ -316,6 +316,8 @@ public:
spec.bold = true;
else if (modes[i] == "italic")
spec.italic = true;
else if (modes[i] == "_no_server_media") // for internal use only
spec.allow_server_media = false;
}
if (!size.empty()) {

314
src/gui/drawItemStack.cpp Normal file
View file

@ -0,0 +1,314 @@
// Luanti
// SPDX-License-Identifier: LGPL-2.1-or-later
// Copyright (C) 2024 cx384
#include "drawItemStack.h"
#include <string>
#include "settings.h"
#include "client/client.h"
#include "porting.h"
#include "inventory.h"
#include "client/mesh.h"
#include "client/wieldmesh.h"
#include "client/texturesource.h"
#include "client/guiscalingfilter.h"
#include "client/item_visuals_manager.h"
struct MeshTimeInfo {
u64 time;
scene::IMesh *mesh = nullptr;
};
void drawItemStack(
video::IVideoDriver *driver,
gui::IGUIFont *font,
const ItemStack &item,
const core::rect<s32> &rect,
const core::rect<s32> *clip,
Client *client,
ItemRotationKind rotation_kind,
const v3s16 &angle,
const v3s16 &rotation_speed)
{
static MeshTimeInfo rotation_time_infos[IT_ROT_NONE];
if (item.empty()) {
if (rotation_kind < IT_ROT_NONE && rotation_kind != IT_ROT_OTHER) {
rotation_time_infos[rotation_kind].mesh = NULL;
}
return;
}
const bool enable_animations = g_settings->getBool("inventory_items_animations");
auto *idef = client->idef();
const ItemDefinition &def = item.getDefinition(idef);
ItemVisualsManager* item_visuals = client->getItemVisualsManager();
bool draw_overlay = false;
const std::string inventory_image = item.getInventoryImage(idef);
const std::string inventory_overlay = item.getInventoryOverlay(idef);
bool has_mesh = false;
ItemMesh *imesh;
core::rect<s32> viewrect = rect;
if (clip != nullptr)
viewrect.clipAgainst(*clip);
// Render as mesh if animated or no inventory image
if ((enable_animations && rotation_kind < IT_ROT_NONE) || inventory_image.empty()) {
imesh = item_visuals->getWieldMesh(item, client);
has_mesh = imesh && imesh->mesh;
}
if (has_mesh) {
scene::IMesh *mesh = imesh->mesh;
driver->clearBuffers(video::ECBF_DEPTH);
s32 delta = 0;
if (rotation_kind < IT_ROT_NONE) {
MeshTimeInfo &ti = rotation_time_infos[rotation_kind];
if (mesh != ti.mesh && rotation_kind != IT_ROT_OTHER) {
ti.mesh = mesh;
ti.time = porting::getTimeMs();
} else {
delta = porting::getDeltaMs(ti.time, porting::getTimeMs()) % 100000;
}
}
core::rect<s32> oldViewPort = driver->getViewPort();
core::matrix4 oldProjMat = driver->getTransform(video::ETS_PROJECTION);
core::matrix4 oldViewMat = driver->getTransform(video::ETS_VIEW);
core::matrix4 ProjMatrix;
ProjMatrix.buildProjectionMatrixOrthoLH(2.0f, 2.0f, -1.0f, 100.0f);
core::matrix4 ViewMatrix;
ViewMatrix.buildProjectionMatrixOrthoLH(
2.0f * viewrect.getWidth() / rect.getWidth(),
2.0f * viewrect.getHeight() / rect.getHeight(),
-1.0f,
100.0f);
ViewMatrix.setTranslation(core::vector3df(
1.0f * (rect.LowerRightCorner.X + rect.UpperLeftCorner.X -
viewrect.LowerRightCorner.X - viewrect.UpperLeftCorner.X) /
viewrect.getWidth(),
1.0f * (viewrect.LowerRightCorner.Y + viewrect.UpperLeftCorner.Y -
rect.LowerRightCorner.Y - rect.UpperLeftCorner.Y) /
viewrect.getHeight(),
0.0f));
driver->setTransform(video::ETS_PROJECTION, ProjMatrix);
driver->setTransform(video::ETS_VIEW, ViewMatrix);
core::matrix4 matrix;
matrix.makeIdentity();
if (enable_animations) {
float timer_f = (float) delta / 5000.f;
matrix.setRotationDegrees(v3f(
angle.X + rotation_speed.X * 3.60f * timer_f,
angle.Y + rotation_speed.Y * 3.60f * timer_f,
angle.Z + rotation_speed.Z * 3.60f * timer_f)
);
}
driver->setTransform(video::ETS_WORLD, matrix);
driver->setViewPort(viewrect);
video::SColor basecolor = item_visuals->getItemstackColor(item, client);
const u32 mc = mesh->getMeshBufferCount();
if (mc > imesh->buffer_info.size())
imesh->buffer_info.resize(mc);
for (u32 j = 0; j < mc; ++j) {
scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
video::SColor c = basecolor;
auto &p = imesh->buffer_info[j];
p.applyOverride(c);
// TODO: could be moved to a shader
if (p.needColorize(c)) {
buf->setDirty(scene::EBT_VERTEX);
if (imesh->needs_shading)
colorizeMeshBuffer(buf, &c);
else
setMeshBufferColor(buf, c);
}
video::SMaterial &material = buf->getMaterial();
// Texture animation
if (p.animation_info) {
p.animation_info->updateTexture(material, client->getAnimationTime());
}
material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
driver->setMaterial(material);
driver->drawMeshBuffer(buf);
}
driver->setTransform(video::ETS_VIEW, oldViewMat);
driver->setTransform(video::ETS_PROJECTION, oldProjMat);
driver->setViewPort(oldViewPort);
draw_overlay = def.type == ITEM_NODE && inventory_image.empty();
} else { // Otherwise just draw as 2D
video::ITexture *texture = item_visuals->getInventoryTexture(item, client);
video::SColor color;
if (texture) {
color = item_visuals->getItemstackColor(item, client);
} else {
color = video::SColor(255, 255, 255, 255);
ITextureSource *tsrc = client->getTextureSource();
texture = tsrc->getTexture("no_texture.png");
if (!texture)
return;
}
const video::SColor colors[] = { color, color, color, color };
draw2DImageFilterScaled(driver, texture, rect,
core::rect<s32>({0, 0}, core::dimension2di(texture->getOriginalSize())),
clip, colors, true);
draw_overlay = true;
}
// draw the inventory_overlay
if (!inventory_overlay.empty() && draw_overlay) {
ITextureSource *tsrc = client->getTextureSource();
video::ITexture *overlay_texture = tsrc->getTexture(inventory_overlay);
core::dimension2d<u32> dimens = overlay_texture->getOriginalSize();
core::rect<s32> srcrect(0, 0, dimens.Width, dimens.Height);
draw2DImageFilterScaled(driver, overlay_texture, rect, srcrect, clip, 0, true);
}
if (def.type == ITEM_TOOL && item.wear != 0) {
// Draw a progressbar
float barheight = static_cast<float>(rect.getHeight()) / 16;
float barpad_x = static_cast<float>(rect.getWidth()) / 16;
float barpad_y = static_cast<float>(rect.getHeight()) / 16;
core::rect<s32> progressrect(
rect.UpperLeftCorner.X + barpad_x,
rect.LowerRightCorner.Y - barpad_y - barheight,
rect.LowerRightCorner.X - barpad_x,
rect.LowerRightCorner.Y - barpad_y);
// Shrink progressrect by amount of tool damage
float wear = item.wear / 65535.0f;
int progressmid =
wear * progressrect.UpperLeftCorner.X +
(1 - wear) * progressrect.LowerRightCorner.X;
// Compute progressbar color
// default scheme:
// wear = 0.0: green
// wear = 0.5: yellow
// wear = 1.0: red
video::SColor color;
auto barParams = item.getWearBarParams(client->idef());
if (barParams.has_value()) {
f32 durabilityPercent = 1.0 - wear;
color = barParams->getWearBarColor(durabilityPercent);
} else {
color = video::SColor(255, 255, 255, 255);
int wear_i = MYMIN(std::floor(wear * 600), 511);
wear_i = MYMIN(wear_i + 10, 511);
if (wear_i <= 255)
color.set(255, wear_i, 255, 0);
else
color.set(255, 255, 511 - wear_i, 0);
}
core::rect<s32> progressrect2 = progressrect;
progressrect2.LowerRightCorner.X = progressmid;
driver->draw2DRectangle(color, progressrect2, clip);
color = video::SColor(255, 0, 0, 0);
progressrect2 = progressrect;
progressrect2.UpperLeftCorner.X = progressmid;
driver->draw2DRectangle(color, progressrect2, clip);
}
const std::string &count_text = item.metadata.getString("count_meta");
if (font != nullptr && (item.count >= 2 || !count_text.empty())) {
// Get the item count as a string
std::string text = count_text.empty() ? itos(item.count) : count_text;
v2u32 dim = font->getDimension(utf8_to_wide(unescape_enriched(text)).c_str());
v2s32 sdim(dim.X, dim.Y);
core::rect<s32> rect2(
rect.LowerRightCorner - sdim,
rect.LowerRightCorner
);
// get the count alignment
s32 count_alignment = stoi(item.metadata.getString("count_alignment"));
if (count_alignment != 0) {
s32 a_x = count_alignment & 3;
s32 a_y = (count_alignment >> 2) & 3;
s32 x1, x2, y1, y2;
switch (a_x) {
case 1: // left
x1 = rect.UpperLeftCorner.X;
x2 = x1 + sdim.X;
break;
case 2: // middle
x1 = (rect.UpperLeftCorner.X + rect.LowerRightCorner.X - sdim.X) / 2;
x2 = x1 + sdim.X;
break;
case 3: // right
x2 = rect.LowerRightCorner.X;
x1 = x2 - sdim.X;
break;
default: // 0 = default
x1 = rect2.UpperLeftCorner.X;
x2 = rect2.LowerRightCorner.X;
break;
}
switch (a_y) {
case 1: // up
y1 = rect.UpperLeftCorner.Y;
y2 = y1 + sdim.Y;
break;
case 2: // middle
y1 = (rect.UpperLeftCorner.Y + rect.LowerRightCorner.Y - sdim.Y) / 2;
y2 = y1 + sdim.Y;
break;
case 3: // down
y2 = rect.LowerRightCorner.Y;
y1 = y2 - sdim.Y;
break;
default: // 0 = default
y1 = rect2.UpperLeftCorner.Y;
y2 = rect2.LowerRightCorner.Y;
break;
}
rect2 = core::rect<s32>(x1, y1, x2, y2);
}
video::SColor color(255, 255, 255, 255);
font->draw(utf8_to_wide(text).c_str(), rect2, color, false, false, &viewrect);
}
}
void drawItemStack(
video::IVideoDriver *driver,
gui::IGUIFont *font,
const ItemStack &item,
const core::rect<s32> &rect,
const core::rect<s32> *clip,
Client *client,
ItemRotationKind rotation_kind)
{
drawItemStack(driver, font, item, rect, clip, client, rotation_kind,
v3s16(0, 0, 0), v3s16(0, 100, 0));
}

41
src/gui/drawItemStack.h Normal file
View file

@ -0,0 +1,41 @@
// Luanti
// SPDX-License-Identifier: LGPL-2.1-or-later
// Copyright (C) 2024 cx384
#pragma once
#include <IGUIFont.h>
#include <IVideoDriver.h>
#include "irrlichttypes.h"
#include "irr_v3d.h"
struct ItemStack;
class Client;
enum ItemRotationKind
{
IT_ROT_SELECTED,
IT_ROT_HOVERED,
IT_ROT_DRAGGED,
IT_ROT_OTHER,
IT_ROT_NONE, // Must be last, also serves as number
};
void drawItemStack(video::IVideoDriver *driver,
gui::IGUIFont *font,
const ItemStack &item,
const core::rect<s32> &rect,
const core::rect<s32> *clip,
Client *client,
ItemRotationKind rotation_kind);
void drawItemStack(
video::IVideoDriver *driver,
gui::IGUIFont *font,
const ItemStack &item,
const core::rect<s32> &rect,
const core::rect<s32> *clip,
Client *client,
ItemRotationKind rotation_kind,
const v3s16 &angle,
const v3s16 &rotation_speed);

View file

@ -1,8 +1,10 @@
#pragma once
#include "irrlichttypes_extrabloated.h"
#include <algorithm>
#include <string>
#include <IGUIElement.h>
#include <IGUIEnvironment.h>
using namespace irr;
class ISimpleTextureSource;

View file

@ -17,8 +17,11 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#pragma once
#include "irrlichttypes_extrabloated.h"
#include "util/string.h"
#include <IGUIElement.h>
#include <IGUIEnvironment.h>
#include "irr_v2d.h"
using namespace irr;
class ISimpleTextureSource;

View file

@ -4,6 +4,7 @@
#include "guiBox.h"
#include <IVideoDriver.h>
#include "irr_v2d.h"
GUIBox::GUIBox(gui::IGUIEnvironment *env, gui::IGUIElement *parent, s32 id,
const core::rect<s32> &rectangle,

View file

@ -4,9 +4,11 @@
#pragma once
#include <vector>
#include <array>
#include "irrlichttypes_extrabloated.h"
#include <IGUIElement.h>
#include <IGUIEnvironment.h>
using namespace irr;
class GUIBox : public gui::IGUIElement
{

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