diff options
Diffstat (limited to 'src/client')
39 files changed, 1344 insertions, 882 deletions
diff --git a/src/client/camera.cpp b/src/client/camera.cpp index 871ea709d..abc55e4b7 100644 --- a/src/client/camera.cpp +++ b/src/client/camera.cpp @@ -86,6 +86,51 @@ Camera::~Camera() m_wieldmgr->drop(); } +void Camera::notifyFovChange() +{ + LocalPlayer *player = m_client->getEnv().getLocalPlayer(); + assert(player); + + PlayerFovSpec spec = player->getFov(); + + /* + * Update m_old_fov_degrees first - it serves as the starting point of the + * upcoming transition. + * + * If an FOV transition is already active, mark current FOV as the start of + * the new transition. If not, set it to the previous transition's target FOV. + */ + if (m_fov_transition_active) + m_old_fov_degrees = m_curr_fov_degrees; + else + m_old_fov_degrees = m_server_sent_fov ? m_target_fov_degrees : m_cache_fov; + + /* + * Update m_server_sent_fov next - it corresponds to the target FOV of the + * upcoming transition. + * + * Set it to m_cache_fov, if server-sent FOV is 0. Otherwise check if + * server-sent FOV is a multiplier, and multiply it with m_cache_fov instead + * of overriding. + */ + if (spec.fov == 0.0f) { + m_server_sent_fov = false; + m_target_fov_degrees = m_cache_fov; + } else { + m_server_sent_fov = true; + m_target_fov_degrees = spec.is_multiplier ? m_cache_fov * spec.fov : spec.fov; + } + + if (spec.transition_time > 0.0f) + m_fov_transition_active = true; + + // If FOV smooth transition is active, initialize required variables + if (m_fov_transition_active) { + m_transition_time = spec.transition_time; + m_fov_diff = m_target_fov_degrees - m_old_fov_degrees; + } +} + bool Camera::successfullyCreated(std::string &error_message) { if (!m_playernode) { @@ -297,9 +342,13 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 busytime, f32 tool_r if (player->getParent()) player_position = player->getParent()->getPosition(); - if(player->touching_ground && - player_position.Y > old_player_position.Y) - { + // Smooth the camera movement when the player instantly moves upward due to stepheight. + // To smooth the 'not touching_ground' stepheight, smoothing is necessary when jumping + // or swimming (for when moving from liquid to land). + // Disable smoothing if climbing or flying, to avoid upwards offset of player model + // when seen in 3rd person view. + bool flying = g_settings->getBool("free_move") && m_client->checkLocalPrivilege("fly"); + if (player_position.Y > old_player_position.Y && !player->is_climbing && !flying) { f32 oldy = old_player_position.Y; f32 newy = player_position.Y; f32 t = std::exp(-23 * frametime); @@ -333,17 +382,21 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 busytime, f32 tool_r fall_bobbing *= m_cache_fall_bobbing_amount; } - // Calculate players eye offset for different camera modes - v3f PlayerEyeOffset = player->getEyeOffset(); - if (m_camera_mode == CAMERA_MODE_FIRST) - PlayerEyeOffset += player->eye_offset_first; - else - PlayerEyeOffset += player->eye_offset_third; - - // Set head node transformation - m_headnode->setPosition(PlayerEyeOffset+v3f(0,cameratilt*-player->hurt_tilt_strength+fall_bobbing,0)); - m_headnode->setRotation(v3f(player->getPitch(), 0, cameratilt*player->hurt_tilt_strength)); - m_headnode->updateAbsolutePosition(); + // Calculate and translate the head SceneNode offsets + { + v3f eye_offset = player->getEyeOffset(); + if (m_camera_mode == CAMERA_MODE_FIRST) + eye_offset += player->eye_offset_first; + else + eye_offset += player->eye_offset_third; + + // Set head node transformation + eye_offset.Y += cameratilt * -player->hurt_tilt_strength + fall_bobbing; + m_headnode->setPosition(eye_offset); + m_headnode->setRotation(v3f(player->getPitch(), 0, + cameratilt * player->hurt_tilt_strength)); + m_headnode->updateAbsolutePosition(); + } // Compute relative camera position and target v3f rel_cam_pos = v3f(0,0,0); @@ -458,35 +511,40 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 busytime, f32 tool_r m_camera_position = my_cp; /* - * Apply server-sent FOV. If server doesn't enforce FOV, - * check for zoom and set to zoom FOV. - * Otherwise, default to m_cache_fov + * Apply server-sent FOV, instantaneous or smooth transition. + * If not, check for zoom and set to zoom FOV. + * Otherwise, default to m_cache_fov. */ - - f32 fov_degrees; - PlayerFovSpec fov_spec = player->getFov(); - if (fov_spec.fov > 0.0f) { - // If server-sent FOV is a multiplier, multiply - // it with m_cache_fov instead of overriding - if (fov_spec.is_multiplier) - fov_degrees = m_cache_fov * fov_spec.fov; - else - fov_degrees = fov_spec.fov; + if (m_fov_transition_active) { + // Smooth FOV transition + // Dynamically calculate FOV delta based on frametimes + f32 delta = (frametime / m_transition_time) * m_fov_diff; + m_curr_fov_degrees += delta; + + // Mark transition as complete if target FOV has been reached + if ((m_fov_diff > 0.0f && m_curr_fov_degrees >= m_target_fov_degrees) || + (m_fov_diff < 0.0f && m_curr_fov_degrees <= m_target_fov_degrees)) { + m_fov_transition_active = false; + m_curr_fov_degrees = m_target_fov_degrees; + } + } else if (m_server_sent_fov) { + // Instantaneous FOV change + m_curr_fov_degrees = m_target_fov_degrees; } else if (player->getPlayerControl().zoom && player->getZoomFOV() > 0.001f) { // Player requests zoom, apply zoom FOV - fov_degrees = player->getZoomFOV(); + m_curr_fov_degrees = player->getZoomFOV(); } else { // Set to client's selected FOV - fov_degrees = m_cache_fov; + m_curr_fov_degrees = m_cache_fov; } - fov_degrees = rangelim(fov_degrees, 1.0f, 160.0f); + m_curr_fov_degrees = rangelim(m_curr_fov_degrees, 1.0f, 160.0f); // FOV and aspect ratio const v2u32 &window_size = RenderingEngine::get_instance()->getWindowSize(); m_aspect = (f32) window_size.X / (f32) window_size.Y; - m_fov_y = fov_degrees * M_PI / 180.0; + m_fov_y = m_curr_fov_degrees * M_PI / 180.0; // Increase vertical FOV on lower aspect ratios (<16:10) - m_fov_y *= MYMAX(1.0, MYMIN(1.4, sqrt(16./10. / m_aspect))); + m_fov_y *= core::clamp(sqrt(16./10. / m_aspect), 1.0, 1.4); m_fov_x = 2 * atan(m_aspect * tan(0.5 * m_fov_y)); m_cameranode->setAspectRatio(m_aspect); m_cameranode->setFOV(m_fov_y); @@ -538,7 +596,7 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 busytime, f32 tool_r m_wieldnode->setPosition(wield_position); m_wieldnode->setRotation(wield_rotation); - m_wieldnode->setColor(player->light_color); + m_wieldnode->setNodeLightColor(player->light_color); // Set render distance updateViewingRange(); @@ -553,14 +611,11 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 busytime, f32 tool_r const bool walking = movement_XZ && player->touching_ground; const bool swimming = (movement_XZ || player->swimming_vertical) && player->in_liquid; const bool climbing = movement_Y && player->is_climbing; - if ((walking || swimming || climbing) && - (!g_settings->getBool("free_move") || !m_client->checkLocalPrivilege("fly"))) { + if ((walking || swimming || climbing) && !flying) { // Start animation m_view_bobbing_state = 1; m_view_bobbing_speed = MYMIN(speed.getLength(), 70); - } - else if (m_view_bobbing_state == 1) - { + } else if (m_view_bobbing_state == 1) { // Stop animation m_view_bobbing_state = 2; m_view_bobbing_speed = 60; diff --git a/src/client/camera.h b/src/client/camera.h index 88de3570a..3a59637bc 100644 --- a/src/client/camera.h +++ b/src/client/camera.h @@ -75,6 +75,12 @@ public: return m_camera_position; } + // Returns the absolute position of the head SceneNode in the world + inline v3f getHeadPosition() const + { + return m_headnode->getAbsolutePosition(); + } + // Get the camera direction (in absolute camera coordinates). // This has view bobbing applied. inline v3f getDirection() const @@ -106,6 +112,9 @@ public: return MYMAX(m_fov_x, m_fov_y); } + // Notify about new server-sent FOV and initialize smooth FOV transition + void notifyFovChange(); + // Checks if the constructor was able to create the scene nodes bool successfullyCreated(std::string &error_message); @@ -180,6 +189,9 @@ private: Client *m_client; + // Default Client FOV (as defined by the "fov" setting) + f32 m_cache_fov; + // Absolute camera position v3f m_camera_position; // Absolute camera direction @@ -187,6 +199,14 @@ private: // Camera offset v3s16 m_camera_offset; + // Server-sent FOV variables + bool m_server_sent_fov = false; + f32 m_curr_fov_degrees, m_old_fov_degrees, m_target_fov_degrees; + + // FOV transition variables + bool m_fov_transition_active = false; + f32 m_fov_diff, m_transition_time; + v2f m_wieldmesh_offset = v2f(55.0f, -35.0f); v2f m_arm_dir; v2f m_cam_vel; @@ -224,7 +244,6 @@ private: f32 m_cache_fall_bobbing_amount; f32 m_cache_view_bobbing_amount; - f32 m_cache_fov; bool m_arm_inertia; std::list<Nametag *> m_nametags; diff --git a/src/client/client.cpp b/src/client/client.cpp index c6d28ce80..65e5b3d8c 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -61,6 +61,28 @@ with this program; if not, write to the Free Software Foundation, Inc., extern gui::IGUIEnvironment* guienv; /* + Utility classes +*/ + +u32 PacketCounter::sum() const +{ + u32 n = 0; + for (const auto &it : m_packets) + n += it.second; + return n; +} + +void PacketCounter::print(std::ostream &o) const +{ + for (const auto &it : m_packets) { + auto name = it.first >= TOCLIENT_NUM_MSG_TYPES ? "?" + : toClientCommandTable[it.first].name; + o << "cmd " << it.first << " (" << name << ") count " + << it.second << std::endl; + } +} + +/* Client */ @@ -164,7 +186,7 @@ void Client::loadMods() infostream << mod.name << " "; infostream << std::endl; - // Load and run "mod" scripts + // Load "mod" scripts for (const ModSpec &mod : m_mods) { if (!string_allowed(mod.name, MODNAME_ALLOWED_CHARS)) { throw ModError("Error loading mod \"" + mod.name + @@ -174,7 +196,7 @@ void Client::loadMods() scanModIntoMemory(mod.name, mod.path); } - // Load and run "mod" scripts + // Run them for (const ModSpec &mod : m_mods) m_script->loadModFromMemory(mod.name); @@ -183,10 +205,14 @@ void Client::loadMods() // Run a callback when mods are loaded m_script->on_mods_loaded(); + + // Create objects if they're ready if (m_state == LC_Ready) m_script->on_client_ready(m_env.getLocalPlayer()); if (m_camera) m_script->on_camera_ready(m_camera); + if (m_minimap) + m_script->on_minimap_ready(m_minimap); } bool Client::checkBuiltinIntegrity() @@ -336,12 +362,14 @@ void Client::step(float dtime) { float &counter = m_packetcounter_timer; counter -= dtime; - if(counter <= 0.0) + if(counter <= 0.0f) { - counter = 20.0; + counter = 30.0f; + u32 sum = m_packetcounter.sum(); + float avg = sum / counter; - infostream << "Client packetcounter (" << m_packetcounter_timer - << "):"<<std::endl; + infostream << "Client packetcounter (" << counter << "s): " + << "sum=" << sum << " avg=" << avg << "/s" << std::endl; m_packetcounter.print(infostream); m_packetcounter.clear(); } @@ -431,12 +459,9 @@ void Client::step(float dtime) /* Handle environment */ - // Control local player (0ms) LocalPlayer *player = m_env.getLocalPlayer(); - assert(player); - player->applyControl(dtime, &m_env); - // Step environment + // Step environment (also handles player controls) m_env.step(dtime); m_sound->step(dtime); @@ -621,14 +646,17 @@ void Client::step(float dtime) m_mod_storage_save_timer -= dtime; if (m_mod_storage_save_timer <= 0.0f) { - verbosestream << "Saving registered mod storages." << std::endl; m_mod_storage_save_timer = g_settings->getFloat("server_map_save_interval"); + int n = 0; for (std::unordered_map<std::string, ModMetadata *>::const_iterator it = m_mod_storages.begin(); it != m_mod_storages.end(); ++it) { if (it->second->isModified()) { it->second->save(getModStoragePath()); + n++; } } + if (n > 0) + infostream << "Saved " << n << " modified mod storages." << std::endl; } // Write server map @@ -639,11 +667,9 @@ void Client::step(float dtime) } } -bool Client::loadMedia(const std::string &data, const std::string &filename) +bool Client::loadMedia(const std::string &data, const std::string &filename, + bool from_media_push) { - // Silly irrlicht's const-incorrectness - Buffer<char> data_rw(data.c_str(), data.size()); - std::string name; const char *image_ext[] = { @@ -653,12 +679,15 @@ bool Client::loadMedia(const std::string &data, const std::string &filename) }; name = removeStringEnd(filename, image_ext); if (!name.empty()) { - verbosestream<<"Client: Attempting to load image " - <<"file \""<<filename<<"\""<<std::endl; + TRACESTREAM(<< "Client: Attempting to load image " + << "file \"" << filename << "\"" << std::endl); io::IFileSystem *irrfs = RenderingEngine::get_filesystem(); video::IVideoDriver *vdrv = RenderingEngine::get_video_driver(); + // Silly irrlicht's const-incorrectness + Buffer<char> data_rw(data.c_str(), data.size()); + // Create an irrlicht memory file io::IReadFile *rfile = irrfs->createMemoryReadFile( *data_rw, data_rw.getSize(), "_tempreadfile"); @@ -687,17 +716,15 @@ bool Client::loadMedia(const std::string &data, const std::string &filename) }; name = removeStringEnd(filename, sound_ext); if (!name.empty()) { - verbosestream<<"Client: Attempting to load sound " - <<"file \""<<filename<<"\""<<std::endl; - m_sound->loadSoundData(name, data); - return true; + TRACESTREAM(<< "Client: Attempting to load sound " + << "file \"" << filename << "\"" << std::endl); + return m_sound->loadSoundData(name, data); } const char *model_ext[] = { ".x", ".b3d", ".md2", ".obj", NULL }; - name = removeStringEnd(filename, model_ext); if (!name.empty()) { verbosestream<<"Client: Storing model into memory: " @@ -714,9 +741,11 @@ bool Client::loadMedia(const std::string &data, const std::string &filename) }; name = removeStringEnd(filename, translate_ext); if (!name.empty()) { - verbosestream << "Client: Loading translation: " - << "\"" << filename << "\"" << std::endl; - g_translations->loadTranslation(data); + if (from_media_push) + return false; + TRACESTREAM(<< "Client: Loading translation: " + << "\"" << filename << "\"" << std::endl); + g_client_translations->loadTranslation(data); return true; } @@ -1722,8 +1751,11 @@ void Client::afterContentReceived() text = wgettext("Initializing nodes..."); RenderingEngine::draw_load_screen(text, guienv, m_tsrc, 0, 72); m_nodedef->updateAliases(m_itemdef); - for (const auto &path : getTextureDirs()) - m_nodedef->applyTextureOverrides(path + DIR_DELIM + "override.txt"); + for (const auto &path : getTextureDirs()) { + TextureOverrideSource override_source(path + DIR_DELIM + "override.txt"); + m_nodedef->applyTextureOverrides(override_source.getNodeTileOverrides()); + m_itemdef->applyTextureOverrides(override_source.getItemTextureOverrides()); + } m_nodedef->setNodeRegistrationStatus(true); m_nodedef->runNodeResolveCallbacks(); delete[] text; @@ -1780,13 +1812,24 @@ void Client::makeScreenshot() char timetstamp_c[64]; strftime(timetstamp_c, sizeof(timetstamp_c), "%Y%m%d_%H%M%S", tm); - std::string filename_base = g_settings->get("screenshot_path") + std::string screenshot_dir; + + if (fs::IsPathAbsolute(g_settings->get("screenshot_path"))) + screenshot_dir = g_settings->get("screenshot_path"); + else + screenshot_dir = porting::path_user + DIR_DELIM + g_settings->get("screenshot_path"); + + std::string filename_base = screenshot_dir + DIR_DELIM + std::string("screenshot_") + std::string(timetstamp_c); std::string filename_ext = "." + g_settings->get("screenshot_format"); std::string filename; + // Create the directory if it doesn't already exist. + // Otherwise, saving the screenshot would fail. + fs::CreateDir(screenshot_dir); + u32 quality = (u32)g_settings->getS32("screenshot_quality"); quality = MYMIN(MYMAX(quality, 0), 100) / 100.0 * 255; diff --git a/src/client/client.h b/src/client/client.h index 1291b944c..733634db1 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -82,34 +82,24 @@ public: void add(u16 command) { - std::map<u16, u16>::iterator n = m_packets.find(command); - if(n == m_packets.end()) - { + auto n = m_packets.find(command); + if (n == m_packets.end()) m_packets[command] = 1; - } else - { n->second++; - } } void clear() { - for (auto &m_packet : m_packets) { - m_packet.second = 0; - } + m_packets.clear(); } - void print(std::ostream &o) - { - for (const auto &m_packet : m_packets) { - o << "cmd "<< m_packet.first <<" count "<< m_packet.second << std::endl; - } - } + u32 sum() const; + void print(std::ostream &o) const; private: // command, count - std::map<u16, u16> m_packets; + std::map<u16, u32> m_packets; }; class ClientScripting; @@ -232,6 +222,7 @@ public: void handleCommand_FormspecPrepend(NetworkPacket *pkt); void handleCommand_CSMRestrictionFlags(NetworkPacket *pkt); void handleCommand_PlayerSpeed(NetworkPacket *pkt); + void handleCommand_MediaPush(NetworkPacket *pkt); void ProcessData(NetworkPacket *pkt); @@ -386,7 +377,8 @@ public: // The following set of functions is used by ClientMediaDownloader // Insert a media file appropriately into the appropriate manager - bool loadMedia(const std::string &data, const std::string &filename); + bool loadMedia(const std::string &data, const std::string &filename, + bool from_media_push = false); // Send a request for conventional media transfer void request_media(const std::vector<std::string> &file_requests); @@ -498,6 +490,7 @@ private: Camera *m_camera = nullptr; Minimap *m_minimap = nullptr; bool m_minimap_disabled_by_server = false; + // Server serialization version u8 m_server_ser_ver; @@ -539,7 +532,6 @@ private: AuthMechanism m_chosen_auth_mech; void *m_auth_data = nullptr; - bool m_access_denied = false; bool m_access_denied_reconnect = false; std::string m_access_denied_reason = ""; @@ -548,7 +540,10 @@ private: bool m_nodedef_received = false; bool m_activeobjects_received = false; bool m_mods_loaded = false; + ClientMediaDownloader *m_media_downloader; + // Set of media filenames pushed by server at runtime + std::unordered_set<std::string> m_media_pushed_files; // time_of_day speed approximation for old protocol bool m_time_of_day_set = false; diff --git a/src/client/clientenvironment.cpp b/src/client/clientenvironment.cpp index 52d133781..895b0193c 100644 --- a/src/client/clientenvironment.cpp +++ b/src/client/clientenvironment.cpp @@ -216,6 +216,9 @@ void ClientEnvironment::step(float dtime) */ { + // Control local player + lplayer->applyControl(dtime_part, this); + // Apply physics if (!free_move && !is_climbing) { // Gravity @@ -320,21 +323,8 @@ void ClientEnvironment::step(float dtime) // Step object cao->step(dtime, this); - if (update_lighting) { - // Update lighting - u8 light = 0; - bool pos_ok; - - // Get node at head - v3s16 p = cao->getLightPosition(); - MapNode n = this->m_map->getNode(p, &pos_ok); - if (pos_ok) - light = n.getLightBlend(day_night_ratio, m_client->ndef()); - else - light = blend_light(day_night_ratio, LIGHT_SUN, 0); - - cao->updateLight(light); - } + if (update_lighting) + cao->updateLight(day_night_ratio); }; m_ao_manager.step(dtime, cb_state); @@ -402,18 +392,7 @@ u16 ClientEnvironment::addActiveObject(ClientActiveObject *object) object->addToScene(m_texturesource); // Update lighting immediately - u8 light = 0; - bool pos_ok; - - // Get node at head - v3s16 p = object->getLightPosition(); - MapNode n = m_map->getNode(p, &pos_ok); - if (pos_ok) - light = n.getLightBlend(getDayNightRatio(), m_client->ndef()); - else - light = blend_light(getDayNightRatio(), LIGHT_SUN, 0); - - object->updateLight(light); + object->updateLight(getDayNightRatio()); return object->getId(); } @@ -450,7 +429,7 @@ void ClientEnvironment::addActiveObject(u16 id, u8 type, // Object initialized: if ((obj = getActiveObject(new_id))) { // Final step is to update all children which are already known - // Data provided by GENERIC_CMD_SPAWN_INFANT + // Data provided by AO_CMD_SPAWN_INFANT const auto &children = obj->getAttachmentChildIds(); for (auto c_id : children) { if (auto *o = getActiveObject(c_id)) diff --git a/src/client/clientevent.h b/src/client/clientevent.h index f5689c25b..9bd31efce 100644 --- a/src/client/clientevent.h +++ b/src/client/clientevent.h @@ -21,8 +21,13 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <string> #include "irrlichttypes_bloated.h" -#include "hud.h" -#include "skyparams.h" + +struct ParticleParameters; +struct ParticleSpawnerParameters; +struct SkyboxParams; +struct SunParams; +struct MoonParams; +struct StarParams; enum ClientEventType : u8 { @@ -77,44 +82,12 @@ struct ClientEvent } show_formspec; // struct{ //} textures_updated; + ParticleParameters *spawn_particle; struct { - v3f *pos; - v3f *vel; - v3f *acc; - f32 expirationtime; - f32 size; - bool collisiondetection; - bool collision_removal; - bool object_collision; - bool vertical; - std::string *texture; - struct TileAnimationParams animation; - u8 glow; - } spawn_particle; - struct - { - u16 amount; - f32 spawntime; - v3f *minpos; - v3f *maxpos; - v3f *minvel; - v3f *maxvel; - v3f *minacc; - v3f *maxacc; - f32 minexptime; - f32 maxexptime; - f32 minsize; - f32 maxsize; - bool collisiondetection; - bool collision_removal; - bool object_collision; + ParticleSpawnerParameters *p; u16 attached_id; - bool vertical; - std::string *texture; u64 id; - struct TileAnimationParams animation; - u8 glow; } add_particlespawner; struct { @@ -136,6 +109,7 @@ struct ClientEvent v3f *world_pos; v2s32 *size; s16 z_index; + std::string *text2; } hudadd; struct { diff --git a/src/client/clientlauncher.cpp b/src/client/clientlauncher.cpp index 2a9d6097f..f18915a55 100644 --- a/src/client/clientlauncher.cpp +++ b/src/client/clientlauncher.cpp @@ -105,7 +105,7 @@ bool ClientLauncher::run(GameParams &game_params, const Settings &cmd_args) } RenderingEngine::get_instance()->setupTopLevelWindow(PROJECT_NAME_C); - + /* This changes the minimum allowed number of vertices in a VBO. Default is 500. diff --git a/src/client/clientmedia.cpp b/src/client/clientmedia.cpp index 6da99bbbf..8cd3b6bcc 100644 --- a/src/client/clientmedia.cpp +++ b/src/client/clientmedia.cpp @@ -35,6 +35,15 @@ static std::string getMediaCacheDir() return porting::path_cache + DIR_DELIM + "media"; } +bool clientMediaUpdateCache(const std::string &raw_hash, const std::string &filedata) +{ + FileCache media_cache(getMediaCacheDir()); + std::string sha1_hex = hex_encode(raw_hash); + if (!media_cache.exists(sha1_hex)) + return media_cache.update(sha1_hex, filedata); + return true; +} + /* ClientMediaDownloader */ @@ -559,7 +568,6 @@ bool ClientMediaDownloader::checkAndLoad( return true; } - /* Minetest Hashset File Format diff --git a/src/client/clientmedia.h b/src/client/clientmedia.h index 92831082c..5a918535b 100644 --- a/src/client/clientmedia.h +++ b/src/client/clientmedia.h @@ -33,6 +33,11 @@ struct HTTPFetchResult; #define MTHASHSET_FILE_SIGNATURE 0x4d544853 // 'MTHS' #define MTHASHSET_FILE_NAME "index.mth" +// Store file into media cache (unless it exists already) +// Validating the hash is responsibility of the caller +bool clientMediaUpdateCache(const std::string &raw_hash, + const std::string &filedata); + class ClientMediaDownloader { public: diff --git a/src/client/clientobject.h b/src/client/clientobject.h index 12e0db35b..ecd8059ef 100644 --- a/src/client/clientobject.h +++ b/src/client/clientobject.h @@ -41,10 +41,9 @@ public: virtual void addToScene(ITextureSource *tsrc) {} virtual void removeFromScene(bool permanent) {} - // 0 <= light_at_pos <= LIGHT_SUN - virtual void updateLight(u8 light_at_pos) {} - virtual void updateLightNoCheck(u8 light_at_pos) {} - virtual v3s16 getLightPosition() { return v3s16(0, 0, 0); } + + virtual void updateLight(u32 day_night_ratio) {} + virtual bool getCollisionBox(aabb3f *toset) const { return false; } virtual bool getSelectionBox(aabb3f *toset) const { return false; } virtual bool collideWithObjects() const { return false; } diff --git a/src/client/clientsimpleobject.h b/src/client/clientsimpleobject.h new file mode 100644 index 000000000..f4a40bcd3 --- /dev/null +++ b/src/client/clientsimpleobject.h @@ -0,0 +1,35 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include "irrlichttypes_bloated.h" +class ClientEnvironment; + +class ClientSimpleObject +{ +protected: +public: + bool m_to_be_removed = false; + + ClientSimpleObject() = default; + virtual ~ClientSimpleObject() = default; + + virtual void step(float dtime) {} +}; diff --git a/src/client/content_cao.cpp b/src/client/content_cao.cpp index d148df522..4f949f6b0 100644 --- a/src/client/content_cao.cpp +++ b/src/client/content_cao.cpp @@ -162,6 +162,15 @@ static void setBillboardTextureMatrix(scene::IBillboardSceneNode *bill, matrix.setTextureScale(txs, tys); } +// Evaluate transform chain recursively; irrlicht does not do this for us +static void updatePositionRecursive(scene::ISceneNode *node) +{ + scene::ISceneNode *parent = node->getParent(); + if (parent) + updatePositionRecursive(parent); + node->updateAbsolutePosition(); +} + /* TestCAO */ @@ -181,8 +190,7 @@ public: void addToScene(ITextureSource *tsrc); void removeFromScene(bool permanent); - void updateLight(u8 light_at_pos); - v3s16 getLightPosition(); + void updateLight(u32 day_night_ratio); void updateNodePos(); void step(float dtime, ClientEnvironment *env); @@ -254,13 +262,8 @@ void TestCAO::removeFromScene(bool permanent) m_node = NULL; } -void TestCAO::updateLight(u8 light_at_pos) -{ -} - -v3s16 TestCAO::getLightPosition() +void TestCAO::updateLight(u32 day_night_ratio) { - return floatToInt(m_position, BS); } void TestCAO::updateNodePos() @@ -304,7 +307,6 @@ void TestCAO::processMessage(const std::string &data) GenericCAO */ -#include "genericobject.h" #include "clientobject.h" GenericCAO::GenericCAO(Client *client, ClientEnvironment *env): @@ -476,6 +478,7 @@ void GenericCAO::setAttachment(int parent_id, const std::string &bone, v3f posit parent->addAttachmentChild(m_id); } + updateAttachments(); } @@ -576,16 +579,23 @@ void GenericCAO::addToScene(ITextureSource *tsrc) m_visuals_expired = false; - if (!m_prop.is_visible) { + if (!m_prop.is_visible) return; - } + + infostream << "GenericCAO::addToScene(): " << m_prop.visual << std::endl; if (m_enable_shaders) { IShaderSource *shader_source = m_client->getShaderSource(); - u32 shader_id = shader_source->getShader( - "object_shader", - TILE_MATERIAL_BASIC, - NDT_NORMAL); + MaterialType material_type; + + if (m_prop.shaded && m_prop.glow == 0) + material_type = (m_prop.use_texture_alpha) ? + TILE_MATERIAL_ALPHA : TILE_MATERIAL_BASIC; + else + material_type = (m_prop.use_texture_alpha) ? + TILE_MATERIAL_PLAIN_ALPHA : TILE_MATERIAL_PLAIN; + + u32 shader_id = shader_source->getShader("object_shader", material_type, NDT_NORMAL); m_material_type = shader_source->getShaderInfo(shader_id).material; } else { m_material_type = (m_prop.use_texture_alpha) ? @@ -593,7 +603,6 @@ void GenericCAO::addToScene(ITextureSource *tsrc) } auto grabMatrixNode = [this] { - infostream << "GenericCAO::addToScene(): " << m_prop.visual << std::endl; m_matrixnode = RenderingEngine::get_scene_manager()-> addDummyTransformationSceneNode(); m_matrixnode->grab(); @@ -714,6 +723,8 @@ void GenericCAO::addToScene(ITextureSource *tsrc) mesh->drop(); m_meshnode->setScale(m_prop.visual_size); + m_meshnode->setMaterialFlag(video::EMF_BACK_FACE_CULLING, + m_prop.backface_culling); setSceneNodeMaterial(m_meshnode); } else if (m_prop.visual == "mesh") { @@ -724,6 +735,14 @@ void GenericCAO::addToScene(ITextureSource *tsrc) addAnimatedMeshSceneNode(mesh, m_matrixnode); m_animated_meshnode->grab(); mesh->drop(); // The scene node took hold of it + + if (!checkMeshNormals(mesh)) { + infostream << "GenericCAO: recalculating normals for mesh " + << m_prop.mesh << std::endl; + m_smgr->getMeshManipulator()-> + recalculateNormals(mesh, true, false); + } + m_animated_meshnode->animateJoints(); // Needed for some animations m_animated_meshnode->setScale(m_prop.visual_size); @@ -775,15 +794,7 @@ void GenericCAO::addToScene(ITextureSource *tsrc) if (node && m_matrixnode) node->setParent(m_matrixnode); - if (node && !m_prop.nametag.empty() && !m_is_local_player) { - // Add nametag - v3f pos; - pos.Y = m_prop.selectionbox.MaxEdge.Y + 0.3f; - m_nametag = m_client->getCamera()->addNametag(node, - m_prop.nametag, m_prop.nametag_color, - pos); - } - + updateNametag(); updateNodePos(); updateAnimation(); updateBonePosition(); @@ -791,34 +802,32 @@ void GenericCAO::addToScene(ITextureSource *tsrc) setNodeLight(m_last_light); } -void GenericCAO::updateLight(u8 light_at_pos) +void GenericCAO::updateLight(u32 day_night_ratio) { - // Don't update light of attached one - if (getParent() != NULL) { + if (m_glow < 0) return; - } - - updateLightNoCheck(light_at_pos); - // Update light of all children - for (u16 i : m_attachment_child_ids) { - ClientActiveObject *obj = m_env->getActiveObject(i); - if (obj) { - obj->updateLightNoCheck(light_at_pos); + u8 light_at_pos = 0; + bool pos_ok = false; + + v3s16 pos[3]; + u16 npos = getLightPosition(pos); + for (u16 i = 0; i < npos; i++) { + bool this_ok; + MapNode n = m_env->getMap().getNode(pos[i], &this_ok); + if (this_ok) { + u8 this_light = n.getLightBlend(day_night_ratio, m_client->ndef()); + light_at_pos = MYMAX(light_at_pos, this_light); + pos_ok = true; } } -} - -void GenericCAO::updateLightNoCheck(u8 light_at_pos) -{ - if (m_glow < 0) - return; + if (!pos_ok) + light_at_pos = blend_light(day_night_ratio, LIGHT_SUN, 0); - u8 li = decode_light(light_at_pos + m_glow); - - if (li != m_last_light) { - m_last_light = li; - setNodeLight(li); + u8 light = decode_light(light_at_pos + m_glow); + if (light != m_last_light) { + m_last_light = light; + setNodeLight(light); } } @@ -827,24 +836,26 @@ void GenericCAO::setNodeLight(u8 light) video::SColor color(255, light, light, light); if (m_prop.visual == "wielditem" || m_prop.visual == "item") { - // Since these types of visuals are using their own shader - // they should be handled separately if (m_wield_meshnode) - m_wield_meshnode->setColor(color); - } else if (m_enable_shaders) { - scene::ISceneNode *node = getSceneNode(); - - if (node == nullptr) - return; + m_wield_meshnode->setNodeLightColor(color); + return; + } + if (m_enable_shaders) { if (m_prop.visual == "upright_sprite") { + if (!m_meshnode) + return; + scene::IMesh *mesh = m_meshnode->getMesh(); for (u32 i = 0; i < mesh->getMeshBufferCount(); ++i) { scene::IMeshBuffer *buf = mesh->getMeshBuffer(i); - video::SMaterial &material = buf->getMaterial(); - material.EmissiveColor = color; + buf->getMaterial().EmissiveColor = color; } } else { + scene::ISceneNode *node = getSceneNode(); + if (!node) + return; + for (u32 i = 0; i < node->getMaterialCount(); ++i) { video::SMaterial &material = node->getMaterial(i); material.EmissiveColor = color; @@ -861,12 +872,49 @@ void GenericCAO::setNodeLight(u8 light) } } -v3s16 GenericCAO::getLightPosition() +u16 GenericCAO::getLightPosition(v3s16 *pos) +{ + const auto &box = m_prop.collisionbox; + pos[0] = floatToInt(m_position + box.MinEdge * BS, BS); + pos[1] = floatToInt(m_position + box.MaxEdge * BS, BS); + + // Skip center pos if it falls into the same node as Min or MaxEdge + if ((box.MaxEdge - box.MinEdge).getLengthSQ() < 3.0f) + return 2; + pos[2] = floatToInt(m_position + box.getCenter() * BS, BS); + return 3; +} + +void GenericCAO::updateNametag() { - if (m_is_player) - return floatToInt(m_position + v3f(0, 0.5 * BS, 0), BS); + if (m_is_local_player) // No nametag for local player + return; + + if (m_prop.nametag.empty()) { + // Delete nametag + if (m_nametag) { + m_client->getCamera()->removeNametag(m_nametag); + m_nametag = nullptr; + } + return; + } - return floatToInt(m_position, BS); + scene::ISceneNode *node = getSceneNode(); + if (!node) + return; + + v3f pos; + pos.Y = m_prop.selectionbox.MaxEdge.Y + 0.3f; + if (!m_nametag) { + // Add nametag + m_nametag = m_client->getCamera()->addNametag(node, + m_prop.nametag, m_prop.nametag_color, pos); + } else { + // Update nametag + m_nametag->nametag_text = m_prop.nametag; + m_nametag->nametag_color = m_prop.nametag_color; + m_nametag->nametag_pos = pos; + } } void GenericCAO::updateNodePos() @@ -1072,10 +1120,13 @@ void GenericCAO::step(float dtime, ClientEnvironment *env) updateTextures(m_previous_texture_modifier); } } + if (!getParent() && std::fabs(m_prop.automatic_rotate) > 0.001) { - m_rotation.Y += dtime * m_prop.automatic_rotate * 180 / M_PI; - rot_translator.val_current = m_rotation; - updateNodePos(); + // This is the child node's rotation. It is only used for automatic_rotate. + v3f local_rot = node->getRotation(); + local_rot.Y = modulo360f(local_rot.Y - dtime * core::RADTODEG * + m_prop.automatic_rotate); + node->setRotation(local_rot); } if (!getParent() && m_prop.automatic_face_movement_dir && @@ -1096,6 +1147,18 @@ void GenericCAO::step(float dtime, ClientEnvironment *env) rot_translator.val_current = m_rotation; updateNodePos(); } + + if (m_animated_meshnode) { + // Everything must be updated; the whole transform + // chain as well as the animated mesh node. + // Otherwise, bone attachments would be relative to + // a position that's one frame old. + if (m_matrixnode) + updatePositionRecursive(m_matrixnode); + m_animated_meshnode->updateAbsolutePosition(); + m_animated_meshnode->animateJoints(); + updateBonePosition(); + } } void GenericCAO::updateTexturePos() @@ -1360,16 +1423,53 @@ void GenericCAO::updateBonePosition() return; m_animated_meshnode->setJointMode(irr::scene::EJUOR_CONTROL); // To write positions to the mesh on render - for(std::unordered_map<std::string, core::vector2d<v3f>>::const_iterator - ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii) { - std::string bone_name = (*ii).first; - v3f bone_pos = (*ii).second.X; - v3f bone_rot = (*ii).second.Y; + for (auto &it : m_bone_position) { + std::string bone_name = it.first; irr::scene::IBoneSceneNode* bone = m_animated_meshnode->getJointNode(bone_name.c_str()); - if(bone) - { - bone->setPosition(bone_pos); + if (bone) { + bone->setPosition(it.second.X); + bone->setRotation(it.second.Y); + } + } + + // search through bones to find mistakenly rotated bones due to bug in Irrlicht + for (u32 i = 0; i < m_animated_meshnode->getJointCount(); ++i) { + irr::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_position) { + 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. + for (u32 i = 0; i < m_animated_meshnode->getJointCount(); ++i) { + auto bone = m_animated_meshnode->getJointNode(i); + // Look for the root bone. + if (bone && bone->getParent() == m_animated_meshnode) { + // Update entire skeleton. + bone->updateAbsolutePositionOfAllChildren(); + break; } } } @@ -1394,15 +1494,17 @@ void GenericCAO::updateAttachments() if (!parent) { // Detach or don't attach if (m_matrixnode) { + v3s16 camera_offset = m_env->getCameraOffset(); v3f old_pos = getPosition(); m_matrixnode->setParent(m_smgr->getRootSceneNode()); - getPosRotMatrix().setTranslation(old_pos); + getPosRotMatrix().setTranslation(old_pos - intToFloat(camera_offset, BS)); m_matrixnode->updateAbsolutePosition(); } } else // Attach { + parent->updateAttachments(); scene::ISceneNode *parent_node = parent->getSceneNode(); scene::IAnimatedMeshSceneNode *parent_animated_mesh_node = parent->getAnimatedMeshSceneNode(); @@ -1412,6 +1514,7 @@ void GenericCAO::updateAttachments() if (m_matrixnode && parent_node) { m_matrixnode->setParent(parent_node); + parent_node->updateAbsolutePosition(); getPosRotMatrix().setTranslation(m_attachment_position); //setPitchYawRoll(getPosRotMatrix(), m_attachment_rotation); // use Irrlicht eulers instead @@ -1421,21 +1524,55 @@ void GenericCAO::updateAttachments() } } +bool GenericCAO::visualExpiryRequired(const ObjectProperties &new_) const +{ + const ObjectProperties &old = m_prop; + /* Visuals do not need to be expired for: + * - nametag props: handled by updateNametag() + * - textures: handled by updateTextures() + * - sprite props: handled by updateTexturePos() + * - 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"); + // 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.visual != new_.visual || + old.visual_size != new_.visual_size || + old.wield_item != new_.wield_item || + old.colors != new_.colors || + (uses_legacy_texture && old.textures != new_.textures); +} + void GenericCAO::processMessage(const std::string &data) { //infostream<<"GenericCAO: Got message"<<std::endl; std::istringstream is(data, std::ios::binary); // command u8 cmd = readU8(is); - if (cmd == GENERIC_CMD_SET_PROPERTIES) { - m_prop = gob_read_set_properties(is); + if (cmd == AO_CMD_SET_PROPERTIES) { + ObjectProperties newprops; + newprops.deSerialize(is); + + // Check what exactly changed + bool expire_visuals = visualExpiryRequired(newprops); + bool textures_changed = m_prop.textures != newprops.textures; + + // Apply changes + m_prop = std::move(newprops); m_selection_box = m_prop.selectionbox; m_selection_box.MinEdge *= BS; m_selection_box.MaxEdge *= BS; - m_tx_size.X = 1.0 / m_prop.spritediv.X; - m_tx_size.Y = 1.0 / m_prop.spritediv.Y; + m_tx_size.X = 1.0f / m_prop.spritediv.X; + m_tx_size.Y = 1.0f / m_prop.spritediv.Y; if(!m_initial_tx_basepos_set){ m_initial_tx_basepos_set = true; @@ -1455,18 +1592,25 @@ void GenericCAO::processMessage(const std::string &data) if ((m_is_player && !m_is_local_player) && m_prop.nametag.empty()) m_prop.nametag = m_name; - expireVisuals(); - } else if (cmd == GENERIC_CMD_UPDATE_POSITION) { + if (expire_visuals) { + expireVisuals(); + } else { + infostream << "GenericCAO: properties updated but expiring visuals" + << " not necessary" << std::endl; + if (textures_changed) { + // don't update while punch texture modifier is active + if (m_reset_textures_timer < 0) + updateTextures(m_current_texture_modifier); + } + updateNametag(); + } + } else if (cmd == AO_CMD_UPDATE_POSITION) { // Not sent by the server if this object is an attachment. // We might however get here if the server notices the object being detached before the client. m_position = readV3F32(is); m_velocity = readV3F32(is); m_acceleration = readV3F32(is); - - if (std::fabs(m_prop.automatic_rotate) < 0.001f) - m_rotation = readV3F32(is); - else - readV3F32(is); + m_rotation = readV3F32(is); m_rotation = wrapDegrees_0_360_v3f(m_rotation); bool do_interpolate = readU8(is); @@ -1490,16 +1634,16 @@ void GenericCAO::processMessage(const std::string &data) } rot_translator.update(m_rotation, false, update_interval); updateNodePos(); - } else if (cmd == GENERIC_CMD_SET_TEXTURE_MOD) { + } else if (cmd == AO_CMD_SET_TEXTURE_MOD) { std::string mod = deSerializeString(is); - // immediatly reset a engine issued texture modifier if a mod sends a different one + // immediately reset a engine issued texture modifier if a mod sends a different one if (m_reset_textures_timer > 0) { m_reset_textures_timer = -1; updateTextures(m_previous_texture_modifier); } updateTextures(mod); - } else if (cmd == GENERIC_CMD_SET_SPRITE) { + } else if (cmd == AO_CMD_SET_SPRITE) { v2s16 p = readV2S16(is); int num_frames = readU16(is); float framelength = readF32(is); @@ -1511,7 +1655,7 @@ void GenericCAO::processMessage(const std::string &data) m_tx_select_horiz_by_yawpitch = select_horiz_by_yawpitch; updateTexturePos(); - } else if (cmd == GENERIC_CMD_SET_PHYSICS_OVERRIDE) { + } else if (cmd == AO_CMD_SET_PHYSICS_OVERRIDE) { float override_speed = readF32(is); float override_jump = readF32(is); float override_gravity = readF32(is); @@ -1531,7 +1675,7 @@ void GenericCAO::processMessage(const std::string &data) player->physics_override_sneak_glitch = sneak_glitch; player->physics_override_new_move = new_move; } - } else if (cmd == GENERIC_CMD_SET_ANIMATION) { + } else if (cmd == AO_CMD_SET_ANIMATION) { // TODO: change frames send as v2s32 value v2f range = readV2F32(is); if (!m_is_local_player) { @@ -1565,17 +1709,17 @@ void GenericCAO::processMessage(const std::string &data) updateAnimation(); } } - } else if (cmd == GENERIC_CMD_SET_ANIMATION_SPEED) { + } else if (cmd == AO_CMD_SET_ANIMATION_SPEED) { m_animation_speed = readF32(is); updateAnimationSpeed(); - } else if (cmd == GENERIC_CMD_SET_BONE_POSITION) { + } else if (cmd == AO_CMD_SET_BONE_POSITION) { std::string bone = deSerializeString(is); v3f position = readV3F32(is); v3f rotation = readV3F32(is); m_bone_position[bone] = core::vector2d<v3f>(position, rotation); - updateBonePosition(); - } else if (cmd == GENERIC_CMD_ATTACH_TO) { + // updateBonePosition(); now called every step + } else if (cmd == AO_CMD_ATTACH_TO) { u16 parent_id = readS16(is); std::string bone = deSerializeString(is); v3f position = readV3F32(is); @@ -1586,7 +1730,7 @@ void GenericCAO::processMessage(const std::string &data) // localplayer itself can't be attached to localplayer if (!m_is_local_player) m_is_visible = !m_attached_to_local; - } else if (cmd == GENERIC_CMD_PUNCHED) { + } else if (cmd == AO_CMD_PUNCHED) { u16 result_hp = readU16(is); // Use this instead of the send damage to not interfere with prediction @@ -1607,13 +1751,11 @@ void GenericCAO::processMessage(const std::string &data) m_smgr, m_env, m_position, v2f(m_prop.visual_size.X, m_prop.visual_size.Y) * BS); m_env->addSimpleObject(simple); - } else if (m_reset_textures_timer < 0) { - // TODO: Execute defined fast response - // Flashing shall suffice as there is no definition + } else if (m_reset_textures_timer < 0 && !m_prop.damage_texture_modifier.empty()) { m_reset_textures_timer = 0.05; if(damage >= 2) m_reset_textures_timer += 0.05 * damage; - updateTextures(m_current_texture_modifier + "^[brighten"); + updateTextures(m_current_texture_modifier + m_prop.damage_texture_modifier); } } @@ -1624,7 +1766,7 @@ void GenericCAO::processMessage(const std::string &data) if (!m_is_player) clearChildAttachments(); } - } else if (cmd == GENERIC_CMD_UPDATE_ARMOR_GROUPS) { + } else if (cmd == AO_CMD_UPDATE_ARMOR_GROUPS) { m_armor_groups.clear(); int armor_groups_size = readU16(is); for(int i=0; i<armor_groups_size; i++) @@ -1633,22 +1775,14 @@ void GenericCAO::processMessage(const std::string &data) int rating = readS16(is); m_armor_groups[name] = rating; } - } else if (cmd == GENERIC_CMD_UPDATE_NAMETAG_ATTRIBUTES) { - // Deprecated, for backwards compatibility only. - readU8(is); // version - m_prop.nametag_color = readARGB8(is); - if (m_nametag != NULL) { - m_nametag->nametag_color = m_prop.nametag_color; - v3f pos; - pos.Y = m_prop.collisionbox.MaxEdge.Y + 0.3f; - m_nametag->nametag_pos = pos; - } - } else if (cmd == GENERIC_CMD_SPAWN_INFANT) { + } else if (cmd == AO_CMD_SPAWN_INFANT) { u16 child_id = readU16(is); u8 type = readU8(is); // maybe this will be useful later (void)type; addAttachmentChild(child_id); + } else if (cmd == AO_CMD_OBSOLETE1) { + // Don't do anything and also don't log a warning } else { warningstream << FUNCTION_NAME << ": unknown command or outdated client \"" @@ -1684,13 +1818,11 @@ bool GenericCAO::directReportPunch(v3f dir, const ItemStack *punchitem, v2f(m_prop.visual_size.X, m_prop.visual_size.Y) * BS); m_env->addSimpleObject(simple); } - // TODO: Execute defined fast response - // Flashing shall suffice as there is no definition - if (m_reset_textures_timer < 0) { + if (m_reset_textures_timer < 0 && !m_prop.damage_texture_modifier.empty()) { m_reset_textures_timer = 0.05; if (result.damage >= 2) m_reset_textures_timer += 0.05 * result.damage; - updateTextures(m_current_texture_modifier + "^[brighten"); + updateTextures(m_current_texture_modifier + m_prop.damage_texture_modifier); } } diff --git a/src/client/content_cao.h b/src/client/content_cao.h index 7c29cbf17..974ff9a1e 100644 --- a/src/client/content_cao.h +++ b/src/client/content_cao.h @@ -130,6 +130,8 @@ private: // Settings bool m_enable_shaders = false; + bool visualExpiryRequired(const ObjectProperties &newprops) const; + public: GenericCAO(Client *client, ClientEnvironment *env); @@ -186,10 +188,11 @@ public: return m_matrixnode->getRelativeTransformationMatrix(); } - inline const core::matrix4 &getAbsolutePosRotMatrix() const + inline const core::matrix4 *getAbsolutePosRotMatrix() const { - assert(m_matrixnode); - return m_matrixnode->getAbsoluteTransformation(); + if (!m_matrixnode) + return nullptr; + return &m_matrixnode->getAbsoluteTransformation(); } inline f32 getStepHeight() const @@ -234,13 +237,16 @@ public: m_visuals_expired = true; } - void updateLight(u8 light_at_pos); - - void updateLightNoCheck(u8 light_at_pos); + void updateLight(u32 day_night_ratio); void setNodeLight(u8 light); - v3s16 getLightPosition(); + /* Get light position(s). + * returns number of positions written into pos[], which must have space + * for at least 3 vectors. */ + u16 getLightPosition(v3s16 *pos); + + void updateNametag(); void updateNodePos(); diff --git a/src/client/content_mapblock.cpp b/src/client/content_mapblock.cpp index 9b4fd221e..3d06584c4 100644 --- a/src/client/content_mapblock.cpp +++ b/src/client/content_mapblock.cpp @@ -17,6 +17,7 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include <cmath> #include "content_mapblock.h" #include "util/numeric.h" #include "util/directiontables.h" @@ -366,6 +367,7 @@ void MapblockMeshGenerator::generateCuboidTextureCoords(const aabb3f &box, f32 * void MapblockMeshGenerator::drawAutoLightedCuboid(aabb3f box, const f32 *txc, TileSpec *tiles, int tile_count) { + bool scale = std::fabs(f->visual_scale - 1.0f) > 1e-3f; f32 texture_coord_buf[24]; f32 dx1 = box.MinEdge.X; f32 dy1 = box.MinEdge.Y; @@ -373,6 +375,14 @@ void MapblockMeshGenerator::drawAutoLightedCuboid(aabb3f box, const f32 *txc, f32 dx2 = box.MaxEdge.X; f32 dy2 = box.MaxEdge.Y; f32 dz2 = box.MaxEdge.Z; + if (scale) { + if (!txc) { // generate texture coords before scaling + generateCuboidTextureCoords(box, texture_coord_buf); + txc = texture_coord_buf; + } + box.MinEdge *= f->visual_scale; + box.MaxEdge *= f->visual_scale; + } box.MinEdge += origin; box.MaxEdge += origin; if (!txc) { @@ -405,8 +415,8 @@ void MapblockMeshGenerator::prepareLiquidNodeDrawing() MapNode ntop = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(p.X, p.Y + 1, p.Z)); MapNode nbottom = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(p.X, p.Y - 1, p.Z)); - c_flowing = nodedef->getId(f->liquid_alternative_flowing); - c_source = nodedef->getId(f->liquid_alternative_source); + c_flowing = f->liquid_alternative_flowing_id; + c_source = f->liquid_alternative_source_id; top_is_same_liquid = (ntop.getContent() == c_flowing) || (ntop.getContent() == c_source); draw_liquid_bottom = (nbottom.getContent() != c_flowing) && (nbottom.getContent() != c_source); if (draw_liquid_bottom) { @@ -512,8 +522,7 @@ f32 MapblockMeshGenerator::getCornerLevel(int i, int k) return 0; } -void MapblockMeshGenerator::drawLiquidSides() -{ +namespace { struct LiquidFaceDesc { v3s16 dir; // XZ v3s16 p[2]; // XZ only; 1 means +, 0 means - @@ -521,20 +530,23 @@ void MapblockMeshGenerator::drawLiquidSides() struct UV { int u, v; }; - static const LiquidFaceDesc base_faces[4] = { + static const LiquidFaceDesc liquid_base_faces[4] = { {v3s16( 1, 0, 0), {v3s16(1, 0, 1), v3s16(1, 0, 0)}}, {v3s16(-1, 0, 0), {v3s16(0, 0, 0), v3s16(0, 0, 1)}}, {v3s16( 0, 0, 1), {v3s16(0, 0, 1), v3s16(1, 0, 1)}}, {v3s16( 0, 0, -1), {v3s16(1, 0, 0), v3s16(0, 0, 0)}}, }; - static const UV base_vertices[4] = { + static const UV liquid_base_vertices[4] = { {0, 1}, {1, 1}, {1, 0}, {0, 0} }; +} - for (const auto &face : base_faces) { +void MapblockMeshGenerator::drawLiquidSides() +{ + for (const auto &face : liquid_base_faces) { const NeighborData &neighbor = liquid_neighbors[face.dir.Z + 1][face.dir.X + 1]; // No face between nodes of the same liquid, unless there is node @@ -554,7 +566,7 @@ void MapblockMeshGenerator::drawLiquidSides() video::S3DVertex vertices[4]; for (int j = 0; j < 4; j++) { - const UV &vertex = base_vertices[j]; + const UV &vertex = liquid_base_vertices[j]; const v3s16 &base = face.p[vertex.u]; float v = vertex.v; @@ -1193,15 +1205,14 @@ bool MapblockMeshGenerator::isSameRail(v3s16 dir) (def2.getGroup(raillike_groupname) == raillike_group)); } -void MapblockMeshGenerator::drawRaillikeNode() -{ - static const v3s16 direction[4] = { +namespace { + static const v3s16 rail_direction[4] = { v3s16( 0, 0, 1), v3s16( 0, 0, -1), v3s16(-1, 0, 0), v3s16( 1, 0, 0), }; - static const int slope_angle[4] = {0, 180, 90, -90}; + static const int rail_slope_angle[4] = {0, 180, 90, -90}; enum RailTile { straight, @@ -1214,8 +1225,8 @@ void MapblockMeshGenerator::drawRaillikeNode() int angle; }; static const RailDesc rail_kinds[16] = { - // +x -x -z +z - //------------- + // +x -x -z +z + //------------- {straight, 0}, // . . . . {straight, 0}, // . . . +Z {straight, 0}, // . . -Z . @@ -1233,7 +1244,10 @@ void MapblockMeshGenerator::drawRaillikeNode() {junction, 270}, // +X -X -Z . { cross, 0}, // +X -X -Z +Z }; +} +void MapblockMeshGenerator::drawRaillikeNode() +{ raillike_group = nodedef->get(n).getGroup(raillike_groupname); int code = 0; @@ -1241,14 +1255,14 @@ void MapblockMeshGenerator::drawRaillikeNode() int tile_index; bool sloped = false; for (int dir = 0; dir < 4; dir++) { - bool rail_above = isSameRail(direction[dir] + v3s16(0, 1, 0)); + bool rail_above = isSameRail(rail_direction[dir] + v3s16(0, 1, 0)); if (rail_above) { sloped = true; - angle = slope_angle[dir]; + angle = rail_slope_angle[dir]; } if (rail_above || - isSameRail(direction[dir]) || - isSameRail(direction[dir] + v3s16(0, -1, 0))) + isSameRail(rail_direction[dir]) || + isSameRail(rail_direction[dir] + v3s16(0, -1, 0))) code |= 1 << dir; } @@ -1276,9 +1290,8 @@ void MapblockMeshGenerator::drawRaillikeNode() drawQuad(vertices); } -void MapblockMeshGenerator::drawNodeboxNode() -{ - static const v3s16 tile_dirs[6] = { +namespace { + static const v3s16 nodebox_tile_dirs[6] = { v3s16(0, 1, 0), v3s16(0, -1, 0), v3s16(1, 0, 0), @@ -1288,7 +1301,7 @@ void MapblockMeshGenerator::drawNodeboxNode() }; // we have this order for some reason... - static const v3s16 connection_dirs[6] = { + static const v3s16 nodebox_connection_dirs[6] = { v3s16( 0, 1, 0), // top v3s16( 0, -1, 0), // bottom v3s16( 0, 0, -1), // front @@ -1296,19 +1309,22 @@ void MapblockMeshGenerator::drawNodeboxNode() v3s16( 0, 0, 1), // back v3s16( 1, 0, 0), // right }; +} +void MapblockMeshGenerator::drawNodeboxNode() +{ TileSpec tiles[6]; for (int face = 0; face < 6; face++) { // Handles facedir rotation for textures - getTile(tile_dirs[face], &tiles[face]); + getTile(nodebox_tile_dirs[face], &tiles[face]); } // locate possible neighboring nodes to connect to - int neighbors_set = 0; + u8 neighbors_set = 0; if (f->node_box.type == NODEBOX_CONNECTED) { for (int dir = 0; dir != 6; dir++) { - int flag = 1 << dir; - v3s16 p2 = blockpos_nodes + p + connection_dirs[dir]; + u8 flag = 1 << dir; + v3s16 p2 = blockpos_nodes + p + nodebox_connection_dirs[dir]; MapNode n2 = data->m_vmanip.getNodeNoEx(p2); if (nodedef->nodeboxConnects(n, n2, flag)) neighbors_set |= flag; @@ -1317,7 +1333,7 @@ void MapblockMeshGenerator::drawNodeboxNode() std::vector<aabb3f> boxes; n.getNodeBoxes(nodedef, &boxes, neighbors_set); - for (const auto &box : boxes) + for (auto &box : boxes) drawAutoLightedCuboid(box, nullptr, tiles, 6); } diff --git a/src/client/filecache.cpp b/src/client/filecache.cpp index 3d1b302a8..46bbe4059 100644 --- a/src/client/filecache.cpp +++ b/src/client/filecache.cpp @@ -82,8 +82,16 @@ bool FileCache::update(const std::string &name, const std::string &data) std::string path = m_dir + DIR_DELIM + name; return updateByPath(path, data); } + bool FileCache::load(const std::string &name, std::ostream &os) { std::string path = m_dir + DIR_DELIM + name; return loadByPath(path, os); } + +bool FileCache::exists(const std::string &name) +{ + std::string path = m_dir + DIR_DELIM + name; + std::ifstream fis(path.c_str(), std::ios_base::binary); + return fis.good(); +} diff --git a/src/client/filecache.h b/src/client/filecache.h index 96e4c8ba1..ea6afc4b2 100644 --- a/src/client/filecache.h +++ b/src/client/filecache.h @@ -33,6 +33,7 @@ public: bool update(const std::string &name, const std::string &data); bool load(const std::string &name, std::ostream &os); + bool exists(const std::string &name); private: std::string m_dir; diff --git a/src/client/fontengine.cpp b/src/client/fontengine.cpp index 2b5841cd8..61d52cc2f 100644 --- a/src/client/fontengine.cpp +++ b/src/client/fontengine.cpp @@ -239,7 +239,7 @@ void FontEngine::updateSkin() FATAL_ERROR_IF(font == NULL, "Could not create/get font"); u32 text_height = font->getDimension(L"Hello, world!").Height; - infostream << "text_height=" << text_height << std::endl; + infostream << "FontEngine: measured text_height=" << text_height << std::endl; } /******************************************************************************/ diff --git a/src/client/game.cpp b/src/client/game.cpp index 0201ded69..069c482ca 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -266,6 +266,7 @@ class SoundMaker public: bool makes_footstep_sound; float m_player_step_timer; + float m_player_jump_timer; SimpleSoundSpec m_player_step_sound; SimpleSoundSpec m_player_leftpunch_sound; @@ -275,7 +276,8 @@ public: m_sound(sound), m_ndef(ndef), makes_footstep_sound(true), - m_player_step_timer(0) + m_player_step_timer(0.0f), + m_player_jump_timer(0.0f) { } @@ -288,6 +290,14 @@ public: } } + void playPlayerJump() + { + if (m_player_jump_timer <= 0.0f) { + m_player_jump_timer = 0.2f; + m_sound->playSound(SimpleSoundSpec("player_jump", 0.5f), false); + } + } + static void viewBobbingStep(MtEvent *e, void *data) { SoundMaker *sm = (SoundMaker *)data; @@ -302,7 +312,8 @@ public: static void playerJump(MtEvent *e, void *data) { - //SoundMaker *sm = (SoundMaker*)data; + SoundMaker *sm = (SoundMaker *)data; + sm->playPlayerJump(); } static void cameraPunchLeft(MtEvent *e, void *data) @@ -351,6 +362,7 @@ public: void step(float dtime) { m_player_step_timer -= dtime; + m_player_jump_timer -= dtime; } }; @@ -843,6 +855,7 @@ private: SoundMaker *soundmaker = nullptr; ChatBackend *chat_backend = nullptr; + LogOutputBuffer m_chat_log_buf; EventManager *eventmgr = nullptr; QuicktuneShortcutter *quicktune = nullptr; @@ -914,6 +927,7 @@ private: }; Game::Game() : + m_chat_log_buf(g_logger), m_game_ui(new GameUI()) { g_settings->registerChangedCallback("doubletap_jump", @@ -1043,7 +1057,7 @@ bool Game::startup(bool *kill, m_invert_mouse = g_settings->getBool("invert_mouse"); m_first_loop_after_window_activation = true; - g_translations->clear(); + g_client_translations->clear(); if (!init(map_dir, address, port, gamespec)) return false; @@ -1159,6 +1173,10 @@ void Game::shutdown() if (formspec) formspec->quitMenu(); +#ifdef HAVE_TOUCHSCREENGUI + g_touchscreengui->hide(); +#endif + showOverlayMessage(N_("Shutting down..."), 0, 0, false); if (clouds) @@ -1180,6 +1198,7 @@ void Game::shutdown() chat_backend->addMessage(L"", L"# Disconnected."); chat_backend->addMessage(L"", L""); + m_chat_log_buf.clear(); if (client) { client->Stop(); @@ -1237,7 +1256,7 @@ bool Game::init( bool Game::initSound() { #if USE_SOUND - if (g_settings->getBool("enable_sound")) { + if (g_settings->getBool("enable_sound") && g_sound_manager_singleton.get()) { infostream << "Attempting to use OpenAL audio" << std::endl; sound = createOpenALSoundManager(g_sound_manager_singleton.get(), &soundfetcher); if (!sound) @@ -1290,7 +1309,6 @@ bool Game::createSingleplayerServer(const std::string &map_dir, } server = new Server(map_dir, gamespec, simple_singleplayer_mode, bind_addr, false); - server->init(); server->start(); return true; @@ -1407,8 +1425,11 @@ bool Game::createClient(const std::string &playername, } mapper = client->getMinimap(); - if (mapper) + if (mapper) { mapper->setMinimapMode(MINIMAP_MODE_OFF); + if (client->modsLoaded()) + client->getScript()->on_minimap_ready(mapper); + } return true; } @@ -1553,7 +1574,8 @@ bool Game::connectToServer(const std::string &playername, } else { registration_confirmation_shown = true; (new GUIConfirmRegistration(guienv, guienv->getRootGUIElement(), -1, - &g_menumgr, client, playername, password, connection_aborted))->drop(); + &g_menumgr, client, playername, password, + connection_aborted, texture_src))->drop(); } } else { wait_time += dtime; @@ -1708,19 +1730,19 @@ inline bool Game::handleCallbacks() if (g_gamecallback->changepassword_requested) { (new GUIPasswordChange(guienv, guiroot, -1, - &g_menumgr, client))->drop(); + &g_menumgr, client, texture_src))->drop(); g_gamecallback->changepassword_requested = false; } if (g_gamecallback->changevolume_requested) { (new GUIVolumeChange(guienv, guiroot, -1, - &g_menumgr))->drop(); + &g_menumgr, texture_src))->drop(); g_gamecallback->changevolume_requested = false; } if (g_gamecallback->keyconfig_requested) { (new GUIKeyChangeMenu(guienv, guiroot, -1, - &g_menumgr))->drop(); + &g_menumgr, texture_src))->drop(); g_gamecallback->keyconfig_requested = false; } @@ -1906,29 +1928,47 @@ void Game::processKeyInput() toggleFast(); } else if (wasKeyDown(KeyType::NOCLIP)) { toggleNoClip(); +#if USE_SOUND } else if (wasKeyDown(KeyType::MUTE)) { - bool new_mute_sound = !g_settings->getBool("mute_sound"); - g_settings->setBool("mute_sound", new_mute_sound); - if (new_mute_sound) - m_game_ui->showTranslatedStatusText("Sound muted"); - else - m_game_ui->showTranslatedStatusText("Sound unmuted"); + if (g_settings->getBool("enable_sound")) { + bool new_mute_sound = !g_settings->getBool("mute_sound"); + g_settings->setBool("mute_sound", new_mute_sound); + if (new_mute_sound) + m_game_ui->showTranslatedStatusText("Sound muted"); + else + m_game_ui->showTranslatedStatusText("Sound unmuted"); + } else { + m_game_ui->showTranslatedStatusText("Sound system is disabled"); + } } else if (wasKeyDown(KeyType::INC_VOLUME)) { - float new_volume = rangelim(g_settings->getFloat("sound_volume") + 0.1f, 0.0f, 1.0f); - wchar_t buf[100]; - g_settings->setFloat("sound_volume", new_volume); - const wchar_t *str = wgettext("Volume changed to %d%%"); - swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, myround(new_volume * 100)); - delete[] str; - m_game_ui->showStatusText(buf); + if (g_settings->getBool("enable_sound")) { + float new_volume = rangelim(g_settings->getFloat("sound_volume") + 0.1f, 0.0f, 1.0f); + wchar_t buf[100]; + g_settings->setFloat("sound_volume", new_volume); + const wchar_t *str = wgettext("Volume changed to %d%%"); + swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, myround(new_volume * 100)); + delete[] str; + m_game_ui->showStatusText(buf); + } else { + m_game_ui->showTranslatedStatusText("Sound system is disabled"); + } } else if (wasKeyDown(KeyType::DEC_VOLUME)) { - float new_volume = rangelim(g_settings->getFloat("sound_volume") - 0.1f, 0.0f, 1.0f); - wchar_t buf[100]; - g_settings->setFloat("sound_volume", new_volume); - const wchar_t *str = wgettext("Volume changed to %d%%"); - swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, myround(new_volume * 100)); - delete[] str; - m_game_ui->showStatusText(buf); + if (g_settings->getBool("enable_sound")) { + float new_volume = rangelim(g_settings->getFloat("sound_volume") - 0.1f, 0.0f, 1.0f); + wchar_t buf[100]; + g_settings->setFloat("sound_volume", new_volume); + const wchar_t *str = wgettext("Volume changed to %d%%"); + swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, myround(new_volume * 100)); + delete[] str; + m_game_ui->showStatusText(buf); + } else { + m_game_ui->showTranslatedStatusText("Sound system is disabled"); + } +#else + } else if (wasKeyDown(KeyType::MUTE) || wasKeyDown(KeyType::INC_VOLUME) + || wasKeyDown(KeyType::DEC_VOLUME)) { + m_game_ui->showTranslatedStatusText("Sound system is not supported on this build"); +#endif } else if (wasKeyDown(KeyType::CINEMATIC)) { toggleCinematic(); } else if (wasKeyDown(KeyType::SCREENSHOT)) { @@ -2010,7 +2050,6 @@ void Game::processItemSelection(u16 *new_playeritem) for (u16 i = 0; i <= max_item; i++) { if (wasKeyDown((GameKeyType) (KeyType::SLOT_1 + i))) { *new_playeritem = i; - infostream << "Selected item: " << new_playeritem << std::endl; break; } } @@ -2039,7 +2078,7 @@ void Game::openInventory() if (!player || !player->getCAO()) return; - infostream << "the_game: " << "Launching inventory" << std::endl; + infostream << "Game: Launching inventory" << std::endl; PlayerInventoryFormSource *fs_src = new PlayerInventoryFormSource(client); @@ -2451,7 +2490,7 @@ void Game::updatePlayerControl(const CameraOrientation &cam) input->joystick.getAxisWithoutDead(JA_FORWARD_MOVE) ); - u32 keypress_bits = + u32 keypress_bits = ( ( (u32)(isKeyDown(KeyType::FORWARD) & 0x1) << 0) | ( (u32)(isKeyDown(KeyType::BACKWARD) & 0x1) << 1) | ( (u32)(isKeyDown(KeyType::LEFT) & 0x1) << 2) | @@ -2460,7 +2499,8 @@ void Game::updatePlayerControl(const CameraOrientation &cam) ( (u32)(isKeyDown(KeyType::SPECIAL1) & 0x1) << 5) | ( (u32)(isKeyDown(KeyType::SNEAK) & 0x1) << 6) | ( (u32)(input->getLeftState() & 0x1) << 7) | - ( (u32)(input->getRightState() & 0x1) << 8 + ( (u32)(input->getRightState() & 0x1) << 8) | + ( (u32)(isKeyDown(KeyType::ZOOM) & 0x1) << 9) ); #ifdef ANDROID @@ -2640,6 +2680,7 @@ void Game::handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam) delete event->hudadd.offset; delete event->hudadd.world_pos; delete event->hudadd.size; + delete event->hudadd.text2; return; } @@ -2657,6 +2698,7 @@ void Game::handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam) e->world_pos = *event->hudadd.world_pos; e->size = *event->hudadd.size; e->z_index = event->hudadd.z_index; + e->text2 = *event->hudadd.text2; hud_server_to_client[server_id] = player->addHud(e); delete event->hudadd.pos; @@ -2667,6 +2709,7 @@ void Game::handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam) delete event->hudadd.offset; delete event->hudadd.world_pos; delete event->hudadd.size; + delete event->hudadd.text2; } void Game::handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam) @@ -2739,6 +2782,10 @@ void Game::handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *ca case HUD_STAT_Z_INDEX: e->z_index = event->hudchange.data; break; + + case HUD_STAT_TEXT2: + e->text2 = *event->hudchange.sdata; + break; } delete event->hudchange.v3fdata; @@ -2764,11 +2811,11 @@ void Game::handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam) // Shows the mesh skybox sky->setVisible(true); // Update mesh based skybox colours if applicable. - sky->setSkyColors(*event->set_sky); + sky->setSkyColors(event->set_sky->sky_color); sky->setHorizonTint( - event->set_sky->sun_tint, - event->set_sky->moon_tint, - event->set_sky->tint_type + event->set_sky->fog_sun_tint, + event->set_sky->fog_moon_tint, + event->set_sky->fog_tint_type ); } else if (event->set_sky->type == "skybox" && event->set_sky->textures.size() == 6) { @@ -2778,9 +2825,9 @@ void Game::handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam) sky->setFallbackBgColor(event->set_sky->bgcolor); // Set sunrise and sunset fog tinting: sky->setHorizonTint( - event->set_sky->sun_tint, - event->set_sky->moon_tint, - event->set_sky->tint_type + event->set_sky->fog_sun_tint, + event->set_sky->fog_moon_tint, + event->set_sky->fog_tint_type ); // Add textures to skybox. for (int i = 0; i < 6; i++) @@ -2864,18 +2911,9 @@ void Game::processClientEvents(CameraOrientation *cam) void Game::updateChat(f32 dtime, const v2u32 &screensize) { - // Add chat log output for errors to be shown in chat - static LogOutputBuffer chat_log_error_buf(g_logger, LL_ERROR); - // Get new messages from error log buffer - while (!chat_log_error_buf.empty()) { - std::wstring error_message = utf8_to_wide(chat_log_error_buf.get()); - if (!g_settings->getBool("disable_escape_sequences")) { - error_message.insert(0, L"\x1b(c@red)"); - error_message.append(L"\x1b(c@white)"); - } - chat_backend->addMessage(L"", error_message); - } + while (!m_chat_log_buf.empty()) + chat_backend->addMessage(L"", utf8_to_wide(m_chat_log_buf.get())); // Get new messages from client std::wstring message; @@ -2996,16 +3034,8 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug) { LocalPlayer *player = client->getEnv().getLocalPlayer(); - v3f player_position = player->getPosition(); - v3f player_eye_position = player->getEyePosition(); - v3f camera_position = camera->getPosition(); - v3f camera_direction = camera->getDirection(); - v3s16 camera_offset = camera->getOffset(); - - if (camera->getCameraMode() == CAMERA_MODE_FIRST) - player_eye_position += player->eye_offset_first; - else - player_eye_position += player->eye_offset_third; + const v3f camera_direction = camera->getDirection(); + const v3s16 camera_offset = camera->getOffset(); /* Calculate what block is the crosshair pointing to @@ -3019,13 +3049,22 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug) core::line3d<f32> shootline; - if (camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT) { - shootline = core::line3d<f32>(player_eye_position, - player_eye_position + camera_direction * BS * d); - } else { + switch (camera->getCameraMode()) { + case CAMERA_MODE_FIRST: + // Shoot from camera position, with bobbing + shootline.start = camera->getPosition(); + break; + case CAMERA_MODE_THIRD: + // Shoot from player head, no bobbing + shootline.start = camera->getHeadPosition(); + break; + case CAMERA_MODE_THIRD_FRONT: + shootline.start = camera->getHeadPosition(); // prevent player pointing anything in front-view - shootline = core::line3d<f32>(camera_position, camera_position); + d = 0; + break; } + shootline.end = shootline.start + camera_direction * BS * d; #ifdef HAVE_TOUCHSCREENGUI @@ -3112,6 +3151,7 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug) } else if (pointed.type == POINTEDTHING_NODE) { handlePointingAtNode(pointed, selected_item, hand_item, dtime); } else if (pointed.type == POINTEDTHING_OBJECT) { + v3f player_position = player->getPosition(); handlePointingAtObject(pointed, tool_item, player_position, show_debug); } else if (input->getLeftState()) { // When button is held down in air, show continuous animation @@ -4147,8 +4187,12 @@ void Game::showPauseMenu() } #ifndef __ANDROID__ - os << "button_exit[4," << (ypos++) << ";3,0.5;btn_sound;" - << strgettext("Sound Volume") << "]"; +#if USE_SOUND + if (g_settings->getBool("enable_sound")) { + os << "button_exit[4," << (ypos++) << ";3,0.5;btn_sound;" + << strgettext("Sound Volume") << "]"; + } +#endif os << "button_exit[4," << (ypos++) << ";3,0.5;btn_key_config;" << strgettext("Change Keys") << "]"; #endif @@ -4245,7 +4289,6 @@ void the_game(bool *kill, reconnect_requested, &chat_backend, gamespec, simple_singleplayer_mode)) { game.run(); - game.shutdown(); } } catch (SerializationError &e) { @@ -4261,4 +4304,5 @@ void the_game(bool *kill, strgettext("\nCheck debug.txt for details."); errorstream << error_message << std::endl; } + game.shutdown(); } diff --git a/src/client/gameui.cpp b/src/client/gameui.cpp index 138dfb4da..c216f405d 100644 --- a/src/client/gameui.cpp +++ b/src/client/gameui.cpp @@ -76,6 +76,11 @@ void GameUI::init() m_guitext_chat = gui::StaticText::add(guienv, L"", core::rect<s32>(0, 0, 0, 0), //false, false); // Disable word wrap as of now false, true, guiroot); + u16 chat_font_size = g_settings->getU16("chat_font_size"); + if (chat_font_size != 0) { + m_guitext_chat->setOverrideFont(g_fontengine->getFont( + chat_font_size, FM_Unspecified)); + } // Profiler text (size is updated when text is updated) m_guitext_profiler = gui::StaticText::add(guienv, L"<Profiler>", @@ -128,9 +133,9 @@ void GameUI::update(const RunStats &stats, Client *client, MapDrawControl *draw_ << "pos: (" << (player_position.X / BS) << ", " << (player_position.Y / BS) << ", " << (player_position.Z / BS) - << ") | yaw: " << (wrapDegrees_0_360(cam.camera_yaw)) << "° " + << ") | yaw: " << (wrapDegrees_0_360(cam.camera_yaw)) << "\xC2\xB0 " << yawToDirectionString(cam.camera_yaw) - << " | pitch: " << (-wrapDegrees_180(cam.camera_pitch)) << "°" + << " | pitch: " << (-wrapDegrees_180(cam.camera_pitch)) << "\xC2\xB0" << " | seed: " << ((u64)client->getMapSeed()); if (pointed_old.type == POINTEDTHING_NODE) { @@ -213,7 +218,6 @@ void GameUI::showTranslatedStatusText(const char *str) void GameUI::setChatText(const EnrichedString &chat_text, u32 recent_chat_count) { - setStaticText(m_guitext_chat, chat_text); // Update gui element size and position s32 chat_y = 5; @@ -221,16 +225,15 @@ void GameUI::setChatText(const EnrichedString &chat_text, u32 recent_chat_count) if (m_flags.show_debug) chat_y += 2 * g_fontengine->getLineHeight(); - // first pass to calculate height of text to be set const v2u32 &window_size = RenderingEngine::get_instance()->getWindowSize(); - s32 width = std::min(g_fontengine->getTextWidth(chat_text.c_str()) + 10, - window_size.X - 20); - m_guitext_chat->setRelativePosition(core::rect<s32>(10, chat_y, width, - chat_y + window_size.Y)); - - // now use real height of text and adjust rect according to this size - m_guitext_chat->setRelativePosition(core::rect<s32>(10, chat_y, width, - chat_y + m_guitext_chat->getTextHeight())); + + core::rect<s32> chat_size(10, chat_y, + window_size.X - 20, 0); + chat_size.LowerRightCorner.Y = std::min((s32)window_size.Y, + m_guitext_chat->getTextHeight() + chat_y); + + m_guitext_chat->setRelativePosition(chat_size); + setStaticText(m_guitext_chat, chat_text); m_recent_chat_count = recent_chat_count; } diff --git a/src/client/hud.cpp b/src/client/hud.cpp index 37de6640b..31e633bc2 100644 --- a/src/client/hud.cpp +++ b/src/client/hud.cpp @@ -51,6 +51,7 @@ Hud::Hud(gui::IGUIEnvironment *guienv, Client *client, LocalPlayer *player, this->inventory = inventory; m_hud_scaling = g_settings->getFloat("hud_scaling"); + m_scale_factor = m_hud_scaling * RenderingEngine::getDisplayDensity(); m_hotbar_imagesize = std::floor(HOTBAR_IMAGE_SIZE * RenderingEngine::getDisplayDensity() + 0.5f); m_hotbar_imagesize *= m_hud_scaling; @@ -213,9 +214,7 @@ void Hud::drawItems(v2s32 upperleftpos, v2s32 screen_offset, s32 itemcount, } // Position of upper left corner of bar - v2s32 pos = screen_offset; - pos.X *= m_hud_scaling * RenderingEngine::getDisplayDensity(); - pos.Y *= m_hud_scaling * RenderingEngine::getDisplayDensity(); + v2s32 pos = screen_offset * m_scale_factor; pos += upperleftpos; // Store hotbar_image in member variable, used by drawItem() @@ -272,6 +271,25 @@ void Hud::drawItems(v2s32 upperleftpos, v2s32 screen_offset, s32 itemcount, } } +// Calculates screen position of waypoint. Returns true if waypoint is visible (in front of the player), else false. +bool Hud::calculateScreenPos(const v3s16 &camera_offset, HudElement *e, v2s32 *pos) +{ + v3f w_pos = e->world_pos * BS; + scene::ICameraSceneNode* camera = + RenderingEngine::get_scene_manager()->getActiveCamera(); + w_pos -= intToFloat(camera_offset, BS); + core::matrix4 trans = camera->getProjectionMatrix(); + trans *= camera->getViewMatrix(); + f32 transformed_pos[4] = { w_pos.X, w_pos.Y, w_pos.Z, 1.0f }; + trans.multiplyWith1x4Matrix(transformed_pos); + if (transformed_pos[3] < 0) + return false; + f32 zDiv = transformed_pos[3] == 0.0f ? 1.0f : + core::reciprocal(transformed_pos[3]); + pos->X = m_screensize.X * (0.5 * transformed_pos[0] * zDiv + 0.5); + pos->Y = m_screensize.Y * (0.5 - transformed_pos[1] * zDiv * 0.5); + return true; +} void Hud::drawLuaElements(const v3s16 &camera_offset) { @@ -299,43 +317,47 @@ void Hud::drawLuaElements(const v3s16 &camera_offset) v2s32 pos(floor(e->pos.X * (float) m_screensize.X + 0.5), floor(e->pos.Y * (float) m_screensize.Y + 0.5)); switch (e->type) { - case HUD_ELEM_IMAGE: { - video::ITexture *texture = tsrc->getTexture(e->text); - if (!texture) - continue; - - const video::SColor color(255, 255, 255, 255); - const video::SColor colors[] = {color, color, color, color}; - core::dimension2di imgsize(texture->getOriginalSize()); - v2s32 dstsize(imgsize.Width * e->scale.X, - imgsize.Height * e->scale.Y); - if (e->scale.X < 0) - dstsize.X = m_screensize.X * (e->scale.X * -0.01); - if (e->scale.Y < 0) - dstsize.Y = m_screensize.Y * (e->scale.Y * -0.01); - v2s32 offset((e->align.X - 1.0) * dstsize.X / 2, - (e->align.Y - 1.0) * dstsize.Y / 2); - core::rect<s32> rect(0, 0, dstsize.X, dstsize.Y); - rect += pos + offset + v2s32(e->offset.X, e->offset.Y); - draw2DImageFilterScaled(driver, texture, rect, - core::rect<s32>(core::position2d<s32>(0,0), imgsize), - NULL, colors, true); - break; } case HUD_ELEM_TEXT: { + irr::gui::IGUIFont *textfont = font; + unsigned int font_size = g_fontengine->getDefaultFontSize(); + + if (e->size.X > 0) + font_size *= e->size.X; + + if (font_size != g_fontengine->getDefaultFontSize()) + textfont = g_fontengine->getFont(font_size); + video::SColor color(255, (e->number >> 16) & 0xFF, (e->number >> 8) & 0xFF, (e->number >> 0) & 0xFF); - core::rect<s32> size(0, 0, e->scale.X, text_height * e->scale.Y); std::wstring text = unescape_translate(utf8_to_wide(e->text)); - core::dimension2d<u32> textsize = font->getDimension(text.c_str()); + core::dimension2d<u32> textsize = textfont->getDimension(text.c_str()); +#ifdef __ANDROID__ + // The text size on Android is not proportional with the actual scaling + irr::gui::IGUIFont *font_scaled = font_size <= 3 ? + textfont : g_fontengine->getFont(font_size - 3); + if (e->offset.X < -20) + textsize = font_scaled->getDimension(text.c_str()); +#endif v2s32 offset((e->align.X - 1.0) * (textsize.Width / 2), (e->align.Y - 1.0) * (textsize.Height / 2)); - v2s32 offs(e->offset.X, e->offset.Y); - font->draw(text.c_str(), size + pos + offset + offs, color); + core::rect<s32> size(0, 0, e->scale.X * m_scale_factor, + text_height * e->scale.Y * m_scale_factor); + v2s32 offs(e->offset.X * m_scale_factor, + e->offset.Y * m_scale_factor); +#ifdef __ANDROID__ + if (e->offset.X < -20) + font_scaled->draw(text.c_str(), size + pos + offset + offs, color); + else +#endif + { + textfont->draw(text.c_str(), size + pos + offset + offs, color); + } break; } case HUD_ELEM_STATBAR: { v2s32 offs(e->offset.X, e->offset.Y); - drawStatbar(pos, HUD_CORNER_UPPER, e->dir, e->text, e->number, offs, e->size); + drawStatbar(pos, HUD_CORNER_UPPER, e->dir, e->text, e->text2, + e->number, e->item, offs, e->size); break; } case HUD_ELEM_INVENTORY: { InventoryList *inv = inventory->getList(e->text); @@ -343,34 +365,59 @@ void Hud::drawLuaElements(const v3s16 &camera_offset) inv, e->item, e->dir); break; } case HUD_ELEM_WAYPOINT: { - v3f p_pos = player->getPosition() / BS; - v3f w_pos = e->world_pos * BS; - float distance = std::floor(10 * p_pos.getDistanceFrom(e->world_pos)) / - 10.0f; - scene::ICameraSceneNode* camera = - RenderingEngine::get_scene_manager()->getActiveCamera(); - w_pos -= intToFloat(camera_offset, BS); - core::matrix4 trans = camera->getProjectionMatrix(); - trans *= camera->getViewMatrix(); - f32 transformed_pos[4] = { w_pos.X, w_pos.Y, w_pos.Z, 1.0f }; - trans.multiplyWith1x4Matrix(transformed_pos); - if (transformed_pos[3] < 0) + if (!calculateScreenPos(camera_offset, e, &pos)) break; - f32 zDiv = transformed_pos[3] == 0.0f ? 1.0f : - core::reciprocal(transformed_pos[3]); - pos.X = m_screensize.X * (0.5 * transformed_pos[0] * zDiv + 0.5); - pos.Y = m_screensize.Y * (0.5 - transformed_pos[1] * zDiv * 0.5); + v3f p_pos = player->getPosition() / BS; + pos += v2s32(e->offset.X, e->offset.Y); video::SColor color(255, (e->number >> 16) & 0xFF, (e->number >> 8) & 0xFF, (e->number >> 0) & 0xFF); - core::rect<s32> size(0, 0, 200, 2 * text_height); std::wstring text = unescape_translate(utf8_to_wide(e->name)); - font->draw(text.c_str(), size + pos, color); - std::ostringstream os; - os << distance << e->text; - text = unescape_translate(utf8_to_wide(os.str())); - pos.Y += text_height; - font->draw(text.c_str(), size + pos, color); + const std::string &unit = e->text; + // waypoints reuse the item field to store precision, item = precision + 1 + u32 item = e->item; + float precision = (item == 0) ? 10.0f : (item - 1.f); + bool draw_precision = precision > 0; + + core::rect<s32> bounds(0, 0, font->getDimension(text.c_str()).Width, (draw_precision ? 2:1) * text_height); + pos.Y += (e->align.Y - 1.0) * bounds.getHeight() / 2; + bounds += pos; + font->draw(text.c_str(), bounds + v2s32((e->align.X - 1.0) * bounds.getWidth() / 2, 0), color); + if (draw_precision) { + std::ostringstream os; + float distance = std::floor(precision * p_pos.getDistanceFrom(e->world_pos)) / precision; + os << distance << unit; + text = unescape_translate(utf8_to_wide(os.str())); + bounds.LowerRightCorner.X = bounds.UpperLeftCorner.X + font->getDimension(text.c_str()).Width; + font->draw(text.c_str(), bounds + v2s32((e->align.X - 1.0f) * bounds.getWidth() / 2, text_height), color); + } + break; } + case HUD_ELEM_IMAGE_WAYPOINT: { + if (!calculateScreenPos(camera_offset, e, &pos)) + break; + } + case HUD_ELEM_IMAGE: { + video::ITexture *texture = tsrc->getTexture(e->text); + if (!texture) + continue; + + const video::SColor color(255, 255, 255, 255); + const video::SColor colors[] = {color, color, color, color}; + core::dimension2di imgsize(texture->getOriginalSize()); + v2s32 dstsize(imgsize.Width * e->scale.X * m_scale_factor, + imgsize.Height * e->scale.Y * m_scale_factor); + if (e->scale.X < 0) + dstsize.X = m_screensize.X * (e->scale.X * -0.01); + if (e->scale.Y < 0) + dstsize.Y = m_screensize.Y * (e->scale.Y * -0.01); + v2s32 offset((e->align.X - 1.0) * dstsize.X / 2, + (e->align.Y - 1.0) * dstsize.Y / 2); + core::rect<s32> rect(0, 0, dstsize.X, dstsize.Y); + rect += pos + offset + v2s32(e->offset.X * m_scale_factor, + e->offset.Y * m_scale_factor); + draw2DImageFilterScaled(driver, texture, rect, + core::rect<s32>(core::position2d<s32>(0,0), imgsize), + NULL, colors, true); break; } default: infostream << "Hud::drawLuaElements: ignoring drawform " << e->type << @@ -380,8 +427,9 @@ void Hud::drawLuaElements(const v3s16 &camera_offset) } -void Hud::drawStatbar(v2s32 pos, u16 corner, u16 drawdir, const std::string &texture, - s32 count, v2s32 offset, v2s32 size) +void Hud::drawStatbar(v2s32 pos, u16 corner, u16 drawdir, + const std::string &texture, const std::string &bgtexture, + s32 count, s32 maxcount, v2s32 offset, v2s32 size) { const video::SColor color(255, 255, 255, 255); const video::SColor colors[] = {color, color, color, color}; @@ -390,16 +438,24 @@ void Hud::drawStatbar(v2s32 pos, u16 corner, u16 drawdir, const std::string &tex if (!stat_texture) return; + video::ITexture *stat_texture_bg = nullptr; + if (!bgtexture.empty()) { + stat_texture_bg = tsrc->getTexture(bgtexture); + } + core::dimension2di srcd(stat_texture->getOriginalSize()); core::dimension2di dstd; if (size == v2s32()) { dstd = srcd; + dstd.Height *= m_scale_factor; + dstd.Width *= m_scale_factor; + offset.X *= m_scale_factor; + offset.Y *= m_scale_factor; } else { - float size_factor = m_hud_scaling * RenderingEngine::getDisplayDensity(); - dstd.Height = size.Y * size_factor; - dstd.Width = size.X * size_factor; - offset.X *= size_factor; - offset.Y *= size_factor; + dstd.Height = size.Y * m_scale_factor; + dstd.Width = size.X * m_scale_factor; + offset.X *= m_scale_factor; + offset.Y *= m_scale_factor; } v2s32 p = pos; @@ -409,43 +465,100 @@ void Hud::drawStatbar(v2s32 pos, u16 corner, u16 drawdir, const std::string &tex p += offset; v2s32 steppos; - core::rect<s32> srchalfrect, dsthalfrect; switch (drawdir) { case HUD_DIR_RIGHT_LEFT: steppos = v2s32(-1, 0); - srchalfrect = core::rect<s32>(srcd.Width / 2, 0, srcd.Width, srcd.Height); - dsthalfrect = core::rect<s32>(dstd.Width / 2, 0, dstd.Width, dstd.Height); break; case HUD_DIR_TOP_BOTTOM: steppos = v2s32(0, 1); - srchalfrect = core::rect<s32>(0, 0, srcd.Width, srcd.Height / 2); - dsthalfrect = core::rect<s32>(0, 0, dstd.Width, dstd.Height / 2); break; case HUD_DIR_BOTTOM_TOP: steppos = v2s32(0, -1); - srchalfrect = core::rect<s32>(0, srcd.Height / 2, srcd.Width, srcd.Height); - dsthalfrect = core::rect<s32>(0, dstd.Height / 2, dstd.Width, dstd.Height); break; default: + // From left to right steppos = v2s32(1, 0); - srchalfrect = core::rect<s32>(0, 0, srcd.Width / 2, srcd.Height); - dsthalfrect = core::rect<s32>(0, 0, dstd.Width / 2, dstd.Height); + break; } + + auto calculate_clipping_rect = [] (core::dimension2di src, + v2s32 steppos) -> core::rect<s32> { + + // Create basic rectangle + core::rect<s32> rect(0, 0, + src.Width - std::abs(steppos.X) * src.Width / 2, + src.Height - std::abs(steppos.Y) * src.Height / 2 + ); + // Move rectangle left or down + if (steppos.X == -1) + rect += v2s32(src.Width / 2, 0); + if (steppos.Y == -1) + rect += v2s32(0, src.Height / 2); + return rect; + }; + // Rectangles for 1/2 the actual value to display + core::rect<s32> srchalfrect, dsthalfrect; + // Rectangles for 1/2 the "off state" texture + core::rect<s32> srchalfrect2, dsthalfrect2; + + if (count % 2 == 1) { + // Need to draw halves: Calculate rectangles + srchalfrect = calculate_clipping_rect(srcd, steppos); + dsthalfrect = calculate_clipping_rect(dstd, steppos); + srchalfrect2 = calculate_clipping_rect(srcd, steppos * -1); + dsthalfrect2 = calculate_clipping_rect(dstd, steppos * -1); + } + steppos.X *= dstd.Width; steppos.Y *= dstd.Height; + // Draw full textures for (s32 i = 0; i < count / 2; i++) { core::rect<s32> srcrect(0, 0, srcd.Width, srcd.Height); - core::rect<s32> dstrect(0,0, dstd.Width, dstd.Height); + core::rect<s32> dstrect(0, 0, dstd.Width, dstd.Height); dstrect += p; - draw2DImageFilterScaled(driver, stat_texture, dstrect, srcrect, NULL, colors, true); + draw2DImageFilterScaled(driver, stat_texture, + dstrect, srcrect, NULL, colors, true); p += steppos; } if (count % 2 == 1) { - dsthalfrect += p; - draw2DImageFilterScaled(driver, stat_texture, dsthalfrect, srchalfrect, NULL, colors, true); + // Draw half a texture + draw2DImageFilterScaled(driver, stat_texture, + dsthalfrect + p, srchalfrect, NULL, colors, true); + + if (stat_texture_bg && maxcount > count) { + draw2DImageFilterScaled(driver, stat_texture_bg, + dsthalfrect2 + p, srchalfrect2, + NULL, colors, true); + p += steppos; + } + } + + if (stat_texture_bg && maxcount > count / 2) { + // Draw "off state" textures + s32 start_offset; + if (count % 2 == 1) + start_offset = count / 2 + 1; + else + start_offset = count / 2; + for (s32 i = start_offset; i < maxcount / 2; i++) { + core::rect<s32> srcrect(0, 0, srcd.Width, srcd.Height); + core::rect<s32> dstrect(0, 0, dstd.Width, dstd.Height); + + dstrect += p; + draw2DImageFilterScaled(driver, stat_texture_bg, + dstrect, srcrect, + NULL, colors, true); + p += steppos; + } + + if (maxcount % 2 == 1) { + draw2DImageFilterScaled(driver, stat_texture_bg, + dsthalfrect + p, srchalfrect, + NULL, colors, true); + } } } @@ -465,7 +578,7 @@ void Hud::drawHotbar(u16 playeritem) { v2s32 pos = centerlowerpos - v2s32(width / 2, m_hotbar_imagesize + m_padding * 3); const v2u32 &window_size = RenderingEngine::get_instance()->getWindowSize(); - if ( (float) width / (float) window_size.X <= + if ((float) width / (float) window_size.X <= g_settings->getFloat("hud_hotbar_max_width")) { if (player->hud_flags & HUD_FLAG_HOTBAR_VISIBLE) { drawItems(pos, v2s32(0, 0), hotbar_itemcount, 0, mainlist, playeritem + 1, 0); @@ -517,12 +630,10 @@ void Hud::drawSelectionMesh() // Draw 3D selection boxes video::SMaterial oldmaterial = driver->getMaterial2D(); driver->setMaterial(m_selection_material); - for (std::vector<aabb3f>::const_iterator - i = m_selection_boxes.begin(); - i != m_selection_boxes.end(); ++i) { + for (auto & selection_box : m_selection_boxes) { aabb3f box = aabb3f( - i->MinEdge + m_selection_pos_with_offset, - i->MaxEdge + m_selection_pos_with_offset); + selection_box.MinEdge + m_selection_pos_with_offset, + selection_box.MaxEdge + m_selection_pos_with_offset); u32 r = (selectionbox_argb.getRed() * m_selection_mesh_color.getRed() / 255); diff --git a/src/client/hud.h b/src/client/hud.h index d9b5e0686..6f4c54626 100644 --- a/src/client/hud.h +++ b/src/client/hud.h @@ -18,8 +18,7 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef CLIENT_HUD_HEADER -#define CLIENT_HUD_HEADER +#pragma once #include <vector> #include <IGUIFont.h> @@ -81,8 +80,10 @@ public: void drawLuaElements(const v3s16 &camera_offset); private: - void drawStatbar(v2s32 pos, u16 corner, u16 drawdir, const std::string &texture, - s32 count, v2s32 offset, v2s32 size = v2s32()); + bool calculateScreenPos(const v3s16 &camera_offset, HudElement *e, v2s32 *pos); + void drawStatbar(v2s32 pos, u16 corner, u16 drawdir, + const std::string &texture, const std::string& bgtexture, + s32 count, s32 maxcount, v2s32 offset, v2s32 size = v2s32()); void drawItems(v2s32 upperleftpos, v2s32 screen_offset, s32 itemcount, s32 inv_offset, InventoryList *mainlist, u16 selectitem, @@ -91,6 +92,7 @@ private: void drawItem(const ItemStack &item, const core::rect<s32> &rect, bool selected); float m_hud_scaling; // cached minetest setting + float m_scale_factor; v3s16 m_camera_offset; v2u32 m_screensize; v2s32 m_displaycenter; @@ -145,4 +147,3 @@ void drawItemStack( const v3s16 &angle, const v3s16 &rotation_speed); -#endif diff --git a/src/client/imagefilters.cpp b/src/client/imagefilters.cpp index dd029628f..0fa501410 100644 --- a/src/client/imagefilters.cpp +++ b/src/client/imagefilters.cpp @@ -91,7 +91,7 @@ void imageScaleNNAA(video::IImage *src, const core::rect<s32> &srcrect, video::I u32 dy, dx; video::SColor pxl; - // Cache rectsngle boundaries. + // Cache rectangle boundaries. double sox = srcrect.UpperLeftCorner.X * 1.0; double soy = srcrect.UpperLeftCorner.Y * 1.0; double sw = srcrect.getWidth() * 1.0; @@ -107,15 +107,15 @@ void imageScaleNNAA(video::IImage *src, const core::rect<s32> &srcrect, video::I // Do some basic clipping, and for mirrored/flipped rects, // make sure min/max are in the right order. minsx = sox + (dx * sw / dim.Width); - minsx = rangelim(minsx, 0, sw); + minsx = rangelim(minsx, 0, sox + sw); maxsx = minsx + sw / dim.Width; - maxsx = rangelim(maxsx, 0, sw); + maxsx = rangelim(maxsx, 0, sox + sw); if (minsx > maxsx) SWAP(double, minsx, maxsx); minsy = soy + (dy * sh / dim.Height); - minsy = rangelim(minsy, 0, sh); + minsy = rangelim(minsy, 0, soy + sh); maxsy = minsy + sh / dim.Height; - maxsy = rangelim(maxsy, 0, sh); + maxsy = rangelim(maxsy, 0, soy + sh); if (minsy > maxsy) SWAP(double, minsy, maxsy); diff --git a/src/client/localplayer.cpp b/src/client/localplayer.cpp index c20c3619f..1e7040d57 100644 --- a/src/client/localplayer.cpp +++ b/src/client/localplayer.cpp @@ -200,6 +200,8 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d, if (noclip && free_move) { position += m_speed * dtime; setPosition(position); + + touching_ground = false; added_velocity = v3f(0.0f); // ignored return; } @@ -436,9 +438,11 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d, Check properties of the node on which the player is standing */ const ContentFeatures &f = nodemgr->get(map->getNode(m_standing_node)); + const ContentFeatures &f1 = nodemgr->get(map->getNode(m_standing_node + v3s16(0, 1, 0))); // Determine if jumping is possible - m_disable_jump = itemgroup_get(f.groups, "disable_jump"); + m_disable_jump = itemgroup_get(f.groups, "disable_jump") || + itemgroup_get(f1.groups, "disable_jump"); m_can_jump = ((touching_ground && !is_climbing) || sneak_can_jump) && !m_disable_jump; // Jump key pressed while jumping off from a bouncy block @@ -785,6 +789,8 @@ void LocalPlayer::old_move(f32 dtime, Environment *env, f32 pos_max_d, if (free_move) { position += m_speed * dtime; setPosition(position); + + touching_ground = false; m_sneak_node_exists = false; added_velocity = v3f(0.0f); return; diff --git a/src/client/localplayer.h b/src/client/localplayer.h index 95dceb1f4..345aec9d9 100644 --- a/src/client/localplayer.h +++ b/src/client/localplayer.h @@ -135,12 +135,17 @@ public: } v3f getPosition() const { return m_position; } + + // Non-transformed eye offset getters + // For accurate positions, use the Camera functions v3f getEyePosition() const { return m_position + getEyeOffset(); } v3f getEyeOffset() const; void setEyeHeight(float eye_height) { m_eye_height = eye_height; } void setCollisionbox(const aabb3f &box) { m_collisionbox = box; } + const aabb3f& getCollisionbox() const { return m_collisionbox; } + float getZoomFOV() const { return m_zoom_fov; } void setZoomFOV(float zoom_fov) { m_zoom_fov = zoom_fov; } diff --git a/src/client/mapblock_mesh.cpp b/src/client/mapblock_mesh.cpp index a5bee6b88..1020e35f5 100644 --- a/src/client/mapblock_mesh.cpp +++ b/src/client/mapblock_mesh.cpp @@ -225,7 +225,7 @@ static u16 getSmoothLightCombined(const v3s16 &p, return f.light_propagates; }; - std::array<bool, 4> obstructed = {{ 1, 1, 1, 1 }}; + bool obstructed[4] = { true, true, true, true }; add_node(0); bool opaque1 = !add_node(1); bool opaque2 = !add_node(2); @@ -372,6 +372,32 @@ void final_color_blend(video::SColor *result, Mesh generation helpers */ +// This table is moved outside getNodeVertexDirs to avoid the compiler using +// a mutex to initialize this table at runtime right in the hot path. +// For details search the internet for "cxa_guard_acquire". +static const v3s16 vertex_dirs_table[] = { + // ( 1, 0, 0) + v3s16( 1,-1, 1), v3s16( 1,-1,-1), + v3s16( 1, 1,-1), v3s16( 1, 1, 1), + // ( 0, 1, 0) + v3s16( 1, 1,-1), v3s16(-1, 1,-1), + v3s16(-1, 1, 1), v3s16( 1, 1, 1), + // ( 0, 0, 1) + v3s16(-1,-1, 1), v3s16( 1,-1, 1), + v3s16( 1, 1, 1), v3s16(-1, 1, 1), + // invalid + v3s16(), v3s16(), v3s16(), v3s16(), + // ( 0, 0,-1) + v3s16( 1,-1,-1), v3s16(-1,-1,-1), + v3s16(-1, 1,-1), v3s16( 1, 1,-1), + // ( 0,-1, 0) + v3s16( 1,-1, 1), v3s16(-1,-1, 1), + v3s16(-1,-1,-1), v3s16( 1,-1,-1), + // (-1, 0, 0) + v3s16(-1,-1,-1), v3s16(-1,-1, 1), + v3s16(-1, 1, 1), v3s16(-1, 1,-1) +}; + /* vertex_dirs: v3s16[4] */ @@ -384,44 +410,16 @@ static void getNodeVertexDirs(const v3s16 &dir, v3s16 *vertex_dirs) 2: top-left 3: top-right */ - if (dir == v3s16(0, 0, 1)) { - // If looking towards z+, this is the face that is behind - // the center point, facing towards z+. - vertex_dirs[0] = v3s16(-1,-1, 1); - vertex_dirs[1] = v3s16( 1,-1, 1); - vertex_dirs[2] = v3s16( 1, 1, 1); - vertex_dirs[3] = v3s16(-1, 1, 1); - } else if (dir == v3s16(0, 0, -1)) { - // faces towards Z- - vertex_dirs[0] = v3s16( 1,-1,-1); - vertex_dirs[1] = v3s16(-1,-1,-1); - vertex_dirs[2] = v3s16(-1, 1,-1); - vertex_dirs[3] = v3s16( 1, 1,-1); - } else if (dir == v3s16(1, 0, 0)) { - // faces towards X+ - vertex_dirs[0] = v3s16( 1,-1, 1); - vertex_dirs[1] = v3s16( 1,-1,-1); - vertex_dirs[2] = v3s16( 1, 1,-1); - vertex_dirs[3] = v3s16( 1, 1, 1); - } else if (dir == v3s16(-1, 0, 0)) { - // faces towards X- - vertex_dirs[0] = v3s16(-1,-1,-1); - vertex_dirs[1] = v3s16(-1,-1, 1); - vertex_dirs[2] = v3s16(-1, 1, 1); - vertex_dirs[3] = v3s16(-1, 1,-1); - } else if (dir == v3s16(0, 1, 0)) { - // faces towards Y+ (assume Z- as "down" in texture) - vertex_dirs[0] = v3s16( 1, 1,-1); - vertex_dirs[1] = v3s16(-1, 1,-1); - vertex_dirs[2] = v3s16(-1, 1, 1); - vertex_dirs[3] = v3s16( 1, 1, 1); - } else if (dir == v3s16(0, -1, 0)) { - // faces towards Y- (assume Z+ as "down" in texture) - vertex_dirs[0] = v3s16( 1,-1, 1); - vertex_dirs[1] = v3s16(-1,-1, 1); - vertex_dirs[2] = v3s16(-1,-1,-1); - vertex_dirs[3] = v3s16( 1,-1,-1); - } + + // Direction must be (1,0,0), (-1,0,0), (0,1,0), (0,-1,0), + // (0,0,1), (0,0,-1) + assert(dir.X * dir.X + dir.Y * dir.Y + dir.Z * dir.Z == 1); + + // Convert direction to single integer for table lookup + u8 idx = (dir.X + 2 * dir.Y + 3 * dir.Z) & 7; + idx = (idx - 1) * 4; + + memcpy(vertex_dirs, &vertex_dirs_table[idx], 4 * sizeof(v3s16)); } static void getNodeTextureCoords(v3f base, const v3f &scale, const v3s16 &dir, float *u, float *v) @@ -890,8 +888,10 @@ static void updateFastFaceRow( v3s16 p_corrected; v3s16 face_dir_corrected; u16 lights[4] = {0, 0, 0, 0}; - u8 waving; + u8 waving = 0; TileSpec tile; + + // Get info of first tile getTileInfo(data, p, face_dir, makes_face, p_corrected, face_dir_corrected, lights, waving, tile); @@ -902,8 +902,6 @@ static void updateFastFaceRow( // If tiling can be done, this is set to false in the next step bool next_is_different = true; - v3s16 p_next; - bool next_makes_face = false; v3s16 next_p_corrected; v3s16 next_face_dir_corrected; @@ -912,9 +910,9 @@ static void updateFastFaceRow( // If at last position, there is nothing to compare to and // the face must be drawn anyway if (j != MAP_BLOCKSIZE - 1) { - p_next = p + translate_dir; + p += translate_dir; - getTileInfo(data, p_next, face_dir, + getTileInfo(data, p, face_dir, next_makes_face, next_p_corrected, next_face_dir_corrected, next_lights, waving, @@ -923,7 +921,7 @@ static void updateFastFaceRow( if (next_makes_face == makes_face && next_p_corrected == p_corrected + translate_dir && next_face_dir_corrected == face_dir_corrected - && memcmp(next_lights, lights, ARRLEN(lights) * sizeof(u16)) == 0 + && memcmp(next_lights, lights, sizeof(lights)) == 0 // Don't apply fast faces to waving water. && (waving != 3 || !waving_liquids) && next_tile.isTileable(tile)) { @@ -961,10 +959,9 @@ static void updateFastFaceRow( makes_face = next_makes_face; p_corrected = next_p_corrected; face_dir_corrected = next_face_dir_corrected; - std::memcpy(lights, next_lights, ARRLEN(lights) * sizeof(u16)); + memcpy(lights, next_lights, sizeof(lights)); if (next_is_different) - tile = next_tile; - p = p_next; + tile = std::move(next_tile); // faster than copy } } diff --git a/src/client/mesh.cpp b/src/client/mesh.cpp index 4d73ead8a..e1ec22068 100644 --- a/src/client/mesh.cpp +++ b/src/client/mesh.cpp @@ -328,6 +328,26 @@ void recalculateBoundingBox(scene::IMesh *src_mesh) src_mesh->setBoundingBox(bbox); } +bool checkMeshNormals(scene::IMesh *mesh) +{ + u32 buffer_count = mesh->getMeshBufferCount(); + + for (u32 i = 0; i < buffer_count; i++) { + scene::IMeshBuffer *buffer = mesh->getMeshBuffer(i); + + // Here we intentionally check only first normal, assuming that if buffer + // has it valid, then most likely all other ones are fine too. We can + // check all of the normals to have length, but it seems like an overkill + // hurting the performance and covering only really weird broken models. + f32 length = buffer->getNormal(0).getLength(); + + if (!std::isfinite(length) || length < 1e-10f) + return false; + } + + return true; +} + scene::IMeshBuffer* cloneMeshBuffer(scene::IMeshBuffer *mesh_buffer) { switch (mesh_buffer->getVertexType()) { diff --git a/src/client/mesh.h b/src/client/mesh.h index 0c4094de2..103c61e45 100644 --- a/src/client/mesh.h +++ b/src/client/mesh.h @@ -122,6 +122,12 @@ scene::IMesh* convertNodeboxesToMesh(const std::vector<aabb3f> &boxes, void recalculateBoundingBox(scene::IMesh *src_mesh); /* + Check if mesh has valid normals and return true if it does. + We assume normal to be valid when it's 0 < length < Inf. and not NaN + */ +bool checkMeshNormals(scene::IMesh *mesh); + +/* Vertex cache optimization according to the Forsyth paper: http://home.comcast.net/~tom_forsyth/papers/fast_vert_cache_opt.html Ported from irrlicht 1.8 diff --git a/src/client/particles.cpp b/src/client/particles.cpp index a0e4e54eb..7acd996dc 100644 --- a/src/client/particles.cpp +++ b/src/client/particles.cpp @@ -37,32 +37,31 @@ with this program; if not, write to the Free Software Foundation, Inc., Utility */ -v3f random_v3f(v3f min, v3f max) +static f32 random_f32(f32 min, f32 max) +{ + return rand() / (float)RAND_MAX * (max - min) + min; +} + +static v3f random_v3f(v3f min, v3f max) { return v3f( - rand() / (float)RAND_MAX * (max.X - min.X) + min.X, - rand() / (float)RAND_MAX * (max.Y - min.Y) + min.Y, - rand() / (float)RAND_MAX * (max.Z - min.Z) + min.Z); + random_f32(min.X, max.X), + random_f32(min.Y, max.Y), + random_f32(min.Z, max.Z)); } +/* + Particle +*/ + Particle::Particle( IGameDef *gamedef, LocalPlayer *player, ClientEnvironment *env, - v3f pos, - v3f velocity, - v3f acceleration, - float expirationtime, - float size, - bool collisiondetection, - bool collision_removal, - bool object_collision, - bool vertical, + const ParticleParameters &p, video::ITexture *texture, v2f texpos, v2f texsize, - const struct TileAnimationParams &anim, - u8 glow, video::SColor color ): scene::ISceneNode(RenderingEngine::get_scene_manager()->getRootSceneNode(), @@ -81,33 +80,28 @@ Particle::Particle( m_material.setTexture(0, texture); m_texpos = texpos; m_texsize = texsize; - m_animation = anim; + m_animation = p.animation; // Color m_base_color = color; m_color = color; // Particle related - m_pos = pos; - m_velocity = velocity; - m_acceleration = acceleration; - m_expiration = expirationtime; + m_pos = p.pos; + m_velocity = p.vel; + m_acceleration = p.acc; + m_expiration = p.expirationtime; m_player = player; - m_size = size; - m_collisiondetection = collisiondetection; - m_collision_removal = collision_removal; - m_object_collision = object_collision; - m_vertical = vertical; - m_glow = glow; + m_size = p.size; + m_collisiondetection = p.collisiondetection; + m_collision_removal = p.collision_removal; + m_object_collision = p.object_collision; + m_vertical = p.vertical; + m_glow = p.glow; // Irrlicht stuff - m_collisionbox = aabb3f( - -size / 2, - -size / 2, - -size / 2, - size / 2, - size / 2, - size / 2); + const float c = p.size / 2; + m_collisionbox = aabb3f(-c, -c, -c, c, c, c); this->setAutomaticCulling(scene::EAC_OFF); // Init lighting @@ -255,52 +249,22 @@ void Particle::updateVertices() ParticleSpawner::ParticleSpawner( IGameDef *gamedef, LocalPlayer *player, - u16 amount, - float time, - v3f minpos, v3f maxpos, - v3f minvel, v3f maxvel, - v3f minacc, v3f maxacc, - float minexptime, float maxexptime, - float minsize, float maxsize, - bool collisiondetection, - bool collision_removal, - bool object_collision, + const ParticleSpawnerParameters &p, u16 attached_id, - bool vertical, video::ITexture *texture, - const struct TileAnimationParams &anim, - u8 glow, ParticleManager *p_manager ): - m_particlemanager(p_manager) + m_particlemanager(p_manager), p(p) { m_gamedef = gamedef; m_player = player; - m_amount = amount; - m_spawntime = time; - m_minpos = minpos; - m_maxpos = maxpos; - m_minvel = minvel; - m_maxvel = maxvel; - m_minacc = minacc; - m_maxacc = maxacc; - m_minexptime = minexptime; - m_maxexptime = maxexptime; - m_minsize = minsize; - m_maxsize = maxsize; - m_collisiondetection = collisiondetection; - m_collision_removal = collision_removal; - m_object_collision = object_collision; m_attached_id = attached_id; - m_vertical = vertical; m_texture = texture; m_time = 0; - m_animation = anim; - m_glow = glow; - for (u16 i = 0; i <= m_amount; i++) - { - float spawntime = (float)rand() / (float)RAND_MAX * m_spawntime; + m_spawntimes.reserve(p.amount + 1); + for (u16 i = 0; i <= p.amount; i++) { + float spawntime = rand() / (float)RAND_MAX * p.time; m_spawntimes.push_back(spawntime); } } @@ -309,7 +273,7 @@ void ParticleSpawner::spawnParticle(ClientEnvironment *env, float radius, const core::matrix4 *attached_absolute_pos_rot_matrix) { v3f ppos = m_player->getPosition() / BS; - v3f pos = random_v3f(m_minpos, m_maxpos); + v3f pos = random_v3f(p.minpos, p.maxpos); // Need to apply this first or the following check // will be wrong for attached spawners @@ -326,41 +290,51 @@ void ParticleSpawner::spawnParticle(ClientEnvironment *env, float radius, if (pos.getDistanceFrom(ppos) > radius) return; - v3f vel = random_v3f(m_minvel, m_maxvel); - v3f acc = random_v3f(m_minacc, m_maxacc); + // Parameters for the single particle we're about to spawn + ParticleParameters pp; + pp.pos = pos; + + pp.vel = random_v3f(p.minvel, p.maxvel); + pp.acc = random_v3f(p.minacc, p.maxacc); if (attached_absolute_pos_rot_matrix) { // Apply attachment rotation - attached_absolute_pos_rot_matrix->rotateVect(vel); - attached_absolute_pos_rot_matrix->rotateVect(acc); + attached_absolute_pos_rot_matrix->rotateVect(pp.vel); + attached_absolute_pos_rot_matrix->rotateVect(pp.acc); } - float exptime = rand() / (float)RAND_MAX - * (m_maxexptime - m_minexptime) - + m_minexptime; + pp.expirationtime = random_f32(p.minexptime, p.maxexptime); + p.copyCommon(pp); + + video::ITexture *texture; + v2f texpos, texsize; + video::SColor color(0xFFFFFFFF); + + if (p.node.getContent() != CONTENT_IGNORE) { + const ContentFeatures &f = + m_particlemanager->m_env->getGameDef()->ndef()->get(p.node); + if (!ParticleManager::getNodeParticleParams(p.node, f, pp, &texture, + texpos, texsize, &color, p.node_tile)) + return; + } else { + texture = m_texture; + texpos = v2f(0.0f, 0.0f); + texsize = v2f(1.0f, 1.0f); + } - float size = rand() / (float)RAND_MAX - * (m_maxsize - m_minsize) - + m_minsize; + // Allow keeping default random size + if (p.maxsize > 0.0f) + pp.size = random_f32(p.minsize, p.maxsize); m_particlemanager->addParticle(new Particle( m_gamedef, m_player, env, - pos, - vel, - acc, - exptime, - size, - m_collisiondetection, - m_collision_removal, - m_object_collision, - m_vertical, - m_texture, - v2f(0.0, 0.0), - v2f(1.0, 1.0), - m_animation, - m_glow + pp, + texture, + texpos, + texsize, + color )); } @@ -375,18 +349,17 @@ void ParticleSpawner::step(float dtime, ClientEnvironment *env) const core::matrix4 *attached_absolute_pos_rot_matrix = nullptr; if (m_attached_id) { if (GenericCAO *attached = dynamic_cast<GenericCAO *>(env->getActiveObject(m_attached_id))) { - attached_absolute_pos_rot_matrix = &attached->getAbsolutePosRotMatrix(); + attached_absolute_pos_rot_matrix = attached->getAbsolutePosRotMatrix(); } else { unloaded = true; } } - if (m_spawntime != 0) { + if (p.time != 0) { // Spawner exists for a predefined timespan - for (std::vector<float>::iterator i = m_spawntimes.begin(); - i != m_spawntimes.end();) { - if ((*i) <= m_time && m_amount > 0) { - --m_amount; + for (auto i = m_spawntimes.begin(); i != m_spawntimes.end(); ) { + if ((*i) <= m_time && p.amount > 0) { + --p.amount; // Pretend to, but don't actually spawn a particle if it is // attached to an unloaded object or distant from player. @@ -405,13 +378,16 @@ void ParticleSpawner::step(float dtime, ClientEnvironment *env) if (unloaded) return; - for (int i = 0; i <= m_amount; i++) { + for (int i = 0; i <= p.amount; i++) { if (rand() / (float)RAND_MAX < dtime) spawnParticle(env, radius, attached_absolute_pos_rot_matrix); } } } +/* + ParticleManager +*/ ParticleManager::ParticleManager(ClientEnvironment *env) : m_env(env) @@ -479,99 +455,106 @@ void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client, { switch (event->type) { case CE_DELETE_PARTICLESPAWNER: { - MutexAutoLock lock(m_spawner_list_lock); - if (m_particle_spawners.find(event->delete_particlespawner.id) != - m_particle_spawners.end()) { - delete m_particle_spawners.find(event->delete_particlespawner.id)->second; - m_particle_spawners.erase(event->delete_particlespawner.id); - } + deleteParticleSpawner(event->delete_particlespawner.id); // no allocated memory in delete event break; } case CE_ADD_PARTICLESPAWNER: { - { - MutexAutoLock lock(m_spawner_list_lock); - if (m_particle_spawners.find(event->add_particlespawner.id) != - m_particle_spawners.end()) { - delete m_particle_spawners.find(event->add_particlespawner.id)->second; - m_particle_spawners.erase(event->add_particlespawner.id); - } - } + deleteParticleSpawner(event->add_particlespawner.id); + + const ParticleSpawnerParameters &p = *event->add_particlespawner.p; video::ITexture *texture = - client->tsrc()->getTextureForMesh(*(event->add_particlespawner.texture)); + client->tsrc()->getTextureForMesh(p.texture); auto toadd = new ParticleSpawner(client, player, - event->add_particlespawner.amount, - event->add_particlespawner.spawntime, - *event->add_particlespawner.minpos, - *event->add_particlespawner.maxpos, - *event->add_particlespawner.minvel, - *event->add_particlespawner.maxvel, - *event->add_particlespawner.minacc, - *event->add_particlespawner.maxacc, - event->add_particlespawner.minexptime, - event->add_particlespawner.maxexptime, - event->add_particlespawner.minsize, - event->add_particlespawner.maxsize, - event->add_particlespawner.collisiondetection, - event->add_particlespawner.collision_removal, - event->add_particlespawner.object_collision, + p, event->add_particlespawner.attached_id, - event->add_particlespawner.vertical, texture, - event->add_particlespawner.animation, - event->add_particlespawner.glow, this); - /* delete allocated content of event */ - delete event->add_particlespawner.minpos; - delete event->add_particlespawner.maxpos; - delete event->add_particlespawner.minvel; - delete event->add_particlespawner.maxvel; - delete event->add_particlespawner.minacc; - delete event->add_particlespawner.texture; - delete event->add_particlespawner.maxacc; - - { - MutexAutoLock lock(m_spawner_list_lock); - m_particle_spawners[event->add_particlespawner.id] = toadd; - } + addParticleSpawner(event->add_particlespawner.id, toadd); + + delete event->add_particlespawner.p; break; } case CE_SPAWN_PARTICLE: { - video::ITexture *texture = - client->tsrc()->getTextureForMesh(*(event->spawn_particle.texture)); - - Particle *toadd = new Particle(client, player, m_env, - *event->spawn_particle.pos, - *event->spawn_particle.vel, - *event->spawn_particle.acc, - event->spawn_particle.expirationtime, - event->spawn_particle.size, - event->spawn_particle.collisiondetection, - event->spawn_particle.collision_removal, - event->spawn_particle.object_collision, - event->spawn_particle.vertical, - texture, - v2f(0.0, 0.0), - v2f(1.0, 1.0), - event->spawn_particle.animation, - event->spawn_particle.glow); + ParticleParameters &p = *event->spawn_particle; - addParticle(toadd); + video::ITexture *texture; + v2f texpos, texsize; + video::SColor color(0xFFFFFFFF); - delete event->spawn_particle.pos; - delete event->spawn_particle.vel; - delete event->spawn_particle.acc; - delete event->spawn_particle.texture; + f32 oldsize = p.size; + + if (p.node.getContent() != CONTENT_IGNORE) { + const ContentFeatures &f = m_env->getGameDef()->ndef()->get(p.node); + if (!getNodeParticleParams(p.node, f, p, &texture, texpos, + texsize, &color, p.node_tile)) + texture = nullptr; + } else { + texture = client->tsrc()->getTextureForMesh(p.texture); + texpos = v2f(0.0f, 0.0f); + texsize = v2f(1.0f, 1.0f); + } + // Allow keeping default random size + if (oldsize > 0.0f) + p.size = oldsize; + + if (texture) { + Particle *toadd = new Particle(client, player, m_env, + p, texture, texpos, texsize, color); + + addParticle(toadd); + } + + delete event->spawn_particle; break; } default: break; } } +bool ParticleManager::getNodeParticleParams(const MapNode &n, + const ContentFeatures &f, ParticleParameters &p, video::ITexture **texture, + v2f &texpos, v2f &texsize, video::SColor *color, u8 tilenum) +{ + // No particles for "airlike" nodes + if (f.drawtype == NDT_AIRLIKE) + return false; + + // Texture + u8 texid; + if (tilenum > 0 && tilenum <= 6) + texid = tilenum - 1; + else + texid = rand() % 6; + const TileLayer &tile = f.tiles[texid].layers[0]; + p.animation.type = TAT_NONE; + + // Only use first frame of animated texture + if (tile.material_flags & MATERIAL_FLAG_ANIMATION) + *texture = (*tile.frames)[0].texture; + else + *texture = tile.texture; + + float size = (rand() % 8) / 64.0f; + p.size = BS * size; + if (tile.scale) + size /= tile.scale; + texsize = v2f(size * 2.0f, size * 2.0f); + texpos.X = (rand() % 64) / 64.0f - texsize.X; + texpos.Y = (rand() % 64) / 64.0f - texsize.Y; + + if (tile.has_color) + *color = tile.color; + else + n.getColor(f, color); + + return true; +} + // The final burst of particles when a node is finally dug, *not* particles // spawned during the digging of a node. @@ -593,73 +576,41 @@ void ParticleManager::addDiggingParticles(IGameDef *gamedef, void ParticleManager::addNodeParticle(IGameDef *gamedef, LocalPlayer *player, v3s16 pos, const MapNode &n, const ContentFeatures &f) { - // No particles for "airlike" nodes - if (f.drawtype == NDT_AIRLIKE) - return; - - // Texture - u8 texid = myrand_range(0, 5); - const TileLayer &tile = f.tiles[texid].layers[0]; + ParticleParameters p; video::ITexture *texture; - struct TileAnimationParams anim; - anim.type = TAT_NONE; + v2f texpos, texsize; + video::SColor color; - // Only use first frame of animated texture - if (tile.material_flags & MATERIAL_FLAG_ANIMATION) - texture = (*tile.frames)[0].texture; - else - texture = tile.texture; + if (!getNodeParticleParams(n, f, p, &texture, texpos, texsize, &color)) + return; - float size = (rand() % 8) / 64.0f; - float visual_size = BS * size; - if (tile.scale) - size /= tile.scale; - v2f texsize(size * 2.0f, size * 2.0f); - v2f texpos; - texpos.X = (rand() % 64) / 64.0f - texsize.X; - texpos.Y = (rand() % 64) / 64.0f - texsize.Y; + p.expirationtime = (rand() % 100) / 100.0f; // Physics - v3f velocity( + p.vel = v3f( (rand() % 150) / 50.0f - 1.5f, (rand() % 150) / 50.0f, (rand() % 150) / 50.0f - 1.5f ); - v3f acceleration( + p.acc = v3f( 0.0f, -player->movement_gravity * player->physics_override_gravity / BS, 0.0f ); - v3f particlepos = v3f( + p.pos = v3f( (f32)pos.X + (rand() % 100) / 200.0f - 0.25f, (f32)pos.Y + (rand() % 100) / 200.0f - 0.25f, (f32)pos.Z + (rand() % 100) / 200.0f - 0.25f ); - video::SColor color; - if (tile.has_color) - color = tile.color; - else - n.getColor(f, &color); - Particle *toadd = new Particle( gamedef, player, m_env, - particlepos, - velocity, - acceleration, - (rand() % 100) / 100.0f, // expiration time - visual_size, - true, - false, - false, - false, + p, texture, texpos, texsize, - anim, - 0, color); addParticle(toadd); @@ -670,3 +621,20 @@ void ParticleManager::addParticle(Particle *toadd) MutexAutoLock lock(m_particle_list_lock); m_particles.push_back(toadd); } + + +void ParticleManager::addParticleSpawner(u64 id, ParticleSpawner *toadd) +{ + MutexAutoLock lock(m_spawner_list_lock); + m_particle_spawners[id] = toadd; +} + +void ParticleManager::deleteParticleSpawner(u64 id) +{ + MutexAutoLock lock(m_spawner_list_lock); + auto it = m_particle_spawners.find(id); + if (it != m_particle_spawners.end()) { + delete it->second; + m_particle_spawners.erase(it); + } +} diff --git a/src/client/particles.h b/src/client/particles.h index e7b8cbe24..2011f0262 100644 --- a/src/client/particles.h +++ b/src/client/particles.h @@ -23,7 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irrlichttypes_extrabloated.h" #include "client/tile.h" #include "localplayer.h" -#include "tileanimation.h" +#include "../particles.h" struct ClientEvent; class ParticleManager; @@ -38,21 +38,11 @@ class Particle : public scene::ISceneNode IGameDef* gamedef, LocalPlayer *player, ClientEnvironment *env, - v3f pos, - v3f velocity, - v3f acceleration, - float expirationtime, - float size, - bool collisiondetection, - bool collision_removal, - bool object_collision, - bool vertical, + const ParticleParameters &p, video::ITexture *texture, v2f texpos, v2f texsize, - const struct TileAnimationParams &anim, - u8 glow, - video::SColor color = video::SColor(0xFFFFFFFF) + video::SColor color ); ~Particle() = default; @@ -119,20 +109,9 @@ class ParticleSpawner public: ParticleSpawner(IGameDef* gamedef, LocalPlayer *player, - u16 amount, - float time, - v3f minp, v3f maxp, - v3f minvel, v3f maxvel, - v3f minacc, v3f maxacc, - float minexptime, float maxexptime, - float minsize, float maxsize, - bool collisiondetection, - bool collision_removal, - bool object_collision, + const ParticleSpawnerParameters &p, u16 attached_id, - bool vertical, video::ITexture *texture, - const struct TileAnimationParams &anim, u8 glow, ParticleManager* p_manager); ~ParticleSpawner() = default; @@ -140,7 +119,7 @@ public: void step(float dtime, ClientEnvironment *env); bool get_expired () - { return (m_amount <= 0) && m_spawntime != 0; } + { return p.amount <= 0 && p.time != 0; } private: void spawnParticle(ClientEnvironment *env, float radius, @@ -150,27 +129,10 @@ private: float m_time; IGameDef *m_gamedef; LocalPlayer *m_player; - u16 m_amount; - float m_spawntime; - v3f m_minpos; - v3f m_maxpos; - v3f m_minvel; - v3f m_maxvel; - v3f m_minacc; - v3f m_maxacc; - float m_minexptime; - float m_maxexptime; - float m_minsize; - float m_maxsize; + ParticleSpawnerParameters p; video::ITexture *m_texture; std::vector<float> m_spawntimes; - bool m_collisiondetection; - bool m_collision_removal; - bool m_object_collision; - bool m_vertical; u16 m_attached_id; - struct TileAnimationParams m_animation; - u8 m_glow; }; /** @@ -197,8 +159,8 @@ public: /** * This function is only used by client particle spawners * - * We don't need to check the particle spawner list because client ID will n - * ever overlap (u64) + * We don't need to check the particle spawner list because client ID will + * never overlap (u64) * @return new id */ u64 generateSpawnerId() @@ -207,9 +169,15 @@ public: } protected: + static bool getNodeParticleParams(const MapNode &n, const ContentFeatures &f, + ParticleParameters &p, video::ITexture **texture, v2f &texpos, + v2f &texsize, video::SColor *color, u8 tilenum = 0); + void addParticle(Particle* toadd); private: + void addParticleSpawner(u64 id, ParticleSpawner *toadd); + void deleteParticleSpawner(u64 id); void stepParticles(float dtime); void stepSpawners(float dtime); diff --git a/src/client/render/factory.h b/src/client/render/factory.h index 22738404a..e3339a836 100644 --- a/src/client/render/factory.h +++ b/src/client/render/factory.h @@ -18,6 +18,8 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#pragma once + #include <string> #include "core.h" diff --git a/src/client/renderingengine.cpp b/src/client/renderingengine.cpp index 6e6509eeb..f5aca8f58 100644 --- a/src/client/renderingengine.cpp +++ b/src/client/renderingengine.cpp @@ -45,7 +45,11 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <X11/Xlib.h> #include <X11/Xutil.h> #include <X11/Xatom.h> +#endif +#ifdef _WIN32 +#include <windows.h> +#include <winuser.h> #endif #if ENABLE_GLES @@ -126,12 +130,9 @@ RenderingEngine::RenderingEngine(IEventReceiver *receiver) params.HighPrecisionFPU = g_settings->getBool("high_precision_fpu"); params.ZBufferBits = 24; #ifdef __ANDROID__ - // clang-format off params.PrivateData = porting::app_global; - params.OGLES2ShaderPath = std::string(porting::path_user + DIR_DELIM + "media" + - DIR_DELIM + "Shaders" + DIR_DELIM).c_str(); - // clang-format on -#elif ENABLE_GLES +#endif +#if ENABLE_GLES // there is no standardized path for these on desktop std::string rel_path = std::string("client") + DIR_DELIM + "shaders" + DIR_DELIM + "Irrlicht"; @@ -226,27 +227,17 @@ bool RenderingEngine::setupTopLevelWindow(const std::string &name) { // FIXME: It would make more sense for there to be a switch of some // sort here that would call the correct toplevel setup methods for - // the environment Minetest is running in but for now not deviating - // from the original pattern. + // the environment Minetest is running in. /* Setting Xorg properties for the top level window */ setupTopLevelXorgWindow(name); - /* Done with Xorg properties */ /* Setting general properties for the top level window */ verbosestream << "Client: Configuring general top level" << " window properties" << std::endl; - bool result = setWindowIcon(); - verbosestream << "Client: Finished configuring general top level" - << " window properties" - << std::endl; - /* Done with general properties */ - - // FIXME: setWindowIcon returns a bool result but it is unused. - // For now continue to return this result. return result; } @@ -262,7 +253,7 @@ void RenderingEngine::setupTopLevelXorgWindow(const std::string &name) return; } - verbosestream << "Client: Configuring Xorg specific top level" + verbosestream << "Client: Configuring X11-specific top level" << " window properties" << std::endl; @@ -309,8 +300,6 @@ void RenderingEngine::setupTopLevelXorgWindow(const std::string &name) Atom NET_WM_PID = XInternAtom(x11_dpl, "_NET_WM_PID", false); pid_t pid = getpid(); - infostream << "Client: PID is '" << static_cast<long>(pid) << "'" - << std::endl; XChangeProperty(x11_dpl, x11_win, NET_WM_PID, XA_CARDINAL, 32, PropModeReplace, @@ -327,13 +316,31 @@ void RenderingEngine::setupTopLevelXorgWindow(const std::string &name) XChangeProperty (x11_dpl, x11_win, WM_CLIENT_LEADER, XA_WINDOW, 32, PropModeReplace, reinterpret_cast<unsigned char *>(&x11_win), 1); - - verbosestream << "Client: Finished configuring Xorg specific top level" - << " window properties" - << std::endl; #endif } +#ifdef _WIN32 +static bool getWindowHandle(irr::video::IVideoDriver *driver, HWND &hWnd) +{ + const video::SExposedVideoData exposedData = driver->getExposedVideoData(); + + switch (driver->getDriverType()) { + case video::EDT_DIRECT3D8: + hWnd = reinterpret_cast<HWND>(exposedData.D3D8.HWnd); + break; + case video::EDT_DIRECT3D9: + hWnd = reinterpret_cast<HWND>(exposedData.D3D9.HWnd); + break; + case video::EDT_OPENGL: + hWnd = reinterpret_cast<HWND>(exposedData.OpenGLWin32.HWnd); + break; + default: + return false; + } + + return true; +} +#endif bool RenderingEngine::setWindowIcon() { @@ -351,22 +358,9 @@ bool RenderingEngine::setWindowIcon() "-xorg-icon-128.png"); #endif #elif defined(_WIN32) - const video::SExposedVideoData exposedData = driver->getExposedVideoData(); HWND hWnd; // Window handle - - switch (driver->getDriverType()) { - case video::EDT_DIRECT3D8: - hWnd = reinterpret_cast<HWND>(exposedData.D3D8.HWnd); - break; - case video::EDT_DIRECT3D9: - hWnd = reinterpret_cast<HWND>(exposedData.D3D9.HWnd); - break; - case video::EDT_OPENGL: - hWnd = reinterpret_cast<HWND>(exposedData.OpenGLWin32.HWnd); - break; - default: + if (!getWindowHandle(driver, hWnd)) return false; - } // Load the ICON from resource file const HICON hicon = LoadIcon(GetModuleHandle(NULL), @@ -648,7 +642,7 @@ const char *RenderingEngine::getVideoDriverFriendlyName(irr::video::E_DRIVER_TYP } #ifndef __ANDROID__ -#ifdef XORG_USED +#if defined(XORG_USED) static float calcDisplayDensity() { @@ -683,12 +677,42 @@ float RenderingEngine::getDisplayDensity() return cached_display_density; } -#else // XORG_USED +#elif defined(_WIN32) + + +static float calcDisplayDensity(irr::video::IVideoDriver *driver) +{ + HWND hWnd; + if (getWindowHandle(driver, hWnd)) { + HDC hdc = GetDC(hWnd); + float dpi = GetDeviceCaps(hdc, LOGPIXELSX); + ReleaseDC(hWnd, hdc); + return dpi / 96.0f; + } + + /* return manually specified dpi */ + return g_settings->getFloat("screen_dpi") / 96.0f; +} + +float RenderingEngine::getDisplayDensity() +{ + static bool cached = false; + static float display_density; + if (!cached) { + display_density = calcDisplayDensity(get_video_driver()); + cached = true; + } + return display_density; +} + +#else + float RenderingEngine::getDisplayDensity() { return g_settings->getFloat("screen_dpi") / 96.0; } -#endif // XORG_USED + +#endif v2u32 RenderingEngine::getDisplaySize() { diff --git a/src/client/shader.cpp b/src/client/shader.cpp index eda415ce6..ee6079f7a 100644 --- a/src/client/shader.cpp +++ b/src/client/shader.cpp @@ -537,11 +537,13 @@ ShaderInfo generate_shader(const std::string &name, u8 material_type, u8 drawtyp shaderinfo.base_material = video::EMT_SOLID; break; case TILE_MATERIAL_ALPHA: + case TILE_MATERIAL_PLAIN_ALPHA: case TILE_MATERIAL_LIQUID_TRANSPARENT: case TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT: shaderinfo.base_material = video::EMT_TRANSPARENT_ALPHA_CHANNEL; break; case TILE_MATERIAL_BASIC: + case TILE_MATERIAL_PLAIN: case TILE_MATERIAL_WAVING_LEAVES: case TILE_MATERIAL_WAVING_PLANTS: case TILE_MATERIAL_WAVING_LIQUID_BASIC: @@ -644,9 +646,11 @@ ShaderInfo generate_shader(const std::string &name, u8 material_type, u8 drawtyp "TILE_MATERIAL_WAVING_LIQUID_BASIC", "TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT", "TILE_MATERIAL_WAVING_LIQUID_OPAQUE", + "TILE_MATERIAL_PLAIN", + "TILE_MATERIAL_PLAIN_ALPHA", }; - for (int i = 0; i < 10; i++){ + for (int i = 0; i < 12; i++){ shaders_header += "#define "; shaders_header += materialTypes[i]; shaders_header += " "; diff --git a/src/client/sky.cpp b/src/client/sky.cpp index 7a7b188ce..2e0cbca86 100644 --- a/src/client/sky.cpp +++ b/src/client/sky.cpp @@ -252,35 +252,10 @@ void Sky::render() if (m_visible) { driver->setMaterial(m_materials[1]); for (u32 j = 0; j < 4; j++) { - video::SColor c = cloudyfogcolor.getInterpolated(m_skycolor, 0.45); - vertices[0] = video::S3DVertex(-1, 0.08, -1, 0, 0, 1, c, t, t); - vertices[1] = video::S3DVertex( 1, 0.08, -1, 0, 0, 1, c, o, t); - vertices[2] = video::S3DVertex( 1, 0.12, -1, 0, 0, 1, c, o, o); - vertices[3] = video::S3DVertex(-1, 0.12, -1, 0, 0, 1, c, t, o); - for (video::S3DVertex &vertex : vertices) { - if (j == 0) - // Don't switch - {} - else if (j == 1) - // Switch from -Z (south) to +X (east) - vertex.Pos.rotateXZBy(90); - else if (j == 2) - // Switch from -Z (south) to -X (west) - vertex.Pos.rotateXZBy(-90); - else - // Switch from -Z (south) to +Z (north) - vertex.Pos.rotateXZBy(-180); - } - driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2); - } - - // Draw far cloudy fog thing at and below all horizons - for (u32 j = 0; j < 4; j++) { - video::SColor c = cloudyfogcolor; - vertices[0] = video::S3DVertex(-1, -1.0, -1, 0, 0, 1, c, t, t); - vertices[1] = video::S3DVertex( 1, -1.0, -1, 0, 0, 1, c, o, t); - vertices[2] = video::S3DVertex( 1, 0.08, -1, 0, 0, 1, c, o, o); - vertices[3] = video::S3DVertex(-1, 0.08, -1, 0, 0, 1, c, t, o); + vertices[0] = video::S3DVertex(-1, -0.02, -1, 0, 0, 1, m_bgcolor, t, t); + vertices[1] = video::S3DVertex( 1, -0.02, -1, 0, 0, 1, m_bgcolor, o, t); + vertices[2] = video::S3DVertex( 1, 0.45, -1, 0, 0, 1, m_skycolor, o, o); + vertices[3] = video::S3DVertex(-1, 0.45, -1, 0, 0, 1, m_skycolor, t, o); for (video::S3DVertex &vertex : vertices) { if (j == 0) // Don't switch @@ -529,7 +504,7 @@ void Sky::update(float time_of_day, float time_brightness, pointcolor_sun_f.g = pointcolor_light * (float)m_materials[3].EmissiveColor.getGreen() / 255; } else if (!m_default_tint) { - pointcolor_sun_f = m_sky_params.sun_tint; + pointcolor_sun_f = m_sky_params.fog_sun_tint; } else { pointcolor_sun_f.r = pointcolor_light * 1; pointcolor_sun_f.b = pointcolor_light * @@ -548,9 +523,9 @@ void Sky::update(float time_of_day, float time_brightness, ); } else { pointcolor_moon_f = video::SColorf( - (m_sky_params.moon_tint.getRed() / 255) * pointcolor_light, - (m_sky_params.moon_tint.getGreen() / 255) * pointcolor_light, - (m_sky_params.moon_tint.getBlue() / 255) * pointcolor_light, + (m_sky_params.fog_moon_tint.getRed() / 255) * pointcolor_light, + (m_sky_params.fog_moon_tint.getGreen() / 255) * pointcolor_light, + (m_sky_params.fog_moon_tint.getBlue() / 255) * pointcolor_light, 1 ); } @@ -932,17 +907,17 @@ void Sky::setStarCount(u16 star_count, bool force_update) } } -void Sky::setSkyColors(const SkyboxParams sky) +void Sky::setSkyColors(const SkyColor &sky_color) { - m_sky_params.sky_color = sky.sky_color; + m_sky_params.sky_color = sky_color; } void Sky::setHorizonTint(video::SColor sun_tint, video::SColor moon_tint, std::string use_sun_tint) { // Change sun and moon tinting: - m_sky_params.sun_tint = sun_tint; - m_sky_params.moon_tint = moon_tint; + m_sky_params.fog_sun_tint = sun_tint; + m_sky_params.fog_moon_tint = moon_tint; // Faster than comparing strings every rendering frame if (use_sun_tint == "default") m_default_tint = true; diff --git a/src/client/sky.h b/src/client/sky.h index 8637f96d4..3227e8f59 100644 --- a/src/client/sky.h +++ b/src/client/sky.h @@ -94,7 +94,7 @@ public: m_bgcolor = bgcolor; m_skycolor = skycolor; } - void setSkyColors(const SkyboxParams sky); + void setSkyColors(const SkyColor &sky_color); void setHorizonTint(video::SColor sun_tint, video::SColor moon_tint, std::string use_sun_tint); void setInClouds(bool clouds) { m_in_clouds = clouds; } diff --git a/src/client/sound_openal.cpp b/src/client/sound_openal.cpp index 8e696f302..20a651c1d 100644 --- a/src/client/sound_openal.cpp +++ b/src/client/sound_openal.cpp @@ -165,8 +165,8 @@ SoundBuffer *load_opened_ogg_file(OggVorbis_File *oggFile, << "preparing sound buffer" << std::endl; } - infostream << "Audio file " - << filename_for_logging << " loaded" << std::endl; + //infostream << "Audio file " + // << filename_for_logging << " loaded" << std::endl; // Clean up! ov_clear(oggFile); @@ -275,25 +275,38 @@ public: m_device(nullptr, delete_alcdevice), m_context(nullptr, delete_alccontext) { - if (!(m_device = unique_ptr_alcdevice(alcOpenDevice(nullptr), delete_alcdevice))) - throw std::runtime_error("Audio: Global Initialization: Device Open"); + } + + bool init() + { + if (!(m_device = unique_ptr_alcdevice(alcOpenDevice(nullptr), delete_alcdevice))) { + errorstream << "Audio: Global Initialization: Failed to open device" << std::endl; + return false; + } if (!(m_context = unique_ptr_alccontext( alcCreateContext(m_device.get(), nullptr), delete_alccontext))) { - throw std::runtime_error("Audio: Global Initialization: Context Create"); + errorstream << "Audio: Global Initialization: Failed to create context" << std::endl; + return false; } - if (!alcMakeContextCurrent(m_context.get())) - throw std::runtime_error("Audio: Global Initialization: Context Current"); + if (!alcMakeContextCurrent(m_context.get())) { + errorstream << "Audio: Global Initialization: Failed to make current context" << std::endl; + return false; + } alDistanceModel(AL_INVERSE_DISTANCE_CLAMPED); - if (alGetError() != AL_NO_ERROR) - throw std::runtime_error("Audio: Global Initialization: OpenAL Error"); + if (alGetError() != AL_NO_ERROR) { + errorstream << "Audio: Global Initialization: OpenAL Error " << alGetError() << std::endl; + return false; + } infostream << "Audio: Global Initialized: OpenAL " << alGetString(AL_VERSION) << ", using " << alcGetString(m_device.get(), ALC_DEVICE_SPECIFIER) << std::endl; + + return true; } ~SoundManagerSingleton() @@ -498,9 +511,11 @@ public: // Remove stopped sounds void maintain() { - verbosestream<<"OpenALSoundManager::maintain(): " - <<m_sounds_playing.size()<<" playing sounds, " - <<m_buffers.size()<<" sound names loaded"<<std::endl; + if (!m_sounds_playing.empty()) { + verbosestream << "OpenALSoundManager::maintain(): " + << m_sounds_playing.size() <<" playing sounds, " + << m_buffers.size() <<" sound names loaded"<<std::endl; + } std::unordered_set<int> del_list; for (const auto &sp : m_sounds_playing) { int id = sp.first; @@ -530,7 +545,7 @@ public: SoundBuffer *buf = load_ogg_from_file(filepath); if (buf) addBuffer(name, buf); - return false; + return !!buf; } bool loadSoundData(const std::string &name, @@ -539,7 +554,7 @@ public: SoundBuffer *buf = load_ogg_from_buffer(filedata, name); if (buf) addBuffer(name, buf); - return false; + return !!buf; } void updateListener(const v3f &pos, const v3f &vel, const v3f &at, const v3f &up) @@ -680,7 +695,11 @@ public: std::shared_ptr<SoundManagerSingleton> createSoundManagerSingleton() { - return std::shared_ptr<SoundManagerSingleton>(new SoundManagerSingleton()); + auto smg = std::make_shared<SoundManagerSingleton>(); + if (!smg->init()) { + smg.reset(); + } + return smg; } ISoundManager *createOpenALSoundManager(SoundManagerSingleton *smg, OnDemandSoundFetcher *fetcher) diff --git a/src/client/tile.cpp b/src/client/tile.cpp index 3189ab28c..d03588b2b 100644 --- a/src/client/tile.cpp +++ b/src/client/tile.cpp @@ -471,8 +471,8 @@ TextureSource::~TextureSource() driver->removeTexture(t); } - infostream << "~TextureSource() "<< textures_before << "/" - << driver->getTextureCount() << std::endl; + infostream << "~TextureSource() before cleanup: "<< textures_before + << " after: " << driver->getTextureCount() << std::endl; } u32 TextureSource::getTextureId(const std::string &name) @@ -668,7 +668,14 @@ video::ITexture* TextureSource::getTexture(const std::string &name, u32 *id) video::ITexture* TextureSource::getTextureForMesh(const std::string &name, u32 *id) { - return getTexture(name + "^[applyfiltersformesh", id); + static thread_local bool filter_needed = + g_settings->getBool("texture_clean_transparent") || + ((m_setting_trilinear_filter || m_setting_bilinear_filter) && + g_settings->getS32("texture_min_size") > 1); + // Avoid duplicating texture if it won't actually change + if (filter_needed) + return getTexture(name + "^[applyfiltersformesh", id); + return getTexture(name, id); } Palette* TextureSource::getPalette(const std::string &name) @@ -763,6 +770,9 @@ void TextureSource::rebuildImagesAndTextures() video::IVideoDriver *driver = RenderingEngine::get_video_driver(); sanity_check(driver); + infostream << "TextureSource: recreating " << m_textureinfo_cache.size() + << " textures" << std::endl; + // Recreate textures for (TextureInfo &ti : m_textureinfo_cache) { video::IImage *img = generateImage(ti.name); @@ -1270,8 +1280,6 @@ bool TextureSource::generateImagePart(std::string part_of_name, video::IImage *img = generateImage(filename); if (img) { core::dimension2d<u32> dim = img->getDimension(); - infostream<<"Size "<<dim.Width - <<"x"<<dim.Height<<std::endl; core::position2d<s32> pos_base(x, y); video::IImage *img2 = driver->createImage(video::ECF_A8R8G8B8, dim); @@ -1622,6 +1630,9 @@ bool TextureSource::generateImagePart(std::string part_of_name, */ else if (str_starts_with(part_of_name, "[applyfiltersformesh")) { + /* IMPORTANT: When changing this, getTextureForMesh() needs to be + * updated too. */ + // Apply the "clean transparent" filter, if configured. if (g_settings->getBool("texture_clean_transparent")) imageCleanTransparent(baseimg, 127); diff --git a/src/client/tile.h b/src/client/tile.h index 533df676e..52e0a2b2b 100644 --- a/src/client/tile.h +++ b/src/client/tile.h @@ -150,6 +150,8 @@ enum MaterialType{ TILE_MATERIAL_WAVING_LIQUID_BASIC, TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT, TILE_MATERIAL_WAVING_LIQUID_OPAQUE, + TILE_MATERIAL_PLAIN, + TILE_MATERIAL_PLAIN_ALPHA }; // Material flags diff --git a/src/client/wieldmesh.cpp b/src/client/wieldmesh.cpp index 2c6807fab..8cd3e29a9 100644 --- a/src/client/wieldmesh.cpp +++ b/src/client/wieldmesh.cpp @@ -347,7 +347,7 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che scene::SMesh *mesh = nullptr; if (m_enable_shaders) { - u32 shader_id = shdrsrc->getShader("wielded_shader", TILE_MATERIAL_BASIC, NDT_NORMAL); + u32 shader_id = shdrsrc->getShader("object_shader", TILE_MATERIAL_BASIC, NDT_NORMAL); m_material_type = shdrsrc->getShaderInfo(shader_id).material; } @@ -467,10 +467,29 @@ void WieldMeshSceneNode::setColor(video::SColor c) bc.getGreen() * green / 255, bc.getBlue() * blue / 255); scene::IMeshBuffer *buf = mesh->getMeshBuffer(j); - colorizeMeshBuffer(buf, &buffercolor); + + if (m_enable_shaders) + setMeshBufferColor(buf, buffercolor); + else + colorizeMeshBuffer(buf, &buffercolor); } } +void WieldMeshSceneNode::setNodeLightColor(video::SColor color) +{ + if (!m_meshnode) + return; + + if (m_enable_shaders) { + for (u32 i = 0; i < m_meshnode->getMaterialCount(); ++i) { + video::SMaterial &material = m_meshnode->getMaterial(i); + material.EmissiveColor = color; + } + } + + setColor(color); +} + void WieldMeshSceneNode::render() { // note: if this method is changed to actually do something, diff --git a/src/client/wieldmesh.h b/src/client/wieldmesh.h index 7c80a811b..933097230 100644 --- a/src/client/wieldmesh.h +++ b/src/client/wieldmesh.h @@ -87,6 +87,8 @@ public: // Must only be used if the constructor was called with lighting = false void setColor(video::SColor color); + void setNodeLightColor(video::SColor color); + scene::IMesh *getMesh() { return m_meshnode->getMesh(); } virtual void render(); |