mirror of
https://github.com/luanti-org/luanti.git
synced 2025-09-15 18:57:08 +00:00
Merge remote-tracking branch 'upstream/master' into Visuals-Vol-2
This commit is contained in:
commit
fa212d19f7
572 changed files with 71629 additions and 67352 deletions
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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, ¤t_intersection, ¤t_normal, ¤t_raw_normal);
|
||||
} else {
|
||||
collision = boxLineCollision(selection_box, rel_pos, line_vector,
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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".
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
|
102
src/client/item_visuals_manager.cpp
Normal file
102
src/client/item_visuals_manager.cpp
Normal 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;
|
||||
}
|
||||
|
68
src/client/item_visuals_manager.h
Normal file
68
src/client/item_visuals_manager.h
Normal 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;
|
||||
};
|
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include "SMeshBuffer.h"
|
||||
|
||||
#include <mutex>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include "../particles.h"
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
#include "anaglyph.h"
|
||||
#include "client/camera.h"
|
||||
#include <IrrlichtDevice.h>
|
||||
|
||||
#include <ISceneManager.h>
|
||||
|
||||
/// SetColorMaskStep step
|
||||
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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; }
|
||||
|
||||
|
|
|
@ -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."));
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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`;",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) };
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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())
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)...);
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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
314
src/gui/drawItemStack.cpp
Normal 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
41
src/gui/drawItemStack.h
Normal 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);
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue