From 76afde861de0afa4c69020b16cf52b023ac88de5 Mon Sep 17 00:00:00 2001 From: Maksim Date: Tue, 21 Jul 2020 14:16:25 +0200 Subject: [PATCH 001/266] Android: Fix ConfirmRegistration and PasswordChange input and scale size (#10182) --- src/gui/guiConfirmRegistration.cpp | 19 +++++++++++++++---- src/gui/guiPasswordChange.cpp | 19 ++++++++++++++----- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/src/gui/guiConfirmRegistration.cpp b/src/gui/guiConfirmRegistration.cpp index 55c111df8..020a2796a 100644 --- a/src/gui/guiConfirmRegistration.cpp +++ b/src/gui/guiConfirmRegistration.cpp @@ -73,7 +73,11 @@ void GUIConfirmRegistration::regenerateGui(v2u32 screensize) /* Calculate new sizes and positions */ +#ifdef __ANDROID__ + const float s = m_gui_scale * porting::getDisplayDensity() / 2; +#else const float s = m_gui_scale; +#endif DesiredRect = core::rect( screensize.X / 2 - 600 * s / 2, screensize.Y / 2 - 360 * s / 2, @@ -257,12 +261,19 @@ bool GUIConfirmRegistration::getAndroidUIInput() if (!hasAndroidUIInput() || m_jni_field_name != "password") return false; - std::string text = porting::getInputDialogValue(); - gui::IGUIElement *e = getElementFromId(ID_confirmPassword); - if (e) - e->setText(utf8_to_wide(text).c_str()); + // still waiting + if (porting::getInputDialogState() == -1) + return true; m_jni_field_name.clear(); + + gui::IGUIElement *e = getElementFromId(ID_confirmPassword); + + if (!e || e->getType() != irr::gui::EGUIET_EDIT_BOX) + return false; + + std::string text = porting::getInputDialogValue(); + e->setText(utf8_to_wide(text).c_str()); return false; } #endif diff --git a/src/gui/guiPasswordChange.cpp b/src/gui/guiPasswordChange.cpp index 5311c6fef..74cd62f5b 100644 --- a/src/gui/guiPasswordChange.cpp +++ b/src/gui/guiPasswordChange.cpp @@ -79,7 +79,11 @@ void GUIPasswordChange::regenerateGui(v2u32 screensize) /* Calculate new sizes and positions */ +#ifdef __ANDROID__ + const float s = m_gui_scale * porting::getDisplayDensity() / 2; +#else const float s = m_gui_scale; +#endif DesiredRect = core::rect( screensize.X / 2 - 580 * s / 2, screensize.Y / 2 - 300 * s / 2, @@ -289,6 +293,10 @@ bool GUIPasswordChange::getAndroidUIInput() if (!hasAndroidUIInput()) return false; + // still waiting + if (porting::getInputDialogState() == -1) + return true; + gui::IGUIElement *e = nullptr; if (m_jni_field_name == "old_password") e = getElementFromId(ID_oldPassword); @@ -296,12 +304,13 @@ bool GUIPasswordChange::getAndroidUIInput() e = getElementFromId(ID_newPassword1); else if (m_jni_field_name == "new_password_2") e = getElementFromId(ID_newPassword2); - - if (e) { - std::string text = porting::getInputDialogValue(); - e->setText(utf8_to_wide(text).c_str()); - } m_jni_field_name.clear(); + + if (!e || e->getType() != irr::gui::EGUIET_EDIT_BOX) + return false; + + std::string text = porting::getInputDialogValue(); + e->setText(utf8_to_wide(text).c_str()); return false; } #endif From 8ca602150d4fdce6dcc63fa13e22aeaac5b927cc Mon Sep 17 00:00:00 2001 From: sfan5 Date: Thu, 23 Jul 2020 19:47:58 +0200 Subject: [PATCH 002/266] Replace std::list in networking code (#10215) --- src/network/connection.cpp | 3 ++- src/network/connection.h | 6 +++--- src/network/connectionthreads.cpp | 26 ++++++++++++-------------- 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/src/network/connection.cpp b/src/network/connection.cpp index 3692e45a9..1875d1461 100644 --- a/src/network/connection.cpp +++ b/src/network/connection.cpp @@ -1269,7 +1269,8 @@ bool Connection::deletePeer(session_t peer_id, bool timeout) return false; peer = m_peers[peer_id]; m_peers.erase(peer_id); - m_peer_ids.remove(peer_id); + auto it = std::find(m_peer_ids.begin(), m_peer_ids.end(), peer_id); + m_peer_ids.erase(it); } Address peer_address; diff --git a/src/network/connection.h b/src/network/connection.h index 47b0805ce..2dc6d4397 100644 --- a/src/network/connection.h +++ b/src/network/connection.h @@ -30,7 +30,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "networkprotocol.h" #include #include -#include +#include #include class NetworkPacket; @@ -795,7 +795,7 @@ protected: void PrintInfo(std::ostream &out); - std::list getPeerIDs() + std::vector getPeerIDs() { MutexAutoLock peerlock(m_peers_mutex); return m_peer_ids; @@ -816,7 +816,7 @@ private: u32 m_protocol_id; std::map m_peers; - std::list m_peer_ids; + std::vector m_peer_ids; std::mutex m_peers_mutex; std::unique_ptr m_sendThread; diff --git a/src/network/connectionthreads.cpp b/src/network/connectionthreads.cpp index 9a6617a1c..28ed798d9 100644 --- a/src/network/connectionthreads.cpp +++ b/src/network/connectionthreads.cpp @@ -144,7 +144,7 @@ void ConnectionSendThread::Trigger() bool ConnectionSendThread::packetsQueued() { - std::list peerIds = m_connection->getPeerIDs(); + std::vector peerIds = m_connection->getPeerIDs(); if (!m_outgoing_queue.empty() && !peerIds.empty()) return true; @@ -171,8 +171,8 @@ bool ConnectionSendThread::packetsQueued() void ConnectionSendThread::runTimeouts(float dtime) { - std::list timeouted_peers; - std::list peerIds = m_connection->getPeerIDs(); + std::vector timeouted_peers; + std::vector peerIds = m_connection->getPeerIDs(); for (session_t &peerId : peerIds) { PeerHelper peer = m_connection->getPeerNoEx(peerId); @@ -548,7 +548,7 @@ void ConnectionSendThread::disconnect() // Send to all - std::list peerids = m_connection->getPeerIDs(); + std::vector peerids = m_connection->getPeerIDs(); for (session_t peerid : peerids) { sendAsPacket(peerid, 0, data, false); @@ -620,7 +620,7 @@ void ConnectionSendThread::sendReliable(ConnectionCommand &c) void ConnectionSendThread::sendToAll(u8 channelnum, const SharedBuffer &data) { - std::list peerids = m_connection->getPeerIDs(); + std::vector peerids = m_connection->getPeerIDs(); for (session_t peerid : peerids) { send(peerid, channelnum, data); @@ -629,7 +629,7 @@ void ConnectionSendThread::sendToAll(u8 channelnum, const SharedBuffer &data void ConnectionSendThread::sendToAllReliable(ConnectionCommand &c) { - std::list peerids = m_connection->getPeerIDs(); + std::vector peerids = m_connection->getPeerIDs(); for (session_t peerid : peerids) { PeerHelper peer = m_connection->getPeerNoEx(peerid); @@ -643,8 +643,8 @@ void ConnectionSendThread::sendToAllReliable(ConnectionCommand &c) void ConnectionSendThread::sendPackets(float dtime) { - std::list peerIds = m_connection->getPeerIDs(); - std::list pendingDisconnect; + std::vector peerIds = m_connection->getPeerIDs(); + std::vector pendingDisconnect; std::map pending_unreliable; const unsigned int peer_packet_quota = m_iteration_packets_avaialble @@ -843,13 +843,11 @@ void *ConnectionReceiveThread::run() if (debug_print_timer > 20.0) { debug_print_timer -= 20.0; - std::list peerids = m_connection->getPeerIDs(); + std::vector peerids = m_connection->getPeerIDs(); - for (std::list::iterator i = peerids.begin(); - i != peerids.end(); - i++) + for (auto id : peerids) { - PeerHelper peer = m_connection->getPeerNoEx(*i); + PeerHelper peer = m_connection->getPeerNoEx(id); if (!peer) continue; @@ -1039,7 +1037,7 @@ void ConnectionReceiveThread::receive(SharedBuffer &packetdata, bool ConnectionReceiveThread::getFromBuffers(session_t &peer_id, SharedBuffer &dst) { - std::list peerids = m_connection->getPeerIDs(); + std::vector peerids = m_connection->getPeerIDs(); for (session_t peerid : peerids) { PeerHelper peer = m_connection->getPeerNoEx(peerid); From 808fa5ecb3ddfd5d993000cc6b4c972257e182db Mon Sep 17 00:00:00 2001 From: Wuzzy Date: Thu, 23 Jul 2020 19:54:58 +0200 Subject: [PATCH 003/266] Improve default inventory+wield images of node drawtypes (#9299) --- doc/texture_packs.txt | 1 + src/client/content_mapblock.cpp | 4 +- src/client/content_mapblock.h | 2 +- src/client/wieldmesh.cpp | 207 +++++++++++----------- src/nodedef.h | 16 ++ textures/base/pack/no_texture_airlike.png | Bin 0 -> 178 bytes 6 files changed, 126 insertions(+), 104 deletions(-) create mode 100644 textures/base/pack/no_texture_airlike.png diff --git a/doc/texture_packs.txt b/doc/texture_packs.txt index e7a7dfd3c..79d65a339 100644 --- a/doc/texture_packs.txt +++ b/doc/texture_packs.txt @@ -90,6 +90,7 @@ by texture packs. All existing fallback textures can be found in the directory * `minimap_mask_square.png`: mask used for the square minimap * `minimap_overlay_round.png`: overlay texture for the round minimap * `minimap_overlay_square.png`: overlay texture for the square minimap +* `no_texture_airlike.png`: fallback inventory image for airlike nodes * `object_marker_red.png`: texture for players on the minimap * `player_marker.png`: texture for the own player on the square minimap diff --git a/src/client/content_mapblock.cpp b/src/client/content_mapblock.cpp index 3d06584c4..65a85709b 100644 --- a/src/client/content_mapblock.cpp +++ b/src/client/content_mapblock.cpp @@ -1454,10 +1454,10 @@ void MapblockMeshGenerator::generate() } } -void MapblockMeshGenerator::renderSingle(content_t node) +void MapblockMeshGenerator::renderSingle(content_t node, u8 param2) { p = {0, 0, 0}; - n = MapNode(node, 0xff, 0x00); + n = MapNode(node, 0xff, param2); f = &nodedef->get(n); drawNode(); } diff --git a/src/client/content_mapblock.h b/src/client/content_mapblock.h index 97947cdbe..487d84a07 100644 --- a/src/client/content_mapblock.h +++ b/src/client/content_mapblock.h @@ -174,5 +174,5 @@ public: public: MapblockMeshGenerator(MeshMakeData *input, MeshCollector *output); void generate(); - void renderSingle(content_t node); + void renderSingle(content_t node, u8 param2 = 0x00); }; diff --git a/src/client/wieldmesh.cpp b/src/client/wieldmesh.cpp index 8cd3e29a9..a268895ed 100644 --- a/src/client/wieldmesh.cpp +++ b/src/client/wieldmesh.cpp @@ -303,13 +303,24 @@ void WieldMeshSceneNode::setExtruded(const std::string &imagename, } } -scene::SMesh *createSpecialNodeMesh(Client *client, content_t id, std::vector *colors) +scene::SMesh *createSpecialNodeMesh(Client *client, content_t id, std::vector *colors, const ContentFeatures &f) { MeshMakeData mesh_make_data(client, false, false); MeshCollector collector; mesh_make_data.setSmoothLighting(false); MapblockMeshGenerator gen(&mesh_make_data, &collector); - gen.renderSingle(id); + u8 param2 = 0; + if (f.param_type_2 == CPT2_WALLMOUNTED || + f.param_type_2 == CPT2_COLORED_WALLMOUNTED) { + if (f.drawtype == NDT_TORCHLIKE) + param2 = 1; + else if (f.drawtype == NDT_SIGNLIKE || + f.drawtype == NDT_NODEBOX || + f.drawtype == NDT_MESH) + param2 = 4; + } + gen.renderSingle(id, param2); + colors->clear(); scene::SMesh *mesh = new scene::SMesh(); for (auto &prebuffers : collector.prebuffers) @@ -319,8 +330,9 @@ scene::SMesh *createSpecialNodeMesh(Client *client, content_t id, std::vectorMaterial.setTexture(0, p.layer.texture); p.layer.applyMaterialOptions(buf->Material); @@ -368,73 +380,61 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che // Handle nodes // See also CItemDefManager::createClientCached() if (def.type == ITEM_NODE) { - if (f.mesh_ptr[0]) { - // e.g. mesh nodes and nodeboxes - mesh = cloneMesh(f.mesh_ptr[0]); - postProcessNodeMesh(mesh, f, m_enable_shaders, true, - &m_material_type, &m_colors); + bool cull_backface = f.needsBackfaceCulling(); + + // Select rendering method + switch (f.drawtype) { + case NDT_AIRLIKE: + setExtruded("no_texture_airlike.png", "", + v3f(1.0, 1.0, 1.0), tsrc, 1); + break; + case NDT_SIGNLIKE: + case NDT_TORCHLIKE: + case NDT_RAILLIKE: + case NDT_PLANTLIKE: + case NDT_PLANTLIKE_ROOTED: + case NDT_FLOWINGLIQUID: { + v3f wscale = def.wield_scale; + if (f.drawtype == NDT_FLOWINGLIQUID) + wscale.Z *= 0.1f; + setExtruded(tsrc->getTextureName(f.tiles[0].layers[0].texture_id), + tsrc->getTextureName(f.tiles[0].layers[1].texture_id), + wscale, tsrc, + f.tiles[0].layers[0].animation_frame_count); + // Add color + const TileLayer &l0 = f.tiles[0].layers[0]; + m_colors.emplace_back(l0.has_color, l0.color); + const TileLayer &l1 = f.tiles[0].layers[1]; + m_colors.emplace_back(l1.has_color, l1.color); + break; + } + case NDT_NORMAL: + case NDT_ALLFACES: + case NDT_LIQUID: + setCube(f, def.wield_scale); + break; + default: + // Render non-trivial drawtypes like the actual node + mesh = createSpecialNodeMesh(client, id, &m_colors, f); changeToMesh(mesh); mesh->drop(); - // mesh is pre-scaled by BS * f->visual_scale m_meshnode->setScale( - def.wield_scale * WIELD_SCALE_FACTOR - / (BS * f.visual_scale)); - } else { - switch (f.drawtype) { - case NDT_AIRLIKE: { - changeToMesh(nullptr); - break; - } - case NDT_PLANTLIKE: { - setExtruded(tsrc->getTextureName(f.tiles[0].layers[0].texture_id), - tsrc->getTextureName(f.tiles[0].layers[1].texture_id), - def.wield_scale, tsrc, - f.tiles[0].layers[0].animation_frame_count); - // Add color - const TileLayer &l0 = f.tiles[0].layers[0]; - m_colors.emplace_back(l0.has_color, l0.color); - const TileLayer &l1 = f.tiles[0].layers[1]; - m_colors.emplace_back(l1.has_color, l1.color); - break; - } - case NDT_PLANTLIKE_ROOTED: { - setExtruded(tsrc->getTextureName(f.special_tiles[0].layers[0].texture_id), - "", def.wield_scale, tsrc, - f.special_tiles[0].layers[0].animation_frame_count); - // Add color - const TileLayer &l0 = f.special_tiles[0].layers[0]; - m_colors.emplace_back(l0.has_color, l0.color); - break; - } - case NDT_NORMAL: - case NDT_ALLFACES: - case NDT_LIQUID: - case NDT_FLOWINGLIQUID: { - setCube(f, def.wield_scale); - break; - } - default: { - mesh = createSpecialNodeMesh(client, id, &m_colors); - changeToMesh(mesh); - mesh->drop(); - m_meshnode->setScale( - def.wield_scale * WIELD_SCALE_FACTOR - / (BS * f.visual_scale)); - } - } + def.wield_scale * WIELD_SCALE_FACTOR + / (BS * f.visual_scale)); + break; } + u32 material_count = m_meshnode->getMaterialCount(); for (u32 i = 0; i < material_count; ++i) { video::SMaterial &material = m_meshnode->getMaterial(i); material.MaterialType = m_material_type; material.MaterialTypeParam = 0.5f; - material.setFlag(video::EMF_BACK_FACE_CULLING, true); + material.setFlag(video::EMF_BACK_FACE_CULLING, cull_backface); material.setFlag(video::EMF_BILINEAR_FILTER, m_bilinear_filter); material.setFlag(video::EMF_TRILINEAR_FILTER, m_trilinear_filter); } return; - } - else if (!def.inventory_image.empty()) { + } else if (!def.inventory_image.empty()) { setExtruded(def.inventory_image, def.inventory_overlay, def.wield_scale, tsrc, 1); m_colors.emplace_back(); @@ -529,6 +529,8 @@ void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result) // Shading is on by default result->needs_shading = true; + bool cull_backface = f.needsBackfaceCulling(); + // If inventory_image is defined, it overrides everything else if (!def.inventory_image.empty()) { mesh = getExtrudedMesh(tsrc, def.inventory_image, @@ -538,51 +540,54 @@ void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result) result->buffer_colors.emplace_back(true, video::SColor(0xFFFFFFFF)); // Items with inventory images do not need shading result->needs_shading = false; + } else if (def.type == ITEM_NODE && f.drawtype == NDT_AIRLIKE) { + // Fallback image for airlike node + mesh = getExtrudedMesh(tsrc, "no_texture_airlike.png", + def.inventory_overlay); + result->needs_shading = false; } else if (def.type == ITEM_NODE) { - if (f.mesh_ptr[0]) { - mesh = cloneMesh(f.mesh_ptr[0]); - scaleMesh(mesh, v3f(0.12, 0.12, 0.12)); + switch (f.drawtype) { + case NDT_NORMAL: + case NDT_ALLFACES: + case NDT_LIQUID: + case NDT_FLOWINGLIQUID: { + scene::IMesh *cube = g_extrusion_mesh_cache->createCube(); + mesh = cloneMesh(cube); + cube->drop(); + if (f.drawtype == NDT_FLOWINGLIQUID) { + scaleMesh(mesh, v3f(1.2, 0.03, 1.2)); + translateMesh(mesh, v3f(0, -0.57, 0)); + } else + scaleMesh(mesh, v3f(1.2, 1.2, 1.2)); + // add overlays postProcessNodeMesh(mesh, f, false, false, nullptr, - &result->buffer_colors); - } else { - switch (f.drawtype) { - case NDT_PLANTLIKE: { - mesh = getExtrudedMesh(tsrc, - tsrc->getTextureName(f.tiles[0].layers[0].texture_id), - tsrc->getTextureName(f.tiles[0].layers[1].texture_id)); - // Add color - const TileLayer &l0 = f.tiles[0].layers[0]; - result->buffer_colors.emplace_back(l0.has_color, l0.color); - const TileLayer &l1 = f.tiles[0].layers[1]; - result->buffer_colors.emplace_back(l1.has_color, l1.color); - break; - } - case NDT_PLANTLIKE_ROOTED: { - mesh = getExtrudedMesh(tsrc, - tsrc->getTextureName(f.special_tiles[0].layers[0].texture_id), ""); - // Add color - const TileLayer &l0 = f.special_tiles[0].layers[0]; - result->buffer_colors.emplace_back(l0.has_color, l0.color); - break; - } - case NDT_NORMAL: - case NDT_ALLFACES: - case NDT_LIQUID: - case NDT_FLOWINGLIQUID: { - scene::IMesh *cube = g_extrusion_mesh_cache->createCube(); - mesh = cloneMesh(cube); - cube->drop(); - scaleMesh(mesh, v3f(1.2, 1.2, 1.2)); - // add overlays - postProcessNodeMesh(mesh, f, false, false, nullptr, - &result->buffer_colors); - break; - } - default: { - mesh = createSpecialNodeMesh(client, id, &result->buffer_colors); - scaleMesh(mesh, v3f(0.12, 0.12, 0.12)); - } - } + &result->buffer_colors, true); + break; + } + case NDT_PLANTLIKE: { + mesh = getExtrudedMesh(tsrc, + tsrc->getTextureName(f.tiles[0].layers[0].texture_id), + tsrc->getTextureName(f.tiles[0].layers[1].texture_id)); + // Add color + const TileLayer &l0 = f.tiles[0].layers[0]; + result->buffer_colors.emplace_back(l0.has_color, l0.color); + const TileLayer &l1 = f.tiles[0].layers[1]; + result->buffer_colors.emplace_back(l1.has_color, l1.color); + break; + } + case NDT_PLANTLIKE_ROOTED: { + mesh = getExtrudedMesh(tsrc, + tsrc->getTextureName(f.special_tiles[0].layers[0].texture_id), ""); + // Add color + const TileLayer &l0 = f.special_tiles[0].layers[0]; + result->buffer_colors.emplace_back(l0.has_color, l0.color); + break; + } + default: + // Render non-trivial drawtypes like the actual node + mesh = createSpecialNodeMesh(client, id, &result->buffer_colors, f); + scaleMesh(mesh, v3f(0.12, 0.12, 0.12)); + break; } u32 mc = mesh->getMeshBufferCount(); @@ -593,7 +598,7 @@ void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result) material.MaterialTypeParam = 0.5f; material.setFlag(video::EMF_BILINEAR_FILTER, false); material.setFlag(video::EMF_TRILINEAR_FILTER, false); - material.setFlag(video::EMF_BACK_FACE_CULLING, true); + material.setFlag(video::EMF_BACK_FACE_CULLING, cull_backface); material.setFlag(video::EMF_LIGHTING, false); } diff --git a/src/nodedef.h b/src/nodedef.h index cf03abaae..ff52f976f 100644 --- a/src/nodedef.h +++ b/src/nodedef.h @@ -425,6 +425,22 @@ struct ContentFeatures /* Some handy methods */ + bool needsBackfaceCulling() const + { + switch (drawtype) { + case NDT_TORCHLIKE: + case NDT_SIGNLIKE: + case NDT_FIRELIKE: + case NDT_RAILLIKE: + case NDT_PLANTLIKE: + case NDT_PLANTLIKE_ROOTED: + case NDT_MESH: + return false; + default: + return true; + } + } + bool isLiquid() const{ return (liquid_type != LIQUID_NONE); } diff --git a/textures/base/pack/no_texture_airlike.png b/textures/base/pack/no_texture_airlike.png new file mode 100644 index 0000000000000000000000000000000000000000..634ee8ca552b07a2b0bc7ecdc1848a480175a18d GIT binary patch literal 178 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`m7Xq+Ar*{sC$aJ!FyJ}z>A$$Y ziJG@KD|_m7*4uAta_96tZl18^Z{qXgHB#yR4(m!b3OF&d&S{>hKRdx8K>R&px(#!~ zffVN*)lDX*2N(>wuJ_GkZK^V5aA^3+`Qw*K5|_7xi*(0th1?{8yI$Km8~HCrZtdM| b=~G@PGsEDqVDx&RBN#kg{an^LB{Ts5xq&{0 literal 0 HcmV?d00001 From ae83edd16581b2b5426b565e703a8766e88dbbf6 Mon Sep 17 00:00:00 2001 From: Wuzzy Date: Mon, 27 Jul 2020 19:40:33 +0200 Subject: [PATCH 004/266] Play place_failed sound if occupied or cannot attach (#9486) --- doc/lua_api.txt | 7 +++++-- src/client/game.cpp | 2 ++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/doc/lua_api.txt b/doc/lua_api.txt index e0c895c97..5fe02b452 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -7217,10 +7217,13 @@ Used by `minetest.register_node`. -- Node was placed. Also played after falling place_failed = , - -- When node placement failed + -- When node placement failed. + -- Note: This happens if the _built-in_ node placement failed. + -- This sound will still be played if the node is placed in the + -- `on_place` callback manually. fall = , - -- When node starts to fall + -- When node starts to fall or is detached }, drop = "", diff --git a/src/client/game.cpp b/src/client/game.cpp index 42d60b21c..20d2c6f90 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -3379,6 +3379,7 @@ bool Game::nodePlacement(const ItemDefinition &selected_def, } else { node = map.getNode(p, &is_valid_position); if (is_valid_position && !nodedef->get(node).buildable_to) { + soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed; // Report to server client->interact(INTERACT_PLACE, pointed); return false; @@ -3451,6 +3452,7 @@ bool Game::nodePlacement(const ItemDefinition &selected_def, pp = p + v3s16(0, -1, 0); if (!nodedef->get(map.getNode(pp)).walkable) { + soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed; // Report to server client->interact(INTERACT_PLACE, pointed); return false; From f948e2c58570df6bc77226b6066fec5ed90051ee Mon Sep 17 00:00:00 2001 From: rubenwardy Date: Tue, 28 Jul 2020 17:01:52 +0100 Subject: [PATCH 005/266] Fix incorrect view URL for games package.id is a sanitised combination of author and basename, used to compare remote and local content. Minetest ignores `_game` when comparing game names, so package.id has `_game` removed. This meant that the wrong URL was being generated for View. --- builtin/mainmenu/dlg_contentstore.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/builtin/mainmenu/dlg_contentstore.lua b/builtin/mainmenu/dlg_contentstore.lua index 01c42be0b..37ceeb6c8 100644 --- a/builtin/mainmenu/dlg_contentstore.lua +++ b/builtin/mainmenu/dlg_contentstore.lua @@ -505,8 +505,9 @@ function store.handle_submit(this, fields) end if fields["view_" .. i] then - local url = ("%s/packages/%s?protocol_version=%d"):format( - core.settings:get("contentdb_url"), package.id, core.get_max_supp_proto()) + local url = ("%s/packages/%s/%s?protocol_version=%d"):format( + core.settings:get("contentdb_url"), + package.author, package.name, core.get_max_supp_proto()) core.open_url(url) return true end From 3ce03d1c2a63d261c83f5962cd13212697f19472 Mon Sep 17 00:00:00 2001 From: Hugues Ross Date: Tue, 28 Jul 2020 13:16:57 -0400 Subject: [PATCH 006/266] Sanitize world directory names on create. Keep original name separate (#9432) Blacklisted characters are replaced by '_' in the path. The display name is stored in world.mt, and duplicate file names are resolved by adding an incrementing suffix (_1, _2, _3, etc). --- src/content/subgames.cpp | 54 +++++++++++++++++++----- src/content/subgames.h | 5 ++- src/script/lua_api/l_mainmenu.cpp | 9 ++-- src/server.cpp | 9 +++- src/util/string.cpp | 68 +++++++++++++++++++++++++++++++ src/util/string.h | 8 ++++ 6 files changed, 136 insertions(+), 17 deletions(-) diff --git a/src/content/subgames.cpp b/src/content/subgames.cpp index 170f54e20..695ba431f 100644 --- a/src/content/subgames.cpp +++ b/src/content/subgames.cpp @@ -31,6 +31,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "client/tile.h" // getImagePath #endif +// The maximum number of identical world names allowed +#define MAX_WORLD_NAMES 100 + bool getGameMinetestConfig(const std::string &game_path, Settings &conf) { std::string conf_path = game_path + DIR_DELIM + "minetest.conf"; @@ -213,6 +216,21 @@ bool getWorldExists(const std::string &world_path) fs::PathExists(world_path + DIR_DELIM + "world.mt")); } +//! Try to get the displayed name of a world +std::string getWorldName(const std::string &world_path, const std::string &default_name) +{ + std::string conf_path = world_path + DIR_DELIM + "world.mt"; + Settings conf; + bool succeeded = conf.readConfigFile(conf_path.c_str()); + if (!succeeded) { + return default_name; + } + + if (!conf.exists("world_name")) + return default_name; + return conf.get("world_name"); +} + std::string getWorldGameId(const std::string &world_path, bool can_be_legacy) { std::string conf_path = world_path + DIR_DELIM + "world.mt"; @@ -259,7 +277,7 @@ std::vector getAvailableWorlds() if (!dln.dir) continue; std::string fullpath = worldspath + DIR_DELIM + dln.name; - std::string name = dln.name; + std::string name = getWorldName(fullpath, dln.name); // Just allow filling in the gameid always for now bool can_be_legacy = true; std::string gameid = getWorldGameId(fullpath, can_be_legacy); @@ -288,8 +306,24 @@ std::vector getAvailableWorlds() return worlds; } -bool loadGameConfAndInitWorld(const std::string &path, const SubgameSpec &gamespec) +void loadGameConfAndInitWorld(const std::string &path, const std::string &name, + const SubgameSpec &gamespec, bool create_world) { + std::string final_path = path; + + // If we're creating a new world, ensure that the path isn't already taken + if (create_world) { + int counter = 1; + while (fs::PathExists(final_path) && counter < MAX_WORLD_NAMES) { + final_path = path + "_" + std::to_string(counter); + counter++; + } + + if (fs::PathExists(final_path)) { + throw BaseException("Too many similar filenames"); + } + } + // Override defaults with those provided by the game. // We clear and reload the defaults because the defaults // might have been overridden by other subgame config @@ -300,15 +334,16 @@ bool loadGameConfAndInitWorld(const std::string &path, const SubgameSpec &gamesp getGameMinetestConfig(gamespec.path, game_defaults); g_settings->overrideDefaults(&game_defaults); - infostream << "Initializing world at " << path << std::endl; + infostream << "Initializing world at " << final_path << std::endl; - fs::CreateAllDirs(path); + fs::CreateAllDirs(final_path); // Create world.mt if does not already exist - std::string worldmt_path = path + DIR_DELIM "world.mt"; + std::string worldmt_path = final_path + DIR_DELIM "world.mt"; if (!fs::PathExists(worldmt_path)) { Settings conf; + conf.set("world_name", name); conf.set("gameid", gamespec.id); conf.set("backend", "sqlite3"); conf.set("player_backend", "sqlite3"); @@ -316,16 +351,16 @@ bool loadGameConfAndInitWorld(const std::string &path, const SubgameSpec &gamesp conf.setBool("creative_mode", g_settings->getBool("creative_mode")); conf.setBool("enable_damage", g_settings->getBool("enable_damage")); - if (!conf.updateConfigFile(worldmt_path.c_str())) - return false; + if (!conf.updateConfigFile(worldmt_path.c_str())) { + throw BaseException("Failed to update the config file"); + } } // Create map_meta.txt if does not already exist - std::string map_meta_path = path + DIR_DELIM + "map_meta.txt"; + std::string map_meta_path = final_path + DIR_DELIM + "map_meta.txt"; if (!fs::PathExists(map_meta_path)) { verbosestream << "Creating map_meta.txt (" << map_meta_path << ")" << std::endl; - fs::CreateAllDirs(path); std::ostringstream oss(std::ios_base::binary); Settings conf; @@ -338,5 +373,4 @@ bool loadGameConfAndInitWorld(const std::string &path, const SubgameSpec &gamesp fs::safeWriteToFile(map_meta_path, oss.str()); } - return true; } diff --git a/src/content/subgames.h b/src/content/subgames.h index 4198ea860..35b619aaf 100644 --- a/src/content/subgames.h +++ b/src/content/subgames.h @@ -63,6 +63,8 @@ std::set getAvailableGameIds(); std::vector getAvailableGames(); bool getWorldExists(const std::string &world_path); +//! Try to get the displayed name of a world +std::string getWorldName(const std::string &world_path, const std::string &default_name); std::string getWorldGameId(const std::string &world_path, bool can_be_legacy = false); struct WorldSpec @@ -88,4 +90,5 @@ std::vector getAvailableWorlds(); // loads the subgame's config and creates world directory // and world.mt if they don't exist -bool loadGameConfAndInitWorld(const std::string &path, const SubgameSpec &gamespec); +void loadGameConfAndInitWorld(const std::string &path, const std::string &name, + const SubgameSpec &gamespec, bool create_world); diff --git a/src/script/lua_api/l_mainmenu.cpp b/src/script/lua_api/l_mainmenu.cpp index f32c477c2..e49ec4052 100644 --- a/src/script/lua_api/l_mainmenu.cpp +++ b/src/script/lua_api/l_mainmenu.cpp @@ -618,7 +618,7 @@ int ModApiMainMenu::l_create_world(lua_State *L) std::string path = porting::path_user + DIR_DELIM "worlds" + DIR_DELIM - + name; + + sanitizeDirName(name, "world_"); std::vector games = getAvailableGames(); @@ -626,10 +626,11 @@ int ModApiMainMenu::l_create_world(lua_State *L) (gameidx < (int) games.size())) { // Create world if it doesn't exist - if (!loadGameConfAndInitWorld(path, games[gameidx])) { - lua_pushstring(L, "Failed to initialize world"); - } else { + try { + loadGameConfAndInitWorld(path, name, games[gameidx], true); lua_pushnil(L); + } catch (const BaseException &e) { + lua_pushstring(L, (std::string("Failed to initialize world: ") + e.what()).c_str()); } } else { lua_pushstring(L, "Invalid game index"); diff --git a/src/server.cpp b/src/server.cpp index fe2bb3840..53ee8c444 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -356,8 +356,13 @@ void Server::init() infostream << "- game: " << m_gamespec.path << std::endl; // Create world if it doesn't exist - if (!loadGameConfAndInitWorld(m_path_world, m_gamespec)) - throw ServerError("Failed to initialize world"); + try { + loadGameConfAndInitWorld(m_path_world, + fs::GetFilenameFromPath(m_path_world.c_str()), + m_gamespec, false); + } catch (const BaseException &e) { + throw ServerError(std::string("Failed to initialize world: ") + e.what()); + } // Create emerge manager m_emerge = new EmergeManager(this); diff --git a/src/util/string.cpp b/src/util/string.cpp index 6e1db798c..8381a29c5 100644 --- a/src/util/string.cpp +++ b/src/util/string.cpp @@ -27,6 +27,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "translation.h" #include +#include #include #include #include @@ -889,3 +890,70 @@ std::wstring translate_string(const std::wstring &s) return translate_string(s, g_client_translations); #endif } + +static const std::array disallowed_dir_names = { + // Problematic filenames from here: + // https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file#file-and-directory-names + L"CON", + L"PRN", + L"AUX", + L"NUL", + L"COM1", + L"COM2", + L"COM3", + L"COM4", + L"COM5", + L"COM6", + L"COM7", + L"COM8", + L"COM9", + L"LPT1", + L"LPT2", + L"LPT3", + L"LPT4", + L"LPT5", + L"LPT6", + L"LPT7", + L"LPT8", + L"LPT9", +}; + +/** + * List of characters that are blacklisted from created directories + */ +static const std::wstring disallowed_path_chars = L"<>:\"/\\|?*."; + +/** + * Sanitize the name of a new directory. This consists of two stages: + * 1. Check for 'reserved filenames' that can't be used on some filesystems + * and add a prefix to them + * 2. Remove 'unsafe' characters from the name by replacing them with '_' + */ +std::string sanitizeDirName(const std::string &str, const std::string &optional_prefix) +{ + std::wstring safe_name = utf8_to_wide(str); + + for (std::wstring disallowed_name : disallowed_dir_names) { + if (str_equal(safe_name, disallowed_name, true)) { + safe_name = utf8_to_wide(optional_prefix) + safe_name; + break; + } + } + + for (unsigned long i = 0; i < safe_name.length(); i++) { + bool is_valid = true; + + // Unlikely, but control characters should always be blacklisted + if (safe_name[i] < 32) { + is_valid = false; + } else if (safe_name[i] < 128) { + is_valid = disallowed_path_chars.find_first_of(safe_name[i]) + == std::wstring::npos; + } + + if (!is_valid) + safe_name[i] = '_'; + } + + return wide_to_utf8(safe_name); +} diff --git a/src/util/string.h b/src/util/string.h index 185fb55e2..6fd11fadc 100644 --- a/src/util/string.h +++ b/src/util/string.h @@ -746,3 +746,11 @@ inline irr::core::stringw utf8_to_stringw(const std::string &input) std::wstring str = utf8_to_wide(input); return irr::core::stringw(str.c_str()); } + +/** + * Sanitize the name of a new directory. This consists of two stages: + * 1. Check for 'reserved filenames' that can't be used on some filesystems + * and prefix them + * 2. Remove 'unsafe' characters from the name by replacing them with '_' + */ +std::string sanitizeDirName(const std::string &str, const std::string &optional_prefix); From f34abaedd2b9277c1862cd9b82ca3338747f104e Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Wed, 29 Jul 2020 20:46:36 +0200 Subject: [PATCH 007/266] decode_base64: Allow '=' padding character '=' is a valid character, but minetest.decode_base64 returned nil when it was used for padding. --- src/util/base64.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/util/base64.cpp b/src/util/base64.cpp index c75f98598..6e1584410 100644 --- a/src/util/base64.cpp +++ b/src/util/base64.cpp @@ -34,8 +34,9 @@ static const std::string base64_chars = "0123456789+/"; -static inline bool is_base64(unsigned char c) { - return (isalnum(c) || (c == '+') || (c == '/')); +static inline bool is_base64(unsigned char c) +{ + return isalnum(c) || c == '+' || c == '/' || c == '='; } bool base64_is_valid(std::string const& s) From 715a123a33db7b0f191259ba68cbc9c565d0d4e8 Mon Sep 17 00:00:00 2001 From: Lejo Date: Thu, 30 Jul 2020 00:16:21 +0300 Subject: [PATCH 008/266] Add PUT and DELETE request + specific method value to HTTP API (#9909) --- doc/lua_api.txt | 12 +++++-- src/client/clientmedia.cpp | 3 +- src/httpfetch.cpp | 66 +++++++++++++++++++++-------------- src/httpfetch.h | 22 +++++++++--- src/network/networkprotocol.h | 2 +- src/script/lua_api/l_http.cpp | 29 +++++++++++++-- src/script/lua_api/l_http.h | 4 +-- src/serverlist.cpp | 4 +-- 8 files changed, 98 insertions(+), 44 deletions(-) diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 5fe02b452..2d22dc899 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -8071,11 +8071,13 @@ Used by `HTTPApiTable.fetch` and `HTTPApiTable.fetch_async`. timeout = 10, -- Timeout for connection in seconds. Default is 3 seconds. - post_data = "Raw POST request data string" OR {field1 = "data1", field2 = "data2"}, - -- Optional, if specified a POST request with post_data is performed. + method = "GET", "POST", "PUT" or "DELETE" + -- The http method to use. Defaults to "GET". + + data = "Raw request data string" OR {field1 = "data1", field2 = "data2"}, + -- Data for the POST, PUT or DELETE request. -- Accepts both a string and a table. If a table is specified, encodes -- table as x-www-form-urlencoded key-value pairs. - -- If post_data is not specified, a GET request is performed instead. user_agent = "ExampleUserAgent", -- Optional, if specified replaces the default minetest user agent with @@ -8089,6 +8091,10 @@ Used by `HTTPApiTable.fetch` and `HTTPApiTable.fetch_async`. multipart = boolean -- Optional, if true performs a multipart HTTP request. -- Default is false. + -- Post only, data must be array + + post_data = "Raw POST request data string" OR {field1 = "data1", field2 = "data2"}, + -- Deprecated, use `data` instead. Forces `method = "POST"`. } `HTTPRequestResult` definition diff --git a/src/client/clientmedia.cpp b/src/client/clientmedia.cpp index 8cd3b6bcc..c4c08c05d 100644 --- a/src/client/clientmedia.cpp +++ b/src/client/clientmedia.cpp @@ -260,7 +260,8 @@ void ClientMediaDownloader::initialStep(Client *client) fetch_request.request_id = m_httpfetch_next_id; // == i fetch_request.timeout = m_httpfetch_timeout; fetch_request.connect_timeout = m_httpfetch_timeout; - fetch_request.post_data = required_hash_set; + fetch_request.method = HTTP_POST; + fetch_request.raw_data = required_hash_set; fetch_request.extra_headers.emplace_back( "Content-Type: application/octet-stream"); diff --git a/src/httpfetch.cpp b/src/httpfetch.cpp index 326b5052f..65202ce3e 100644 --- a/src/httpfetch.cpp +++ b/src/httpfetch.cpp @@ -294,13 +294,11 @@ HTTPFetchOngoing::HTTPFetchOngoing(const HTTPFetchRequest &request_, curl_easy_setopt(curl, CURLOPT_WRITEDATA, &oss); } - // Set POST (or GET) data - if (request.post_fields.empty() && request.post_data.empty()) { - curl_easy_setopt(curl, CURLOPT_HTTPGET, 1); - } else if (request.multipart) { + // Set data from fields or raw_data + if (request.multipart) { curl_httppost *last = NULL; - for (StringMap::iterator it = request.post_fields.begin(); - it != request.post_fields.end(); ++it) { + for (StringMap::iterator it = request.fields.begin(); + it != request.fields.end(); ++it) { curl_formadd(&post, &last, CURLFORM_NAMELENGTH, it->first.size(), CURLFORM_PTRNAME, it->first.c_str(), @@ -311,28 +309,42 @@ HTTPFetchOngoing::HTTPFetchOngoing(const HTTPFetchRequest &request_, curl_easy_setopt(curl, CURLOPT_HTTPPOST, post); // request.post_fields must now *never* be // modified until CURLOPT_HTTPPOST is cleared - } else if (request.post_data.empty()) { - curl_easy_setopt(curl, CURLOPT_POST, 1); - std::string str; - for (auto &post_field : request.post_fields) { - if (!str.empty()) - str += "&"; - str += urlencode(post_field.first); - str += "="; - str += urlencode(post_field.second); - } - curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, - str.size()); - curl_easy_setopt(curl, CURLOPT_COPYPOSTFIELDS, - str.c_str()); } else { - curl_easy_setopt(curl, CURLOPT_POST, 1); - curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, - request.post_data.size()); - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, - request.post_data.c_str()); - // request.post_data must now *never* be - // modified until CURLOPT_POSTFIELDS is cleared + switch (request.method) { + case HTTP_GET: + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1); + break; + case HTTP_POST: + curl_easy_setopt(curl, CURLOPT_POST, 1); + break; + case HTTP_PUT: + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT"); + break; + case HTTP_DELETE: + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE"); + break; + } + if (request.method != HTTP_GET) { + if (!request.raw_data.empty()) { + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, + request.raw_data.size()); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, + request.raw_data.c_str()); + } else if (!request.fields.empty()) { + std::string str; + for (auto &field : request.fields) { + if (!str.empty()) + str += "&"; + str += urlencode(field.first); + str += "="; + str += urlencode(field.second); + } + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, + str.size()); + curl_easy_setopt(curl, CURLOPT_COPYPOSTFIELDS, + str.c_str()); + } + } } // Set additional HTTP headers for (const std::string &extra_header : request.extra_headers) { diff --git a/src/httpfetch.h b/src/httpfetch.h index ae8b5afb5..3b9f17f0a 100644 --- a/src/httpfetch.h +++ b/src/httpfetch.h @@ -28,6 +28,15 @@ with this program; if not, write to the Free Software Foundation, Inc., #define HTTPFETCH_DISCARD 0 #define HTTPFETCH_SYNC 1 +// Methods +enum HttpMethod : u8 +{ + HTTP_GET, + HTTP_POST, + HTTP_PUT, + HTTP_DELETE, +}; + struct HTTPFetchRequest { std::string url = ""; @@ -50,12 +59,15 @@ struct HTTPFetchRequest // application/x-www-form-urlencoded. POST-only. bool multipart = false; - // POST fields. Fields are escaped properly. - // If this is empty a GET request is done instead. - StringMap post_fields; + // The Method to use default = GET + // Avaible methods GET, POST, PUT, DELETE + HttpMethod method = HTTP_GET; - // Raw POST data, overrides post_fields. - std::string post_data; + // Fields of the request + StringMap fields; + + // Raw data of the request overrides fields + std::string raw_data; // If not empty, should contain entries such as "Accept: text/html" std::vector extra_headers; diff --git a/src/network/networkprotocol.h b/src/network/networkprotocol.h index 28abf02c0..05600cda9 100644 --- a/src/network/networkprotocol.h +++ b/src/network/networkprotocol.h @@ -1034,7 +1034,7 @@ const static std::string accessDeniedStrings[SERVER_ACCESSDENIED_MAX] = { "This server has experienced an internal error. You will now be disconnected." }; -enum PlayerListModifer: u8 +enum PlayerListModifer : u8 { PLAYER_LIST_INIT, PLAYER_LIST_ADD, diff --git a/src/script/lua_api/l_http.cpp b/src/script/lua_api/l_http.cpp index ec43bf174..5ea3b3f99 100644 --- a/src/script/lua_api/l_http.cpp +++ b/src/script/lua_api/l_http.cpp @@ -49,17 +49,40 @@ void ModApiHttp::read_http_fetch_request(lua_State *L, HTTPFetchRequest &req) req.multipart = getboolfield_default(L, 1, "multipart", false); req.timeout = getintfield_default(L, 1, "timeout", 3) * 1000; - // post_data: if table, post form data, otherwise raw data + lua_getfield(L, 1, "method"); + if (lua_isstring(L, -1)) { + std::string mth = getstringfield_default(L, 1, "method", ""); + if (mth == "GET") + req.method = HTTP_GET; + else if (mth == "POST") + req.method = HTTP_POST; + else if (mth == "PUT") + req.method = HTTP_PUT; + else if (mth == "DELETE") + req.method = HTTP_DELETE; + } + lua_pop(L, 1); + + // post_data: if table, post form data, otherwise raw data DEPRECATED use data and method instead lua_getfield(L, 1, "post_data"); + if (lua_isnil(L, 2)) { + lua_pop(L, 1); + lua_getfield(L, 1, "data"); + } + else { + req.method = HTTP_POST; + } + if (lua_istable(L, 2)) { lua_pushnil(L); while (lua_next(L, 2) != 0) { - req.post_fields[readParam(L, -2)] = readParam(L, -1); + req.fields[readParam(L, -2)] = readParam(L, -1); lua_pop(L, 1); } } else if (lua_isstring(L, 2)) { - req.post_data = readParam(L, 2); + req.raw_data = readParam(L, 2); } + lua_pop(L, 1); lua_getfield(L, 1, "extra_headers"); diff --git a/src/script/lua_api/l_http.h b/src/script/lua_api/l_http.h index de6e51b37..c3a2a5276 100644 --- a/src/script/lua_api/l_http.h +++ b/src/script/lua_api/l_http.h @@ -32,10 +32,10 @@ private: static void read_http_fetch_request(lua_State *L, HTTPFetchRequest &req); static void push_http_fetch_result(lua_State *L, HTTPFetchResult &res, bool completed = true); - // http_fetch_sync({url=, timeout=, post_data=}) + // http_fetch_sync({url=, timeout=, data=}) static int l_http_fetch_sync(lua_State *L); - // http_fetch_async({url=, timeout=, post_data=}) + // http_fetch_async({url=, timeout=, data=}) static int l_http_fetch_async(lua_State *L); // http_fetch_async_get(handle) diff --git a/src/serverlist.cpp b/src/serverlist.cpp index 18264e933..2f6ab2e61 100644 --- a/src/serverlist.cpp +++ b/src/serverlist.cpp @@ -261,11 +261,11 @@ void sendAnnounce(AnnounceAction action, HTTPFetchRequest fetch_request; fetch_request.url = g_settings->get("serverlist_url") + std::string("/announce"); - fetch_request.post_fields["json"] = fastWriteJson(server); + fetch_request.method = HTTP_POST; + fetch_request.fields["json"] = fastWriteJson(server); fetch_request.multipart = true; httpfetch_async(fetch_request); } #endif } // namespace ServerList - From 470f32821627a56b682ea1947ab5a50ef57c1c10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20M=C3=BCller?= <34514239+appgurueu@users.noreply.github.com> Date: Wed, 29 Jul 2020 23:17:52 +0200 Subject: [PATCH 009/266] Revert "Get rid of non-ascii characters in the debug display code (#8821)" (#9828) This reverts commit 4f9ccd89b347dad3db5ce63d3405a8d60c163af5. --- src/client/gameui.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client/gameui.cpp b/src/client/gameui.cpp index c216f405d..81c268e44 100644 --- a/src/client/gameui.cpp +++ b/src/client/gameui.cpp @@ -133,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)) << "\xC2\xB0 " + << ") | yaw: " << (wrapDegrees_0_360(cam.camera_yaw)) << "° " << yawToDirectionString(cam.camera_yaw) - << " | pitch: " << (-wrapDegrees_180(cam.camera_pitch)) << "\xC2\xB0" + << " | pitch: " << (-wrapDegrees_180(cam.camera_pitch)) << "°" << " | seed: " << ((u64)client->getMapSeed()); if (pointed_old.type == POINTEDTHING_NODE) { From e5725dfb8e476a5a6f63f020a23a53ca3ef610e9 Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Wed, 29 Jul 2020 23:20:01 +0200 Subject: [PATCH 010/266] Allow starting local server using --go again (#10229) --- src/client/clientlauncher.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/client/clientlauncher.cpp b/src/client/clientlauncher.cpp index ce16797e6..29427f609 100644 --- a/src/client/clientlauncher.cpp +++ b/src/client/clientlauncher.cpp @@ -327,13 +327,13 @@ void ClientLauncher::init_args(GameStartData &start_data, const Settings &cmd_ar // Join a remote server start_data.address = cmd_args.get("address"); start_data.world_path.clear(); + start_data.name = g_settings->get("name"); } if (!start_data.world_path.empty()) { // Start a singleplayer instance start_data.address = ""; } - start_data.name = g_settings->get("name"); if (cmd_args.exists("name")) start_data.name = cmd_args.get("name"); @@ -419,7 +419,6 @@ bool ClientLauncher::launch_game(std::string &error_message, /* Show the GUI menu */ std::string server_name, server_description; - start_data.local_server = false; if (!skip_main_menu) { // Initialize menu data // TODO: Re-use existing structs (GameStartData) @@ -467,6 +466,9 @@ bool ClientLauncher::launch_game(std::string &error_message, start_data.local_server = !menudata.simple_singleplayer_mode && start_data.address.empty(); + } else { + start_data.local_server = !start_data.world_path.empty() && + start_data.address.empty() && !start_data.name.empty(); } if (!RenderingEngine::run()) From 9bba52c4000a06043f5100dbb0ef66d869707ffc Mon Sep 17 00:00:00 2001 From: sfan5 Date: Thu, 30 Jul 2020 17:39:57 +0200 Subject: [PATCH 011/266] content_cao: Support texture animation for upright_sprite (#10020) --- doc/lua_api.txt | 18 ++++++------- games/devtest/mods/testentities/visuals.lua | 16 ++++++++++-- src/client/content_cao.cpp | 28 ++++++++++++++++++--- src/client/mesh.cpp | 9 +++++++ src/client/mesh.h | 7 ++++++ 5 files changed, 64 insertions(+), 14 deletions(-) diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 2d22dc899..8ac3ad7f2 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -6077,15 +6077,15 @@ object you are working with still exists. * `get_yaw()`: returns number in radians * `set_texture_mod(mod)` * `get_texture_mod()` returns current texture modifier -* `set_sprite(p, num_frames, framelength, select_horiz_by_yawpitch)` - * Select sprite from spritesheet with optional animation and Dungeon Master - style texture selection based on yaw relative to camera - * `p`: {x=number, y=number}, the coordinate of the first frame - (x: column, y: row), default: `{x=0, y=0}` - * `num_frames`: number, default: `1` - * `framelength`: number, default: `0.2` - * `select_horiz_by_yawpitch`: boolean, this was once used for the Dungeon - Master mob, default: `false` +* `set_sprite(p, num_frames, framelength, select_x_by_camera)` + * Specifies and starts a sprite animation + * Animations iterate along the frame `y` position. + * `p`: {x=column number, y=row number}, the coordinate of the first frame + default: `{x=0, y=0}` + * `num_frames`: Total frames in the texture, default: `1` + * `framelength`: Time per animated frame in seconds, default: `0.2` + * `select_x_by_camera`: Only for visual = `sprite`. Changes the frame `x` + position according to the view direction. default: `false`. * `get_entity_name()` (**Deprecated**: Will be removed in a future version) * `get_luaentity()` diff --git a/games/devtest/mods/testentities/visuals.lua b/games/devtest/mods/testentities/visuals.lua index 83f361f16..8848ba49f 100644 --- a/games/devtest/mods/testentities/visuals.lua +++ b/games/devtest/mods/testentities/visuals.lua @@ -68,7 +68,7 @@ minetest.register_entity("testentities:mesh_unshaded", { -- Advanced visual tests --- A test entity for testing animated and yaw-modulated sprites +-- An entity for testing animated and yaw-modulated sprites minetest.register_entity("testentities:yawsprite", { initial_properties = { selectionbox = {-0.3, -0.5, -0.3, 0.3, 0.3, 0.3}, @@ -79,6 +79,18 @@ minetest.register_entity("testentities:yawsprite", { initial_sprite_basepos = {x=0, y=0}, }, on_activate = function(self, staticdata) - self.object:set_sprite({x=0, y=0}, 1, 0, true) + self.object:set_sprite({x=0, y=0}, 3, 0.5, true) + end, +}) + +-- An entity for testing animated upright sprites +minetest.register_entity("testentities:upright_animated", { + initial_properties = { + visual = "upright_sprite", + textures = {"testnodes_anim.png"}, + spritediv = {x = 1, y = 4}, + }, + on_activate = function(self) + self.object:set_sprite({x=0, y=0}, 4, 1.0, false) end, }) diff --git a/src/client/content_cao.cpp b/src/client/content_cao.cpp index 4f949f6b0..88688d18c 100644 --- a/src/client/content_cao.cpp +++ b/src/client/content_cao.cpp @@ -1176,6 +1176,7 @@ void GenericCAO::updateTexturePos() int row = m_tx_basepos.Y; int col = m_tx_basepos.X; + // Yawpitch goes rightwards if (m_tx_select_horiz_by_yawpitch) { if (cam_to_entity.Y > 0.75) col += 5; @@ -1206,6 +1207,27 @@ void GenericCAO::updateTexturePos() float tys = m_tx_size.Y; setBillboardTextureMatrix(m_spritenode, txs, tys, col, row); } + + else if (m_meshnode) { + if (m_prop.visual == "upright_sprite") { + int row = m_tx_basepos.Y; + int col = m_tx_basepos.X; + + // Animation goes downwards + row += m_anim_frame; + + const auto &tx = m_tx_size; + v2f t[4] = { // cf. vertices in GenericCAO::addToScene() + tx * v2f(col+1, row+1), + tx * v2f(col, row+1), + tx * v2f(col, row), + tx * v2f(col+1, row), + }; + auto mesh = m_meshnode->getMesh(); + setMeshBufferTextureCoords(mesh->getMeshBuffer(0), t, 4); + setMeshBufferTextureCoords(mesh->getMeshBuffer(1), t, 4); + } + } } // Do not pass by reference, see header. @@ -1247,7 +1269,7 @@ void GenericCAO::updateTextures(std::string mod) } } - if (m_animated_meshnode) { + else if (m_animated_meshnode) { if (m_prop.visual == "mesh") { for (u32 i = 0; i < m_prop.textures.size() && i < m_animated_meshnode->getMaterialCount(); ++i) { @@ -1296,8 +1318,8 @@ void GenericCAO::updateTextures(std::string mod) } } } - if(m_meshnode) - { + + else if (m_meshnode) { if(m_prop.visual == "cube") { for (u32 i = 0; i < 6; ++i) diff --git a/src/client/mesh.cpp b/src/client/mesh.cpp index e1ec22068..2400a374c 100644 --- a/src/client/mesh.cpp +++ b/src/client/mesh.cpp @@ -203,6 +203,15 @@ void setMeshColor(scene::IMesh *mesh, const video::SColor &color) setMeshBufferColor(mesh->getMeshBuffer(j), color); } +void setMeshBufferTextureCoords(scene::IMeshBuffer *buf, const v2f *uv, u32 count) +{ + const u32 stride = getVertexPitchFromType(buf->getVertexType()); + assert(buf->getVertexCount() >= count); + u8 *vertices = (u8 *) buf->getVertices(); + for (u32 i = 0; i < count; i++) + ((video::S3DVertex*) (vertices + i * stride))->TCoords = uv[i]; +} + template static void applyToMesh(scene::IMesh *mesh, const F &fn) { diff --git a/src/client/mesh.h b/src/client/mesh.h index 103c61e45..dbc091a06 100644 --- a/src/client/mesh.h +++ b/src/client/mesh.h @@ -58,6 +58,13 @@ void setMeshBufferColor(scene::IMeshBuffer *buf, const video::SColor &color); */ void setMeshColor(scene::IMesh *mesh, const video::SColor &color); + +/* + Sets texture coords for vertices in the mesh buffer. + `uv[]` must have `count` elements +*/ +void setMeshBufferTextureCoords(scene::IMeshBuffer *buf, const v2f *uv, u32 count); + /* Set a constant color for an animated mesh */ From aba8c3753162320c7cc8a66913ad82f4f1fd0d8b Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Thu, 30 Jul 2020 19:03:48 +0200 Subject: [PATCH 012/266] Falling: Fix error caused by missing param2 Falling nodes that were spawned prior the recent falling node changes did not require param2. Default to param2 = 0 when none is found in the node data. --- builtin/game/falling.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/builtin/game/falling.lua b/builtin/game/falling.lua index 714506a5f..4bfcca9e7 100644 --- a/builtin/game/falling.lua +++ b/builtin/game/falling.lua @@ -52,6 +52,7 @@ core.register_entity(":__builtin:falling_node", { floats = false, set_node = function(self, node, meta) + node.param2 = node.param2 or 0 self.node = node meta = meta or {} if type(meta.to_table) == "function" then From 542df11bed89ebad786220f1162597353ecc277d Mon Sep 17 00:00:00 2001 From: Paul Ouellette Date: Sat, 1 Aug 2020 11:25:33 -0400 Subject: [PATCH 013/266] Fix GCC class-memaccess warnings (#10239) --- src/client/mapblock_mesh.cpp | 7 +++++++ src/noise.cpp | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/client/mapblock_mesh.cpp b/src/client/mapblock_mesh.cpp index 1020e35f5..2f96ca61f 100644 --- a/src/client/mapblock_mesh.cpp +++ b/src/client/mapblock_mesh.cpp @@ -419,7 +419,14 @@ static void getNodeVertexDirs(const v3s16 &dir, v3s16 *vertex_dirs) u8 idx = (dir.X + 2 * dir.Y + 3 * dir.Z) & 7; idx = (idx - 1) * 4; +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wclass-memaccess" +#endif memcpy(vertex_dirs, &vertex_dirs_table[idx], 4 * sizeof(v3s16)); +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic pop +#endif } static void getNodeTextureCoords(v3f base, const v3f &scale, const v3s16 &dir, float *u, float *v) diff --git a/src/noise.cpp b/src/noise.cpp index 5a1d989cb..e16564b05 100644 --- a/src/noise.cpp +++ b/src/noise.cpp @@ -424,7 +424,7 @@ float NoisePerlin3D(NoiseParams *np, float x, float y, float z, s32 seed) Noise::Noise(NoiseParams *np_, s32 seed, u32 sx, u32 sy, u32 sz) { - memcpy(&np, np_, sizeof(np)); + np = *np_; this->seed = seed; this->sx = sx; this->sy = sy; From d22fd6fc348ecf393f535c9b172410f4a82a2d52 Mon Sep 17 00:00:00 2001 From: Seeker Date: Mon, 3 Aug 2020 14:38:45 -0700 Subject: [PATCH 014/266] Fix build for Visual Studio (explicitly cast pointers) (#10256) --- src/util/md32_common.h | 2 +- src/util/sha256.c | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/util/md32_common.h b/src/util/md32_common.h index a4c2099c9..2c050b72a 100644 --- a/src/util/md32_common.h +++ b/src/util/md32_common.h @@ -301,7 +301,7 @@ int HASH_UPDATE(HASH_CTX *c, const void *data_, size_t len) { - const unsigned char *data = data_; + const unsigned char *data = (const unsigned char *)data_; unsigned char *p; HASH_LONG l; size_t n; diff --git a/src/util/sha256.c b/src/util/sha256.c index 4241f31f3..5c8266f9a 100644 --- a/src/util/sha256.c +++ b/src/util/sha256.c @@ -18,13 +18,13 @@ const char SHA256_version[] = "SHA-256" OPENSSL_VERSION_PTEXT; unsigned static char cleanse_ctr = 0; static void OPENSSL_cleanse(void *ptr, size_t len) { - unsigned char *p = ptr; + unsigned char *p = (unsigned char *)ptr; size_t loop = len, ctr = cleanse_ctr; while (loop--) { *(p++) = (unsigned char)ctr; ctr += (17 + ((size_t)p & 0xF)); } - p = memchr(ptr, (unsigned char)ctr, len); + p = (unsigned char *)memchr(ptr, (unsigned char)ctr, len); if (p) ctr += (63 + (size_t)p); cleanse_ctr = (unsigned char)ctr; @@ -262,7 +262,7 @@ static void sha256_block_data_order(SHA256_CTX *ctx, const void *in, unsigned MD32_REG_T a, b, c, d, e, f, g, h, s0, s1, T1; SHA_LONG X[16]; int i; - const unsigned char *data = in; + const unsigned char *data = (const unsigned char *)in; const union { long one; char little; From 93ecc589bc49a80187705f6e06df23a71263d3d7 Mon Sep 17 00:00:00 2001 From: Hugues Ross Date: Tue, 4 Aug 2020 14:12:47 -0400 Subject: [PATCH 015/266] Implement override.txt support for special tiles (#10140) Add override targets for all special_tiles entries in node definitions, allowing texture packs to replace these textures. This makes overrides work properly with a variety of drawtypes. The targets are named special1 through special6, covering the the current length of the special_tiles array. --- doc/texture_packs.txt | 18 +++++++++++++++++- src/nodedef.cpp | 21 +++++++++++++++++++++ src/nodedef.h | 5 +++++ src/texture_override.cpp | 38 ++++++++++++++++++++++++++------------ src/texture_override.h | 18 +++++++++++++++--- util/ci/lint.sh | 0 6 files changed, 84 insertions(+), 16 deletions(-) mode change 100644 => 100755 util/ci/lint.sh diff --git a/doc/texture_packs.txt b/doc/texture_packs.txt index 79d65a339..8af2cbad6 100644 --- a/doc/texture_packs.txt +++ b/doc/texture_packs.txt @@ -195,11 +195,27 @@ Here are targets you can choose from: | bottom | y- face | | sides | x-, x+, z-, z+ faces | | all | All faces. You can also use '*' instead of 'all'. | +| special1 | The first entry in the special_tiles list | +| special2 | The second entry in the special_tiles list | +| special3 | The third entry in the special_tiles list | +| special4 | The fourth entry in the special_tiles list | +| special5 | The fifth entry in the special_tiles list | +| special6 | The sixth entry in the special_tiles list | | inventory | The inventory texture | | wield | The texture used when held by the player | Nodes support all targets, but other items only support 'inventory' -and 'wield' +and 'wield'. + +### Using the special targets + +The special* targets only apply to specific drawtypes: + +* `flowingliquid`: special1 sets the top texture, special2 sets the side texture +* `allfaces_optional`: special1 is used by simple mode, see below +* `glasslike_framed`: When containing a liquid, special1 sets the liquid texture +* `glasslike_framed_optional`: Same as `glasslike_framed` +* `plantlike_rooted`: special1 sets the plant's texture Designing leaves textures for the leaves rendering options ---------------------------------------------------------- diff --git a/src/nodedef.cpp b/src/nodedef.cpp index e5cd2c2a7..82c4581fa 100644 --- a/src/nodedef.cpp +++ b/src/nodedef.cpp @@ -1337,6 +1337,7 @@ void NodeDefManager::applyTextureOverrides(const std::vector &o ContentFeatures &nodedef = m_content_features[id]; + // Override tiles if (texture_override.hasTarget(OverrideTarget::TOP)) nodedef.tiledef[0].name = texture_override.texture; @@ -1354,6 +1355,26 @@ void NodeDefManager::applyTextureOverrides(const std::vector &o if (texture_override.hasTarget(OverrideTarget::FRONT)) nodedef.tiledef[5].name = texture_override.texture; + + + // Override special tiles, if applicable + if (texture_override.hasTarget(OverrideTarget::SPECIAL_1)) + nodedef.tiledef_special[0].name = texture_override.texture; + + if (texture_override.hasTarget(OverrideTarget::SPECIAL_2)) + nodedef.tiledef_special[1].name = texture_override.texture; + + if (texture_override.hasTarget(OverrideTarget::SPECIAL_3)) + nodedef.tiledef_special[2].name = texture_override.texture; + + if (texture_override.hasTarget(OverrideTarget::SPECIAL_4)) + nodedef.tiledef_special[3].name = texture_override.texture; + + if (texture_override.hasTarget(OverrideTarget::SPECIAL_5)) + nodedef.tiledef_special[4].name = texture_override.texture; + + if (texture_override.hasTarget(OverrideTarget::SPECIAL_6)) + nodedef.tiledef_special[5].name = texture_override.texture; } } diff --git a/src/nodedef.h b/src/nodedef.h index ff52f976f..d0da367ee 100644 --- a/src/nodedef.h +++ b/src/nodedef.h @@ -261,6 +261,11 @@ struct TileDef NodeDrawType drawtype); }; +// Defines the number of special tiles per nodedef +// +// NOTE: When changing this value, the enum entries of OverrideTarget and +// parser in TextureOverrideSource must be updated so that all special +// tiles can be overridden. #define CF_SPECIAL_COUNT 6 struct ContentFeatures diff --git a/src/texture_override.cpp b/src/texture_override.cpp index 10d129b87..effdb0efd 100644 --- a/src/texture_override.cpp +++ b/src/texture_override.cpp @@ -24,6 +24,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include +#define override_cast static_cast + TextureOverrideSource::TextureOverrideSource(std::string filepath) { std::ifstream infile(filepath.c_str()); @@ -56,25 +58,37 @@ TextureOverrideSource::TextureOverrideSource(std::string filepath) std::vector targets = str_split(splitted[1], ','); for (const std::string &target : targets) { if (target == "top") - texture_override.target |= static_cast(OverrideTarget::TOP); + texture_override.target |= override_cast(OverrideTarget::TOP); else if (target == "bottom") - texture_override.target |= static_cast(OverrideTarget::BOTTOM); + texture_override.target |= override_cast(OverrideTarget::BOTTOM); else if (target == "left") - texture_override.target |= static_cast(OverrideTarget::LEFT); + texture_override.target |= override_cast(OverrideTarget::LEFT); else if (target == "right") - texture_override.target |= static_cast(OverrideTarget::RIGHT); + texture_override.target |= override_cast(OverrideTarget::RIGHT); else if (target == "front") - texture_override.target |= static_cast(OverrideTarget::FRONT); + texture_override.target |= override_cast(OverrideTarget::FRONT); else if (target == "back") - texture_override.target |= static_cast(OverrideTarget::BACK); + texture_override.target |= override_cast(OverrideTarget::BACK); else if (target == "inventory") - texture_override.target |= static_cast(OverrideTarget::INVENTORY); + texture_override.target |= override_cast(OverrideTarget::INVENTORY); else if (target == "wield") - texture_override.target |= static_cast(OverrideTarget::WIELD); + texture_override.target |= override_cast(OverrideTarget::WIELD); + else if (target == "special1") + texture_override.target |= override_cast(OverrideTarget::SPECIAL_1); + else if (target == "special2") + texture_override.target |= override_cast(OverrideTarget::SPECIAL_2); + else if (target == "special3") + texture_override.target |= override_cast(OverrideTarget::SPECIAL_3); + else if (target == "special4") + texture_override.target |= override_cast(OverrideTarget::SPECIAL_4); + else if (target == "special5") + texture_override.target |= override_cast(OverrideTarget::SPECIAL_5); + else if (target == "special6") + texture_override.target |= override_cast(OverrideTarget::SPECIAL_6); else if (target == "sides") - texture_override.target |= static_cast(OverrideTarget::SIDES); + texture_override.target |= override_cast(OverrideTarget::SIDES); else if (target == "all" || target == "*") - texture_override.target |= static_cast(OverrideTarget::ALL_FACES); + texture_override.target |= override_cast(OverrideTarget::ALL_FACES); else { // Report invalid target warningstream << filepath << ":" << line_index @@ -85,7 +99,7 @@ TextureOverrideSource::TextureOverrideSource(std::string filepath) } // If there are no valid targets, skip adding this override - if (texture_override.target == static_cast(OverrideTarget::INVALID)) { + if (texture_override.target == override_cast(OverrideTarget::INVALID)) { continue; } @@ -112,7 +126,7 @@ std::vector TextureOverrideSource::getNodeTileOverrides() std::vector found_overrides; for (const TextureOverride &texture_override : m_overrides) { - if (texture_override.hasTarget(OverrideTarget::ALL_FACES)) + if (texture_override.hasTarget(OverrideTarget::NODE_TARGETS)) found_overrides.push_back(texture_override); } diff --git a/src/texture_override.h b/src/texture_override.h index db03bd014..bdc95e732 100644 --- a/src/texture_override.h +++ b/src/texture_override.h @@ -23,8 +23,10 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include +typedef u16 override_t; + //! Bitmask enum specifying what a texture override should apply to -enum class OverrideTarget : u8 +enum class OverrideTarget : override_t { INVALID = 0, TOP = 1 << 0, @@ -35,23 +37,33 @@ enum class OverrideTarget : u8 BACK = 1 << 5, INVENTORY = 1 << 6, WIELD = 1 << 7, + SPECIAL_1 = 1 << 8, + SPECIAL_2 = 1 << 9, + SPECIAL_3 = 1 << 10, + SPECIAL_4 = 1 << 11, + SPECIAL_5 = 1 << 12, + SPECIAL_6 = 1 << 13, + // clang-format off SIDES = LEFT | RIGHT | FRONT | BACK, ALL_FACES = TOP | BOTTOM | SIDES, + ALL_SPECIAL = SPECIAL_1 | SPECIAL_2 | SPECIAL_3 | SPECIAL_4 | SPECIAL_5 | SPECIAL_6, + NODE_TARGETS = ALL_FACES | ALL_SPECIAL, ITEM_TARGETS = INVENTORY | WIELD, + // clang-format on }; struct TextureOverride { std::string id; std::string texture; - u8 target; + override_t target; // Helper function for checking if an OverrideTarget is found in // a TextureOverride without casting inline bool hasTarget(OverrideTarget overrideTarget) const { - return (target & static_cast(overrideTarget)) != 0; + return (target & static_cast(overrideTarget)) != 0; } }; diff --git a/util/ci/lint.sh b/util/ci/lint.sh old mode 100644 new mode 100755 From f92a393f6f949377b1d78630a8f7d24e78e6ff9d Mon Sep 17 00:00:00 2001 From: Paramat Date: Wed, 5 Aug 2020 05:00:00 +0100 Subject: [PATCH 016/266] Mapgen Flat: Add caverns, disabled by default (#9913) Add the caverns used in V5, V7, Valleys, Carpathian. Disabled by default to not be force-enabled in existing worlds. --- builtin/mainmenu/dlg_create_world.lua | 1 + builtin/settingtypes.txt | 14 +++++- src/mapgen/mapgen_flat.cpp | 66 +++++++++++++++++++-------- src/mapgen/mapgen_flat.h | 24 ++++++---- 4 files changed, 77 insertions(+), 28 deletions(-) diff --git a/builtin/mainmenu/dlg_create_world.lua b/builtin/mainmenu/dlg_create_world.lua index 36df23cce..b2e706b6b 100644 --- a/builtin/mainmenu/dlg_create_world.lua +++ b/builtin/mainmenu/dlg_create_world.lua @@ -61,6 +61,7 @@ local flag_checkboxes = { fgettext("Low humidity and high heat causes shallow or dry rivers") }, }, flat = { + cb_caverns, { "hills", fgettext("Hills"), "hills" }, { "lakes", fgettext("Lakes"), "lakes" }, }, diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index c0620542d..01736f586 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -1860,7 +1860,7 @@ mgcarpathian_np_dungeons (Dungeon noise) noise_params_3d 0.9, 0.5, (500, 500, 50 # Map generation attributes specific to Mapgen Flat. # Occasional lakes and hills can be added to the flat world. -mgflat_spflags (Mapgen Flat specific flags) flags nolakes,nohills lakes,hills,nolakes,nohills +mgflat_spflags (Mapgen Flat specific flags) flags nolakes,nohills,nocaverns lakes,hills,caverns,nolakes,nohills,nocaverns # Y of flat ground. mgflat_ground_level (Ground level) int 8 @@ -1904,6 +1904,15 @@ mgflat_hill_threshold (Hill threshold) float 0.45 # Controls steepness/height of hills. mgflat_hill_steepness (Hill steepness) float 64.0 +# Y-level of cavern upper limit. +mgflat_cavern_limit (Cavern limit) int -256 + +# Y-distance over which caverns expand to full size. +mgflat_cavern_taper (Cavern taper) int 256 + +# Defines full size of caverns, smaller values create larger caverns. +mgflat_cavern_threshold (Cavern threshold) float 0.7 + # Lower Y limit of dungeons. mgflat_dungeon_ymin (Dungeon minimum Y) int -31000 @@ -1924,6 +1933,9 @@ mgflat_np_cave1 (Cave1 noise) noise_params_3d 0, 12, (61, 61, 61), 52534, 3, 0.5 # Second of two 3D noises that together define tunnels. mgflat_np_cave2 (Cave2 noise) noise_params_3d 0, 12, (67, 67, 67), 10325, 3, 0.5, 2.0 +# 3D noise defining giant caverns. +mgflat_np_cavern (Cavern noise) noise_params_3d 0, 1, (384, 128, 384), 723, 5, 0.63, 2.0 + # 3D noise that determines number of dungeons per mapchunk. mgflat_np_dungeons (Dungeon noise) noise_params_3d 0.9, 0.5, (500, 500, 500), 0, 2, 0.8, 2.0 diff --git a/src/mapgen/mapgen_flat.cpp b/src/mapgen/mapgen_flat.cpp index 369777ad2..df6469ad9 100644 --- a/src/mapgen/mapgen_flat.cpp +++ b/src/mapgen/mapgen_flat.cpp @@ -1,7 +1,7 @@ /* Minetest -Copyright (C) 2015-2018 paramat -Copyright (C) 2015-2018 kwolekr, Ryan Kwolek +Copyright (C) 2015-2020 paramat +Copyright (C) 2015-2016 kwolekr, Ryan Kwolek 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 @@ -39,8 +39,9 @@ with this program; if not, write to the Free Software Foundation, Inc., FlagDesc flagdesc_mapgen_flat[] = { - {"lakes", MGFLAT_LAKES}, - {"hills", MGFLAT_HILLS}, + {"lakes", MGFLAT_LAKES}, + {"hills", MGFLAT_HILLS}, + {"caverns", MGFLAT_CAVERNS}, {NULL, 0} }; @@ -52,17 +53,21 @@ MapgenFlat::MapgenFlat(MapgenFlatParams *params, EmergeParams *emerge) { spflags = params->spflags; ground_level = params->ground_level; - large_cave_depth = params->large_cave_depth; - small_cave_num_min = params->small_cave_num_min; - small_cave_num_max = params->small_cave_num_max; - large_cave_num_min = params->large_cave_num_min; - large_cave_num_max = params->large_cave_num_max; - large_cave_flooded = params->large_cave_flooded; - cave_width = params->cave_width; lake_threshold = params->lake_threshold; lake_steepness = params->lake_steepness; hill_threshold = params->hill_threshold; hill_steepness = params->hill_steepness; + + cave_width = params->cave_width; + small_cave_num_min = params->small_cave_num_min; + small_cave_num_max = params->small_cave_num_max; + large_cave_num_min = params->large_cave_num_min; + large_cave_num_max = params->large_cave_num_max; + large_cave_depth = params->large_cave_depth; + large_cave_flooded = params->large_cave_flooded; + cavern_limit = params->cavern_limit; + cavern_taper = params->cavern_taper; + cavern_threshold = params->cavern_threshold; dungeon_ymin = params->dungeon_ymin; dungeon_ymax = params->dungeon_ymax; @@ -71,9 +76,11 @@ MapgenFlat::MapgenFlat(MapgenFlatParams *params, EmergeParams *emerge) if ((spflags & MGFLAT_LAKES) || (spflags & MGFLAT_HILLS)) noise_terrain = new Noise(¶ms->np_terrain, seed, csize.X, csize.Z); + // 3D noise MapgenBasic::np_cave1 = params->np_cave1; MapgenBasic::np_cave2 = params->np_cave2; + MapgenBasic::np_cavern = params->np_cavern; MapgenBasic::np_dungeons = params->np_dungeons; } @@ -88,11 +95,12 @@ MapgenFlat::~MapgenFlat() MapgenFlatParams::MapgenFlatParams(): - np_terrain (0, 1, v3f(600, 600, 600), 7244, 5, 0.6, 2.0), - np_filler_depth (0, 1.2, v3f(150, 150, 150), 261, 3, 0.7, 2.0), - np_cave1 (0, 12, v3f(61, 61, 61), 52534, 3, 0.5, 2.0), - np_cave2 (0, 12, v3f(67, 67, 67), 10325, 3, 0.5, 2.0), - np_dungeons (0.9, 0.5, v3f(500, 500, 500), 0, 2, 0.8, 2.0) + np_terrain (0, 1, v3f(600, 600, 600), 7244, 5, 0.6, 2.0), + np_filler_depth (0, 1.2, v3f(150, 150, 150), 261, 3, 0.7, 2.0), + np_cavern (0.0, 1.0, v3f(384, 128, 384), 723, 5, 0.63, 2.0), + np_cave1 (0, 12, v3f(61, 61, 61), 52534, 3, 0.5, 2.0), + np_cave2 (0, 12, v3f(67, 67, 67), 10325, 3, 0.5, 2.0), + np_dungeons (0.9, 0.5, v3f(500, 500, 500), 0, 2, 0.8, 2.0) { } @@ -112,11 +120,15 @@ void MapgenFlatParams::readParams(const Settings *settings) settings->getFloatNoEx("mgflat_lake_steepness", lake_steepness); settings->getFloatNoEx("mgflat_hill_threshold", hill_threshold); settings->getFloatNoEx("mgflat_hill_steepness", hill_steepness); + settings->getS16NoEx("mgflat_cavern_limit", cavern_limit); + settings->getS16NoEx("mgflat_cavern_taper", cavern_taper); + settings->getFloatNoEx("mgflat_cavern_threshold", cavern_threshold); settings->getS16NoEx("mgflat_dungeon_ymin", dungeon_ymin); settings->getS16NoEx("mgflat_dungeon_ymax", dungeon_ymax); settings->getNoiseParams("mgflat_np_terrain", np_terrain); settings->getNoiseParams("mgflat_np_filler_depth", np_filler_depth); + settings->getNoiseParams("mgflat_np_cavern", np_cavern); settings->getNoiseParams("mgflat_np_cave1", np_cave1); settings->getNoiseParams("mgflat_np_cave2", np_cave2); settings->getNoiseParams("mgflat_np_dungeons", np_dungeons); @@ -138,11 +150,15 @@ void MapgenFlatParams::writeParams(Settings *settings) const settings->setFloat("mgflat_lake_steepness", lake_steepness); settings->setFloat("mgflat_hill_threshold", hill_threshold); settings->setFloat("mgflat_hill_steepness", hill_steepness); + settings->setS16("mgflat_cavern_limit", cavern_limit); + settings->setS16("mgflat_cavern_taper", cavern_taper); + settings->setFloat("mgflat_cavern_threshold", cavern_threshold); settings->setS16("mgflat_dungeon_ymin", dungeon_ymin); settings->setS16("mgflat_dungeon_ymax", dungeon_ymax); settings->setNoiseParams("mgflat_np_terrain", np_terrain); settings->setNoiseParams("mgflat_np_filler_depth", np_filler_depth); + settings->setNoiseParams("mgflat_np_cavern", np_cavern); settings->setNoiseParams("mgflat_np_cave1", np_cave1); settings->setNoiseParams("mgflat_np_cave2", np_cave2); settings->setNoiseParams("mgflat_np_dungeons", np_dungeons); @@ -226,11 +242,25 @@ void MapgenFlat::makeChunk(BlockMakeData *data) generateBiomes(); } + // Generate tunnels, caverns and large randomwalk caves if (flags & MG_CAVES) { - // Generate tunnels + // Generate tunnels first as caverns confuse them generateCavesNoiseIntersection(stone_surface_max_y); + + // Generate caverns + bool near_cavern = false; + if (spflags & MGFLAT_CAVERNS) + near_cavern = generateCavernsNoise(stone_surface_max_y); + // Generate large randomwalk caves - generateCavesRandomWalk(stone_surface_max_y, large_cave_depth); + if (near_cavern) + // Disable large randomwalk caves in this mapchunk by setting + // 'large cave depth' to world base. Avoids excessive liquid in + // large caverns and floating blobs of overgenerated liquid. + generateCavesRandomWalk(stone_surface_max_y, + -MAX_MAP_GENERATION_LIMIT); + else + generateCavesRandomWalk(stone_surface_max_y, large_cave_depth); } // Generate the registered ores diff --git a/src/mapgen/mapgen_flat.h b/src/mapgen/mapgen_flat.h index 4902a802c..4b46aff27 100644 --- a/src/mapgen/mapgen_flat.h +++ b/src/mapgen/mapgen_flat.h @@ -1,7 +1,7 @@ /* Minetest -Copyright (C) 2015-2018 paramat -Copyright (C) 2015-2018 kwolekr, Ryan Kwolek +Copyright (C) 2015-2020 paramat +Copyright (C) 2015-2016 kwolekr, Ryan Kwolek 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 @@ -25,6 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc., /////// Mapgen Flat flags #define MGFLAT_LAKES 0x01 #define MGFLAT_HILLS 0x02 +#define MGFLAT_CAVERNS 0x04 class BiomeManager; @@ -33,22 +34,27 @@ extern FlagDesc flagdesc_mapgen_flat[]; struct MapgenFlatParams : public MapgenParams { s16 ground_level = 8; - s16 large_cave_depth = -33; - u16 small_cave_num_min = 0; - u16 small_cave_num_max = 0; - u16 large_cave_num_min = 0; - u16 large_cave_num_max = 2; - float large_cave_flooded = 0.5f; - float cave_width = 0.09f; float lake_threshold = -0.45f; float lake_steepness = 48.0f; float hill_threshold = 0.45f; float hill_steepness = 64.0f; + + float cave_width = 0.09f; + u16 small_cave_num_min = 0; + u16 small_cave_num_max = 0; + u16 large_cave_num_min = 0; + u16 large_cave_num_max = 2; + s16 large_cave_depth = -33; + float large_cave_flooded = 0.5f; + s16 cavern_limit = -256; + s16 cavern_taper = 256; + float cavern_threshold = 0.7f; s16 dungeon_ymin = -31000; s16 dungeon_ymax = 31000; NoiseParams np_terrain; NoiseParams np_filler_depth; + NoiseParams np_cavern; NoiseParams np_cave1; NoiseParams np_cave2; NoiseParams np_dungeons; From abfea69e5f68c0f2c946bfcd4444f8cb32e781cf Mon Sep 17 00:00:00 2001 From: Emojigit <55009343+Emojigit@users.noreply.github.com> Date: Fri, 7 Aug 2020 01:30:41 +0800 Subject: [PATCH 017/266] Change `last-login` command to show player name in output (#10263) --- builtin/game/chat.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/builtin/game/chat.lua b/builtin/game/chat.lua index aae811794..8131a6860 100644 --- a/builtin/game/chat.lua +++ b/builtin/game/chat.lua @@ -1070,10 +1070,10 @@ core.register_chatcommand("last-login", { local pauth = core.get_auth_handler().get_auth(param) if pauth and pauth.last_login and pauth.last_login ~= -1 then -- Time in UTC, ISO 8601 format - return true, "Last login time was " .. + return true, param.."'s last login time was " .. os.date("!%Y-%m-%dT%H:%M:%SZ", pauth.last_login) end - return false, "Last login time is unknown" + return false, param.."'s last login time is unknown" end, }) From cd0e213a3640e980e15735f97dd874754f0dc679 Mon Sep 17 00:00:00 2001 From: v-rob Date: Tue, 11 Aug 2020 11:07:17 -0700 Subject: [PATCH 018/266] Add font styling options to tables and textlists (#10203) --- src/gui/guiFormSpecMenu.cpp | 2 ++ src/gui/guiTable.cpp | 27 ++++++++++++++++++++++++++- src/gui/guiTable.h | 6 ++++++ 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/gui/guiFormSpecMenu.cpp b/src/gui/guiFormSpecMenu.cpp index 601c5c18e..98392f3c0 100644 --- a/src/gui/guiFormSpecMenu.cpp +++ b/src/gui/guiFormSpecMenu.cpp @@ -1225,6 +1225,7 @@ void GUIFormSpecMenu::parseTable(parserData* data, const std::string &element) auto style = getDefaultStyleForElement("table", name); e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); + e->setOverrideFont(style.getFont()); m_tables.emplace_back(spec, e); m_fields.push_back(spec); @@ -1302,6 +1303,7 @@ void GUIFormSpecMenu::parseTextList(parserData* data, const std::string &element auto style = getDefaultStyleForElement("textlist", name); e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); + e->setOverrideFont(style.getFont()); m_tables.emplace_back(spec, e); m_fields.push_back(spec); diff --git a/src/gui/guiTable.cpp b/src/gui/guiTable.cpp index c705e17fb..cab2e19fd 100644 --- a/src/gui/guiTable.cpp +++ b/src/gui/guiTable.cpp @@ -56,7 +56,7 @@ GUITable::GUITable(gui::IGUIEnvironment *env, m_font = skin->getFont(); if (m_font) { m_font->grab(); - m_rowheight = m_font->getDimension(L"A").Height + 4; + m_rowheight = m_font->getDimension(L"Ay").Height + 4; m_rowheight = MYMAX(m_rowheight, 1); } @@ -586,6 +586,31 @@ void GUITable::setSelected(s32 index) } } +void GUITable::setOverrideFont(IGUIFont *font) +{ + if (m_font == font) + return; + + if (font == nullptr) + font = Environment->getSkin()->getFont(); + + if (m_font) + m_font->drop(); + + m_font = font; + m_font->grab(); + + m_rowheight = m_font->getDimension(L"Ay").Height + 4; + m_rowheight = MYMAX(m_rowheight, 1); + + updateScrollBar(); +} + +IGUIFont *GUITable::getOverrideFont() const +{ + return m_font; +} + GUITable::DynamicData GUITable::getDynamicData() const { DynamicData dyndata; diff --git a/src/gui/guiTable.h b/src/gui/guiTable.h index 11093ea72..76a0e94d0 100644 --- a/src/gui/guiTable.h +++ b/src/gui/guiTable.h @@ -123,6 +123,12 @@ public: // Autoscroll to make the selected row fully visible void setSelected(s32 index); + //! Sets another skin independent font. If this is set to zero, the button uses the font of the skin. + virtual void setOverrideFont(gui::IGUIFont *font = nullptr); + + //! Gets the override font (if any) + virtual gui::IGUIFont *getOverrideFont() const; + /* Get selection, scroll position and opened (sub)trees */ DynamicData getDynamicData() const; From 1c38027c3a72402d752a8150701a44753e22990e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20M=C3=BCller?= <34514239+appgurueu@users.noreply.github.com> Date: Wed, 12 Aug 2020 11:51:50 +0200 Subject: [PATCH 019/266] Fix precision not working in hud_change (#10186) --- games/devtest/mods/util_commands/init.lua | 56 +++++++++++++++++++++++ src/hud.cpp | 1 + src/script/common/c_content.cpp | 5 +- 3 files changed, 61 insertions(+), 1 deletion(-) diff --git a/games/devtest/mods/util_commands/init.lua b/games/devtest/mods/util_commands/init.lua index 3a0e91a41..f2a155fb2 100644 --- a/games/devtest/mods/util_commands/init.lua +++ b/games/devtest/mods/util_commands/init.lua @@ -112,6 +112,62 @@ minetest.register_chatcommand("detach", { end, }) +-- Use this to test waypoint capabilities +minetest.register_chatcommand("test_waypoints", { + params = "[change_immediate]", + description = "tests waypoint capabilities", + func = function(name, params) + local player = minetest.get_player_by_name(name) + local regular = player:hud_add { + hud_elem_type = "waypoint", + name = "regular waypoint", + text = "m", + number = 0xFF0000, + world_pos = vector.add(player:get_pos(), {x = 0, y = 1.5, z = 0}) + } + local reduced_precision = player:hud_add { + hud_elem_type = "waypoint", + name = "better waypoint", + text = "m (0.5 steps, precision = 2)", + precision = 10, + number = 0xFFFF00, + world_pos = vector.add(player:get_pos(), {x = 0, y = 1, z = 0}) + } + local function change() + if regular then + player:hud_change(regular, "world_pos", vector.add(player:get_pos(), {x = 0, y = 3, z = 0})) + end + if reduced_precision then + player:hud_change(reduced_precision, "precision", 2) + end + end + if params ~= "" then + -- change immediate + change() + else + minetest.after(0.5, change) + end + regular = regular or "error" + reduced_precision = reduced_precision or "error" + local hidden_distance = player:hud_add { + hud_elem_type = "waypoint", + name = "waypoint with hidden distance", + text = "this text is hidden as well (precision = 0)", + precision = 0, + number = 0x0000FF, + world_pos = vector.add(player:get_pos(), {x = 0, y = 0.5, z = 0}) + } or "error" + local image_waypoint = player:hud_add { + hud_elem_type = "image_waypoint", + text = "wieldhand.png", + world_pos = player:get_pos(), + scale = {x = 10, y = 10}, + offset = {x = 0, y = -32} + } or "error" + minetest.chat_send_player(name, "Waypoint IDs: regular: " .. regular .. ", reduced precision: " .. reduced_precision .. + ", hidden distance: " .. hidden_distance .. ", image waypoint: " .. image_waypoint) + end +}) -- Unlimited node placement minetest.register_on_placenode(function(pos, newnode, placer, oldnode, itemstack) diff --git a/src/hud.cpp b/src/hud.cpp index 3079b5cd8..4830c56a4 100644 --- a/src/hud.cpp +++ b/src/hud.cpp @@ -40,6 +40,7 @@ const struct EnumString es_HudElementStat[] = {HUD_STAT_TEXT, "text"}, {HUD_STAT_NUMBER, "number"}, {HUD_STAT_ITEM, "item"}, + {HUD_STAT_ITEM, "precision"}, {HUD_STAT_DIR, "direction"}, {HUD_STAT_ALIGN, "alignment"}, {HUD_STAT_OFFSET, "offset"}, diff --git a/src/script/common/c_content.cpp b/src/script/common/c_content.cpp index 3dfd7ce61..774b6a326 100644 --- a/src/script/common/c_content.cpp +++ b/src/script/common/c_content.cpp @@ -1959,9 +1959,10 @@ void push_hud_element(lua_State *L, HudElement *elem) HudElementStat read_hud_change(lua_State *L, HudElement *elem, void **value) { HudElementStat stat = HUD_STAT_NUMBER; + std::string statstr; if (lua_isstring(L, 3)) { int statint; - std::string statstr = lua_tostring(L, 3); + statstr = lua_tostring(L, 3); stat = string_to_enum(es_HudElementStat, statint, statstr) ? (HudElementStat)statint : stat; } @@ -1989,6 +1990,8 @@ HudElementStat read_hud_change(lua_State *L, HudElement *elem, void **value) break; case HUD_STAT_ITEM: elem->item = luaL_checknumber(L, 4); + if (elem->type == HUD_ELEM_WAYPOINT && statstr == "precision") + elem->item++; *value = &elem->item; break; case HUD_STAT_DIR: From fff03931871b68e092e12bfce9056f760e8ec9dd Mon Sep 17 00:00:00 2001 From: sfan5 Date: Wed, 12 Aug 2020 11:52:50 +0200 Subject: [PATCH 020/266] Render nodeboxes with opaque material if possible (#10122) --- doc/lua_api.txt | 2 ++ src/nodedef.cpp | 57 ++++++++++++++++++++++++++++++++++++++++++++++--- src/nodedef.h | 12 +++++++++++ 3 files changed, 68 insertions(+), 3 deletions(-) diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 8ac3ad7f2..6366a34c3 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -7065,6 +7065,8 @@ Used by `minetest.register_node`. use_texture_alpha = false, -- Use texture's alpha channel + -- If this is set to false, the node will be rendered fully opaque + -- regardless of any texture transparency. palette = "palette.png", -- The node's `param2` is used to select a pixel from the image. diff --git a/src/nodedef.cpp b/src/nodedef.cpp index 82c4581fa..392f5eb98 100644 --- a/src/nodedef.cpp +++ b/src/nodedef.cpp @@ -695,9 +695,54 @@ static void fillTileAttribs(ITextureSource *tsrc, TileLayer *layer, } } } -#endif -#ifndef SERVER +bool ContentFeatures::textureAlphaCheck(ITextureSource *tsrc, const TileDef *tiles, int length) +{ + video::IVideoDriver *driver = RenderingEngine::get_video_driver(); + static thread_local bool long_warning_printed = false; + std::set seen; + for (int i = 0; i < length; i++) { + if (seen.find(tiles[i].name) != seen.end()) + continue; + seen.insert(tiles[i].name); + + // Load the texture and see if there's any transparent pixels + video::ITexture *texture = tsrc->getTexture(tiles[i].name); + video::IImage *image = driver->createImage(texture, + core::position2d(0, 0), texture->getOriginalSize()); + if (!image) + continue; + core::dimension2d dim = image->getDimension(); + bool ok = true; + for (u16 x = 0; x < dim.Width; x++) { + for (u16 y = 0; y < dim.Height; y++) { + if (image->getPixel(x, y).getAlpha() < 255) { + ok = false; + goto break_loop; + } + } + } + +break_loop: + image->drop(); + if (!ok) { + warningstream << "Texture \"" << tiles[i].name << "\" of " + << name << " has transparent pixels, assuming " + "use_texture_alpha = true." << std::endl; + if (!long_warning_printed) { + warningstream << " This warning can be a false-positive if " + "unused pixels in the texture are transparent. However if " + "it is meant to be transparent, you *MUST* update the " + "nodedef and set use_texture_alpha = true! This compatibility " + "code will be removed in a few releases." << std::endl; + long_warning_printed = true; + } + return true; + } + } + return false; +} + bool isWorldAligned(AlignStyle style, WorldAlignMode mode, NodeDrawType drawtype) { if (style == ALIGN_STYLE_WORLD) @@ -814,13 +859,19 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc break; case NDT_MESH: case NDT_NODEBOX: + if (alpha == 255 && textureAlphaCheck(tsrc, tdef, 6)) + alpha = 0; + solidness = 0; if (waving == 1) material_type = TILE_MATERIAL_WAVING_PLANTS; else if (waving == 2) material_type = TILE_MATERIAL_WAVING_LEAVES; else if (waving == 3) - material_type = TILE_MATERIAL_WAVING_LIQUID_BASIC; + material_type = (alpha == 255) ? TILE_MATERIAL_WAVING_LIQUID_OPAQUE : + TILE_MATERIAL_WAVING_LIQUID_BASIC; + else if (alpha == 255) + material_type = TILE_MATERIAL_OPAQUE; break; case NDT_TORCHLIKE: case NDT_SIGNLIKE: diff --git a/src/nodedef.h b/src/nodedef.h index d0da367ee..71c56bda9 100644 --- a/src/nodedef.h +++ b/src/nodedef.h @@ -418,6 +418,7 @@ struct ContentFeatures void reset(); void serialize(std::ostream &os, u16 protocol_version) const; void deSerialize(std::istream &is); + /*! * Since vertex alpha is no longer supported, this method * adds opacity directly to the texture pixels. @@ -427,6 +428,17 @@ struct ContentFeatures */ void correctAlpha(TileDef *tiles, int length); +#ifndef SERVER + /* + * Checks if any tile texture has any transparent pixels. + * Prints a warning and returns true if that is the case, false otherwise. + * This is supposed to be used for use_texture_alpha backwards compatibility. + */ + bool textureAlphaCheck(ITextureSource *tsrc, const TileDef *tiles, + int length); +#endif + + /* Some handy methods */ From 291a6b70d674d9003f522b5875a60f7e2753e32b Mon Sep 17 00:00:00 2001 From: ANAND Date: Fri, 5 Jun 2020 18:36:35 +0530 Subject: [PATCH 021/266] Allow binding dig, place actions to keys; remove LMB/RMB hardcoding Co-authored-by: Sam Caulfield --- builtin/settingtypes.txt | 14 ++- doc/client_lua_api.txt | 4 +- doc/lua_api.txt | 24 ++-- src/client/client.cpp | 2 +- src/client/content_cao.cpp | 12 +- src/client/game.cpp | 162 +++++++++++++------------- src/client/inputhandler.cpp | 166 +++++++++++++-------------- src/client/inputhandler.h | 156 +++++++++---------------- src/client/keys.h | 2 + src/defaultsettings.cpp | 4 +- src/network/serverpackethandler.cpp | 23 ++-- src/player.h | 12 +- src/script/lua_api/l_localplayer.cpp | 4 +- src/script/lua_api/l_object.cpp | 9 +- 14 files changed, 284 insertions(+), 310 deletions(-) diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 01736f586..3aa113190 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -110,9 +110,9 @@ doubletap_jump (Double tap jump for fly) bool false # enabled. always_fly_fast (Always fly and fast) bool true -# The time in seconds it takes between repeated right clicks when holding the right -# mouse button. -repeat_rightclick_time (Rightclick repetition interval) float 0.25 0.001 +# The time in seconds it takes between repeated node placements when holding +# the place button. +repeat_place_time (Place repetition interval) float 0.25 0.001 # Automatically jump up single-node obstacles. autojump (Automatic jumping) bool false @@ -182,6 +182,14 @@ keymap_jump (Jump key) key KEY_SPACE # See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3 keymap_sneak (Sneak key) key KEY_LSHIFT +# Key for digging. +# See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3 +keymap_dig (Dig key) key KEY_LBUTTON + +# Key for placing. +# See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3 +keymap_place (Place key) key KEY_RBUTTON + # Key for opening the inventory. # See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3 keymap_inventory (Inventory key) key KEY_KEY_I diff --git a/doc/client_lua_api.txt b/doc/client_lua_api.txt index 3b0046b4f..4c5231b79 100644 --- a/doc/client_lua_api.txt +++ b/doc/client_lua_api.txt @@ -1100,8 +1100,8 @@ Methods: aux1 = boolean, sneak = boolean, zoom = boolean, - LMB = boolean, - RMB = boolean, + dig = boolean, + place = boolean, } ``` diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 6366a34c3..88d99fcd5 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -6163,15 +6163,23 @@ object you are working with still exists. * Only affects formspecs shown after this is called. * `get_formspec_prepend(formspec)`: returns a formspec string. * `get_player_control()`: returns table with player pressed keys - * The table consists of fields with boolean value representing the pressed - keys, the fields are jump, right, left, LMB, RMB, sneak, aux1, down, up, zoom. - * example: `{jump=false, right=true, left=false, LMB=false, RMB=false, - sneak=true, aux1=false, down=false, up=false, zoom=false}` - * The `zoom` field is available since 5.3 + * The table consists of fields with the following boolean values + representing the pressed keys: `up`, `down`, `left`, `right`, `jump`, + `aux1`, `sneak`, `dig`, `place`, `LMB`, `RMB`, and `zoom`. + * The fields `LMB` and `RMB` are equal to `dig` and `place` respectively, + and exist only to preserve backwards compatibility. * `get_player_control_bits()`: returns integer with bit packed player pressed - keys. - * bit nr/meaning: 0/up, 1/down, 2/left, 3/right, 4/jump, 5/aux1, 6/sneak, - 7/LMB, 8/RMB, 9/zoom (zoom available since 5.3) + keys. Bits: + * 0 - up + * 1 - down + * 2 - left + * 3 - right + * 4 - jump + * 5 - aux1 + * 6 - sneak + * 7 - dig + * 8 - place + * 9 - zoom * `set_physics_override(override_table)` * `override_table` is a table with the following fields: * `speed`: multiplier to default walking speed value (default: `1`) diff --git a/src/client/client.cpp b/src/client/client.cpp index 65e5b3d8c..745cce900 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -1307,7 +1307,7 @@ void Client::sendPlayerPos() player->last_pitch == player->getPitch() && player->last_yaw == player->getYaw() && player->last_keyPressed == player->keyPressed && - player->last_camera_fov == camera_fov && + player->last_camera_fov == camera_fov && player->last_wanted_range == wanted_range) return; diff --git a/src/client/content_cao.cpp b/src/client/content_cao.cpp index 88688d18c..599139aa3 100644 --- a/src/client/content_cao.cpp +++ b/src/client/content_cao.cpp @@ -975,13 +975,13 @@ void GenericCAO::step(float dtime, ClientEnvironment *env) if (controls.sneak && walking) new_speed /= 2; - if (walking && (controls.LMB || controls.RMB)) { + if (walking && (controls.dig || controls.place)) { new_anim = player->local_animations[3]; player->last_animation = WD_ANIM; - } else if(walking) { + } else if (walking) { new_anim = player->local_animations[1]; player->last_animation = WALK_ANIM; - } else if(controls.LMB || controls.RMB) { + } else if (controls.dig || controls.place) { new_anim = player->local_animations[2]; player->last_animation = DIG_ANIM; } @@ -1004,9 +1004,9 @@ void GenericCAO::step(float dtime, ClientEnvironment *env) // Update local player animations if ((player->last_animation != old_anim || - m_animation_speed != old_anim_speed) && - player->last_animation != NO_ANIM && allow_update) - updateAnimation(); + m_animation_speed != old_anim_speed) && + player->last_animation != NO_ANIM && allow_update) + updateAnimation(); } } diff --git a/src/client/game.cpp b/src/client/game.cpp index 20d2c6f90..0d3a0ca15 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -604,7 +604,6 @@ public: #endif /**************************************************************************** - ****************************************************************************/ const float object_hit_delay = 0.2; @@ -625,15 +624,15 @@ struct GameRunData { u16 new_playeritem; PointedThing pointed_old; bool digging; - bool ldown_for_dig; + bool punching; + bool btn_down_for_dig; bool dig_instantly; bool digging_blocked; - bool left_punch; bool reset_jump_timer; float nodig_delay_timer; float dig_time; float dig_time_complete; - float repeat_rightclick_timer; + float repeat_place_timer; float object_hit_delay_timer; float time_from_last_punch; ClientActiveObject *selected_object; @@ -787,6 +786,14 @@ protected: { return input->wasKeyDown(k); } + inline bool wasKeyPressed(GameKeyType k) + { + return input->wasKeyPressed(k); + } + inline bool wasKeyReleased(GameKeyType k) + { + return input->wasKeyReleased(k); + } #ifdef __ANDROID__ void handleAndroidChatInput(); @@ -900,7 +907,7 @@ private: bool m_cache_enable_free_move; f32 m_cache_mouse_sensitivity; f32 m_cache_joystick_frustum_sensitivity; - f32 m_repeat_right_click_time; + f32 m_repeat_place_time; f32 m_cache_cam_smoothing; f32 m_cache_fog_start; @@ -934,7 +941,7 @@ Game::Game() : &settingChangedCallback, this); g_settings->registerChangedCallback("joystick_frustum_sensitivity", &settingChangedCallback, this); - g_settings->registerChangedCallback("repeat_rightclick_time", + g_settings->registerChangedCallback("repeat_place_time", &settingChangedCallback, this); g_settings->registerChangedCallback("noclip", &settingChangedCallback, this); @@ -992,7 +999,7 @@ Game::~Game() &settingChangedCallback, this); g_settings->deregisterChangedCallback("mouse_sensitivity", &settingChangedCallback, this); - g_settings->deregisterChangedCallback("repeat_rightclick_time", + g_settings->deregisterChangedCallback("repeat_place_time", &settingChangedCallback, this); g_settings->deregisterChangedCallback("noclip", &settingChangedCallback, this); @@ -2465,8 +2472,8 @@ void Game::updatePlayerControl(const CameraOrientation &cam) isKeyDown(KeyType::SPECIAL1), isKeyDown(KeyType::SNEAK), isKeyDown(KeyType::ZOOM), - input->getLeftState(), - input->getRightState(), + isKeyDown(KeyType::DIG), + isKeyDown(KeyType::PLACE), cam.camera_pitch, cam.camera_yaw, input->joystick.getAxisWithoutDead(JA_SIDEWARD_MOVE), @@ -2481,8 +2488,8 @@ void Game::updatePlayerControl(const CameraOrientation &cam) ( (u32)(isKeyDown(KeyType::JUMP) & 0x1) << 4) | ( (u32)(isKeyDown(KeyType::SPECIAL1) & 0x1) << 5) | ( (u32)(isKeyDown(KeyType::SNEAK) & 0x1) << 6) | - ( (u32)(input->getLeftState() & 0x1) << 7) | - ( (u32)(input->getRightState() & 0x1) << 8) | + ( (u32)(isKeyDown(KeyType::DIG) & 0x1) << 7) | + ( (u32)(isKeyDown(KeyType::PLACE) & 0x1) << 8) | ( (u32)(isKeyDown(KeyType::ZOOM) & 0x1) << 9) ); @@ -3064,7 +3071,7 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug) PointedThing pointed = updatePointedThing(shootline, selected_def.liquids_pointable, - !runData.ldown_for_dig, + !runData.btn_down_for_dig, camera_offset); if (pointed != runData.pointed_old) { @@ -3072,20 +3079,18 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug) hud->updateSelectionMesh(camera_offset); } - if (runData.digging_blocked && !input->getLeftState()) { - // allow digging again if button is not pressed + // Allow digging again if button is not pressed + if (runData.digging_blocked && !isKeyDown(KeyType::DIG)) runData.digging_blocked = false; - } /* Stop digging when - - releasing left mouse button + - releasing dig button - pointing away from node */ if (runData.digging) { - if (input->getLeftReleased()) { - infostream << "Left button released" - << " (stopped digging)" << std::endl; + if (wasKeyReleased(KeyType::DIG)) { + infostream << "Dig button released (stopped digging)" << std::endl; runData.digging = false; } else if (pointed != runData.pointed_old) { if (pointed.type == POINTEDTHING_NODE @@ -3095,8 +3100,7 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug) // Still pointing to the same node, but a different face. // Don't reset. } else { - infostream << "Pointing away from node" - << " (stopped digging)" << std::endl; + infostream << "Pointing away from node (stopped digging)" << std::endl; runData.digging = false; hud->updateSelectionMesh(camera_offset); } @@ -3107,55 +3111,57 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug) client->setCrack(-1, v3s16(0, 0, 0)); runData.dig_time = 0.0; } - } else if (runData.dig_instantly && input->getLeftReleased()) { - // Remove e.g. torches faster when clicking instead of holding LMB + } else if (runData.dig_instantly && wasKeyReleased(KeyType::DIG)) { + // Remove e.g. torches faster when clicking instead of holding dig button runData.nodig_delay_timer = 0; runData.dig_instantly = false; } - if (!runData.digging && runData.ldown_for_dig && !input->getLeftState()) { - runData.ldown_for_dig = false; - } + if (!runData.digging && runData.btn_down_for_dig && !isKeyDown(KeyType::DIG)) + runData.btn_down_for_dig = false; - runData.left_punch = false; + runData.punching = false; soundmaker->m_player_leftpunch_sound.name = ""; // Prepare for repeating, unless we're not supposed to - if (input->getRightState() && !g_settings->getBool("safe_dig_and_place")) - runData.repeat_rightclick_timer += dtime; + if (isKeyDown(KeyType::PLACE) && !g_settings->getBool("safe_dig_and_place")) + runData.repeat_place_timer += dtime; else - runData.repeat_rightclick_timer = 0; + runData.repeat_place_timer = 0; - if (selected_def.usable && input->getLeftState()) { - if (input->getLeftClicked() && (!client->modsLoaded() - || !client->getScript()->on_item_use(selected_item, pointed))) + if (selected_def.usable && isKeyDown(KeyType::DIG)) { + if (wasKeyPressed(KeyType::DIG) && (!client->modsLoaded() || + !client->getScript()->on_item_use(selected_item, pointed))) client->interact(INTERACT_USE, pointed); } 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()) { + } else if (isKeyDown(KeyType::DIG)) { // When button is held down in air, show continuous animation - runData.left_punch = true; + runData.punching = true; // Run callback even though item is not usable - if (input->getLeftClicked() && client->modsLoaded()) + if (wasKeyPressed(KeyType::DIG) && client->modsLoaded()) client->getScript()->on_item_use(selected_item, pointed); - } else if (input->getRightClicked()) { + } else if (wasKeyPressed(KeyType::PLACE)) { handlePointingAtNothing(selected_item); } runData.pointed_old = pointed; - if (runData.left_punch || input->getLeftClicked()) - camera->setDigging(0); // left click animation + if (runData.punching || wasKeyPressed(KeyType::DIG)) + camera->setDigging(0); // dig animation - input->resetLeftClicked(); - input->resetRightClicked(); + input->clearWasKeyPressed(); + input->clearWasKeyReleased(); - input->resetLeftReleased(); - input->resetRightReleased(); + input->joystick.clearWasKeyDown(KeyType::MOUSE_L); + input->joystick.clearWasKeyDown(KeyType::MOUSE_R); + + input->joystick.clearWasKeyReleased(KeyType::MOUSE_L); + input->joystick.clearWasKeyReleased(KeyType::MOUSE_R); } @@ -3255,7 +3261,7 @@ PointedThing Game::updatePointedThing( void Game::handlePointingAtNothing(const ItemStack &playerItem) { - infostream << "Right Clicked in Air" << std::endl; + infostream << "Attempted to place item while pointing at nothing" << std::endl; PointedThing fauxPointed; fauxPointed.type = POINTEDTHING_NOTHING; client->interact(INTERACT_ACTIVATE, fauxPointed); @@ -3274,7 +3280,7 @@ void Game::handlePointingAtNode(const PointedThing &pointed, ClientMap &map = client->getEnv().getClientMap(); - if (runData.nodig_delay_timer <= 0.0 && input->getLeftState() + if (runData.nodig_delay_timer <= 0.0 && isKeyDown(KeyType::DIG) && !runData.digging_blocked && client->checkPrivilege("interact")) { handleDigging(pointed, nodepos, selected_item, hand_item, dtime); @@ -3295,13 +3301,14 @@ void Game::handlePointingAtNode(const PointedThing &pointed, } } - if ((input->getRightClicked() || - runData.repeat_rightclick_timer >= m_repeat_right_click_time) && + if ((wasKeyPressed(KeyType::PLACE) || + runData.repeat_place_timer >= m_repeat_place_time) && client->checkPrivilege("interact")) { - runData.repeat_rightclick_timer = 0; - infostream << "Ground right-clicked" << std::endl; + runData.repeat_place_timer = 0; + infostream << "Place button pressed while looking at ground" << std::endl; - camera->setDigging(1); // right click animation (always shown for feedback) + // Placing animation (always shown for feedback) + camera->setDigging(1); soundmaker->m_player_rightpunch_sound = SimpleSoundSpec(); @@ -3367,8 +3374,7 @@ bool Game::nodePlacement(const ItemDefinition &selected_def, } verbosestream << "Node placement prediction for " - << selected_def.name << " is " - << prediction << std::endl; + << selected_def.name << " is " << prediction << std::endl; v3s16 p = neighbourpos; // Place inside node itself if buildable_to @@ -3529,7 +3535,7 @@ void Game::handlePointingAtObject(const PointedThing &pointed, m_game_ui->setInfoText(infotext); - if (input->getLeftState()) { + if (isKeyDown(KeyType::DIG)) { bool do_punch = false; bool do_punch_damage = false; @@ -3539,12 +3545,12 @@ void Game::handlePointingAtObject(const PointedThing &pointed, runData.object_hit_delay_timer = object_hit_delay; } - if (input->getLeftClicked()) + if (wasKeyPressed(KeyType::DIG)) do_punch = true; if (do_punch) { - infostream << "Left-clicked object" << std::endl; - runData.left_punch = true; + infostream << "Punched object" << std::endl; + runData.punching = true; } if (do_punch_damage) { @@ -3559,8 +3565,8 @@ void Game::handlePointingAtObject(const PointedThing &pointed, if (!disable_send) client->interact(INTERACT_START_DIGGING, pointed); } - } else if (input->getRightClicked()) { - infostream << "Right-clicked object" << std::endl; + } else if (wasKeyDown(KeyType::PLACE)) { + infostream << "Pressed place button while pointing at object" << std::endl; client->interact(INTERACT_PLACE, pointed); // place } } @@ -3606,7 +3612,7 @@ void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos, return; client->interact(INTERACT_START_DIGGING, pointed); runData.digging = true; - runData.ldown_for_dig = true; + runData.btn_down_for_dig = true; } if (!runData.dig_instantly) { @@ -3700,7 +3706,7 @@ void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos, client->setCrack(-1, nodepos); } - camera->setDigging(0); // left click animation + camera->setDigging(0); // Dig animation } @@ -4039,7 +4045,7 @@ void Game::readSettings() m_cache_enable_fog = g_settings->getBool("enable_fog"); m_cache_mouse_sensitivity = g_settings->getFloat("mouse_sensitivity"); m_cache_joystick_frustum_sensitivity = g_settings->getFloat("joystick_frustum_sensitivity"); - m_repeat_right_click_time = g_settings->getFloat("repeat_rightclick_time"); + m_repeat_place_time = g_settings->getFloat("repeat_place_time"); m_cache_enable_noclip = g_settings->getBool("noclip"); m_cache_enable_free_move = g_settings->getBool("free_move"); @@ -4131,30 +4137,32 @@ void Game::showPauseMenu() "- %s: move backwards\n" "- %s: move left\n" "- %s: move right\n" - "- %s: jump/climb\n" - "- %s: sneak/go down\n" + "- %s: jump/climb up\n" + "- %s: dig/punch\n" + "- %s: place/use\n" + "- %s: sneak/climb down\n" "- %s: drop item\n" "- %s: inventory\n" "- Mouse: turn/look\n" - "- Mouse left: dig/punch\n" - "- Mouse right: place/use\n" "- Mouse wheel: select item\n" "- %s: chat\n" ); - char control_text_buf[600]; + char control_text_buf[600]; - porting::mt_snprintf(control_text_buf, sizeof(control_text_buf), control_text_template.c_str(), - GET_KEY_NAME(keymap_forward), - GET_KEY_NAME(keymap_backward), - GET_KEY_NAME(keymap_left), - GET_KEY_NAME(keymap_right), - GET_KEY_NAME(keymap_jump), - GET_KEY_NAME(keymap_sneak), - GET_KEY_NAME(keymap_drop), - GET_KEY_NAME(keymap_inventory), - GET_KEY_NAME(keymap_chat) - ); + porting::mt_snprintf(control_text_buf, sizeof(control_text_buf), control_text_template.c_str(), + GET_KEY_NAME(keymap_forward), + GET_KEY_NAME(keymap_backward), + GET_KEY_NAME(keymap_left), + GET_KEY_NAME(keymap_right), + GET_KEY_NAME(keymap_jump), + GET_KEY_NAME(keymap_dig), + GET_KEY_NAME(keymap_place), + GET_KEY_NAME(keymap_sneak), + GET_KEY_NAME(keymap_drop), + GET_KEY_NAME(keymap_inventory), + GET_KEY_NAME(keymap_chat) + ); std::string control_text = std::string(control_text_buf); str_formspec_escape(control_text); diff --git a/src/client/inputhandler.cpp b/src/client/inputhandler.cpp index a79b04a90..608a405a8 100644 --- a/src/client/inputhandler.cpp +++ b/src/client/inputhandler.cpp @@ -37,6 +37,8 @@ void KeyCache::populate() key[KeyType::JUMP] = getKeySetting("keymap_jump"); key[KeyType::SPECIAL1] = getKeySetting("keymap_special1"); key[KeyType::SNEAK] = getKeySetting("keymap_sneak"); + key[KeyType::DIG] = getKeySetting("keymap_dig"); + key[KeyType::PLACE] = getKeySetting("keymap_place"); key[KeyType::AUTOFORWARD] = getKeySetting("keymap_autoforward"); @@ -111,57 +113,81 @@ bool MyEventReceiver::OnEvent(const SEvent &event) if (event.EventType == irr::EET_KEY_INPUT_EVENT) { const KeyPress &keyCode = event.KeyInput; if (keysListenedFor[keyCode]) { + // If the key is being held down then the OS may + // send a continuous stream of keydown events. + // In this case, we don't want to let this + // stream reach the application as it will cause + // certain actions to repeat constantly. if (event.KeyInput.PressedDown) { + if (!IsKeyDown(keyCode)) { + keyWasDown.set(keyCode); + keyWasPressed.set(keyCode); + } keyIsDown.set(keyCode); - keyWasDown.set(keyCode); } else { + if (IsKeyDown(keyCode)) + keyWasReleased.set(keyCode); + keyIsDown.unset(keyCode); } + return true; } - } #ifdef HAVE_TOUCHSCREENGUI - // case of touchscreengui we have to handle different events - if (m_touchscreengui && event.EventType == irr::EET_TOUCH_INPUT_EVENT) { + } else if (m_touchscreengui && event.EventType == irr::EET_TOUCH_INPUT_EVENT) { + // In case of touchscreengui, we have to handle different events m_touchscreengui->translateEvent(event); return true; - } #endif - if (event.EventType == irr::EET_JOYSTICK_INPUT_EVENT) { + } else if (event.EventType == irr::EET_JOYSTICK_INPUT_EVENT) { /* TODO add a check like: if (event.JoystickEvent != joystick_we_listen_for) return false; */ return joystick->handleEvent(event.JoystickEvent); - } - // handle mouse events - if (event.EventType == irr::EET_MOUSE_INPUT_EVENT) { - if (isMenuActive()) { - left_active = false; - middle_active = false; - right_active = false; - } else { - left_active = event.MouseInput.isLeftPressed(); - middle_active = event.MouseInput.isMiddlePressed(); - right_active = event.MouseInput.isRightPressed(); - - if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) { - leftclicked = true; - } - if (event.MouseInput.Event == EMIE_RMOUSE_PRESSED_DOWN) { - rightclicked = true; - } - if (event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP) { - leftreleased = true; - } - if (event.MouseInput.Event == EMIE_RMOUSE_LEFT_UP) { - rightreleased = true; - } - if (event.MouseInput.Event == EMIE_MOUSE_WHEEL) { - mouse_wheel += event.MouseInput.Wheel; - } + } else if (event.EventType == irr::EET_MOUSE_INPUT_EVENT) { + // Handle mouse events + KeyPress key; + switch (event.MouseInput.Event) { + case EMIE_LMOUSE_PRESSED_DOWN: + key = "KEY_LBUTTON"; + keyIsDown.set(key); + keyWasDown.set(key); + keyWasPressed.set(key); + break; + case EMIE_MMOUSE_PRESSED_DOWN: + key = "KEY_MBUTTON"; + keyIsDown.set(key); + keyWasDown.set(key); + keyWasPressed.set(key); + break; + case EMIE_RMOUSE_PRESSED_DOWN: + key = "KEY_RBUTTON"; + keyIsDown.set(key); + keyWasDown.set(key); + keyWasPressed.set(key); + break; + case EMIE_LMOUSE_LEFT_UP: + key = "KEY_LBUTTON"; + keyIsDown.unset(key); + keyWasReleased.set(key); + break; + case EMIE_MMOUSE_LEFT_UP: + key = "KEY_MBUTTON"; + keyIsDown.unset(key); + keyWasReleased.set(key); + break; + case EMIE_RMOUSE_LEFT_UP: + key = "KEY_RBUTTON"; + keyIsDown.unset(key); + keyWasReleased.set(key); + break; + case EMIE_MOUSE_WHEEL: + mouse_wheel += event.MouseInput.Wheel; + break; + default: break; } } else if (event.EventType == irr::EET_LOG_TEXT_EVENT) { static const LogLevel irr_loglev_conv[] = { @@ -188,38 +214,28 @@ s32 RandomInputHandler::Rand(s32 min, s32 max) return (myrand() % (max - min + 1)) + min; } +struct RandomInputHandlerSimData { + std::string key; + float counter; + int time_max; +}; + void RandomInputHandler::step(float dtime) { - { - static float counter1 = 0; - counter1 -= dtime; - if (counter1 < 0.0) { - counter1 = 0.1 * Rand(1, 40); - keydown.toggle(getKeySetting("keymap_jump")); - } - } - { - static float counter1 = 0; - counter1 -= dtime; - if (counter1 < 0.0) { - counter1 = 0.1 * Rand(1, 40); - keydown.toggle(getKeySetting("keymap_special1")); - } - } - { - static float counter1 = 0; - counter1 -= dtime; - if (counter1 < 0.0) { - counter1 = 0.1 * Rand(1, 40); - keydown.toggle(getKeySetting("keymap_forward")); - } - } - { - static float counter1 = 0; - counter1 -= dtime; - if (counter1 < 0.0) { - counter1 = 0.1 * Rand(1, 40); - keydown.toggle(getKeySetting("keymap_left")); + static RandomInputHandlerSimData rnd_data[] = { + { "keymap_jump", 0.0f, 40 }, + { "keymap_special1", 0.0f, 40 }, + { "keymap_forward", 0.0f, 40 }, + { "keymap_left", 0.0f, 40 }, + { "keymap_dig", 0.0f, 30 }, + { "keymap_place", 0.0f, 15 } + }; + + for (auto &i : rnd_data) { + i.counter -= dtime; + if (i.counter < 0.0) { + i.counter = 0.1 * Rand(1, i.time_max); + keydown.toggle(getKeySetting(i.key.c_str())); } } { @@ -230,29 +246,5 @@ void RandomInputHandler::step(float dtime) mousespeed = v2s32(Rand(-20, 20), Rand(-15, 20)); } } - { - static float counter1 = 0; - counter1 -= dtime; - if (counter1 < 0.0) { - counter1 = 0.1 * Rand(1, 30); - leftdown = !leftdown; - if (leftdown) - leftclicked = true; - if (!leftdown) - leftreleased = true; - } - } - { - static float counter1 = 0; - counter1 -= dtime; - if (counter1 < 0.0) { - counter1 = 0.1 * Rand(1, 15); - rightdown = !rightdown; - if (rightdown) - rightclicked = true; - if (!rightdown) - rightreleased = true; - } - } mousepos += mousespeed; } diff --git a/src/client/inputhandler.h b/src/client/inputhandler.h index fc7998f20..def147a82 100644 --- a/src/client/inputhandler.h +++ b/src/client/inputhandler.h @@ -144,6 +144,14 @@ public: return b; } + // Checks whether a key was just pressed. State will be cleared + // in the subsequent iteration of Game::processPlayerInteraction + bool WasKeyPressed(const KeyPress &keycode) const { return keyWasPressed[keycode]; } + + // Checks whether a key was just released. State will be cleared + // in the subsequent iteration of Game::processPlayerInteraction + bool WasKeyReleased(const KeyPress &keycode) const { return keyWasReleased[keycode]; } + void listenForKey(const KeyPress &keyCode) { keysListenedFor.set(keyCode); } void dontListenForKeys() { keysListenedFor.clear(); } @@ -158,19 +166,22 @@ public: { keyIsDown.clear(); keyWasDown.clear(); - - leftclicked = false; - rightclicked = false; - leftreleased = false; - rightreleased = false; - - left_active = false; - middle_active = false; - right_active = false; + keyWasPressed.clear(); + keyWasReleased.clear(); mouse_wheel = 0; } + void clearWasKeyPressed() + { + keyWasPressed.clear(); + } + + void clearWasKeyReleased() + { + keyWasReleased.clear(); + } + MyEventReceiver() { #ifdef HAVE_TOUCHSCREENGUI @@ -178,15 +189,6 @@ public: #endif } - bool leftclicked = false; - bool rightclicked = false; - bool leftreleased = false; - bool rightreleased = false; - - bool left_active = false; - bool middle_active = false; - bool right_active = false; - s32 mouse_wheel = 0; JoystickController *joystick = nullptr; @@ -198,8 +200,16 @@ public: private: // The current state of keys KeyList keyIsDown; - // Whether a key has been pressed or not + + // Whether a key was down KeyList keyWasDown; + + // Whether a key has just been pressed + KeyList keyWasPressed; + + // Whether a key has just been released + KeyList keyWasReleased; + // List of keys we listen for // TODO perhaps the type of this is not really // performant as KeyList is designed for few but @@ -226,27 +236,19 @@ public: virtual bool isKeyDown(GameKeyType k) = 0; virtual bool wasKeyDown(GameKeyType k) = 0; + virtual bool wasKeyPressed(GameKeyType k) = 0; + virtual bool wasKeyReleased(GameKeyType k) = 0; virtual bool cancelPressed() = 0; + virtual void clearWasKeyPressed() {} + virtual void clearWasKeyReleased() {} + virtual void listenForKey(const KeyPress &keyCode) {} virtual void dontListenForKeys() {} virtual v2s32 getMousePos() = 0; virtual void setMousePos(s32 x, s32 y) = 0; - virtual bool getLeftState() = 0; - virtual bool getRightState() = 0; - - virtual bool getLeftClicked() = 0; - virtual bool getRightClicked() = 0; - virtual void resetLeftClicked() = 0; - virtual void resetRightClicked() = 0; - - virtual bool getLeftReleased() = 0; - virtual bool getRightReleased() = 0; - virtual void resetLeftReleased() = 0; - virtual void resetRightReleased() = 0; - virtual s32 getMouseWheel() = 0; virtual void step(float dtime) {} @@ -275,10 +277,26 @@ public: { return m_receiver->WasKeyDown(keycache.key[k]) || joystick.wasKeyDown(k); } + virtual bool wasKeyPressed(GameKeyType k) + { + return m_receiver->WasKeyPressed(keycache.key[k]) || joystick.wasKeyReleased(k); + } + virtual bool wasKeyReleased(GameKeyType k) + { + return m_receiver->WasKeyReleased(keycache.key[k]) || joystick.wasKeyReleased(k); + } virtual bool cancelPressed() { return wasKeyDown(KeyType::ESC) || m_receiver->WasKeyDown(CancelKey); } + virtual void clearWasKeyPressed() + { + m_receiver->clearWasKeyPressed(); + } + virtual void clearWasKeyReleased() + { + m_receiver->clearWasKeyReleased(); + } virtual void listenForKey(const KeyPress &keyCode) { m_receiver->listenForKey(keyCode); @@ -306,59 +324,6 @@ public: } } - virtual bool getLeftState() - { - return m_receiver->left_active || joystick.isKeyDown(KeyType::MOUSE_L); - } - virtual bool getRightState() - { - return m_receiver->right_active || joystick.isKeyDown(KeyType::MOUSE_R); - } - - virtual bool getLeftClicked() - { - return m_receiver->leftclicked || - joystick.getWasKeyDown(KeyType::MOUSE_L); - } - virtual bool getRightClicked() - { - return m_receiver->rightclicked || - joystick.getWasKeyDown(KeyType::MOUSE_R); - } - - virtual void resetLeftClicked() - { - m_receiver->leftclicked = false; - joystick.clearWasKeyDown(KeyType::MOUSE_L); - } - virtual void resetRightClicked() - { - m_receiver->rightclicked = false; - joystick.clearWasKeyDown(KeyType::MOUSE_R); - } - - virtual bool getLeftReleased() - { - return m_receiver->leftreleased || - joystick.wasKeyReleased(KeyType::MOUSE_L); - } - virtual bool getRightReleased() - { - return m_receiver->rightreleased || - joystick.wasKeyReleased(KeyType::MOUSE_R); - } - - virtual void resetLeftReleased() - { - m_receiver->leftreleased = false; - joystick.clearWasKeyReleased(KeyType::MOUSE_L); - } - virtual void resetRightReleased() - { - m_receiver->rightreleased = false; - joystick.clearWasKeyReleased(KeyType::MOUSE_R); - } - virtual s32 getMouseWheel() { return m_receiver->getMouseWheel(); } void clear() @@ -384,23 +349,12 @@ public: virtual bool isKeyDown(GameKeyType k) { return keydown[keycache.key[k]]; } virtual bool wasKeyDown(GameKeyType k) { return false; } + virtual bool wasKeyPressed(GameKeyType k) { return false; } + virtual bool wasKeyReleased(GameKeyType k) { return false; } virtual bool cancelPressed() { return false; } virtual v2s32 getMousePos() { return mousepos; } virtual void setMousePos(s32 x, s32 y) { mousepos = v2s32(x, y); } - virtual bool getLeftState() { return leftdown; } - virtual bool getRightState() { return rightdown; } - - virtual bool getLeftClicked() { return leftclicked; } - virtual bool getRightClicked() { return rightclicked; } - virtual void resetLeftClicked() { leftclicked = false; } - virtual void resetRightClicked() { rightclicked = false; } - - virtual bool getLeftReleased() { return leftreleased; } - virtual bool getRightReleased() { return rightreleased; } - virtual void resetLeftReleased() { leftreleased = false; } - virtual void resetRightReleased() { rightreleased = false; } - virtual s32 getMouseWheel() { return 0; } virtual void step(float dtime); @@ -411,10 +365,4 @@ private: KeyList keydown; v2s32 mousepos; v2s32 mousespeed; - bool leftdown = false; - bool rightdown = false; - bool leftclicked = false; - bool rightclicked = false; - bool leftreleased = false; - bool rightreleased = false; }; diff --git a/src/client/keys.h b/src/client/keys.h index 50d3d194b..b6ce59b4a 100644 --- a/src/client/keys.h +++ b/src/client/keys.h @@ -35,6 +35,8 @@ public: SPECIAL1, SNEAK, AUTOFORWARD, + DIG, + PLACE, ESC, diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 07bf0ebb8..103f0fb02 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -73,6 +73,8 @@ void set_default_settings(Settings *settings) settings->setDefault("keymap_right", "KEY_KEY_D"); settings->setDefault("keymap_jump", "KEY_SPACE"); settings->setDefault("keymap_sneak", "KEY_LSHIFT"); + settings->setDefault("keymap_dig", "KEY_LBUTTON"); + settings->setDefault("keymap_place", "KEY_RBUTTON"); settings->setDefault("keymap_drop", "KEY_KEY_Q"); settings->setDefault("keymap_zoom", "KEY_KEY_Z"); settings->setDefault("keymap_inventory", "KEY_KEY_I"); @@ -269,7 +271,7 @@ void set_default_settings(Settings *settings) // Input settings->setDefault("invert_mouse", "false"); settings->setDefault("mouse_sensitivity", "0.2"); - settings->setDefault("repeat_rightclick_time", "0.25"); + settings->setDefault("repeat_place_time", "0.25"); settings->setDefault("safe_dig_and_place", "false"); settings->setDefault("random_input", "false"); settings->setDefault("aux1_descends", "false"); diff --git a/src/network/serverpackethandler.cpp b/src/network/serverpackethandler.cpp index b3008bb50..dcbb114bf 100644 --- a/src/network/serverpackethandler.cpp +++ b/src/network/serverpackethandler.cpp @@ -491,17 +491,18 @@ void Server::process_PlayerPos(RemotePlayer *player, PlayerSAO *playersao, playersao->setPlayerYaw(yaw); playersao->setFov(fov); playersao->setWantedRange(wanted_range); + player->keyPressed = keyPressed; - player->control.up = (keyPressed & 1); - player->control.down = (keyPressed & 2); - player->control.left = (keyPressed & 4); - player->control.right = (keyPressed & 8); - player->control.jump = (keyPressed & 16); - player->control.aux1 = (keyPressed & 32); - player->control.sneak = (keyPressed & 64); - player->control.LMB = (keyPressed & 128); - player->control.RMB = (keyPressed & 256); - player->control.zoom = (keyPressed & 512); + player->control.up = (keyPressed & (0x1 << 0)); + player->control.down = (keyPressed & (0x1 << 1)); + player->control.left = (keyPressed & (0x1 << 2)); + player->control.right = (keyPressed & (0x1 << 3)); + player->control.jump = (keyPressed & (0x1 << 4)); + player->control.aux1 = (keyPressed & (0x1 << 5)); + player->control.sneak = (keyPressed & (0x1 << 6)); + player->control.dig = (keyPressed & (0x1 << 7)); + player->control.place = (keyPressed & (0x1 << 8)); + player->control.zoom = (keyPressed & (0x1 << 9)); if (playersao->checkMovementCheat()) { // Call callbacks @@ -1670,7 +1671,7 @@ void Server::handleCommand_SrpBytesM(NetworkPacket* pkt) if (client->chosen_mech != AUTH_MECHANISM_SRP && client->chosen_mech != AUTH_MECHANISM_LEGACY_PASSWORD) { actionstream << "Server: got SRP _M packet, while auth" - << "is going on with mech " << client->chosen_mech << " from " + << "is going on with mech " << client->chosen_mech << " from " << addr_s << " (wantSudo=" << wantSudo << "). Denying." << std::endl; if (wantSudo) { DenySudoAccess(peer_id); diff --git a/src/player.h b/src/player.h index 3bc7762fa..ec068a8b1 100644 --- a/src/player.h +++ b/src/player.h @@ -57,8 +57,8 @@ struct PlayerControl bool a_aux1, bool a_sneak, bool a_zoom, - bool a_LMB, - bool a_RMB, + bool a_dig, + bool a_place, float a_pitch, float a_yaw, float a_sidew_move_joystick_axis, @@ -73,8 +73,8 @@ struct PlayerControl aux1 = a_aux1; sneak = a_sneak; zoom = a_zoom; - LMB = a_LMB; - RMB = a_RMB; + dig = a_dig; + place = a_place; pitch = a_pitch; yaw = a_yaw; sidew_move_joystick_axis = a_sidew_move_joystick_axis; @@ -88,8 +88,8 @@ struct PlayerControl bool aux1 = false; bool sneak = false; bool zoom = false; - bool LMB = false; - bool RMB = false; + bool dig = false; + bool place = false; float pitch = 0.0f; float yaw = 0.0f; float sidew_move_joystick_axis = 0.0f; diff --git a/src/script/lua_api/l_localplayer.cpp b/src/script/lua_api/l_localplayer.cpp index 851ede535..33fa27c8b 100644 --- a/src/script/lua_api/l_localplayer.cpp +++ b/src/script/lua_api/l_localplayer.cpp @@ -231,8 +231,8 @@ int LuaLocalPlayer::l_get_control(lua_State *L) set("aux1", c.aux1); set("sneak", c.sneak); set("zoom", c.zoom); - set("LMB", c.LMB); - set("RMB", c.RMB); + set("dig", c.dig); + set("place", c.place); return 1; } diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index e7394133a..495d8bced 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -1455,9 +1455,14 @@ int ObjectRef::l_get_player_control(lua_State *L) lua_setfield(L, -2, "aux1"); lua_pushboolean(L, control.sneak); lua_setfield(L, -2, "sneak"); - lua_pushboolean(L, control.LMB); + lua_pushboolean(L, control.dig); + lua_setfield(L, -2, "dig"); + lua_pushboolean(L, control.place); + lua_setfield(L, -2, "place"); + // Legacy fields to ensure mod compatibility + lua_pushboolean(L, control.dig); lua_setfield(L, -2, "LMB"); - lua_pushboolean(L, control.RMB); + lua_pushboolean(L, control.place); lua_setfield(L, -2, "RMB"); lua_pushboolean(L, control.zoom); lua_setfield(L, -2, "zoom"); From 7242de1d4bdb4cc98fce723044b2c8c5d9861c8b Mon Sep 17 00:00:00 2001 From: rubenwardy Date: Mon, 17 Aug 2020 19:09:33 +0100 Subject: [PATCH 022/266] ContentDB: Add Update All and download queuing (#9995) --- builtin/mainmenu/dlg_contentstore.lua | 91 ++++++++++++++++++++++++--- builtin/settingtypes.txt | 3 + src/defaultsettings.cpp | 2 + 3 files changed, 86 insertions(+), 10 deletions(-) diff --git a/builtin/mainmenu/dlg_contentstore.lua b/builtin/mainmenu/dlg_contentstore.lua index 37ceeb6c8..6525f6013 100644 --- a/builtin/mainmenu/dlg_contentstore.lua +++ b/builtin/mainmenu/dlg_contentstore.lua @@ -45,6 +45,9 @@ local filter_types_titles = { fgettext("Texture packs"), } +local number_downloading = 0 +local download_queue = {} + local filter_types_type = { nil, "game", @@ -67,12 +70,14 @@ local function download_package(param) end end -local function start_install(calling_dialog, package) +local function start_install(package) local params = { package = package, filename = os.tempfolder() .. "_MODNAME_" .. package.name .. ".zip", } + number_downloading = number_downloading + 1 + local function callback(result) if result.successful then local path, msg = pkgmgr.install(package.type, @@ -121,9 +126,20 @@ local function start_install(calling_dialog, package) end package.downloading = false + + number_downloading = number_downloading - 1 + + local next = download_queue[1] + if next then + table.remove(download_queue, 1) + + start_install(next) + end + ui.update() end + package.queued = false package.downloading = true if not core.handle_async(download_package, params, callback) then @@ -133,6 +149,16 @@ local function start_install(calling_dialog, package) end end +local function queue_download(package) + local max_concurrent_downloads = tonumber(minetest.settings:get("contentdb_max_concurrent_downloads")) + if number_downloading < max_concurrent_downloads then + start_install(package) + else + table.insert(download_queue, package) + package.queued = true + end +end + local function get_file_extension(path) local parts = path:split(".") return parts[#parts] @@ -279,7 +305,7 @@ function store.filter_packages(query) table.insert(keywords, word) end - local function matches_keywords(package, keywords) + local function matches_keywords(package) for k = 1, #keywords do local keyword = keywords[k] @@ -296,7 +322,7 @@ function store.filter_packages(query) store.packages = {} for _, package in pairs(store.packages_full) do - if (query == "" or matches_keywords(package, keywords)) and + if (query == "" or matches_keywords(package)) and (filter_type == 1 or package.type == filter_types_type[filter_type]) then store.packages[#store.packages + 1] = package end @@ -321,11 +347,14 @@ function store.get_formspec(dlgdata) "formspec_version[3]", "size[15.75,9.5]", "position[0.5,0.55]", + + "style[status;border=false]", + "container[0.375,0.375]", - "field[0,0;10.225,0.8;search_string;;", core.formspec_escape(search_string), "]", + "field[0,0;7.225,0.8;search_string;;", core.formspec_escape(search_string), "]", "field_close_on_enter[search_string;false]", - "button[10.225,0;2,0.8;search;", fgettext("Search"), "]", - "dropdown[12.6,0;2.4,0.8;type;", table.concat(filter_types_titles, ","), ";", filter_type, "]", + "button[7.225,0;2,0.8;search;", fgettext("Search"), "]", + "dropdown[9.6,0;2.4,0.8;type;", table.concat(filter_types_titles, ","), ";", filter_type, "]", "container_end[]", -- Page nav buttons @@ -344,6 +373,35 @@ function store.get_formspec(dlgdata) "container_end[]", } + if number_downloading > 0 then + formspec[#formspec + 1] = "button[12.75,0.375;2.625,0.8;status;" + if #download_queue > 0 then + formspec[#formspec + 1] = fgettext("$1 downloading,\n$2 queued", number_downloading, #download_queue) + else + formspec[#formspec + 1] = fgettext("$1 downloading...", number_downloading) + end + formspec[#formspec + 1] = "]" + else + local num_avail_updates = 0 + for i=1, #store.packages_full do + local package = store.packages_full[i] + if package.path and package.installed_release < package.release and + not (package.downloading or package.queued) then + num_avail_updates = num_avail_updates + 1 + end + end + + if num_avail_updates == 0 then + formspec[#formspec + 1] = "button[12.75,0.375;2.625,0.8;status;" + formspec[#formspec + 1] = fgettext("No updates") + formspec[#formspec + 1] = "]" + else + formspec[#formspec + 1] = "button[12.75,0.375;2.625,0.8;update_all;" + formspec[#formspec + 1] = fgettext("Update All [$1]", num_avail_updates) + formspec[#formspec + 1] = "]" + end + end + if #store.packages == 0 then formspec[#formspec + 1] = "label[4,3;" formspec[#formspec + 1] = fgettext("No results") @@ -386,11 +444,13 @@ function store.get_formspec(dlgdata) formspec[#formspec + 1] = ",0.1]" if package.downloading then - formspec[#formspec + 1] = "style[download;border=false]" - - formspec[#formspec + 1] = "button[-3.5,0;2,0.8;download;" + formspec[#formspec + 1] = "button[-3.5,0;2,0.8;status;" formspec[#formspec + 1] = fgettext("Downloading...") formspec[#formspec + 1] = "]" + elseif package.queued then + formspec[#formspec + 1] = "button[-3.5,0;2,0.8;status;" + formspec[#formspec + 1] = fgettext("Queued") + formspec[#formspec + 1] = "]" elseif not package.path then formspec[#formspec + 1] = "button[-3,0;1.5,0.8;install_" formspec[#formspec + 1] = tostring(i) @@ -485,6 +545,17 @@ function store.handle_submit(this, fields) end end + if fields.update_all then + for i=1, #store.packages_full do + local package = store.packages_full[i] + if package.path and package.installed_release < package.release and + not (package.downloading or package.queued) then + queue_download(package) + end + end + return true + end + local start_idx = (cur_page - 1) * num_per_page + 1 assert(start_idx ~= nil) for i=start_idx, math.min(#store.packages, start_idx+num_per_page-1) do @@ -492,7 +563,7 @@ function store.handle_submit(this, fields) assert(package) if fields["install_" .. i] then - start_install(this, package) + queue_download(package) return true end diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 3aa113190..1f71e4cb9 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -2220,3 +2220,6 @@ contentdb_url (ContentDB URL) string https://content.minetest.net # These flags are independent from Minetest versions, # so see a full list at https://content.minetest.net/help/content_flags/ contentdb_flag_blacklist (ContentDB Flag Blacklist) string nonfree, desktop_default + +# Maximum number of concurrent downloads. Downloads exceeding this limit will be queued. +contentdb_max_concurrent_downloads (ContentDB Max Concurrent Downloads) int 3 diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 103f0fb02..8b6de0412 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -330,6 +330,8 @@ void set_default_settings(Settings *settings) // ContentDB settings->setDefault("contentdb_url", "https://content.minetest.net"); + settings->setDefault("contentdb_max_concurrent_downloads", "3"); + #ifdef __ANDROID__ settings->setDefault("contentdb_flag_blacklist", "nonfree, android_default"); #else From 649211bf272df2dc7057347992c2653cf1c3a6fb Mon Sep 17 00:00:00 2001 From: Lars Date: Wed, 12 Aug 2020 16:02:07 -0700 Subject: [PATCH 023/266] Allow the ABM time budget to be configurable. --- builtin/settingtypes.txt | 4 ++++ src/defaultsettings.cpp | 1 + src/environment.cpp | 1 + src/environment.h | 1 + src/serverenvironment.cpp | 4 ++-- 5 files changed, 9 insertions(+), 2 deletions(-) diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 1f71e4cb9..af4dc1def 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -1282,6 +1282,10 @@ active_block_mgmt_interval (Active block management interval) float 2.0 # Length of time between Active Block Modifier (ABM) execution cycles abm_interval (ABM interval) float 1.0 +# The time budget allowed for ABMs to execute on each step +# (as a fraction of the ABM Interval) +abm_time_budget (ABM time budget) float 0.2 0.1 0.9 + # Length of time between NodeTimer execution cycles nodetimer_interval (NodeTimer interval) float 0.2 diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 8b6de0412..87c272630 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -403,6 +403,7 @@ void set_default_settings(Settings *settings) settings->setDefault("dedicated_server_step", "0.09"); settings->setDefault("active_block_mgmt_interval", "2.0"); settings->setDefault("abm_interval", "1.0"); + settings->setDefault("abm_time_budget", "0.2"); settings->setDefault("nodetimer_interval", "0.2"); settings->setDefault("ignore_world_load_errors", "false"); settings->setDefault("remote_media", ""); diff --git a/src/environment.cpp b/src/environment.cpp index 6751f39e4..06f2b8bf9 100644 --- a/src/environment.cpp +++ b/src/environment.cpp @@ -36,6 +36,7 @@ Environment::Environment(IGameDef *gamedef): m_cache_active_block_mgmt_interval = g_settings->getFloat("active_block_mgmt_interval"); m_cache_abm_interval = g_settings->getFloat("abm_interval"); m_cache_nodetimer_interval = g_settings->getFloat("nodetimer_interval"); + m_cache_abm_time_budget = g_settings->getFloat("abm_time_budget"); m_time_of_day = g_settings->getU32("world_start_time"); m_time_of_day_f = (float)m_time_of_day / 24000.0f; diff --git a/src/environment.h b/src/environment.h index 91c33ba15..b4884fdb3 100644 --- a/src/environment.h +++ b/src/environment.h @@ -147,6 +147,7 @@ protected: float m_cache_active_block_mgmt_interval; float m_cache_abm_interval; float m_cache_nodetimer_interval; + float m_cache_abm_time_budget; IGameDef *m_gamedef; diff --git a/src/serverenvironment.cpp b/src/serverenvironment.cpp index ad2ffc9a4..320042e19 100644 --- a/src/serverenvironment.cpp +++ b/src/serverenvironment.cpp @@ -1354,8 +1354,8 @@ void ServerEnvironment::step(float dtime) std::shuffle(output.begin(), output.end(), m_rgen); int i = 0; - // The time budget for ABMs is 20%. - u32 max_time_ms = m_cache_abm_interval * 1000 / 5; + // determine the time budget for ABMs + u32 max_time_ms = m_cache_abm_interval * 1000 * m_cache_abm_time_budget; for (const v3s16 &p : output) { MapBlock *block = m_map->getBlockNoCreateNoEx(p); if (!block) From 5bda36143f9bd332b6ba420d9b91be1713092eae Mon Sep 17 00:00:00 2001 From: hecks <42101236+hecktest@users.noreply.github.com> Date: Wed, 19 Aug 2020 19:26:37 +0200 Subject: [PATCH 024/266] Clean up sound_fade (#10119) Add proper documentation and correct gain reduction calculations. Co-authored-by: hecktest <> --- doc/lua_api.txt | 7 +++-- src/client/sound_openal.cpp | 55 ++++++++++++++++++++----------------- 2 files changed, 35 insertions(+), 27 deletions(-) diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 88d99fcd5..ba55033dd 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -5238,9 +5238,12 @@ Sounds * `minetest.sound_fade(handle, step, gain)` * `handle` is a handle returned by `minetest.sound_play` * `step` determines how fast a sound will fade. - Negative step will lower the sound volume, positive step will increase - the sound volume. + The gain will change by this much per second, + until it reaches the target gain. + Note: Older versions used a signed step. This is deprecated, but old + code will still work. (the client uses abs(step) to correct it) * `gain` the target gain for the fade. + Fading to zero will delete the sound. Timing ------ diff --git a/src/client/sound_openal.cpp b/src/client/sound_openal.cpp index 20a651c1d..c1c916e68 100644 --- a/src/client/sound_openal.cpp +++ b/src/client/sound_openal.cpp @@ -337,14 +337,12 @@ private: }; std::unordered_map m_sounds_fading; - float m_fade_delay; public: OpenALSoundManager(SoundManagerSingleton *smg, OnDemandSoundFetcher *fetcher): m_fetcher(fetcher), m_device(smg->m_device.get()), m_context(smg->m_context.get()), - m_next_id(1), - m_fade_delay(0) + m_next_id(1) { infostream << "Audio: Initialized: OpenAL " << std::endl; } @@ -616,38 +614,45 @@ public: void fadeSound(int soundid, float step, float gain) { - m_sounds_fading[soundid] = FadeState(step, getSoundGain(soundid), gain); + // Ignore the command if step isn't valid. + if (step == 0) + return; + float current_gain = getSoundGain(soundid); + step = gain - current_gain > 0 ? abs(step) : -abs(step); + if (m_sounds_fading.find(soundid) != m_sounds_fading.end()) { + auto current_fade = m_sounds_fading[soundid]; + // Do not replace the fade if it's equivalent. + if (current_fade.target_gain == gain && current_fade.step == step) + return; + m_sounds_fading.erase(soundid); + } + gain = rangelim(gain, 0, 1); + m_sounds_fading[soundid] = FadeState(step, current_gain, gain); } void doFades(float dtime) { - m_fade_delay += dtime; + for (auto i = m_sounds_fading.begin(); i != m_sounds_fading.end();) { + FadeState& fade = i->second; + assert(fade.step != 0); + fade.current_gain += (fade.step * dtime); - if (m_fade_delay < 0.1f) - return; - - float chkGain = 0; - for (auto i = m_sounds_fading.begin(); - i != m_sounds_fading.end();) { - if (i->second.step < 0.f) - chkGain = -(i->second.current_gain); + if (fade.step < 0.f) + fade.current_gain = std::max(fade.current_gain, fade.target_gain); else - chkGain = i->second.current_gain; + fade.current_gain = std::min(fade.current_gain, fade.target_gain); - if (chkGain < i->second.target_gain) { - i->second.current_gain += (i->second.step * m_fade_delay); - i->second.current_gain = rangelim(i->second.current_gain, 0, 1); - - updateSoundGain(i->first, i->second.current_gain); - ++i; - } else { - if (i->second.target_gain <= 0.f) - stopSound(i->first); + if (fade.current_gain <= 0.f) + stopSound(i->first); + else + updateSoundGain(i->first, fade.current_gain); + // The increment must happen during the erase call, or else it'll segfault. + if (fade.current_gain == fade.target_gain) m_sounds_fading.erase(i++); - } + else + i++; } - m_fade_delay = 0; } bool soundExists(int sound) From 471497fa9198695becae26a83097ec80643b4cc5 Mon Sep 17 00:00:00 2001 From: v-rob Date: Wed, 19 Aug 2020 16:13:29 -0700 Subject: [PATCH 025/266] Optimize formspec form size (#10144) --- src/gui/guiFormSpecMenu.cpp | 66 ++++++++++++++++++------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/src/gui/guiFormSpecMenu.cpp b/src/gui/guiFormSpecMenu.cpp index 98392f3c0..f702b40bf 100644 --- a/src/gui/guiFormSpecMenu.cpp +++ b/src/gui/guiFormSpecMenu.cpp @@ -3112,42 +3112,42 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) // and default scaling (1.00). use_imgsize = 0.5555 * screen_dpi * gui_scaling; } else { - // In variable-size mode, we prefer to make the - // inventory image size 1/15 of screen height, - // multiplied by the gui_scaling config parameter. - // If the preferred size won't fit the whole - // form on the screen, either horizontally or - // vertically, then we scale it down to fit. - // (The magic numbers in the computation of what - // fits arise from the scaling factors in the - // following stanza, including the form border, - // help text space, and 0.1 inventory slot spare.) - // However, a minimum size is also set, that - // the image size can't be less than 0.3 inch - // multiplied by gui_scaling, even if this means - // the form doesn't fit the screen. + // Variables for the maximum imgsize that can fit in the screen. + double fitx_imgsize; + double fity_imgsize; + + // Pad the screensize with 5% of the screensize on all sides to ensure + // that even the largest formspecs don't touch the screen borders. + v2f padded_screensize( + mydata.screensize.X * 0.9f, + mydata.screensize.Y * 0.9f + ); + + if (mydata.real_coordinates) { + fitx_imgsize = padded_screensize.X / mydata.invsize.X; + fity_imgsize = padded_screensize.Y / mydata.invsize.Y; + } else { + // The maximum imgsize in the old coordinate system also needs to + // factor in padding and spacing along with 0.1 inventory slot spare + // and help text space, hence the magic numbers. + fitx_imgsize = padded_screensize.X / + ((5.0 / 4.0) * (0.5 + mydata.invsize.X)); + fity_imgsize = padded_screensize.Y / + ((15.0 / 13.0) * (0.85 + mydata.invsize.Y)); + } + #ifdef __ANDROID__ - // For mobile devices these magic numbers are - // different and forms should always use the - // maximum screen space available. - double prefer_imgsize = mydata.screensize.Y / 10 * gui_scaling; - double fitx_imgsize = mydata.screensize.X / - ((12.0 / 8.0) * (0.5 + mydata.invsize.X)); - double fity_imgsize = mydata.screensize.Y / - ((15.0 / 11.0) * (0.85 + mydata.invsize.Y)); - use_imgsize = MYMIN(prefer_imgsize, - MYMIN(fitx_imgsize, fity_imgsize)); + // In Android, the preferred imgsize should be larger to accommodate the + // smaller screensize. + double prefer_imgsize = padded_screensize.Y / 10 * gui_scaling; #else - double prefer_imgsize = mydata.screensize.Y / 15 * gui_scaling; - double fitx_imgsize = mydata.screensize.X / - ((5.0 / 4.0) * (0.5 + mydata.invsize.X)); - double fity_imgsize = mydata.screensize.Y / - ((15.0 / 13.0) * (0.85 * mydata.invsize.Y)); - double screen_dpi = RenderingEngine::getDisplayDensity() * 96; - double min_imgsize = 0.3 * screen_dpi * gui_scaling; - use_imgsize = MYMAX(min_imgsize, MYMIN(prefer_imgsize, - MYMIN(fitx_imgsize, fity_imgsize))); + // Desktop computers have more space, so try to fit 15 coordinates. + double prefer_imgsize = padded_screensize.Y / 15 * gui_scaling; #endif + // Try to use the preferred imgsize, but if that's bigger than the maximum + // size, use the maximum size. + use_imgsize = std::min(prefer_imgsize, + std::min(fitx_imgsize, fity_imgsize)); } // Everything else is scaled in proportion to the From 83d0c360cc648e02650aa4d341dbf5205c1e708f Mon Sep 17 00:00:00 2001 From: v-rob Date: Wed, 19 Aug 2020 18:14:47 -0700 Subject: [PATCH 026/266] Add gradients and borders to FormSpec boxes (#8676) --- doc/lua_api.txt | 43 +++++++++++++------ src/gui/StyleSpec.h | 64 +++++++++++++++++++++++++++ src/gui/guiBox.cpp | 86 +++++++++++++++++++++++++++++++++++-- src/gui/guiBox.h | 11 ++++- src/gui/guiFormSpecMenu.cpp | 65 +++++++++++++++------------- 5 files changed, 220 insertions(+), 49 deletions(-) diff --git a/doc/lua_api.txt b/doc/lua_api.txt index ba55033dd..49fbe0d94 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -2442,6 +2442,8 @@ Elements * Simple colored box * `color` is color specified as a `ColorString`. If the alpha component is left blank, the box will be semitransparent. + If the color is not specified, the box will use the options specified by + its style. If the color is specified, all styling options will be ignored. ### `dropdown[,;;;,, ...,;;]` @@ -2708,21 +2710,23 @@ Setting a property to nothing will reset it to the default value. For example: Some types may inherit styles from parent types. * animated_image, inherits from image +* box * button * button_exit, inherits from button * checkbox -* scrollbar -* table -* textlist * dropdown * field -* pwdfield, inherits from field -* textarea -* label -* vertlabel, inherits from field +* image * image_button * item_image_button +* label +* pwdfield, inherits from field +* scrollbar * tabheader +* table +* textarea +* textlist +* vertlabel, inherits from label ### Valid Properties @@ -2731,7 +2735,18 @@ Some types may inherit styles from parent types. * noclip - boolean, set to true to allow the element to exceed formspec bounds. * box * noclip - boolean, set to true to allow the element to exceed formspec bounds. - * Default to false in formspec_version version 3 or higher + * Defaults to false in formspec_version version 3 or higher + * **Note**: `colors`, `bordercolors`, and `borderwidths` accept multiple input types: + * Single value (e.g. `#FF0`): All corners/borders. + * Two values (e.g. `red,#FFAAFF`): top-left and bottom-right,top-right and bottom-left/ + top and bottom,left and right. + * Four values (e.g. `blue,#A0F,green,#FFFA`): top-left/top and rotates clockwise. + * These work similarly to CSS borders. + * colors - `ColorString`. Sets the color(s) of the box corners. Default `black`. + * bordercolors - `ColorString`. Sets the color(s) of the borders. Default `black`. + * borderwidths - Integer. Sets the width(s) of the borders in pixels. If the width is + negative, the border will extend inside the box, whereas positive extends outside + the box. A width of zero results in no border; this is default. * button, button_exit, image_button, item_image_button * alpha - boolean, whether to draw alpha in bgimg. Default true. * bgcolor - color, sets button tint. @@ -2767,12 +2782,6 @@ Some types may inherit styles from parent types. * textcolor - color, default white. * checkbox * noclip - boolean, set to true to allow the element to exceed formspec bounds. -* scrollbar - * noclip - boolean, set to true to allow the element to exceed formspec bounds. -* table, textlist - * font - Sets font type. See button `font` property for more information. - * font_size - Sets font size. See button `font_size` property for more information. - * noclip - boolean, set to true to allow the element to exceed formspec bounds. * dropdown * noclip - boolean, set to true to allow the element to exceed formspec bounds. * field, pwdfield, textarea @@ -2797,9 +2806,15 @@ Some types may inherit styles from parent types. * fgimg_pressed - image when pressed. Defaults to fgimg when not provided. * This is deprecated, use states instead. * NOTE: The parameters of any given image_button will take precedence over fgimg/fgimg_pressed +* scrollbar + * noclip - boolean, set to true to allow the element to exceed formspec bounds. * tabheader * noclip - boolean, set to true to allow the element to exceed formspec bounds. * textcolor - color. Default white. +* table, textlist + * font - Sets font type. See button `font` property for more information. + * font_size - Sets font size. See button `font_size` property for more information. + * noclip - boolean, set to true to allow the element to exceed formspec bounds. ### Valid States diff --git a/src/gui/StyleSpec.h b/src/gui/StyleSpec.h index 67caf4f7b..36ad51a89 100644 --- a/src/gui/StyleSpec.h +++ b/src/gui/StyleSpec.h @@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/string.h" #include #include +#include #pragma once @@ -50,6 +51,9 @@ public: PADDING, FONT, FONT_SIZE, + COLORS, + BORDERCOLORS, + BORDERWIDTHS, NUM_PROPERTIES, NONE }; @@ -106,6 +110,12 @@ public: return FONT; } else if (name == "font_size") { return FONT_SIZE; + } else if (name == "colors") { + return COLORS; + } else if (name == "bordercolors") { + return BORDERCOLORS; + } else if (name == "borderwidths") { + return BORDERWIDTHS; } else { return NONE; } @@ -187,6 +197,42 @@ public: return color; } + std::array getColorArray(Property prop, + std::array def) const + { + const auto &val = properties[prop]; + if (val.empty()) + return def; + + std::vector strs; + if (!parseArray(val, strs)) + return def; + + for (size_t i = 0; i <= 3; i++) { + video::SColor color; + if (parseColorString(strs[i], color, false, 0xff)) + def[i] = color; + } + + return def; + } + + std::array getIntArray(Property prop, std::array def) const + { + const auto &val = properties[prop]; + if (val.empty()) + return def; + + std::vector strs; + if (!parseArray(val, strs)) + return def; + + for (size_t i = 0; i <= 3; i++) + def[i] = stoi(strs[i]); + + return def; + } + irr::core::rect getRect(Property prop, irr::core::rect def) const { const auto &val = properties[prop]; @@ -334,6 +380,24 @@ public: } private: + bool parseArray(const std::string &value, std::vector &arr) const + { + std::vector strs = split(value, ','); + + if (strs.size() == 1) { + arr = {strs[0], strs[0], strs[0], strs[0]}; + } else if (strs.size() == 2) { + arr = {strs[0], strs[1], strs[0], strs[1]}; + } else if (strs.size() == 4) { + arr = strs; + } else { + warningstream << "Invalid array size (" << strs.size() + << " arguments): \"" << value << "\"" << std::endl; + return false; + } + return true; + } + bool parseRect(const std::string &value, irr::core::rect *parsed_rect) const { irr::core::rect rect; diff --git a/src/gui/guiBox.cpp b/src/gui/guiBox.cpp index 7f329cc32..99a115daf 100644 --- a/src/gui/guiBox.cpp +++ b/src/gui/guiBox.cpp @@ -20,9 +20,14 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "guiBox.h" GUIBox::GUIBox(gui::IGUIEnvironment *env, gui::IGUIElement *parent, s32 id, - const core::rect &rectangle, const video::SColor &color) : + const core::rect &rectangle, + const std::array &colors, + const std::array &bordercolors, + const std::array &borderwidths) : gui::IGUIElement(gui::EGUIET_ELEMENT, env, parent, id, rectangle), - m_color(color) + m_colors(colors), + m_bordercolors(bordercolors), + m_borderwidths(borderwidths) { } @@ -31,8 +36,81 @@ void GUIBox::draw() if (!IsVisible) return; - Environment->getVideoDriver()->draw2DRectangle(m_color, AbsoluteRect, - &AbsoluteClippingRect); + std::array negative_borders = {0, 0, 0, 0}; + std::array positive_borders = {0, 0, 0, 0}; + + for (size_t i = 0; i <= 3; i++) { + if (m_borderwidths[i] > 0) + positive_borders[i] = m_borderwidths[i]; + else + negative_borders[i] = m_borderwidths[i]; + } + + v2s32 upperleft = AbsoluteRect.UpperLeftCorner; + v2s32 lowerright = AbsoluteRect.LowerRightCorner; + + v2s32 topleft_border = { + upperleft.X - positive_borders[3], + upperleft.Y - positive_borders[0] + }; + v2s32 topleft_rect = { + upperleft.X - negative_borders[3], + upperleft.Y - negative_borders[0] + }; + + v2s32 lowerright_border = { + lowerright.X + positive_borders[1], + lowerright.Y + positive_borders[2] + }; + v2s32 lowerright_rect = { + lowerright.X + negative_borders[1], + lowerright.Y + negative_borders[2] + }; + + core::rect main_rect( + topleft_rect.X, + topleft_rect.Y, + lowerright_rect.X, + lowerright_rect.Y + ); + + std::array, 4> border_rects; + + border_rects[0] = core::rect( + topleft_border.X, + topleft_border.Y, + lowerright_border.X, + topleft_rect.Y + ); + + border_rects[1] = core::rect( + lowerright_rect.X, + topleft_rect.Y, + lowerright_border.X, + lowerright_rect.Y + ); + + border_rects[2] = core::rect( + topleft_border.X, + lowerright_rect.Y, + lowerright_border.X, + lowerright_border.Y + ); + + border_rects[3] = core::rect( + topleft_border.X, + topleft_rect.Y, + topleft_rect.X, + lowerright_rect.Y + ); + + video::IVideoDriver *driver = Environment->getVideoDriver(); + + driver->draw2DRectangle(main_rect, m_colors[0], m_colors[1], m_colors[3], + m_colors[2], nullptr); + + for (size_t i = 0; i <= 3; i++) + driver->draw2DRectangle(m_bordercolors[i], border_rects[i], nullptr); IGUIElement::draw(); } diff --git a/src/gui/guiBox.h b/src/gui/guiBox.h index 5306fdf65..ca8f83771 100644 --- a/src/gui/guiBox.h +++ b/src/gui/guiBox.h @@ -19,16 +19,23 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once +#include +#include #include "irrlichttypes_extrabloated.h" class GUIBox : public gui::IGUIElement { public: GUIBox(gui::IGUIEnvironment *env, gui::IGUIElement *parent, s32 id, - const core::rect &rectangle, const video::SColor &color); + const core::rect &rectangle, + const std::array &colors, + const std::array &bordercolors, + const std::array &borderwidths); virtual void draw() override; private: - video::SColor m_color; + std::array m_colors; + std::array m_bordercolors; + std::array m_borderwidths; }; diff --git a/src/gui/guiFormSpecMenu.cpp b/src/gui/guiFormSpecMenu.cpp index f702b40bf..9bd5f27fd 100644 --- a/src/gui/guiFormSpecMenu.cpp +++ b/src/gui/guiFormSpecMenu.cpp @@ -2211,16 +2211,16 @@ void GUIFormSpecMenu::parseItemImageButton(parserData* data, const std::string & void GUIFormSpecMenu::parseBox(parserData* data, const std::string &element) { - std::vector parts = split(element,';'); + std::vector parts = split(element, ';'); if ((parts.size() == 3) || ((parts.size() > 3) && (m_formspec_version > FORMSPEC_API_VERSION))) { - std::vector v_pos = split(parts[0],','); - std::vector v_geom = split(parts[1],','); + std::vector v_pos = split(parts[0], ','); + std::vector v_geom = split(parts[1], ','); - MY_CHECKPOS("box",0); - MY_CHECKGEOM("box",1); + MY_CHECKPOS("box", 0); + MY_CHECKGEOM("box", 1); v2s32 pos; v2s32 geom; @@ -2234,36 +2234,43 @@ void GUIFormSpecMenu::parseBox(parserData* data, const std::string &element) geom.Y = stof(v_geom[1]) * spacing.Y; } + FieldSpec spec( + "", + L"", + L"", + 258 + m_fields.size(), + -2 + ); + spec.ftype = f_Box; + + auto style = getDefaultStyleForElement("box", spec.fname); + video::SColor tmp_color; + std::array colors; + std::array bordercolors = {0x0, 0x0, 0x0, 0x0}; + std::array borderwidths = {0, 0, 0, 0}; - if (parseColorString(parts[2], tmp_color, false, 0x8C)) { - FieldSpec spec( - "", - L"", - L"", - 258 + m_fields.size(), - -2 - ); - spec.ftype = f_Box; - - core::rect rect(pos, pos + geom); - - GUIBox *e = new GUIBox(Environment, data->current_parent, spec.fid, - rect, tmp_color); - - auto style = getDefaultStyleForElement("box", spec.fname); - e->setNotClipped(style.getBool(StyleSpec::NOCLIP, m_formspec_version < 3)); - - e->drop(); - - m_fields.push_back(spec); - + if (parseColorString(parts[2], tmp_color, true, 0x8C)) { + colors = {tmp_color, tmp_color, tmp_color, tmp_color}; } else { - errorstream<< "Invalid Box element(" << parts.size() << "): '" << element << "' INVALID COLOR" << std::endl; + colors = style.getColorArray(StyleSpec::COLORS, {0x0, 0x0, 0x0, 0x0}); + bordercolors = style.getColorArray(StyleSpec::BORDERCOLORS, + {0x0, 0x0, 0x0, 0x0}); + borderwidths = style.getIntArray(StyleSpec::BORDERWIDTHS, {0, 0, 0, 0}); } + + core::rect rect(pos, pos + geom); + + GUIBox *e = new GUIBox(Environment, data->current_parent, spec.fid, rect, + colors, bordercolors, borderwidths); + e->setNotClipped(style.getBool(StyleSpec::NOCLIP, m_formspec_version < 3)); + e->drop(); + + m_fields.push_back(spec); return; } - errorstream<< "Invalid Box element(" << parts.size() << "): '" << element << "'" << std::endl; + errorstream << "Invalid Box element(" << parts.size() << "): '" << element + << "'" << std::endl; } void GUIFormSpecMenu::parseBackgroundColor(parserData* data, const std::string &element) From 71287894adbf3cbe092ded1cbf02755678305afb Mon Sep 17 00:00:00 2001 From: Pierre-Yves Rollo Date: Thu, 20 Aug 2020 12:25:02 +0200 Subject: [PATCH 027/266] Fix missing translation call in hypertext (#10296) --- src/gui/guiFormSpecMenu.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/guiFormSpecMenu.cpp b/src/gui/guiFormSpecMenu.cpp index 9bd5f27fd..76441c645 100644 --- a/src/gui/guiFormSpecMenu.cpp +++ b/src/gui/guiFormSpecMenu.cpp @@ -1740,7 +1740,7 @@ void GUIFormSpecMenu::parseHyperText(parserData *data, const std::string &elemen FieldSpec spec( name, - utf8_to_wide(unescape_string(text)), + translate_string(utf8_to_wide(unescape_string(text))), L"", 258 + m_fields.size() ); From 9c7340104a7ec4007e3dfe0bb4482f3c8f9878e0 Mon Sep 17 00:00:00 2001 From: Sebastien Marie Date: Thu, 20 Aug 2020 19:10:58 +0200 Subject: [PATCH 028/266] Define environ variable on OpenBSD (#10302) --- src/porting.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/porting.cpp b/src/porting.cpp index d902d3737..b3c8cae99 100644 --- a/src/porting.cpp +++ b/src/porting.cpp @@ -25,7 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "porting.h" -#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__DragonFly__) +#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__DragonFly__) || defined(__OpenBSD__) #include #include extern char **environ; From 98faeac5a7b382e5d7ce0474bf7d52fc5975a23c Mon Sep 17 00:00:00 2001 From: DS Date: Thu, 20 Aug 2020 22:25:29 +0200 Subject: [PATCH 029/266] Load media from subfolders (#9065) --- doc/lua_api.txt | 24 +++++++++++------- .../dirt_with_grass/default_grass.png | Bin 0 -> 829 bytes .../default_grass_side.png | Bin .../textures/dirt_with_grass/info.txt | 3 +++ src/server.cpp | 16 ++++++++---- src/server/mods.cpp | 10 ++++---- src/unittest/test_servermodmanager.cpp | 2 -- 7 files changed, 34 insertions(+), 21 deletions(-) create mode 100644 games/devtest/mods/basenodes/textures/dirt_with_grass/default_grass.png rename games/devtest/mods/basenodes/textures/{ => dirt_with_grass}/default_grass_side.png (100%) create mode 100644 games/devtest/mods/basenodes/textures/dirt_with_grass/info.txt diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 49fbe0d94..9770ae4a9 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -152,7 +152,11 @@ Mod directory structure │   ├── models │   ├── textures │   │   ├── modname_stuff.png - │   │   └── modname_something_else.png + │   │   ├── modname_something_else.png + │   │   ├── subfolder_foo + │   │   │ ├── modname_more_stuff.png + │   │   │ └── another_subfolder + │   │   └── bar_subfolder │   ├── sounds │   ├── media │   ├── locale @@ -221,18 +225,20 @@ registered callbacks. `minetest.settings` can be used to read custom or existing settings at load time, if necessary. (See [`Settings`]) -### `models` - -Models for entities or meshnodes. - -### `textures`, `sounds`, `media` +### `textures`, `sounds`, `media`, `models`, `locale` Media files (textures, sounds, whatever) that will be transferred to the -client and will be available for use by the mod. +client and will be available for use by the mod and translation files for +the clients (see [Translations]). -### `locale` +It is suggested to use the folders for the purpous they are thought for, +eg. put textures into `textures`, translation files into `locale`, +models for entities or meshnodes into `models` et cetera. -Translation files for the clients. (See [Translations]) +These folders and subfolders can contain subfolders. +Subfolders with names starting with `_` or `.` are ignored. +If a subfolder contains a media file with the same name as a media file +in one of its parents, the parent's file is used. Naming conventions ------------------ diff --git a/games/devtest/mods/basenodes/textures/dirt_with_grass/default_grass.png b/games/devtest/mods/basenodes/textures/dirt_with_grass/default_grass.png new file mode 100644 index 0000000000000000000000000000000000000000..29fde6b26a7689e0091c92f49355ab7072b7c056 GIT binary patch literal 829 zcmV-D1H$}?P)+ajpshR{=5W`0H1vO`a^tn z^HmA((KjzhfEz!)^)S)UHv!<<2ft&20N}#aXNn>Qfc>9duElEayO*wC{B{5P7vm}i zfG>V{Q@MEUoa>pc5p+$F7hLwxwD9=GnM}r65ueH&GYKIICc4=-gI+FmhfF5ibEvhZ5uK&W ziu3!yrfPY*M4Zlm$EDS*d+vqq7kWB$?XncF2BUlR@p5efxZ6R$t%2REYRDb^U zFU#9IcaMWjb$u(que|XX6D`pjj8)sFUP!4%_!3&#=wKU$2@S7$u}D^kVnP+X^OS%5 zAjcZEJ-tZk<+N-Y>DVq(tNX_@F2w0Gep%Mh&~^+rNwfa}tdL}?X}J{Q00000NkvXX Hu0mjfO)-U1 literal 0 HcmV?d00001 diff --git a/games/devtest/mods/basenodes/textures/default_grass_side.png b/games/devtest/mods/basenodes/textures/dirt_with_grass/default_grass_side.png similarity index 100% rename from games/devtest/mods/basenodes/textures/default_grass_side.png rename to games/devtest/mods/basenodes/textures/dirt_with_grass/default_grass_side.png diff --git a/games/devtest/mods/basenodes/textures/dirt_with_grass/info.txt b/games/devtest/mods/basenodes/textures/dirt_with_grass/info.txt new file mode 100644 index 000000000..8db21ed9c --- /dev/null +++ b/games/devtest/mods/basenodes/textures/dirt_with_grass/info.txt @@ -0,0 +1,3 @@ +This is for testing loading textures from subfolders. +If it works correctly, the default_grass_side.png file in this folder is used but +default_grass.png is not overwritten by the file in this folder. diff --git a/src/server.cpp b/src/server.cpp index 53ee8c444..ef36aedca 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -2494,19 +2494,25 @@ void Server::fillMediaCache() // Collect all media file paths std::vector paths; - m_modmgr->getModsMediaPaths(paths); - fs::GetRecursiveDirs(paths, m_gamespec.path + DIR_DELIM + "textures"); + // The paths are ordered in descending priority fs::GetRecursiveDirs(paths, porting::path_user + DIR_DELIM + "textures" + DIR_DELIM + "server"); + fs::GetRecursiveDirs(paths, m_gamespec.path + DIR_DELIM + "textures"); + m_modmgr->getModsMediaPaths(paths); // Collect media file information from paths into cache for (const std::string &mediapath : paths) { std::vector dirlist = fs::GetDirListing(mediapath); for (const fs::DirListNode &dln : dirlist) { - if (dln.dir) // Ignore dirs + if (dln.dir) // Ignore dirs (already in paths) continue; + + const std::string &filename = dln.name; + if (m_media.find(filename) != m_media.end()) // Do not override + continue; + std::string filepath = mediapath; - filepath.append(DIR_DELIM).append(dln.name); - addMediaFile(dln.name, filepath); + filepath.append(DIR_DELIM).append(filename); + addMediaFile(filename, filepath); } } diff --git a/src/server/mods.cpp b/src/server/mods.cpp index 6ac530739..cf1467648 100644 --- a/src/server/mods.cpp +++ b/src/server/mods.cpp @@ -99,10 +99,10 @@ void ServerModManager::getModNames(std::vector &modlist) const void ServerModManager::getModsMediaPaths(std::vector &paths) const { for (const ModSpec &spec : m_sorted_mods) { - paths.push_back(spec.path + DIR_DELIM + "textures"); - paths.push_back(spec.path + DIR_DELIM + "sounds"); - paths.push_back(spec.path + DIR_DELIM + "media"); - paths.push_back(spec.path + DIR_DELIM + "models"); - paths.push_back(spec.path + DIR_DELIM + "locale"); + fs::GetRecursiveDirs(paths, spec.path + DIR_DELIM + "textures"); + fs::GetRecursiveDirs(paths, spec.path + DIR_DELIM + "sounds"); + fs::GetRecursiveDirs(paths, spec.path + DIR_DELIM + "media"); + fs::GetRecursiveDirs(paths, spec.path + DIR_DELIM + "models"); + fs::GetRecursiveDirs(paths, spec.path + DIR_DELIM + "locale"); } } diff --git a/src/unittest/test_servermodmanager.cpp b/src/unittest/test_servermodmanager.cpp index 799936757..e3edb0c32 100644 --- a/src/unittest/test_servermodmanager.cpp +++ b/src/unittest/test_servermodmanager.cpp @@ -169,6 +169,4 @@ void TestServerModManager::testGetModMediaPaths() std::vector result; sm.getModsMediaPaths(result); UASSERTEQ(bool, result.empty(), false); - // We should have 5 folders for each mod (textures, media, locale, model, sounds) - UASSERTEQ(unsigned long, result.size() % 5, 0); } From cf5547227d9fffd9fb0043ce0b5633b831536eb6 Mon Sep 17 00:00:00 2001 From: David CARLIER Date: Sun, 23 Aug 2020 14:39:32 +0100 Subject: [PATCH 030/266] Complete Haiku platform support. (#10311) Fixing linkage/libraries missing issue. Implements missing platform specifics. --- src/CMakeLists.txt | 4 ++++ src/network/CMakeLists.txt | 5 ----- src/porting.cpp | 10 ++++++++++ src/porting.h | 2 ++ src/threading/thread.cpp | 4 ++++ src/threading/thread.h | 4 ++++ 6 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3d6d1b0ea..7e1d0bd39 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -351,6 +351,10 @@ else() if (ICONV_LIBRARY) set(PLATFORM_LIBS ${PLATFORM_LIBS} ${ICONV_LIBRARY}) endif() + if (HAIKU) + set(PLATFORM_LIBS ${PLATFORM_LIBS} intl network) + endif() + endif() check_include_files(endian.h HAVE_ENDIAN_H) diff --git a/src/network/CMakeLists.txt b/src/network/CMakeLists.txt index c6995ab22..d2e2f52e9 100644 --- a/src/network/CMakeLists.txt +++ b/src/network/CMakeLists.txt @@ -16,8 +16,3 @@ if (BUILD_CLIENT) PARENT_SCOPE ) endif() - -# Haiku networking support -if(HAIKU) - set(PLATFORM_LIBS -lnetwork ${PLATFORM_LIBS}) -endif() diff --git a/src/porting.cpp b/src/porting.cpp index b3c8cae99..e7ed4e090 100644 --- a/src/porting.cpp +++ b/src/porting.cpp @@ -56,6 +56,10 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #endif +#if defined(__HAIKU__) + #include +#endif + #include "config.h" #include "debug.h" #include "filesys.h" @@ -321,6 +325,12 @@ bool getCurrentExecPath(char *buf, size_t len) return true; } +#elif defined(__HAIKU__) + +bool getCurrentExecPath(char *buf, size_t len) +{ + return find_path(B_APP_IMAGE_SYMBOL, B_FIND_PATH_IMAGE_PATH, NULL, buf, len) == B_OK; +} //// Solaris #elif defined(__sun) || defined(sun) diff --git a/src/porting.h b/src/porting.h index f50f0a950..c7adf12a2 100644 --- a/src/porting.h +++ b/src/porting.h @@ -309,6 +309,8 @@ inline const char *getPlatformName() #else "SunOS" #endif +#elif defined(__HAIKU__) + "Haiku" #elif defined(__CYGWIN__) "Cygwin" #elif defined(__unix__) || defined(__unix) diff --git a/src/threading/thread.cpp b/src/threading/thread.cpp index e0f808c4d..f678a09be 100644 --- a/src/threading/thread.cpp +++ b/src/threading/thread.cpp @@ -225,6 +225,10 @@ void Thread::setName(const std::string &name) pthread_setname_np(name.c_str()); +#elif defined(__HAIKU__) + + rename_thread(find_thread(NULL), name.c_str()); + #elif defined(_MSC_VER) // Windows itself doesn't support thread names, diff --git a/src/threading/thread.h b/src/threading/thread.h index cea92226f..3946335f5 100644 --- a/src/threading/thread.h +++ b/src/threading/thread.h @@ -36,6 +36,10 @@ DEALINGS IN THE SOFTWARE. #include // for tid_t #endif +#ifdef __HAIKU__ + #include +#endif + /* * On platforms using pthreads, these five priority classes correlate to * even divisions between the minimum and maximum reported thread priority. From f5a203fbcd3af358bc1639467014063ba8409d3e Mon Sep 17 00:00:00 2001 From: BenjaminRi <28017860+BenjaminRi@users.noreply.github.com> Date: Sun, 23 Aug 2020 15:39:55 +0200 Subject: [PATCH 031/266] Fix light overflow of u8 if light is saturated at 255 (#10305) --- src/light.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/light.cpp b/src/light.cpp index 8196fedff..d5389b450 100644 --- a/src/light.cpp +++ b/src/light.cpp @@ -18,6 +18,7 @@ with this program; if not, write to the Free Software Foundation, Inc., */ #include "light.h" +#include #include #include "util/numeric.h" #include "settings.h" @@ -81,9 +82,11 @@ void set_light_table(float gamma) // Strictly speaking, rangelim is not necessary here—if the implementation // is conforming. But we don’t want problems in any case. light_LUT[i] = rangelim((s32)(255.0f * brightness), 0, 255); + // Ensure light brightens with each level - if (i > 1 && light_LUT[i] <= light_LUT[i - 1]) - light_LUT[i] = light_LUT[i - 1] + 1; + if (i > 0 && light_LUT[i] <= light_LUT[i - 1]) { + light_LUT[i] = std::min((u8)254, light_LUT[i - 1]) + 1; + } } } From 47948793c16fc293c1d6b4ccee05421bc0864360 Mon Sep 17 00:00:00 2001 From: Vincent Robinson Date: Sun, 23 Aug 2020 06:40:34 -0700 Subject: [PATCH 032/266] Auto focus on OK button in main menu error messages (#10300) --- builtin/fstk/ui.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/builtin/fstk/ui.lua b/builtin/fstk/ui.lua index 6d26aabf0..7eeebdd47 100644 --- a/builtin/fstk/ui.lua +++ b/builtin/fstk/ui.lua @@ -64,6 +64,7 @@ function ui.update() formspec = { "size[14,8]", "real_coordinates[true]", + "set_focus[btn_reconnect_yes;true]", "box[0.5,1.2;13,5;#000]", ("textarea[0.5,1.2;13,5;;%s;%s]"):format( fgettext("The server has requested a reconnect:"), error_message), @@ -82,6 +83,7 @@ function ui.update() formspec = { "size[14,8]", "real_coordinates[true]", + "set_focus[btn_error_confirm;true]", "box[0.5,1.2;13,5;#000]", ("textarea[0.5,1.2;13,5;;%s;%s]"):format( error_title, error_message), From 3c2890692bb4c292023a8260cf9ce70f82b2e780 Mon Sep 17 00:00:00 2001 From: adrido Date: Sun, 23 Aug 2020 15:41:04 +0200 Subject: [PATCH 033/266] Fix MSVC compiler warnings (#10197) --- src/client/fontengine.h | 2 +- src/gui/guiChatConsole.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/client/fontengine.h b/src/client/fontengine.h index 865b2d3ff..c6efa0df4 100644 --- a/src/client/fontengine.h +++ b/src/client/fontengine.h @@ -48,7 +48,7 @@ struct FontSpec { u16 getHash() { - return (mode << 2) | (bold << 1) | italic; + return (mode << 2) | (static_cast(bold) << 1) | static_cast(italic); } unsigned int size; diff --git a/src/gui/guiChatConsole.cpp b/src/gui/guiChatConsole.cpp index 8de00c12f..575c6ab25 100644 --- a/src/gui/guiChatConsole.cpp +++ b/src/gui/guiChatConsole.cpp @@ -545,7 +545,7 @@ bool GUIChatConsole::OnEvent(const SEvent& event) if (prompt.getCursorLength() <= 0) return true; std::wstring wselected = prompt.getSelection(); - std::string selected(wselected.begin(), wselected.end()); + std::string selected = wide_to_utf8(wselected); Environment->getOSOperator()->copyToClipboard(selected.c_str()); return true; } @@ -575,7 +575,7 @@ bool GUIChatConsole::OnEvent(const SEvent& event) if (prompt.getCursorLength() <= 0) return true; std::wstring wselected = prompt.getSelection(); - std::string selected(wselected.begin(), wselected.end()); + std::string selected = wide_to_utf8(wselected); Environment->getOSOperator()->copyToClipboard(selected.c_str()); prompt.cursorOperation( ChatPrompt::CURSOROP_DELETE, From 287f3fb2e3317bb6bd6d8608f1aa051c601aeca4 Mon Sep 17 00:00:00 2001 From: EvidenceB <49488517+EvidenceBKidscode@users.noreply.github.com> Date: Sun, 23 Aug 2020 22:50:14 +0200 Subject: [PATCH 034/266] Avoid drawing clipped out formspec elements (#10095) --- src/gui/guiFormSpecMenu.cpp | 10 +++++++--- src/gui/guiScrollContainer.cpp | 12 ++++++++++++ src/gui/guiScrollContainer.h | 2 ++ 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/gui/guiFormSpecMenu.cpp b/src/gui/guiFormSpecMenu.cpp index 76441c645..7e3ad3b15 100644 --- a/src/gui/guiFormSpecMenu.cpp +++ b/src/gui/guiFormSpecMenu.cpp @@ -3483,10 +3483,14 @@ void GUIFormSpecMenu::drawMenu() e->setVisible(true); /* - Call base class - (This is where all the drawing happens.) + This is where all the drawing happens. */ - gui::IGUIElement::draw(); + core::list::Iterator it = Children.begin(); + for (; it != Children.end(); ++it) + if ((*it)->isNotClipped() || + AbsoluteClippingRect.isRectCollided( + (*it)->getAbsolutePosition())) + (*it)->draw(); for (gui::IGUIElement *e : m_clickthrough_elements) e->setVisible(false); diff --git a/src/gui/guiScrollContainer.cpp b/src/gui/guiScrollContainer.cpp index 88cdc7057..0fe4c41bd 100644 --- a/src/gui/guiScrollContainer.cpp +++ b/src/gui/guiScrollContainer.cpp @@ -56,6 +56,18 @@ bool GUIScrollContainer::OnEvent(const SEvent &event) return IGUIElement::OnEvent(event); } +void GUIScrollContainer::draw() +{ + if (isVisible()) { + core::list::Iterator it = Children.begin(); + for (; it != Children.end(); ++it) + if ((*it)->isNotClipped() || + AbsoluteClippingRect.isRectCollided( + (*it)->getAbsolutePosition())) + (*it)->draw(); + } +} + void GUIScrollContainer::updateScrolling() { s32 pos = m_scrollbar->getPos(); diff --git a/src/gui/guiScrollContainer.h b/src/gui/guiScrollContainer.h index a0306291e..9e3ec6e93 100644 --- a/src/gui/guiScrollContainer.h +++ b/src/gui/guiScrollContainer.h @@ -32,6 +32,8 @@ public: virtual bool OnEvent(const SEvent &event) override; + virtual void draw() override; + inline void onScrollEvent(gui::IGUIElement *caller) { if (caller == m_scrollbar) From 3e5bce2251deb8e5fcbaa266431f8c0f10078bf2 Mon Sep 17 00:00:00 2001 From: Kezi Date: Sun, 23 Aug 2020 22:50:45 +0200 Subject: [PATCH 035/266] darken tabheader background color (#10299) --- src/gui/guiSkin.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/guiSkin.cpp b/src/gui/guiSkin.cpp index 8892a00b4..e09209bd9 100644 --- a/src/gui/guiSkin.cpp +++ b/src/gui/guiSkin.cpp @@ -29,7 +29,7 @@ GUISkin::GUISkin(EGUI_SKIN_TYPE type, video::IVideoDriver* driver) { Colors[EGDC_3D_DARK_SHADOW] = video::SColor(101,50,50,50); Colors[EGDC_3D_SHADOW] = video::SColor(101,130,130,130); - Colors[EGDC_3D_FACE] = video::SColor(101,210,210,210); + Colors[EGDC_3D_FACE] = video::SColor(220,100,100,100); Colors[EGDC_3D_HIGH_LIGHT] = video::SColor(101,255,255,255); Colors[EGDC_3D_LIGHT] = video::SColor(101,210,210,210); Colors[EGDC_ACTIVE_BORDER] = video::SColor(101,16,14,115); From f27cf4777933f06f85fa2f013d56ca0a2cf1d588 Mon Sep 17 00:00:00 2001 From: Desour Date: Sun, 23 Aug 2020 19:44:25 +0200 Subject: [PATCH 036/266] Properly handle mod-errors in on_shutdown --- src/client/game.cpp | 3 ++- src/server.cpp | 17 +++++++++++++++-- src/server.h | 7 ++++++- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/client/game.cpp b/src/client/game.cpp index 0d3a0ca15..920383aaf 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -1300,7 +1300,8 @@ bool Game::createSingleplayerServer(const std::string &map_dir, return false; } - server = new Server(map_dir, gamespec, simple_singleplayer_mode, bind_addr, false); + server = new Server(map_dir, gamespec, simple_singleplayer_mode, bind_addr, + false, nullptr, error_message); server->start(); return true; diff --git a/src/server.cpp b/src/server.cpp index ef36aedca..7b3978462 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -213,7 +213,8 @@ Server::Server( bool simple_singleplayer_mode, Address bind_addr, bool dedicated, - ChatInterface *iface + ChatInterface *iface, + std::string *on_shutdown_errmsg ): m_bind_addr(bind_addr), m_path_world(path_world), @@ -232,6 +233,7 @@ Server::Server( m_thread(new ServerThread(this)), m_clients(m_con), m_admin_chat(iface), + m_on_shutdown_errmsg(on_shutdown_errmsg), m_modchannel_mgr(new ModChannelMgr()) { if (m_path_world.empty()) @@ -314,7 +316,18 @@ Server::~Server() // Execute script shutdown hooks infostream << "Executing shutdown hooks" << std::endl; - m_script->on_shutdown(); + try { + m_script->on_shutdown(); + } catch (ModError &e) { + errorstream << "ModError: " << e.what() << std::endl; + if (m_on_shutdown_errmsg) { + if (m_on_shutdown_errmsg->empty()) { + *m_on_shutdown_errmsg = std::string("ModError: ") + e.what(); + } else { + *m_on_shutdown_errmsg += std::string("\nModError: ") + e.what(); + } + } + } infostream << "Server: Saving environment metadata" << std::endl; m_env->saveMeta(); diff --git a/src/server.h b/src/server.h index f44716531..be6f60abc 100644 --- a/src/server.h +++ b/src/server.h @@ -131,7 +131,8 @@ public: bool simple_singleplayer_mode, Address bind_addr, bool dedicated, - ChatInterface *iface = nullptr + ChatInterface *iface = nullptr, + std::string *on_shutdown_errmsg = nullptr ); ~Server(); DISABLE_CLASS_COPY(Server); @@ -596,6 +597,10 @@ private: ChatInterface *m_admin_chat; std::string m_admin_nick; + // if a mod-error occurs in the on_shutdown callback, the error message will + // be written into this + std::string *const m_on_shutdown_errmsg; + /* Map edit event queue. Automatically receives all map edits. The constructor of this class registers us to receive them through From 44c98089cf923fda924902bceec4edb01f36ce2c Mon Sep 17 00:00:00 2001 From: mntmn Date: Tue, 25 Aug 2020 20:49:51 +0200 Subject: [PATCH 037/266] shaders: Fix transparency on GC7000L (#10036) Workaround for the missing GL_ALPHA_TEST implementation in Mesa (etnaviv driver). --- .../shaders/nodes_shader/opengl_fragment.glsl | 8 ++++++++ .../shaders/object_shader/opengl_fragment.glsl | 8 ++++++++ src/client/shader.cpp | 18 ++++++++++++++++++ 3 files changed, 34 insertions(+) diff --git a/client/shaders/nodes_shader/opengl_fragment.glsl b/client/shaders/nodes_shader/opengl_fragment.glsl index 19e6c2d86..7213612bd 100644 --- a/client/shaders/nodes_shader/opengl_fragment.glsl +++ b/client/shaders/nodes_shader/opengl_fragment.glsl @@ -190,6 +190,14 @@ void main(void) #endif vec4 base = texture2D(baseTexture, uv).rgba; +#ifdef USE_DISCARD + // If alpha is zero, we can just discard the pixel. This fixes transparency + // on GPUs like GC7000L, where GL_ALPHA_TEST is not implemented in mesa. + if (base.a == 0.0) { + discard; + } +#endif + #ifdef ENABLE_BUMPMAPPING if (use_normalmap) { vec3 L = normalize(lightVec); diff --git a/client/shaders/object_shader/opengl_fragment.glsl b/client/shaders/object_shader/opengl_fragment.glsl index 0534dc049..2d33ca439 100644 --- a/client/shaders/object_shader/opengl_fragment.glsl +++ b/client/shaders/object_shader/opengl_fragment.glsl @@ -129,6 +129,14 @@ void main(void) vec4 base = texture2D(baseTexture, uv).rgba; +#ifdef USE_DISCARD + // If alpha is zero, we can just discard the pixel. This fixes transparency + // on GPUs like GC7000L, where GL_ALPHA_TEST is not implemented in mesa. + if (base.a == 0.0) { + discard; + } +#endif + #ifdef ENABLE_BUMPMAPPING if (use_normalmap) { vec3 L = normalize(lightVec); diff --git a/src/client/shader.cpp b/src/client/shader.cpp index ee6079f7a..0aba7b118 100644 --- a/src/client/shader.cpp +++ b/src/client/shader.cpp @@ -38,6 +38,16 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "gamedef.h" #include "client/tile.h" +#if ENABLE_GLES +#ifdef _IRR_COMPILE_WITH_OGLES1_ +#include +#else +#include +#endif +#else +#include +#endif + /* A cache from shader name to shader path */ @@ -607,6 +617,14 @@ ShaderInfo generate_shader(const std::string &name, u8 material_type, u8 drawtyp // Create shaders header std::string shaders_header = "#version 120\n"; +#ifdef __unix__ + // For renderers that should use discard instead of GL_ALPHA_TEST + const char* gl_renderer = (const char*)glGetString(GL_RENDERER); + if (strstr(gl_renderer, "GC7000")) { + shaders_header += "#define USE_DISCARD\n"; + } +#endif + static const char* drawTypes[] = { "NDT_NORMAL", "NDT_AIRLIKE", From b262184acf34896f36b4270aba29546fc5b0e65b Mon Sep 17 00:00:00 2001 From: David CARLIER Date: Tue, 25 Aug 2020 19:50:41 +0100 Subject: [PATCH 038/266] NetBSD build fix proposal. (#10308) Fixing thread naming call and let the class setting RANDOM_MIN/RANDOM_MAX. --- src/noise.h | 7 +++++++ src/threading/thread.cpp | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/noise.h b/src/noise.h index 7b5e83251..613879890 100644 --- a/src/noise.h +++ b/src/noise.h @@ -29,6 +29,13 @@ #include "exceptions.h" #include "util/string.h" +#if defined(RANDOM_MIN) +#undef RANDOM_MIN +#endif +#if defined(RANDOM_MAX) +#undef RANDOM_MAX +#endif + extern FlagDesc flagdesc_noiseparams[]; // Note: this class is not polymorphic so that its high level of diff --git a/src/threading/thread.cpp b/src/threading/thread.cpp index f678a09be..eb51516c6 100644 --- a/src/threading/thread.cpp +++ b/src/threading/thread.cpp @@ -219,7 +219,7 @@ void Thread::setName(const std::string &name) #elif defined(__NetBSD__) - pthread_setname_np(pthread_self(), name.c_str()); + pthread_setname_np(pthread_self(), "%s", const_cast(name.c_str())); #elif defined(__APPLE__) From 788f29759565f1eb35fb2189a79a6b2a41e1eb73 Mon Sep 17 00:00:00 2001 From: Maksim Date: Thu, 27 Aug 2020 21:46:57 +0200 Subject: [PATCH 039/266] Android: drop simple MainMenu (#10227) The default (PC-style) MainMenu works great on Android. Provides access to ContentDB and allows players to create many worlds in a few clicks. Makes the interface consistent and eliminates player confusion. --- builtin/mainmenu/init.lua | 102 ++++--------- builtin/mainmenu/tab_settings.lua | 67 +------- builtin/mainmenu/tab_simple_main.lua | 220 --------------------------- builtin/settingtypes.txt | 6 - src/defaultsettings.cpp | 1 - 5 files changed, 33 insertions(+), 363 deletions(-) delete mode 100644 builtin/mainmenu/tab_simple_main.lua diff --git a/builtin/mainmenu/init.lua b/builtin/mainmenu/init.lua index c17e79270..96d02d06c 100644 --- a/builtin/mainmenu/init.lua +++ b/builtin/mainmenu/init.lua @@ -22,7 +22,6 @@ mt_color_dark_green = "#25C191" local menupath = core.get_mainmenu_path() local basepath = core.get_builtin_path() -local menustyle = core.settings:get("main_menu_style") defaulttexturedir = core.get_texturepath_share() .. DIR_DELIM .. "base" .. DIR_DELIM .. "pack" .. DIR_DELIM @@ -39,24 +38,18 @@ dofile(menupath .. DIR_DELIM .. "textures.lua") dofile(menupath .. DIR_DELIM .. "dlg_config_world.lua") dofile(menupath .. DIR_DELIM .. "dlg_settings_advanced.lua") dofile(menupath .. DIR_DELIM .. "dlg_contentstore.lua") -if menustyle ~= "simple" then - dofile(menupath .. DIR_DELIM .. "dlg_create_world.lua") - dofile(menupath .. DIR_DELIM .. "dlg_delete_content.lua") - dofile(menupath .. DIR_DELIM .. "dlg_delete_world.lua") - dofile(menupath .. DIR_DELIM .. "dlg_rename_modpack.lua") -end +dofile(menupath .. DIR_DELIM .. "dlg_create_world.lua") +dofile(menupath .. DIR_DELIM .. "dlg_delete_content.lua") +dofile(menupath .. DIR_DELIM .. "dlg_delete_world.lua") +dofile(menupath .. DIR_DELIM .. "dlg_rename_modpack.lua") local tabs = {} tabs.settings = dofile(menupath .. DIR_DELIM .. "tab_settings.lua") tabs.content = dofile(menupath .. DIR_DELIM .. "tab_content.lua") tabs.credits = dofile(menupath .. DIR_DELIM .. "tab_credits.lua") -if menustyle == "simple" then - tabs.simple_main = dofile(menupath .. DIR_DELIM .. "tab_simple_main.lua") -else - tabs.local_game = dofile(menupath .. DIR_DELIM .. "tab_local.lua") - tabs.play_online = dofile(menupath .. DIR_DELIM .. "tab_online.lua") -end +tabs.local_game = dofile(menupath .. DIR_DELIM .. "tab_local.lua") +tabs.play_online = dofile(menupath .. DIR_DELIM .. "tab_online.lua") -------------------------------------------------------------------------------- local function main_event_handler(tabview, event) @@ -71,68 +64,35 @@ local function init_globals() -- Init gamedata gamedata.worldindex = 0 - if menustyle == "simple" then - local world_list = core.get_worlds() - local world_index - - local found_singleplayerworld = false - for i, world in ipairs(world_list) do - if world.name == "singleplayerworld" then - found_singleplayerworld = true - world_index = i - break - end + menudata.worldlist = filterlist.create( + core.get_worlds, + compare_worlds, + -- Unique id comparison function + function(element, uid) + return element.name == uid + end, + -- Filter function + function(element, gameid) + return element.gameid == gameid end + ) - if not found_singleplayerworld then - core.create_world("singleplayerworld", 1) + menudata.worldlist:add_sort_mechanism("alphabetic", sort_worlds_alphabetic) + menudata.worldlist:set_sortmode("alphabetic") - world_list = core.get_worlds() - - for i, world in ipairs(world_list) do - if world.name == "singleplayerworld" then - world_index = i - break - end - end - end - - gamedata.worldindex = world_index - else - menudata.worldlist = filterlist.create( - core.get_worlds, - compare_worlds, - -- Unique id comparison function - function(element, uid) - return element.name == uid - end, - -- Filter function - function(element, gameid) - return element.gameid == gameid - end - ) - - menudata.worldlist:add_sort_mechanism("alphabetic", sort_worlds_alphabetic) - menudata.worldlist:set_sortmode("alphabetic") - - if not core.settings:get("menu_last_game") then - local default_game = core.settings:get("default_game") or "minetest" - core.settings:set("menu_last_game", default_game) - end - - mm_texture.init() + if not core.settings:get("menu_last_game") then + local default_game = core.settings:get("default_game") or "minetest" + core.settings:set("menu_last_game", default_game) end + mm_texture.init() + -- Create main tabview local tv_main = tabview_create("maintab", {x = 12, y = 5.4}, {x = 0, y = 0}) - if menustyle == "simple" then - tv_main:add(tabs.simple_main) - else - tv_main:set_autosave_tab(true) - tv_main:add(tabs.local_game) - tv_main:add(tabs.play_online) - end + tv_main:set_autosave_tab(true) + tv_main:add(tabs.local_game) + tv_main:add(tabs.play_online) tv_main:add(tabs.content) tv_main:add(tabs.settings) @@ -141,11 +101,9 @@ local function init_globals() tv_main:set_global_event_handler(main_event_handler) tv_main:set_fixed_size(false) - if menustyle ~= "simple" then - local last_tab = core.settings:get("maintab_LAST") - if last_tab and tv_main.current_tab ~= last_tab then - tv_main:set_tab(last_tab) - end + local last_tab = core.settings:get("maintab_LAST") + if last_tab and tv_main.current_tab ~= last_tab then + tv_main:set_tab(last_tab) end ui.set_default("maintab") tv_main:show() diff --git a/builtin/mainmenu/tab_settings.lua b/builtin/mainmenu/tab_settings.lua index 1e5264904..02b15c81b 100644 --- a/builtin/mainmenu/tab_settings.lua +++ b/builtin/mainmenu/tab_settings.lua @@ -122,56 +122,6 @@ local function antialiasing_fname_to_name(fname) return 0 end -local function dlg_confirm_reset_formspec(data) - return "size[8,3]" .. - "label[1,1;" .. fgettext("Are you sure to reset your singleplayer world?") .. "]" .. - "button[1,2;2.6,0.5;dlg_reset_singleplayer_confirm;" .. fgettext("Yes") .. "]" .. - "button[4,2;2.8,0.5;dlg_reset_singleplayer_cancel;" .. fgettext("No") .. "]" -end - -local function dlg_confirm_reset_btnhandler(this, fields, dialogdata) - - if fields["dlg_reset_singleplayer_confirm"] ~= nil then - local worldlist = core.get_worlds() - local found_singleplayerworld = false - - for i = 1, #worldlist do - if worldlist[i].name == "singleplayerworld" then - found_singleplayerworld = true - gamedata.worldindex = i - end - end - - if found_singleplayerworld then - core.delete_world(gamedata.worldindex) - end - - core.create_world("singleplayerworld", 1) - worldlist = core.get_worlds() - - for i = 1, #worldlist do - if worldlist[i].name == "singleplayerworld" then - gamedata.worldindex = i - end - end - end - - this.parent:show() - this:hide() - this:delete() - return true -end - -local function showconfirm_reset(tabview) - local new_dlg = dialog_create("reset_spworld", - dlg_confirm_reset_formspec, - dlg_confirm_reset_btnhandler, - nil) - new_dlg:set_parent(tabview) - tabview:hide() - new_dlg:show() -end - local function formspec(tabview, name, tabdata) local tab_string = "box[0,0;3.75,4.5;#999999]" .. @@ -218,16 +168,9 @@ local function formspec(tabview, name, tabdata) fgettext("Shaders (unavailable)")) .. "]" end - if core.settings:get("main_menu_style") == "simple" then - -- 'Reset singleplayer world' only functions with simple menu - tab_string = tab_string .. - "button[8,4.75;3.95,1;btn_reset_singleplayer;" - .. fgettext("Reset singleplayer world") .. "]" - else - tab_string = tab_string .. - "button[8,4.75;3.95,1;btn_change_keys;" - .. fgettext("Change Keys") .. "]" - end + tab_string = tab_string .. + "button[8,4.75;3.95,1;btn_change_keys;" + .. fgettext("Change Keys") .. "]" tab_string = tab_string .. "button[0,4.75;3.95,1;btn_advanced_settings;" @@ -359,10 +302,6 @@ local function handle_settings_buttons(this, fields, tabname, tabdata) core.settings:set("touchtarget", fields["cb_touchscreen_target"]) return true end - if fields["btn_reset_singleplayer"] then - showconfirm_reset(this) - return true - end --Note dropdowns have to be handled LAST! local ddhandled = false diff --git a/builtin/mainmenu/tab_simple_main.lua b/builtin/mainmenu/tab_simple_main.lua deleted file mode 100644 index 7ec95158a..000000000 --- a/builtin/mainmenu/tab_simple_main.lua +++ /dev/null @@ -1,220 +0,0 @@ ---Minetest ---Copyright (C) 2013 sapier --- ---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. - --------------------------------------------------------------------------------- -local function get_formspec(tabview, name, tabdata) - -- Update the cached supported proto info, - -- it may have changed after a change by the settings menu. - common_update_cached_supp_proto() - local fav_selected = menudata.favorites[tabdata.fav_selected] - - local retval = - "label[9.5,0;".. fgettext("Name / Password") .. "]" .. - "field[0.25,3.35;5.5,0.5;te_address;;" .. - core.formspec_escape(core.settings:get("address")) .."]" .. - "field[5.75,3.35;2.25,0.5;te_port;;" .. - core.formspec_escape(core.settings:get("remote_port")) .."]" .. - "button[10,2.6;2,1.5;btn_mp_connect;".. fgettext("Connect") .. "]" .. - "field[9.8,1;2.6,0.5;te_name;;" .. - core.formspec_escape(core.settings:get("name")) .."]" .. - "pwdfield[9.8,2;2.6,0.5;te_pwd;]" - - - if tabdata.fav_selected and fav_selected then - if gamedata.fav then - retval = retval .. "button[7.7,2.6;2.3,1.5;btn_delete_favorite;" .. - fgettext("Del. Favorite") .. "]" - end - end - - retval = retval .. "tablecolumns[" .. - image_column(fgettext("Favorite"), "favorite") .. ";" .. - image_column(fgettext("Ping"), "") .. ",padding=0.25;" .. - "color,span=3;" .. - "text,align=right;" .. -- clients - "text,align=center,padding=0.25;" .. -- "/" - "text,align=right,padding=0.25;" .. -- clients_max - image_column(fgettext("Creative mode"), "creative") .. ",padding=1;" .. - image_column(fgettext("Damage enabled"), "damage") .. ",padding=0.25;" .. - image_column(fgettext("PvP enabled"), "pvp") .. ",padding=0.25;" .. - "color,span=1;" .. - "text,padding=1]" .. -- name - "table[-0.05,0;9.2,2.75;favourites;" - - if #menudata.favorites > 0 then - local favs = core.get_favorites("local") - if #favs > 0 then - for i = 1, #favs do - for j = 1, #menudata.favorites do - if menudata.favorites[j].address == favs[i].address and - menudata.favorites[j].port == favs[i].port then - table.insert(menudata.favorites, i, - table.remove(menudata.favorites, j)) - end - end - if favs[i].address ~= menudata.favorites[i].address then - table.insert(menudata.favorites, i, favs[i]) - end - end - end - retval = retval .. render_serverlist_row(menudata.favorites[1], (#favs > 0)) - for i = 2, #menudata.favorites do - retval = retval .. "," .. render_serverlist_row(menudata.favorites[i], (i <= #favs)) - end - end - - if tabdata.fav_selected then - retval = retval .. ";" .. tabdata.fav_selected .. "]" - else - retval = retval .. ";0]" - end - - -- separator - retval = retval .. "box[-0.28,3.75;12.4,0.1;#FFFFFF]" - - -- checkboxes - retval = retval .. - "checkbox[8.0,3.9;cb_creative;".. fgettext("Creative Mode") .. ";" .. - dump(core.settings:get_bool("creative_mode")) .. "]".. - "checkbox[8.0,4.4;cb_damage;".. fgettext("Enable Damage") .. ";" .. - dump(core.settings:get_bool("enable_damage")) .. "]" - -- buttons - retval = retval .. - "button[0,3.7;8,1.5;btn_start_singleplayer;" .. fgettext("Start Singleplayer") .. "]" .. - "button[0,4.5;8,1.5;btn_config_sp_world;" .. fgettext("Config mods") .. "]" - - return retval -end - --------------------------------------------------------------------------------- -local function main_button_handler(tabview, fields, name, tabdata) - if fields.btn_start_singleplayer then - gamedata.selected_world = gamedata.worldindex - gamedata.singleplayer = true - core.start() - return true - end - - if fields.favourites then - local event = core.explode_table_event(fields.favourites) - if event.type == "CHG" then - if event.row <= #menudata.favorites then - gamedata.fav = false - local favs = core.get_favorites("local") - local fav = menudata.favorites[event.row] - local address = fav.address - local port = fav.port - gamedata.serverdescription = fav.description - - for i = 1, #favs do - if fav.address == favs[i].address and - fav.port == favs[i].port then - gamedata.fav = true - end - end - - if address and port then - core.settings:set("address", address) - core.settings:set("remote_port", port) - end - tabdata.fav_selected = event.row - end - return true - end - end - - if fields.btn_delete_favorite then - local current_favourite = core.get_table_index("favourites") - if not current_favourite then return end - - core.delete_favorite(current_favourite) - asyncOnlineFavourites() - tabdata.fav_selected = nil - - core.settings:set("address", "") - core.settings:set("remote_port", "30000") - return true - end - - if fields.cb_creative then - core.settings:set("creative_mode", fields.cb_creative) - return true - end - - if fields.cb_damage then - core.settings:set("enable_damage", fields.cb_damage) - return true - end - - if fields.btn_mp_connect or fields.key_enter then - gamedata.playername = fields.te_name - gamedata.password = fields.te_pwd - gamedata.address = fields.te_address - gamedata.port = fields.te_port - local fav_idx = core.get_textlist_index("favourites") - - if fav_idx and fav_idx <= #menudata.favorites and - menudata.favorites[fav_idx].address == fields.te_address and - menudata.favorites[fav_idx].port == fields.te_port then - local fav = menudata.favorites[fav_idx] - gamedata.servername = fav.name - gamedata.serverdescription = fav.description - - if menudata.favorites_is_public and - not is_server_protocol_compat_or_error( - fav.proto_min, fav.proto_max) then - return true - end - else - gamedata.servername = "" - gamedata.serverdescription = "" - end - - gamedata.selected_world = 0 - - core.settings:set("address", fields.te_address) - core.settings:set("remote_port", fields.te_port) - - core.start() - return true - end - - if fields.btn_config_sp_world then - local configdialog = create_configure_world_dlg(1) - if configdialog then - configdialog:set_parent(tabview) - tabview:hide() - configdialog:show() - end - return true - end -end - --------------------------------------------------------------------------------- -local function on_activate(type,old_tab,new_tab) - if type == "LEAVE" then return end - asyncOnlineFavourites() -end - --------------------------------------------------------------------------------- -return { - name = "main", - caption = fgettext("Main"), - cbf_formspec = get_formspec, - cbf_button_handler = main_button_handler, - on_change = on_activate -} diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index af4dc1def..ef56f99bf 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -1446,12 +1446,6 @@ curl_file_download_timeout (cURL file download timeout) int 300000 # Makes DirectX work with LuaJIT. Disable if it causes troubles. high_precision_fpu (High-precision FPU) bool true -# Changes the main menu UI: -# - Full: Multiple singleplayer worlds, game choice, texture pack chooser, etc. -# - Simple: One singleplayer world, no game or texture pack choosers. May be -# necessary for smaller screens. -main_menu_style (Main menu style) enum full full,simple - # Replaces the default main menu with a custom one. main_menu_script (Main menu script) string diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 87c272630..c93af9506 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -290,7 +290,6 @@ void set_default_settings(Settings *settings) settings->setDefault("joystick_frustum_sensitivity", "170"); // Main menu - settings->setDefault("main_menu_style", "full"); settings->setDefault("main_menu_path", ""); settings->setDefault("serverlist_file", "favoriteservers.txt"); From d052593c7ab3c57f83ea250fc10f7c1927962c09 Mon Sep 17 00:00:00 2001 From: karamel59 Date: Thu, 27 Aug 2020 21:47:38 +0200 Subject: [PATCH 040/266] Lua API: Register missing get_texture_mod function (#10338) --- src/script/lua_api/l_object.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index 495d8bced..24667e769 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -2299,6 +2299,7 @@ luaL_Reg ObjectRef::methods[] = { luamethod(ObjectRef, set_rotation), luamethod(ObjectRef, get_rotation), luamethod_aliased(ObjectRef, set_texture_mod, settexturemod), + luamethod(ObjectRef, get_texture_mod), luamethod_aliased(ObjectRef, set_sprite, setsprite), luamethod(ObjectRef, get_entity_name), luamethod(ObjectRef, get_luaentity), From 1eaff3dfa4091c638be7a1e9777d35b5f665972c Mon Sep 17 00:00:00 2001 From: karamel59 Date: Thu, 27 Aug 2020 21:48:18 +0200 Subject: [PATCH 041/266] Improve set_texture_mod documentation (#10339) Properly documents the select_x_by_camera values --- doc/lua_api.txt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 9770ae4a9..acbaf421e 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -6100,6 +6100,9 @@ object you are working with still exists. * `set_yaw(radians)`: sets the yaw (heading). * `get_yaw()`: returns number in radians * `set_texture_mod(mod)` + * Set a texture modifier to the base texture, for sprites and meshes. + * When calling `set_texture_mod` again, the previous one is discarded. + * `mod` the texture modifier. See [Texture modifiers]. * `get_texture_mod()` returns current texture modifier * `set_sprite(p, num_frames, framelength, select_x_by_camera)` * Specifies and starts a sprite animation @@ -6110,6 +6113,12 @@ object you are working with still exists. * `framelength`: Time per animated frame in seconds, default: `0.2` * `select_x_by_camera`: Only for visual = `sprite`. Changes the frame `x` position according to the view direction. default: `false`. + * First column: subject facing the camera + * Second column: subject looking to the left + * Third column: subject backing the camera + * Fourth column: subject looking to the right + * Fifth column: subject viewed from above + * Sixth column: subject viewed from below * `get_entity_name()` (**Deprecated**: Will be removed in a future version) * `get_luaentity()` From 454009a7f2a798dfa70d881a3c9811f7e29481f7 Mon Sep 17 00:00:00 2001 From: DS Date: Sat, 29 Aug 2020 00:14:19 +0200 Subject: [PATCH 042/266] Fix luacheck warnings in builtin/common/tests (#10322) --- .luacheckrc | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.luacheckrc b/.luacheckrc index 3ab6e10c8..e010ab95c 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -72,3 +72,11 @@ files["builtin/mainmenu"] = { "PLATFORM", }, } + +files["builtin/common/tests"] = { + read_globals = { + "describe", + "it", + "assert", + }, +} From d28f1b01707dfa15e770096a26495452513bc56f Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Sat, 29 Aug 2020 00:14:38 +0200 Subject: [PATCH 043/266] Mapgen: Fix on-by-default flags broken since eca6ee9 (#10318) --- src/map_settings_manager.cpp | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/map_settings_manager.cpp b/src/map_settings_manager.cpp index 4f070e910..9c447b3d0 100644 --- a/src/map_settings_manager.cpp +++ b/src/map_settings_manager.cpp @@ -32,6 +32,10 @@ MapSettingsManager::MapSettingsManager(Settings *user_settings, m_user_settings(user_settings) { assert(m_user_settings != NULL); + + Mapgen::setDefaultSettings(m_map_settings); + // This inherits the combined defaults provided by loadGameConfAndInitWorld. + m_map_settings->overrideDefaults(user_settings); } @@ -179,20 +183,8 @@ MapgenParams *MapSettingsManager::makeMapgenParams() params->mgtype = mgtype; - // Load the mapgen param defaults - /* FIXME: Why is it done like this? MapgenParams should just - * set the defaults in its constructor instead. */ - { - Settings default_settings; - Mapgen::setDefaultSettings(&default_settings); - params->MapgenParams::readParams(&default_settings); - params->readParams(&default_settings); - } - // Load the rest of the mapgen params from our active settings - params->MapgenParams::readParams(m_user_settings); params->MapgenParams::readParams(m_map_settings); - params->readParams(m_user_settings); params->readParams(m_map_settings); // Hold onto our params From 3693b6871eba268ecc79b3f52d00d3cefe761131 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20M=C3=BCller?= <34514239+appgurueu@users.noreply.github.com> Date: Sat, 29 Aug 2020 17:41:03 +0200 Subject: [PATCH 044/266] Prevent players accessing inventories of other players (#10341) --- src/network/serverpackethandler.cpp | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/network/serverpackethandler.cpp b/src/network/serverpackethandler.cpp index dcbb114bf..abd9deff0 100644 --- a/src/network/serverpackethandler.cpp +++ b/src/network/serverpackethandler.cpp @@ -630,13 +630,19 @@ void Server::handleCommand_InventoryAction(NetworkPacket* pkt) if (ma->from_inv != ma->to_inv) m_inventory_mgr->setInventoryModified(ma->to_inv); - bool from_inv_is_current_player = - (ma->from_inv.type == InventoryLocation::PLAYER) && - (ma->from_inv.name == player->getName()); - - bool to_inv_is_current_player = - (ma->to_inv.type == InventoryLocation::PLAYER) && - (ma->to_inv.name == player->getName()); + bool from_inv_is_current_player = false; + if (ma->from_inv.type == InventoryLocation::PLAYER) { + if (ma->from_inv.name != player->getName()) + return; + from_inv_is_current_player = true; + } + + bool to_inv_is_current_player = false; + if (ma->to_inv.type == InventoryLocation::PLAYER) { + if (ma->to_inv.name != player->getName()) + return; + to_inv_is_current_player = true; + } InventoryLocation *remote = from_inv_is_current_player ? &ma->to_inv : &ma->from_inv; From 9976f36b18b8d227e3240feb24000dda0916ee44 Mon Sep 17 00:00:00 2001 From: Hugues Ross Date: Sat, 29 Aug 2020 11:41:19 -0400 Subject: [PATCH 045/266] Make bgcolor tint button background images (#9818) --- games/devtest/mods/testformspec/formspec.lua | 2 +- src/client/guiscalingfilter.cpp | 5 +---- src/client/guiscalingfilter.h | 3 ++- src/gui/guiButton.cpp | 21 +++++++++++++------- src/gui/guiButton.h | 1 + 5 files changed, 19 insertions(+), 13 deletions(-) diff --git a/games/devtest/mods/testformspec/formspec.lua b/games/devtest/mods/testformspec/formspec.lua index 08c1b6dc0..87a05fc96 100644 --- a/games/devtest/mods/testformspec/formspec.lua +++ b/games/devtest/mods/testformspec/formspec.lua @@ -164,7 +164,7 @@ local style_fs = [[ style[one_btn14:hovered+pressed;textcolor=purple] image_button[0,9.6;1,1;testformspec_button_image.png;one_btn14;Bg] - style[one_btn15;border=false;bgimg=testformspec_bg.png;bgimg_hovered=testformspec_bg_hovered.png;bgimg_pressed=testformspec_bg_pressed.png] + style[one_btn15;border=false;bgcolor=#1cc;bgimg=testformspec_bg.png;bgimg_hovered=testformspec_bg_hovered.png;bgimg_pressed=testformspec_bg_pressed.png] item_image_button[1.25,9.6;1,1;testformspec:item;one_btn15;Bg] style[one_btn16;border=false;bgimg=testformspec_bg_9slice.png;bgimg_hovered=testformspec_bg_9slice_hovered.png;bgimg_pressed=testformspec_bg_9slice_pressed.png;bgimg_middle=4,6] diff --git a/src/client/guiscalingfilter.cpp b/src/client/guiscalingfilter.cpp index 4262331bd..406c096e6 100644 --- a/src/client/guiscalingfilter.cpp +++ b/src/client/guiscalingfilter.cpp @@ -172,11 +172,8 @@ void draw2DImageFilterScaled(video::IVideoDriver *driver, video::ITexture *txr, void draw2DImage9Slice(video::IVideoDriver *driver, video::ITexture *texture, const core::rect &rect, const core::rect &middle, - const core::rect *cliprect) + const core::rect *cliprect, const video::SColor *const colors) { - const video::SColor color(255,255,255,255); - const video::SColor colors[] = {color,color,color,color}; - auto originalSize = texture->getOriginalSize(); core::vector2di lowerRightOffset = core::vector2di(originalSize.Width, originalSize.Height) - middle.LowerRightCorner; diff --git a/src/client/guiscalingfilter.h b/src/client/guiscalingfilter.h index b703d91f0..379a4bdb0 100644 --- a/src/client/guiscalingfilter.h +++ b/src/client/guiscalingfilter.h @@ -54,4 +54,5 @@ void draw2DImageFilterScaled(video::IVideoDriver *driver, video::ITexture *txr, */ void draw2DImage9Slice(video::IVideoDriver *driver, video::ITexture *texture, const core::rect &rect, const core::rect &middle, - const core::rect *cliprect = nullptr); + const core::rect *cliprect = nullptr, + const video::SColor *const colors = nullptr); diff --git a/src/gui/guiButton.cpp b/src/gui/guiButton.cpp index e0d6337cd..b98e5de82 100644 --- a/src/gui/guiButton.cpp +++ b/src/gui/guiButton.cpp @@ -313,11 +313,12 @@ void GUIButton::draw() // PATCH video::ITexture* texture = ButtonImages[(u32)imageState].Texture; + video::SColor image_colors[] = { BgColor, BgColor, BgColor, BgColor }; if (BgMiddle.getArea() == 0) { driver->draw2DImage(texture, ScaleImage? AbsoluteRect : core::rect(pos, sourceRect.getSize()), sourceRect, &AbsoluteClippingRect, - 0, UseAlphaChannel); + image_colors, UseAlphaChannel); } else { core::rect middle = BgMiddle; // `-x` is interpreted as `w - x` @@ -327,7 +328,7 @@ void GUIButton::draw() middle.LowerRightCorner.Y += texture->getOriginalSize().Height; draw2DImage9Slice(driver, texture, ScaleImage ? AbsoluteRect : core::rect(pos, sourceRect.getSize()), - middle, &AbsoluteClippingRect); + middle, &AbsoluteClippingRect, image_colors); } // END PATCH } @@ -722,6 +723,8 @@ GUIButton* GUIButton::addButton(IGUIEnvironment *environment, void GUIButton::setColor(video::SColor color) { + BgColor = color; + float d = 0.65f; for (size_t i = 0; i < 4; i++) { video::SColor base = Environment->getSkin()->getColor((gui::EGUI_DEFAULT_COLOR)i); @@ -750,22 +753,26 @@ void GUIButton::setFromStyle(const StyleSpec& style) bool pressed = (style.getState() & StyleSpec::STATE_PRESSED) != 0; if (style.isNotDefault(StyleSpec::BGCOLOR)) { - setColor(style.getColor(StyleSpec::BGCOLOR)); // If we have a propagated hover/press color, we need to automatically // lighten/darken it if (!Styles[style.getState()].isNotDefault(StyleSpec::BGCOLOR)) { - for (size_t i = 0; i < 4; i++) { if (pressed) { - Colors[i] = multiplyColorValue(Colors[i], COLOR_PRESSED_MOD); + BgColor = multiplyColorValue(BgColor, COLOR_PRESSED_MOD); + + for (size_t i = 0; i < 4; i++) + Colors[i] = multiplyColorValue(Colors[i], COLOR_PRESSED_MOD); } else if (hovered) { - Colors[i] = multiplyColorValue(Colors[i], COLOR_HOVERED_MOD); + BgColor = multiplyColorValue(BgColor, COLOR_HOVERED_MOD); + + for (size_t i = 0; i < 4; i++) + Colors[i] = multiplyColorValue(Colors[i], COLOR_HOVERED_MOD); } - } } } else { + BgColor = video::SColor(255, 255, 255, 255); for (size_t i = 0; i < 4; i++) { video::SColor base = Environment->getSkin()->getColor((gui::EGUI_DEFAULT_COLOR)i); diff --git a/src/gui/guiButton.h b/src/gui/guiButton.h index 95fa1a2a1..4e1b04aac 100644 --- a/src/gui/guiButton.h +++ b/src/gui/guiButton.h @@ -338,5 +338,6 @@ private: core::rect BgMiddle; core::rect Padding; core::vector2d ContentOffset; + video::SColor BgColor; // END PATCH }; From 28e87ce9d5fdf163c1eb0daf83279e949f84765d Mon Sep 17 00:00:00 2001 From: DS Date: Sat, 29 Aug 2020 17:41:29 +0200 Subject: [PATCH 046/266] Add vector.offset (#10321) --- builtin/common/tests/vector_spec.lua | 4 ++++ builtin/common/vector.lua | 6 ++++++ doc/lua_api.txt | 10 ++++++---- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/builtin/common/tests/vector_spec.lua b/builtin/common/tests/vector_spec.lua index 6f308a4a8..0f287363a 100644 --- a/builtin/common/tests/vector_spec.lua +++ b/builtin/common/tests/vector_spec.lua @@ -44,6 +44,10 @@ describe("vector", function() assert.same({ x = 2, y = 4, z = 6 }, vector.add(vector.new(1, 2, 3), { x = 1, y = 2, z = 3 })) end) + it("offset()", function() + assert.same({ x = 41, y = 52, z = 63 }, vector.offset(vector.new(1, 2, 3), 40, 50, 60)) + end) + -- This function is needed because of floating point imprecision. local function almost_equal(a, b) if type(a) == "number" then diff --git a/builtin/common/vector.lua b/builtin/common/vector.lua index 1fd784ce2..d6437deda 100644 --- a/builtin/common/vector.lua +++ b/builtin/common/vector.lua @@ -137,6 +137,12 @@ function vector.divide(a, b) end end +function vector.offset(v, x, y, z) + return {x = v.x + x, + y = v.y + y, + z = v.z + z} +end + function vector.sort(a, b) return {x = math.min(a.x, b.x), y = math.min(a.y, b.y), z = math.min(a.z, b.z)}, {x = math.max(a.x, b.x), y = math.max(a.y, b.y), z = math.max(a.z, b.z)} diff --git a/doc/lua_api.txt b/doc/lua_api.txt index acbaf421e..5ed84fe9a 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -3062,10 +3062,12 @@ For the following functions, `v`, `v1`, `v2` are vectors, * Returns in order minp, maxp vectors of the cuboid defined by `v1`, `v2`. * `vector.angle(v1, v2)`: * Returns the angle between `v1` and `v2` in radians. -* `vector.dot(v1, v2)` - * Returns the dot product of `v1` and `v2` -* `vector.cross(v1, v2)` - * Returns the cross product of `v1` and `v2` +* `vector.dot(v1, v2)`: + * Returns the dot product of `v1` and `v2`. +* `vector.cross(v1, v2)`: + * Returns the cross product of `v1` and `v2`. +* `vector.offset(v, x, y, z)`: + * Returns the sum of the vectors `v` and `{x = x, y = y, z = z}`. For the following functions `x` can be either a vector or a number: From 386d5f778a299cc0d891eb476674f3eddb108376 Mon Sep 17 00:00:00 2001 From: HybridDog <3192173+HybridDog@users.noreply.github.com> Date: Sat, 29 Aug 2020 17:43:09 +0200 Subject: [PATCH 047/266] Document normalmap textures (#10096) --- doc/lua_api.txt | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 5ed84fe9a..08bdba03e 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -152,6 +152,7 @@ Mod directory structure │   ├── models │   ├── textures │   │   ├── modname_stuff.png + │   │   ├── modname_stuff_normal.png │   │   ├── modname_something_else.png │   │   ├── subfolder_foo │   │   │ ├── modname_more_stuff.png @@ -384,6 +385,36 @@ stripping out the file extension: * e.g. `foomod_foothing.png` * e.g. `foomod_foothing` + +Normalmap Textures +------------------ + +If shaders and bumpmapping or parallax occlusion is enabled, Minetest tries +to load normalmaps. +Those image files have to end with `_normal.png` and start with the same name +as their corresponding texture. +For example a normalmap for `foomod_foothing.png` has to be called +`foomod_foothing_normal.png`. + +The sRGB R, G and B colour values of a normalmap pixel are each directly +mapped from `{0, ..., 255}` to `[-1, 1]` and, taken together, +define the normal vector. +The alpha channel defines the heightmap for parallax occlusion. +To be safe, the alpha values should always be bigger than zero +because the colour values, which define the normal vector, +may be undefined for image formats where colour is discarded in fully +transparent pixels. + +Bumpmapping and parallax occlusion are currently experimental features: + +* Bumpmapping in Minetest happens in an obscure way; there are no light sources + defined in the shaders except the sunlight direction. +* Parallax occlusion with relief-mapping mode does not yet work correctly + together with Minetest's Fastfaces. +* The normalmap files must end with `.png`, so other image files are not + supported. + + Texture modifiers ----------------- From 5c4b560b6812bbdf7a9d3202c95bad5c9df97d66 Mon Sep 17 00:00:00 2001 From: EvidenceB <49488517+EvidenceBKidscode@users.noreply.github.com> Date: Sat, 29 Aug 2020 20:13:30 +0200 Subject: [PATCH 048/266] Add compass HUD element (#9312) Co-authored-by: Jean-Patrick Guerrero Co-authored-by: Pierre-Yves Rollo Co-authored-by: SmallJoker --- doc/lua_api.txt | 18 +++++- src/client/hud.cpp | 140 +++++++++++++++++++++++++++++++++++++++++++++ src/client/hud.h | 8 +++ src/hud.cpp | 1 + src/hud.h | 10 +++- 5 files changed, 175 insertions(+), 2 deletions(-) diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 08bdba03e..c81824163 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -1451,7 +1451,23 @@ Same as `image`, but does not accept a `position`; the position is instead deter * `world_pos`: World position of the waypoint. * `offset`: offset in pixels from position. +### `compass` +Displays an image oriented or translated according to current heading direction. + +* `size`: The size of this element. Negative values represent percentage + of the screen; e.g. `x=-100` means 100% (width). +* `scale`: Scale of the translated image (used only for dir = 2 or dir = 3). +* `text`: The name of the texture to use. +* `alignment`: The alignment of the image. +* `offset`: Offset in pixels from position. +* `dir`: How the image is rotated/translated: + * 0 - Rotate as heading direction + * 1 - Rotate in reverse direction + * 2 - Translate as landscape direction + * 3 - Translate in reverse direction + +If translation is chosen, texture is repeated horizontally to fill the whole element. Representations of simple things ================================ @@ -7968,7 +7984,7 @@ Used by `Player:hud_add`. Returned by `Player:hud_get`. { hud_elem_type = "image", -- See HUD element types - -- Type of element, can be "image", "text", "statbar", or "inventory" + -- Type of element, can be "image", "text", "statbar", "inventory" or "compass" position = {x=0.5, y=0.5}, -- Left corner position of element diff --git a/src/client/hud.cpp b/src/client/hud.cpp index 2b347c1e0..d3038230c 100644 --- a/src/client/hud.cpp +++ b/src/client/hud.cpp @@ -114,6 +114,28 @@ Hud::Hud(gui::IGUIEnvironment *guienv, Client *client, LocalPlayer *player, } else { m_selection_material.MaterialType = video::EMT_SOLID; } + + // Prepare mesh for compass drawing + m_rotation_mesh_buffer.Vertices.set_used(4); + m_rotation_mesh_buffer.Indices.set_used(6); + + video::SColor white(255, 255, 255, 255); + v3f normal(0.f, 0.f, 1.f); + + m_rotation_mesh_buffer.Vertices[0] = video::S3DVertex(v3f(-1.f, -1.f, 0.f), normal, white, v2f(0.f, 1.f)); + m_rotation_mesh_buffer.Vertices[1] = video::S3DVertex(v3f(-1.f, 1.f, 0.f), normal, white, v2f(0.f, 0.f)); + m_rotation_mesh_buffer.Vertices[2] = video::S3DVertex(v3f( 1.f, 1.f, 0.f), normal, white, v2f(1.f, 0.f)); + m_rotation_mesh_buffer.Vertices[3] = video::S3DVertex(v3f( 1.f, -1.f, 0.f), normal, white, v2f(1.f, 1.f)); + + m_rotation_mesh_buffer.Indices[0] = 0; + m_rotation_mesh_buffer.Indices[1] = 1; + m_rotation_mesh_buffer.Indices[2] = 2; + m_rotation_mesh_buffer.Indices[3] = 2; + m_rotation_mesh_buffer.Indices[4] = 3; + m_rotation_mesh_buffer.Indices[5] = 0; + + m_rotation_mesh_buffer.getMaterial().Lighting = false; + m_rotation_mesh_buffer.getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; } Hud::~Hud() @@ -423,6 +445,54 @@ void Hud::drawLuaElements(const v3s16 &camera_offset) core::rect(core::position2d(0,0), imgsize), NULL, colors, true); break; } + case HUD_ELEM_COMPASS: { + video::ITexture *texture = tsrc->getTexture(e->text); + if (!texture) + continue; + + // Positionning : + v2s32 dstsize(e->size.X, e->size.Y); + if (e->size.X < 0) + dstsize.X = m_screensize.X * (e->size.X * -0.01); + if (e->size.Y < 0) + dstsize.Y = m_screensize.Y * (e->size.Y * -0.01); + + if (dstsize.X <= 0 || dstsize.Y <= 0) + return; // Avoid zero divides + + // Angle according to camera view + v3f fore(0.f, 0.f, 1.f); + scene::ICameraSceneNode *cam = RenderingEngine::get_scene_manager()->getActiveCamera(); + cam->getAbsoluteTransformation().rotateVect(fore); + int angle = - fore.getHorizontalAngle().Y; + + // Limit angle and ajust with given offset + angle = (angle + (int)e->number) % 360; + + core::rect dstrect(0, 0, dstsize.X, dstsize.Y); + dstrect += pos + v2s32( + (e->align.X - 1.0) * dstsize.X / 2, + (e->align.Y - 1.0) * dstsize.Y / 2) + + v2s32(e->offset.X * m_hud_scaling, e->offset.Y * m_hud_scaling); + + switch (e->dir) { + case HUD_COMPASS_ROTATE: + drawCompassRotate(e, texture, dstrect, angle); + break; + case HUD_COMPASS_ROTATE_REVERSE: + drawCompassRotate(e, texture, dstrect, -angle); + break; + case HUD_COMPASS_TRANSLATE: + drawCompassTranslate(e, texture, dstrect, angle); + break; + case HUD_COMPASS_TRANSLATE_REVERSE: + drawCompassTranslate(e, texture, dstrect, -angle); + break; + default: + break; + } + + break; } default: infostream << "Hud::drawLuaElements: ignoring drawform " << e->type << " of hud element ID " << i << " due to unrecognized type" << std::endl; @@ -430,6 +500,76 @@ void Hud::drawLuaElements(const v3s16 &camera_offset) } } +void Hud::drawCompassTranslate(HudElement *e, video::ITexture *texture, + const core::rect &rect, int angle) +{ + const video::SColor color(255, 255, 255, 255); + const video::SColor colors[] = {color, color, color, color}; + + // Compute source image scaling + core::dimension2di imgsize(texture->getOriginalSize()); + core::rect srcrect(0, 0, imgsize.Width, imgsize.Height); + + v2s32 dstsize(rect.getHeight() * e->scale.X * imgsize.Width / imgsize.Height, + rect.getHeight() * e->scale.Y); + + // Avoid infinite loop + if (dstsize.X <= 0 || dstsize.Y <= 0) + return; + + core::rect tgtrect(0, 0, dstsize.X, dstsize.Y); + tgtrect += v2s32( + (rect.getWidth() - dstsize.X) / 2, + (rect.getHeight() - dstsize.Y) / 2) + + rect.UpperLeftCorner; + + int offset = angle * dstsize.X / 360; + + tgtrect += v2s32(offset, 0); + + // Repeat image as much as needed + while (tgtrect.UpperLeftCorner.X > rect.UpperLeftCorner.X) + tgtrect -= v2s32(dstsize.X, 0); + + draw2DImageFilterScaled(driver, texture, tgtrect, srcrect, &rect, colors, true); + tgtrect += v2s32(dstsize.X, 0); + + while (tgtrect.UpperLeftCorner.X < rect.LowerRightCorner.X) { + draw2DImageFilterScaled(driver, texture, tgtrect, srcrect, &rect, colors, true); + tgtrect += v2s32(dstsize.X, 0); + } +} + +void Hud::drawCompassRotate(HudElement *e, video::ITexture *texture, + const core::rect &rect, int angle) +{ + core::dimension2di imgsize(texture->getOriginalSize()); + + core::rect oldViewPort = driver->getViewPort(); + core::matrix4 oldProjMat = driver->getTransform(video::ETS_PROJECTION); + core::matrix4 oldViewMat = driver->getTransform(video::ETS_VIEW); + + core::matrix4 Matrix; + Matrix.makeIdentity(); + Matrix.setRotationDegrees(v3f(0.f, 0.f, angle)); + + driver->setViewPort(rect); + driver->setTransform(video::ETS_PROJECTION, core::matrix4()); + driver->setTransform(video::ETS_VIEW, core::matrix4()); + driver->setTransform(video::ETS_WORLD, Matrix); + + video::SMaterial &material = m_rotation_mesh_buffer.getMaterial(); + material.TextureLayer[0].Texture = texture; + driver->setMaterial(material); + driver->drawMeshBuffer(&m_rotation_mesh_buffer); + + driver->setTransform(video::ETS_WORLD, core::matrix4()); + driver->setTransform(video::ETS_VIEW, oldViewMat); + driver->setTransform(video::ETS_PROJECTION, oldProjMat); + + // restore the view area + driver->setViewPort(oldViewPort); +} void Hud::drawStatbar(v2s32 pos, u16 corner, u16 drawdir, const std::string &texture, const std::string &bgtexture, diff --git a/src/client/hud.h b/src/client/hud.h index ba34d479d..cf83cb16e 100644 --- a/src/client/hud.h +++ b/src/client/hud.h @@ -95,6 +95,12 @@ private: void drawItem(const ItemStack &item, const core::rect &rect, bool selected); + void drawCompassTranslate(HudElement *e, video::ITexture *texture, + const core::rect &rect, int way); + + void drawCompassRotate(HudElement *e, video::ITexture *texture, + const core::rect &rect, int way); + float m_hud_scaling; // cached minetest setting float m_scale_factor; v3s16 m_camera_offset; @@ -115,6 +121,8 @@ private: video::SMaterial m_selection_material; + scene::SMeshBuffer m_rotation_mesh_buffer; + enum { HIGHLIGHT_BOX, diff --git a/src/hud.cpp b/src/hud.cpp index 4830c56a4..175701342 100644 --- a/src/hud.cpp +++ b/src/hud.cpp @@ -28,6 +28,7 @@ const struct EnumString es_HudElementType[] = {HUD_ELEM_INVENTORY, "inventory"}, {HUD_ELEM_WAYPOINT, "waypoint"}, {HUD_ELEM_IMAGE_WAYPOINT, "image_waypoint"}, + {HUD_ELEM_COMPASS, "compass"}, {0, NULL}, }; diff --git a/src/hud.h b/src/hud.h index e015baec1..0a6993c1b 100644 --- a/src/hud.h +++ b/src/hud.h @@ -60,7 +60,8 @@ enum HudElementType { HUD_ELEM_STATBAR = 2, HUD_ELEM_INVENTORY = 3, HUD_ELEM_WAYPOINT = 4, - HUD_ELEM_IMAGE_WAYPOINT = 5 + HUD_ELEM_IMAGE_WAYPOINT = 5, + HUD_ELEM_COMPASS = 6 }; enum HudElementStat { @@ -79,6 +80,13 @@ enum HudElementStat { HUD_STAT_TEXT2, }; +enum HudCompassDir { + HUD_COMPASS_ROTATE = 0, + HUD_COMPASS_ROTATE_REVERSE, + HUD_COMPASS_TRANSLATE, + HUD_COMPASS_TRANSLATE_REVERSE, +}; + struct HudElement { HudElementType type; v2f pos; From c18dbadcb898db6d1f68b72afec56c599af317ec Mon Sep 17 00:00:00 2001 From: LoneWolfHT Date: Sat, 29 Aug 2020 16:02:21 -0700 Subject: [PATCH 049/266] Fix dropped craftitems/tools not using light_source values (#9438) --- builtin/game/item_entity.lua | 2 +- doc/lua_api.txt | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/builtin/game/item_entity.lua b/builtin/game/item_entity.lua index 20dd18044..f380d071d 100644 --- a/builtin/game/item_entity.lua +++ b/builtin/game/item_entity.lua @@ -54,7 +54,7 @@ core.register_entity(":__builtin:item", { local max_count = stack:get_stack_max() local count = math.min(stack:get_count(), max_count) local size = 0.2 + 0.1 * (count / max_count) ^ (1 / 3) - local def = core.registered_nodes[itemname] + local def = core.registered_items[itemname] local glow = def and math.floor(def.light_source / 2 + 0.5) self.object:set_properties({ diff --git a/doc/lua_api.txt b/doc/lua_api.txt index c81824163..6341571c7 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -7026,6 +7026,13 @@ Used by `minetest.register_node`, `minetest.register_craftitem`, and liquids_pointable = false, + light_source = 0, + -- When used for nodes: Defines amount of light emitted by node. + -- Otherwise: Defines texture glow when viewed as a dropped item + -- To set the maximum (14), use the value 'minetest.LIGHT_MAX'. + -- A value outside the range 0 to minetest.LIGHT_MAX causes undefined + -- behavior. + -- See "Tools" section for an example including explanation tool_capabilities = { full_punch_interval = 1.0, @@ -7225,12 +7232,6 @@ Used by `minetest.register_node`. drowning = 0, -- Player will take this amount of damage if no bubbles are left - light_source = 0, - -- Amount of light emitted by node. - -- To set the maximum (14), use the value 'minetest.LIGHT_MAX'. - -- A value outside the range 0 to minetest.LIGHT_MAX causes undefined - -- behavior. - damage_per_second = 0, -- If player is inside node, this damage is caused From d3d218940bfbe7b0ac8edb280c7487b0a65c736e Mon Sep 17 00:00:00 2001 From: Pierre-Yves Rollo Date: Sun, 30 Aug 2020 15:34:28 +0200 Subject: [PATCH 050/266] Fix #10349 game crashing if dropped an item with undefined light_source (#10351) --- builtin/game/item_entity.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/builtin/game/item_entity.lua b/builtin/game/item_entity.lua index f380d071d..9b1b23bfd 100644 --- a/builtin/game/item_entity.lua +++ b/builtin/game/item_entity.lua @@ -55,7 +55,8 @@ core.register_entity(":__builtin:item", { local count = math.min(stack:get_count(), max_count) local size = 0.2 + 0.1 * (count / max_count) ^ (1 / 3) local def = core.registered_items[itemname] - local glow = def and math.floor(def.light_source / 2 + 0.5) + local glow = def and def.light_source and + math.floor(def.light_source / 2 + 0.5) self.object:set_properties({ is_visible = true, From f5df70764d560addf747c707c8f9db7f5b0aea4e Mon Sep 17 00:00:00 2001 From: HybridDog <3192173+HybridDog@users.noreply.github.com> Date: Tue, 1 Sep 2020 08:08:18 +0200 Subject: [PATCH 051/266] [2] Code cleanup in serverpackethandler (#9349) * Code cleanup in serverpackethandler * do not define p_under unless a node is pointed * use switch-case and reduce indentation --- src/network/serverpackethandler.cpp | 381 +++++++++++++--------------- 1 file changed, 181 insertions(+), 200 deletions(-) diff --git a/src/network/serverpackethandler.cpp b/src/network/serverpackethandler.cpp index abd9deff0..d133b4ff9 100644 --- a/src/network/serverpackethandler.cpp +++ b/src/network/serverpackethandler.cpp @@ -986,10 +986,6 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) // Update wielded item playersao->getPlayer()->setWieldIndex(item_i); - // Get pointed to node (undefined if not POINTEDTYPE_NODE) - v3s16 p_under = pointed.node_undersurface; - v3s16 p_above = pointed.node_abovesurface; - // Get pointed to object (NULL if not POINTEDTYPE_OBJECT) ServerActiveObject *pointed_object = NULL; if (pointed.type == POINTEDTHING_OBJECT) { @@ -1002,17 +998,6 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) } - v3f pointed_pos_under = player_pos; - v3f pointed_pos_above = player_pos; - if (pointed.type == POINTEDTHING_NODE) { - pointed_pos_under = intToFloat(p_under, BS); - pointed_pos_above = intToFloat(p_above, BS); - } - else if (pointed.type == POINTEDTHING_OBJECT) { - pointed_pos_under = pointed_object->getBasePosition(); - pointed_pos_above = pointed_pos_under; - } - /* Make sure the player is allowed to do it */ @@ -1020,16 +1005,19 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) actionstream << player->getName() << " attempted to interact with " << pointed.dump() << " without 'interact' privilege" << std::endl; + if (pointed.type != POINTEDTHING_NODE) + return; + // Re-send block to revert change on client-side RemoteClient *client = getClient(peer_id); // Digging completed -> under if (action == INTERACT_DIGGING_COMPLETED) { - v3s16 blockpos = getNodeBlockPos(floatToInt(pointed_pos_under, BS)); + v3s16 blockpos = getNodeBlockPos(pointed.node_undersurface); client->SetBlockNotSent(blockpos); } // Placement -> above else if (action == INTERACT_PLACE) { - v3s16 blockpos = getNodeBlockPos(floatToInt(pointed_pos_above, BS)); + v3s16 blockpos = getNodeBlockPos(pointed.node_abovesurface); client->SetBlockNotSent(blockpos); } return; @@ -1037,7 +1025,6 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) /* Check that target is reasonably close - (only when digging or placing things) */ static thread_local const bool enable_anticheat = !g_settings->getBool("disable_anticheat"); @@ -1045,12 +1032,19 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) if ((action == INTERACT_START_DIGGING || action == INTERACT_DIGGING_COMPLETED || action == INTERACT_PLACE || action == INTERACT_USE) && enable_anticheat && !isSingleplayer()) { - float d = playersao->getEyePosition().getDistanceFrom(pointed_pos_under); + v3f target_pos = player_pos; + if (pointed.type == POINTEDTHING_NODE) { + target_pos = intToFloat(pointed.node_undersurface, BS); + } else if (pointed.type == POINTEDTHING_OBJECT) { + target_pos = pointed_object->getBasePosition(); + } + float d = playersao->getEyePosition().getDistanceFrom(target_pos); - if (!checkInteractDistance(player, d, pointed.dump())) { + if (!checkInteractDistance(player, d, pointed.dump()) + && pointed.type == POINTEDTHING_NODE) { // Re-send block to revert change on client-side RemoteClient *client = getClient(peer_id); - v3s16 blockpos = getNodeBlockPos(floatToInt(pointed_pos_under, BS)); + v3s16 blockpos = getNodeBlockPos(pointed.node_undersurface); client->SetBlockNotSent(blockpos); return; } @@ -1062,20 +1056,20 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) RollbackScopeActor rollback_scope(m_rollback, std::string("player:")+player->getName()); - /* - 0: start digging or punch object - */ - if (action == INTERACT_START_DIGGING) { + switch (action) { + // Start digging or punch object + case INTERACT_START_DIGGING: { if (pointed.type == POINTEDTHING_NODE) { MapNode n(CONTENT_IGNORE); bool pos_ok; + v3s16 p_under = pointed.node_undersurface; n = m_env->getMap().getNode(p_under, &pos_ok); if (!pos_ok) { infostream << "Server: Not punching: Node not found. " "Adding block to emerge queue." << std::endl; - m_emerge->enqueueBlockEmerge(peer_id, getNodeBlockPos(p_above), - false); + m_emerge->enqueueBlockEmerge(peer_id, + getNodeBlockPos(pointed.node_abovesurface), false); } if (n.getContent() != CONTENT_IGNORE) @@ -1083,159 +1077,155 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) // Cheat prevention playersao->noCheatDigStart(p_under); - } - else if (pointed.type == POINTEDTHING_OBJECT) { - // Skip if object can't be interacted with anymore - if (pointed_object->isGone()) - return; - ItemStack selected_item, hand_item; - ItemStack tool_item = playersao->getWieldedItem(&selected_item, &hand_item); - ToolCapabilities toolcap = - tool_item.getToolCapabilities(m_itemdef); - v3f dir = (pointed_object->getBasePosition() - - (playersao->getBasePosition() + playersao->getEyeOffset()) - ).normalize(); - float time_from_last_punch = - playersao->resetTimeFromLastPunch(); - - u16 src_original_hp = pointed_object->getHP(); - u16 dst_origin_hp = playersao->getHP(); - - u16 wear = pointed_object->punch(dir, &toolcap, playersao, - time_from_last_punch); - - // Callback may have changed item, so get it again - playersao->getWieldedItem(&selected_item); - bool changed = selected_item.addWear(wear, m_itemdef); - if (changed) - playersao->setWieldedItem(selected_item); - - // If the object is a player and its HP changed - if (src_original_hp != pointed_object->getHP() && - pointed_object->getType() == ACTIVEOBJECT_TYPE_PLAYER) { - SendPlayerHPOrDie((PlayerSAO *)pointed_object, - PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, playersao)); - } - - // If the puncher is a player and its HP changed - if (dst_origin_hp != playersao->getHP()) - SendPlayerHPOrDie(playersao, - PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, pointed_object)); + return; } + // Skip if the object can't be interacted with anymore + if (pointed.type != POINTEDTHING_OBJECT || pointed_object->isGone()) + return; + + ItemStack selected_item, hand_item; + ItemStack tool_item = playersao->getWieldedItem(&selected_item, &hand_item); + ToolCapabilities toolcap = + tool_item.getToolCapabilities(m_itemdef); + v3f dir = (pointed_object->getBasePosition() - + (playersao->getBasePosition() + playersao->getEyeOffset()) + ).normalize(); + float time_from_last_punch = + playersao->resetTimeFromLastPunch(); + + u16 src_original_hp = pointed_object->getHP(); + u16 dst_origin_hp = playersao->getHP(); + + u16 wear = pointed_object->punch(dir, &toolcap, playersao, + time_from_last_punch); + + // Callback may have changed item, so get it again + playersao->getWieldedItem(&selected_item); + bool changed = selected_item.addWear(wear, m_itemdef); + if (changed) + playersao->setWieldedItem(selected_item); + + // If the object is a player and its HP changed + if (src_original_hp != pointed_object->getHP() && + pointed_object->getType() == ACTIVEOBJECT_TYPE_PLAYER) { + SendPlayerHPOrDie((PlayerSAO *)pointed_object, + PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, playersao)); + } + + // If the puncher is a player and its HP changed + if (dst_origin_hp != playersao->getHP()) + SendPlayerHPOrDie(playersao, + PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, pointed_object)); + + return; } // action == INTERACT_START_DIGGING - /* - 1: stop digging - */ - else if (action == INTERACT_STOP_DIGGING) { - } // action == INTERACT_STOP_DIGGING + case INTERACT_STOP_DIGGING: + // Nothing to do + return; - /* - 2: Digging completed - */ - else if (action == INTERACT_DIGGING_COMPLETED) { + case INTERACT_DIGGING_COMPLETED: { // Only digging of nodes - if (pointed.type == POINTEDTHING_NODE) { - bool pos_ok; - MapNode n = m_env->getMap().getNode(p_under, &pos_ok); - if (!pos_ok) { - infostream << "Server: Not finishing digging: Node not found. " - "Adding block to emerge queue." << std::endl; - m_emerge->enqueueBlockEmerge(peer_id, getNodeBlockPos(p_above), - false); + if (pointed.type != POINTEDTHING_NODE) + return; + bool pos_ok; + v3s16 p_under = pointed.node_undersurface; + MapNode n = m_env->getMap().getNode(p_under, &pos_ok); + if (!pos_ok) { + infostream << "Server: Not finishing digging: Node not found. " + "Adding block to emerge queue." << std::endl; + m_emerge->enqueueBlockEmerge(peer_id, + getNodeBlockPos(pointed.node_abovesurface), false); + } + + /* Cheat prevention */ + bool is_valid_dig = true; + if (enable_anticheat && !isSingleplayer()) { + v3s16 nocheat_p = playersao->getNoCheatDigPos(); + float nocheat_t = playersao->getNoCheatDigTime(); + playersao->noCheatDigEnd(); + // If player didn't start digging this, ignore dig + if (nocheat_p != p_under) { + infostream << "Server: " << player->getName() + << " started digging " + << PP(nocheat_p) << " and completed digging " + << PP(p_under) << "; not digging." << std::endl; + is_valid_dig = false; + // Call callbacks + m_script->on_cheat(playersao, "finished_unknown_dig"); } - /* Cheat prevention */ - bool is_valid_dig = true; - if (enable_anticheat && !isSingleplayer()) { - v3s16 nocheat_p = playersao->getNoCheatDigPos(); - float nocheat_t = playersao->getNoCheatDigTime(); - playersao->noCheatDigEnd(); - // If player didn't start digging this, ignore dig - if (nocheat_p != p_under) { - infostream << "Server: " << player->getName() - << " started digging " - << PP(nocheat_p) << " and completed digging " - << PP(p_under) << "; not digging." << std::endl; - is_valid_dig = false; - // Call callbacks - m_script->on_cheat(playersao, "finished_unknown_dig"); - } + // Get player's wielded item + // See also: Game::handleDigging + ItemStack selected_item, hand_item; + playersao->getPlayer()->getWieldedItem(&selected_item, &hand_item); - // Get player's wielded item - // See also: Game::handleDigging - ItemStack selected_item, hand_item; - playersao->getPlayer()->getWieldedItem(&selected_item, &hand_item); - - // Get diggability and expected digging time - DigParams params = getDigParams(m_nodedef->get(n).groups, - &selected_item.getToolCapabilities(m_itemdef)); - // If can't dig, try hand - if (!params.diggable) { - params = getDigParams(m_nodedef->get(n).groups, - &hand_item.getToolCapabilities(m_itemdef)); - } - // If can't dig, ignore dig - if (!params.diggable) { - infostream << "Server: " << player->getName() - << " completed digging " << PP(p_under) - << ", which is not diggable with tool; not digging." - << std::endl; - is_valid_dig = false; - // Call callbacks - m_script->on_cheat(playersao, "dug_unbreakable"); - } - // Check digging time - // If already invalidated, we don't have to - if (!is_valid_dig) { - // Well not our problem then - } - // Clean and long dig - else if (params.time > 2.0 && nocheat_t * 1.2 > params.time) { - // All is good, but grab time from pool; don't care if - // it's actually available - playersao->getDigPool().grab(params.time); - } - // Short or laggy dig - // Try getting the time from pool - else if (playersao->getDigPool().grab(params.time)) { - // All is good - } - // Dig not possible - else { - infostream << "Server: " << player->getName() - << " completed digging " << PP(p_under) - << "too fast; not digging." << std::endl; - is_valid_dig = false; - // Call callbacks - m_script->on_cheat(playersao, "dug_too_fast"); - } + // Get diggability and expected digging time + DigParams params = getDigParams(m_nodedef->get(n).groups, + &selected_item.getToolCapabilities(m_itemdef)); + // If can't dig, try hand + if (!params.diggable) { + params = getDigParams(m_nodedef->get(n).groups, + &hand_item.getToolCapabilities(m_itemdef)); } - - /* Actually dig node */ - - if (is_valid_dig && n.getContent() != CONTENT_IGNORE) - m_script->node_on_dig(p_under, n, playersao); - - v3s16 blockpos = getNodeBlockPos(floatToInt(pointed_pos_under, BS)); - RemoteClient *client = getClient(peer_id); - // Send unusual result (that is, node not being removed) - if (m_env->getMap().getNode(p_under).getContent() != CONTENT_AIR) { - // Re-send block to revert change on client-side - client->SetBlockNotSent(blockpos); + // If can't dig, ignore dig + if (!params.diggable) { + infostream << "Server: " << player->getName() + << " completed digging " << PP(p_under) + << ", which is not diggable with tool; not digging." + << std::endl; + is_valid_dig = false; + // Call callbacks + m_script->on_cheat(playersao, "dug_unbreakable"); } + // Check digging time + // If already invalidated, we don't have to + if (!is_valid_dig) { + // Well not our problem then + } + // Clean and long dig + else if (params.time > 2.0 && nocheat_t * 1.2 > params.time) { + // All is good, but grab time from pool; don't care if + // it's actually available + playersao->getDigPool().grab(params.time); + } + // Short or laggy dig + // Try getting the time from pool + else if (playersao->getDigPool().grab(params.time)) { + // All is good + } + // Dig not possible else { - client->ResendBlockIfOnWire(blockpos); + infostream << "Server: " << player->getName() + << " completed digging " << PP(p_under) + << "too fast; not digging." << std::endl; + is_valid_dig = false; + // Call callbacks + m_script->on_cheat(playersao, "dug_too_fast"); } } + + /* Actually dig node */ + + if (is_valid_dig && n.getContent() != CONTENT_IGNORE) + m_script->node_on_dig(p_under, n, playersao); + + v3s16 blockpos = getNodeBlockPos(p_under); + RemoteClient *client = getClient(peer_id); + // Send unusual result (that is, node not being removed) + if (m_env->getMap().getNode(p_under).getContent() != CONTENT_AIR) + // Re-send block to revert change on client-side + client->SetBlockNotSent(blockpos); + else + client->ResendBlockIfOnWire(blockpos); + + return; } // action == INTERACT_DIGGING_COMPLETED - /* - 3: place block or right-click object - */ - else if (action == INTERACT_PLACE) { + // Place block or right-click object + case INTERACT_PLACE: { ItemStack selected_item; playersao->getWieldedItem(&selected_item, nullptr); @@ -1264,59 +1254,54 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) } pointed_object->rightClick(playersao); - } else if (m_script->item_OnPlace( - selected_item, playersao, pointed)) { + } else if (m_script->item_OnPlace(selected_item, playersao, pointed)) { // Placement was handled in lua // Apply returned ItemStack - if (playersao->setWieldedItem(selected_item)) { + if (playersao->setWieldedItem(selected_item)) SendInventory(playersao, true); - } } + if (pointed.type != POINTEDTHING_NODE) + return; + // If item has node placement prediction, always send the // blocks to make sure the client knows what exactly happened RemoteClient *client = getClient(peer_id); - v3s16 blockpos = getNodeBlockPos(floatToInt(pointed_pos_above, BS)); - v3s16 blockpos2 = getNodeBlockPos(floatToInt(pointed_pos_under, BS)); - if (!selected_item.getDefinition(m_itemdef).node_placement_prediction.empty()) { + v3s16 blockpos = getNodeBlockPos(pointed.node_abovesurface); + v3s16 blockpos2 = getNodeBlockPos(pointed.node_undersurface); + if (!selected_item.getDefinition(m_itemdef + ).node_placement_prediction.empty()) { client->SetBlockNotSent(blockpos); - if (blockpos2 != blockpos) { + if (blockpos2 != blockpos) client->SetBlockNotSent(blockpos2); - } - } - else { + } else { client->ResendBlockIfOnWire(blockpos); - if (blockpos2 != blockpos) { + if (blockpos2 != blockpos) client->ResendBlockIfOnWire(blockpos2); - } } + + return; } // action == INTERACT_PLACE - /* - 4: use - */ - else if (action == INTERACT_USE) { + case INTERACT_USE: { ItemStack selected_item; playersao->getWieldedItem(&selected_item, nullptr); actionstream << player->getName() << " uses " << selected_item.name << ", pointing at " << pointed.dump() << std::endl; - if (m_script->item_OnUse( - selected_item, playersao, pointed)) { + if (m_script->item_OnUse(selected_item, playersao, pointed)) { // Apply returned ItemStack - if (playersao->setWieldedItem(selected_item)) { + if (playersao->setWieldedItem(selected_item)) SendInventory(playersao, true); - } } - } // action == INTERACT_USE + return; + } - /* - 5: rightclick air - */ - else if (action == INTERACT_ACTIVATE) { + // Rightclick air + case INTERACT_ACTIVATE: { ItemStack selected_item; playersao->getWieldedItem(&selected_item, nullptr); @@ -1325,21 +1310,17 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) pointed.type = POINTEDTHING_NOTHING; // can only ever be NOTHING - if (m_script->item_OnSecondaryUse( - selected_item, playersao, pointed)) { - if (playersao->setWieldedItem(selected_item)) { + if (m_script->item_OnSecondaryUse(selected_item, playersao, pointed)) { + if (playersao->setWieldedItem(selected_item)) SendInventory(playersao, true); - } } - } // action == INTERACT_ACTIVATE + return; + } + + default: + warningstream << "Server: Invalid action " << action << std::endl; - /* - Catch invalid actions - */ - else { - warningstream << "Server: Invalid action " - << action << std::endl; } } From 9ed84cfa85fe9a03803f2f2aba041363db65672d Mon Sep 17 00:00:00 2001 From: DS Date: Tue, 1 Sep 2020 20:18:10 +0200 Subject: [PATCH 052/266] Mark multiply and divide with two vectors as deprecated (Schur product and quotient) (#10329) --- doc/lua_api.txt | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 6341571c7..cc4af970c 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -3080,7 +3080,8 @@ Internally, it is implemented as a table with the 3 fields `x`, `y` and `z`. Example: `{x = 0, y = 1, z = 0}`. For the following functions, `v`, `v1`, `v2` are vectors, -`p1`, `p2` are positions: +`p1`, `p2` are positions, +`s` is a scalar (a number): * `vector.new(a[, b, c])`: * Returns a vector. @@ -3126,10 +3127,12 @@ For the following functions `x` can be either a vector or a number: * Returns a vector. * If `x` is a vector: Returns the difference of `v` subtracted by `x`. * If `x` is a number: Subtracts `x` from each component of `v`. -* `vector.multiply(v, x)`: - * Returns a scaled vector or Schur product. -* `vector.divide(v, x)`: - * Returns a scaled vector or Schur quotient. +* `vector.multiply(v, s)`: + * Returns a scaled vector. + * Deprecated: If `s` is a vector: Returns the Schur product. +* `vector.divide(v, s)`: + * Returns a scaled vector. + * Deprecated: If `s` is a vector: Returns the Schur quotient. For the following functions `a` is an angle in radians and `r` is a rotation vector ({x = , y = , z = }) where pitch, yaw and roll are From 74e22b72e1fc577d05ea703aed4bfa8c1e1f0599 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Tue, 1 Sep 2020 20:18:32 +0200 Subject: [PATCH 053/266] Change default ambient occlusion gamma to 1.8 (#10185) --- src/defaultsettings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index c93af9506..d2115c959 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -230,7 +230,7 @@ void set_default_settings(Settings *settings) settings->setDefault("show_entity_selectionbox", "false"); settings->setDefault("texture_clean_transparent", "false"); settings->setDefault("texture_min_size", "64"); - settings->setDefault("ambient_occlusion_gamma", "2.2"); + settings->setDefault("ambient_occlusion_gamma", "1.8"); #if ENABLE_GLES settings->setDefault("enable_shaders", "false"); #else From 4ba5046308d6bdf7b38394770c6f82b6927393f2 Mon Sep 17 00:00:00 2001 From: Paramat Date: Thu, 3 Sep 2020 01:28:40 +0100 Subject: [PATCH 054/266] Add 'ores' global mapgen flag (#10276) --- builtin/settingtypes.txt | 2 +- src/mapgen/mapgen.cpp | 3 ++- src/mapgen/mapgen.h | 1 + src/mapgen/mapgen_carpathian.cpp | 3 ++- src/mapgen/mapgen_flat.cpp | 3 ++- src/mapgen/mapgen_fractal.cpp | 3 ++- src/mapgen/mapgen_v5.cpp | 3 ++- src/mapgen/mapgen_v6.cpp | 3 ++- src/mapgen/mapgen_v7.cpp | 3 ++- src/mapgen/mapgen_valleys.cpp | 3 ++- 10 files changed, 18 insertions(+), 9 deletions(-) diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index ef56f99bf..39cc22d62 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -1475,7 +1475,7 @@ mapgen_limit (Map generation limit) int 31000 0 31000 # Global map generation attributes. # In Mapgen v6 the 'decorations' flag controls all decorations except trees # and junglegrass, in all other mapgens this flag controls all decorations. -mg_flags (Mapgen flags) flags caves,dungeons,light,decorations,biomes caves,dungeons,light,decorations,biomes,nocaves,nodungeons,nolight,nodecorations,nobiomes +mg_flags (Mapgen flags) flags caves,dungeons,light,decorations,biomes,ores caves,dungeons,light,decorations,biomes,ores,nocaves,nodungeons,nolight,nodecorations,nobiomes,noores [*Biome API temperature and humidity noise parameters] diff --git a/src/mapgen/mapgen.cpp b/src/mapgen/mapgen.cpp index f57529082..52ef64e7e 100644 --- a/src/mapgen/mapgen.cpp +++ b/src/mapgen/mapgen.cpp @@ -58,6 +58,7 @@ FlagDesc flagdesc_mapgen[] = { {"light", MG_LIGHT}, {"decorations", MG_DECORATIONS}, {"biomes", MG_BIOMES}, + {"ores", MG_ORES}, {NULL, 0} }; @@ -217,7 +218,7 @@ void Mapgen::getMapgenNames(std::vector *mgnames, bool include_hid void Mapgen::setDefaultSettings(Settings *settings) { settings->setDefault("mg_flags", flagdesc_mapgen, - MG_CAVES | MG_DUNGEONS | MG_LIGHT | MG_DECORATIONS | MG_BIOMES); + MG_CAVES | MG_DUNGEONS | MG_LIGHT | MG_DECORATIONS | MG_BIOMES | MG_ORES); for (int i = 0; i < (int)MAPGEN_INVALID; ++i) { MapgenParams *params = createMapgenParams((MapgenType)i); diff --git a/src/mapgen/mapgen.h b/src/mapgen/mapgen.h index a92b3b0d0..0f2977c4c 100644 --- a/src/mapgen/mapgen.h +++ b/src/mapgen/mapgen.h @@ -37,6 +37,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #define MG_LIGHT 0x10 #define MG_DECORATIONS 0x20 #define MG_BIOMES 0x40 +#define MG_ORES 0x80 typedef u16 biome_t; // copy from mg_biome.h to avoid an unnecessary include diff --git a/src/mapgen/mapgen_carpathian.cpp b/src/mapgen/mapgen_carpathian.cpp index feb9b428c..74ed263ba 100644 --- a/src/mapgen/mapgen_carpathian.cpp +++ b/src/mapgen/mapgen_carpathian.cpp @@ -315,7 +315,8 @@ void MapgenCarpathian::makeChunk(BlockMakeData *data) } // Generate the registered ores - m_emerge->oremgr->placeAllOres(this, blockseed, node_min, node_max); + if (flags & MG_ORES) + m_emerge->oremgr->placeAllOres(this, blockseed, node_min, node_max); // Generate dungeons if (flags & MG_DUNGEONS) diff --git a/src/mapgen/mapgen_flat.cpp b/src/mapgen/mapgen_flat.cpp index df6469ad9..d62548014 100644 --- a/src/mapgen/mapgen_flat.cpp +++ b/src/mapgen/mapgen_flat.cpp @@ -264,7 +264,8 @@ void MapgenFlat::makeChunk(BlockMakeData *data) } // Generate the registered ores - m_emerge->oremgr->placeAllOres(this, blockseed, node_min, node_max); + if (flags & MG_ORES) + m_emerge->oremgr->placeAllOres(this, blockseed, node_min, node_max); if (flags & MG_DUNGEONS) generateDungeons(stone_surface_max_y); diff --git a/src/mapgen/mapgen_fractal.cpp b/src/mapgen/mapgen_fractal.cpp index cb55bc288..3b6bbd6c1 100644 --- a/src/mapgen/mapgen_fractal.cpp +++ b/src/mapgen/mapgen_fractal.cpp @@ -250,7 +250,8 @@ void MapgenFractal::makeChunk(BlockMakeData *data) } // Generate the registered ores - m_emerge->oremgr->placeAllOres(this, blockseed, node_min, node_max); + if (flags & MG_ORES) + m_emerge->oremgr->placeAllOres(this, blockseed, node_min, node_max); // Generate dungeons if (flags & MG_DUNGEONS) diff --git a/src/mapgen/mapgen_v5.cpp b/src/mapgen/mapgen_v5.cpp index 124667e5d..0f6a19fa1 100644 --- a/src/mapgen/mapgen_v5.cpp +++ b/src/mapgen/mapgen_v5.cpp @@ -257,7 +257,8 @@ void MapgenV5::makeChunk(BlockMakeData *data) } // Generate the registered ores - m_emerge->oremgr->placeAllOres(this, blockseed, node_min, node_max); + if (flags & MG_ORES) + m_emerge->oremgr->placeAllOres(this, blockseed, node_min, node_max); // Generate dungeons and desert temples if (flags & MG_DUNGEONS) diff --git a/src/mapgen/mapgen_v6.cpp b/src/mapgen/mapgen_v6.cpp index e9692246c..8bea5eb69 100644 --- a/src/mapgen/mapgen_v6.cpp +++ b/src/mapgen/mapgen_v6.cpp @@ -652,7 +652,8 @@ void MapgenV6::makeChunk(BlockMakeData *data) m_emerge->decomgr->placeAllDecos(this, blockseed, node_min, node_max); // Generate the registered ores - m_emerge->oremgr->placeAllOres(this, blockseed, node_min, node_max); + if (flags & MG_ORES) + m_emerge->oremgr->placeAllOres(this, blockseed, node_min, node_max); // Calculate lighting if (flags & MG_LIGHT) diff --git a/src/mapgen/mapgen_v7.cpp b/src/mapgen/mapgen_v7.cpp index e93dc9140..a1fe25ab6 100644 --- a/src/mapgen/mapgen_v7.cpp +++ b/src/mapgen/mapgen_v7.cpp @@ -377,7 +377,8 @@ void MapgenV7::makeChunk(BlockMakeData *data) } // Generate the registered ores - m_emerge->oremgr->placeAllOres(this, blockseed, node_min, node_max); + if (flags & MG_ORES) + m_emerge->oremgr->placeAllOres(this, blockseed, node_min, node_max); // Generate dungeons if (flags & MG_DUNGEONS) diff --git a/src/mapgen/mapgen_valleys.cpp b/src/mapgen/mapgen_valleys.cpp index efcc8ee85..d7b6f738f 100644 --- a/src/mapgen/mapgen_valleys.cpp +++ b/src/mapgen/mapgen_valleys.cpp @@ -268,7 +268,8 @@ void MapgenValleys::makeChunk(BlockMakeData *data) } // Generate the registered ores - m_emerge->oremgr->placeAllOres(this, blockseed, node_min, node_max); + if (flags & MG_ORES) + m_emerge->oremgr->placeAllOres(this, blockseed, node_min, node_max); // Dungeon creation if (flags & MG_DUNGEONS) From 050964bed6005f8d816ddf362b9fc8675581d190 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20M=C3=BCller?= <34514239+appgurueu@users.noreply.github.com> Date: Fri, 4 Sep 2020 20:49:07 +0200 Subject: [PATCH 055/266] Fix inventory swapping not calling all callbacks (#9923) "Predicts" whether something will be swapped for allow callbacks, then calls callbacks a second time with swapped properties. Co-authored-by: SmallJoker --- doc/lua_api.txt | 25 ++++ src/inventory.cpp | 9 +- src/inventorymanager.cpp | 301 ++++++++++++++++++++++----------------- src/inventorymanager.h | 12 ++ 4 files changed, 209 insertions(+), 138 deletions(-) diff --git a/doc/lua_api.txt b/doc/lua_api.txt index cc4af970c..86627c19e 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -5888,6 +5888,31 @@ An `InvRef` is a reference to an inventory. `minetest.get_inventory(location)`. * returns `{type="undefined"}` in case location is not known +### Callbacks + +Detached & nodemeta inventories provide the following callbacks for move actions: + +#### Before + +The `allow_*` callbacks return how many items can be moved. + +* `allow_move`/`allow_metadata_inventory_move`: Moving items in the inventory +* `allow_take`/`allow_metadata_inventory_take`: Taking items from the inventory +* `allow_put`/`allow_metadata_inventory_put`: Putting items to the inventory + +#### After + +The `on_*` callbacks are called after the items have been placed in the inventories. + +* `on_move`/`on_metadata_inventory_move`: Moving items in the inventory +* `on_take`/`on_metadata_inventory_take`: Taking items from the inventory +* `on_put`/`on_metadata_inventory_put`: Putting items to the inventory + +#### Swapping + +When a player tries to put an item to a place where another item is, the items are *swapped*. +This means that all callbacks will be called twice (once for each action). + `ItemStack` ----------- diff --git a/src/inventory.cpp b/src/inventory.cpp index 349ee503d..cf72cb005 100644 --- a/src/inventory.cpp +++ b/src/inventory.cpp @@ -732,17 +732,17 @@ void InventoryList::moveItemSomewhere(u32 i, InventoryList *dest, u32 count) u32 InventoryList::moveItem(u32 i, InventoryList *dest, u32 dest_i, u32 count, bool swap_if_needed, bool *did_swap) { - if(this == dest && i == dest_i) + if (this == dest && i == dest_i) return count; // Take item from source list ItemStack item1; - if(count == 0) + if (count == 0) item1 = changeItem(i, ItemStack()); else item1 = takeItem(i, count); - if(item1.empty()) + if (item1.empty()) return 0; // Try to add the item to destination list @@ -750,8 +750,7 @@ u32 InventoryList::moveItem(u32 i, InventoryList *dest, u32 dest_i, item1 = dest->addItem(dest_i, item1); // If something is returned, the item was not fully added - if(!item1.empty()) - { + if (!item1.empty()) { // If olditem is returned, nothing was added. bool nothing_added = (item1.count == oldcount); diff --git a/src/inventorymanager.cpp b/src/inventorymanager.cpp index b6f464901..635bd2e4b 100644 --- a/src/inventorymanager.cpp +++ b/src/inventorymanager.cpp @@ -154,6 +154,93 @@ IMoveAction::IMoveAction(std::istream &is, bool somewhere) : } } +void IMoveAction::swapDirections() +{ + std::swap(from_inv, to_inv); + std::swap(from_list, to_list); + std::swap(from_i, to_i); +} + +void IMoveAction::onPutAndOnTake(const ItemStack &src_item, ServerActiveObject *player) const +{ + ServerScripting *sa = PLAYER_TO_SA(player); + if (to_inv.type == InventoryLocation::DETACHED) + sa->detached_inventory_OnPut(*this, src_item, player); + else if (to_inv.type == InventoryLocation::NODEMETA) + sa->nodemeta_inventory_OnPut(*this, src_item, player); + else if (to_inv.type == InventoryLocation::PLAYER) + sa->player_inventory_OnPut(*this, src_item, player); + else + assert(false); + + if (from_inv.type == InventoryLocation::DETACHED) + sa->detached_inventory_OnTake(*this, src_item, player); + else if (from_inv.type == InventoryLocation::NODEMETA) + sa->nodemeta_inventory_OnTake(*this, src_item, player); + else if (from_inv.type == InventoryLocation::PLAYER) + sa->player_inventory_OnTake(*this, src_item, player); + else + assert(false); +} + +void IMoveAction::onMove(int count, ServerActiveObject *player) const +{ + ServerScripting *sa = PLAYER_TO_SA(player); + if (from_inv.type == InventoryLocation::DETACHED) + sa->detached_inventory_OnMove(*this, count, player); + else if (from_inv.type == InventoryLocation::NODEMETA) + sa->nodemeta_inventory_OnMove(*this, count, player); + else if (from_inv.type == InventoryLocation::PLAYER) + sa->player_inventory_OnMove(*this, count, player); + else + assert(false); +} + +int IMoveAction::allowPut(const ItemStack &dst_item, ServerActiveObject *player) const +{ + ServerScripting *sa = PLAYER_TO_SA(player); + int dst_can_put_count = 0xffff; + if (to_inv.type == InventoryLocation::DETACHED) + dst_can_put_count = sa->detached_inventory_AllowPut(*this, dst_item, player); + else if (to_inv.type == InventoryLocation::NODEMETA) + dst_can_put_count = sa->nodemeta_inventory_AllowPut(*this, dst_item, player); + else if (to_inv.type == InventoryLocation::PLAYER) + dst_can_put_count = sa->player_inventory_AllowPut(*this, dst_item, player); + else + assert(false); + return dst_can_put_count; +} + +int IMoveAction::allowTake(const ItemStack &src_item, ServerActiveObject *player) const +{ + ServerScripting *sa = PLAYER_TO_SA(player); + int src_can_take_count = 0xffff; + if (from_inv.type == InventoryLocation::DETACHED) + src_can_take_count = sa->detached_inventory_AllowTake(*this, src_item, player); + else if (from_inv.type == InventoryLocation::NODEMETA) + src_can_take_count = sa->nodemeta_inventory_AllowTake(*this, src_item, player); + else if (from_inv.type == InventoryLocation::PLAYER) + src_can_take_count = sa->player_inventory_AllowTake(*this, src_item, player); + else + assert(false); + return src_can_take_count; +} + +int IMoveAction::allowMove(int try_take_count, ServerActiveObject *player) const +{ + ServerScripting *sa = PLAYER_TO_SA(player); + int src_can_take_count = 0xffff; + if (from_inv.type == InventoryLocation::DETACHED) + src_can_take_count = sa->detached_inventory_AllowMove(*this, try_take_count, player); + else if (from_inv.type == InventoryLocation::NODEMETA) + src_can_take_count = sa->nodemeta_inventory_AllowMove(*this, try_take_count, player); + else if (from_inv.type == InventoryLocation::PLAYER) + src_can_take_count = sa->player_inventory_AllowMove(*this, try_take_count, player); + else + assert(false); + return src_can_take_count; +} + void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGameDef *gamedef) { Inventory *inv_from = mgr->getInventory(from_inv); @@ -251,93 +338,80 @@ void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame Collect information of endpoints */ - int try_take_count = count; - if (try_take_count == 0) - try_take_count = list_from->getItem(from_i).count; + ItemStack src_item = list_from->getItem(from_i); + if (count > 0) + src_item.count = count; + if (src_item.empty()) + return; int src_can_take_count = 0xffff; int dst_can_put_count = 0xffff; - /* Query detached inventories */ + // this is needed for swapping items inside one inventory to work + ItemStack restitem; + bool allow_swap = !list_to->itemFits(to_i, src_item, &restitem) + && restitem.count == src_item.count + && !caused_by_move_somewhere; - // Move occurs in the same detached inventory - if (from_inv.type == InventoryLocation::DETACHED && - from_inv == to_inv) { - src_can_take_count = PLAYER_TO_SA(player)->detached_inventory_AllowMove( - *this, try_take_count, player); - dst_can_put_count = src_can_take_count; - } else { - // Destination is detached - if (to_inv.type == InventoryLocation::DETACHED) { - ItemStack src_item = list_from->getItem(from_i); - src_item.count = try_take_count; - dst_can_put_count = PLAYER_TO_SA(player)->detached_inventory_AllowPut( - *this, src_item, player); - } - // Source is detached - if (from_inv.type == InventoryLocation::DETACHED) { - ItemStack src_item = list_from->getItem(from_i); - src_item.count = try_take_count; - src_can_take_count = PLAYER_TO_SA(player)->detached_inventory_AllowTake( - *this, src_item, player); - } + // Shift-click: Cannot fill this stack, proceed with next slot + if (caused_by_move_somewhere && restitem.count == src_item.count) + return; + + if (allow_swap) { + // Swap will affect the entire stack if it can performed. + src_item = list_from->getItem(from_i); + count = src_item.count; } - /* Query node metadata inventories */ + if (from_inv == to_inv) { + // Move action within the same inventory + src_can_take_count = allowMove(src_item.count, player); - // Both endpoints are nodemeta - // Move occurs in the same nodemeta inventory - if (from_inv.type == InventoryLocation::NODEMETA && - from_inv == to_inv) { - src_can_take_count = PLAYER_TO_SA(player)->nodemeta_inventory_AllowMove( - *this, try_take_count, player); - dst_can_put_count = src_can_take_count; + bool swap_expected = allow_swap; + allow_swap = allow_swap + && (src_can_take_count == -1 || src_can_take_count >= src_item.count); + if (allow_swap) { + int try_put_count = list_to->getItem(to_i).count; + swapDirections(); + dst_can_put_count = allowMove(try_put_count, player); + allow_swap = allow_swap + && (dst_can_put_count == -1 || dst_can_put_count >= try_put_count); + swapDirections(); + } else { + dst_can_put_count = src_can_take_count; + } + if (swap_expected != allow_swap) + src_can_take_count = dst_can_put_count = 0; } else { - // Destination is nodemeta - if (to_inv.type == InventoryLocation::NODEMETA) { - ItemStack src_item = list_from->getItem(from_i); - src_item.count = try_take_count; - dst_can_put_count = PLAYER_TO_SA(player)->nodemeta_inventory_AllowPut( - *this, src_item, player); - } - // Source is nodemeta - if (from_inv.type == InventoryLocation::NODEMETA) { - ItemStack src_item = list_from->getItem(from_i); - src_item.count = try_take_count; - src_can_take_count = PLAYER_TO_SA(player)->nodemeta_inventory_AllowTake( - *this, src_item, player); - } - } + // Take from one inventory, put into another + dst_can_put_count = allowPut(src_item, player); + src_can_take_count = allowTake(src_item, player); + + bool swap_expected = allow_swap; + allow_swap = allow_swap + && (src_can_take_count == -1 || src_can_take_count >= src_item.count) + && (dst_can_put_count == -1 || dst_can_put_count >= src_item.count); + // A swap is expected, which means that we have to + // run the "allow" callbacks a second time with swapped inventories + if (allow_swap) { + ItemStack dst_item = list_to->getItem(to_i); + swapDirections(); - // Query player inventories - - // Move occurs in the same player inventory - if (from_inv.type == InventoryLocation::PLAYER && - from_inv == to_inv) { - src_can_take_count = PLAYER_TO_SA(player)->player_inventory_AllowMove( - *this, try_take_count, player); - dst_can_put_count = src_can_take_count; - } else { - // Destination is a player - if (to_inv.type == InventoryLocation::PLAYER) { - ItemStack src_item = list_from->getItem(from_i); - src_item.count = try_take_count; - dst_can_put_count = PLAYER_TO_SA(player)->player_inventory_AllowPut( - *this, src_item, player); - } - // Source is a player - if (from_inv.type == InventoryLocation::PLAYER) { - ItemStack src_item = list_from->getItem(from_i); - src_item.count = try_take_count; - src_can_take_count = PLAYER_TO_SA(player)->player_inventory_AllowTake( - *this, src_item, player); + int src_can_take = allowPut(dst_item, player); + int dst_can_put = allowTake(dst_item, player); + allow_swap = allow_swap + && (src_can_take == -1 || src_can_take >= dst_item.count) + && (dst_can_put == -1 || dst_can_put >= dst_item.count); + swapDirections(); } + if (swap_expected != allow_swap) + src_can_take_count = dst_can_put_count = 0; } int old_count = count; /* Modify count according to collected data */ - count = try_take_count; + count = src_item.count; if (src_can_take_count != -1 && count > src_can_take_count) count = src_can_take_count; if (dst_can_put_count != -1 && count > dst_can_put_count) @@ -367,7 +441,7 @@ void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame return; } - ItemStack src_item = list_from->getItem(from_i); + src_item = list_from->getItem(from_i); src_item.count = count; ItemStack from_stack_was = list_from->getItem(from_i); ItemStack to_stack_was = list_to->getItem(to_i); @@ -380,7 +454,8 @@ void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame */ bool did_swap = false; move_count = list_from->moveItem(from_i, - list_to, to_i, count, !caused_by_move_somewhere, &did_swap); + list_to, to_i, count, allow_swap, &did_swap); + assert(allow_swap == did_swap); // If source is infinite, reset it's stack if (src_can_take_count == -1) { @@ -471,69 +546,29 @@ void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame Report move to endpoints */ - /* Detached inventories */ - - // Both endpoints are same detached - if (from_inv.type == InventoryLocation::DETACHED && - from_inv == to_inv) { - PLAYER_TO_SA(player)->detached_inventory_OnMove( - *this, count, player); + // Source = destination => move + if (from_inv == to_inv) { + onMove(count, player); + if (did_swap) { + // Item is now placed in source list + src_item = list_from->getItem(from_i); + swapDirections(); + onMove(src_item.count, player); + swapDirections(); + } + mgr->setInventoryModified(from_inv); } else { - // Destination is detached - if (to_inv.type == InventoryLocation::DETACHED) { - PLAYER_TO_SA(player)->detached_inventory_OnPut( - *this, src_item, player); + onPutAndOnTake(src_item, player); + if (did_swap) { + // Item is now placed in source list + src_item = list_from->getItem(from_i); + swapDirections(); + onPutAndOnTake(src_item, player); + swapDirections(); } - // Source is detached - if (from_inv.type == InventoryLocation::DETACHED) { - PLAYER_TO_SA(player)->detached_inventory_OnTake( - *this, src_item, player); - } - } - - /* Node metadata inventories */ - - // Both endpoints are same nodemeta - if (from_inv.type == InventoryLocation::NODEMETA && - from_inv == to_inv) { - PLAYER_TO_SA(player)->nodemeta_inventory_OnMove( - *this, count, player); - } else { - // Destination is nodemeta - if (to_inv.type == InventoryLocation::NODEMETA) { - PLAYER_TO_SA(player)->nodemeta_inventory_OnPut( - *this, src_item, player); - } - // Source is nodemeta - if (from_inv.type == InventoryLocation::NODEMETA) { - PLAYER_TO_SA(player)->nodemeta_inventory_OnTake( - *this, src_item, player); - } - } - - // Player inventories - - // Both endpoints are same player inventory - if (from_inv.type == InventoryLocation::PLAYER && - from_inv == to_inv) { - PLAYER_TO_SA(player)->player_inventory_OnMove( - *this, count, player); - } else { - // Destination is player inventory - if (to_inv.type == InventoryLocation::PLAYER) { - PLAYER_TO_SA(player)->player_inventory_OnPut( - *this, src_item, player); - } - // Source is player inventory - if (from_inv.type == InventoryLocation::PLAYER) { - PLAYER_TO_SA(player)->player_inventory_OnTake( - *this, src_item, player); - } - } - - mgr->setInventoryModified(from_inv); - if (inv_from != inv_to) mgr->setInventoryModified(to_inv); + mgr->setInventoryModified(from_inv); + } } void IMoveAction::clientApply(InventoryManager *mgr, IGameDef *gamedef) diff --git a/src/inventorymanager.h b/src/inventorymanager.h index 69bf30169..4ad5d3f49 100644 --- a/src/inventorymanager.h +++ b/src/inventorymanager.h @@ -183,6 +183,18 @@ struct IMoveAction : public InventoryAction, public MoveAction void apply(InventoryManager *mgr, ServerActiveObject *player, IGameDef *gamedef); void clientApply(InventoryManager *mgr, IGameDef *gamedef); + + void swapDirections(); + + void onPutAndOnTake(const ItemStack &src_item, ServerActiveObject *player) const; + + void onMove(int count, ServerActiveObject *player) const; + + int allowPut(const ItemStack &dst_item, ServerActiveObject *player) const; + + int allowTake(const ItemStack &src_item, ServerActiveObject *player) const; + + int allowMove(int try_take_count, ServerActiveObject *player) const; }; struct IDropAction : public InventoryAction, public MoveAction From b3ace8f19746b53f839e7b0bdff0843c83866f64 Mon Sep 17 00:00:00 2001 From: Wuzzy Date: Fri, 4 Sep 2020 20:49:50 +0200 Subject: [PATCH 056/266] Scale inventory image for scaled allfaces nodes (#10225) The inventory image size of the inventory image of nodes with drawtype allfaces (and related) is scaled as well if visual_scale is set (previously, the inventory image size was always the same) --- games/devtest/mods/testnodes/drawtypes.lua | 9 +++++++++ src/client/wieldmesh.cpp | 2 ++ 2 files changed, 11 insertions(+) diff --git a/games/devtest/mods/testnodes/drawtypes.lua b/games/devtest/mods/testnodes/drawtypes.lua index 6bf57fa37..82d862819 100644 --- a/games/devtest/mods/testnodes/drawtypes.lua +++ b/games/devtest/mods/testnodes/drawtypes.lua @@ -514,6 +514,15 @@ local scale = function(subname, desc_double, desc_half) minetest.register_node("testnodes:"..subname.."_half", def) end +scale("allfaces", + S("Double-sized Allfaces Drawtype Test Node"), + S("Half-sized Allfaces Drawtype Test Node")) +scale("allfaces_optional", + S("Double-sized Allfaces Optional Drawtype Test Node"), + S("Half-sized Allfaces Optional Drawtype Test Node")) +scale("allfaces_optional_waving", + S("Double-sized Waving Allfaces Optional Drawtype Test Node"), + S("Half-sized Waving Allfaces Optional Drawtype Test Node")) scale("plantlike", S("Double-sized Plantlike Drawtype Test Node"), S("Half-sized Plantlike Drawtype Test Node")) diff --git a/src/client/wieldmesh.cpp b/src/client/wieldmesh.cpp index a268895ed..285cc38e5 100644 --- a/src/client/wieldmesh.cpp +++ b/src/client/wieldmesh.cpp @@ -562,6 +562,8 @@ void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result) // add overlays postProcessNodeMesh(mesh, f, false, false, nullptr, &result->buffer_colors, true); + if (f.drawtype == NDT_ALLFACES) + scaleMesh(mesh, v3f(f.visual_scale)); break; } case NDT_PLANTLIKE: { From 9faeca329039f33f7e2af99eb021cea5b18beceb Mon Sep 17 00:00:00 2001 From: Wuzzy Date: Fri, 4 Sep 2020 20:50:03 +0200 Subject: [PATCH 057/266] Devtest: Extend tooltips of many items and tools (#10312) Also show error message when using tools wrong --- games/devtest/README.md | 2 +- games/devtest/mods/basenodes/init.lua | 23 ++++-- games/devtest/mods/basetools/init.lua | 72 ++++++++++++------ games/devtest/mods/bucket/init.lua | 3 +- games/devtest/mods/chest/init.lua | 3 +- .../devtest/mods/chest_of_everything/init.lua | 3 +- games/devtest/mods/experimental/items.lua | 6 +- games/devtest/mods/soundstuff/init.lua | 7 +- games/devtest/mods/testentities/armor.lua | 2 +- games/devtest/mods/testnodes/light.lua | 6 +- games/devtest/mods/testpathfinder/init.lua | 6 +- games/devtest/mods/testtools/init.lua | 74 ++++++++++++++----- 12 files changed, 147 insertions(+), 60 deletions(-) diff --git a/games/devtest/README.md b/games/devtest/README.md index a7e93cf11..8b71da625 100644 --- a/games/devtest/README.md +++ b/games/devtest/README.md @@ -25,7 +25,7 @@ Basically, just create a world and start. A few important things to note: * Check out the game settings and server commands for additional tests and features * Creative Mode does nothing (apart from default engine behavior) -Confused by a certain node or item? Check out for inline code comments. +Confused by a certain node or item? Check out for inline code comments. The usages of most tools are explained in their tooltips. ### Example tests diff --git a/games/devtest/mods/basenodes/init.lua b/games/devtest/mods/basenodes/init.lua index 8156c4bec..7ffbadbea 100644 --- a/games/devtest/mods/basenodes/init.lua +++ b/games/devtest/mods/basenodes/init.lua @@ -124,7 +124,8 @@ minetest.register_node("basenodes:pine_needles", { }) minetest.register_node("basenodes:water_source", { - description = "Water Source", + description = "Water Source".."\n".. + "Drowning damage: 1", drawtype = "liquid", tiles = {"default_water.png"}, special_tiles = { @@ -148,7 +149,8 @@ minetest.register_node("basenodes:water_source", { }) minetest.register_node("basenodes:water_flowing", { - description = "Flowing Water", + description = "Flowing Water".."\n".. + "Drowning damage: 1", drawtype = "flowingliquid", tiles = {"default_water_flowing.png"}, special_tiles = { @@ -173,7 +175,8 @@ minetest.register_node("basenodes:water_flowing", { }) minetest.register_node("basenodes:river_water_source", { - description = "River Water Source", + description = "River Water Source".."\n".. + "Drowning damage: 1", drawtype = "liquid", tiles = { "default_river_water.png" }, special_tiles = { @@ -199,7 +202,8 @@ minetest.register_node("basenodes:river_water_source", { }) minetest.register_node("basenodes:river_water_flowing", { - description = "Flowing River Water", + description = "Flowing River Water".."\n".. + "Drowning damage: 1", drawtype = "flowingliquid", tiles = {"default_river_water_flowing.png"}, special_tiles = { @@ -226,7 +230,9 @@ minetest.register_node("basenodes:river_water_flowing", { }) minetest.register_node("basenodes:lava_flowing", { - description = "Flowing Lava", + description = "Flowing Lava".."\n".. + "4 damage per second".."\n".. + "Drowning damage: 1", drawtype = "flowingliquid", tiles = {"default_lava_flowing.png"}, special_tiles = { @@ -251,7 +257,9 @@ minetest.register_node("basenodes:lava_flowing", { }) minetest.register_node("basenodes:lava_source", { - description = "Lava Source", + description = "Lava Source".."\n".. + "4 damage per second".."\n".. + "Drowning damage: 1", drawtype = "liquid", tiles = { "default_lava.png" }, special_tiles = { @@ -290,7 +298,8 @@ minetest.register_node("basenodes:mossycobble", { }) minetest.register_node("basenodes:apple", { - description = "Apple", + description = "Apple".."\n".. + "Food (+2)", drawtype = "plantlike", tiles ={"default_apple.png"}, inventory_image = "default_apple.png", diff --git a/games/devtest/mods/basetools/init.lua b/games/devtest/mods/basetools/init.lua index c5b4cd76c..e4a36ca46 100644 --- a/games/devtest/mods/basetools/init.lua +++ b/games/devtest/mods/basetools/init.lua @@ -42,7 +42,8 @@ minetest.register_item(":", { -- Mese Pickaxe: special tool that digs "everything" instantly minetest.register_tool("basetools:pick_mese", { - description = "Mese Pickaxe", + description = "Mese Pickaxe".."\n".. + "Digs diggable nodes instantly", inventory_image = "basetools_mesepick.png", tool_capabilities = { full_punch_interval = 1.0, @@ -65,7 +66,9 @@ minetest.register_tool("basetools:pick_mese", { -- This should break after only 1 use minetest.register_tool("basetools:pick_dirt", { - description = "Dirt Pickaxe", + description = "Dirt Pickaxe".."\n".. + "Digs cracky=3".."\n".. + "1 use only", inventory_image = "basetools_dirtpick.png", tool_capabilities = { max_drop_level=0, @@ -76,7 +79,8 @@ minetest.register_tool("basetools:pick_dirt", { }) minetest.register_tool("basetools:pick_wood", { - description = "Wooden Pickaxe", + description = "Wooden Pickaxe".."\n".. + "Digs cracky=3", inventory_image = "basetools_woodpick.png", tool_capabilities = { max_drop_level=0, @@ -86,7 +90,8 @@ minetest.register_tool("basetools:pick_wood", { }, }) minetest.register_tool("basetools:pick_stone", { - description = "Stone Pickaxe", + description = "Stone Pickaxe".."\n".. + "Digs cracky=2..3", inventory_image = "basetools_stonepick.png", tool_capabilities = { max_drop_level=0, @@ -96,7 +101,8 @@ minetest.register_tool("basetools:pick_stone", { }, }) minetest.register_tool("basetools:pick_steel", { - description = "Steel Pickaxe", + description = "Steel Pickaxe".."\n".. + "Digs cracky=1..3", inventory_image = "basetools_steelpick.png", tool_capabilities = { max_drop_level=1, @@ -106,7 +112,9 @@ minetest.register_tool("basetools:pick_steel", { }, }) minetest.register_tool("basetools:pick_steel_l1", { - description = "Steel Pickaxe Level 1", + description = "Steel Pickaxe Level 1".."\n".. + "Digs cracky=1..3".."\n".. + "maxlevel=1", inventory_image = "basetools_steelpick_l1.png", tool_capabilities = { max_drop_level=1, @@ -116,7 +124,9 @@ minetest.register_tool("basetools:pick_steel_l1", { }, }) minetest.register_tool("basetools:pick_steel_l2", { - description = "Steel Pickaxe Level 2", + description = "Steel Pickaxe Level 2".."\n".. + "Digs cracky=1..3".."\n".. + "maxlevel=2", inventory_image = "basetools_steelpick_l2.png", tool_capabilities = { max_drop_level=1, @@ -131,7 +141,8 @@ minetest.register_tool("basetools:pick_steel_l2", { -- minetest.register_tool("basetools:shovel_wood", { - description = "Wooden Shovel", + description = "Wooden Shovel".."\n".. + "Digs crumbly=3", inventory_image = "basetools_woodshovel.png", tool_capabilities = { max_drop_level=0, @@ -141,7 +152,8 @@ minetest.register_tool("basetools:shovel_wood", { }, }) minetest.register_tool("basetools:shovel_stone", { - description = "Stone Shovel", + description = "Stone Shovel".."\n".. + "Digs crumbly=2..3", inventory_image = "basetools_stoneshovel.png", tool_capabilities = { max_drop_level=0, @@ -151,7 +163,8 @@ minetest.register_tool("basetools:shovel_stone", { }, }) minetest.register_tool("basetools:shovel_steel", { - description = "Steel Shovel", + description = "Steel Shovel".."\n".. + "Digs crumbly=1..3", inventory_image = "basetools_steelshovel.png", tool_capabilities = { max_drop_level=1, @@ -166,7 +179,8 @@ minetest.register_tool("basetools:shovel_steel", { -- minetest.register_tool("basetools:axe_wood", { - description = "Wooden Axe", + description = "Wooden Axe".."\n".. + "Digs choppy=3", inventory_image = "basetools_woodaxe.png", tool_capabilities = { max_drop_level=0, @@ -176,7 +190,8 @@ minetest.register_tool("basetools:axe_wood", { }, }) minetest.register_tool("basetools:axe_stone", { - description = "Stone Axe", + description = "Stone Axe".."\n".. + "Digs choppy=2..3", inventory_image = "basetools_stoneaxe.png", tool_capabilities = { max_drop_level=0, @@ -186,7 +201,8 @@ minetest.register_tool("basetools:axe_stone", { }, }) minetest.register_tool("basetools:axe_steel", { - description = "Steel Axe", + description = "Steel Axe".."\n".. + "Digs choppy=1..3", inventory_image = "basetools_steelaxe.png", tool_capabilities = { max_drop_level=1, @@ -201,7 +217,8 @@ minetest.register_tool("basetools:axe_steel", { -- minetest.register_tool("basetools:shears_wood", { - description = "Wooden Shears", + description = "Wooden Shears".."\n".. + "Digs snappy=3", inventory_image = "basetools_woodshears.png", tool_capabilities = { max_drop_level=0, @@ -211,7 +228,8 @@ minetest.register_tool("basetools:shears_wood", { }, }) minetest.register_tool("basetools:shears_stone", { - description = "Stone Shears", + description = "Stone Shears".."\n".. + "Digs snappy=2..3", inventory_image = "basetools_stoneshears.png", tool_capabilities = { max_drop_level=0, @@ -221,7 +239,8 @@ minetest.register_tool("basetools:shears_stone", { }, }) minetest.register_tool("basetools:shears_steel", { - description = "Steel Shears", + description = "Steel Shears".."\n".. + "Digs snappy=1..3", inventory_image = "basetools_steelshears.png", tool_capabilities = { max_drop_level=1, @@ -236,7 +255,8 @@ minetest.register_tool("basetools:shears_steel", { -- minetest.register_tool("basetools:sword_wood", { - description = "Wooden Sword", + description = "Wooden Sword".."\n".. + "Damage: fleshy=2", inventory_image = "basetools_woodsword.png", tool_capabilities = { full_punch_interval = 1.0, @@ -244,7 +264,8 @@ minetest.register_tool("basetools:sword_wood", { } }) minetest.register_tool("basetools:sword_stone", { - description = "Stone Sword", + description = "Stone Sword".."\n".. + "Damage: fleshy=4", inventory_image = "basetools_stonesword.png", tool_capabilities = { full_punch_interval = 1.0, @@ -253,7 +274,8 @@ minetest.register_tool("basetools:sword_stone", { } }) minetest.register_tool("basetools:sword_steel", { - description = "Steel Sword", + description = "Steel Sword".."\n".. + "Damage: fleshy=6", inventory_image = "basetools_steelsword.png", tool_capabilities = { full_punch_interval = 1.0, @@ -264,7 +286,8 @@ minetest.register_tool("basetools:sword_steel", { -- Fire/Ice sword: Deal damage to non-fleshy damage groups minetest.register_tool("basetools:sword_fire", { - description = "Fire Sword", + description = "Fire Sword".."\n".. + "Damage: icy=6", inventory_image = "basetools_firesword.png", tool_capabilities = { full_punch_interval = 1.0, @@ -273,12 +296,13 @@ minetest.register_tool("basetools:sword_fire", { } }) minetest.register_tool("basetools:sword_ice", { - description = "Ice Sword", + description = "Ice Sword".."\n".. + "Damage: fiery=6", inventory_image = "basetools_icesword.png", tool_capabilities = { full_punch_interval = 1.0, max_drop_level=0, - damage_groups = {firy=6}, + damage_groups = {fiery=6}, } }) @@ -286,7 +310,9 @@ minetest.register_tool("basetools:sword_ice", { -- Dagger: Low damage, fast punch interval -- minetest.register_tool("basetools:dagger_steel", { - description = "Steel Dagger", + description = "Steel Dagger".."\n".. + "Damage: fleshy=2".."\n".. + "Full Punch Interval: 0.5s", inventory_image = "basetools_steeldagger.png", tool_capabilities = { full_punch_interval = 0.5, diff --git a/games/devtest/mods/bucket/init.lua b/games/devtest/mods/bucket/init.lua index 3189d4aa6..ff58b0669 100644 --- a/games/devtest/mods/bucket/init.lua +++ b/games/devtest/mods/bucket/init.lua @@ -1,7 +1,8 @@ -- Bucket: Punch liquid source or flowing liquid to collect it minetest.register_tool("bucket:bucket", { - description = "Bucket", + description = "Bucket".."\n".. + "Picks up liquid nodes", inventory_image = "bucket.png", stack_max = 1, liquids_pointable = true, diff --git a/games/devtest/mods/chest/init.lua b/games/devtest/mods/chest/init.lua index c44522cb9..fc92bfdd1 100644 --- a/games/devtest/mods/chest/init.lua +++ b/games/devtest/mods/chest/init.lua @@ -1,5 +1,6 @@ minetest.register_node("chest:chest", { - description = "Chest", + description = "Chest" .. "\n" .. + "32 inventory slots", tiles ={"chest_chest.png^[sheet:2x2:0,0", "chest_chest.png^[sheet:2x2:0,0", "chest_chest.png^[sheet:2x2:1,0", "chest_chest.png^[sheet:2x2:1,0", "chest_chest.png^[sheet:2x2:1,0", "chest_chest.png^[sheet:2x2:0,1"}, diff --git a/games/devtest/mods/chest_of_everything/init.lua b/games/devtest/mods/chest_of_everything/init.lua index 7d61abebf..3e9d2678a 100644 --- a/games/devtest/mods/chest_of_everything/init.lua +++ b/games/devtest/mods/chest_of_everything/init.lua @@ -43,7 +43,8 @@ local function get_chest_formspec(page) end minetest.register_node("chest_of_everything:chest", { - description = "Chest of Everything", + description = "Chest of Everything" .. "\n" .. + "Grants access to all items", tiles ={"chest_of_everything_chest.png^[sheet:2x2:0,0", "chest_of_everything_chest.png^[sheet:2x2:0,0", "chest_of_everything_chest.png^[sheet:2x2:1,0", "chest_of_everything_chest.png^[sheet:2x2:1,0", "chest_of_everything_chest.png^[sheet:2x2:1,0", "chest_of_everything_chest.png^[sheet:2x2:0,1"}, diff --git a/games/devtest/mods/experimental/items.lua b/games/devtest/mods/experimental/items.lua index 51b063ba2..94be71cf7 100644 --- a/games/devtest/mods/experimental/items.lua +++ b/games/devtest/mods/experimental/items.lua @@ -44,7 +44,8 @@ minetest.register_node("experimental:callback_node", { }) minetest.register_tool("experimental:privatizer", { - description = "Node Meta Privatizer", + description = "Node Meta Privatizer".."\n".. + "Punch: Marks 'infotext' and 'formspec' meta fields of chest as private", inventory_image = "experimental_tester_tool_1.png", groups = { testtool = 1, disable_repair = 1 }, on_use = function(itemstack, user, pointed_thing) @@ -67,7 +68,8 @@ minetest.register_tool("experimental:privatizer", { }) minetest.register_tool("experimental:particle_spawner", { - description = "Particle Spawner", + description = "Particle Spawner".."\n".. + "Punch: Spawn random test particle", inventory_image = "experimental_tester_tool_1.png^[invert:g", groups = { testtool = 1, disable_repair = 1 }, on_use = function(itemstack, user, pointed_thing) diff --git a/games/devtest/mods/soundstuff/init.lua b/games/devtest/mods/soundstuff/init.lua index 22012ba14..40ad8f562 100644 --- a/games/devtest/mods/soundstuff/init.lua +++ b/games/devtest/mods/soundstuff/init.lua @@ -107,7 +107,8 @@ minetest.register_node("soundstuff:footstep_climbable", { minetest.register_craftitem("soundstuff:eat", { - description = "Eat Sound Item", + description = "Eat Sound Item".."\n".. + "Makes a sound when 'eaten' (with punch key)", inventory_image = "soundstuff_eat.png", on_use = minetest.item_eat(0), sound = { @@ -116,7 +117,9 @@ minetest.register_craftitem("soundstuff:eat", { }) minetest.register_tool("soundstuff:breaks", { - description = "Break Sound Tool", + description = "Break Sound Tool".."\n".. + "Digs cracky=3 and more".."\n".. + "Makes a sound when it breaks", inventory_image = "soundstuff_node_dug.png", sound = { breaks = { name = "soundstuff_mono", gain = 1.0 }, diff --git a/games/devtest/mods/testentities/armor.lua b/games/devtest/mods/testentities/armor.lua index 4c30cec8d..306953d50 100644 --- a/games/devtest/mods/testentities/armor.lua +++ b/games/devtest/mods/testentities/armor.lua @@ -3,7 +3,7 @@ local phasearmor = { [0]={icy=100}, - [1]={firy=100}, + [1]={fiery=100}, [2]={fleshy=100}, [3]={immortal=1}, [4]={punch_operable=1}, diff --git a/games/devtest/mods/testnodes/light.lua b/games/devtest/mods/testnodes/light.lua index 94409e83f..8ab4416d9 100644 --- a/games/devtest/mods/testnodes/light.lua +++ b/games/devtest/mods/testnodes/light.lua @@ -22,7 +22,8 @@ end -- Lets light through, but not sunlight, leading to a -- reduction in light level when light passes through minetest.register_node("testnodes:sunlight_filter", { - description = S("Sunlight Filter"), + description = S("Sunlight Filter") .."\n".. + S("Lets light through, but weakens sunlight"), paramtype = "light", @@ -35,7 +36,8 @@ minetest.register_node("testnodes:sunlight_filter", { -- Lets light and sunlight through without obstruction minetest.register_node("testnodes:sunlight_propagator", { - description = S("Sunlight Propagator"), + description = S("Sunlight Propagator") .."\n".. + S("Lets all light through"), paramtype = "light", sunlight_propagates = true, diff --git a/games/devtest/mods/testpathfinder/init.lua b/games/devtest/mods/testpathfinder/init.lua index f94848236..67748afca 100644 --- a/games/devtest/mods/testpathfinder/init.lua +++ b/games/devtest/mods/testpathfinder/init.lua @@ -121,7 +121,11 @@ end -- Sneak+punch: Select pathfinding algorithm -- Place: Select destination node minetest.register_tool("testpathfinder:testpathfinder", { - description = S("Pathfinder Tester"), + description = S("Pathfinder Tester") .."\n".. + S("Finds path between 2 points") .."\n".. + S("Place on node: Select destination") .."\n".. + S("Punch: Find path from here") .."\n".. + S("Sneak+Punch: Change algorithm"), inventory_image = "testpathfinder_testpathfinder.png", groups = { testtool = 1, disable_repair = 1 }, on_use = find_path_or_set_algorithm, diff --git a/games/devtest/mods/testtools/init.lua b/games/devtest/mods/testtools/init.lua index d68a086b9..df5bc8e55 100644 --- a/games/devtest/mods/testtools/init.lua +++ b/games/devtest/mods/testtools/init.lua @@ -3,13 +3,13 @@ local F = minetest.formspec_escape -- TODO: Add a Node Metadata tool --- Param 2 Tool: Set param2 value of tools --- Punch: +1 --- Punch+Shift: +8 --- Place: -1 --- Place+Shift: -8 minetest.register_tool("testtools:param2tool", { - description = S("Param2 Tool"), + description = S("Param2 Tool") .."\n".. + S("Modify param2 value of nodes") .."\n".. + S("Punch: +1") .."\n".. + S("Sneak+Punch: +8") .."\n".. + S("Place: -1") .."\n".. + S("Sneak+Place: -8"), inventory_image = "testtools_param2tool.png", groups = { testtool = 1, disable_repair = 1 }, on_use = function(itemstack, user, pointed_thing) @@ -47,7 +47,11 @@ minetest.register_tool("testtools:param2tool", { }) minetest.register_tool("testtools:node_setter", { - description = S("Node Setter"), + description = S("Node Setter") .."\n".. + S("Replace pointed node with something else") .."\n".. + S("Punch: Select pointed node") .."\n".. + S("Place on node: Replace node with selected node") .."\n".. + S("Place in air: Manually select a node"), inventory_image = "testtools_node_setter.png", groups = { testtool = 1, disable_repair = 1 }, on_use = function(itemstack, user, pointed_thing) @@ -125,7 +129,8 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) end) minetest.register_tool("testtools:remover", { - description = S("Remover"), + description = S("Remover") .."\n".. + S("Punch: Remove pointed node or object"), inventory_image = "testtools_remover.png", groups = { testtool = 1, disable_repair = 1 }, on_use = function(itemstack, user, pointed_thing) @@ -136,13 +141,17 @@ minetest.register_tool("testtools:remover", { local obj = pointed_thing.ref if not obj:is_player() then obj:remove() + else + minetest.chat_send_player(user:get_player_name(), S("Can't remove players!")) end end end, }) minetest.register_tool("testtools:falling_node_tool", { - description = S("Falling Node Tool"), + description = S("Falling Node Tool") .."\n".. + S("Punch: Make pointed node fall") .."\n".. + S("Place: Move pointed node 2 units upwards, then make it fall"), inventory_image = "testtools_falling_node_tool.png", groups = { testtool = 1, disable_repair = 1 }, on_place = function(itemstack, user, pointed_thing) @@ -191,7 +200,11 @@ minetest.register_tool("testtools:falling_node_tool", { }) minetest.register_tool("testtools:rotator", { - description = S("Entity Rotator"), + description = S("Entity Rotator") .. "\n" .. + S("Rotate pointed entity") .."\n".. + S("Punch: Yaw") .."\n".. + S("Sneak+Punch: Pitch") .."\n".. + S("Aux1+Punch: Roll"), inventory_image = "testtools_entity_rotator.png", groups = { testtool = 1, disable_repair = 1 }, on_use = function(itemstack, user, pointed_thing) @@ -244,7 +257,12 @@ local mover_config = function(itemstack, user, pointed_thing) end minetest.register_tool("testtools:object_mover", { - description = S("Object Mover"), + description = S("Object Mover") .."\n".. + S("Move pointed object towards or away from you") .."\n".. + S("Punch: Move by distance").."\n".. + S("Sneak+Punch: Move by negative distance").."\n".. + S("Place: Increase distance").."\n".. + S("Sneak+Place: Decrease distance"), inventory_image = "testtools_object_mover.png", groups = { testtool = 1, disable_repair = 1 }, on_place = mover_config, @@ -287,7 +305,10 @@ minetest.register_tool("testtools:object_mover", { minetest.register_tool("testtools:entity_scaler", { - description = S("Entity Visual Scaler"), + description = S("Entity Visual Scaler") .."\n".. + S("Scale visual size of entities") .."\n".. + S("Punch: Increase size") .."\n".. + S("Sneak+Punch: Decrease scale"), inventory_image = "testtools_entity_scaler.png", groups = { testtool = 1, disable_repair = 1 }, on_use = function(itemstack, user, pointed_thing) @@ -342,14 +363,21 @@ local function get_entity_list() return entity_list end minetest.register_tool("testtools:entity_spawner", { - description = S("Entity Spawner"), + description = S("Entity Spawner") .."\n".. + S("Spawns entities") .."\n".. + S("Punch: Select entity to spawn") .."\n".. + S("Place: Spawn selected entity"), inventory_image = "testtools_entity_spawner.png", groups = { testtool = 1, disable_repair = 1 }, on_place = function(itemstack, user, pointed_thing) local name = user:get_player_name() - if selections[name] and pointed_thing.type == "node" then - local pos = pointed_thing.above - minetest.add_entity(pos, get_entity_list()[selections[name]]) + if pointed_thing.type == "node" then + if selections[name] then + local pos = pointed_thing.above + minetest.add_entity(pos, get_entity_list()[selections[name]]) + else + minetest.chat_send_player(name, S("Select an entity first (with punch key)!")) + end end end, on_use = function(itemstack, user, pointed_thing) @@ -435,7 +463,10 @@ local editor_formspec = function(playername, obj, value, sel) end minetest.register_tool("testtools:object_editor", { - description = S("Object Property Editor"), + description = S("Object Property Editor") .."\n".. + S("Edit properties of objects") .."\n".. + S("Punch object: Edit object") .."\n".. + S("Punch air: Edit yourself"), inventory_image = "testtools_object_editor.png", groups = { testtool = 1, disable_repair = 1 }, on_use = function(itemstack, user, pointed_thing) @@ -515,7 +546,14 @@ local attacher_config = function(itemstack, user, pointed_thing) end minetest.register_tool("testtools:object_attacher", { - description = S("Object Attacher"), + description = S("Object Attacher") .."\n".. + S("Attach object to another") .."\n".. + S("Punch objects to first select parent object, then the child object to attach") .."\n".. + S("Punch air to select yourself") .."\n".. + S("Place: Incease attachment Y offset") .."\n".. + S("Sneak+Place: Decease attachment Y offset") .."\n".. + S("Aux1+Place: Incease attachment rotation") .."\n".. + S("Aux1+Sneak+Place: Decrease attachment rotation"), inventory_image = "testtools_object_attacher.png", groups = { testtool = 1, disable_repair = 1 }, on_place = attacher_config, From 6dcc9e63318f815a3de8c9db2ee7b845066e0135 Mon Sep 17 00:00:00 2001 From: Lejo Date: Fri, 4 Sep 2020 20:50:33 +0200 Subject: [PATCH 058/266] Prevent interacting with items out of the hotbar (#10359) --- src/network/serverpackethandler.cpp | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/network/serverpackethandler.cpp b/src/network/serverpackethandler.cpp index d133b4ff9..fe70d376e 100644 --- a/src/network/serverpackethandler.cpp +++ b/src/network/serverpackethandler.cpp @@ -636,7 +636,7 @@ void Server::handleCommand_InventoryAction(NetworkPacket* pkt) return; from_inv_is_current_player = true; } - + bool to_inv_is_current_player = false; if (ma->to_inv.type == InventoryLocation::PLAYER) { if (ma->to_inv.name != player->getName()) @@ -869,6 +869,15 @@ void Server::handleCommand_PlayerItem(NetworkPacket* pkt) *pkt >> item; + if (item >= player->getHotbarItemcount()) { + actionstream << "Player: " << player->getName() + << " tried to access item=" << item + << " out of hotbar_itemcount=" + << player->getHotbarItemcount() + << "; ignoring." << std::endl; + return; + } + playersao->getPlayer()->setWieldIndex(item); } @@ -984,6 +993,16 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) v3f player_pos = playersao->getLastGoodPosition(); // Update wielded item + + if (item_i >= player->getHotbarItemcount()) { + actionstream << "Player: " << player->getName() + << " tried to access item=" << item_i + << " out of hotbar_itemcount=" + << player->getHotbarItemcount() + << "; ignoring." << std::endl; + return; + } + playersao->getPlayer()->setWieldIndex(item_i); // Get pointed to object (NULL if not POINTEDTYPE_OBJECT) From 0d128ab344e3d04d2b30dbd5e047f4ac700013b7 Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Mon, 7 Sep 2020 21:19:38 +0200 Subject: [PATCH 059/266] Inventory: Protect Craft and Drop actions (#10353) Change dangerous pointer to unique_ptr for automated deletion. --- src/network/serverpackethandler.cpp | 79 +++++++++++++---------------- 1 file changed, 35 insertions(+), 44 deletions(-) diff --git a/src/network/serverpackethandler.cpp b/src/network/serverpackethandler.cpp index fe70d376e..0bd09e637 100644 --- a/src/network/serverpackethandler.cpp +++ b/src/network/serverpackethandler.cpp @@ -600,7 +600,7 @@ void Server::handleCommand_InventoryAction(NetworkPacket* pkt) << std::endl; std::istringstream is(datastring, std::ios_base::binary); // Create an action - InventoryAction *a = InventoryAction::deSerialize(is); + std::unique_ptr a(InventoryAction::deSerialize(is)); if (!a) { infostream << "TOSERVER_INVENTORY_ACTION: " << "InventoryAction::deSerialize() returned NULL" @@ -617,11 +617,30 @@ void Server::handleCommand_InventoryAction(NetworkPacket* pkt) where the client made a bad prediction. */ + const bool player_has_interact = checkPriv(player->getName(), "interact"); + + auto check_inv_access = [player, player_has_interact] ( + const InventoryLocation &loc) -> bool { + if (loc.type == InventoryLocation::CURRENT_PLAYER) + return false; // Only used internally on the client, never sent + if (loc.type == InventoryLocation::PLAYER) { + // Allow access to own inventory in all cases + return loc.name == player->getName(); + } + + if (!player_has_interact) { + infostream << "Cannot modify foreign inventory: " + << "No interact privilege" << std::endl; + return false; + } + return true; + }; + /* Handle restrictions and special cases of the move action */ if (a->getType() == IAction::Move) { - IMoveAction *ma = (IMoveAction*)a; + IMoveAction *ma = (IMoveAction*)a.get(); ma->from_inv.applyCurrentPlayer(player->getName()); ma->to_inv.applyCurrentPlayer(player->getName()); @@ -630,21 +649,11 @@ void Server::handleCommand_InventoryAction(NetworkPacket* pkt) if (ma->from_inv != ma->to_inv) m_inventory_mgr->setInventoryModified(ma->to_inv); - bool from_inv_is_current_player = false; - if (ma->from_inv.type == InventoryLocation::PLAYER) { - if (ma->from_inv.name != player->getName()) - return; - from_inv_is_current_player = true; - } + if (!check_inv_access(ma->from_inv) || + !check_inv_access(ma->to_inv)) + return; - bool to_inv_is_current_player = false; - if (ma->to_inv.type == InventoryLocation::PLAYER) { - if (ma->to_inv.name != player->getName()) - return; - to_inv_is_current_player = true; - } - - InventoryLocation *remote = from_inv_is_current_player ? + InventoryLocation *remote = ma->from_inv.type == InventoryLocation::PLAYER ? &ma->to_inv : &ma->from_inv; // Check for out-of-range interaction @@ -664,7 +673,6 @@ void Server::handleCommand_InventoryAction(NetworkPacket* pkt) << (ma->from_inv.dump()) << ":" << ma->from_list << " to " << (ma->to_inv.dump()) << ":" << ma->to_list << " because src is " << ma->from_list << std::endl; - delete a; return; } @@ -676,18 +684,6 @@ void Server::handleCommand_InventoryAction(NetworkPacket* pkt) << (ma->from_inv.dump()) << ":" << ma->from_list << " to " << (ma->to_inv.dump()) << ":" << ma->to_list << " because dst is " << ma->to_list << std::endl; - delete a; - return; - } - - // Disallow moving items in elsewhere than player's inventory - // if not allowed to interact - if (!checkPriv(player->getName(), "interact") && - (!from_inv_is_current_player || - !to_inv_is_current_player)) { - infostream << "Cannot move outside of player's inventory: " - << "No interact privilege" << std::endl; - delete a; return; } } @@ -695,7 +691,7 @@ void Server::handleCommand_InventoryAction(NetworkPacket* pkt) Handle restrictions and special cases of the drop action */ else if (a->getType() == IAction::Drop) { - IDropAction *da = (IDropAction*)a; + IDropAction *da = (IDropAction*)a.get(); da->from_inv.applyCurrentPlayer(player->getName()); @@ -708,22 +704,18 @@ void Server::handleCommand_InventoryAction(NetworkPacket* pkt) infostream << "Ignoring IDropAction from " << (da->from_inv.dump()) << ":" << da->from_list << " because src is " << da->from_list << std::endl; - delete a; return; } // Disallow dropping items if not allowed to interact - if (!checkPriv(player->getName(), "interact")) { - delete a; + if (!player_has_interact || !check_inv_access(da->from_inv)) return; - } // Disallow dropping items if dead if (playersao->isDead()) { infostream << "Ignoring IDropAction from " << (da->from_inv.dump()) << ":" << da->from_list << " because player is dead." << std::endl; - delete a; return; } } @@ -731,29 +723,28 @@ void Server::handleCommand_InventoryAction(NetworkPacket* pkt) Handle restrictions and special cases of the craft action */ else if (a->getType() == IAction::Craft) { - ICraftAction *ca = (ICraftAction*)a; + ICraftAction *ca = (ICraftAction*)a.get(); ca->craft_inv.applyCurrentPlayer(player->getName()); m_inventory_mgr->setInventoryModified(ca->craft_inv); - //bool craft_inv_is_current_player = - // (ca->craft_inv.type == InventoryLocation::PLAYER) && - // (ca->craft_inv.name == player->getName()); - // Disallow crafting if not allowed to interact - if (!checkPriv(player->getName(), "interact")) { + if (!player_has_interact) { infostream << "Cannot craft: " << "No interact privilege" << std::endl; - delete a; return; } + + if (!check_inv_access(ca->craft_inv)) + return; + } else { + // Unknown action. Ignored. + return; } // Do the action a->apply(m_inventory_mgr.get(), playersao, this); - // Eat the action - delete a; } void Server::handleCommand_ChatMessage(NetworkPacket* pkt) From 62913b872ea1b21a5aada55ed323476fbcea61dc Mon Sep 17 00:00:00 2001 From: David CARLIER Date: Wed, 9 Sep 2020 18:12:03 +0100 Subject: [PATCH 060/266] Darwin platform build fix (#10376) the event header seemingly being generic with libevent thus renaming it. openal and opengl are deprecated on newer mac os releases thus suppressing the build warnings. --- src/client/camera.cpp | 2 +- src/client/clientenvironment.cpp | 2 +- src/client/event_manager.h | 2 +- src/client/localplayer.cpp | 2 +- src/client/shader.cpp | 5 +++++ src/client/sound_openal.cpp | 1 + src/{event.h => mtevent.h} | 0 7 files changed, 10 insertions(+), 4 deletions(-) rename src/{event.h => mtevent.h} (100%) diff --git a/src/client/camera.cpp b/src/client/camera.cpp index abc55e4b7..11f8a1c90 100644 --- a/src/client/camera.cpp +++ b/src/client/camera.cpp @@ -30,7 +30,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "wieldmesh.h" #include "noise.h" // easeCurve #include "sound.h" -#include "event.h" +#include "mtevent.h" #include "nodedef.h" #include "util/numeric.h" #include "constants.h" diff --git a/src/client/clientenvironment.cpp b/src/client/clientenvironment.cpp index 895b0193c..0b7e92325 100644 --- a/src/client/clientenvironment.cpp +++ b/src/client/clientenvironment.cpp @@ -25,7 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "clientmap.h" #include "scripting_client.h" #include "mapblock_mesh.h" -#include "event.h" +#include "mtevent.h" #include "collision.h" #include "nodedef.h" #include "profiler.h" diff --git a/src/client/event_manager.h b/src/client/event_manager.h index 3762e89bf..16f7bcf07 100644 --- a/src/client/event_manager.h +++ b/src/client/event_manager.h @@ -19,7 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once -#include "event.h" +#include "mtevent.h" #include #include diff --git a/src/client/localplayer.cpp b/src/client/localplayer.cpp index 1e7040d57..f3eb1a2dd 100644 --- a/src/client/localplayer.cpp +++ b/src/client/localplayer.cpp @@ -19,7 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "localplayer.h" #include -#include "event.h" +#include "mtevent.h" #include "collision.h" #include "nodedef.h" #include "settings.h" diff --git a/src/client/shader.cpp b/src/client/shader.cpp index 0aba7b118..fc0a72319 100644 --- a/src/client/shader.cpp +++ b/src/client/shader.cpp @@ -45,7 +45,12 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #endif #else +#ifndef __APPLE__ #include +#else +#define GL_SILENCE_DEPRECATION +#include +#endif #endif /* diff --git a/src/client/sound_openal.cpp b/src/client/sound_openal.cpp index c1c916e68..f4e61f93e 100644 --- a/src/client/sound_openal.cpp +++ b/src/client/sound_openal.cpp @@ -28,6 +28,7 @@ with this program; ifnot, write to the Free Software Foundation, Inc., #include //#include #elif defined(__APPLE__) + #define OPENAL_DEPRECATED #include #include //#include diff --git a/src/event.h b/src/mtevent.h similarity index 100% rename from src/event.h rename to src/mtevent.h From 0683bea283d456253de343f37720789382ece6b2 Mon Sep 17 00:00:00 2001 From: David CARLIER Date: Wed, 9 Sep 2020 18:12:58 +0100 Subject: [PATCH 061/266] Add NetBSD cpu affinity support code (#10378) --- src/threading/thread.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/threading/thread.cpp b/src/threading/thread.cpp index eb51516c6..e19e6ae60 100644 --- a/src/threading/thread.cpp +++ b/src/threading/thread.cpp @@ -33,6 +33,8 @@ DEALINGS IN THE SOFTWARE. #include #elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__) #include +#elif defined(__NetBSD__) + #include #elif defined(_MSC_VER) struct THREADNAME_INFO { DWORD dwType; // Must be 0x1000 @@ -285,7 +287,14 @@ bool Thread::bindToProcessor(unsigned int proc_number) CPU_SET(proc_number, &cpuset); return pthread_setaffinity_np(getThreadHandle(), sizeof(cpuset), &cpuset) == 0; +#elif defined(__NetBSD__) + cpuset_t *cpuset = cpuset_create(); + if (cpuset == NULL) + return false; + int r = pthread_setaffinity_np(getThreadHandle(), cpuset_size(cpuset), cpuset); + cpuset_destroy(cpuset); + return r == 0; #elif defined(__sun) || defined(sun) return processor_bind(P_LWPID, P_MYID, proc_number, NULL) == 0 From 3fb1f45301880a3aa901b752af1ecc912efe5915 Mon Sep 17 00:00:00 2001 From: Sebastien Marie Date: Thu, 10 Sep 2020 12:19:18 +0200 Subject: [PATCH 062/266] Remove Thread::kill() and related unittest (#10317) Closes: #6065 --- src/threading/thread.cpp | 54 ++++++++++++++------------------- src/threading/thread.h | 8 ----- src/unittest/test_threading.cpp | 25 --------------- 3 files changed, 22 insertions(+), 65 deletions(-) diff --git a/src/threading/thread.cpp b/src/threading/thread.cpp index e19e6ae60..5cfc60995 100644 --- a/src/threading/thread.cpp +++ b/src/threading/thread.cpp @@ -73,7 +73,28 @@ Thread::Thread(const std::string &name) : Thread::~Thread() { - kill(); + // kill the thread if running + if (!m_running) { + wait(); + } else { + + m_running = false; + +#if defined(_WIN32) + // See https://msdn.microsoft.com/en-us/library/hh920601.aspx#thread__native_handle_method + TerminateThread((HANDLE) m_thread_obj->native_handle(), 0); + CloseHandle((HANDLE) m_thread_obj->native_handle()); +#else + // We need to pthread_kill instead on Android since NDKv5's pthread + // implementation is incomplete. +# ifdef __ANDROID__ + pthread_kill(getThreadHandle(), SIGKILL); +# else + pthread_cancel(getThreadHandle()); +# endif + wait(); +#endif + } // Make sure start finished mutex is unlocked before it's destroyed if (m_start_finished_mutex.try_lock()) @@ -138,37 +159,6 @@ bool Thread::wait() } -bool Thread::kill() -{ - if (!m_running) { - wait(); - return false; - } - - m_running = false; - -#if defined(_WIN32) - // See https://msdn.microsoft.com/en-us/library/hh920601.aspx#thread__native_handle_method - TerminateThread((HANDLE) m_thread_obj->native_handle(), 0); - CloseHandle((HANDLE) m_thread_obj->native_handle()); -#else - // We need to pthread_kill instead on Android since NDKv5's pthread - // implementation is incomplete. -# ifdef __ANDROID__ - pthread_kill(getThreadHandle(), SIGKILL); -# else - pthread_cancel(getThreadHandle()); -# endif - wait(); -#endif - - m_retval = nullptr; - m_joinable = false; - m_request_stop = false; - - return true; -} - bool Thread::getReturnValue(void **ret) { diff --git a/src/threading/thread.h b/src/threading/thread.h index 3946335f5..45fb171da 100644 --- a/src/threading/thread.h +++ b/src/threading/thread.h @@ -74,14 +74,6 @@ public: */ bool stop(); - /* - * Immediately terminates the thread. - * This should be used with extreme caution, as the thread will not have - * any opportunity to release resources it may be holding (such as memory - * or locks). - */ - bool kill(); - /* * Waits for thread to finish. * Note: This does not stop a thread, you have to do this on your own. diff --git a/src/unittest/test_threading.cpp b/src/unittest/test_threading.cpp index 8d4d814fd..65ef7c02d 100644 --- a/src/unittest/test_threading.cpp +++ b/src/unittest/test_threading.cpp @@ -31,7 +31,6 @@ public: void runTests(IGameDef *gamedef); void testStartStopWait(); - void testThreadKill(); void testAtomicSemaphoreThread(); }; @@ -40,7 +39,6 @@ static TestThreading g_test_instance; void TestThreading::runTests(IGameDef *gamedef) { TEST(testStartStopWait); - TEST(testThreadKill); TEST(testAtomicSemaphoreThread); } @@ -111,29 +109,6 @@ void TestThreading::testStartStopWait() } -void TestThreading::testThreadKill() -{ - SimpleTestThread *thread = new SimpleTestThread(300); - - UASSERT(thread->start() == true); - - // kill()ing is quite violent, so let's make sure our victim is sleeping - // before we do this... so we don't corrupt the rest of the program's state - sleep_ms(100); - UASSERT(thread->kill() == true); - - // The state of the thread object should be reset if all went well - UASSERT(thread->isRunning() == false); - UASSERT(thread->start() == true); - UASSERT(thread->stop() == true); - UASSERT(thread->wait() == true); - - // kill() after already waiting should fail. - UASSERT(thread->kill() == false); - - delete thread; -} - class AtomicTestThread : public Thread { public: From fcff9f291103411af6d8f946fbb275b6fa0583b9 Mon Sep 17 00:00:00 2001 From: hecks <42101236+hecktest@users.noreply.github.com> Date: Mon, 14 Sep 2020 19:27:25 +0200 Subject: [PATCH 063/266] Remove "generate normal maps" feature (#10313) Erase all traces of normal "generation" from fragment shaders Remove the "feature" from the engine and default config Remove any leftover documentation of it --- builtin/mainmenu/tab_settings.lua | 22 +++------ builtin/settingtypes.txt | 14 +----- .../shaders/nodes_shader/opengl_fragment.glsl | 47 +------------------ .../object_shader/opengl_fragment.glsl | 31 ------------ minetest.conf.example | 17 +------ src/client/shader.cpp | 28 ----------- src/defaultsettings.cpp | 3 -- src/settings_translation_file.cpp | 8 +--- 8 files changed, 12 insertions(+), 158 deletions(-) diff --git a/builtin/mainmenu/tab_settings.lua b/builtin/mainmenu/tab_settings.lua index 02b15c81b..510346f8d 100644 --- a/builtin/mainmenu/tab_settings.lua +++ b/builtin/mainmenu/tab_settings.lua @@ -191,15 +191,13 @@ local function formspec(tabview, name, tabdata) .. dump(core.settings:get_bool("enable_bumpmapping")) .. "]" .. "checkbox[8.25,1;cb_tonemapping;" .. fgettext("Tone Mapping") .. ";" .. dump(core.settings:get_bool("tone_mapping")) .. "]" .. - "checkbox[8.25,1.5;cb_generate_normalmaps;" .. fgettext("Generate Normal Maps") .. ";" - .. dump(core.settings:get_bool("generate_normalmaps")) .. "]" .. - "checkbox[8.25,2;cb_parallax;" .. fgettext("Parallax Occlusion") .. ";" + "checkbox[8.25,1.5;cb_parallax;" .. fgettext("Parallax Occlusion") .. ";" .. dump(core.settings:get_bool("enable_parallax_occlusion")) .. "]" .. - "checkbox[8.25,2.5;cb_waving_water;" .. fgettext("Waving Liquids") .. ";" + "checkbox[8.25,2;cb_waving_water;" .. fgettext("Waving Liquids") .. ";" .. dump(core.settings:get_bool("enable_waving_water")) .. "]" .. - "checkbox[8.25,3;cb_waving_leaves;" .. fgettext("Waving Leaves") .. ";" + "checkbox[8.25,2.5;cb_waving_leaves;" .. fgettext("Waving Leaves") .. ";" .. dump(core.settings:get_bool("enable_waving_leaves")) .. "]" .. - "checkbox[8.25,3.5;cb_waving_plants;" .. fgettext("Waving Plants") .. ";" + "checkbox[8.25,3;cb_waving_plants;" .. fgettext("Waving Plants") .. ";" .. dump(core.settings:get_bool("enable_waving_plants")) .. "]" else tab_string = tab_string .. @@ -208,14 +206,12 @@ local function formspec(tabview, name, tabdata) "label[8.38,1.2;" .. core.colorize("#888888", fgettext("Tone Mapping")) .. "]" .. "label[8.38,1.7;" .. core.colorize("#888888", - fgettext("Generate Normal Maps")) .. "]" .. - "label[8.38,2.2;" .. core.colorize("#888888", fgettext("Parallax Occlusion")) .. "]" .. - "label[8.38,2.7;" .. core.colorize("#888888", + "label[8.38,2.2;" .. core.colorize("#888888", fgettext("Waving Liquids")) .. "]" .. - "label[8.38,3.2;" .. core.colorize("#888888", + "label[8.38,2.7;" .. core.colorize("#888888", fgettext("Waving Leaves")) .. "]" .. - "label[8.38,3.7;" .. core.colorize("#888888", + "label[8.38,3.2;" .. core.colorize("#888888", fgettext("Waving Plants")) .. "]" end @@ -275,10 +271,6 @@ local function handle_settings_buttons(this, fields, tabname, tabdata) core.settings:set("tone_mapping", fields["cb_tonemapping"]) return true end - if fields["cb_generate_normalmaps"] then - core.settings:set("generate_normalmaps", fields["cb_generate_normalmaps"]) - return true - end if fields["cb_parallax"] then core.settings:set("enable_parallax_occlusion", fields["cb_parallax"]) return true diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 39cc22d62..6d9c6f573 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -543,22 +543,10 @@ tone_mapping (Filmic tone mapping) bool false [***Bumpmapping] -# Enables bumpmapping for textures. Normalmaps need to be supplied by the texture pack -# or need to be auto-generated. +# Enables bumpmapping for textures. Normalmaps need to be supplied by the texture pack. # Requires shaders to be enabled. enable_bumpmapping (Bumpmapping) bool false -# Enables on the fly normalmap generation (Emboss effect). -# Requires bumpmapping to be enabled. -generate_normalmaps (Generate normalmaps) bool false - -# Strength of generated normalmaps. -normalmaps_strength (Normalmaps strength) float 0.6 - -# Defines sampling step of texture. -# A higher value results in smoother normal maps. -normalmaps_smooth (Normalmaps sampling) int 0 0 2 - [***Parallax Occlusion] # Enables parallax occlusion mapping. diff --git a/client/shaders/nodes_shader/opengl_fragment.glsl b/client/shaders/nodes_shader/opengl_fragment.glsl index 7213612bd..437b325d3 100644 --- a/client/shaders/nodes_shader/opengl_fragment.glsl +++ b/client/shaders/nodes_shader/opengl_fragment.glsl @@ -71,16 +71,6 @@ void get_texture_flags() } } -float intensity(vec3 color) -{ - return (color.r + color.g + color.b) / 3.0; -} - -float get_rgb_height(vec2 uv) -{ - return intensity(texture2D(baseTexture, uv).rgb); -} - vec4 get_normal_map(vec2 uv) { vec4 bump = texture2D(normalTexture, uv).rgba; @@ -115,19 +105,6 @@ float find_intersection(vec2 dp, vec2 ds) return best_depth; } -float find_intersectionRGB(vec2 dp, vec2 ds) -{ - const float depth_step = 1.0 / 24.0; - float depth = 1.0; - for (int i = 0 ; i < 24 ; i++) { - float h = get_rgb_height(dp + ds * depth); - if (h >= depth) - break; - depth -= depth_step; - } - return depth; -} - void main(void) { vec3 color; @@ -149,21 +126,17 @@ void main(void) float h = normal.a * scale - bias; uv += h * normal.z * eyeRay; } + } #endif - #if PARALLAX_OCCLUSION_MODE == 1 // Relief mapping if (normalTexturePresent && area_enable_parallax > 0.0) { vec2 ds = eyeRay * PARALLAX_OCCLUSION_SCALE; float dist = find_intersection(uv, ds); uv += dist * ds; -#endif - } else if (GENERATE_NORMALMAPS == 1 && area_enable_parallax > 0.0) { - vec2 ds = eyeRay * PARALLAX_OCCLUSION_SCALE; - float dist = find_intersectionRGB(uv, ds); - uv += dist * ds; } #endif +#endif #if USE_NORMALMAPS == 1 if (normalTexturePresent) { @@ -172,22 +145,6 @@ void main(void) } #endif -#if GENERATE_NORMALMAPS == 1 - if (normalTexturePresent == false) { - float tl = get_rgb_height(vec2(uv.x - SAMPLE_STEP, uv.y + SAMPLE_STEP)); - float t = get_rgb_height(vec2(uv.x - SAMPLE_STEP, uv.y - SAMPLE_STEP)); - float tr = get_rgb_height(vec2(uv.x + SAMPLE_STEP, uv.y + SAMPLE_STEP)); - float r = get_rgb_height(vec2(uv.x + SAMPLE_STEP, uv.y)); - float br = get_rgb_height(vec2(uv.x + SAMPLE_STEP, uv.y - SAMPLE_STEP)); - float b = get_rgb_height(vec2(uv.x, uv.y - SAMPLE_STEP)); - float bl = get_rgb_height(vec2(uv.x -SAMPLE_STEP, uv.y - SAMPLE_STEP)); - float l = get_rgb_height(vec2(uv.x - SAMPLE_STEP, uv.y)); - float dX = (tr + 2.0 * r + br) - (tl + 2.0 * l + bl); - float dY = (bl + 2.0 * b + br) - (tl + 2.0 * t + tr); - bump = vec4(normalize(vec3 (dX, dY, NORMALMAPS_STRENGTH)), 1.0); - use_normalmap = true; - } -#endif vec4 base = texture2D(baseTexture, uv).rgba; #ifdef USE_DISCARD diff --git a/client/shaders/object_shader/opengl_fragment.glsl b/client/shaders/object_shader/opengl_fragment.glsl index 2d33ca439..a8a43e1e2 100644 --- a/client/shaders/object_shader/opengl_fragment.glsl +++ b/client/shaders/object_shader/opengl_fragment.glsl @@ -74,20 +74,6 @@ void get_texture_flags() } } -float intensity(vec3 color) -{ - return (color.r + color.g + color.b) / 3.0; -} - -float get_rgb_height(vec2 uv) -{ - if (texSeamless) { - return intensity(texture2D(baseTexture, uv).rgb); - } else { - return intensity(texture2D(baseTexture, clamp(uv, 0.0, 0.999)).rgb); - } -} - vec4 get_normal_map(vec2 uv) { vec4 bump = texture2D(normalTexture, uv).rgba; @@ -110,23 +96,6 @@ void main(void) } #endif -#if GENERATE_NORMALMAPS == 1 - if (normalTexturePresent == false) { - float tl = get_rgb_height(vec2(uv.x - SAMPLE_STEP, uv.y + SAMPLE_STEP)); - float t = get_rgb_height(vec2(uv.x - SAMPLE_STEP, uv.y - SAMPLE_STEP)); - float tr = get_rgb_height(vec2(uv.x + SAMPLE_STEP, uv.y + SAMPLE_STEP)); - float r = get_rgb_height(vec2(uv.x + SAMPLE_STEP, uv.y)); - float br = get_rgb_height(vec2(uv.x + SAMPLE_STEP, uv.y - SAMPLE_STEP)); - float b = get_rgb_height(vec2(uv.x, uv.y - SAMPLE_STEP)); - float bl = get_rgb_height(vec2(uv.x -SAMPLE_STEP, uv.y - SAMPLE_STEP)); - float l = get_rgb_height(vec2(uv.x - SAMPLE_STEP, uv.y)); - float dX = (tr + 2.0 * r + br) - (tl + 2.0 * l + bl); - float dY = (bl + 2.0 * b + br) - (tl + 2.0 * t + tr); - bump = vec4(normalize(vec3 (dX, dY, NORMALMAPS_STRENGTH)), 1.0); - use_normalmap = true; - } -#endif - vec4 base = texture2D(baseTexture, uv).rgba; #ifdef USE_DISCARD diff --git a/minetest.conf.example b/minetest.conf.example index 520125713..6b315b6ea 100644 --- a/minetest.conf.example +++ b/minetest.conf.example @@ -604,26 +604,11 @@ #### Bumpmapping -# Enables bumpmapping for textures. Normalmaps need to be supplied by the texture pack -# or need to be auto-generated. +# Enables bumpmapping for textures. Normalmaps need to be supplied by the texture pack. # Requires shaders to be enabled. # type: bool # enable_bumpmapping = false -# Enables on the fly normalmap generation (Emboss effect). -# Requires bumpmapping to be enabled. -# type: bool -# generate_normalmaps = false - -# Strength of generated normalmaps. -# type: float -# normalmaps_strength = 0.6 - -# Defines sampling step of texture. -# A higher value results in smoother normal maps. -# type: int min: 0 max: 2 -# normalmaps_smooth = 0 - #### Parallax Occlusion # Enables parallax occlusion mapping. diff --git a/src/client/shader.cpp b/src/client/shader.cpp index fc0a72319..c5fe5dfe0 100644 --- a/src/client/shader.cpp +++ b/src/client/shader.cpp @@ -688,34 +688,6 @@ ShaderInfo generate_shader(const std::string &name, u8 material_type, u8 drawtyp shaders_header += itos(drawtype); shaders_header += "\n"; - if (g_settings->getBool("generate_normalmaps")) { - shaders_header += "#define GENERATE_NORMALMAPS 1\n"; - } else { - shaders_header += "#define GENERATE_NORMALMAPS 0\n"; - } - shaders_header += "#define NORMALMAPS_STRENGTH "; - shaders_header += ftos(g_settings->getFloat("normalmaps_strength")); - shaders_header += "\n"; - float sample_step; - int smooth = (int)g_settings->getFloat("normalmaps_smooth"); - switch (smooth){ - case 0: - sample_step = 0.0078125; // 1.0 / 128.0 - break; - case 1: - sample_step = 0.00390625; // 1.0 / 256.0 - break; - case 2: - sample_step = 0.001953125; // 1.0 / 512.0 - break; - default: - sample_step = 0.0078125; - break; - } - shaders_header += "#define SAMPLE_STEP "; - shaders_header += ftos(sample_step); - shaders_header += "\n"; - if (g_settings->getBool("enable_bumpmapping")) shaders_header += "#define ENABLE_BUMPMAPPING\n"; diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index d2115c959..3a0b88dc2 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -253,9 +253,6 @@ void set_default_settings(Settings *settings) settings->setDefault("tone_mapping", "false"); settings->setDefault("enable_bumpmapping", "false"); settings->setDefault("enable_parallax_occlusion", "false"); - settings->setDefault("generate_normalmaps", "false"); - settings->setDefault("normalmaps_strength", "0.6"); - settings->setDefault("normalmaps_smooth", "1"); settings->setDefault("parallax_occlusion_mode", "1"); settings->setDefault("parallax_occlusion_iterations", "4"); settings->setDefault("parallax_occlusion_scale", "0.08"); diff --git a/src/settings_translation_file.cpp b/src/settings_translation_file.cpp index febfbb9d3..0cd772337 100644 --- a/src/settings_translation_file.cpp +++ b/src/settings_translation_file.cpp @@ -242,13 +242,7 @@ fake_function() { gettext("Enables Hable's 'Uncharted 2' filmic tone mapping.\nSimulates the tone curve of photographic film and how this approximates the\nappearance of high dynamic range images. Mid-range contrast is slightly\nenhanced, highlights and shadows are gradually compressed."); gettext("Bumpmapping"); gettext("Bumpmapping"); - gettext("Enables bumpmapping for textures. Normalmaps need to be supplied by the texture pack\nor need to be auto-generated.\nRequires shaders to be enabled."); - gettext("Generate normalmaps"); - gettext("Enables on the fly normalmap generation (Emboss effect).\nRequires bumpmapping to be enabled."); - gettext("Normalmaps strength"); - gettext("Strength of generated normalmaps."); - gettext("Normalmaps sampling"); - gettext("Defines sampling step of texture.\nA higher value results in smoother normal maps."); + gettext("Enables bumpmapping for textures. Normalmaps need to be supplied by the texture pack.\nRequires shaders to be enabled."); gettext("Parallax Occlusion"); gettext("Parallax occlusion"); gettext("Enables parallax occlusion mapping.\nRequires shaders to be enabled."); From c8303f790ccb03857d031ecb73de7f9f59a6ceba Mon Sep 17 00:00:00 2001 From: Zughy <63455151+Zughy@users.noreply.github.com> Date: Wed, 16 Sep 2020 15:47:20 +0200 Subject: [PATCH 064/266] lua_api.txt: author, release and title fields when downloading from ContentDB (#10129) Co-authored-by: Marco <4279489-marco_a@users.noreply.gitlab.com> Co-authored-by: rubenwardy --- doc/lua_api.txt | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 86627c19e..a1baadf6d 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -62,12 +62,12 @@ Where `` is unique to each game. The game directory can contain the following files: * `game.conf`, with the following keys: - * `name`: Required, human readable name e.g. `name = Minetest` + * `name`: Required, a human readable title to address the game, e.g. `name = Minetest`. * `description`: Short description to be shown in the content tab * `allowed_mapgens = ` e.g. `allowed_mapgens = v5,v6,flat` - Mapgens not in this list are removed from the list of mapgens for - the game. + Mapgens not in this list are removed from the list of mapgens for the + game. If not specified, all mapgens are allowed. * `disallowed_mapgens = ` e.g. `disallowed_mapgens = v5,v6,flat` @@ -79,6 +79,10 @@ The game directory can contain the following files: e.g. `disallowed_mapgen_settings = mgv5_spflags` These settings are hidden for this game in the world creation dialog and game start menu. + * `author`: The author of the game. It only appears when downloaded from + ContentDB. + * `release`: Ignore this: Should only ever be set by ContentDB, as it is + an internal ID used to track versions. * `minetest.conf`: Used to set default settings when running this game. * `settingtypes.txt`: @@ -134,9 +138,15 @@ Mods can be put in a subdirectory, if the parent directory, which otherwise should be a mod, contains a file named `modpack.conf`. The file is a key-value store of modpack details. -* `name`: The modpack name. +* `name`: The modpack name. Allows Minetest to determine the modpack name even + if the folder is wrongly named. * `description`: Description of mod to be shown in the Mods tab of the main menu. +* `author`: The author of the modpack. It only appears when downloaded from + ContentDB. +* `release`: Ignore this: Should only ever be set by ContentDB, as it is an + internal ID used to track versions. +* `title`: A human-readable title to address the modpack. Note: to support 0.4.x, please also create an empty modpack.txt file. @@ -181,6 +191,11 @@ A `Settings` file that provides meta information about the mod. loaded before this mod. * `optional_depends`: A comma separated list of optional dependencies. Like a dependency, but no error if the mod doesn't exist. +* `author`: The author of the mod. It only appears when downloaded from + ContentDB. +* `release`: Ignore this: Should only ever be set by ContentDB, as it is an + internal ID used to track versions. +* `title`: A human-readable title to address the mod. Note: to support 0.4.x, please also provide depends.txt. From 9ec75d77651c333eca3c5b46a3a56c8353fed464 Mon Sep 17 00:00:00 2001 From: rubenwardy Date: Wed, 16 Sep 2020 14:51:11 +0100 Subject: [PATCH 065/266] Clean up server-side translations, remove global variable (#10075) --- src/client/client.cpp | 9 ++---- src/filesys.cpp | 15 ++++++++++ src/filesys.h | 2 ++ src/script/lua_api/l_env.cpp | 6 ++-- src/server.cpp | 53 +++++++++++++++--------------------- src/server.h | 7 +++-- src/serverlist.cpp | 10 +------ src/translation.cpp | 8 ------ src/translation.h | 5 ---- 9 files changed, 50 insertions(+), 65 deletions(-) diff --git a/src/client/client.cpp b/src/client/client.cpp index 745cce900..d6e529c40 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -238,18 +238,13 @@ void Client::scanModSubfolder(const std::string &mod_name, const std::string &mo infostream << "Client::scanModSubfolder(): Loading \"" << real_path << "\" as \"" << vfs_path << "\"." << std::endl; - std::ifstream is(real_path, std::ios::binary | std::ios::ate); - if(!is.good()) { + std::string contents; + if (!fs::ReadFile(real_path, contents)) { errorstream << "Client::scanModSubfolder(): Can't read file \"" << real_path << "\"." << std::endl; continue; } - auto size = is.tellg(); - std::string contents(size, '\0'); - is.seekg(0); - is.read(&contents[0], size); - infostream << " size: " << size << " bytes" << std::endl; m_mod_vfs.emplace(vfs_path, contents); } } diff --git a/src/filesys.cpp b/src/filesys.cpp index 0bc351669..2470b1b64 100644 --- a/src/filesys.cpp +++ b/src/filesys.cpp @@ -750,6 +750,21 @@ bool safeWriteToFile(const std::string &path, const std::string &content) return true; } +bool ReadFile(const std::string &path, std::string &out) +{ + std::ifstream is(path, std::ios::binary | std::ios::ate); + if (!is.good()) { + return false; + } + + auto size = is.tellg(); + out.resize(size); + is.seekg(0); + is.read(&out[0], size); + + return true; +} + bool Rename(const std::string &from, const std::string &to) { return rename(from.c_str(), to.c_str()) == 0; diff --git a/src/filesys.h b/src/filesys.h index 09f129aa3..b2c800c55 100644 --- a/src/filesys.h +++ b/src/filesys.h @@ -128,6 +128,8 @@ const char *GetFilenameFromPath(const char *path); bool safeWriteToFile(const std::string &path, const std::string &content); +bool ReadFile(const std::string &path, std::string &out); + bool Rename(const std::string &from, const std::string &to); } // namespace fs diff --git a/src/script/lua_api/l_env.cpp b/src/script/lua_api/l_env.cpp index b2bc8c5f6..138e88ae8 100644 --- a/src/script/lua_api/l_env.cpp +++ b/src/script/lua_api/l_env.cpp @@ -1340,9 +1340,9 @@ int ModApiEnvMod::l_get_translated_string(lua_State * L) GET_ENV_PTR; std::string lang_code = luaL_checkstring(L, 1); std::string string = luaL_checkstring(L, 2); - getServer(L)->loadTranslationLanguage(lang_code); - string = wide_to_utf8(translate_string(utf8_to_wide(string), - &(*g_server_translations)[lang_code])); + + auto *translations = getServer(L)->getTranslationLanguage(lang_code); + string = wide_to_utf8(translate_string(utf8_to_wide(string), translations)); lua_pushstring(L, string.c_str()); return 1; } diff --git a/src/server.cpp b/src/server.cpp index 7b3978462..b8a99f6ae 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -2451,31 +2451,14 @@ bool Server::addMediaFile(const std::string &filename, // Ok, attempt to load the file and add to cache // Read data - std::ifstream fis(filepath.c_str(), std::ios_base::binary); - if (!fis.good()) { - errorstream << "Server::addMediaFile(): Could not open \"" - << filename << "\" for reading" << std::endl; - return false; - } std::string filedata; - bool bad = false; - for (;;) { - char buf[1024]; - fis.read(buf, sizeof(buf)); - std::streamsize len = fis.gcount(); - filedata.append(buf, len); - if (fis.eof()) - break; - if (!fis.good()) { - bad = true; - break; - } - } - if (bad) { - errorstream << "Server::addMediaFile(): Failed to read \"" - << filename << "\"" << std::endl; + if (!fs::ReadFile(filepath, filedata)) { + errorstream << "Server::addMediaFile(): Failed to open \"" + << filename << "\" for reading" << std::endl; return false; - } else if (filedata.empty()) { + } + + if (filedata.empty()) { errorstream << "Server::addMediaFile(): Empty file \"" << filepath << "\"" << std::endl; return false; @@ -3890,19 +3873,27 @@ void Server::broadcastModChannelMessage(const std::string &channel, } } -void Server::loadTranslationLanguage(const std::string &lang_code) +Translations *Server::getTranslationLanguage(const std::string &lang_code) { - if (g_server_translations->count(lang_code)) - return; // Already loaded + if (lang_code.empty()) + return nullptr; + + auto it = server_translations.find(lang_code); + if (it != server_translations.end()) + return &it->second; // Already loaded + + // [] will create an entry + auto *translations = &server_translations[lang_code]; std::string suffix = "." + lang_code + ".tr"; for (const auto &i : m_media) { if (str_ends_with(i.first, suffix)) { - std::ifstream t(i.second.path); - std::string data((std::istreambuf_iterator(t)), - std::istreambuf_iterator()); - - (*g_server_translations)[lang_code].loadTranslation(data); + std::string data; + if (fs::ReadFile(i.second.path, data)) { + translations->loadTranslation(data); + } } } + + return translations; } diff --git a/src/server.h b/src/server.h index be6f60abc..258d75eaf 100644 --- a/src/server.h +++ b/src/server.h @@ -38,6 +38,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "serverenvironment.h" #include "clientiface.h" #include "chatmessage.h" +#include "translation.h" #include #include #include @@ -343,8 +344,8 @@ public: // Send block to specific player only bool SendBlock(session_t peer_id, const v3s16 &blockpos); - // Load translations for a language - void loadTranslationLanguage(const std::string &lang_code); + // Get or load translations for a language + Translations *getTranslationLanguage(const std::string &lang_code); // Bind address Address m_bind_addr; @@ -557,6 +558,8 @@ private: // Mods std::unique_ptr m_modmgr; + std::unordered_map server_translations; + /* Threads */ diff --git a/src/serverlist.cpp b/src/serverlist.cpp index 2f6ab2e61..c7fe2d888 100644 --- a/src/serverlist.cpp +++ b/src/serverlist.cpp @@ -52,15 +52,7 @@ std::vector getLocal() { std::string path = ServerList::getFilePath(); std::string liststring; - if (fs::PathExists(path)) { - std::ifstream istream(path.c_str()); - if (istream.is_open()) { - std::ostringstream ostream; - ostream << istream.rdbuf(); - liststring = ostream.str(); - istream.close(); - } - } + fs::ReadFile(path, liststring); return deSerialize(liststring); } diff --git a/src/translation.cpp b/src/translation.cpp index 8bbaee0a3..82e813a5d 100644 --- a/src/translation.cpp +++ b/src/translation.cpp @@ -29,14 +29,6 @@ Translations client_translations; Translations *g_client_translations = &client_translations; #endif -// Per language server translations -std::unordered_map server_translations; -std::unordered_map *g_server_translations = &server_translations; - -Translations::~Translations() -{ - clear(); -} void Translations::clear() { diff --git a/src/translation.h b/src/translation.h index 71423b15e..f1a336fca 100644 --- a/src/translation.h +++ b/src/translation.h @@ -23,7 +23,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include class Translations; -extern std::unordered_map *g_server_translations; #ifndef SERVER extern Translations *g_client_translations; #endif @@ -31,10 +30,6 @@ extern Translations *g_client_translations; class Translations { public: - Translations() = default; - - ~Translations(); - void loadTranslation(const std::string &data); void clear(); const std::wstring &getTranslation( From 49117de47658cd6c885f88010c9328f8ce3e6e93 Mon Sep 17 00:00:00 2001 From: Lars Date: Sun, 20 Sep 2020 12:51:12 -0700 Subject: [PATCH 066/266] Record player existence in dymmy database. --- src/database/database-dummy.cpp | 23 +++++++++++++++++++++++ src/database/database-dummy.h | 9 +++++---- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/database/database-dummy.cpp b/src/database/database-dummy.cpp index a3d8cd579..b56f341c5 100644 --- a/src/database/database-dummy.cpp +++ b/src/database/database-dummy.cpp @@ -22,6 +22,7 @@ Dummy database class */ #include "database-dummy.h" +#include "remoteplayer.h" bool Database_Dummy::saveBlock(const v3s16 &pos, const std::string &data) @@ -57,3 +58,25 @@ void Database_Dummy::listAllLoadableBlocks(std::vector &dst) } } +void Database_Dummy::savePlayer(RemotePlayer *player) +{ + m_player_database.insert(player->getName()); +} + +bool Database_Dummy::loadPlayer(RemotePlayer *player, PlayerSAO *sao) +{ + return m_player_database.find(player->getName()) != m_player_database.end(); +} + +bool Database_Dummy::removePlayer(const std::string &name) +{ + m_player_database.erase(name); + return true; +} + +void Database_Dummy::listPlayers(std::vector &res) +{ + for (const auto &player : m_player_database) { + res.emplace_back(player); + } +} diff --git a/src/database/database-dummy.h b/src/database/database-dummy.h index 2d87d58f6..b69919f84 100644 --- a/src/database/database-dummy.h +++ b/src/database/database-dummy.h @@ -32,14 +32,15 @@ public: bool deleteBlock(const v3s16 &pos); void listAllLoadableBlocks(std::vector &dst); - void savePlayer(RemotePlayer *player) {} - bool loadPlayer(RemotePlayer *player, PlayerSAO *sao) { return true; } - bool removePlayer(const std::string &name) { return true; } - void listPlayers(std::vector &res) {} + void savePlayer(RemotePlayer *player); + bool loadPlayer(RemotePlayer *player, PlayerSAO *sao); + bool removePlayer(const std::string &name); + void listPlayers(std::vector &res); void beginSave() {} void endSave() {} private: std::map m_database; + std::set m_player_database; }; From e7f33ee2f1c57b2b5c48d1a54a5f9e4c72a3275c Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Mon, 21 Sep 2020 19:10:44 +0200 Subject: [PATCH 067/266] Settings: Fix crash on exit due to group double-free --- src/settings.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/settings.cpp b/src/settings.cpp index 55404319e..473a216bf 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -824,13 +824,21 @@ bool Settings::setDefault(const std::string &name, const std::string &value) bool Settings::setGroup(const std::string &name, Settings *group) { - return setEntry(name, &group, true, false); + // Settings must own the group pointer + // avoid double-free by copying the source + Settings *copy = new Settings(); + *copy = *group; + return setEntry(name, ©, true, false); } bool Settings::setGroupDefault(const std::string &name, Settings *group) { - return setEntry(name, &group, true, true); + // Settings must own the group pointer + // avoid double-free by copying the source + Settings *copy = new Settings(); + *copy = *group; + return setEntry(name, ©, true, true); } From 55e2dd911b16a70ee976e067cf34a48922db9dcb Mon Sep 17 00:00:00 2001 From: Wuzzy Date: Tue, 22 Sep 2020 18:38:33 +0200 Subject: [PATCH 068/266] Fix chat/infotext overlap if many chat lines (#10399) Moves the infotext depending on the value of the recent_chat_messages value + 2 additional lines to account for the 2 debug mode lines + 1 additional line as "buffer" for better readability if chat is full. --- src/client/gameui.cpp | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/client/gameui.cpp b/src/client/gameui.cpp index 81c268e44..75e7d15b9 100644 --- a/src/client/gameui.cpp +++ b/src/client/gameui.cpp @@ -61,17 +61,6 @@ void GameUI::init() m_guitext2 = gui::StaticText::add(guienv, L"", core::rect(0, 0, 0, 0), false, false, guiroot); - // At the middle of the screen - // Object infos are shown in this - m_guitext_info = gui::StaticText::add(guienv, L"", - core::rect(0, 0, 400, g_fontengine->getTextHeight() * 5 + 5) - + v2s32(100, 200), false, true, guiroot); - - // Status text (displays info when showing and hiding GUI stuff, etc.) - m_guitext_status = gui::StaticText::add(guienv, L"", - core::rect(0, 0, 0, 0), false, false, guiroot); - m_guitext_status->setVisible(false); - // Chat text m_guitext_chat = gui::StaticText::add(guienv, L"", core::rect(0, 0, 0, 0), //false, false); // Disable word wrap as of now @@ -82,6 +71,20 @@ void GameUI::init() chat_font_size, FM_Unspecified)); } + // At the middle of the screen + // Object infos are shown in this + u32 chat_font_height = m_guitext_chat->getActiveFont()->getDimension(L"Ay").Height; + m_guitext_info = gui::StaticText::add(guienv, L"", + core::rect(0, 0, 400, g_fontengine->getTextHeight() * 5 + 5) + + v2s32(100, chat_font_height * + (g_settings->getU16("recent_chat_messages") + 3)), + false, true, guiroot); + + // Status text (displays info when showing and hiding GUI stuff, etc.) + m_guitext_status = gui::StaticText::add(guienv, L"", + core::rect(0, 0, 0, 0), false, false, guiroot); + m_guitext_status->setVisible(false); + // Profiler text (size is updated when text is updated) m_guitext_profiler = gui::StaticText::add(guienv, L"", core::rect(0, 0, 0, 0), false, false, guiroot); From add68369a59af90f8b4203b53695521c6d657d6b Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Tue, 22 Sep 2020 18:40:34 +0200 Subject: [PATCH 069/266] Settings: Fix unittest memory leak, change input types --- src/settings.cpp | 10 +++++----- src/settings.h | 4 ++-- src/unittest/test_settings.cpp | 16 ++++++++-------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/settings.cpp b/src/settings.cpp index 473a216bf..28b72c4e3 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -822,22 +822,22 @@ bool Settings::setDefault(const std::string &name, const std::string &value) } -bool Settings::setGroup(const std::string &name, Settings *group) +bool Settings::setGroup(const std::string &name, const Settings &group) { // Settings must own the group pointer // avoid double-free by copying the source Settings *copy = new Settings(); - *copy = *group; + *copy = group; return setEntry(name, ©, true, false); } -bool Settings::setGroupDefault(const std::string &name, Settings *group) +bool Settings::setGroupDefault(const std::string &name, const Settings &group) { // Settings must own the group pointer // avoid double-free by copying the source Settings *copy = new Settings(); - *copy = *group; + *copy = group; return setEntry(name, ©, true, true); } @@ -1060,7 +1060,7 @@ void Settings::overrideDefaults(Settings *other) { for (const auto &setting : other->m_settings) { if (setting.second.is_group) { - setGroupDefault(setting.first, setting.second.group); + setGroupDefault(setting.first, *setting.second.group); continue; } const FlagDesc *flagdesc = getFlagDescFallback(setting.first); diff --git a/src/settings.h b/src/settings.h index 0c9a155db..ccc252049 100644 --- a/src/settings.h +++ b/src/settings.h @@ -192,8 +192,8 @@ public: bool set_group, bool set_default); bool set(const std::string &name, const std::string &value); bool setDefault(const std::string &name, const std::string &value); - bool setGroup(const std::string &name, Settings *group); - bool setGroupDefault(const std::string &name, Settings *group); + bool setGroup(const std::string &name, const Settings &group); + bool setGroupDefault(const std::string &name, const Settings &group); bool setBool(const std::string &name, bool value); bool setS16(const std::string &name, s16 value); bool setU16(const std::string &name, u16 value); diff --git a/src/unittest/test_settings.cpp b/src/unittest/test_settings.cpp index aa56f3e06..f91ba5b67 100644 --- a/src/unittest/test_settings.cpp +++ b/src/unittest/test_settings.cpp @@ -149,15 +149,15 @@ void TestSettings::testAllSettings() UASSERT(group->getS16("a") == 5); UASSERT(fabs(group->getFloat("bb") - 2.5) < 0.001); - Settings *group3 = new Settings; - group3->set("cat", "meow"); - group3->set("dog", "woof"); + Settings group3; + group3.set("cat", "meow"); + group3.set("dog", "woof"); - Settings *group2 = new Settings; - group2->setS16("num_apples", 4); - group2->setS16("num_oranges", 53); - group2->setGroup("animals", group3); - group2->set("animals", "cute"); //destroys group 3 + Settings group2; + group2.setS16("num_apples", 4); + group2.setS16("num_oranges", 53); + group2.setGroup("animals", group3); + group2.set("animals", "cute"); //destroys group 3 s.setGroup("groupy_thing", group2); // Test set failure conditions From 34e3ede8eeb05e193e64ba3d055fc67959d87d86 Mon Sep 17 00:00:00 2001 From: tenplus1 Date: Wed, 23 Sep 2020 18:11:56 +0100 Subject: [PATCH 070/266] Ability to remove minetest.after once set (#10103) --- builtin/common/after.lua | 6 ++++-- doc/lua_api.txt | 5 ++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/builtin/common/after.lua b/builtin/common/after.lua index b314711c9..e20f292f0 100644 --- a/builtin/common/after.lua +++ b/builtin/common/after.lua @@ -31,11 +31,13 @@ function core.after(after, func, ...) assert(tonumber(after) and type(func) == "function", "Invalid minetest.after invocation") local expire = time + after - jobs[#jobs + 1] = { + local new_job = { func = func, expire = expire, arg = {...}, - mod_origin = core.get_last_run_mod() + mod_origin = core.get_last_run_mod(), } + jobs[#jobs + 1] = new_job time_next = math.min(time_next, expire) + return { cancel = function() new_job.func = function() end end } end diff --git a/doc/lua_api.txt b/doc/lua_api.txt index a1baadf6d..bd845aad3 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -5336,10 +5336,13 @@ Sounds Timing ------ -* `minetest.after(time, func, ...)` +* `minetest.after(time, func, ...)` : returns job table to use as below. * Call the function `func` after `time` seconds, may be fractional * Optional: Variable number of arguments that are passed to `func` +* `job:cancel()` + * Cancels the job function from being called + Server ------ From 787561b29afdbc78769f68c2f5c4f2cff1b32340 Mon Sep 17 00:00:00 2001 From: Vincent Robinson Date: Wed, 23 Sep 2020 10:12:20 -0700 Subject: [PATCH 071/266] Replace MyEventReceiver KeyList with std::unordered_set (#10419) --- src/client/inputhandler.cpp | 48 ++++++++-------- src/client/inputhandler.h | 108 +++++++----------------------------- src/client/keycode.h | 19 +++++++ 3 files changed, 64 insertions(+), 111 deletions(-) diff --git a/src/client/inputhandler.cpp b/src/client/inputhandler.cpp index 608a405a8..ee3e37ae9 100644 --- a/src/client/inputhandler.cpp +++ b/src/client/inputhandler.cpp @@ -112,7 +112,7 @@ bool MyEventReceiver::OnEvent(const SEvent &event) // Remember whether each key is down or up if (event.EventType == irr::EET_KEY_INPUT_EVENT) { const KeyPress &keyCode = event.KeyInput; - if (keysListenedFor[keyCode]) { + if (keysListenedFor.count(keyCode)) { // If the key is being held down then the OS may // send a continuous stream of keydown events. // In this case, we don't want to let this @@ -120,15 +120,15 @@ bool MyEventReceiver::OnEvent(const SEvent &event) // certain actions to repeat constantly. if (event.KeyInput.PressedDown) { if (!IsKeyDown(keyCode)) { - keyWasDown.set(keyCode); - keyWasPressed.set(keyCode); + keyWasDown.insert(keyCode); + keyWasPressed.insert(keyCode); } - keyIsDown.set(keyCode); + keyIsDown.insert(keyCode); } else { if (IsKeyDown(keyCode)) - keyWasReleased.set(keyCode); + keyWasReleased.insert(keyCode); - keyIsDown.unset(keyCode); + keyIsDown.erase(keyCode); } return true; @@ -153,36 +153,36 @@ bool MyEventReceiver::OnEvent(const SEvent &event) switch (event.MouseInput.Event) { case EMIE_LMOUSE_PRESSED_DOWN: key = "KEY_LBUTTON"; - keyIsDown.set(key); - keyWasDown.set(key); - keyWasPressed.set(key); + keyIsDown.insert(key); + keyWasDown.insert(key); + keyWasPressed.insert(key); break; case EMIE_MMOUSE_PRESSED_DOWN: key = "KEY_MBUTTON"; - keyIsDown.set(key); - keyWasDown.set(key); - keyWasPressed.set(key); + keyIsDown.insert(key); + keyWasDown.insert(key); + keyWasPressed.insert(key); break; case EMIE_RMOUSE_PRESSED_DOWN: key = "KEY_RBUTTON"; - keyIsDown.set(key); - keyWasDown.set(key); - keyWasPressed.set(key); + keyIsDown.insert(key); + keyWasDown.insert(key); + keyWasPressed.insert(key); break; case EMIE_LMOUSE_LEFT_UP: key = "KEY_LBUTTON"; - keyIsDown.unset(key); - keyWasReleased.set(key); + keyIsDown.erase(key); + keyWasReleased.insert(key); break; case EMIE_MMOUSE_LEFT_UP: key = "KEY_MBUTTON"; - keyIsDown.unset(key); - keyWasReleased.set(key); + keyIsDown.erase(key); + keyWasReleased.insert(key); break; case EMIE_RMOUSE_LEFT_UP: key = "KEY_RBUTTON"; - keyIsDown.unset(key); - keyWasReleased.set(key); + keyIsDown.erase(key); + keyWasReleased.insert(key); break; case EMIE_MOUSE_WHEEL: mouse_wheel += event.MouseInput.Wheel; @@ -235,7 +235,11 @@ void RandomInputHandler::step(float dtime) i.counter -= dtime; if (i.counter < 0.0) { i.counter = 0.1 * Rand(1, i.time_max); - keydown.toggle(getKeySetting(i.key.c_str())); + KeyPress k = getKeySetting(i.key.c_str()); + if (keydown.count(k)) + keydown.erase(k); + else + keydown.insert(k); } } { diff --git a/src/client/inputhandler.h b/src/client/inputhandler.h index def147a82..885f34e05 100644 --- a/src/client/inputhandler.h +++ b/src/client/inputhandler.h @@ -21,9 +21,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irrlichttypes_extrabloated.h" #include "joystick_controller.h" -#include #include "keycode.h" #include "renderingengine.h" +#include #ifdef HAVE_TOUCHSCREENGUI #include "gui/touchscreengui.h" @@ -61,98 +61,32 @@ struct KeyCache InputHandler *handler; }; -class KeyList : private std::list -{ - typedef std::list super; - typedef super::iterator iterator; - typedef super::const_iterator const_iterator; - - virtual const_iterator find(const KeyPress &key) const - { - const_iterator f(begin()); - const_iterator e(end()); - - while (f != e) { - if (*f == key) - return f; - - ++f; - } - - return e; - } - - virtual iterator find(const KeyPress &key) - { - iterator f(begin()); - iterator e(end()); - - while (f != e) { - if (*f == key) - return f; - - ++f; - } - - return e; - } - -public: - void clear() { super::clear(); } - - void set(const KeyPress &key) - { - if (find(key) == end()) - push_back(key); - } - - void unset(const KeyPress &key) - { - iterator p(find(key)); - - if (p != end()) - erase(p); - } - - void toggle(const KeyPress &key) - { - iterator p(this->find(key)); - - if (p != end()) - erase(p); - else - push_back(key); - } - - bool operator[](const KeyPress &key) const { return find(key) != end(); } -}; - class MyEventReceiver : public IEventReceiver { public: // This is the one method that we have to implement virtual bool OnEvent(const SEvent &event); - bool IsKeyDown(const KeyPress &keyCode) const { return keyIsDown[keyCode]; } + bool IsKeyDown(const KeyPress &keyCode) const { return keyIsDown.count(keyCode); } // Checks whether a key was down and resets the state bool WasKeyDown(const KeyPress &keyCode) { - bool b = keyWasDown[keyCode]; + bool b = keyWasDown.count(keyCode); if (b) - keyWasDown.unset(keyCode); + keyWasDown.erase(keyCode); return b; } // Checks whether a key was just pressed. State will be cleared // in the subsequent iteration of Game::processPlayerInteraction - bool WasKeyPressed(const KeyPress &keycode) const { return keyWasPressed[keycode]; } + bool WasKeyPressed(const KeyPress &keycode) const { return keyWasPressed.count(keycode); } // Checks whether a key was just released. State will be cleared // in the subsequent iteration of Game::processPlayerInteraction - bool WasKeyReleased(const KeyPress &keycode) const { return keyWasReleased[keycode]; } + bool WasKeyReleased(const KeyPress &keycode) const { return keyWasReleased.count(keycode); } - void listenForKey(const KeyPress &keyCode) { keysListenedFor.set(keyCode); } + void listenForKey(const KeyPress &keyCode) { keysListenedFor.insert(keyCode); } void dontListenForKeys() { keysListenedFor.clear(); } s32 getMouseWheel() @@ -198,24 +132,20 @@ public: #endif private: - // The current state of keys - KeyList keyIsDown; + //! The current state of keys + std::unordered_set keyIsDown; - // Whether a key was down - KeyList keyWasDown; + //! Whether a key was down + std::unordered_set keyWasDown; - // Whether a key has just been pressed - KeyList keyWasPressed; + //! Whether a key has just been pressed + std::unordered_set keyWasPressed; - // Whether a key has just been released - KeyList keyWasReleased; + //! Whether a key has just been released + std::unordered_set keyWasReleased; - // List of keys we listen for - // TODO perhaps the type of this is not really - // performant as KeyList is designed for few but - // often changing keys, and keysListenedFor is expected - // to change seldomly but contain lots of keys. - KeyList keysListenedFor; + //! List of keys we listen for + std::unordered_set keysListenedFor; }; class InputHandler @@ -347,7 +277,7 @@ public: return true; } - virtual bool isKeyDown(GameKeyType k) { return keydown[keycache.key[k]]; } + virtual bool isKeyDown(GameKeyType k) { return keydown.count(keycache.key[k]); } virtual bool wasKeyDown(GameKeyType k) { return false; } virtual bool wasKeyPressed(GameKeyType k) { return false; } virtual bool wasKeyReleased(GameKeyType k) { return false; } @@ -362,7 +292,7 @@ public: s32 Rand(s32 min, s32 max); private: - KeyList keydown; + std::unordered_set keydown; v2s32 mousepos; v2s32 mousespeed; }; diff --git a/src/client/keycode.h b/src/client/keycode.h index 7036705d1..263b722c7 100644 --- a/src/client/keycode.h +++ b/src/client/keycode.h @@ -24,12 +24,20 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include +class KeyPress; +namespace std +{ + template <> struct hash; +} + /* A key press, consisting of either an Irrlicht keycode or an actual char */ class KeyPress { public: + friend struct std::hash; + KeyPress() = default; KeyPress(const char *name); @@ -55,6 +63,17 @@ protected: std::string m_name = ""; }; +namespace std +{ + template <> struct hash + { + size_t operator()(const KeyPress &key) const + { + return key.Key; + } + }; +} + extern const KeyPress EscapeKey; extern const KeyPress CancelKey; From 9bff154cba14686f5a3b56f4cba405824b88c402 Mon Sep 17 00:00:00 2001 From: Paramat Date: Thu, 24 Sep 2020 00:10:50 +0100 Subject: [PATCH 072/266] Fix horizontal/vertical merging bug of hardware-colored framed glass (#10417) Previously, the param2-controlled horizontal/vertical merge feature (which was undocumented and forgotten) was always active, causing uses of param2 other than "glasslikeliquidlevel" to affect H/V merging. Only respect H/V merge bits when paramtype2 = "glasslikeliquidlevel". H/V merge bits and liquid level bits are designed to be used simultaneously. --- src/client/content_mapblock.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/client/content_mapblock.cpp b/src/client/content_mapblock.cpp index 65a85709b..df2748212 100644 --- a/src/client/content_mapblock.cpp +++ b/src/client/content_mapblock.cpp @@ -723,7 +723,8 @@ void MapblockMeshGenerator::drawGlasslikeFramedNode() for (auto &glass_tile : glass_tiles) glass_tile = tiles[4]; - u8 param2 = n.getParam2(); + // Only respect H/V merge bits when paramtype2 = "glasslikeliquidlevel" (liquid tank) + u8 param2 = (f->param_type_2 == CPT2_GLASSLIKE_LIQUID_LEVEL) ? n.getParam2() : 0; bool H_merge = !(param2 & 128); bool V_merge = !(param2 & 64); param2 &= 63; From c6e3050357e8378ad15e7fa7a9aa80f3936fbc2d Mon Sep 17 00:00:00 2001 From: Buckaroo Banzai <39065740+BuckarooBanzay@users.noreply.github.com> Date: Fri, 25 Sep 2020 18:52:42 +0200 Subject: [PATCH 073/266] Correct erroneous reported max lag with prometheus (#10427) Co-authored-by: BuckarooBanzay --- src/server.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/server.cpp b/src/server.cpp index b8a99f6ae..456edfeb8 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -653,7 +653,12 @@ void Server::AsyncRunStep(bool initial_step) } m_clients.step(dtime); - m_lag_gauge->increment((m_lag_gauge->get() > dtime ? -1 : 1) * dtime/100); + // increase/decrease lag gauge gradually + if (m_lag_gauge->get() > dtime) { + m_lag_gauge->decrement(dtime/100); + } else { + m_lag_gauge->increment(dtime/100); + } #if USE_CURL // send masterserver announce { From 09af0c5946c7082fc82563faf0ad9ef144fb9a06 Mon Sep 17 00:00:00 2001 From: luk3yx Date: Sun, 27 Sep 2020 01:31:54 +1200 Subject: [PATCH 074/266] Remove null bytes from TOCLIENT_BLOCKDATA (#10433) --- src/server.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server.cpp b/src/server.cpp index 456edfeb8..d40ff259f 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -2338,7 +2338,7 @@ void Server::SendBlockNoLock(session_t peer_id, MapBlock *block, u8 ver, block->serializeNetworkSpecific(os); std::string s = os.str(); - NetworkPacket pkt(TOCLIENT_BLOCKDATA, 2 + 2 + 2 + 2 + s.size(), peer_id); + NetworkPacket pkt(TOCLIENT_BLOCKDATA, 2 + 2 + 2 + s.size(), peer_id); pkt << block->getPos(); pkt.putRawString(s.c_str(), s.size()); From 9eb4516cbc6a5a790dcb77178e1f1408ec0b06d0 Mon Sep 17 00:00:00 2001 From: Buckaroo Banzai <39065740+BuckarooBanzay@users.noreply.github.com> Date: Sat, 26 Sep 2020 15:32:49 +0200 Subject: [PATCH 075/266] Enable LuaJIT on the Docker image (#10414) --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 72343ab9c..871ca9825 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ WORKDIR /usr/src/minetest RUN apk add --no-cache git build-base irrlicht-dev cmake bzip2-dev libpng-dev \ jpeg-dev libxxf86vm-dev mesa-dev sqlite-dev libogg-dev \ libvorbis-dev openal-soft-dev curl-dev freetype-dev zlib-dev \ - gmp-dev jsoncpp-dev postgresql-dev ca-certificates && \ + gmp-dev jsoncpp-dev postgresql-dev luajit-dev ca-certificates && \ git clone --depth=1 -b ${MINETEST_GAME_VERSION} https://github.com/minetest/minetest_game.git ./games/minetest_game && \ rm -fr ./games/minetest_game/.git @@ -51,7 +51,7 @@ RUN mkdir build && \ FROM alpine:3.11 -RUN apk add --no-cache sqlite-libs curl gmp libstdc++ libgcc libpq && \ +RUN apk add --no-cache sqlite-libs curl gmp libstdc++ libgcc libpq luajit && \ adduser -D minetest --uid 30000 -h /var/lib/minetest && \ chown -R minetest:minetest /var/lib/minetest From 917e357bcaf5b7ebb77afe16b8c8fbe53b827e79 Mon Sep 17 00:00:00 2001 From: Tyler Schwend Date: Sat, 26 Sep 2020 12:41:16 -0400 Subject: [PATCH 076/266] Log server announce on updates and deletes too (#10177) --- src/serverlist.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/serverlist.cpp b/src/serverlist.cpp index c7fe2d888..80a8c2f1a 100644 --- a/src/serverlist.cpp +++ b/src/serverlist.cpp @@ -245,12 +245,19 @@ void sendAnnounce(AnnounceAction action, for (const ModSpec &mod : mods) { server["mods"].append(mod.name); } - actionstream << "Announcing to " << g_settings->get("serverlist_url") << std::endl; } else if (action == AA_UPDATE) { if (lag) server["lag"] = lag; } + if (action == AA_START) { + actionstream << "Announcing " << aa_names[action] << " to " << + g_settings->get("serverlist_url") << std::endl; + } else { + infostream << "Announcing " << aa_names[action] << " to " << + g_settings->get("serverlist_url") << std::endl; + } + HTTPFetchRequest fetch_request; fetch_request.url = g_settings->get("serverlist_url") + std::string("/announce"); fetch_request.method = HTTP_POST; From 65c15e137fe584edb38edea21c49873be00d554c Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein <54945686+EliasFleckenstein03@users.noreply.github.com> Date: Sat, 26 Sep 2020 18:41:44 +0200 Subject: [PATCH 077/266] Patch fast/teleport vulnerability when attached to an entity (#10340) --- src/server/player_sao.cpp | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/server/player_sao.cpp b/src/server/player_sao.cpp index 67efed210..e5b239bba 100644 --- a/src/server/player_sao.cpp +++ b/src/server/player_sao.cpp @@ -558,11 +558,34 @@ void PlayerSAO::setMaxSpeedOverride(const v3f &vel) bool PlayerSAO::checkMovementCheat() { - if (isAttached() || m_is_singleplayer || + if (m_is_singleplayer || g_settings->getBool("disable_anticheat")) { m_last_good_position = m_base_position; return false; } + if (UnitSAO *parent = dynamic_cast(getParent())) { + v3f attachment_pos; + { + int parent_id; + std::string bone; + v3f attachment_rot; + getAttachment(&parent_id, &bone, &attachment_pos, &attachment_rot); + } + + v3f parent_pos = parent->getBasePosition(); + f32 diff = m_base_position.getDistanceFromSQ(parent_pos) - attachment_pos.getLengthSQ(); + const f32 maxdiff = 4.0f * BS; // fair trade-off value for various latencies + + if (diff > maxdiff * maxdiff) { + setBasePosition(parent_pos); + actionstream << "Server: " << m_player->getName() + << " moved away from parent; diff=" << sqrtf(diff) / BS + << " resetting position." << std::endl; + return true; + } + // Player movement is locked to the entity. Skip further checks + return false; + } bool cheated = false; /* From 4298d95b16ab9a795b912f3d911c7b9ad2e3b03d Mon Sep 17 00:00:00 2001 From: Maksim Date: Sat, 26 Sep 2020 18:42:22 +0200 Subject: [PATCH 078/266] Android: replace InputDialogActivity on simple dialog window (#10034) --- .../android/app/src/main/AndroidManifest.xml | 7 +- .../net/minetest/minetest/CustomEditText.java | 45 +++++++++ .../net/minetest/minetest/GameActivity.java | 73 +++++++++----- .../minetest/InputDialogActivity.java | 98 ------------------- .../app/src/main/res/values/styles.xml | 9 +- 5 files changed, 95 insertions(+), 137 deletions(-) create mode 100644 build/android/app/src/main/java/net/minetest/minetest/CustomEditText.java delete mode 100644 build/android/app/src/main/java/net/minetest/minetest/InputDialogActivity.java diff --git a/build/android/app/src/main/AndroidManifest.xml b/build/android/app/src/main/AndroidManifest.xml index 0a7c8d95a..fa93e7069 100644 --- a/build/android/app/src/main/AndroidManifest.xml +++ b/build/android/app/src/main/AndroidManifest.xml @@ -17,8 +17,8 @@ android:allowBackup="false" android:icon="@mipmap/ic_launcher" android:label="@string/label" - android:resizeableActivity="false" android:requestLegacyExternalStorage="true" + android:resizeableActivity="false" tools:ignore="UnusedAttribute"> - - +Copyright (C) 2014-2020 ubulem, Bektur Mambetov + +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. +*/ + +package net.minetest.minetest; + +import android.content.Context; +import android.view.KeyEvent; +import android.view.inputmethod.InputMethodManager; + +import androidx.appcompat.widget.AppCompatEditText; + +import java.util.Objects; + +public class CustomEditText extends AppCompatEditText { + public CustomEditText(Context context) { + super(context); + } + + @Override + public boolean onKeyPreIme(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_BACK) { + InputMethodManager mgr = (InputMethodManager) + getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + Objects.requireNonNull(mgr).hideSoftInputFromWindow(this.getWindowToken(), 0); + } + return false; + } +} diff --git a/build/android/app/src/main/java/net/minetest/minetest/GameActivity.java b/build/android/app/src/main/java/net/minetest/minetest/GameActivity.java index 635512569..db126a2b9 100644 --- a/build/android/app/src/main/java/net/minetest/minetest/GameActivity.java +++ b/build/android/app/src/main/java/net/minetest/minetest/GameActivity.java @@ -25,8 +25,16 @@ import android.content.Intent; import android.net.Uri; import android.os.Build; import android.os.Bundle; +import android.text.InputType; +import android.view.KeyEvent; import android.view.View; import android.view.WindowManager; +import android.view.inputmethod.InputMethodManager; +import android.widget.EditText; + +import androidx.appcompat.app.AlertDialog; + +import java.util.Objects; public class GameActivity extends NativeActivity { static { @@ -34,8 +42,8 @@ public class GameActivity extends NativeActivity { System.loadLibrary("Minetest"); } - private int messageReturnCode; - private String messageReturnValue; + private int messageReturnCode = -1; + private String messageReturnValue = ""; public static native void putMessageBoxResult(String text); @@ -43,8 +51,6 @@ public class GameActivity extends NativeActivity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - messageReturnCode = -1; - messageReturnValue = ""; } private void makeFullScreen() { @@ -73,29 +79,46 @@ public class GameActivity extends NativeActivity { // Ignore the back press so Minetest can handle it } - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - if (requestCode == 101) { - if (resultCode == RESULT_OK) { - String text = data.getStringExtra("text"); - messageReturnCode = 0; - messageReturnValue = text; - } else - messageReturnCode = 1; - } + public void showDialog(String acceptButton, String hint, String current, int editType) { + runOnUiThread(() -> showDialogUI(hint, current, editType)); } - public void showDialog(String acceptButton, String hint, String current, int editType) { - Intent intent = new Intent(this, InputDialogActivity.class); - Bundle params = new Bundle(); - params.putString("acceptButton", acceptButton); - params.putString("hint", hint); - params.putString("current", current); - params.putInt("editType", editType); - intent.putExtras(params); - startActivityForResult(intent, 101); - messageReturnValue = ""; - messageReturnCode = -1; + private void showDialogUI(String hint, String current, int editType) { + final AlertDialog.Builder builder = new AlertDialog.Builder(this); + EditText editText = new CustomEditText(this); + builder.setView(editText); + AlertDialog alertDialog = builder.create(); + editText.requestFocus(); + editText.setHint(hint); + editText.setText(current); + final InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE); + Objects.requireNonNull(imm).toggleSoftInput(InputMethodManager.SHOW_FORCED, + InputMethodManager.HIDE_IMPLICIT_ONLY); + if (editType == 1) + editText.setInputType(InputType.TYPE_CLASS_TEXT | + InputType.TYPE_TEXT_FLAG_MULTI_LINE); + else if (editType == 3) + editText.setInputType(InputType.TYPE_CLASS_TEXT | + InputType.TYPE_TEXT_VARIATION_PASSWORD); + else + editText.setInputType(InputType.TYPE_CLASS_TEXT); + editText.setSelection(editText.getText().length()); + editText.setOnKeyListener((view, KeyCode, event) -> { + if (KeyCode == KeyEvent.KEYCODE_ENTER) { + imm.hideSoftInputFromWindow(editText.getWindowToken(), 0); + messageReturnCode = 0; + messageReturnValue = editText.getText().toString(); + alertDialog.dismiss(); + return true; + } + return false; + }); + alertDialog.show(); + alertDialog.setOnCancelListener(dialog -> { + getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN); + messageReturnValue = current; + messageReturnCode = -1; + }); } public int getDialogState() { diff --git a/build/android/app/src/main/java/net/minetest/minetest/InputDialogActivity.java b/build/android/app/src/main/java/net/minetest/minetest/InputDialogActivity.java deleted file mode 100644 index 7c6aa111d..000000000 --- a/build/android/app/src/main/java/net/minetest/minetest/InputDialogActivity.java +++ /dev/null @@ -1,98 +0,0 @@ -/* -Minetest -Copyright (C) 2014-2020 MoNTE48, Maksim Gamarnik -Copyright (C) 2014-2020 ubulem, Bektur Mambetov - -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. -*/ - -package net.minetest.minetest; - -import android.app.Activity; -import android.content.Intent; -import android.os.Build; -import android.os.Bundle; -import android.text.InputType; -import android.view.KeyEvent; -import android.view.View; -import android.view.inputmethod.InputMethodManager; -import android.widget.EditText; - -import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.app.AppCompatActivity; - -import java.util.Objects; - -public class InputDialogActivity extends AppCompatActivity { - private AlertDialog alertDialog; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - Bundle b = getIntent().getExtras(); - int editType = Objects.requireNonNull(b).getInt("editType"); - String hint = b.getString("hint"); - String current = b.getString("current"); - final AlertDialog.Builder builder = new AlertDialog.Builder(this); - EditText editText = new EditText(this); - builder.setView(editText); - editText.requestFocus(); - editText.setHint(hint); - editText.setText(current); - final InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE); - Objects.requireNonNull(imm).toggleSoftInput(InputMethodManager.SHOW_FORCED, - InputMethodManager.HIDE_IMPLICIT_ONLY); - if (editType == 3) - editText.setInputType(InputType.TYPE_CLASS_TEXT | - InputType.TYPE_TEXT_VARIATION_PASSWORD); - else - editText.setInputType(InputType.TYPE_CLASS_TEXT); - editText.setOnKeyListener((view, KeyCode, event) -> { - if (KeyCode == KeyEvent.KEYCODE_ENTER) { - imm.hideSoftInputFromWindow(editText.getWindowToken(), 0); - pushResult(editText.getText().toString()); - return true; - } - return false; - }); - alertDialog = builder.create(); - if (!this.isFinishing()) - alertDialog.show(); - alertDialog.setOnCancelListener(dialog -> { - pushResult(editText.getText().toString()); - setResult(Activity.RESULT_CANCELED); - alertDialog.dismiss(); - makeFullScreen(); - finish(); - }); - } - - private void pushResult(String text) { - Intent resultData = new Intent(); - resultData.putExtra("text", text); - setResult(AppCompatActivity.RESULT_OK, resultData); - alertDialog.dismiss(); - makeFullScreen(); - finish(); - } - - private void makeFullScreen() { - if (Build.VERSION.SDK_INT >= 19) - this.getWindow().getDecorView().setSystemUiVisibility( - View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | - View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | - View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); - } -} diff --git a/build/android/app/src/main/res/values/styles.xml b/build/android/app/src/main/res/values/styles.xml index 618507e63..291a4eaf1 100644 --- a/build/android/app/src/main/res/values/styles.xml +++ b/build/android/app/src/main/res/values/styles.xml @@ -8,15 +8,8 @@ shortEdges - - - From d3e327a853d0cbe339e7e76d0c29c1c941644de0 Mon Sep 17 00:00:00 2001 From: LoneWolfHT Date: Tue, 29 Sep 2020 10:39:36 -0700 Subject: [PATCH 079/266] Update wording of game descriptions in installer (#10441) --- CMakeLists.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index fec9709d4..31f548be7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -249,15 +249,15 @@ cpack_add_component(Docs cpack_add_component(SUBGAME_MINETEST_GAME DISPLAY_NAME "Minetest Game" - DESCRIPTION "The official subgame for the Minetest engine, that can easily extended by mods." - GROUP "Subgames" + DESCRIPTION "The default game bundled in the Minetest engine. Mainly used as a modding base." + GROUP "Games" ) cpack_add_component(SUBGAME_MINIMAL DISPLAY_NAME "Development Test" - DESCRIPTION "A minimal test game helping to develop the engine." + DESCRIPTION "A basic testing environment used for engine development and sometimes for testing mods." DISABLED #DISABLED does not mean it is disabled, and is just not selected by default. - GROUP "Subgames" + GROUP "Games" ) cpack_add_component_group(Subgames From 995d4052610b98821e1dcaf5031477de6f202bef Mon Sep 17 00:00:00 2001 From: "k.h.lai" Date: Wed, 30 Sep 2020 01:39:53 +0800 Subject: [PATCH 080/266] Improve MSVC cmake and update vcpkg instruction (#10407) Remove unnecessary compiler flag for clang-cl Update vcpkg instruction in README.md --- README.md | 5 +++-- src/CMakeLists.txt | 26 ++++++++++++++++---------- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 6a3c11f40..a06c3e257 100644 --- a/README.md +++ b/README.md @@ -314,13 +314,14 @@ It is highly recommended to use vcpkg as package manager. After you successfully built vcpkg you can easily install the required libraries: ```powershell -vcpkg install irrlicht zlib curl[winssl] openal-soft libvorbis libogg sqlite3 freetype luajit --triplet x64-windows +vcpkg install irrlicht zlib curl[winssl] openal-soft libvorbis libogg sqlite3 freetype luajit gmp jsoncpp --triplet x64-windows ``` - `curl` is optional, but required to read the serverlist, `curl[winssl]` is required to use the content store. - `openal-soft`, `libvorbis` and `libogg` are optional, but required to use sound. - `freetype` is optional, it allows true-type font rendering. - `luajit` is optional, it replaces the integrated Lua interpreter with a faster just-in-time interpreter. +- `gmp` and `jsoncpp` are optional, otherwise the bundled versions will be compiled There are other optional libraries, but they are not tested if they can build and link correctly. @@ -353,7 +354,7 @@ This is outdated and not recommended. Follow the instructions on https://dev.min Run the following script in PowerShell: ```powershell -cmake . -G"Visual Studio 15 2017 Win64" -DCMAKE_TOOLCHAIN_FILE=D:/vcpkg/scripts/buildsystems/vcpkg.cmake -DCMAKE_BUILD_TYPE=Release -DENABLE_GETTEXT=0 -DENABLE_CURSES=0 +cmake . -G"Visual Studio 15 2017 Win64" -DCMAKE_TOOLCHAIN_FILE=D:/vcpkg/scripts/buildsystems/vcpkg.cmake -DCMAKE_BUILD_TYPE=Release -DENABLE_GETTEXT=OFF -DENABLE_CURSES=OFF -DENABLE_SYSTEM_JSONCPP=ON cmake --build . --config Release ``` Make sure that the right compiler is selected and the path to the vcpkg toolchain is correct. diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7e1d0bd39..b5d4fae0e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -701,21 +701,14 @@ include(CheckCSourceCompiles) if(MSVC) # Visual Studio - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /D WIN32_LEAN_AND_MEAN /MP") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /D WIN32_LEAN_AND_MEAN") # EHa enables SEH exceptions (used for catching segfaults) - set(CMAKE_CXX_FLAGS_RELEASE "/EHa /Ox /GL /FD /MD /GS- /Zi /fp:fast /D NDEBUG /D _HAS_ITERATOR_DEBUGGING=0 /TP") + set(CMAKE_CXX_FLAGS_RELEASE "/EHa /Ox /MD /GS- /Zi /fp:fast /D NDEBUG /D _HAS_ITERATOR_DEBUGGING=0") if(CMAKE_SIZEOF_VOID_P EQUAL 4) set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /arch:SSE") endif() - - if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") - set(CMAKE_EXE_LINKER_FLAGS_RELEASE "/INCREMENTAL:NO /DEBUG /OPT:REF /OPT:ICF") - else() - set(CMAKE_EXE_LINKER_FLAGS_RELEASE "/LTCG /INCREMENTAL:NO /DEBUG /OPT:REF /OPT:ICF") - endif() - - set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /SUBSYSTEM:WINDOWS /ENTRY:mainCRTStartup") + set(CMAKE_EXE_LINKER_FLAGS_RELEASE "/INCREMENTAL:NO /DEBUG /OPT:REF /OPT:ICF /SUBSYSTEM:WINDOWS /ENTRY:mainCRTStartup") set(CMAKE_CXX_FLAGS_SEMIDEBUG "/MDd /Zi /Ob0 /O1 /RTC1") @@ -726,6 +719,19 @@ if(MSVC) # Flags for C files (sqlite) # /MD = dynamically link to MSVCRxxx.dll set(CMAKE_C_FLAGS_RELEASE "/O2 /Ob2 /MD") + + # Flags that cannot be shared between cl and clang-cl + # https://clang.llvm.org/docs/UsersManual.html#clang-cl + if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fuse-ld=lld") + + # Disable pragma-pack warning + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -Wno-pragma-pack") + else() + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP") + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /TP /FD /GL") + set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /LTCG") + endif() else() # GCC or compatible compilers such as Clang set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") From a69bc67ce26348198a29997d076df3a852a3447e Mon Sep 17 00:00:00 2001 From: Paramat Date: Tue, 29 Sep 2020 23:37:03 +0100 Subject: [PATCH 081/266] Improve layout of main menu 'local' tab (#10366) --- builtin/mainmenu/tab_local.lua | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/builtin/mainmenu/tab_local.lua b/builtin/mainmenu/tab_local.lua index a21cf12b1..1aee246fc 100644 --- a/builtin/mainmenu/tab_local.lua +++ b/builtin/mainmenu/tab_local.lua @@ -114,45 +114,44 @@ local function get_formspec(tabview, name, tabdata) ) retval = retval .. - "button[4,3.95;2.6,1;world_delete;".. fgettext("Delete") .. "]" .. - "button[6.5,3.95;2.8,1;world_configure;".. fgettext("Configure") .. "]" .. - "button[9.2,3.95;2.5,1;world_create;".. fgettext("New") .. "]" .. - "label[4,-0.25;".. fgettext("Select World:") .. "]".. - "checkbox[0.25,0.25;cb_creative_mode;".. fgettext("Creative Mode") .. ";" .. + "button[3.9,3.8;2.8,1;world_delete;".. fgettext("Delete") .. "]" .. + "button[6.55,3.8;2.8,1;world_configure;".. fgettext("Configure") .. "]" .. + "button[9.2,3.8;2.8,1;world_create;".. fgettext("New") .. "]" .. + "label[3.9,-0.05;".. fgettext("Select World:") .. "]".. + "checkbox[0,-0.20;cb_creative_mode;".. fgettext("Creative Mode") .. ";" .. dump(core.settings:get_bool("creative_mode")) .. "]".. - "checkbox[0.25,0.7;cb_enable_damage;".. fgettext("Enable Damage") .. ";" .. + "checkbox[0,0.25;cb_enable_damage;".. fgettext("Enable Damage") .. ";" .. dump(core.settings:get_bool("enable_damage")) .. "]".. - "checkbox[0.25,1.15;cb_server;".. fgettext("Host Server") ..";" .. + "checkbox[0,0.7;cb_server;".. fgettext("Host Server") ..";" .. dump(core.settings:get_bool("enable_server")) .. "]" .. - "textlist[4,0.25;7.5,3.7;sp_worlds;" .. + "textlist[3.9,0.4;7.9,3.45;sp_worlds;" .. menu_render_worldlist() .. ";" .. index .. "]" if core.settings:get_bool("enable_server") then retval = retval .. - "button[8.5,4.8;3.2,1;play;".. fgettext("Host Game") .. "]" .. - "checkbox[0.25,1.6;cb_server_announce;" .. fgettext("Announce Server") .. ";" .. + "button[7.9,4.75;4.1,1;play;".. fgettext("Host Game") .. "]" .. + "checkbox[0,1.15;cb_server_announce;" .. fgettext("Announce Server") .. ";" .. dump(core.settings:get_bool("server_announce")) .. "]" .. - "label[0.25,2.2;" .. fgettext("Name/Password") .. "]" .. - "field[0.55,3.2;3.5,0.5;te_playername;;" .. + "field[0.3,2.85;3.8,0.5;te_playername;" .. fgettext("Name") .. ";" .. core.formspec_escape(core.settings:get("name")) .. "]" .. - "pwdfield[0.55,4;3.5,0.5;te_passwd;]" + "pwdfield[0.3,4.05;3.8,0.5;te_passwd;" .. fgettext("Password") .. "]" local bind_addr = core.settings:get("bind_address") if bind_addr ~= nil and bind_addr ~= "" then retval = retval .. - "field[0.55,5.2;2.25,0.5;te_serveraddr;" .. fgettext("Bind Address") .. ";" .. + "field[0.3,5.25;2.5,0.5;te_serveraddr;" .. fgettext("Bind Address") .. ";" .. core.formspec_escape(core.settings:get("bind_address")) .. "]" .. - "field[2.8,5.2;1.25,0.5;te_serverport;" .. fgettext("Port") .. ";" .. + "field[2.85,5.25;1.25,0.5;te_serverport;" .. fgettext("Port") .. ";" .. core.formspec_escape(core.settings:get("port")) .. "]" else retval = retval .. - "field[0.55,5.2;3.5,0.5;te_serverport;" .. fgettext("Server Port") .. ";" .. + "field[0.3,5.25;3.8,0.5;te_serverport;" .. fgettext("Server Port") .. ";" .. core.formspec_escape(core.settings:get("port")) .. "]" end else retval = retval .. - "button[8.5,4.8;3.2,1;play;".. fgettext("Play Game") .. "]" + "button[7.9,4.75;4.1,1;play;" .. fgettext("Play Game") .. "]" end return retval From 79414aa3e5591fdaffa0956a08610a2228042941 Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Sun, 20 Sep 2020 12:52:35 +0200 Subject: [PATCH 082/266] Settings: Remove unused functions Make Settings-internal functions private --- src/settings.cpp | 87 +++++------------------------------------------- src/settings.h | 49 ++++++++++++++------------- 2 files changed, 33 insertions(+), 103 deletions(-) diff --git a/src/settings.cpp b/src/settings.cpp index 28b72c4e3..56ab9e12b 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -45,7 +45,13 @@ Settings::~Settings() Settings & Settings::operator += (const Settings &other) { - update(other); + if (&other == this) + return *this; + + MutexAutoLock lock(m_mutex); + MutexAutoLock lock2(other.m_mutex); + + updateNoLock(other); return *this; } @@ -512,25 +518,6 @@ u32 Settings::getFlagStr(const std::string &name, const FlagDesc *flagdesc, return flags; } -// N.B. if getStruct() is used to read a non-POD aggregate type, -// the behavior is undefined. -bool Settings::getStruct(const std::string &name, const std::string &format, - void *out, size_t olen) const -{ - std::string valstr; - - try { - valstr = get(name); - } catch (SettingNotFoundException &e) { - return false; - } - - if (!deSerializeStringToStruct(valstr, format, out, olen)) - return false; - - return true; -} - bool Settings::getNoiseParams(const std::string &name, NoiseParams &np) const { @@ -546,6 +533,7 @@ bool Settings::getNoiseParamsFromValue(const std::string &name, if (!getNoEx(name, value)) return false; + // Format: f32,f32,(f32,f32,f32),s32,s32,f32[,f32] Strfnd f(value); np.offset = stof(f.next(",")); @@ -615,28 +603,6 @@ std::vector Settings::getNames() const * Getters that don't throw exceptions * ***************************************/ -bool Settings::getEntryNoEx(const std::string &name, SettingsEntry &val) const -{ - try { - val = getEntry(name); - return true; - } catch (SettingNotFoundException &e) { - return false; - } -} - - -bool Settings::getEntryDefaultNoEx(const std::string &name, SettingsEntry &val) const -{ - try { - val = getEntryDefault(name); - return true; - } catch (SettingNotFoundException &e) { - return false; - } -} - - bool Settings::getGroupNoEx(const std::string &name, Settings *&val) const { try { @@ -908,17 +874,6 @@ bool Settings::setFlagStr(const std::string &name, u32 flags, } -bool Settings::setStruct(const std::string &name, const std::string &format, - void *value) -{ - std::string structstr; - if (!serializeStructToString(&structstr, format, value)) - return false; - - return set(name, structstr); -} - - bool Settings::setNoiseParams(const std::string &name, const NoiseParams &np, bool set_default) { @@ -969,32 +924,6 @@ void Settings::clearDefaults() clearDefaultsNoLock(); } -void Settings::updateValue(const Settings &other, const std::string &name) -{ - if (&other == this) - return; - - MutexAutoLock lock(m_mutex); - - try { - m_settings[name] = other.get(name); - } catch (SettingNotFoundException &e) { - } -} - - -void Settings::update(const Settings &other) -{ - if (&other == this) - return; - - MutexAutoLock lock(m_mutex); - MutexAutoLock lock2(other.m_mutex); - - updateNoLock(other); -} - - SettingsParseEvent Settings::parseConfigObject(const std::string &line, const std::string &end, std::string &name, std::string &value) { diff --git a/src/settings.h b/src/settings.h index ccc252049..7db5539b2 100644 --- a/src/settings.h +++ b/src/settings.h @@ -113,23 +113,10 @@ public: bool parseConfigLines(std::istream &is, const std::string &end = ""); void writeLines(std::ostream &os, u32 tab_depth=0) const; - SettingsParseEvent parseConfigObject(const std::string &line, - const std::string &end, std::string &name, std::string &value); - bool updateConfigObject(std::istream &is, std::ostream &os, - const std::string &end, u32 tab_depth=0); - - static bool checkNameValid(const std::string &name); - static bool checkValueValid(const std::string &value); - static std::string getMultiline(std::istream &is, size_t *num_lines=NULL); - static void printEntry(std::ostream &os, const std::string &name, - const SettingsEntry &entry, u32 tab_depth=0); - /*********** * Getters * ***********/ - const SettingsEntry &getEntry(const std::string &name) const; - const SettingsEntry &getEntryDefault(const std::string &name) const; Settings *getGroup(const std::string &name) const; const std::string &get(const std::string &name) const; const std::string &getDefault(const std::string &name) const; @@ -144,10 +131,6 @@ public: v3f getV3F(const std::string &name) const; u32 getFlagStr(const std::string &name, const FlagDesc *flagdesc, u32 *flagmask) const; - // N.B. if getStruct() is used to read a non-POD aggregate type, - // the behavior is undefined. - bool getStruct(const std::string &name, const std::string &format, - void *out, size_t olen) const; bool getNoiseParams(const std::string &name, NoiseParams &np) const; bool getNoiseParamsFromValue(const std::string &name, NoiseParams &np) const; bool getNoiseParamsFromGroup(const std::string &name, NoiseParams &np) const; @@ -161,8 +144,6 @@ public: * Getters that don't throw exceptions * ***************************************/ - bool getEntryNoEx(const std::string &name, SettingsEntry &val) const; - bool getEntryDefaultNoEx(const std::string &name, SettingsEntry &val) const; bool getGroupNoEx(const std::string &name, Settings *&val) const; bool getNoEx(const std::string &name, std::string &val) const; bool getDefaultNoEx(const std::string &name, std::string &val) const; @@ -206,16 +187,11 @@ public: const FlagDesc *flagdesc = nullptr, u32 flagmask = U32_MAX); bool setNoiseParams(const std::string &name, const NoiseParams &np, bool set_default=false); - // N.B. if setStruct() is used to write a non-POD aggregate type, - // the behavior is undefined. - bool setStruct(const std::string &name, const std::string &format, void *value); // remove a setting bool remove(const std::string &name); void clear(); void clearDefaults(); - void updateValue(const Settings &other, const std::string &name); - void update(const Settings &other); /************** * Miscellany * @@ -232,6 +208,31 @@ public: SettingsChangedCallback cbf, void *userdata = NULL); private: + /*********************** + * Reading and writing * + ***********************/ + + SettingsParseEvent parseConfigObject(const std::string &line, + const std::string &end, std::string &name, std::string &value); + bool updateConfigObject(std::istream &is, std::ostream &os, + const std::string &end, u32 tab_depth=0); + + static bool checkNameValid(const std::string &name); + static bool checkValueValid(const std::string &value); + static std::string getMultiline(std::istream &is, size_t *num_lines=NULL); + static void printEntry(std::ostream &os, const std::string &name, + const SettingsEntry &entry, u32 tab_depth=0); + + /*********** + * Getters * + ***********/ + + const SettingsEntry &getEntry(const std::string &name) const; + const SettingsEntry &getEntryDefault(const std::string &name) const; + + // Allow TestSettings to run sanity checks using private functions. + friend class TestSettings; + void updateNoLock(const Settings &other); void clearNoLock(); void clearDefaultsNoLock(); From ca5c2dbefab3676514e48b445b36de50993de9f1 Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Sun, 20 Sep 2020 13:04:01 +0200 Subject: [PATCH 083/266] Clean up serialization This reverts 1a5b4b3 and further functions in serialize.cpp that are unused The intend for a sane NetworkPacket/stream replacement was good, but a wrapper class around i/ostream might be more versatile than introducing a new vector-based serialization class. --- src/unittest/test_serialization.cpp | 324 -------------------------- src/util/serialize.cpp | 349 ---------------------------- src/util/serialize.h | 242 ------------------- 3 files changed, 915 deletions(-) diff --git a/src/unittest/test_serialization.cpp b/src/unittest/test_serialization.cpp index 51e28f144..d72bf0d4c 100644 --- a/src/unittest/test_serialization.cpp +++ b/src/unittest/test_serialization.cpp @@ -32,18 +32,12 @@ public: void buildTestStrings(); void testSerializeString(); - void testSerializeWideString(); void testSerializeLongString(); void testSerializeJsonString(); - void testSerializeHex(); void testDeSerializeString(); - void testDeSerializeWideString(); void testDeSerializeLongString(); void testStreamRead(); void testStreamWrite(); - void testVecPut(); - void testStringLengthLimits(); - void testBufReader(); void testFloatFormat(); std::string teststring2; @@ -61,17 +55,11 @@ void TestSerialization::runTests(IGameDef *gamedef) TEST(testSerializeString); TEST(testDeSerializeString); - TEST(testSerializeWideString); - TEST(testDeSerializeWideString); TEST(testSerializeLongString); TEST(testDeSerializeLongString); TEST(testSerializeJsonString); - TEST(testSerializeHex); TEST(testStreamRead); TEST(testStreamWrite); - TEST(testVecPut); - TEST(testStringLengthLimits); - TEST(testBufReader); TEST(testFloatFormat); } @@ -136,50 +124,6 @@ void TestSerialization::testDeSerializeString() } } -void TestSerialization::testSerializeWideString() -{ - // Test blank string - UASSERT(serializeWideString(L"") == mkstr("\0\0")); - - // Test basic string - UASSERT(serializeWideString(utf8_to_wide("Hello world!")) == - mkstr("\0\14\0H\0e\0l\0l\0o\0 \0w\0o\0r\0l\0d\0!")); - - // Test character range - UASSERT(serializeWideString(teststring2_w) == - mkstr("\1\0") + teststring2_w_encoded); -} - -void TestSerialization::testDeSerializeWideString() -{ - // Test deserialize - { - std::istringstream is(serializeWideString(teststring2_w), std::ios::binary); - UASSERT(deSerializeWideString(is) == teststring2_w); - UASSERT(!is.eof()); - is.get(); - UASSERT(is.eof()); - } - - // Test deserialize an incomplete length specifier - { - std::istringstream is(mkstr("\x53"), std::ios::binary); - EXCEPTION_CHECK(SerializationError, deSerializeWideString(is)); - } - - // Test deserialize a string with an incomplete character - { - std::istringstream is(mkstr("\x00\x07\0a\0b\0c\0d\0e\0f\0"), std::ios::binary); - EXCEPTION_CHECK(SerializationError, deSerializeWideString(is)); - } - - // Test deserialize a string with incomplete data - { - std::istringstream is(mkstr("\x00\x08\0a\0b\0c\0d\0e\0f"), std::ios::binary); - EXCEPTION_CHECK(SerializationError, deSerializeWideString(is)); - } -} - void TestSerialization::testSerializeLongString() { // Test blank string @@ -268,25 +212,6 @@ void TestSerialization::testSerializeJsonString() UASSERT(is.eof()); } -void TestSerialization::testSerializeHex() -{ - // Test blank string - UASSERT(serializeHexString("") == ""); - UASSERT(serializeHexString("", true) == ""); - - // Test basic string - UASSERT(serializeHexString("Hello world!") == - "48656c6c6f20776f726c6421"); - UASSERT(serializeHexString("Hello world!", true) == - "48 65 6c 6c 6f 20 77 6f 72 6c 64 21"); - - // Test binary string - UASSERT(serializeHexString(mkstr("\x00\x0a\xb0\x63\x1f\x00\xff")) == - "000ab0631f00ff"); - UASSERT(serializeHexString(mkstr("\x00\x0a\xb0\x63\x1f\x00\xff"), true) == - "00 0a b0 63 1f 00 ff"); -} - void TestSerialization::testStreamRead() { @@ -376,255 +301,6 @@ void TestSerialization::testStreamWrite() } -void TestSerialization::testVecPut() -{ - std::vector buf; - - putU8(&buf, 0x11); - putU16(&buf, 0x2233); - putU32(&buf, 0x44556677); - putU64(&buf, 0x8899AABBCCDDEEFFLL); - - putS8(&buf, -128); - putS16(&buf, 30000); - putS32(&buf, -6); - putS64(&buf, -43); - - putF1000(&buf, 53.53467f); - putF1000(&buf, -300000.32f); - putF1000(&buf, F1000_MIN); - putF1000(&buf, F1000_MAX); - - putString(&buf, "foobar!"); - - putV2S16(&buf, v2s16(500, 500)); - putV3S16(&buf, v3s16(4207, 604, -30)); - putV2S32(&buf, v2s32(1920, 1080)); - putV3S32(&buf, v3s32(-400, 6400054, 290549855)); - - putWideString(&buf, L"\x02~woof~\x5455"); - - putV3F1000(&buf, v3f(500, 10024.2f, -192.54f)); - putARGB8(&buf, video::SColor(255, 128, 50, 128)); - - putLongString(&buf, "some longer string here"); - - putU16(&buf, 0xF00D); - - UASSERT(buf.size() == sizeof(test_serialized_data)); - UASSERT(!memcmp(&buf[0], test_serialized_data, sizeof(test_serialized_data))); -} - - -void TestSerialization::testStringLengthLimits() -{ - std::vector buf; - std::string too_long(STRING_MAX_LEN + 1, 'A'); - std::string way_too_large(LONG_STRING_MAX_LEN + 1, 'B'); - std::wstring too_long_wide(WIDE_STRING_MAX_LEN + 1, L'C'); - - EXCEPTION_CHECK(SerializationError, putString(&buf, too_long)); - - putLongString(&buf, too_long); - too_long.resize(too_long.size() - 1); - putString(&buf, too_long); - - EXCEPTION_CHECK(SerializationError, putWideString(&buf, too_long_wide)); - too_long_wide.resize(too_long_wide.size() - 1); - putWideString(&buf, too_long_wide); -} - - -void TestSerialization::testBufReader() -{ - u8 u8_data; - u16 u16_data; - u32 u32_data; - u64 u64_data; - s8 s8_data; - s16 s16_data; - s32 s32_data; - s64 s64_data; - f32 f32_data, f32_data2, f32_data3, f32_data4; - video::SColor scolor_data; - v2s16 v2s16_data; - v3s16 v3s16_data; - v2s32 v2s32_data; - v3s32 v3s32_data; - v3f v3f_data; - std::string string_data; - std::wstring widestring_data; - std::string longstring_data; - u8 raw_data[10] = {0}; - - BufReader buf(test_serialized_data, sizeof(test_serialized_data)); - - // Try reading data like normal - UASSERT(buf.getU8() == 0x11); - UASSERT(buf.getU16() == 0x2233); - UASSERT(buf.getU32() == 0x44556677); - UASSERT(buf.getU64() == 0x8899AABBCCDDEEFFLL); - UASSERT(buf.getS8() == -128); - UASSERT(buf.getS16() == 30000); - UASSERT(buf.getS32() == -6); - UASSERT(buf.getS64() == -43); - UASSERT(buf.getF1000() == 53.534f); - UASSERT(buf.getF1000() == -300000.32f); - UASSERT(buf.getF1000() == F1000_MIN); - UASSERT(buf.getF1000() == F1000_MAX); - UASSERT(buf.getString() == "foobar!"); - UASSERT(buf.getV2S16() == v2s16(500, 500)); - UASSERT(buf.getV3S16() == v3s16(4207, 604, -30)); - UASSERT(buf.getV2S32() == v2s32(1920, 1080)); - UASSERT(buf.getV3S32() == v3s32(-400, 6400054, 290549855)); - UASSERT(buf.getWideString() == L"\x02~woof~\x5455"); - UASSERT(buf.getV3F1000() == v3f(500, 10024.2f, -192.54f)); - UASSERT(buf.getARGB8() == video::SColor(255, 128, 50, 128)); - UASSERT(buf.getLongString() == "some longer string here"); - - // Verify the offset and data is unchanged after a failed read - size_t orig_pos = buf.pos; - u32_data = 0; - UASSERT(buf.getU32NoEx(&u32_data) == false); - UASSERT(buf.pos == orig_pos); - UASSERT(u32_data == 0); - - // Now try the same for a failed string read - UASSERT(buf.getStringNoEx(&string_data) == false); - UASSERT(buf.pos == orig_pos); - UASSERT(string_data == ""); - - // Now try the same for a failed string read - UASSERT(buf.getWideStringNoEx(&widestring_data) == false); - UASSERT(buf.pos == orig_pos); - UASSERT(widestring_data == L""); - - UASSERT(buf.getU16() == 0xF00D); - - UASSERT(buf.remaining() == 0); - - // Check to make sure these each blow exceptions as they're supposed to - EXCEPTION_CHECK(SerializationError, buf.getU8()); - EXCEPTION_CHECK(SerializationError, buf.getU16()); - EXCEPTION_CHECK(SerializationError, buf.getU32()); - EXCEPTION_CHECK(SerializationError, buf.getU64()); - - EXCEPTION_CHECK(SerializationError, buf.getS8()); - EXCEPTION_CHECK(SerializationError, buf.getS16()); - EXCEPTION_CHECK(SerializationError, buf.getS32()); - EXCEPTION_CHECK(SerializationError, buf.getS64()); - - EXCEPTION_CHECK(SerializationError, buf.getF1000()); - EXCEPTION_CHECK(SerializationError, buf.getARGB8()); - - EXCEPTION_CHECK(SerializationError, buf.getV2S16()); - EXCEPTION_CHECK(SerializationError, buf.getV3S16()); - EXCEPTION_CHECK(SerializationError, buf.getV2S32()); - EXCEPTION_CHECK(SerializationError, buf.getV3S32()); - EXCEPTION_CHECK(SerializationError, buf.getV3F1000()); - - EXCEPTION_CHECK(SerializationError, buf.getString()); - EXCEPTION_CHECK(SerializationError, buf.getWideString()); - EXCEPTION_CHECK(SerializationError, buf.getLongString()); - EXCEPTION_CHECK(SerializationError, - buf.getRawData(raw_data, sizeof(raw_data))); - - // See if we can skip backwards - buf.pos = 5; - UASSERT(buf.getRawDataNoEx(raw_data, 3) == true); - UASSERT(raw_data[0] == 0x66); - UASSERT(raw_data[1] == 0x77); - UASSERT(raw_data[2] == 0x88); - - UASSERT(buf.getU32() == 0x99AABBCC); - UASSERT(buf.pos == 12); - - // Now let's try it all over again using the NoEx variants - buf.pos = 0; - - UASSERT(buf.getU8NoEx(&u8_data)); - UASSERT(buf.getU16NoEx(&u16_data)); - UASSERT(buf.getU32NoEx(&u32_data)); - UASSERT(buf.getU64NoEx(&u64_data)); - - UASSERT(buf.getS8NoEx(&s8_data)); - UASSERT(buf.getS16NoEx(&s16_data)); - UASSERT(buf.getS32NoEx(&s32_data)); - UASSERT(buf.getS64NoEx(&s64_data)); - - UASSERT(buf.getF1000NoEx(&f32_data)); - UASSERT(buf.getF1000NoEx(&f32_data2)); - UASSERT(buf.getF1000NoEx(&f32_data3)); - UASSERT(buf.getF1000NoEx(&f32_data4)); - - UASSERT(buf.getStringNoEx(&string_data)); - UASSERT(buf.getV2S16NoEx(&v2s16_data)); - UASSERT(buf.getV3S16NoEx(&v3s16_data)); - UASSERT(buf.getV2S32NoEx(&v2s32_data)); - UASSERT(buf.getV3S32NoEx(&v3s32_data)); - UASSERT(buf.getWideStringNoEx(&widestring_data)); - UASSERT(buf.getV3F1000NoEx(&v3f_data)); - UASSERT(buf.getARGB8NoEx(&scolor_data)); - - UASSERT(buf.getLongStringNoEx(&longstring_data)); - - // and make sure we got the correct data - UASSERT(u8_data == 0x11); - UASSERT(u16_data == 0x2233); - UASSERT(u32_data == 0x44556677); - UASSERT(u64_data == 0x8899AABBCCDDEEFFLL); - UASSERT(s8_data == -128); - UASSERT(s16_data == 30000); - UASSERT(s32_data == -6); - UASSERT(s64_data == -43); - UASSERT(f32_data == 53.534f); - UASSERT(f32_data2 == -300000.32f); - UASSERT(f32_data3 == F1000_MIN); - UASSERT(f32_data4 == F1000_MAX); - UASSERT(string_data == "foobar!"); - UASSERT(v2s16_data == v2s16(500, 500)); - UASSERT(v3s16_data == v3s16(4207, 604, -30)); - UASSERT(v2s32_data == v2s32(1920, 1080)); - UASSERT(v3s32_data == v3s32(-400, 6400054, 290549855)); - UASSERT(widestring_data == L"\x02~woof~\x5455"); - UASSERT(v3f_data == v3f(500, 10024.2f, -192.54f)); - UASSERT(scolor_data == video::SColor(255, 128, 50, 128)); - UASSERT(longstring_data == "some longer string here"); - - UASSERT(buf.remaining() == 2); - UASSERT(buf.getRawDataNoEx(raw_data, 3) == false); - UASSERT(buf.remaining() == 2); - UASSERT(buf.getRawDataNoEx(raw_data, 2) == true); - UASSERT(raw_data[0] == 0xF0); - UASSERT(raw_data[1] == 0x0D); - UASSERT(buf.remaining() == 0); - - // Make sure no more available data causes a failure - UASSERT(!buf.getU8NoEx(&u8_data)); - UASSERT(!buf.getU16NoEx(&u16_data)); - UASSERT(!buf.getU32NoEx(&u32_data)); - UASSERT(!buf.getU64NoEx(&u64_data)); - - UASSERT(!buf.getS8NoEx(&s8_data)); - UASSERT(!buf.getS16NoEx(&s16_data)); - UASSERT(!buf.getS32NoEx(&s32_data)); - UASSERT(!buf.getS64NoEx(&s64_data)); - - UASSERT(!buf.getF1000NoEx(&f32_data)); - UASSERT(!buf.getARGB8NoEx(&scolor_data)); - - UASSERT(!buf.getV2S16NoEx(&v2s16_data)); - UASSERT(!buf.getV3S16NoEx(&v3s16_data)); - UASSERT(!buf.getV2S32NoEx(&v2s32_data)); - UASSERT(!buf.getV3S32NoEx(&v3s32_data)); - UASSERT(!buf.getV3F1000NoEx(&v3f_data)); - - UASSERT(!buf.getStringNoEx(&string_data)); - UASSERT(!buf.getWideStringNoEx(&widestring_data)); - UASSERT(!buf.getLongStringNoEx(&longstring_data)); - UASSERT(!buf.getRawDataNoEx(raw_data, sizeof(raw_data))); -} - void TestSerialization::testFloatFormat() { FloatType type = getFloatSerializationType(); diff --git a/src/util/serialize.cpp b/src/util/serialize.cpp index 5b276668d..fd5cbda21 100644 --- a/src/util/serialize.cpp +++ b/src/util/serialize.cpp @@ -30,74 +30,6 @@ with this program; if not, write to the Free Software Foundation, Inc., FloatType g_serialize_f32_type = FLOATTYPE_UNKNOWN; -//// -//// BufReader -//// - -bool BufReader::getStringNoEx(std::string *val) -{ - u16 num_chars; - if (!getU16NoEx(&num_chars)) - return false; - - if (pos + num_chars > size) { - pos -= sizeof(num_chars); - return false; - } - - val->assign((const char *)data + pos, num_chars); - pos += num_chars; - - return true; -} - -bool BufReader::getWideStringNoEx(std::wstring *val) -{ - u16 num_chars; - if (!getU16NoEx(&num_chars)) - return false; - - if (pos + num_chars * 2 > size) { - pos -= sizeof(num_chars); - return false; - } - - for (size_t i = 0; i != num_chars; i++) { - val->push_back(readU16(data + pos)); - pos += 2; - } - - return true; -} - -bool BufReader::getLongStringNoEx(std::string *val) -{ - u32 num_chars; - if (!getU32NoEx(&num_chars)) - return false; - - if (pos + num_chars > size) { - pos -= sizeof(num_chars); - return false; - } - - val->assign((const char *)data + pos, num_chars); - pos += num_chars; - - return true; -} - -bool BufReader::getRawDataNoEx(void *val, size_t len) -{ - if (pos + len > size) - return false; - - memcpy(val, data + pos, len); - pos += len; - - return true; -} - //// //// String @@ -404,284 +336,3 @@ std::string deSerializeJsonStringIfNeeded(std::istream &is) return tmp_os.str(); } -//// -//// String/Struct conversions -//// - -bool deSerializeStringToStruct(std::string valstr, - std::string format, void *out, size_t olen) -{ - size_t len = olen; - std::vector strs_alloced; - std::string *str; - char *f, *snext; - size_t pos; - - char *s = &valstr[0]; - char *buf = new char[len]; - char *bufpos = buf; - - char *fmtpos, *fmt = &format[0]; - while ((f = strtok_r(fmt, ",", &fmtpos)) && s) { - fmt = nullptr; - - bool is_unsigned = false; - int width = 0; - char valtype = *f; - - width = (int)strtol(f + 1, &f, 10); - if (width && valtype == 's') - valtype = 'i'; - - switch (valtype) { - case 'u': - is_unsigned = true; - /* FALLTHROUGH */ - case 'i': - if (width == 16) { - bufpos += PADDING(bufpos, u16); - if ((bufpos - buf) + sizeof(u16) <= len) { - if (is_unsigned) - *(u16 *)bufpos = (u16)strtoul(s, &s, 10); - else - *(s16 *)bufpos = (s16)strtol(s, &s, 10); - } - bufpos += sizeof(u16); - } else if (width == 32) { - bufpos += PADDING(bufpos, u32); - if ((bufpos - buf) + sizeof(u32) <= len) { - if (is_unsigned) - *(u32 *)bufpos = (u32)strtoul(s, &s, 10); - else - *(s32 *)bufpos = (s32)strtol(s, &s, 10); - } - bufpos += sizeof(u32); - } else if (width == 64) { - bufpos += PADDING(bufpos, u64); - if ((bufpos - buf) + sizeof(u64) <= len) { - if (is_unsigned) - *(u64 *)bufpos = (u64)strtoull(s, &s, 10); - else - *(s64 *)bufpos = (s64)strtoll(s, &s, 10); - } - bufpos += sizeof(u64); - } - s = strchr(s, ','); - break; - case 'b': - snext = strchr(s, ','); - if (snext) - *snext++ = 0; - - bufpos += PADDING(bufpos, bool); - if ((bufpos - buf) + sizeof(bool) <= len) - *(bool *)bufpos = is_yes(std::string(s)); - bufpos += sizeof(bool); - - s = snext; - break; - case 'f': - bufpos += PADDING(bufpos, float); - if ((bufpos - buf) + sizeof(float) <= len) - *(float *)bufpos = strtof(s, &s); - bufpos += sizeof(float); - - s = strchr(s, ','); - break; - case 's': - while (*s == ' ' || *s == '\t') - s++; - if (*s++ != '"') //error, expected string - goto fail; - snext = s; - - while (snext[0] && !(snext[-1] != '\\' && snext[0] == '"')) - snext++; - *snext++ = 0; - - bufpos += PADDING(bufpos, std::string *); - - str = new std::string(s); - pos = 0; - while ((pos = str->find("\\\"", pos)) != std::string::npos) - str->erase(pos, 1); - - if ((bufpos - buf) + sizeof(std::string *) <= len) - *(std::string **)bufpos = str; - bufpos += sizeof(std::string *); - strs_alloced.push_back(str); - - s = *snext ? snext + 1 : nullptr; - break; - case 'v': - while (*s == ' ' || *s == '\t') - s++; - if (*s++ != '(') //error, expected vector - goto fail; - - if (width == 2) { - bufpos += PADDING(bufpos, v2f); - - if ((bufpos - buf) + sizeof(v2f) <= len) { - v2f *v = (v2f *)bufpos; - v->X = strtof(s, &s); - s++; - v->Y = strtof(s, &s); - } - - bufpos += sizeof(v2f); - } else if (width == 3) { - bufpos += PADDING(bufpos, v3f); - if ((bufpos - buf) + sizeof(v3f) <= len) { - v3f *v = (v3f *)bufpos; - v->X = strtof(s, &s); - s++; - v->Y = strtof(s, &s); - s++; - v->Z = strtof(s, &s); - } - - bufpos += sizeof(v3f); - } - s = strchr(s, ','); - break; - default: //error, invalid format specifier - goto fail; - } - - if (s && *s == ',') - s++; - - if ((size_t)(bufpos - buf) > len) //error, buffer too small - goto fail; - } - - if (f && *f) { //error, mismatched number of fields and values -fail: - for (size_t i = 0; i != strs_alloced.size(); i++) - delete strs_alloced[i]; - delete[] buf; - return false; - } - - memcpy(out, buf, olen); - delete[] buf; - return true; -} - -// Casts *buf to a signed or unsigned fixed-width integer of 'w' width -#define SIGN_CAST(w, buf) (is_unsigned ? *((u##w *) buf) : *((s##w *) buf)) - -bool serializeStructToString(std::string *out, - std::string format, void *value) -{ - std::ostringstream os; - std::string str; - char *f; - size_t strpos; - - char *bufpos = (char *) value; - char *fmtpos, *fmt = &format[0]; - while ((f = strtok_r(fmt, ",", &fmtpos))) { - fmt = nullptr; - bool is_unsigned = false; - int width = 0; - char valtype = *f; - - width = (int)strtol(f + 1, &f, 10); - if (width && valtype == 's') - valtype = 'i'; - - switch (valtype) { - case 'u': - is_unsigned = true; - /* FALLTHROUGH */ - case 'i': - if (width == 16) { - bufpos += PADDING(bufpos, u16); - os << SIGN_CAST(16, bufpos); - bufpos += sizeof(u16); - } else if (width == 32) { - bufpos += PADDING(bufpos, u32); - os << SIGN_CAST(32, bufpos); - bufpos += sizeof(u32); - } else if (width == 64) { - bufpos += PADDING(bufpos, u64); - os << SIGN_CAST(64, bufpos); - bufpos += sizeof(u64); - } - break; - case 'b': - bufpos += PADDING(bufpos, bool); - os << std::boolalpha << *((bool *) bufpos); - bufpos += sizeof(bool); - break; - case 'f': - bufpos += PADDING(bufpos, float); - os << *((float *) bufpos); - bufpos += sizeof(float); - break; - case 's': - bufpos += PADDING(bufpos, std::string *); - str = **((std::string **) bufpos); - - strpos = 0; - while ((strpos = str.find('"', strpos)) != std::string::npos) { - str.insert(strpos, 1, '\\'); - strpos += 2; - } - - os << str; - bufpos += sizeof(std::string *); - break; - case 'v': - if (width == 2) { - bufpos += PADDING(bufpos, v2f); - v2f *v = (v2f *) bufpos; - os << '(' << v->X << ", " << v->Y << ')'; - bufpos += sizeof(v2f); - } else { - bufpos += PADDING(bufpos, v3f); - v3f *v = (v3f *) bufpos; - os << '(' << v->X << ", " << v->Y << ", " << v->Z << ')'; - bufpos += sizeof(v3f); - } - break; - default: - return false; - } - os << ", "; - } - *out = os.str(); - - // Trim off the trailing comma and space - if (out->size() >= 2) - out->resize(out->size() - 2); - - return true; -} - -#undef SIGN_CAST - -//// -//// Other -//// - -std::string serializeHexString(const std::string &data, bool insert_spaces) -{ - std::string result; - result.reserve(data.size() * (2 + insert_spaces)); - - static const char hex_chars[] = "0123456789abcdef"; - - const size_t len = data.size(); - for (size_t i = 0; i != len; i++) { - u8 byte = data[i]; - result.push_back(hex_chars[(byte >> 4) & 0x0F]); - result.push_back(hex_chars[(byte >> 0) & 0x0F]); - if (insert_spaces && i != len - 1) - result.push_back(' '); - } - - return result; -} diff --git a/src/util/serialize.h b/src/util/serialize.h index a4b5a234a..a988a8f78 100644 --- a/src/util/serialize.h +++ b/src/util/serialize.h @@ -442,15 +442,9 @@ MAKE_STREAM_WRITE_FXN(video::SColor, ARGB8, 4); // Creates a string with the length as the first two bytes std::string serializeString(const std::string &plain); -// Creates a string with the length as the first two bytes from wide string -std::string serializeWideString(const std::wstring &plain); - // Reads a string with the length as the first two bytes std::string deSerializeString(std::istream &is); -// Reads a wide string with the length as the first two bytes -std::wstring deSerializeWideString(std::istream &is); - // Creates a string with the length as the first four bytes std::string serializeLongString(const std::string &plain); @@ -469,239 +463,3 @@ std::string serializeJsonStringIfNeeded(const std::string &s); // Parses a string serialized by serializeJsonStringIfNeeded. std::string deSerializeJsonStringIfNeeded(std::istream &is); - -// Creates a string consisting of the hexadecimal representation of `data` -std::string serializeHexString(const std::string &data, bool insert_spaces=false); - -// Creates a string containing comma delimited values of a struct whose layout is -// described by the parameter format -bool serializeStructToString(std::string *out, - std::string format, void *value); - -// Reads a comma delimited string of values into a struct whose layout is -// decribed by the parameter format -bool deSerializeStringToStruct(std::string valstr, - std::string format, void *out, size_t olen); - -//// -//// BufReader -//// - -#define MAKE_BUFREADER_GETNOEX_FXN(T, N, S) \ - inline bool get ## N ## NoEx(T *val) \ - { \ - if (pos + S > size) \ - return false; \ - *val = read ## N(data + pos); \ - pos += S; \ - return true; \ - } - -#define MAKE_BUFREADER_GET_FXN(T, N) \ - inline T get ## N() \ - { \ - T val; \ - if (!get ## N ## NoEx(&val)) \ - throw SerializationError("Attempted read past end of data"); \ - return val; \ - } - -class BufReader { -public: - BufReader(const u8 *data_, size_t size_) : - data(data_), - size(size_) - { - } - - MAKE_BUFREADER_GETNOEX_FXN(u8, U8, 1); - MAKE_BUFREADER_GETNOEX_FXN(u16, U16, 2); - MAKE_BUFREADER_GETNOEX_FXN(u32, U32, 4); - MAKE_BUFREADER_GETNOEX_FXN(u64, U64, 8); - MAKE_BUFREADER_GETNOEX_FXN(s8, S8, 1); - MAKE_BUFREADER_GETNOEX_FXN(s16, S16, 2); - MAKE_BUFREADER_GETNOEX_FXN(s32, S32, 4); - MAKE_BUFREADER_GETNOEX_FXN(s64, S64, 8); - MAKE_BUFREADER_GETNOEX_FXN(f32, F1000, 4); - MAKE_BUFREADER_GETNOEX_FXN(v2s16, V2S16, 4); - MAKE_BUFREADER_GETNOEX_FXN(v3s16, V3S16, 6); - MAKE_BUFREADER_GETNOEX_FXN(v2s32, V2S32, 8); - MAKE_BUFREADER_GETNOEX_FXN(v3s32, V3S32, 12); - MAKE_BUFREADER_GETNOEX_FXN(v3f, V3F1000, 12); - MAKE_BUFREADER_GETNOEX_FXN(video::SColor, ARGB8, 4); - - bool getStringNoEx(std::string *val); - bool getWideStringNoEx(std::wstring *val); - bool getLongStringNoEx(std::string *val); - bool getRawDataNoEx(void *data, size_t len); - - MAKE_BUFREADER_GET_FXN(u8, U8); - MAKE_BUFREADER_GET_FXN(u16, U16); - MAKE_BUFREADER_GET_FXN(u32, U32); - MAKE_BUFREADER_GET_FXN(u64, U64); - MAKE_BUFREADER_GET_FXN(s8, S8); - MAKE_BUFREADER_GET_FXN(s16, S16); - MAKE_BUFREADER_GET_FXN(s32, S32); - MAKE_BUFREADER_GET_FXN(s64, S64); - MAKE_BUFREADER_GET_FXN(f32, F1000); - MAKE_BUFREADER_GET_FXN(v2s16, V2S16); - MAKE_BUFREADER_GET_FXN(v3s16, V3S16); - MAKE_BUFREADER_GET_FXN(v2s32, V2S32); - MAKE_BUFREADER_GET_FXN(v3s32, V3S32); - MAKE_BUFREADER_GET_FXN(v3f, V3F1000); - MAKE_BUFREADER_GET_FXN(video::SColor, ARGB8); - MAKE_BUFREADER_GET_FXN(std::string, String); - MAKE_BUFREADER_GET_FXN(std::wstring, WideString); - MAKE_BUFREADER_GET_FXN(std::string, LongString); - - inline void getRawData(void *val, size_t len) - { - if (!getRawDataNoEx(val, len)) - throw SerializationError("Attempted read past end of data"); - } - - inline size_t remaining() - { - assert(pos <= size); - return size - pos; - } - - const u8 *data; - size_t size; - size_t pos = 0; -}; - -#undef MAKE_BUFREADER_GET_FXN -#undef MAKE_BUFREADER_GETNOEX_FXN - - -//// -//// Vector-based write routines -//// - -inline void putU8(std::vector *dest, u8 val) -{ - dest->push_back((val >> 0) & 0xFF); -} - -inline void putU16(std::vector *dest, u16 val) -{ - dest->push_back((val >> 8) & 0xFF); - dest->push_back((val >> 0) & 0xFF); -} - -inline void putU32(std::vector *dest, u32 val) -{ - dest->push_back((val >> 24) & 0xFF); - dest->push_back((val >> 16) & 0xFF); - dest->push_back((val >> 8) & 0xFF); - dest->push_back((val >> 0) & 0xFF); -} - -inline void putU64(std::vector *dest, u64 val) -{ - dest->push_back((val >> 56) & 0xFF); - dest->push_back((val >> 48) & 0xFF); - dest->push_back((val >> 40) & 0xFF); - dest->push_back((val >> 32) & 0xFF); - dest->push_back((val >> 24) & 0xFF); - dest->push_back((val >> 16) & 0xFF); - dest->push_back((val >> 8) & 0xFF); - dest->push_back((val >> 0) & 0xFF); -} - -inline void putS8(std::vector *dest, s8 val) -{ - putU8(dest, val); -} - -inline void putS16(std::vector *dest, s16 val) -{ - putU16(dest, val); -} - -inline void putS32(std::vector *dest, s32 val) -{ - putU32(dest, val); -} - -inline void putS64(std::vector *dest, s64 val) -{ - putU64(dest, val); -} - -inline void putF1000(std::vector *dest, f32 val) -{ - putS32(dest, val * FIXEDPOINT_FACTOR); -} - -inline void putV2S16(std::vector *dest, v2s16 val) -{ - putS16(dest, val.X); - putS16(dest, val.Y); -} - -inline void putV3S16(std::vector *dest, v3s16 val) -{ - putS16(dest, val.X); - putS16(dest, val.Y); - putS16(dest, val.Z); -} - -inline void putV2S32(std::vector *dest, v2s32 val) -{ - putS32(dest, val.X); - putS32(dest, val.Y); -} - -inline void putV3S32(std::vector *dest, v3s32 val) -{ - putS32(dest, val.X); - putS32(dest, val.Y); - putS32(dest, val.Z); -} - -inline void putV3F1000(std::vector *dest, v3f val) -{ - putF1000(dest, val.X); - putF1000(dest, val.Y); - putF1000(dest, val.Z); -} - -inline void putARGB8(std::vector *dest, video::SColor val) -{ - putU32(dest, val.color); -} - -inline void putString(std::vector *dest, const std::string &val) -{ - if (val.size() > STRING_MAX_LEN) - throw SerializationError("String too long"); - - putU16(dest, val.size()); - dest->insert(dest->end(), val.begin(), val.end()); -} - -inline void putWideString(std::vector *dest, const std::wstring &val) -{ - if (val.size() > WIDE_STRING_MAX_LEN) - throw SerializationError("String too long"); - - putU16(dest, val.size()); - for (size_t i = 0; i != val.size(); i++) - putU16(dest, val[i]); -} - -inline void putLongString(std::vector *dest, const std::string &val) -{ - if (val.size() > LONG_STRING_MAX_LEN) - throw SerializationError("String too long"); - - putU32(dest, val.size()); - dest->insert(dest->end(), val.begin(), val.end()); -} - -inline void putRawData(std::vector *dest, const void *src, size_t len) -{ - dest->insert(dest->end(), (u8 *)src, (u8 *)src + len); -} From 947466ab28129fd69e6630974c6c4e901f2bebc6 Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Sun, 20 Sep 2020 13:12:55 +0200 Subject: [PATCH 084/266] (se)SerializeString: Include max length in the name This commit clarifies the maximal length of the serialized strings. It will avoid accidental use of serializeString() when a larger string can be expected. Removes unused Wide String serialization functions --- src/client/content_cao.cpp | 12 +++--- src/content_nodemeta.cpp | 18 ++++----- src/database/database-leveldb.cpp | 16 ++++---- src/itemdef.cpp | 52 ++++++++++++------------ src/mapblock.cpp | 4 +- src/mapgen/mg_schematic.cpp | 4 +- src/nameidmapping.cpp | 4 +- src/network/clientpackethandler.cpp | 8 ++-- src/nodedef.cpp | 40 +++++++++--------- src/nodemetadata.cpp | 8 ++-- src/object_properties.cpp | 28 ++++++------- src/particles.cpp | 4 +- src/server.cpp | 4 +- src/server/luaentity_sao.cpp | 28 ++++++------- src/server/player_sao.cpp | 16 ++++---- src/server/serveractiveobject.cpp | 2 +- src/server/unit_sao.cpp | 6 +-- src/sound.h | 4 +- src/staticobject.cpp | 4 +- src/tool.cpp | 8 ++-- src/unittest/test_serialization.cpp | 50 ++++++++++------------- src/util/serialize.cpp | 63 ++++------------------------- src/util/serialize.h | 8 ++-- 23 files changed, 168 insertions(+), 223 deletions(-) diff --git a/src/client/content_cao.cpp b/src/client/content_cao.cpp index 599139aa3..71a9d4b54 100644 --- a/src/client/content_cao.cpp +++ b/src/client/content_cao.cpp @@ -371,7 +371,7 @@ void GenericCAO::processInitData(const std::string &data) } // PROTOCOL_VERSION >= 37 - m_name = deSerializeString(is); + m_name = deSerializeString16(is); m_is_player = readU8(is); m_id = readU16(is); m_position = readV3F32(is); @@ -381,7 +381,7 @@ void GenericCAO::processInitData(const std::string &data) const u8 num_messages = readU8(is); for (int i = 0; i < num_messages; i++) { - std::string message = deSerializeLongString(is); + std::string message = deSerializeString32(is); processMessage(message); } @@ -1657,7 +1657,7 @@ void GenericCAO::processMessage(const std::string &data) rot_translator.update(m_rotation, false, update_interval); updateNodePos(); } else if (cmd == AO_CMD_SET_TEXTURE_MOD) { - std::string mod = deSerializeString(is); + std::string mod = deSerializeString16(is); // immediately reset a engine issued texture modifier if a mod sends a different one if (m_reset_textures_timer > 0) { @@ -1735,7 +1735,7 @@ void GenericCAO::processMessage(const std::string &data) m_animation_speed = readF32(is); updateAnimationSpeed(); } else if (cmd == AO_CMD_SET_BONE_POSITION) { - std::string bone = deSerializeString(is); + std::string bone = deSerializeString16(is); v3f position = readV3F32(is); v3f rotation = readV3F32(is); m_bone_position[bone] = core::vector2d(position, rotation); @@ -1743,7 +1743,7 @@ void GenericCAO::processMessage(const std::string &data) // updateBonePosition(); now called every step } else if (cmd == AO_CMD_ATTACH_TO) { u16 parent_id = readS16(is); - std::string bone = deSerializeString(is); + std::string bone = deSerializeString16(is); v3f position = readV3F32(is); v3f rotation = readV3F32(is); @@ -1793,7 +1793,7 @@ void GenericCAO::processMessage(const std::string &data) int armor_groups_size = readU16(is); for(int i=0; igetInventory()->deSerialize(is); - deSerializeLongString(is); // m_text - deSerializeString(is); // m_owner + deSerializeString32(is); // m_text + deSerializeString16(is); // m_owner - meta->setString("infotext",deSerializeString(is)); - meta->setString("formspec",deSerializeString(is)); + meta->setString("infotext",deSerializeString16(is)); + meta->setString("formspec",deSerializeString16(is)); readU8(is); // m_allow_text_input readU8(is); // m_allow_removal readU8(is); // m_enforce_owner int num_vars = readU32(is); for(int i=0; isetString(name, var); } return false; } else if(id == NODEMETA_SIGN) // SignNodeMetadata { - meta->setString("text", deSerializeString(is)); + meta->setString("text", deSerializeString16(is)); //meta->setString("infotext","\"${text}\""); meta->setString("infotext", std::string("\"") + meta->getString("text") + "\""); @@ -87,7 +87,7 @@ static bool content_nodemeta_deserialize_legacy_body( } else if(id == NODEMETA_LOCKABLE_CHEST) // LockingChestNodeMetadata { - meta->setString("owner", deSerializeString(is)); + meta->setString("owner", deSerializeString16(is)); meta->getInventory()->deSerialize(is); // Rename inventory list "0" to "main" @@ -138,7 +138,7 @@ static bool content_nodemeta_deserialize_legacy_meta( s16 id = readS16(is); // Read data - std::string data = deSerializeString(is); + std::string data = deSerializeString16(is); std::istringstream tmp_is(data, std::ios::binary); return content_nodemeta_deserialize_legacy_body(tmp_is, id, meta); } diff --git a/src/database/database-leveldb.cpp b/src/database/database-leveldb.cpp index 1976ae13d..73cd63f6d 100644 --- a/src/database/database-leveldb.cpp +++ b/src/database/database-leveldb.cpp @@ -145,8 +145,8 @@ void PlayerDatabaseLevelDB::savePlayer(RemotePlayer *player) StringMap stringvars = sao->getMeta().getStrings(); writeU32(os, stringvars.size()); for (const auto &it : stringvars) { - os << serializeString(it.first); - os << serializeLongString(it.second); + os << serializeString16(it.first); + os << serializeString32(it.second); } player->inventory.serialize(os); @@ -183,8 +183,8 @@ bool PlayerDatabaseLevelDB::loadPlayer(RemotePlayer *player, PlayerSAO *sao) u32 attribute_count = readU32(is); for (u32 i = 0; i < attribute_count; i++) { - std::string name = deSerializeString(is); - std::string value = deSerializeLongString(is); + std::string name = deSerializeString16(is); + std::string value = deSerializeString32(is); sao->getMeta().setString(name, value); } sao->getMeta().setModified(false); @@ -247,13 +247,13 @@ bool AuthDatabaseLevelDB::getAuth(const std::string &name, AuthEntry &res) res.id = 1; res.name = name; - res.password = deSerializeString(is); + res.password = deSerializeString16(is); u16 privilege_count = readU16(is); res.privileges.clear(); res.privileges.reserve(privilege_count); for (u16 i = 0; i < privilege_count; i++) { - res.privileges.push_back(deSerializeString(is)); + res.privileges.push_back(deSerializeString16(is)); } res.last_login = readS64(is); @@ -264,14 +264,14 @@ bool AuthDatabaseLevelDB::saveAuth(const AuthEntry &authEntry) { std::ostringstream os; writeU8(os, 1); - os << serializeString(authEntry.password); + os << serializeString16(authEntry.password); size_t privilege_count = authEntry.privileges.size(); FATAL_ERROR_IF(privilege_count > U16_MAX, "Unsupported number of privileges"); writeU16(os, privilege_count); for (const std::string &privilege : authEntry.privileges) { - os << serializeString(privilege); + os << serializeString16(privilege); } writeS64(os, authEntry.last_login); diff --git a/src/itemdef.cpp b/src/itemdef.cpp index 8e0492827..df20bdf15 100644 --- a/src/itemdef.cpp +++ b/src/itemdef.cpp @@ -128,10 +128,10 @@ void ItemDefinition::serialize(std::ostream &os, u16 protocol_version) const u8 version = 6; writeU8(os, version); writeU8(os, type); - os << serializeString(name); - os << serializeString(description); - os << serializeString(inventory_image); - os << serializeString(wield_image); + os << serializeString16(name); + os << serializeString16(description); + os << serializeString16(inventory_image); + os << serializeString16(wield_image); writeV3F32(os, wield_scale); writeS16(os, stack_max); writeU8(os, usable); @@ -143,25 +143,25 @@ void ItemDefinition::serialize(std::ostream &os, u16 protocol_version) const tool_capabilities->serialize(tmp_os, protocol_version); tool_capabilities_s = tmp_os.str(); } - os << serializeString(tool_capabilities_s); + os << serializeString16(tool_capabilities_s); writeU16(os, groups.size()); for (const auto &group : groups) { - os << serializeString(group.first); + os << serializeString16(group.first); writeS16(os, group.second); } - os << serializeString(node_placement_prediction); + os << serializeString16(node_placement_prediction); // Version from ContentFeatures::serialize to keep in sync sound_place.serialize(os, CONTENTFEATURES_VERSION); sound_place_failed.serialize(os, CONTENTFEATURES_VERSION); writeF32(os, range); - os << serializeString(palette_image); + os << serializeString16(palette_image); writeARGB8(os, color); - os << serializeString(inventory_overlay); - os << serializeString(wield_overlay); + os << serializeString16(inventory_overlay); + os << serializeString16(wield_overlay); } void ItemDefinition::deSerialize(std::istream &is) @@ -175,16 +175,16 @@ void ItemDefinition::deSerialize(std::istream &is) throw SerializationError("unsupported ItemDefinition version"); type = (enum ItemType)readU8(is); - name = deSerializeString(is); - description = deSerializeString(is); - inventory_image = deSerializeString(is); - wield_image = deSerializeString(is); + name = deSerializeString16(is); + description = deSerializeString16(is); + inventory_image = deSerializeString16(is); + wield_image = deSerializeString16(is); wield_scale = readV3F32(is); stack_max = readS16(is); usable = readU8(is); liquids_pointable = readU8(is); - std::string tool_capabilities_s = deSerializeString(is); + std::string tool_capabilities_s = deSerializeString16(is); if (!tool_capabilities_s.empty()) { std::istringstream tmp_is(tool_capabilities_s, std::ios::binary); tool_capabilities = new ToolCapabilities; @@ -194,22 +194,22 @@ void ItemDefinition::deSerialize(std::istream &is) groups.clear(); u32 groups_size = readU16(is); for(u32 i=0; iserialize(tmp_os, protocol_version); - os << serializeString(tmp_os.str()); + os << serializeString16(tmp_os.str()); } writeU16(os, m_aliases.size()); for (const auto &it : m_aliases) { - os << serializeString(it.first); - os << serializeString(it.second); + os << serializeString16(it.first); + os << serializeString16(it.second); } } void deSerialize(std::istream &is) @@ -543,7 +543,7 @@ public: for(u16 i=0; iidef()); } else { - //std::string data = deSerializeLongString(is); + //std::string data = deSerializeString32(is); std::ostringstream oss(std::ios_base::binary); decompressZlib(is, oss); std::istringstream iss(oss.str(), std::ios_base::binary); diff --git a/src/mapgen/mg_schematic.cpp b/src/mapgen/mg_schematic.cpp index ba102d997..dfd414709 100644 --- a/src/mapgen/mg_schematic.cpp +++ b/src/mapgen/mg_schematic.cpp @@ -314,7 +314,7 @@ bool Schematic::deserializeFromMts(std::istream *is, //// Read node names u16 nidmapcount = readU16(ss); for (int i = 0; i != nidmapcount; i++) { - std::string name = deSerializeString(ss); + std::string name = deSerializeString16(ss); // Instances of "ignore" from v1 are converted to air (and instances // are fixed to have MTSCHEM_PROB_NEVER later on). @@ -372,7 +372,7 @@ bool Schematic::serializeToMts(std::ostream *os, writeU16(ss, names.size()); // name count for (size_t i = 0; i != names.size(); i++) - ss << serializeString(names[i]); // node names + ss << serializeString16(names[i]); // node names // compressed bulk node data MapNode::serializeBulk(ss, SER_FMT_VER_HIGHEST_WRITE, diff --git a/src/nameidmapping.cpp b/src/nameidmapping.cpp index bd5bb1fc8..05cfae069 100644 --- a/src/nameidmapping.cpp +++ b/src/nameidmapping.cpp @@ -27,7 +27,7 @@ void NameIdMapping::serialize(std::ostream &os) const writeU16(os, m_id_to_name.size()); for (const auto &i : m_id_to_name) { writeU16(os, i.first); - os << serializeString(i.second); + os << serializeString16(i.second); } } @@ -41,7 +41,7 @@ void NameIdMapping::deSerialize(std::istream &is) m_name_to_id.clear(); for (u32 i = 0; i < count; i++) { u16 id = readU16(is); - std::string name = deSerializeString(is); + std::string name = deSerializeString16(is); m_id_to_name[id] = name; m_name_to_id[name] = id; } diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index 8d87ff8f2..5683564a0 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -497,7 +497,7 @@ void Client::handleCommand_ActiveObjectMessages(NetworkPacket* pkt) if (!is.good()) break; - std::string message = deSerializeString(is); + std::string message = deSerializeString16(is); // Pass on to the environment m_env.processActiveObjectMessage(id, message); @@ -994,7 +994,7 @@ void Client::handleCommand_AddParticleSpawner(NetworkPacket* pkt) p.minsize = readF32(is); p.maxsize = readF32(is); p.collisiondetection = readU8(is); - p.texture = deSerializeLongString(is); + p.texture = deSerializeString32(is); server_id = readU32(is); @@ -1207,11 +1207,11 @@ void Client::handleCommand_HudSetSky(NetworkPacket* pkt) SkyboxParams skybox; skybox.bgcolor = video::SColor(readARGB8(is)); - skybox.type = std::string(deSerializeString(is)); + skybox.type = std::string(deSerializeString16(is)); u16 count = readU16(is); for (size_t i = 0; i < count; i++) - skybox.textures.emplace_back(deSerializeString(is)); + skybox.textures.emplace_back(deSerializeString16(is)); skybox.clouds = true; try { diff --git a/src/nodedef.cpp b/src/nodedef.cpp index 392f5eb98..3a5934cf3 100644 --- a/src/nodedef.cpp +++ b/src/nodedef.cpp @@ -207,7 +207,7 @@ void TileDef::serialize(std::ostream &os, u16 protocol_version) const u8 version = 6; writeU8(os, version); - os << serializeString(name); + os << serializeString16(name); animation.serialize(os, version); bool has_scale = scale > 0; u16 flags = 0; @@ -241,7 +241,7 @@ void TileDef::deSerialize(std::istream &is, u8 contentfeatures_version, int version = readU8(is); if (version < 6) throw SerializationError("unsupported TileDef version"); - name = deSerializeString(is); + name = deSerializeString16(is); animation.deSerialize(is, version); u16 flags = readU16(is); backface_culling = flags & TILE_FLAG_BACKFACE_CULLING; @@ -416,10 +416,10 @@ void ContentFeatures::serialize(std::ostream &os, u16 protocol_version) const writeU8(os, version); // general - os << serializeString(name); + os << serializeString16(name); writeU16(os, groups.size()); for (const auto &group : groups) { - os << serializeString(group.first); + os << serializeString16(group.first); writeS16(os, group.second); } writeU8(os, param_type); @@ -427,7 +427,7 @@ void ContentFeatures::serialize(std::ostream &os, u16 protocol_version) const // visual writeU8(os, drawtype); - os << serializeString(mesh); + os << serializeString16(mesh); writeF32(os, visual_scale); writeU8(os, 6); for (const TileDef &td : tiledef) @@ -442,7 +442,7 @@ void ContentFeatures::serialize(std::ostream &os, u16 protocol_version) const writeU8(os, color.getRed()); writeU8(os, color.getGreen()); writeU8(os, color.getBlue()); - os << serializeString(palette_name); + os << serializeString16(palette_name); writeU8(os, waving); writeU8(os, connect_sides); writeU16(os, connects_to_ids.size()); @@ -470,8 +470,8 @@ void ContentFeatures::serialize(std::ostream &os, u16 protocol_version) const // liquid writeU8(os, liquid_type); - os << serializeString(liquid_alternative_flowing); - os << serializeString(liquid_alternative_source); + os << serializeString16(liquid_alternative_flowing); + os << serializeString16(liquid_alternative_source); writeU8(os, liquid_viscosity); writeU8(os, liquid_renewable); writeU8(os, liquid_range); @@ -492,7 +492,7 @@ void ContentFeatures::serialize(std::ostream &os, u16 protocol_version) const writeU8(os, legacy_facedir_simple); writeU8(os, legacy_wallmounted); - os << serializeString(node_dig_prediction); + os << serializeString16(node_dig_prediction); writeU8(os, leveled_max); } @@ -519,11 +519,11 @@ void ContentFeatures::deSerialize(std::istream &is) throw SerializationError("unsupported ContentFeatures version"); // general - name = deSerializeString(is); + name = deSerializeString16(is); groups.clear(); u32 groups_size = readU16(is); for (u32 i = 0; i < groups_size; i++) { - std::string name = deSerializeString(is); + std::string name = deSerializeString16(is); int value = readS16(is); groups[name] = value; } @@ -532,7 +532,7 @@ void ContentFeatures::deSerialize(std::istream &is) // visual drawtype = (enum NodeDrawType) readU8(is); - mesh = deSerializeString(is); + mesh = deSerializeString16(is); visual_scale = readF32(is); if (readU8(is) != 6) throw SerializationError("unsupported tile count"); @@ -548,7 +548,7 @@ void ContentFeatures::deSerialize(std::istream &is) color.setRed(readU8(is)); color.setGreen(readU8(is)); color.setBlue(readU8(is)); - palette_name = deSerializeString(is); + palette_name = deSerializeString16(is); waving = readU8(is); connect_sides = readU8(is); u16 connects_to_size = readU16(is); @@ -578,8 +578,8 @@ void ContentFeatures::deSerialize(std::istream &is) // liquid liquid_type = (enum LiquidType) readU8(is); - liquid_alternative_flowing = deSerializeString(is); - liquid_alternative_source = deSerializeString(is); + liquid_alternative_flowing = deSerializeString16(is); + liquid_alternative_source = deSerializeString16(is); liquid_viscosity = readU8(is); liquid_renewable = readU8(is); liquid_range = readU8(is); @@ -601,7 +601,7 @@ void ContentFeatures::deSerialize(std::istream &is) legacy_wallmounted = readU8(is); try { - node_dig_prediction = deSerializeString(is); + node_dig_prediction = deSerializeString16(is); u8 tmp_leveled_max = readU8(is); if (is.eof()) /* readU8 doesn't throw exceptions so we have to do this */ throw SerializationError(""); @@ -1472,7 +1472,7 @@ void NodeDefManager::serialize(std::ostream &os, u16 protocol_version) const // strict version incompatibilities std::ostringstream wrapper_os(std::ios::binary); f->serialize(wrapper_os, protocol_version); - os2<= 2) writeU8(os, (priv) ? 1 : 0); } @@ -63,8 +63,8 @@ void NodeMetadata::deSerialize(std::istream &is, u8 version) clear(); int num_vars = readU32(is); for(int i=0; i= 2) { if (readU8(is) == 1) diff --git a/src/object_properties.cpp b/src/object_properties.cpp index 8d51bcbfa..c31c667e7 100644 --- a/src/object_properties.cpp +++ b/src/object_properties.cpp @@ -84,11 +84,11 @@ void ObjectProperties::serialize(std::ostream &os) const writeV3F32(os, selectionbox.MinEdge); writeV3F32(os, selectionbox.MaxEdge); writeU8(os, pointable); - os << serializeString(visual); + os << serializeString16(visual); writeV3F32(os, visual_size); writeU16(os, textures.size()); for (const std::string &texture : textures) { - os << serializeString(texture); + os << serializeString16(texture); } writeV2S16(os, spritediv); writeV2S16(os, initial_sprite_basepos); @@ -96,7 +96,7 @@ void ObjectProperties::serialize(std::ostream &os) const writeU8(os, makes_footstep_sound); writeF32(os, automatic_rotate); // Added in protocol version 14 - os << serializeString(mesh); + os << serializeString16(mesh); writeU16(os, colors.size()); for (video::SColor color : colors) { writeARGB8(os, color); @@ -106,17 +106,17 @@ void ObjectProperties::serialize(std::ostream &os) const writeU8(os, automatic_face_movement_dir); writeF32(os, automatic_face_movement_dir_offset); writeU8(os, backface_culling); - os << serializeString(nametag); + os << serializeString16(nametag); writeARGB8(os, nametag_color); writeF32(os, automatic_face_movement_max_rotation_per_sec); - os << serializeString(infotext); - os << serializeString(wield_item); + os << serializeString16(infotext); + os << serializeString16(wield_item); writeS8(os, glow); writeU16(os, breath_max); writeF32(os, eye_height); writeF32(os, zoom_fov); writeU8(os, use_texture_alpha); - os << serializeString(damage_texture_modifier); + os << serializeString16(damage_texture_modifier); writeU8(os, shaded); // Add stuff only at the bottom. @@ -137,19 +137,19 @@ void ObjectProperties::deSerialize(std::istream &is) selectionbox.MinEdge = readV3F32(is); selectionbox.MaxEdge = readV3F32(is); pointable = readU8(is); - visual = deSerializeString(is); + visual = deSerializeString16(is); visual_size = readV3F32(is); textures.clear(); u32 texture_count = readU16(is); for (u32 i = 0; i < texture_count; i++){ - textures.push_back(deSerializeString(is)); + textures.push_back(deSerializeString16(is)); } spritediv = readV2S16(is); initial_sprite_basepos = readV2S16(is); is_visible = readU8(is); makes_footstep_sound = readU8(is); automatic_rotate = readF32(is); - mesh = deSerializeString(is); + mesh = deSerializeString16(is); colors.clear(); u32 color_count = readU16(is); for (u32 i = 0; i < color_count; i++){ @@ -160,18 +160,18 @@ void ObjectProperties::deSerialize(std::istream &is) automatic_face_movement_dir = readU8(is); automatic_face_movement_dir_offset = readF32(is); backface_culling = readU8(is); - nametag = deSerializeString(is); + nametag = deSerializeString16(is); nametag_color = readARGB8(is); automatic_face_movement_max_rotation_per_sec = readF32(is); - infotext = deSerializeString(is); - wield_item = deSerializeString(is); + infotext = deSerializeString16(is); + wield_item = deSerializeString16(is); glow = readS8(is); breath_max = readU16(is); eye_height = readF32(is); zoom_fov = readF32(is); use_texture_alpha = readU8(is); try { - damage_texture_modifier = deSerializeString(is); + damage_texture_modifier = deSerializeString16(is); u8 tmp = readU8(is); if (is.eof()) throw SerializationError(""); diff --git a/src/particles.cpp b/src/particles.cpp index fd81238dc..14c987958 100644 --- a/src/particles.cpp +++ b/src/particles.cpp @@ -28,7 +28,7 @@ void ParticleParameters::serialize(std::ostream &os, u16 protocol_ver) const writeF32(os, expirationtime); writeF32(os, size); writeU8(os, collisiondetection); - os << serializeLongString(texture); + os << serializeString32(texture); writeU8(os, vertical); writeU8(os, collision_removal); animation.serialize(os, 6); /* NOT the protocol ver */ @@ -47,7 +47,7 @@ void ParticleParameters::deSerialize(std::istream &is, u16 protocol_ver) expirationtime = readF32(is); size = readF32(is); collisiondetection = readU8(is); - texture = deSerializeLongString(is); + texture = deSerializeString32(is); vertical = readU8(is); collision_removal = readU8(is); animation.deSerialize(is, 6); /* NOT the protocol ver */ diff --git a/src/server.cpp b/src/server.cpp index d40ff259f..982f904f4 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -802,7 +802,7 @@ void Server::AsyncRunStep(bool initial_step) // u16 id // std::string data buffer.append(idbuf, sizeof(idbuf)); - buffer.append(serializeString(aom.datastring)); + buffer.append(serializeString16(aom.datastring)); } } /* @@ -1993,7 +1993,7 @@ void Server::SendActiveObjectRemoveAdd(RemoteClient *client, PlayerSAO *playersa writeU8((u8*)buf, type); data.append(buf, 1); - data.append(serializeLongString( + data.append(serializeString32( obj->getClientInitializationData(client->net_proto_version))); // Add to known objects diff --git a/src/server/luaentity_sao.cpp b/src/server/luaentity_sao.cpp index d504c42ca..f20914f7f 100644 --- a/src/server/luaentity_sao.cpp +++ b/src/server/luaentity_sao.cpp @@ -42,8 +42,8 @@ LuaEntitySAO::LuaEntitySAO(ServerEnvironment *env, v3f pos, const std::string &d u8 version2 = 0; u8 version = readU8(is); - name = deSerializeString(is); - state = deSerializeLongString(is); + name = deSerializeString16(is); + state = deSerializeString32(is); if (version < 1) break; @@ -225,7 +225,7 @@ std::string LuaEntitySAO::getClientInitializationData(u16 protocol_version) // PROTOCOL_VERSION >= 37 writeU8(os, 1); // version - os << serializeString(""); // name + os << serializeString16(""); // name writeU8(os, 0); // is_player writeU16(os, getId()); //id writeV3F32(os, m_base_position); @@ -233,26 +233,26 @@ std::string LuaEntitySAO::getClientInitializationData(u16 protocol_version) writeU16(os, m_hp); std::ostringstream msg_os(std::ios::binary); - msg_os << serializeLongString(getPropertyPacket()); // message 1 - msg_os << serializeLongString(generateUpdateArmorGroupsCommand()); // 2 - msg_os << serializeLongString(generateUpdateAnimationCommand()); // 3 + msg_os << serializeString32(getPropertyPacket()); // message 1 + msg_os << serializeString32(generateUpdateArmorGroupsCommand()); // 2 + msg_os << serializeString32(generateUpdateAnimationCommand()); // 3 for (const auto &bone_pos : m_bone_position) { - msg_os << serializeLongString(generateUpdateBonePositionCommand( + msg_os << serializeString32(generateUpdateBonePositionCommand( bone_pos.first, bone_pos.second.X, bone_pos.second.Y)); // m_bone_position.size } - msg_os << serializeLongString(generateUpdateAttachmentCommand()); // 4 + msg_os << serializeString32(generateUpdateAttachmentCommand()); // 4 int message_count = 4 + m_bone_position.size(); for (const auto &id : getAttachmentChildIds()) { if (ServerActiveObject *obj = m_env->getActiveObject(id)) { message_count++; - msg_os << serializeLongString(obj->generateUpdateInfantCommand( + msg_os << serializeString32(obj->generateUpdateInfantCommand( id, protocol_version)); } } - msg_os << serializeLongString(generateSetTextureModCommand()); + msg_os << serializeString32(generateSetTextureModCommand()); message_count++; writeU8(os, message_count); @@ -270,14 +270,14 @@ void LuaEntitySAO::getStaticData(std::string *result) const // version must be 1 to keep backwards-compatibility. See version2 writeU8(os, 1); // name - os<getScriptIface()-> luaentity_GetStaticdata(m_id); - os<= 15 writeU8(os, 1); // version - os << serializeString(m_player->getName()); // name + os << serializeString16(m_player->getName()); // name writeU8(os, 1); // is_player writeS16(os, getId()); // id writeV3F32(os, m_base_position); @@ -117,22 +117,22 @@ std::string PlayerSAO::getClientInitializationData(u16 protocol_version) writeU16(os, getHP()); std::ostringstream msg_os(std::ios::binary); - msg_os << serializeLongString(getPropertyPacket()); // message 1 - msg_os << serializeLongString(generateUpdateArmorGroupsCommand()); // 2 - msg_os << serializeLongString(generateUpdateAnimationCommand()); // 3 + msg_os << serializeString32(getPropertyPacket()); // message 1 + msg_os << serializeString32(generateUpdateArmorGroupsCommand()); // 2 + msg_os << serializeString32(generateUpdateAnimationCommand()); // 3 for (const auto &bone_pos : m_bone_position) { - msg_os << serializeLongString(generateUpdateBonePositionCommand( + msg_os << serializeString32(generateUpdateBonePositionCommand( bone_pos.first, bone_pos.second.X, bone_pos.second.Y)); // m_bone_position.size } - msg_os << serializeLongString(generateUpdateAttachmentCommand()); // 4 - msg_os << serializeLongString(generateUpdatePhysicsOverrideCommand()); // 5 + msg_os << serializeString32(generateUpdateAttachmentCommand()); // 4 + msg_os << serializeString32(generateUpdatePhysicsOverrideCommand()); // 5 int message_count = 5 + m_bone_position.size(); for (const auto &id : getAttachmentChildIds()) { if (ServerActiveObject *obj = m_env->getActiveObject(id)) { message_count++; - msg_os << serializeLongString(obj->generateUpdateInfantCommand( + msg_os << serializeString32(obj->generateUpdateInfantCommand( id, protocol_version)); } } diff --git a/src/server/serveractiveobject.cpp b/src/server/serveractiveobject.cpp index 3341dc008..8cb59b2d6 100644 --- a/src/server/serveractiveobject.cpp +++ b/src/server/serveractiveobject.cpp @@ -61,7 +61,7 @@ std::string ServerActiveObject::generateUpdateInfantCommand(u16 infant_id, u16 p // Clients since 4aa9a66 so no longer need this data // Version 38 is the first bump after that commit. // See also: ClientEnvironment::addActiveObject - os << serializeLongString(getClientInitializationData(protocol_version)); + os << serializeString32(getClientInitializationData(protocol_version)); } return os.str(); } diff --git a/src/server/unit_sao.cpp b/src/server/unit_sao.cpp index ef0e87f2c..d906e885e 100644 --- a/src/server/unit_sao.cpp +++ b/src/server/unit_sao.cpp @@ -242,7 +242,7 @@ std::string UnitSAO::generateUpdateAttachmentCommand() const writeU8(os, AO_CMD_ATTACH_TO); // parameters writeS16(os, m_attachment_parent_id); - os << serializeString(m_attachment_bone); + os << serializeString16(m_attachment_bone); writeV3F32(os, m_attachment_position); writeV3F32(os, m_attachment_rotation); return os.str(); @@ -255,7 +255,7 @@ std::string UnitSAO::generateUpdateBonePositionCommand( // command writeU8(os, AO_CMD_SET_BONE_POSITION); // parameters - os << serializeString(bone); + os << serializeString16(bone); writeV3F32(os, position); writeV3F32(os, rotation); return os.str(); @@ -291,7 +291,7 @@ std::string UnitSAO::generateUpdateArmorGroupsCommand() const writeU8(os, AO_CMD_UPDATE_ARMOR_GROUPS); writeU16(os, m_armor_groups.size()); for (const auto &armor_group : m_armor_groups) { - os << serializeString(armor_group.first); + os << serializeString16(armor_group.first); writeS16(os, armor_group.second); } return os.str(); diff --git a/src/sound.h b/src/sound.h index 6cbd55e8f..6f7b0a1af 100644 --- a/src/sound.h +++ b/src/sound.h @@ -39,7 +39,7 @@ struct SimpleSoundSpec // keep in sync with item definitions void serialize(std::ostream &os, u8 cf_version) const { - os << serializeString(name); + os << serializeString16(name); writeF32(os, gain); writeF32(os, pitch); writeF32(os, fade); @@ -49,7 +49,7 @@ struct SimpleSoundSpec void deSerialize(std::istream &is, u8 cf_version) { - name = deSerializeString(is); + name = deSerializeString16(is); gain = readF32(is); pitch = readF32(is); fade = readF32(is); diff --git a/src/staticobject.cpp b/src/staticobject.cpp index 5ccb7baf5..86e455b9f 100644 --- a/src/staticobject.cpp +++ b/src/staticobject.cpp @@ -35,7 +35,7 @@ void StaticObject::serialize(std::ostream &os) // pos writeV3F1000(os, pos); // data - os<uses); writeS16(os, cap->maxlevel); writeU32(os, cap->times.size()); @@ -79,7 +79,7 @@ void ToolCapabilities::serialize(std::ostream &os, u16 protocol_version) const writeU32(os, damageGroups.size()); for (const auto &damageGroup : damageGroups) { - os << serializeString(damageGroup.first); + os << serializeString16(damageGroup.first); writeS16(os, damageGroup.second); } @@ -98,7 +98,7 @@ void ToolCapabilities::deSerialize(std::istream &is) groupcaps.clear(); u32 groupcaps_size = readU32(is); for (u32 i = 0; i < groupcaps_size; i++) { - std::string name = deSerializeString(is); + std::string name = deSerializeString16(is); ToolGroupCap cap; cap.uses = readS16(is); cap.maxlevel = readS16(is); @@ -113,7 +113,7 @@ void ToolCapabilities::deSerialize(std::istream &is) u32 damage_groups_size = readU32(is); for (u32 i = 0; i < damage_groups_size; i++) { - std::string name = deSerializeString(is); + std::string name = deSerializeString16(is); s16 rating = readS16(is); damageGroups[name] = rating; } diff --git a/src/unittest/test_serialization.cpp b/src/unittest/test_serialization.cpp index d72bf0d4c..660d77d02 100644 --- a/src/unittest/test_serialization.cpp +++ b/src/unittest/test_serialization.cpp @@ -44,7 +44,7 @@ public: std::wstring teststring2_w; std::string teststring2_w_encoded; - static const u8 test_serialized_data[12 * 13 - 8]; + static const u8 test_serialized_data[12 * 11 - 2]; }; static TestSerialization g_test_instance; @@ -91,21 +91,21 @@ void TestSerialization::buildTestStrings() void TestSerialization::testSerializeString() { // Test blank string - UASSERT(serializeString("") == mkstr("\0\0")); + UASSERT(serializeString16("") == mkstr("\0\0")); // Test basic string - UASSERT(serializeString("Hello world!") == mkstr("\0\14Hello world!")); + UASSERT(serializeString16("Hello world!") == mkstr("\0\14Hello world!")); // Test character range - UASSERT(serializeString(teststring2) == mkstr("\1\0") + teststring2); + UASSERT(serializeString16(teststring2) == mkstr("\1\0") + teststring2); } void TestSerialization::testDeSerializeString() { // Test deserialize { - std::istringstream is(serializeString(teststring2), std::ios::binary); - UASSERT(deSerializeString(is) == teststring2); + std::istringstream is(serializeString16(teststring2), std::ios::binary); + UASSERT(deSerializeString16(is) == teststring2); UASSERT(!is.eof()); is.get(); UASSERT(is.eof()); @@ -114,34 +114,34 @@ void TestSerialization::testDeSerializeString() // Test deserialize an incomplete length specifier { std::istringstream is(mkstr("\x53"), std::ios::binary); - EXCEPTION_CHECK(SerializationError, deSerializeString(is)); + EXCEPTION_CHECK(SerializationError, deSerializeString16(is)); } // Test deserialize a string with incomplete data { std::istringstream is(mkstr("\x00\x55 abcdefg"), std::ios::binary); - EXCEPTION_CHECK(SerializationError, deSerializeString(is)); + EXCEPTION_CHECK(SerializationError, deSerializeString16(is)); } } void TestSerialization::testSerializeLongString() { // Test blank string - UASSERT(serializeLongString("") == mkstr("\0\0\0\0")); + UASSERT(serializeString32("") == mkstr("\0\0\0\0")); // Test basic string - UASSERT(serializeLongString("Hello world!") == mkstr("\0\0\0\14Hello world!")); + UASSERT(serializeString32("Hello world!") == mkstr("\0\0\0\14Hello world!")); // Test character range - UASSERT(serializeLongString(teststring2) == mkstr("\0\0\1\0") + teststring2); + UASSERT(serializeString32(teststring2) == mkstr("\0\0\1\0") + teststring2); } void TestSerialization::testDeSerializeLongString() { // Test deserialize { - std::istringstream is(serializeLongString(teststring2), std::ios::binary); - UASSERT(deSerializeLongString(is) == teststring2); + std::istringstream is(serializeString32(teststring2), std::ios::binary); + UASSERT(deSerializeString32(is) == teststring2); UASSERT(!is.eof()); is.get(); UASSERT(is.eof()); @@ -150,19 +150,19 @@ void TestSerialization::testDeSerializeLongString() // Test deserialize an incomplete length specifier { std::istringstream is(mkstr("\x53"), std::ios::binary); - EXCEPTION_CHECK(SerializationError, deSerializeLongString(is)); + EXCEPTION_CHECK(SerializationError, deSerializeString32(is)); } // Test deserialize a string with incomplete data { std::istringstream is(mkstr("\x00\x00\x00\x05 abc"), std::ios::binary); - EXCEPTION_CHECK(SerializationError, deSerializeLongString(is)); + EXCEPTION_CHECK(SerializationError, deSerializeString32(is)); } // Test deserialize a string with a length too large { std::istringstream is(mkstr("\xFF\xFF\xFF\xFF blah"), std::ios::binary); - EXCEPTION_CHECK(SerializationError, deSerializeLongString(is)); + EXCEPTION_CHECK(SerializationError, deSerializeString32(is)); } } @@ -235,19 +235,17 @@ void TestSerialization::testStreamRead() UASSERT(readF1000(is) == F1000_MIN); UASSERT(readF1000(is) == F1000_MAX); - UASSERT(deSerializeString(is) == "foobar!"); + UASSERT(deSerializeString16(is) == "foobar!"); UASSERT(readV2S16(is) == v2s16(500, 500)); UASSERT(readV3S16(is) == v3s16(4207, 604, -30)); UASSERT(readV2S32(is) == v2s32(1920, 1080)); UASSERT(readV3S32(is) == v3s32(-400, 6400054, 290549855)); - UASSERT(deSerializeWideString(is) == L"\x02~woof~\x5455"); - UASSERT(readV3F1000(is) == v3f(500, 10024.2f, -192.54f)); UASSERT(readARGB8(is) == video::SColor(255, 128, 50, 128)); - UASSERT(deSerializeLongString(is) == "some longer string here"); + UASSERT(deSerializeString32(is) == "some longer string here"); UASSERT(is.rdbuf()->in_avail() == 2); UASSERT(readU16(is) == 0xF00D); @@ -275,7 +273,7 @@ void TestSerialization::testStreamWrite() writeF1000(os, F1000_MIN); writeF1000(os, F1000_MAX); - os << serializeString("foobar!"); + os << serializeString16("foobar!"); data = os.str(); UASSERT(data.size() < sizeof(test_serialized_data)); @@ -286,12 +284,10 @@ void TestSerialization::testStreamWrite() writeV2S32(os, v2s32(1920, 1080)); writeV3S32(os, v3s32(-400, 6400054, 290549855)); - os << serializeWideString(L"\x02~woof~\x5455"); - writeV3F1000(os, v3f(500, 10024.2f, -192.54f)); writeARGB8(os, video::SColor(255, 128, 50, 128)); - os << serializeLongString("some longer string here"); + os << serializeString32("some longer string here"); writeU16(os, 0xF00D); @@ -384,7 +380,7 @@ void TestSerialization::testFloatFormat() UASSERT(test_single(i)); } -const u8 TestSerialization::test_serialized_data[12 * 13 - 8] = { +const u8 TestSerialization::test_serialized_data[12 * 11 - 2] = { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x80, 0x75, 0x30, 0xff, 0xff, 0xff, 0xfa, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xd5, 0x00, 0x00, 0xd1, 0x1e, 0xee, 0x1e, @@ -392,9 +388,7 @@ const u8 TestSerialization::test_serialized_data[12 * 13 - 8] = { 0x66, 0x6f, 0x6f, 0x62, 0x61, 0x72, 0x21, 0x01, 0xf4, 0x01, 0xf4, 0x10, 0x6f, 0x02, 0x5c, 0xff, 0xe2, 0x00, 0x00, 0x07, 0x80, 0x00, 0x00, 0x04, 0x38, 0xff, 0xff, 0xfe, 0x70, 0x00, 0x61, 0xa8, 0x36, 0x11, 0x51, 0x70, - 0x5f, 0x00, 0x08, 0x00, - 0x02, 0x00, 0x7e, 0x00, 'w', 0x00, 'o', 0x00, 'o', 0x00, 'f', 0x00, // \x02~woof~\x5455 - 0x7e, 0x54, 0x55, 0x00, 0x07, 0xa1, 0x20, 0x00, 0x98, 0xf5, 0x08, 0xff, + 0x5f, 0x00, 0x07, 0xa1, 0x20, 0x00, 0x98, 0xf5, 0x08, 0xff, 0xfd, 0x0f, 0xe4, 0xff, 0x80, 0x32, 0x80, 0x00, 0x00, 0x00, 0x17, 0x73, 0x6f, 0x6d, 0x65, 0x20, 0x6c, 0x6f, 0x6e, 0x67, 0x65, 0x72, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x20, 0x68, 0x65, 0x72, 0x65, 0xF0, 0x0D, diff --git a/src/util/serialize.cpp b/src/util/serialize.cpp index fd5cbda21..d770101f2 100644 --- a/src/util/serialize.cpp +++ b/src/util/serialize.cpp @@ -35,13 +35,13 @@ FloatType g_serialize_f32_type = FLOATTYPE_UNKNOWN; //// String //// -std::string serializeString(const std::string &plain) +std::string serializeString16(const std::string &plain) { std::string s; char buf[2]; if (plain.size() > STRING_MAX_LEN) - throw SerializationError("String too long for serializeString"); + throw SerializationError("String too long for serializeString16"); s.reserve(2 + plain.size()); writeU16((u8 *)&buf[0], plain.size()); @@ -51,14 +51,14 @@ std::string serializeString(const std::string &plain) return s; } -std::string deSerializeString(std::istream &is) +std::string deSerializeString16(std::istream &is) { std::string s; char buf[2]; is.read(buf, 2); if (is.gcount() != 2) - throw SerializationError("deSerializeString: size not read"); + throw SerializationError("deSerializeString16: size not read"); u16 s_size = readU16((u8 *)buf); if (s_size == 0) @@ -67,66 +67,17 @@ std::string deSerializeString(std::istream &is) s.resize(s_size); is.read(&s[0], s_size); if (is.gcount() != s_size) - throw SerializationError("deSerializeString: couldn't read all chars"); + throw SerializationError("deSerializeString16: couldn't read all chars"); return s; } -//// -//// Wide String -//// - -std::string serializeWideString(const std::wstring &plain) -{ - std::string s; - char buf[2]; - - if (plain.size() > WIDE_STRING_MAX_LEN) - throw SerializationError("String too long for serializeWideString"); - s.reserve(2 + 2 * plain.size()); - - writeU16((u8 *)buf, plain.size()); - s.append(buf, 2); - - for (wchar_t i : plain) { - writeU16((u8 *)buf, i); - s.append(buf, 2); - } - return s; -} - -std::wstring deSerializeWideString(std::istream &is) -{ - std::wstring s; - char buf[2]; - - is.read(buf, 2); - if (is.gcount() != 2) - throw SerializationError("deSerializeWideString: size not read"); - - u16 s_size = readU16((u8 *)buf); - if (s_size == 0) - return s; - - s.reserve(s_size); - for (u32 i = 0; i < s_size; i++) { - is.read(&buf[0], 2); - if (is.gcount() != 2) { - throw SerializationError( - "deSerializeWideString: couldn't read all chars"); - } - - wchar_t c16 = readU16((u8 *)buf); - s.append(&c16, 1); - } - return s; -} //// //// Long String //// -std::string serializeLongString(const std::string &plain) +std::string serializeString32(const std::string &plain) { std::string s; char buf[4]; @@ -141,7 +92,7 @@ std::string serializeLongString(const std::string &plain) return s; } -std::string deSerializeLongString(std::istream &is) +std::string deSerializeString32(std::istream &is) { std::string s; char buf[4]; diff --git a/src/util/serialize.h b/src/util/serialize.h index a988a8f78..b3ec28eab 100644 --- a/src/util/serialize.h +++ b/src/util/serialize.h @@ -440,16 +440,16 @@ MAKE_STREAM_WRITE_FXN(video::SColor, ARGB8, 4); //// // Creates a string with the length as the first two bytes -std::string serializeString(const std::string &plain); +std::string serializeString16(const std::string &plain); // Reads a string with the length as the first two bytes -std::string deSerializeString(std::istream &is); +std::string deSerializeString16(std::istream &is); // Creates a string with the length as the first four bytes -std::string serializeLongString(const std::string &plain); +std::string serializeString32(const std::string &plain); // Reads a string with the length as the first four bytes -std::string deSerializeLongString(std::istream &is); +std::string deSerializeString32(std::istream &is); // Creates a string encoded in JSON format (almost equivalent to a C string literal) std::string serializeJsonString(const std::string &plain); From 4b423ee9b12aabe6ccd6a8e7dbc7db1d89dc365b Mon Sep 17 00:00:00 2001 From: HybridDog <3192173+HybridDog@users.noreply.github.com> Date: Sat, 3 Oct 2020 18:33:43 +0200 Subject: [PATCH 085/266] Chatcommand: Show help message if func returns false without message #9440 --- builtin/game/chat.lua | 13 +++++++++++-- doc/lua_api.txt | 2 ++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/builtin/game/chat.lua b/builtin/game/chat.lua index 8131a6860..1d277730a 100644 --- a/builtin/game/chat.lua +++ b/builtin/game/chat.lua @@ -66,8 +66,17 @@ core.register_on_chat_message(function(name, message) local has_privs, missing_privs = core.check_player_privs(name, cmd_def.privs) if has_privs then core.set_last_run_mod(cmd_def.mod_origin) - local _, result = cmd_def.func(name, param) - if result then + local success, result = cmd_def.func(name, param) + if success == false and result == nil then + core.chat_send_player(name, "-!- Invalid command usage") + local help_def = core.registered_chatcommands["help"] + if help_def then + local _, helpmsg = help_def.func(name, cmd) + if helpmsg then + core.chat_send_player(name, helpmsg) + end + end + elseif result then core.chat_send_player(name, result) end else diff --git a/doc/lua_api.txt b/doc/lua_api.txt index bd845aad3..edacfe05f 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -7949,6 +7949,8 @@ Used by `minetest.register_chatcommand`. func = function(name, param), -- Called when command is run. Returns boolean success and text output. + -- Special case: The help message is shown to the player if `func` + -- returns false without a text output. } Note that in params, use of symbols is as follows: From 9dc29a75b416c6dab27ce93d0129383309cbf2c2 Mon Sep 17 00:00:00 2001 From: HybridDog <3192173+HybridDog@users.noreply.github.com> Date: Sat, 3 Oct 2020 18:33:51 +0200 Subject: [PATCH 086/266] Reduce the FPS when the window is unfocused (#8837) --- builtin/settingtypes.txt | 4 ++-- src/client/game.cpp | 7 ++++--- src/defaultsettings.cpp | 4 ++-- src/gui/guiEngine.cpp | 17 +++++++++-------- src/gui/guiEngine.h | 2 +- 5 files changed, 18 insertions(+), 16 deletions(-) diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 6d9c6f573..7f2d12be5 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -606,8 +606,8 @@ arm_inertia (Arm inertia) bool true # to not waste CPU power for no benefit. fps_max (Maximum FPS) int 60 1 -# Maximum FPS when game is paused. -pause_fps_max (FPS in pause menu) int 20 1 +# Maximum FPS when the window is not focused, or when the game is paused. +fps_max_unfocused (FPS when unfocused or paused) int 20 1 # Open the pause menu when the window's focus is lost. Does not pause if a formspec is # open. diff --git a/src/client/game.cpp b/src/client/game.cpp index 920383aaf..8f9d51417 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -3996,9 +3996,10 @@ inline void Game::limitFps(FpsControl *fps_timings, f32 *dtime) else fps_timings->busy_time = 0; - u32 frametime_min = 1000 / (g_menumgr.pausesGame() - ? g_settings->getFloat("pause_fps_max") - : g_settings->getFloat("fps_max")); + u32 frametime_min = 1000 / ( + device->isWindowFocused() && !g_menumgr.pausesGame() + ? g_settings->getFloat("fps_max") + : g_settings->getFloat("fps_max_unfocused")); if (fps_timings->busy_time < frametime_min) { fps_timings->sleep_time = frametime_min - fps_timings->busy_time; diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 3a0b88dc2..8f5ed3e17 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -165,7 +165,7 @@ void set_default_settings(Settings *settings) settings->setDefault("tooltip_show_delay", "400"); settings->setDefault("tooltip_append_itemname", "false"); settings->setDefault("fps_max", "60"); - settings->setDefault("pause_fps_max", "20"); + settings->setDefault("fps_max_unfocused", "20"); settings->setDefault("viewing_range", "100"); #if ENABLE_GLES settings->setDefault("near_plane", "0.1"); @@ -477,7 +477,7 @@ void set_default_settings(Settings *settings) settings->setDefault("max_block_generate_distance", "5"); settings->setDefault("enable_3d_clouds", "false"); settings->setDefault("fps_max", "30"); - settings->setDefault("pause_fps_max", "10"); + settings->setDefault("fps_max_unfocused", "10"); settings->setDefault("max_objects_per_block", "20"); settings->setDefault("sqlite_synchronous", "1"); settings->setDefault("server_map_save_interval", "15"); diff --git a/src/gui/guiEngine.cpp b/src/gui/guiEngine.cpp index b40707d01..4a13f0b11 100644 --- a/src/gui/guiEngine.cpp +++ b/src/gui/guiEngine.cpp @@ -297,10 +297,14 @@ void GUIEngine::run() driver->endScene(); + IrrlichtDevice *device = RenderingEngine::get_raw_device(); + u32 frametime_min = 1000 / (device->isWindowFocused() + ? g_settings->getFloat("fps_max") + : g_settings->getFloat("fps_max_unfocused")); if (m_clouds_enabled) - cloudPostProcess(); + cloudPostProcess(frametime_min, device); else - sleep_ms(25); + sleep_ms(frametime_min); m_script->step(); @@ -367,9 +371,8 @@ void GUIEngine::cloudPreProcess() } /******************************************************************************/ -void GUIEngine::cloudPostProcess() +void GUIEngine::cloudPostProcess(u32 frametime_min, IrrlichtDevice *device) { - float fps_max = g_settings->getFloat("pause_fps_max"); // Time of frame without fps limit u32 busytime_u32; @@ -380,12 +383,10 @@ void GUIEngine::cloudPostProcess() else busytime_u32 = 0; - // FPS limiter - u32 frametime_min = 1000./fps_max; - + // FPS limit if (busytime_u32 < frametime_min) { u32 sleeptime = frametime_min - busytime_u32; - RenderingEngine::get_raw_device()->sleep(sleeptime); + device->sleep(sleeptime); } } diff --git a/src/gui/guiEngine.h b/src/gui/guiEngine.h index f9ad0fb0a..e5b3edce7 100644 --- a/src/gui/guiEngine.h +++ b/src/gui/guiEngine.h @@ -277,7 +277,7 @@ private: /** do preprocessing for cloud subsystem */ void cloudPreProcess(); /** do postprocessing for cloud subsystem */ - void cloudPostProcess(); + void cloudPostProcess(u32 frametime_min, IrrlichtDevice *device); /** internam data required for drawing clouds */ struct clouddata { From 07500479191ed927ab661b3758ffcd2fd43158c5 Mon Sep 17 00:00:00 2001 From: random-geek <35757396+random-geek@users.noreply.github.com> Date: Sat, 3 Oct 2020 09:34:34 -0700 Subject: [PATCH 087/266] Fix scroll bar overlapping text (again) (#9058) --- src/gui/guiEditBoxWithScrollbar.cpp | 4 +--- src/gui/intlGUIEditBox.cpp | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/gui/guiEditBoxWithScrollbar.cpp b/src/gui/guiEditBoxWithScrollbar.cpp index 442406688..169425a9a 100644 --- a/src/gui/guiEditBoxWithScrollbar.cpp +++ b/src/gui/guiEditBoxWithScrollbar.cpp @@ -1028,7 +1028,7 @@ void GUIEditBoxWithScrollBar::breakText() s32 last_line_start = 0; s32 size = Text.size(); s32 length = 0; - s32 el_width = RelativeRect.getWidth() - 6; + s32 el_width = RelativeRect.getWidth() - m_scrollbar_width - 10; wchar_t c; for (s32 i = 0; i < size; ++i) { @@ -1399,8 +1399,6 @@ void GUIEditBoxWithScrollBar::createVScrollBar() m_scrollbar_width = skin ? skin->getSize(gui::EGDS_SCROLLBAR_SIZE) : 16; - RelativeRect.LowerRightCorner.X -= m_scrollbar_width + 4; - irr::core::rect scrollbarrect = m_frame_rect; scrollbarrect.UpperLeftCorner.X += m_frame_rect.getWidth() - m_scrollbar_width; m_vscrollbar = new GUIScrollBar(Environment, getParent(), -1, diff --git a/src/gui/intlGUIEditBox.cpp b/src/gui/intlGUIEditBox.cpp index 10395423c..8be63fd6f 100644 --- a/src/gui/intlGUIEditBox.cpp +++ b/src/gui/intlGUIEditBox.cpp @@ -1165,7 +1165,7 @@ void intlGUIEditBox::breakText() s32 lastLineStart = 0; s32 size = Text.size(); s32 length = 0; - s32 elWidth = RelativeRect.getWidth() - 6; + s32 elWidth = RelativeRect.getWidth() - m_scrollbar_width - 10; wchar_t c; for (s32 i=0; i scrollbarrect = FrameRect; scrollbarrect.UpperLeftCorner.X += FrameRect.getWidth() - m_scrollbar_width; m_vscrollbar = new GUIScrollBar(Environment, getParent(), -1, From 7d3641021b755be133dea58404fcb039211fbe52 Mon Sep 17 00:00:00 2001 From: Elijah Duffy Date: Sat, 3 Oct 2020 09:38:08 -0700 Subject: [PATCH 088/266] Lua API: Add register_on_chatcommand to SSM and CSM (#7862) Allows catching a chatcommand call just after the command and the parameters are parsed but before its existence is checked and before the corresponding function is run. Returning `true` from a callback function will prevent default handling of the command leaving mods to handle the command manually. --- builtin/client/chatcommands.lua | 5 +++++ builtin/client/register.lua | 1 + builtin/game/chat.lua | 5 +++++ builtin/game/register.lua | 1 + clientmods/preview/init.lua | 4 ++++ doc/client_lua_api.txt | 5 +++++ doc/lua_api.txt | 5 +++++ games/devtest/mods/experimental/commands.lua | 3 +++ 8 files changed, 29 insertions(+) diff --git a/builtin/client/chatcommands.lua b/builtin/client/chatcommands.lua index 5cb1b40bb..0e8d4dd03 100644 --- a/builtin/client/chatcommands.lua +++ b/builtin/client/chatcommands.lua @@ -23,6 +23,11 @@ core.register_on_sending_chat_message(function(message) return true end + -- Run core.registered_on_chatcommand callbacks. + if core.run_callbacks(core.registered_on_chatcommand, 5, cmd, param) then + return true + end + local cmd_def = core.registered_chatcommands[cmd] if cmd_def then core.set_last_run_mod(cmd_def.mod_origin) diff --git a/builtin/client/register.lua b/builtin/client/register.lua index c1b4965c1..acd36ab36 100644 --- a/builtin/client/register.lua +++ b/builtin/client/register.lua @@ -63,6 +63,7 @@ core.registered_on_mods_loaded, core.register_on_mods_loaded = make_registration core.registered_on_shutdown, core.register_on_shutdown = make_registration() core.registered_on_receiving_chat_message, core.register_on_receiving_chat_message = make_registration() core.registered_on_sending_chat_message, core.register_on_sending_chat_message = make_registration() +core.registered_on_chatcommand, core.register_on_chatcommand = make_registration() core.registered_on_death, core.register_on_death = make_registration() core.registered_on_hp_modification, core.register_on_hp_modification = make_registration() core.registered_on_damage_taken, core.register_on_damage_taken = make_registration() diff --git a/builtin/game/chat.lua b/builtin/game/chat.lua index 1d277730a..945707623 100644 --- a/builtin/game/chat.lua +++ b/builtin/game/chat.lua @@ -58,6 +58,11 @@ core.register_on_chat_message(function(name, message) param = param or "" + -- Run core.registered_on_chatcommands callbacks. + if core.run_callbacks(core.registered_on_chatcommands, 5, name, cmd, param) then + return true + end + local cmd_def = core.registered_chatcommands[cmd] if not cmd_def then core.chat_send_player(name, "-!- Invalid command: " .. cmd) diff --git a/builtin/game/register.lua b/builtin/game/register.lua index 1034d4f2b..3de67c04b 100644 --- a/builtin/game/register.lua +++ b/builtin/game/register.lua @@ -584,6 +584,7 @@ core.unregister_biome = make_wrap_deregistration(core.register_biome, core.clear_registered_biomes, core.registered_biomes) core.registered_on_chat_messages, core.register_on_chat_message = make_registration() +core.registered_on_chatcommands, core.register_on_chatcommand = make_registration() core.registered_globalsteps, core.register_globalstep = make_registration() core.registered_playerevents, core.register_playerevent = make_registration() core.registered_on_mods_loaded, core.register_on_mods_loaded = make_registration() diff --git a/clientmods/preview/init.lua b/clientmods/preview/init.lua index 089955d2f..977ed0ec3 100644 --- a/clientmods/preview/init.lua +++ b/clientmods/preview/init.lua @@ -109,6 +109,10 @@ core.register_on_sending_chat_message(function(message) return false end) +core.register_on_chatcommand(function(command, params) + print("[PREVIEW] caught command '"..command.."'. Parameters: '"..params.."'") +end) + -- This is an example function to ensure it's working properly, should be removed before merge core.register_on_hp_modification(function(hp) print("[PREVIEW] HP modified " .. hp) diff --git a/doc/client_lua_api.txt b/doc/client_lua_api.txt index 4c5231b79..32be2fabf 100644 --- a/doc/client_lua_api.txt +++ b/doc/client_lua_api.txt @@ -686,6 +686,11 @@ Call these functions only at load time! * Adds definition to minetest.registered_chatcommands * `minetest.unregister_chatcommand(name)` * Unregisters a chatcommands registered with register_chatcommand. +* `minetest.register_on_chatcommand(function(command, params))` + * Called always when a chatcommand is triggered, before `minetest.registered_chatcommands` + is checked to see if that the command exists, but after the input is parsed. + * Return `true` to mark the command as handled, which means that the default + handlers will be prevented. * `minetest.register_on_death(function())` * Called when the local player dies * `minetest.register_on_hp_modification(function(hp))` diff --git a/doc/lua_api.txt b/doc/lua_api.txt index edacfe05f..e8eb403c4 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -4606,6 +4606,11 @@ Call these functions only at load time! * Called always when a player says something * Return `true` to mark the message as handled, which means that it will not be sent to other players. +* `minetest.register_on_chatcommand(function(name, command, params))` + * Called always when a chatcommand is triggered, before `minetest.registered_chatcommands` + is checked to see if the command exists, but after the input is parsed. + * Return `true` to mark the command as handled, which means that the default + handlers will be prevented. * `minetest.register_on_player_receive_fields(function(player, formname, fields))` * Called when the server received input from `player` in a formspec with the given `formname`. Specifically, this is called on any of the diff --git a/games/devtest/mods/experimental/commands.lua b/games/devtest/mods/experimental/commands.lua index 158e5039d..132b08b0b 100644 --- a/games/devtest/mods/experimental/commands.lua +++ b/games/devtest/mods/experimental/commands.lua @@ -214,3 +214,6 @@ minetest.register_chatcommand("test_place_nodes", { end, }) +core.register_on_chatcommand(function(name, command, params) + minetest.log("caught command '"..command.."', issued by '"..name.."'. Parameters: '"..params.."'") +end) From 962438717913498bc44ec98d37bccc7a4798bca2 Mon Sep 17 00:00:00 2001 From: LoneWolfHT Date: Sat, 3 Oct 2020 10:00:08 -0700 Subject: [PATCH 089/266] Add note to docs on how to override privileges (#9792) --- doc/lua_api.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/lua_api.txt b/doc/lua_api.txt index e8eb403c4..5d5e4b93a 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -5767,6 +5767,9 @@ Global tables * Map of registered tool definitions, indexed by name * `minetest.registered_entities` * Map of registered entity prototypes, indexed by name + * Values in this table may be modified directly. + Note: changes to initial properties will only affect entities spawned afterwards, + as they are only read when spawning. * `minetest.object_refs` * Map of object references, indexed by active object id * `minetest.luaentities` @@ -5797,6 +5800,7 @@ Global tables * Map of registered chat command definitions, indexed by name * `minetest.registered_privileges` * Map of registered privilege definitions, indexed by name + * Registered privileges can be modified directly in this table. ### Registered callback tables From 41a6136f774a30364b6d3fd2042c9dbeb34f2109 Mon Sep 17 00:00:00 2001 From: Wuzzy Date: Sat, 3 Oct 2020 19:01:38 +0200 Subject: [PATCH 090/266] Document builtin entities (#9453) This PR adds a new text file doc/builtin_entities.txt which explains how the two builtin entities __builtin:item and __builtin:falling_node work. --- doc/builtin_entities.txt | 101 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 doc/builtin_entities.txt diff --git a/doc/builtin_entities.txt b/doc/builtin_entities.txt new file mode 100644 index 000000000..be3f73357 --- /dev/null +++ b/doc/builtin_entities.txt @@ -0,0 +1,101 @@ +# Builtin Entities +Minetest registers two entities by default: Falling nodes and dropped items. +This document describes how they behave and what you can do with them. + +## Falling node (`__builtin:falling_node`) + +This entity is created by `minetest.check_for_falling` in place of a node +with the special group `falling_node=1`. Falling nodes can also be created +artificially with `minetest.spawn_falling_node`. + +Needs manual initialization when spawned using `/spawnentity`. + +Default behaviour: + +* Falls down in a straight line (gravity = `movement_gravity` setting) +* Collides with `walkable` node +* Collides with all physical objects except players +* If the node group `float=1` is set, it also collides with liquid nodes +* When it hits a solid (=`walkable`) node, it will try to place itself as a + node, replacing the node above. + * If the falling node cannot replace the destination node, it is dropped. + * If the destination node is a leveled node (`paramtype2="leveled"`) of the + same node name, the levels of both are summed. + +### Entity fields + +* `set_node(self, node[, meta])` + * Function to initialize the falling node + * `node` and `meta` are explained below. + * The `meta` argument is optional. +* `node`: Node table of the node (`name`, `param1`, `param2`) that this + entity represents. Read-only. +* `meta`: Node metadata of the falling node. Will be used when the falling + nodes tries to place itself as a node. Read-only. + +### Rendering / supported nodes + +Falling nodes have visuals to look as close as possible to the original node. +This works for most drawtypes, but there are limitations. + +Supported drawtypes: + +* `normal` +* `signlike` +* `torchlike` +* `nodebox` +* `raillike` +* `glasslike` +* `glasslike_framed` +* `glasslike_framed_optional` +* `allfaces` +* `allfaces_optional` +* `firelike` +* `mesh` +* `fencelike` +* `liquid` +* `airlike` (not pointable) + +Other drawtypes still kinda work, but they might look weird. + +Supported `paramtype2` values: + +* `wallmounted` +* `facedir` +* `colorwallmounted` +* `colorfacedir` +* `color` + +## Dropped item stack (`__builtin:item`) + +This is an item stack in a collectable form. + +Common cases that spawn a dropped item: + +* Item dropped by player +* The root node of a node with the group `attached_node=1` is removed +* `minetest.add_item` is called + +Needs manual initialization when spawned using `/spawnentity`. + +### Behavior + +* Players can collect it by punching +* Lifespan is defined by the setting `item_entity_ttl` +* Slides on `slippery` nodes +* Subject to gravity (uses `movement_gravity` setting) +* Collides with `walkable` nodes +* Does not collide physical objects +* When it's inside a solid (`walkable=true`) node, it tries to escape to a + neighboring non-solid (`walkable=false`) node + +### Entity fields + +* `set_item(self, item)`: + * Function to initialize the dropped item + * `item` (type `ItemStack`) specifies the item to represent +* `age`: Age in seconds. Behaviour according to the setting `item_entity_ttl` +* `itemstring`: Itemstring of the item that this item entity represents. + Read-only. + +Other fields are for internal use only. From 3250b37e32f264db04c1ca449d9feefc22fd38b7 Mon Sep 17 00:00:00 2001 From: rubenwardy Date: Sun, 4 Oct 2020 00:33:45 +0100 Subject: [PATCH 091/266] Deprecate get_player_velocity and add_player_velocity (#10173) --- builtin/game/features.lua | 1 + doc/lua_api.txt | 31 +++++++------ src/script/lua_api/l_base.cpp | 5 +- src/script/lua_api/l_object.cpp | 82 +++++++++++++++------------------ src/script/lua_api/l_object.h | 6 --- 5 files changed, 58 insertions(+), 67 deletions(-) diff --git a/builtin/game/features.lua b/builtin/game/features.lua index a15475333..4d3c90ff0 100644 --- a/builtin/game/features.lua +++ b/builtin/game/features.lua @@ -17,6 +17,7 @@ core.features = { area_store_persistent_ids = true, pathfinder_works = true, object_step_has_moveresult = true, + direct_velocity_on_players = true, } function core.has_feature(arg) diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 5d5e4b93a..c21da1871 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -4340,6 +4340,8 @@ Utilities pathfinder_works = true, -- Whether Collision info is available to an objects' on_step (5.3.0) object_step_has_moveresult = true, + -- Whether get_velocity() and add_velocity() can be used on players (5.4.0) + direct_velocity_on_players = true, } * `minetest.has_feature(arg)`: returns `boolean, missing_features` @@ -6118,6 +6120,19 @@ object you are working with still exists. * `get_pos()`: returns `{x=num, y=num, z=num}` * `set_pos(pos)`: `pos`=`{x=num, y=num, z=num}` +* `get_velocity()`: returns the velocity, a vector. +* `add_velocity(vel)` + * `vel` is a vector, e.g. `{x=0.0, y=2.3, z=1.0}` + * In comparison to using get_velocity, adding the velocity and then using + set_velocity, add_velocity is supposed to avoid synchronization problems. + Additionally, players also do not support set_velocity. + * If a player: + * Does not apply during free_move. + * Note that since the player speed is normalized at each move step, + increasing e.g. Y velocity beyond what would usually be achieved + (see: physics overrides) will cause existing X/Z velocity to be reduced. + * Example: `add_velocity({x=0, y=6.5, z=0})` is equivalent to + pressing the jump key (assuming default settings) * `move_to(pos, continuous=false)` * Does an interpolated move for Lua entities for visually smooth transitions. * If `continuous` is true, the Lua entity will not be moved to the current @@ -6189,11 +6204,6 @@ object you are working with still exists. no effect and returning `nil`. * `set_velocity(vel)` * `vel` is a vector, e.g. `{x=0.0, y=2.3, z=1.0}` -* `add_velocity(vel)` - * `vel` is a vector, e.g. `{x=0.0, y=2.3, z=1.0}` - * In comparison to using get_velocity, adding the velocity and then using - set_velocity, add_velocity is supposed to avoid synchronization problems. -* `get_velocity()`: returns the velocity, a vector * `set_acceleration(acc)` * `acc` is a vector * `get_acceleration()`: returns the acceleration, a vector @@ -6229,16 +6239,9 @@ object you are working with still exists. #### Player only (no-op for other objects) * `get_player_name()`: returns `""` if is not a player -* `get_player_velocity()`: returns `nil` if is not a player, otherwise a +* `get_player_velocity()`: **DEPRECATED**, use get_velocity() instead. table {x, y, z} representing the player's instantaneous velocity in nodes/s -* `add_player_velocity(vel)` - * Adds to player velocity, this happens client-side and only once. - * Does not apply during free_move. - * Note that since the player speed is normalized at each move step, - increasing e.g. Y velocity beyond what would usually be achieved - (see: physics overrides) will cause existing X/Z velocity to be reduced. - * Example: `add_player_velocity({x=0, y=6.5, z=0})` is equivalent to - pressing the jump key (assuming default settings) +* `add_player_velocity(vel)`: **DEPRECATED**, use add_velocity(vel) instead. * `get_look_dir()`: get camera direction as a unit vector * `get_look_vertical()`: pitch in radians * Angle ranges between -pi/2 and pi/2, which are straight up and down diff --git a/src/script/lua_api/l_base.cpp b/src/script/lua_api/l_base.cpp index 2bee09436..03ef5447a 100644 --- a/src/script/lua_api/l_base.cpp +++ b/src/script/lua_api/l_base.cpp @@ -170,10 +170,11 @@ void ModApiBase::markAliasDeprecated(luaL_Reg *reg) m_deprecated_wrappers.emplace( std::pair(reg->name, original_reg)); reg->func = l_deprecated_function; + } else { + last_func = reg->func; + last_name = reg->name; } - last_func = reg->func; - last_name = reg->name; ++reg; } } diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index 24667e769..303b1175b 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -863,12 +863,21 @@ int ObjectRef::l_add_velocity(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - LuaEntitySAO *co = getluaobject(ref); - if (!co) + v3f vel = checkFloatPos(L, 2); + + ServerActiveObject *obj = getobject(ref); + if (obj == nullptr) return 0; - v3f pos = checkFloatPos(L, 2); - // Do it - co->addVelocity(pos); + + if (obj->getType() == ACTIVEOBJECT_TYPE_LUAENTITY) { + LuaEntitySAO *co = dynamic_cast(obj); + co->addVelocity(vel); + } else if (obj->getType() == ACTIVEOBJECT_TYPE_PLAYER) { + PlayerSAO *player = dynamic_cast(obj); + player->setMaxSpeedOverride(vel); + getServer(L)->SendPlayerSpeed(player->getPeerID(), vel); + } + return 0; } @@ -877,11 +886,23 @@ int ObjectRef::l_get_velocity(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - LuaEntitySAO *co = getluaobject(ref); - if (co == NULL) return 0; - // Do it - v3f v = co->getVelocity(); - pushFloatPos(L, v); + + ServerActiveObject *obj = getobject(ref); + if (obj == nullptr) + return 0; + + if (obj->getType() == ACTIVEOBJECT_TYPE_LUAENTITY) { + LuaEntitySAO *co = dynamic_cast(obj); + v3f v = co->getVelocity(); + pushFloatPos(L, v); + return 1; + } else if (obj->getType() == ACTIVEOBJECT_TYPE_PLAYER) { + RemotePlayer *player = dynamic_cast(obj)->getPlayer(); + push_v3f(L, player->getSpeed() / BS); + return 1; + } + + lua_pushnil(L); return 1; } @@ -1082,38 +1103,6 @@ int ObjectRef::l_get_player_name(lua_State *L) return 1; } -// get_player_velocity(self) -int ObjectRef::l_get_player_velocity(lua_State *L) -{ - NO_MAP_LOCK_REQUIRED; - ObjectRef *ref = checkobject(L, 1); - RemotePlayer *player = getplayer(ref); - if (player == NULL) { - lua_pushnil(L); - return 1; - } - // Do it - push_v3f(L, player->getSpeed() / BS); - return 1; -} - -// add_player_velocity(self, {x=num, y=num, z=num}) -int ObjectRef::l_add_player_velocity(lua_State *L) -{ - NO_MAP_LOCK_REQUIRED; - ObjectRef *ref = checkobject(L, 1); - v3f vel = checkFloatPos(L, 2); - - PlayerSAO *co = getplayersao(ref); - if (!co) - return 0; - - // Do it - co->setMaxSpeedOverride(vel); - getServer(L)->SendPlayerSpeed(co->getPeerID(), vel); - return 0; -} - // get_look_dir(self) int ObjectRef::l_get_look_dir(lua_State *L) { @@ -2288,10 +2277,14 @@ luaL_Reg ObjectRef::methods[] = { luamethod(ObjectRef, get_properties), luamethod(ObjectRef, set_nametag_attributes), luamethod(ObjectRef, get_nametag_attributes), - // LuaEntitySAO-only + luamethod_aliased(ObjectRef, set_velocity, setvelocity), luamethod(ObjectRef, add_velocity), + {"add_player_velocity", ObjectRef::l_add_velocity}, luamethod_aliased(ObjectRef, get_velocity, getvelocity), + {"get_player_velocity", ObjectRef::l_get_velocity}, + + // LuaEntitySAO-only luamethod_aliased(ObjectRef, set_acceleration, setacceleration), luamethod_aliased(ObjectRef, get_acceleration, getacceleration), luamethod_aliased(ObjectRef, set_yaw, setyaw), @@ -2307,8 +2300,7 @@ luaL_Reg ObjectRef::methods[] = { luamethod(ObjectRef, is_player), luamethod(ObjectRef, is_player_connected), luamethod(ObjectRef, get_player_name), - luamethod(ObjectRef, get_player_velocity), - luamethod(ObjectRef, add_player_velocity), + luamethod(ObjectRef, get_look_dir), luamethod(ObjectRef, get_look_pitch), luamethod(ObjectRef, get_look_yaw), diff --git a/src/script/lua_api/l_object.h b/src/script/lua_api/l_object.h index a75c59fd9..126719b1f 100644 --- a/src/script/lua_api/l_object.h +++ b/src/script/lua_api/l_object.h @@ -209,12 +209,6 @@ private: // get_player_name(self) static int l_get_player_name(lua_State *L); - // get_player_velocity(self) - static int l_get_player_velocity(lua_State *L); - - // add_player_velocity(self, {x=num, y=num, z=num}) - static int l_add_player_velocity(lua_State *L); - // get_fov(self) static int l_get_fov(lua_State *L); From 0f98b54aa4b2361575002d92b29fe222703ba557 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20M=C3=BCller?= <34514239+appgurueu@users.noreply.github.com> Date: Sun, 4 Oct 2020 15:09:12 +0200 Subject: [PATCH 092/266] Fix short 180 degree rotation when using set_bone_position (#10405) --- src/client/content_cao.cpp | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/src/client/content_cao.cpp b/src/client/content_cao.cpp index 71a9d4b54..c1715a289 100644 --- a/src/client/content_cao.cpp +++ b/src/client/content_cao.cpp @@ -1460,24 +1460,17 @@ void GenericCAO::updateBonePosition() 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) { + float offset_X = fabsf(bone_rot.X - bone->getRotation().X); + float offset_Y = fabsf(bone_rot.Y - bone->getRotation().Y); + float offset_Z = fabsf(bone_rot.Z - bone->getRotation().Z); + if ((offset_X > 179.9f && offset_X < 180.1f) + || (offset_Y > 179.9f && offset_Y < 180.1f) + || (offset_Z > 179.9f && offset_Z < 180.1f)) { bone->setRotation(bone_rot); bone->updateAbsolutePosition(); } From 3068853e8a58ccc7370a5ce977c08223601c497a Mon Sep 17 00:00:00 2001 From: Jordan Snelling Date: Sun, 4 Oct 2020 14:10:34 +0100 Subject: [PATCH 093/266] Add First Person Attachments (#10360) Fixes some other third person camera specific attachments. Implements a single new flag for entities to be forced visible in first person mode. Old mods do not need to be updated to use the new flag and are fully backwards compatible. --- doc/lua_api.txt | 8 +++-- src/activeobject.h | 4 +-- src/client/content_cao.cpp | 56 +++++++++++++++++++++++++++++---- src/client/content_cao.h | 8 +++++ src/client/game.cpp | 3 +- src/clientiface.cpp | 5 +-- src/script/lua_api/l_camera.cpp | 3 +- src/script/lua_api/l_object.cpp | 14 ++++++--- src/server/luaentity_sao.cpp | 4 +-- src/server/player_sao.cpp | 9 +++--- src/server/unit_sao.cpp | 17 +++++----- src/server/unit_sao.h | 5 +-- 12 files changed, 101 insertions(+), 35 deletions(-) diff --git a/doc/lua_api.txt b/doc/lua_api.txt index c21da1871..77fb4a654 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -6167,12 +6167,14 @@ object you are working with still exists. `frame_loop`. * `set_animation_frame_speed(frame_speed)` * `frame_speed`: number, default: `15.0` -* `set_attach(parent, bone, position, rotation)` +* `set_attach(parent, bone, position, rotation, forced_visible)` * `bone`: string * `position`: `{x=num, y=num, z=num}` (relative) * `rotation`: `{x=num, y=num, z=num}` = Rotation on each axis, in degrees -* `get_attach()`: returns parent, bone, position, rotation or nil if it isn't - attached. + * `forced_visible`: Boolean to control whether the attached entity + should appear in first person. +* `get_attach()`: returns parent, bone, position, rotation, forced_visible, + or nil if it isn't attached. * `set_detach()` * `set_bone_position(bone, position, rotation)` * `bone`: string diff --git a/src/activeobject.h b/src/activeobject.h index 85e160d10..0829858ad 100644 --- a/src/activeobject.h +++ b/src/activeobject.h @@ -120,9 +120,9 @@ public: virtual void setAttachment(int parent_id, const std::string &bone, v3f position, - v3f rotation) {} + v3f rotation, bool force_visible) {} virtual void getAttachment(int *parent_id, std::string *bone, v3f *position, - v3f *rotation) const {} + v3f *rotation, bool *force_visible) const {} virtual void clearChildAttachments() {} virtual void clearParentAttachment() {} virtual void addAttachmentChild(int child_id) {} diff --git a/src/client/content_cao.cpp b/src/client/content_cao.cpp index c1715a289..fae06554a 100644 --- a/src/client/content_cao.cpp +++ b/src/client/content_cao.cpp @@ -456,7 +456,8 @@ void GenericCAO::setChildrenVisible(bool toset) for (u16 cao_id : m_attachment_child_ids) { GenericCAO *obj = m_env->getGenericCAO(cao_id); if (obj) { - obj->setVisible(toset); + // Check if the entity is forced to appear in first person. + obj->setVisible(obj->isForcedVisible() ? true : toset); } } } @@ -477,8 +478,6 @@ void GenericCAO::setAttachment(int parent_id, const std::string &bone, v3f posit if (parent) parent->addAttachmentChild(m_id); } - - updateAttachments(); } @@ -498,7 +497,7 @@ void GenericCAO::clearChildAttachments() int child_id = *m_attachment_child_ids.begin(); if (ClientActiveObject *child = m_env->getActiveObject(child_id)) - child->setAttachment(0, "", v3f(), v3f()); + child->setAttachment(0, "", v3f(), v3f(), false); removeAttachmentChild(child_id); } @@ -800,6 +799,7 @@ void GenericCAO::addToScene(ITextureSource *tsrc) updateBonePosition(); updateAttachments(); setNodeLight(m_last_light); + updateMeshCulling(); } void GenericCAO::updateLight(u32 day_night_ratio) @@ -1411,6 +1411,9 @@ void GenericCAO::updateTextures(std::string mod) setMeshColor(mesh, m_prop.colors[0]); } } + // Prevent showing the player after changing texture + if (m_is_local_player) + updateMeshCulling(); } void GenericCAO::updateAnimation() @@ -1739,12 +1742,25 @@ void GenericCAO::processMessage(const std::string &data) std::string bone = deSerializeString16(is); v3f position = readV3F32(is); v3f rotation = readV3F32(is); + m_force_visible = readU8(is); // Returns false for EOF setAttachment(parent_id, bone, position, rotation); + // Forcibly show attachments if required by set_attach + if (m_force_visible) + m_is_visible = true; // localplayer itself can't be attached to localplayer - if (!m_is_local_player) - m_is_visible = !m_attached_to_local; + else if (!m_is_local_player) { + // Objects attached to the local player should be hidden in first + // person provided the forced boolean isn't set. + m_is_visible = !m_attached_to_local || + m_client->getCamera()->getCameraMode() != CAMERA_MODE_FIRST; + m_force_visible = false; + } else { + // Local players need to have this set, + // otherwise first person attachments fail. + m_is_visible = true; + } } else if (cmd == AO_CMD_PUNCHED) { u16 result_hp = readU16(is); @@ -1858,5 +1874,33 @@ std::string GenericCAO::debugInfoText() return os.str(); } +void GenericCAO::updateMeshCulling() +{ + if (!m_is_local_player) + return; + + // Grab the active player scene node so we know there's + // at least a mesh to occlude from the camera. + irr::scene::ISceneNode *node = getSceneNode(); + if (!node) + return; + + if (m_client->getCamera()->getCameraMode() == CAMERA_MODE_FIRST) { + // Hide the mesh by culling both front and + // back faces. Serious hackyness but it works for our + // purposes. This also preserves the skeletal armature. + node->setMaterialFlag(video::EMF_BACK_FACE_CULLING, + true); + node->setMaterialFlag(video::EMF_FRONT_FACE_CULLING, + true); + } else { + // Restore mesh visibility. + node->setMaterialFlag(video::EMF_BACK_FACE_CULLING, + m_prop.backface_culling); + node->setMaterialFlag(video::EMF_FRONT_FACE_CULLING, + false); + } +} + // Prototype GenericCAO proto_GenericCAO(NULL, NULL); diff --git a/src/client/content_cao.h b/src/client/content_cao.h index 974ff9a1e..daf697767 100644 --- a/src/client/content_cao.h +++ b/src/client/content_cao.h @@ -124,6 +124,7 @@ private: float m_step_distance_counter = 0.0f; u8 m_last_light = 255; bool m_is_visible = false; + bool m_force_visible = false; s8 m_glow = 0; // Material video::E_MATERIAL_TYPE m_material_type; @@ -215,6 +216,11 @@ public: m_is_visible = toset; } + inline bool isForcedVisible() const + { + return m_force_visible; + } + void setChildrenVisible(bool toset); void setAttachment(int parent_id, const std::string &bone, v3f position, v3f rotation); void getAttachment(int *parent_id, std::string *bone, v3f *position, @@ -275,4 +281,6 @@ public: { return m_prop.infotext; } + + void updateMeshCulling(); }; diff --git a/src/client/game.cpp b/src/client/game.cpp index 8f9d51417..366464467 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -2950,7 +2950,8 @@ void Game::updateCamera(u32 busy_time, f32 dtime) camera->toggleCameraMode(); - playercao->setVisible(camera->getCameraMode() > CAMERA_MODE_FIRST); + // Make the player visible depending on camera mode. + playercao->updateMeshCulling(); playercao->setChildrenVisible(camera->getCameraMode() > CAMERA_MODE_FIRST); } diff --git a/src/clientiface.cpp b/src/clientiface.cpp index 602a44c90..28a0ee770 100644 --- a/src/clientiface.cpp +++ b/src/clientiface.cpp @@ -80,10 +80,11 @@ LuaEntitySAO *getAttachedObject(PlayerSAO *sao, ServerEnvironment *env) int id; std::string bone; v3f dummy; - sao->getAttachment(&id, &bone, &dummy, &dummy); + bool force_visible; + sao->getAttachment(&id, &bone, &dummy, &dummy, &force_visible); ServerActiveObject *ao = env->getActiveObject(id); while (id && ao) { - ao->getAttachment(&id, &bone, &dummy, &dummy); + ao->getAttachment(&id, &bone, &dummy, &dummy, &force_visible); if (id) ao = env->getActiveObject(id); } diff --git a/src/script/lua_api/l_camera.cpp b/src/script/lua_api/l_camera.cpp index bfa60be67..40251154c 100644 --- a/src/script/lua_api/l_camera.cpp +++ b/src/script/lua_api/l_camera.cpp @@ -63,7 +63,8 @@ int LuaCamera::l_set_camera_mode(lua_State *L) return 0; camera->setCameraMode((CameraMode)((int)lua_tonumber(L, 2))); - playercao->setVisible(camera->getCameraMode() > CAMERA_MODE_FIRST); + // Make the player visible depending on camera mode. + playercao->updateMeshCulling(); playercao->setChildrenVisible(camera->getCameraMode() > CAMERA_MODE_FIRST); return 0; } diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index 303b1175b..fead4e849 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -664,7 +664,7 @@ int ObjectRef::l_get_bone_position(lua_State *L) return 2; } -// set_attach(self, parent, bone, position, rotation) +// set_attach(self, parent, bone, position, rotation, force_visible) int ObjectRef::l_set_attach(lua_State *L) { GET_ENV_PTR; @@ -687,7 +687,8 @@ int ObjectRef::l_set_attach(lua_State *L) std::string bone; v3f position = v3f(0, 0, 0); v3f rotation = v3f(0, 0, 0); - co->getAttachment(&parent_id, &bone, &position, &rotation); + bool force_visible; + co->getAttachment(&parent_id, &bone, &position, &rotation, &force_visible); if (parent_id) { ServerActiveObject *old_parent = env->getActiveObject(parent_id); old_parent->removeAttachmentChild(co->getId()); @@ -702,7 +703,8 @@ int ObjectRef::l_set_attach(lua_State *L) rotation = v3f(0, 0, 0); if (!lua_isnil(L, 5)) rotation = read_v3f(L, 5); - co->setAttachment(parent->getId(), bone, position, rotation); + force_visible = readParam(L, 6, false); + co->setAttachment(parent->getId(), bone, position, rotation, force_visible); parent->addAttachmentChild(co->getId()); return 0; } @@ -722,7 +724,8 @@ int ObjectRef::l_get_attach(lua_State *L) std::string bone; v3f position = v3f(0, 0, 0); v3f rotation = v3f(0, 0, 0); - co->getAttachment(&parent_id, &bone, &position, &rotation); + bool force_visible; + co->getAttachment(&parent_id, &bone, &position, &rotation, &force_visible); if (!parent_id) return 0; ServerActiveObject *parent = env->getActiveObject(parent_id); @@ -731,7 +734,8 @@ int ObjectRef::l_get_attach(lua_State *L) lua_pushlstring(L, bone.c_str(), bone.size()); push_v3f(L, position); push_v3f(L, rotation); - return 4; + lua_pushboolean(L, force_visible); + return 5; } // set_detach(self) diff --git a/src/server/luaentity_sao.cpp b/src/server/luaentity_sao.cpp index f20914f7f..b39797531 100644 --- a/src/server/luaentity_sao.cpp +++ b/src/server/luaentity_sao.cpp @@ -238,9 +238,9 @@ std::string LuaEntitySAO::getClientInitializationData(u16 protocol_version) msg_os << serializeString32(generateUpdateAnimationCommand()); // 3 for (const auto &bone_pos : m_bone_position) { msg_os << serializeString32(generateUpdateBonePositionCommand( - bone_pos.first, bone_pos.second.X, bone_pos.second.Y)); // m_bone_position.size + bone_pos.first, bone_pos.second.X, bone_pos.second.Y)); // 3 + N } - msg_os << serializeString32(generateUpdateAttachmentCommand()); // 4 + msg_os << serializeString32(generateUpdateAttachmentCommand()); // 4 + m_bone_position.size int message_count = 4 + m_bone_position.size(); diff --git a/src/server/player_sao.cpp b/src/server/player_sao.cpp index 8d4938c3c..344d18a20 100644 --- a/src/server/player_sao.cpp +++ b/src/server/player_sao.cpp @@ -122,10 +122,10 @@ std::string PlayerSAO::getClientInitializationData(u16 protocol_version) msg_os << serializeString32(generateUpdateAnimationCommand()); // 3 for (const auto &bone_pos : m_bone_position) { msg_os << serializeString32(generateUpdateBonePositionCommand( - bone_pos.first, bone_pos.second.X, bone_pos.second.Y)); // m_bone_position.size + bone_pos.first, bone_pos.second.X, bone_pos.second.Y)); // 3 + N } - msg_os << serializeString32(generateUpdateAttachmentCommand()); // 4 - msg_os << serializeString32(generateUpdatePhysicsOverrideCommand()); // 5 + msg_os << serializeString32(generateUpdateAttachmentCommand()); // 4 + m_bone_position.size + msg_os << serializeString32(generateUpdatePhysicsOverrideCommand()); // 5 + m_bone_position.size int message_count = 5 + m_bone_position.size(); @@ -569,7 +569,8 @@ bool PlayerSAO::checkMovementCheat() int parent_id; std::string bone; v3f attachment_rot; - getAttachment(&parent_id, &bone, &attachment_pos, &attachment_rot); + bool force_visible; + getAttachment(&parent_id, &bone, &attachment_pos, &attachment_rot, &force_visible); } v3f parent_pos = parent->getBasePosition(); diff --git a/src/server/unit_sao.cpp b/src/server/unit_sao.cpp index d906e885e..2371640ca 100644 --- a/src/server/unit_sao.cpp +++ b/src/server/unit_sao.cpp @@ -121,8 +121,8 @@ void UnitSAO::sendOutdatedData() } // clang-format on -void UnitSAO::setAttachment( - int parent_id, const std::string &bone, v3f position, v3f rotation) +void UnitSAO::setAttachment(int parent_id, const std::string &bone, v3f position, + v3f rotation, bool force_visible) { // Attachments need to be handled on both the server and client. // If we just attach on the server, we can only copy the position of the parent. @@ -137,6 +137,7 @@ void UnitSAO::setAttachment( m_attachment_bone = bone; m_attachment_position = position; m_attachment_rotation = rotation; + m_force_visible = force_visible; m_attachment_sent = false; if (parent_id != old_parent) { @@ -145,13 +146,14 @@ void UnitSAO::setAttachment( } } -void UnitSAO::getAttachment( - int *parent_id, std::string *bone, v3f *position, v3f *rotation) const +void UnitSAO::getAttachment(int *parent_id, std::string *bone, v3f *position, + v3f *rotation, bool *force_visible) const { *parent_id = m_attachment_parent_id; *bone = m_attachment_bone; *position = m_attachment_position; *rotation = m_attachment_rotation; + *force_visible = m_force_visible; } void UnitSAO::clearChildAttachments() @@ -159,7 +161,7 @@ void UnitSAO::clearChildAttachments() for (int child_id : m_attachment_child_ids) { // Child can be NULL if it was deleted earlier if (ServerActiveObject *child = m_env->getActiveObject(child_id)) - child->setAttachment(0, "", v3f(0, 0, 0), v3f(0, 0, 0)); + child->setAttachment(0, "", v3f(0, 0, 0), v3f(0, 0, 0), false); } m_attachment_child_ids.clear(); } @@ -169,9 +171,9 @@ void UnitSAO::clearParentAttachment() ServerActiveObject *parent = nullptr; if (m_attachment_parent_id) { parent = m_env->getActiveObject(m_attachment_parent_id); - setAttachment(0, "", m_attachment_position, m_attachment_rotation); + setAttachment(0, "", m_attachment_position, m_attachment_rotation, false); } else { - setAttachment(0, "", v3f(0, 0, 0), v3f(0, 0, 0)); + setAttachment(0, "", v3f(0, 0, 0), v3f(0, 0, 0), false); } // Do it if (parent) @@ -245,6 +247,7 @@ std::string UnitSAO::generateUpdateAttachmentCommand() const os << serializeString16(m_attachment_bone); writeV3F32(os, m_attachment_position); writeV3F32(os, m_attachment_rotation); + writeU8(os, m_force_visible); return os.str(); } diff --git a/src/server/unit_sao.h b/src/server/unit_sao.h index 3cb7f0ad5..a21e055c5 100644 --- a/src/server/unit_sao.h +++ b/src/server/unit_sao.h @@ -64,9 +64,9 @@ public: ServerActiveObject *getParent() const; inline bool isAttached() const { return getParent(); } void setAttachment(int parent_id, const std::string &bone, v3f position, - v3f rotation); + v3f rotation, bool force_visible); void getAttachment(int *parent_id, std::string *bone, v3f *position, - v3f *rotation) const; + v3f *rotation, bool *force_visible) const; void clearChildAttachments(); void clearParentAttachment(); void addAttachmentChild(int child_id); @@ -133,4 +133,5 @@ private: v3f m_attachment_position; v3f m_attachment_rotation; bool m_attachment_sent = false; + bool m_force_visible = false; }; From 81c66d6efb9fb0ab8a03b40e2bc22aa49eff9a04 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Rollo Date: Sun, 4 Oct 2020 15:24:29 +0200 Subject: [PATCH 094/266] Minimap as HUD element with API control Features: * Define Minimap available modes (surface/radar, scale) from Lua, using player:set_minimap_modes() * New HUD elements for displaying minimap with custom size and placing * New minimap mode for displaying a texture instead of the map --- doc/lua_api.txt | 33 +++- src/client/client.cpp | 1 + src/client/client.h | 1 + src/client/game.cpp | 75 ++++----- src/client/hud.cpp | 30 +++- src/client/hud.h | 2 + src/client/mapblock_mesh.cpp | 2 +- src/client/minimap.cpp | 237 +++++++++++++++++++++------- src/client/minimap.h | 41 ++--- src/hud.cpp | 1 + src/hud.h | 13 +- src/network/clientopcodes.cpp | 1 + src/network/clientpackethandler.cpp | 44 +++++- src/network/networkprotocol.h | 14 +- src/network/serveropcodes.cpp | 1 + src/script/lua_api/l_minimap.cpp | 24 +-- src/script/lua_api/l_object.cpp | 62 ++++++++ src/script/lua_api/l_object.h | 3 + src/server.cpp | 17 ++ src/server.h | 12 ++ 20 files changed, 470 insertions(+), 144 deletions(-) diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 77fb4a654..9e9af20da 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -1484,6 +1484,15 @@ Displays an image oriented or translated according to current heading direction. If translation is chosen, texture is repeated horizontally to fill the whole element. +### `minimap` + +Displays a minimap on the HUD. + +* `size`: Size of the minimap to display. Minimap should be a square to avoid + distortion. +* `alignment`: The alignment of the minimap. +* `offset`: offset in pixels from position. + Representations of simple things ================================ @@ -6366,6 +6375,27 @@ object you are working with still exists. * `hud_set_hotbar_selected_image(texturename)` * sets image for selected item of hotbar * `hud_get_hotbar_selected_image`: returns texturename +* `set_minimap_modes({mode, mode, ...}, selected_mode)` + * Overrides the available minimap modes (and toggle order), and changes the + selected mode. + * `mode` is a table consisting of up to four fields: + * `type`: Available type: + * `off`: Minimap off + * `surface`: Minimap in surface mode + * `radar`: Minimap in radar mode + * `texture`: Texture to be displayed instead of terrain map + (texture is centered around 0,0 and can be scaled). + Texture size is limited to 512 x 512 pixel. + * `label`: Optional label to display on minimap mode toggle + The translation must be handled within the mod. + * `size`: Sidelength or diameter, in number of nodes, of the terrain + displayed in minimap + * `texture`: Only for texture type, name of the texture to display + * `scale`: Only for texture type, scale of the texture map in nodes per + pixel (for example a `scale` of 2 means each pixel represents a 2x2 + nodes square) + * `selected_mode` is the mode index to be selected after modes have been changed + (0 is the first mode). * `set_sky(parameters)` * `parameters` is a table with the following optional fields: * `base_color`: ColorSpec, changes fog in "skybox" and "plain". @@ -8047,7 +8077,8 @@ Used by `Player:hud_add`. Returned by `Player:hud_get`. { hud_elem_type = "image", -- See HUD element types - -- Type of element, can be "image", "text", "statbar", "inventory" or "compass" + -- Type of element, can be "image", "text", "statbar", "inventory", + -- "compass" or "minimap" position = {x=0.5, y=0.5}, -- Left corner position of element diff --git a/src/client/client.cpp b/src/client/client.cpp index d6e529c40..7fafe0da9 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -129,6 +129,7 @@ Client::Client( if (g_settings->getBool("enable_minimap")) { m_minimap = new Minimap(this); } + m_cache_save_interval = g_settings->getU16("server_map_save_interval"); } diff --git a/src/client/client.h b/src/client/client.h index 733634db1..8f4aac6e3 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -223,6 +223,7 @@ public: void handleCommand_CSMRestrictionFlags(NetworkPacket *pkt); void handleCommand_PlayerSpeed(NetworkPacket *pkt); void handleCommand_MediaPush(NetworkPacket *pkt); + void handleCommand_MinimapModes(NetworkPacket *pkt); void ProcessData(NetworkPacket *pkt); diff --git a/src/client/game.cpp b/src/client/game.cpp index 366464467..54e0c9f20 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -1416,11 +1416,9 @@ bool Game::createClient(const GameStartData &start_data) } mapper = client->getMinimap(); - if (mapper) { - mapper->setMinimapMode(MINIMAP_MODE_OFF); - if (client->modsLoaded()) - client->getScript()->on_minimap_ready(mapper); - } + + if (mapper && client->modsLoaded()) + client->getScript()->on_minimap_ready(mapper); return true; } @@ -2222,52 +2220,37 @@ void Game::toggleMinimap(bool shift_pressed) if (!mapper || !m_game_ui->m_flags.show_hud || !g_settings->getBool("enable_minimap")) return; - if (shift_pressed) { + if (shift_pressed) mapper->toggleMinimapShape(); - return; - } + else + mapper->nextMode(); + // TODO: When legacy minimap is deprecated, keep only HUD minimap stuff here + + // Not so satisying code to keep compatibility with old fixed mode system + // --> u32 hud_flags = client->getEnv().getLocalPlayer()->hud_flags; - MinimapMode mode = MINIMAP_MODE_OFF; - if (hud_flags & HUD_FLAG_MINIMAP_VISIBLE) { - mode = mapper->getMinimapMode(); - mode = (MinimapMode)((int)mode + 1); - // If radar is disabled and in, or switching to, radar mode - if (!(hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE) && mode > 3) - mode = MINIMAP_MODE_OFF; - } + if (!(hud_flags & HUD_FLAG_MINIMAP_VISIBLE)) { + m_game_ui->m_flags.show_minimap = false; + } else { - m_game_ui->m_flags.show_minimap = true; - switch (mode) { - case MINIMAP_MODE_SURFACEx1: - m_game_ui->showTranslatedStatusText("Minimap in surface mode, Zoom x1"); - break; - case MINIMAP_MODE_SURFACEx2: - m_game_ui->showTranslatedStatusText("Minimap in surface mode, Zoom x2"); - break; - case MINIMAP_MODE_SURFACEx4: - m_game_ui->showTranslatedStatusText("Minimap in surface mode, Zoom x4"); - break; - case MINIMAP_MODE_RADARx1: - m_game_ui->showTranslatedStatusText("Minimap in radar mode, Zoom x1"); - break; - case MINIMAP_MODE_RADARx2: - m_game_ui->showTranslatedStatusText("Minimap in radar mode, Zoom x2"); - break; - case MINIMAP_MODE_RADARx4: - m_game_ui->showTranslatedStatusText("Minimap in radar mode, Zoom x4"); - break; - default: - mode = MINIMAP_MODE_OFF; - m_game_ui->m_flags.show_minimap = false; - if (hud_flags & HUD_FLAG_MINIMAP_VISIBLE) - m_game_ui->showTranslatedStatusText("Minimap hidden"); - else - m_game_ui->showTranslatedStatusText("Minimap currently disabled by game or mod"); - } + // If radar is disabled, try to find a non radar mode or fall back to 0 + if (!(hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE)) + while (mapper->getModeIndex() && + mapper->getModeDef().type == MINIMAP_TYPE_RADAR) + mapper->nextMode(); - mapper->setMinimapMode(mode); + m_game_ui->m_flags.show_minimap = mapper->getModeDef().type != + MINIMAP_TYPE_OFF; + } + // <-- + // End of 'not so satifying code' + if ((hud_flags & HUD_FLAG_MINIMAP_VISIBLE) || + (hud && hud->hasElementOfType(HUD_ELEM_MINIMAP))) + m_game_ui->showStatusText(utf8_to_wide(mapper->getModeDef().label)); + else + m_game_ui->showTranslatedStatusText("Minimap currently disabled by game or mod"); } void Game::toggleFog() @@ -3954,7 +3937,7 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, /* Update minimap pos and rotation */ - if (mapper && m_game_ui->m_flags.show_minimap && m_game_ui->m_flags.show_hud) { + if (mapper && m_game_ui->m_flags.show_hud) { mapper->setPos(floatToInt(player->getPosition(), BS)); mapper->setAngle(player->getYaw()); } diff --git a/src/client/hud.cpp b/src/client/hud.cpp index d3038230c..f6497fe25 100644 --- a/src/client/hud.cpp +++ b/src/client/hud.cpp @@ -36,6 +36,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "mesh.h" #include "wieldmesh.h" #include "client/renderingengine.h" +#include "client/minimap.h" #ifdef HAVE_TOUCHSCREENGUI #include "gui/touchscreengui.h" @@ -297,6 +298,18 @@ void Hud::drawItems(v2s32 upperleftpos, v2s32 screen_offset, s32 itemcount, } } +bool Hud::hasElementOfType(HudElementType type) +{ + for (size_t i = 0; i != player->maxHudId(); i++) { + HudElement *e = player->getHud(i); + if (!e) + continue; + if (e->type == type) + return true; + } + return false; +} + // 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) { @@ -491,7 +504,22 @@ void Hud::drawLuaElements(const v3s16 &camera_offset) default: break; } - + break; } + case HUD_ELEM_MINIMAP: { + if (e->size.X <= 0 || e->size.Y <= 0) + break; + if (!client->getMinimap()) + break; + // Draw a minimap of size "size" + v2s32 dstsize(e->size.X * m_scale_factor, + e->size.Y * m_scale_factor); + // (no percent size as minimap would likely be anamorphosed) + v2s32 offset((e->align.X - 1.0) * dstsize.X / 2, + (e->align.Y - 1.0) * dstsize.Y / 2); + core::rect rect(0, 0, dstsize.X, dstsize.Y); + rect += pos + offset + v2s32(e->offset.X * m_scale_factor, + e->offset.Y * m_scale_factor); + client->getMinimap()->drawMinimap(rect); break; } default: infostream << "Hud::drawLuaElements: ignoring drawform " << e->type << diff --git a/src/client/hud.h b/src/client/hud.h index cf83cb16e..d46545d71 100644 --- a/src/client/hud.h +++ b/src/client/hud.h @@ -81,6 +81,8 @@ public: m_selected_face_normal = face_normal; } + bool hasElementOfType(HudElementType type); + void drawLuaElements(const v3s16 &camera_offset); private: diff --git a/src/client/mapblock_mesh.cpp b/src/client/mapblock_mesh.cpp index 2f96ca61f..b59679523 100644 --- a/src/client/mapblock_mesh.cpp +++ b/src/client/mapblock_mesh.cpp @@ -1044,7 +1044,7 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset): m_use_tangent_vertices = data->m_use_tangent_vertices; m_enable_vbo = g_settings->getBool("enable_vbo"); - if (g_settings->getBool("enable_minimap")) { + if (data->m_client->getMinimap()) { m_minimap_mapblock = new MinimapMapblock; m_minimap_mapblock->getMinimapNodes( &data->m_vmanip, data->m_blockpos * MAP_BLOCKSIZE); diff --git a/src/client/minimap.cpp b/src/client/minimap.cpp index 68770ec19..6bae408b4 100644 --- a/src/client/minimap.cpp +++ b/src/client/minimap.cpp @@ -25,7 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "shader.h" #include "mapblock.h" #include "client/renderingengine.h" - +#include "gettext.h" //// //// MinimapUpdateThread @@ -108,8 +108,11 @@ void MinimapUpdateThread::doUpdate() } } - if (data->map_invalidated && data->mode != MINIMAP_MODE_OFF) { - getMap(data->pos, data->map_size, data->scan_height); + + if (data->map_invalidated && ( + data->mode.type == MINIMAP_TYPE_RADAR || + data->mode.type == MINIMAP_TYPE_SURFACE)) { + getMap(data->pos, data->mode.map_size, data->mode.scan_height); data->map_invalidated = false; } } @@ -181,19 +184,26 @@ Minimap::Minimap(Client *client) this->m_ndef = client->getNodeDefManager(); m_angle = 0.f; + m_current_mode_index = 0; // Initialize static settings m_enable_shaders = g_settings->getBool("enable_shaders"); m_surface_mode_scan_height = g_settings->getBool("minimap_double_scan_height") ? 256 : 128; + // Initialize minimap modes + addMode(MINIMAP_TYPE_OFF); + addMode(MINIMAP_TYPE_SURFACE, 256); + addMode(MINIMAP_TYPE_SURFACE, 128); + addMode(MINIMAP_TYPE_SURFACE, 64); + addMode(MINIMAP_TYPE_RADAR, 512); + addMode(MINIMAP_TYPE_RADAR, 256); + addMode(MINIMAP_TYPE_RADAR, 128); + // Initialize minimap data data = new MinimapData; - data->mode = MINIMAP_MODE_OFF; - data->is_radar = false; - data->map_invalidated = true; - data->texture = NULL; - data->heightmap_texture = NULL; + data->map_invalidated = true; + data->minimap_shape_round = g_settings->getBool("minimap_shape_round"); // Get round minimap textures @@ -215,6 +225,8 @@ Minimap::Minimap(Client *client) // Create object marker texture data->object_marker_red = m_tsrc->getTexture("object_marker_red.png"); + setModeIndex(0); + // Create mesh buffer for minimap m_meshbuffer = getMinimapMeshBuffer(); @@ -280,29 +292,101 @@ MinimapShape Minimap::getMinimapShape() return MINIMAP_SHAPE_SQUARE; } -void Minimap::setMinimapMode(MinimapMode mode) +void Minimap::setModeIndex(size_t index) { - static const MinimapModeDef modedefs[MINIMAP_MODE_COUNT] = { - {false, 0, 0}, - {false, m_surface_mode_scan_height, 256}, - {false, m_surface_mode_scan_height, 128}, - {false, m_surface_mode_scan_height, 64}, - {true, 32, 128}, - {true, 32, 64}, - {true, 32, 32} - }; - - if (mode >= MINIMAP_MODE_COUNT) - return; - MutexAutoLock lock(m_mutex); - data->is_radar = modedefs[mode].is_radar; - data->scan_height = modedefs[mode].scan_height; - data->map_size = modedefs[mode].map_size; - data->mode = mode; + if (index < m_modes.size()) { + data->mode = m_modes[index]; + m_current_mode_index = index; + } else { + data->mode = MinimapModeDef{MINIMAP_TYPE_OFF, N_("Minimap hidden"), 0, 0, ""}; + m_current_mode_index = 0; + } - m_minimap_update_thread->deferUpdate(); + data->map_invalidated = true; + + if (m_minimap_update_thread) + m_minimap_update_thread->deferUpdate(); +} + +void Minimap::addMode(MinimapModeDef mode) +{ + // Check validity + if (mode.type == MINIMAP_TYPE_TEXTURE) { + if (mode.texture.empty()) + return; + if (mode.scale < 1) + mode.scale = 1; + } + + int zoom = -1; + + // Build a default standard label + if (mode.label == "") { + switch (mode.type) { + case MINIMAP_TYPE_OFF: + mode.label = N_("Minimap hidden"); + break; + case MINIMAP_TYPE_SURFACE: + mode.label = N_("Minimap in surface mode, Zoom x%d"); + if (mode.map_size > 0) + zoom = 256 / mode.map_size; + break; + case MINIMAP_TYPE_RADAR: + mode.label = N_("Minimap in radar mode, Zoom x%d"); + if (mode.map_size > 0) + zoom = 512 / mode.map_size; + break; + case MINIMAP_TYPE_TEXTURE: + mode.label = N_("Minimap in texture mode"); + break; + default: + break; + } + } + + if (zoom >= 0) { + char label_buf[1024]; + porting::mt_snprintf(label_buf, sizeof(label_buf), + mode.label.c_str(), zoom); + mode.label = label_buf; + } + + m_modes.push_back(mode); +} + +void Minimap::addMode(MinimapType type, u16 size, std::string label, + std::string texture, u16 scale) +{ + MinimapModeDef mode; + mode.type = type; + mode.label = label; + mode.map_size = size; + mode.texture = texture; + mode.scale = scale; + switch (type) { + case MINIMAP_TYPE_SURFACE: + mode.scan_height = m_surface_mode_scan_height; + break; + case MINIMAP_TYPE_RADAR: + mode.scan_height = 32; + break; + default: + mode.scan_height = 0; + } + addMode(mode); +} + +void Minimap::nextMode() +{ + if (m_modes.empty()) + return; + m_current_mode_index++; + if (m_current_mode_index >= m_modes.size()) + m_current_mode_index = 0; + + setModeIndex(m_current_mode_index); } void Minimap::setPos(v3s16 pos) @@ -331,16 +415,16 @@ void Minimap::setAngle(f32 angle) void Minimap::blitMinimapPixelsToImageRadar(video::IImage *map_image) { video::SColor c(240, 0, 0, 0); - for (s16 x = 0; x < data->map_size; x++) - for (s16 z = 0; z < data->map_size; z++) { - MinimapPixel *mmpixel = &data->minimap_scan[x + z * data->map_size]; + for (s16 x = 0; x < data->mode.map_size; x++) + for (s16 z = 0; z < data->mode.map_size; z++) { + MinimapPixel *mmpixel = &data->minimap_scan[x + z * data->mode.map_size]; if (mmpixel->air_count > 0) c.setGreen(core::clamp(core::round32(32 + mmpixel->air_count * 8), 0, 255)); else c.setGreen(0); - map_image->setPixel(x, data->map_size - z - 1, c); + map_image->setPixel(x, data->mode.map_size - z - 1, c); } } @@ -349,9 +433,9 @@ void Minimap::blitMinimapPixelsToImageSurface( { // This variable creation/destruction has a 1% cost on rendering minimap video::SColor tilecolor; - for (s16 x = 0; x < data->map_size; x++) - for (s16 z = 0; z < data->map_size; z++) { - MinimapPixel *mmpixel = &data->minimap_scan[x + z * data->map_size]; + for (s16 x = 0; x < data->mode.map_size; x++) + for (s16 z = 0; z < data->mode.map_size; z++) { + MinimapPixel *mmpixel = &data->minimap_scan[x + z * data->mode.map_size]; const ContentFeatures &f = m_ndef->get(mmpixel->n); const TileDef *tile = &f.tiledef[0]; @@ -367,10 +451,10 @@ void Minimap::blitMinimapPixelsToImageSurface( tilecolor.setBlue(tilecolor.getBlue() * f.minimap_color.getBlue() / 255); tilecolor.setAlpha(240); - map_image->setPixel(x, data->map_size - z - 1, tilecolor); + map_image->setPixel(x, data->mode.map_size - z - 1, tilecolor); u32 h = mmpixel->height; - heightmap_image->setPixel(x,data->map_size - z - 1, + heightmap_image->setPixel(x,data->mode.map_size - z - 1, video::SColor(255, h, h, h)); } } @@ -378,21 +462,46 @@ void Minimap::blitMinimapPixelsToImageSurface( video::ITexture *Minimap::getMinimapTexture() { // update minimap textures when new scan is ready - if (data->map_invalidated) + if (data->map_invalidated && data->mode.type != MINIMAP_TYPE_TEXTURE) return data->texture; // create minimap and heightmap images in memory - core::dimension2d dim(data->map_size, data->map_size); + core::dimension2d dim(data->mode.map_size, data->mode.map_size); video::IImage *map_image = driver->createImage(video::ECF_A8R8G8B8, dim); video::IImage *heightmap_image = driver->createImage(video::ECF_A8R8G8B8, dim); video::IImage *minimap_image = driver->createImage(video::ECF_A8R8G8B8, core::dimension2d(MINIMAP_MAX_SX, MINIMAP_MAX_SY)); // Blit MinimapPixels to images - if (data->is_radar) - blitMinimapPixelsToImageRadar(map_image); - else + switch(data->mode.type) { + case MINIMAP_TYPE_OFF: + break; + case MINIMAP_TYPE_SURFACE: blitMinimapPixelsToImageSurface(map_image, heightmap_image); + break; + case MINIMAP_TYPE_RADAR: + blitMinimapPixelsToImageRadar(map_image); + break; + case MINIMAP_TYPE_TEXTURE: + // Want to use texture source, to : 1 find texture, 2 cache it + video::ITexture* texture = m_tsrc->getTexture(data->mode.texture); + video::IImage* image = driver->createImageFromData( + texture->getColorFormat(), texture->getSize(), texture->lock(), true, false); + texture->unlock(); + + auto dim = image->getDimension(); + + map_image->fill(video::SColor(255, 0, 0, 0)); + + image->copyTo(map_image, + irr::core::vector2d { + ((data->mode.map_size - (static_cast(dim.Width))) >> 1) + - data->pos.X / data->mode.scale, + ((data->mode.map_size - (static_cast(dim.Height))) >> 1) + + data->pos.Z / data->mode.scale + }); + image->drop(); + } map_image->copyToScaling(minimap_image); map_image->drop(); @@ -461,21 +570,31 @@ scene::SMeshBuffer *Minimap::getMinimapMeshBuffer() void Minimap::drawMinimap() { + // Non hud managed minimap drawing (legacy minimap) + v2u32 screensize = RenderingEngine::get_instance()->getWindowSize(); + const u32 size = 0.25 * screensize.Y; + + drawMinimap(core::rect( + screensize.X - size - 10, 10, + screensize.X - 10, size + 10)); +} + +void Minimap::drawMinimap(core::rect rect) { + video::ITexture *minimap_texture = getMinimapTexture(); if (!minimap_texture) return; + if (data->mode.type == MINIMAP_TYPE_OFF) + return; + updateActiveMarkers(); - v2u32 screensize = RenderingEngine::get_instance()->getWindowSize(); - const u32 size = 0.25 * screensize.Y; core::rect oldViewPort = driver->getViewPort(); core::matrix4 oldProjMat = driver->getTransform(video::ETS_PROJECTION); core::matrix4 oldViewMat = driver->getTransform(video::ETS_VIEW); - driver->setViewPort(core::rect( - screensize.X - size - 10, 10, - screensize.X - 10, size + 10)); + driver->setViewPort(rect); driver->setTransform(video::ETS_PROJECTION, core::matrix4()); driver->setTransform(video::ETS_VIEW, core::matrix4()); @@ -488,7 +607,7 @@ void Minimap::drawMinimap() material.TextureLayer[0].Texture = minimap_texture; material.TextureLayer[1].Texture = data->heightmap_texture; - if (m_enable_shaders && !data->is_radar) { + if (m_enable_shaders && data->mode.type == MINIMAP_TYPE_SURFACE) { u16 sid = m_shdrsrc->getShader("minimap_shader", 1, 1); material.MaterialType = m_shdrsrc->getShaderInfo(sid).material; } else { @@ -529,14 +648,14 @@ void Minimap::drawMinimap() driver->setViewPort(oldViewPort); // Draw player markers - v2s32 s_pos(screensize.X - size - 10, 10); + v2s32 s_pos(rect.UpperLeftCorner.X, rect.UpperLeftCorner.Y); core::dimension2di imgsize(data->object_marker_red->getOriginalSize()); core::rect img_rect(0, 0, imgsize.Width, imgsize.Height); static const video::SColor col(255, 255, 255, 255); static const video::SColor c[4] = {col, col, col, col}; f32 sin_angle = std::sin(m_angle * core::DEGTORAD); f32 cos_angle = std::cos(m_angle * core::DEGTORAD); - s32 marker_size2 = 0.025 * (float)size; + s32 marker_size2 = 0.025 * (float)rect.getWidth();; for (std::list::const_iterator i = m_active_markers.begin(); i != m_active_markers.end(); ++i) { @@ -547,8 +666,8 @@ void Minimap::drawMinimap() posf.X = t1; posf.Y = t2; } - posf.X = (posf.X + 0.5) * (float)size; - posf.Y = (posf.Y + 0.5) * (float)size; + posf.X = (posf.X + 0.5) * (float)rect.getWidth(); + posf.Y = (posf.Y + 0.5) * (float)rect.getHeight(); core::rect dest_rect( s_pos.X + posf.X - marker_size2, s_pos.Y + posf.Y - marker_size2, @@ -571,16 +690,16 @@ void Minimap::updateActiveMarkers() for (Nametag *nametag : nametags) { v3s16 pos = floatToInt(nametag->parent_node->getAbsolutePosition() + intToFloat(client->getCamera()->getOffset(), BS), BS); - pos -= data->pos - v3s16(data->map_size / 2, - data->scan_height / 2, - data->map_size / 2); - if (pos.X < 0 || pos.X > data->map_size || - pos.Y < 0 || pos.Y > data->scan_height || - pos.Z < 0 || pos.Z > data->map_size) { + pos -= data->pos - v3s16(data->mode.map_size / 2, + data->mode.scan_height / 2, + data->mode.map_size / 2); + if (pos.X < 0 || pos.X > data->mode.map_size || + pos.Y < 0 || pos.Y > data->mode.scan_height || + pos.Z < 0 || pos.Z > data->mode.map_size) { continue; } - pos.X = ((float)pos.X / data->map_size) * MINIMAP_MAX_SX; - pos.Z = ((float)pos.Z / data->map_size) * MINIMAP_MAX_SY; + pos.X = ((float)pos.X / data->mode.map_size) * MINIMAP_MAX_SX; + pos.Z = ((float)pos.Z / data->mode.map_size) * MINIMAP_MAX_SY; const video::SColor &mask_col = minimap_mask->getPixel(pos.X, pos.Z); if (!mask_col.getAlpha()) { continue; diff --git a/src/client/minimap.h b/src/client/minimap.h index 258d5330d..11374b116 100644 --- a/src/client/minimap.h +++ b/src/client/minimap.h @@ -19,6 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once +#include "../hud.h" #include "irrlichttypes_extrabloated.h" #include "util/thread.h" #include "voxel.h" @@ -33,26 +34,18 @@ class IShaderSource; #define MINIMAP_MAX_SX 512 #define MINIMAP_MAX_SY 512 -enum MinimapMode { - MINIMAP_MODE_OFF, - MINIMAP_MODE_SURFACEx1, - MINIMAP_MODE_SURFACEx2, - MINIMAP_MODE_SURFACEx4, - MINIMAP_MODE_RADARx1, - MINIMAP_MODE_RADARx2, - MINIMAP_MODE_RADARx4, - MINIMAP_MODE_COUNT, -}; - enum MinimapShape { MINIMAP_SHAPE_SQUARE, MINIMAP_SHAPE_ROUND, }; struct MinimapModeDef { - bool is_radar; + MinimapType type; + std::string label; u16 scan_height; u16 map_size; + std::string texture; + u16 scale; }; struct MinimapPixel { @@ -69,12 +62,9 @@ struct MinimapMapblock { }; struct MinimapData { - bool is_radar; - MinimapMode mode; + MinimapModeDef mode; v3s16 pos; v3s16 old_pos; - u16 scan_height; - u16 map_size; MinimapPixel minimap_scan[MINIMAP_MAX_SX * MINIMAP_MAX_SY]; bool map_invalidated; bool minimap_shape_round; @@ -127,12 +117,22 @@ public: v3s16 getPos() const { return data->pos; } void setAngle(f32 angle); f32 getAngle() const { return m_angle; } - void setMinimapMode(MinimapMode mode); - MinimapMode getMinimapMode() const { return data->mode; } void toggleMinimapShape(); void setMinimapShape(MinimapShape shape); MinimapShape getMinimapShape(); + void clearModes() { m_modes.clear(); }; + void addMode(MinimapModeDef mode); + void addMode(MinimapType type, u16 size = 0, std::string label = "", + std::string texture = "", u16 scale = 1); + + void setModeIndex(size_t index); + size_t getModeIndex() const { return m_current_mode_index; }; + size_t getMaxModeIndex() const { return m_modes.size() - 1; }; + void nextMode(); + + void setModesFromString(std::string modes_string); + MinimapModeDef getModeDef() const { return data->mode; } video::ITexture *getMinimapTexture(); @@ -144,6 +144,7 @@ public: void updateActiveMarkers(); void drawMinimap(); + void drawMinimap(core::rect rect); video::IVideoDriver *driver; Client* client; @@ -153,9 +154,11 @@ private: ITextureSource *m_tsrc; IShaderSource *m_shdrsrc; const NodeDefManager *m_ndef; - MinimapUpdateThread *m_minimap_update_thread; + MinimapUpdateThread *m_minimap_update_thread = nullptr; scene::SMeshBuffer *m_meshbuffer; bool m_enable_shaders; + std::vector m_modes; + size_t m_current_mode_index; u16 m_surface_mode_scan_height; f32 m_angle; std::mutex m_mutex; diff --git a/src/hud.cpp b/src/hud.cpp index 175701342..1791e04df 100644 --- a/src/hud.cpp +++ b/src/hud.cpp @@ -29,6 +29,7 @@ const struct EnumString es_HudElementType[] = {HUD_ELEM_WAYPOINT, "waypoint"}, {HUD_ELEM_IMAGE_WAYPOINT, "image_waypoint"}, {HUD_ELEM_COMPASS, "compass"}, + {HUD_ELEM_MINIMAP, "minimap"}, {0, NULL}, }; diff --git a/src/hud.h b/src/hud.h index 0a6993c1b..a0613ae98 100644 --- a/src/hud.h +++ b/src/hud.h @@ -51,7 +51,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #define HUD_HOTBAR_ITEMCOUNT_DEFAULT 8 #define HUD_HOTBAR_ITEMCOUNT_MAX 32 - #define HOTBAR_IMAGE_SIZE 48 enum HudElementType { @@ -61,7 +60,8 @@ enum HudElementType { HUD_ELEM_INVENTORY = 3, HUD_ELEM_WAYPOINT = 4, HUD_ELEM_IMAGE_WAYPOINT = 5, - HUD_ELEM_COMPASS = 6 + HUD_ELEM_COMPASS = 6, + HUD_ELEM_MINIMAP = 7 }; enum HudElementStat { @@ -108,3 +108,12 @@ extern const EnumString es_HudElementType[]; extern const EnumString es_HudElementStat[]; extern const EnumString es_HudBuiltinElement[]; +// Minimap stuff + +enum MinimapType { + MINIMAP_TYPE_OFF, + MINIMAP_TYPE_SURFACE, + MINIMAP_TYPE_RADAR, + MINIMAP_TYPE_TEXTURE, +}; + diff --git a/src/network/clientopcodes.cpp b/src/network/clientopcodes.cpp index f812a08a1..55cfdd4dc 100644 --- a/src/network/clientopcodes.cpp +++ b/src/network/clientopcodes.cpp @@ -122,6 +122,7 @@ const ToClientCommandHandler toClientCommandTable[TOCLIENT_NUM_MSG_TYPES] = null_command_handler, { "TOCLIENT_SRP_BYTES_S_B", TOCLIENT_STATE_NOT_CONNECTED, &Client::handleCommand_SrpBytesSandB }, // 0x60 { "TOCLIENT_FORMSPEC_PREPEND", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_FormspecPrepend }, // 0x61, + { "TOCLIENT_MINIMAP_MODES", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_MinimapModes }, // 0x62, }; const static ServerCommandFactory null_command_factory = { "TOSERVER_NULL", 0, false }; diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index 5683564a0..65db02300 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -1164,15 +1164,24 @@ void Client::handleCommand_HudSetFlags(NetworkPacket* pkt) m_minimap_disabled_by_server = !(player->hud_flags & HUD_FLAG_MINIMAP_VISIBLE); bool m_minimap_radar_disabled_by_server = !(player->hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE); + // Not so satisying code to keep compatibility with old fixed mode system + // --> + // Hide minimap if it has been disabled by the server if (m_minimap && m_minimap_disabled_by_server && was_minimap_visible) // defers a minimap update, therefore only call it if really // needed, by checking that minimap was visible before - m_minimap->setMinimapMode(MINIMAP_MODE_OFF); + m_minimap->setModeIndex(0); - // Switch to surface mode if radar disabled by server - if (m_minimap && m_minimap_radar_disabled_by_server && was_minimap_radar_visible) - m_minimap->setMinimapMode(MINIMAP_MODE_SURFACEx1); + // If radar has been disabled, try to find a non radar mode or fall back to 0 + if (m_minimap && m_minimap_radar_disabled_by_server + && was_minimap_radar_visible) { + while (m_minimap->getModeIndex() > 0 && + m_minimap->getModeDef().type == MINIMAP_TYPE_RADAR) + m_minimap->nextMode(); + } + // <-- + // End of 'not so satifying code' } void Client::handleCommand_HudSetParam(NetworkPacket* pkt) @@ -1611,3 +1620,30 @@ void Client::handleCommand_ModChannelSignal(NetworkPacket *pkt) if (valid_signal) m_script->on_modchannel_signal(channel, signal); } + +void Client::handleCommand_MinimapModes(NetworkPacket *pkt) +{ + u16 count; // modes + u16 mode; // wanted current mode index after change + + *pkt >> count >> mode; + + if (m_minimap) + m_minimap->clearModes(); + + for (size_t index = 0; index < count; index++) { + u16 type; + std::string label; + u16 size; + std::string texture; + u16 scale; + + *pkt >> type >> label >> size >> texture >> scale; + + if (m_minimap) + m_minimap->addMode(MinimapType(type), size, label, texture, scale); + } + + if (m_minimap) + m_minimap->setModeIndex(mode); +} diff --git a/src/network/networkprotocol.h b/src/network/networkprotocol.h index 05600cda9..666e75e45 100644 --- a/src/network/networkprotocol.h +++ b/src/network/networkprotocol.h @@ -204,6 +204,7 @@ with this program; if not, write to the Free Software Foundation, Inc., PROTOCOL VERSION 39: Updated set_sky packet Adds new sun, moon and stars packets + Minimap modes */ #define LATEST_PROTOCOL_VERSION 39 @@ -764,7 +765,18 @@ enum ToClientCommand u8[len] formspec */ - TOCLIENT_NUM_MSG_TYPES = 0x62, + TOCLIENT_MINIMAP_MODES = 0x62, + /* + u16 count // modes + u16 mode // wanted current mode index after change + for each mode + u16 type + std::string label + u16 size + std::string extra + */ + + TOCLIENT_NUM_MSG_TYPES = 0x63, }; enum ToServerCommand diff --git a/src/network/serveropcodes.cpp b/src/network/serveropcodes.cpp index 2fc3197c2..aea5d7174 100644 --- a/src/network/serveropcodes.cpp +++ b/src/network/serveropcodes.cpp @@ -221,4 +221,5 @@ const ClientCommandFactory clientCommandFactoryTable[TOCLIENT_NUM_MSG_TYPES] = null_command_factory, // 0x5f { "TOSERVER_SRP_BYTES_S_B", 0, true }, // 0x60 { "TOCLIENT_FORMSPEC_PREPEND", 0, true }, // 0x61 + { "TOCLIENT_MINIMAP_MODES", 0, true }, // 0x62 }; diff --git a/src/script/lua_api/l_minimap.cpp b/src/script/lua_api/l_minimap.cpp index 5fba76eb8..3bbb6e5e3 100644 --- a/src/script/lua_api/l_minimap.cpp +++ b/src/script/lua_api/l_minimap.cpp @@ -89,7 +89,7 @@ int LuaMinimap::l_get_mode(lua_State *L) LuaMinimap *ref = checkobject(L, 1); Minimap *m = getobject(ref); - lua_pushinteger(L, m->getMinimapMode()); + lua_pushinteger(L, m->getModeIndex()); return 1; } @@ -98,13 +98,11 @@ int LuaMinimap::l_set_mode(lua_State *L) LuaMinimap *ref = checkobject(L, 1); Minimap *m = getobject(ref); - s32 mode = lua_tointeger(L, 2); - if (mode < MINIMAP_MODE_OFF || - mode >= MINIMAP_MODE_COUNT) { + u32 mode = lua_tointeger(L, 2); + if (mode >= m->getMaxModeIndex()) return 0; - } - m->setMinimapMode((MinimapMode) mode); + m->setModeIndex(mode); return 1; } @@ -140,8 +138,11 @@ int LuaMinimap::l_show(lua_State *L) LuaMinimap *ref = checkobject(L, 1); Minimap *m = getobject(ref); - if (m->getMinimapMode() == MINIMAP_MODE_OFF) - m->setMinimapMode(MINIMAP_MODE_SURFACEx1); + // This is not very adapted to new minimap mode management. Btw, tried + // to do something compatible. + + if (m->getModeIndex() == 0 && m->getMaxModeIndex() > 0) + m->setModeIndex(1); client->showMinimap(true); return 1; @@ -155,8 +156,11 @@ int LuaMinimap::l_hide(lua_State *L) LuaMinimap *ref = checkobject(L, 1); Minimap *m = getobject(ref); - if (m->getMinimapMode() != MINIMAP_MODE_OFF) - m->setMinimapMode(MINIMAP_MODE_OFF); + // This is not very adapted to new minimap mode management. Btw, tried + // to do something compatible. + + if (m->getModeIndex() != 0) + m->setModeIndex(0); client->showMinimap(false); return 1; diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index fead4e849..57ed7e2cd 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -2199,6 +2199,67 @@ int ObjectRef::l_get_day_night_ratio(lua_State *L) return 1; } +// set_minimap_modes(self, modes, modeindex) +int ObjectRef::l_set_minimap_modes(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + + // Arg 1 should be a player + ObjectRef *ref = checkobject(L, 1); + RemotePlayer *player = getplayer(ref); + if (player == NULL) + return 0; + + // Arg 2 should be a table (of tables) + if (!lua_istable(L, 2)) { + return 0; + } + + // Arg 3 should be a number + s16 wantedmode = lua_tonumber(L, 3); + + std::vector modes; + + lua_pushnil(L); + while (lua_next(L, 2) != 0) { + /* key is at index -2, value is at index -1 */ + if (lua_istable(L, -1)) { + bool ok = true; + MinimapMode mode; + std::string type = getstringfield_default(L, -1, "type", ""); + if (type == "off") + mode.type = MINIMAP_TYPE_OFF; + else if (type == "surface") + mode.type = MINIMAP_TYPE_SURFACE; + else if (type == "radar") + mode.type = MINIMAP_TYPE_RADAR; + else if (type == "texture") { + mode.type = MINIMAP_TYPE_TEXTURE; + mode.texture = getstringfield_default(L, -1, "texture", ""); + mode.scale = getintfield_default(L, -1, "scale", 1); + } else { + warningstream << "Minimap mode of unknown type \"" << type.c_str() + << "\" ignored.\n" << std::endl; + ok = false; + } + + if (ok) { + mode.label = getstringfield_default(L, -1, "label", ""); + // Size is limited to 512. Performance gets poor if size too large, and + // segfaults have been experienced. + mode.size = rangelim(getintfield_default(L, -1, "size", 0), 1, 512); + modes.push_back(mode); + } + } + /* removes 'value'; keeps 'key' for next iteration */ + lua_pop(L, 1); + } + lua_pop(L, 1); // Remove key + + getServer(L)->SendMinimapModes(player->getPeerId(), modes, wantedmode); + return 0; +} + ObjectRef::ObjectRef(ServerActiveObject *object): m_object(object) { @@ -2359,5 +2420,6 @@ luaL_Reg ObjectRef::methods[] = { luamethod(ObjectRef, set_eye_offset), luamethod(ObjectRef, get_eye_offset), luamethod(ObjectRef, send_mapblock), + luamethod(ObjectRef, set_minimap_modes), {0,0} }; diff --git a/src/script/lua_api/l_object.h b/src/script/lua_api/l_object.h index 126719b1f..ca03dfa2e 100644 --- a/src/script/lua_api/l_object.h +++ b/src/script/lua_api/l_object.h @@ -377,4 +377,7 @@ private: // send_mapblock(pos) static int l_send_mapblock(lua_State *L); + + // set_minimap_modes(self, modes, wanted_mode) + static int l_set_minimap_modes(lua_State *L); }; diff --git a/src/server.cpp b/src/server.cpp index 982f904f4..8f6257afe 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -2662,6 +2662,23 @@ void Server::sendRequestedMedia(session_t peer_id, } } +void Server::SendMinimapModes(session_t peer_id, + std::vector &modes, size_t wanted_mode) +{ + RemotePlayer *player = m_env->getPlayer(peer_id); + assert(player); + if (player->getPeerId() == PEER_ID_INEXISTENT) + return; + + NetworkPacket pkt(TOCLIENT_MINIMAP_MODES, 0, peer_id); + pkt << (u16)modes.size() << (u16)wanted_mode; + + for (auto &mode : modes) + pkt << (u16)mode.type << mode.label << mode.size << mode.texture << mode.scale; + + Send(&pkt); +} + void Server::sendDetachedInventory(Inventory *inventory, const std::string &name, session_t peer_id) { NetworkPacket pkt(TOCLIENT_DETACHED_INVENTORY, 0, peer_id); diff --git a/src/server.h b/src/server.h index 258d75eaf..4b3ac5cf7 100644 --- a/src/server.h +++ b/src/server.h @@ -118,6 +118,14 @@ struct ServerPlayingSound std::unordered_set clients; // peer ids }; +struct MinimapMode { + MinimapType type = MINIMAP_TYPE_OFF; + std::string label; + u16 size = 0; + std::string texture; + u16 scale = 1; +}; + class Server : public con::PeerHandler, public MapEventReceiver, public IGameDef { @@ -331,6 +339,10 @@ public: void SendPlayerSpeed(session_t peer_id, const v3f &added_vel); void SendPlayerFov(session_t peer_id); + void SendMinimapModes(session_t peer_id, + std::vector &modes, + size_t wanted_mode); + void sendDetachedInventories(session_t peer_id, bool incremental); virtual bool registerModStorage(ModMetadata *storage); From f46509d5e2c681b6da2abdeb27779be3c36a6916 Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Mon, 5 Oct 2020 09:07:33 +0200 Subject: [PATCH 095/266] Remove unused functions reported by cppcheck (#10463) Run unused functions reported by cppcheck This change removes a few (but not all) unused functions. Some unused helper functions were not removed due to their complexity and potential of future use. --- src/chat.cpp | 15 ++--- src/chat.h | 2 - src/client/client.cpp | 5 -- src/client/client.h | 1 - src/client/clientenvironment.cpp | 15 ----- src/client/clientmap.cpp | 2 +- src/client/mapblock_mesh.cpp | 27 --------- src/client/mapblock_mesh.h | 5 -- src/content/CMakeLists.txt | 1 - src/content/packages.cpp | 69 ---------------------- src/content/packages.h | 52 ---------------- src/gui/guiChatConsole.cpp | 5 -- src/gui/guiChatConsole.h | 4 -- src/log.cpp | 11 +--- src/log.h | 10 ---- src/map.cpp | 63 +------------------- src/map.h | 21 +------ src/mapgen/mapgen.cpp | 33 ----------- src/mapgen/mapgen.h | 7 +-- src/mapgen/mapgen_v6.cpp | 6 -- src/mapgen/mapgen_v6.h | 1 - src/mapgen/treegen.cpp | 13 ---- src/mapgen/treegen.h | 2 - src/network/networkpacket.cpp | 7 --- src/network/networkpacket.h | 1 - src/script/common/c_converter.cpp | 45 -------------- src/script/common/c_converter.h | 2 - src/script/lua_api/l_mainmenu.cpp | 1 - src/unittest/test_map_settings_manager.cpp | 28 +-------- 29 files changed, 14 insertions(+), 440 deletions(-) delete mode 100644 src/content/packages.cpp delete mode 100644 src/content/packages.h diff --git a/src/chat.cpp b/src/chat.cpp index c3ed59804..2f65e68b3 100644 --- a/src/chat.cpp +++ b/src/chat.cpp @@ -123,16 +123,16 @@ void ChatBuffer::deleteByAge(f32 maxAge) deleteOldest(count); } -u32 ChatBuffer::getColumns() const -{ - return m_cols; -} - u32 ChatBuffer::getRows() const { return m_rows; } +void ChatBuffer::scrollTop() +{ + m_scroll = getTopScrollPos(); +} + void ChatBuffer::reformat(u32 cols, u32 rows) { if (cols == 0 || rows == 0) @@ -220,11 +220,6 @@ void ChatBuffer::scrollBottom() m_scroll = getBottomScrollPos(); } -void ChatBuffer::scrollTop() -{ - m_scroll = getTopScrollPos(); -} - u32 ChatBuffer::formatChatLine(const ChatLine& line, u32 cols, std::vector& destination) const { diff --git a/src/chat.h b/src/chat.h index f84ece206..0b98e4d3c 100644 --- a/src/chat.h +++ b/src/chat.h @@ -94,8 +94,6 @@ public: // Delete lines older than maxAge. void deleteByAge(f32 maxAge); - // Get number of columns, 0 if reformat has not been called yet. - u32 getColumns() const; // Get number of rows, 0 if reformat has not been called yet. u32 getRows() const; // Update console size and reformat all formatted lines. diff --git a/src/client/client.cpp b/src/client/client.cpp index 7fafe0da9..0bd98b256 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -1665,11 +1665,6 @@ ClientEvent *Client::getClientEvent() return event; } -bool Client::connectedToServer() -{ - return m_con->Connected(); -} - const Address Client::getServerAddress() { return m_con->GetPeerAddress(PEER_ID_SERVER); diff --git a/src/client/client.h b/src/client/client.h index 8f4aac6e3..bffdc7ec6 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -338,7 +338,6 @@ public: u16 getProtoVersion() { return m_proto_ver; } - bool connectedToServer(); void confirmRegistration(); bool m_is_registration_confirmation_state = false; bool m_simple_singleplayer_mode; diff --git a/src/client/clientenvironment.cpp b/src/client/clientenvironment.cpp index 0b7e92325..f831978a8 100644 --- a/src/client/clientenvironment.cpp +++ b/src/client/clientenvironment.cpp @@ -368,21 +368,6 @@ bool isFreeClientActiveObjectId(const u16 id, } -u16 getFreeClientActiveObjectId(ClientActiveObjectMap &objects) -{ - // try to reuse id's as late as possible - static u16 last_used_id = 0; - u16 startid = last_used_id; - for(;;) { - last_used_id ++; - if (isFreeClientActiveObjectId(last_used_id, objects)) - return last_used_id; - - if (last_used_id == startid) - return 0; - } -} - u16 ClientEnvironment::addActiveObject(ClientActiveObject *object) { // Register object. If failed return zero id diff --git a/src/client/clientmap.cpp b/src/client/clientmap.cpp index 3e4ab2e94..3124313e7 100644 --- a/src/client/clientmap.cpp +++ b/src/client/clientmap.cpp @@ -36,7 +36,7 @@ ClientMap::ClientMap( MapDrawControl &control, s32 id ): - Map(dout_client, client), + Map(client), scene::ISceneNode(RenderingEngine::get_scene_manager()->getRootSceneNode(), RenderingEngine::get_scene_manager(), id), m_client(client), diff --git a/src/client/mapblock_mesh.cpp b/src/client/mapblock_mesh.cpp index b59679523..f9332e399 100644 --- a/src/client/mapblock_mesh.cpp +++ b/src/client/mapblock_mesh.cpp @@ -81,33 +81,6 @@ void MeshMakeData::fill(MapBlock *block) } } -void MeshMakeData::fillSingleNode(MapNode *node) -{ - m_blockpos = v3s16(0,0,0); - - v3s16 blockpos_nodes = v3s16(0,0,0); - VoxelArea area(blockpos_nodes-v3s16(1,1,1)*MAP_BLOCKSIZE, - blockpos_nodes+v3s16(1,1,1)*MAP_BLOCKSIZE*2-v3s16(1,1,1)); - s32 volume = area.getVolume(); - s32 our_node_index = area.index(1,1,1); - - // Allocate this block + neighbors - m_vmanip.clear(); - m_vmanip.addArea(area); - - // Fill in data - MapNode *data = new MapNode[volume]; - for(s32 i = 0; i < volume; i++) - { - if (i == our_node_index) - data[i] = *node; - else - data[i] = MapNode(CONTENT_AIR, LIGHT_MAX, 0); - } - m_vmanip.copyFrom(data, area, area.MinEdge, area.MinEdge, area.getExtent()); - delete[] data; -} - void MeshMakeData::setCrack(int crack_level, v3s16 crack_pos) { if (crack_level >= 0) diff --git a/src/client/mapblock_mesh.h b/src/client/mapblock_mesh.h index 6af23a656..f8aed40dc 100644 --- a/src/client/mapblock_mesh.h +++ b/src/client/mapblock_mesh.h @@ -62,11 +62,6 @@ struct MeshMakeData */ void fill(MapBlock *block); - /* - Set up with only a single node at (1,1,1) - */ - void fillSingleNode(MapNode *node); - /* Set the (node) position of a crack */ diff --git a/src/content/CMakeLists.txt b/src/content/CMakeLists.txt index 5adcf6b1e..6dd049418 100644 --- a/src/content/CMakeLists.txt +++ b/src/content/CMakeLists.txt @@ -1,6 +1,5 @@ set(content_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/content.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/packages.cpp ${CMAKE_CURRENT_SOURCE_DIR}/mods.cpp ${CMAKE_CURRENT_SOURCE_DIR}/subgames.cpp PARENT_SCOPE diff --git a/src/content/packages.cpp b/src/content/packages.cpp deleted file mode 100644 index 2d488eb76..000000000 --- a/src/content/packages.cpp +++ /dev/null @@ -1,69 +0,0 @@ -/* -Minetest -Copyright (C) 2018 rubenwardy - -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. -*/ - -#include "content/packages.h" -#include "log.h" -#include "filesys.h" -#include "porting.h" -#include "settings.h" -#include "content/mods.h" -#include "content/subgames.h" - -std::string Package::getDownloadURL(const std::string &baseURL) const -{ - return baseURL + "/packages/" + author + "/" + name + "/releases/" + - std::to_string(release) + "/download/"; -} - -#if USE_CURL -std::vector getPackagesFromURL(const std::string &url) -{ - std::vector extra_headers; - extra_headers.emplace_back("Accept: application/json"); - - Json::Value json = fetchJsonValue(url, &extra_headers); - if (!json.isArray()) { - errorstream << "Invalid JSON download " << std::endl; - return std::vector(); - } - - std::vector packages; - - // Note: `unsigned int` is required to index JSON - for (unsigned int i = 0; i < json.size(); ++i) { - Package package; - - package.author = json[i]["author"].asString(); - package.name = json[i]["name"].asString(); - package.title = json[i]["title"].asString(); - package.type = json[i]["type"].asString(); - package.shortDesc = json[i]["shortDesc"].asString(); - package.release = json[i]["release"].asInt(); - if (json[i].isMember("thumbnail")) - package.thumbnail = json[i]["thumbnail"].asString(); - - if (package.valid()) - packages.push_back(package); - else - errorstream << "Invalid package at " << i << std::endl; - } - - return packages; -} -#endif diff --git a/src/content/packages.h b/src/content/packages.h deleted file mode 100644 index 9029475ef..000000000 --- a/src/content/packages.h +++ /dev/null @@ -1,52 +0,0 @@ -/* -Minetest -Copyright (C) 2018 rubenwardy - -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 "config.h" -#include "convert_json.h" -#include "irrlichttypes.h" - -struct Package -{ - std::string author; - std::string name; // Technical name - std::string title; - std::string type; // One of "mod", "game", or "txp" - - std::string shortDesc; - u32 release; - std::string thumbnail; - - bool valid() const - { - return !(author.empty() || name.empty() || title.empty() || - type.empty() || release <= 0); - } - - std::string getDownloadURL(const std::string &baseURL) const; -}; - -#if USE_CURL -std::vector getPackagesFromURL(const std::string &url); -#else -inline std::vector getPackagesFromURL(const std::string &url) -{ - return std::vector(); -} -#endif diff --git a/src/gui/guiChatConsole.cpp b/src/gui/guiChatConsole.cpp index 575c6ab25..ef471106d 100644 --- a/src/gui/guiChatConsole.cpp +++ b/src/gui/guiChatConsole.cpp @@ -136,11 +136,6 @@ void GUIChatConsole::closeConsoleAtOnce() recalculateConsolePosition(); } -f32 GUIChatConsole::getDesiredHeight() const -{ - return m_desired_height_fraction; -} - void GUIChatConsole::replaceAndAddToHistory(const std::wstring &line) { ChatPrompt& prompt = m_chat_backend->getPrompt(); diff --git a/src/gui/guiChatConsole.h b/src/gui/guiChatConsole.h index 7be40e27c..204f9f9cc 100644 --- a/src/gui/guiChatConsole.h +++ b/src/gui/guiChatConsole.h @@ -55,10 +55,6 @@ public: // Set whether to close the console after the user presses enter. void setCloseOnEnter(bool close) { m_close_on_enter = close; } - // Return the desired height (fraction of screen size) - // Zero if the console is closed or getting closed - f32 getDesiredHeight() const; - // Replace actual line when adding the actual to the history (if there is any) void replaceAndAddToHistory(const std::wstring &line); diff --git a/src/log.cpp b/src/log.cpp index 54442c39b..3c61414e9 100644 --- a/src/log.cpp +++ b/src/log.cpp @@ -97,16 +97,7 @@ LogBuffer verbose_buf(g_logger, LL_VERBOSE); std::ostream *dout_con_ptr = &null_stream; std::ostream *derr_con_ptr = &verbosestream; -// Server -std::ostream *dout_server_ptr = &infostream; -std::ostream *derr_server_ptr = &errorstream; - -#ifndef SERVER -// Client -std::ostream *dout_client_ptr = &infostream; -std::ostream *derr_client_ptr = &errorstream; -#endif - +// Common streams std::ostream rawstream(&raw_buf); std::ostream dstream(&none_buf); std::ostream errorstream(&error_buf); diff --git a/src/log.h b/src/log.h index 856d3479b..6ed6b1fb7 100644 --- a/src/log.h +++ b/src/log.h @@ -192,14 +192,8 @@ extern std::ostream null_stream; extern std::ostream *dout_con_ptr; extern std::ostream *derr_con_ptr; -extern std::ostream *dout_server_ptr; extern std::ostream *derr_server_ptr; -#ifndef SERVER -extern std::ostream *dout_client_ptr; -extern std::ostream *derr_client_ptr; -#endif - extern Logger g_logger; // Writes directly to all LL_NONE log outputs for g_logger with no prefix. @@ -222,8 +216,4 @@ extern std::ostream dstream; #define dout_con (*dout_con_ptr) #define derr_con (*derr_con_ptr) -#define dout_server (*dout_server_ptr) -#ifndef SERVER - #define dout_client (*dout_client_ptr) -#endif diff --git a/src/map.cpp b/src/map.cpp index b9ab7c066..ef7eddb39 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -62,8 +62,7 @@ with this program; if not, write to the Free Software Foundation, Inc., Map */ -Map::Map(std::ostream &dout, IGameDef *gamedef): - m_dout(dout), +Map::Map(IGameDef *gamedef): m_gamedef(gamedef), m_nodedef(gamedef->ndef()) { @@ -1201,7 +1200,7 @@ bool Map::isBlockOccluded(MapBlock *block, v3s16 cam_pos_nodes) */ ServerMap::ServerMap(const std::string &savedir, IGameDef *gamedef, EmergeManager *emerge, MetricsBackend *mb): - Map(dout_server, gamedef), + Map(gamedef), settings_mgr(g_settings, savedir + DIR_DELIM + "map_meta.txt"), m_emerge(emerge) { @@ -1327,11 +1326,6 @@ u64 ServerMap::getSeed() return getMapgenParams()->seed; } -s16 ServerMap::getWaterLevel() -{ - return getMapgenParams()->water_level; -} - bool ServerMap::blockpos_over_mapgen_limit(v3s16 p) { const s16 mapgen_limit_bp = rangelim( @@ -1732,59 +1726,6 @@ void ServerMap::updateVManip(v3s16 pos) vm->m_is_dirty = true; } -s16 ServerMap::findGroundLevel(v2s16 p2d) -{ -#if 0 - /* - Uh, just do something random... - */ - // Find existing map from top to down - s16 max=63; - s16 min=-64; - v3s16 p(p2d.X, max, p2d.Y); - for(; p.Y>min; p.Y--) - { - MapNode n = getNodeNoEx(p); - if(n.getContent() != CONTENT_IGNORE) - break; - } - if(p.Y == min) - goto plan_b; - // If this node is not air, go to plan b - if(getNodeNoEx(p).getContent() != CONTENT_AIR) - goto plan_b; - // Search existing walkable and return it - for(; p.Y>min; p.Y--) - { - MapNode n = getNodeNoEx(p); - if(content_walkable(n.d) && n.getContent() != CONTENT_IGNORE) - return p.Y; - } - - // Move to plan b -plan_b: -#endif - - /* - Determine from map generator noise functions - */ - - s16 level = m_emerge->getGroundLevelAtPoint(p2d); - return level; - - //double level = base_rock_level_2d(m_seed, p2d) + AVERAGE_MUD_AMOUNT; - //return (s16)level; -} - -void ServerMap::createDirs(const std::string &path) -{ - if (!fs::CreateAllDirs(path)) { - m_dout<<"ServerMap: Failed to create directory " - <<"\""< *getSectorsPtr(){return &m_sectors;} - /* Variables */ @@ -283,8 +276,6 @@ public: protected: friend class LuaVoxelManip; - std::ostream &m_dout; // A bit deprecated, could be removed - IGameDef *m_gamedef; std::set m_event_receivers; @@ -374,15 +365,6 @@ public: */ MapBlock *getBlockOrEmerge(v3s16 p3d); - // Helper for placing objects on ground level - s16 findGroundLevel(v2s16 p2d); - - /* - Misc. helper functions for fiddling with directory and file - names when saving - */ - void createDirs(const std::string &path); - /* Database functions */ @@ -414,7 +396,6 @@ public: bool isSavingEnabled(){ return m_map_saving_enabled; } u64 getSeed(); - s16 getWaterLevel(); /*! * Fixes lighting in one map block. diff --git a/src/mapgen/mapgen.cpp b/src/mapgen/mapgen.cpp index 52ef64e7e..e0dfd2d71 100644 --- a/src/mapgen/mapgen.cpp +++ b/src/mapgen/mapgen.cpp @@ -244,26 +244,6 @@ u32 Mapgen::getBlockSeed2(v3s16 p, s32 seed) } -// Returns Y one under area minimum if not found -s16 Mapgen::findGroundLevelFull(v2s16 p2d) -{ - const v3s16 &em = vm->m_area.getExtent(); - s16 y_nodes_max = vm->m_area.MaxEdge.Y; - s16 y_nodes_min = vm->m_area.MinEdge.Y; - u32 i = vm->m_area.index(p2d.X, y_nodes_max, p2d.Y); - s16 y; - - for (y = y_nodes_max; y >= y_nodes_min; y--) { - MapNode &n = vm->m_data[i]; - if (ndef->get(n).walkable) - break; - - VoxelArea::add_y(em, i, -1); - } - return (y >= y_nodes_min) ? y : y_nodes_min - 1; -} - - // Returns -MAX_MAP_GENERATION_LIMIT if not found s16 Mapgen::findGroundLevel(v2s16 p2d, s16 ymin, s16 ymax) { @@ -984,19 +964,6 @@ GenerateNotifier::GenerateNotifier(u32 notify_on, } -void GenerateNotifier::setNotifyOn(u32 notify_on) -{ - m_notify_on = notify_on; -} - - -void GenerateNotifier::setNotifyOnDecoIds( - const std::set *notify_on_deco_ids) -{ - m_notify_on_deco_ids = notify_on_deco_ids; -} - - bool GenerateNotifier::addEvent(GenNotifyType type, v3s16 pos, u32 id) { if (!(m_notify_on & (1 << type))) diff --git a/src/mapgen/mapgen.h b/src/mapgen/mapgen.h index 0f2977c4c..1487731e2 100644 --- a/src/mapgen/mapgen.h +++ b/src/mapgen/mapgen.h @@ -88,19 +88,17 @@ struct GenNotifyEvent { class GenerateNotifier { public: + // Use only for temporary Mapgen objects with no map generation! GenerateNotifier() = default; GenerateNotifier(u32 notify_on, const std::set *notify_on_deco_ids); - void setNotifyOn(u32 notify_on); - void setNotifyOnDecoIds(const std::set *notify_on_deco_ids); - bool addEvent(GenNotifyType type, v3s16 pos, u32 id=0); void getEvents(std::map > &event_map); void clearEvents(); private: u32 m_notify_on = 0; - const std::set *m_notify_on_deco_ids; + const std::set *m_notify_on_deco_ids = nullptr; std::list m_notify_events; }; @@ -186,7 +184,6 @@ public: static u32 getBlockSeed(v3s16 p, s32 seed); static u32 getBlockSeed2(v3s16 p, s32 seed); - s16 findGroundLevelFull(v2s16 p2d); s16 findGroundLevel(v2s16 p2d, s16 ymin, s16 ymax); s16 findLiquidSurface(v2s16 p2d, s16 ymin, s16 ymax); void updateHeightmap(v3s16 nmin, v3s16 nmax); diff --git a/src/mapgen/mapgen_v6.cpp b/src/mapgen/mapgen_v6.cpp index 8bea5eb69..90a52e031 100644 --- a/src/mapgen/mapgen_v6.cpp +++ b/src/mapgen/mapgen_v6.cpp @@ -341,12 +341,6 @@ float MapgenV6::baseTerrainLevelFromMap(int index) } -s16 MapgenV6::find_ground_level_from_noise(u64 seed, v2s16 p2d, s16 precision) -{ - return baseTerrainLevelFromNoise(p2d) + MGV6_AVERAGE_MUD_AMOUNT; -} - - int MapgenV6::getGroundLevelAtPoint(v2s16 p) { return baseTerrainLevelFromNoise(p) + MGV6_AVERAGE_MUD_AMOUNT; diff --git a/src/mapgen/mapgen_v6.h b/src/mapgen/mapgen_v6.h index ff565edec..a6e6da8c6 100644 --- a/src/mapgen/mapgen_v6.h +++ b/src/mapgen/mapgen_v6.h @@ -150,7 +150,6 @@ public: s16 find_stone_level(v2s16 p2d); bool block_is_underground(u64 seed, v3s16 blockpos); - s16 find_ground_level_from_noise(u64 seed, v2s16 p2d, s16 precision); float getHumidity(v2s16 p); float getTreeAmount(v2s16 p); diff --git a/src/mapgen/treegen.cpp b/src/mapgen/treegen.cpp index e7e30c880..e633d800a 100644 --- a/src/mapgen/treegen.cpp +++ b/src/mapgen/treegen.cpp @@ -527,19 +527,6 @@ treegen::error make_ltree(MMVManip &vmanip, v3s16 p0, } -void tree_node_placement(MMVManip &vmanip, v3f p0, MapNode node) -{ - v3s16 p1 = v3s16(myround(p0.X), myround(p0.Y), myround(p0.Z)); - if (!vmanip.m_area.contains(p1)) - return; - u32 vi = vmanip.m_area.index(p1); - if (vmanip.m_data[vi].getContent() != CONTENT_AIR - && vmanip.m_data[vi].getContent() != CONTENT_IGNORE) - return; - vmanip.m_data[vmanip.m_area.index(p1)] = node; -} - - void tree_trunk_placement(MMVManip &vmanip, v3f p0, TreeDef &tree_definition) { v3s16 p1 = v3s16(myround(p0.X), myround(p0.Y), myround(p0.Z)); diff --git a/src/mapgen/treegen.h b/src/mapgen/treegen.h index 447baabb3..59a418824 100644 --- a/src/mapgen/treegen.h +++ b/src/mapgen/treegen.h @@ -76,8 +76,6 @@ namespace treegen { const NodeDefManager *ndef, const TreeDef &tree_definition); // L-System tree gen helper functions - void tree_node_placement(MMVManip &vmanip, v3f p0, - MapNode node); void tree_trunk_placement(MMVManip &vmanip, v3f p0, TreeDef &tree_definition); void tree_leaves_placement(MMVManip &vmanip, v3f p0, diff --git a/src/network/networkpacket.cpp b/src/network/networkpacket.cpp index 4d531b611..6d0abb12c 100644 --- a/src/network/networkpacket.cpp +++ b/src/network/networkpacket.cpp @@ -223,13 +223,6 @@ NetworkPacket& NetworkPacket::operator>>(char& dst) return *this; } -char NetworkPacket::getChar(u32 offset) -{ - checkReadOffset(offset, 1); - - return readU8(&m_data[offset]); -} - NetworkPacket& NetworkPacket::operator<<(char src) { checkDataSize(1); diff --git a/src/network/networkpacket.h b/src/network/networkpacket.h index fc8617651..e77bfb744 100644 --- a/src/network/networkpacket.h +++ b/src/network/networkpacket.h @@ -64,7 +64,6 @@ public: std::string readLongString(); - char getChar(u32 offset); NetworkPacket &operator>>(char &dst); NetworkPacket &operator<<(char src); diff --git a/src/script/common/c_converter.cpp b/src/script/common/c_converter.cpp index eb6ab5331..c00401b58 100644 --- a/src/script/common/c_converter.cpp +++ b/src/script/common/c_converter.cpp @@ -679,48 +679,3 @@ size_t write_array_slice_float( return elem_index - 1; } - - -size_t write_array_slice_u16( - lua_State *L, - int table_index, - u16 *data, - v3u16 data_size, - v3u16 slice_offset, - v3u16 slice_size) -{ - v3u16 pmin, pmax(data_size); - - if (slice_offset.X > 0) { - slice_offset.X--; - pmin.X = slice_offset.X; - pmax.X = MYMIN(slice_offset.X + slice_size.X, data_size.X); - } - - if (slice_offset.Y > 0) { - slice_offset.Y--; - pmin.Y = slice_offset.Y; - pmax.Y = MYMIN(slice_offset.Y + slice_size.Y, data_size.Y); - } - - if (slice_offset.Z > 0) { - slice_offset.Z--; - pmin.Z = slice_offset.Z; - pmax.Z = MYMIN(slice_offset.Z + slice_size.Z, data_size.Z); - } - - const u32 ystride = data_size.X; - const u32 zstride = data_size.X * data_size.Y; - - u32 elem_index = 1; - for (u32 z = pmin.Z; z != pmax.Z; z++) - for (u32 y = pmin.Y; y != pmax.Y; y++) - for (u32 x = pmin.X; x != pmax.X; x++) { - u32 i = z * zstride + y * ystride + x; - lua_pushinteger(L, data[i]); - lua_rawseti(L, table_index, elem_index); - elem_index++; - } - - return elem_index - 1; -} diff --git a/src/script/common/c_converter.h b/src/script/common/c_converter.h index a4a7079fd..6ad6f3212 100644 --- a/src/script/common/c_converter.h +++ b/src/script/common/c_converter.h @@ -136,5 +136,3 @@ void warn_if_field_exists(lua_State *L, int table, size_t write_array_slice_float(lua_State *L, int table_index, float *data, v3u16 data_size, v3u16 slice_offset, v3u16 slice_size); -size_t write_array_slice_u16(lua_State *L, int table_index, u16 *data, - v3u16 data_size, v3u16 slice_offset, v3u16 slice_size); diff --git a/src/script/lua_api/l_mainmenu.cpp b/src/script/lua_api/l_mainmenu.cpp index e49ec4052..0aa2760e9 100644 --- a/src/script/lua_api/l_mainmenu.cpp +++ b/src/script/lua_api/l_mainmenu.cpp @@ -29,7 +29,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "porting.h" #include "filesys.h" #include "convert_json.h" -#include "content/packages.h" #include "content/content.h" #include "content/subgames.h" #include "serverlist.h" diff --git a/src/unittest/test_map_settings_manager.cpp b/src/unittest/test_map_settings_manager.cpp index 0a5c971b9..3d642a9b4 100644 --- a/src/unittest/test_map_settings_manager.cpp +++ b/src/unittest/test_map_settings_manager.cpp @@ -65,31 +65,6 @@ void check_noise_params(const NoiseParams *np1, const NoiseParams *np2) } -std::string read_file_to_string(const std::string &filepath) -{ - std::string buf; - FILE *f = fopen(filepath.c_str(), "rb"); - if (!f) - return ""; - - fseek(f, 0, SEEK_END); - - long filesize = ftell(f); - if (filesize == -1) { - fclose(f); - return ""; - } - rewind(f); - - buf.resize(filesize); - - UASSERTEQ(size_t, fread(&buf[0], 1, filesize, f), 1); - - fclose(f); - return buf; -} - - void TestMapSettingsManager::makeUserConfig(Settings *conf) { conf->set("mg_name", "v7"); @@ -199,7 +174,8 @@ void TestMapSettingsManager::testMapSettingsManager() }; SHA1 ctx; - std::string metafile_contents = read_file_to_string(test_mapmeta_path); + std::string metafile_contents; + UASSERT(fs::ReadFile(test_mapmeta_path, metafile_contents)); ctx.addBytes(&metafile_contents[0], metafile_contents.size()); unsigned char *sha1_result = ctx.getDigest(); int resultdiff = memcmp(sha1_result, expected_contents_hash, 20); From e80fc22dd996e5b0efd8c4f67700c0920e323e46 Mon Sep 17 00:00:00 2001 From: rubenwardy Date: Tue, 6 Oct 2020 12:10:37 +0100 Subject: [PATCH 096/266] Prevent games from setting secure settings (#10460) --- src/content/subgames.cpp | 8 ++++++++ src/content/subgames.h | 3 --- src/settings.cpp | 13 +++++++++++++ src/settings.h | 2 ++ 4 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/content/subgames.cpp b/src/content/subgames.cpp index 695ba431f..c6350f2dd 100644 --- a/src/content/subgames.cpp +++ b/src/content/subgames.cpp @@ -34,12 +34,17 @@ with this program; if not, write to the Free Software Foundation, Inc., // The maximum number of identical world names allowed #define MAX_WORLD_NAMES 100 +namespace +{ + bool getGameMinetestConfig(const std::string &game_path, Settings &conf) { std::string conf_path = game_path + DIR_DELIM + "minetest.conf"; return conf.readConfigFile(conf_path.c_str()); } +} + struct GameFindPath { std::string path; @@ -330,8 +335,11 @@ void loadGameConfAndInitWorld(const std::string &path, const std::string &name, // files that were loaded before. g_settings->clearDefaults(); set_default_settings(g_settings); + Settings game_defaults; getGameMinetestConfig(gamespec.path, game_defaults); + game_defaults.removeSecureSettings(); + g_settings->overrideDefaults(&game_defaults); infostream << "Initializing world at " << final_path << std::endl; diff --git a/src/content/subgames.h b/src/content/subgames.h index 35b619aaf..60392639b 100644 --- a/src/content/subgames.h +++ b/src/content/subgames.h @@ -53,9 +53,6 @@ struct SubgameSpec bool isValid() const { return (!id.empty() && !path.empty()); } }; -// minetest.conf -bool getGameMinetestConfig(const std::string &game_path, Settings &conf); - SubgameSpec findSubgame(const std::string &id); SubgameSpec findWorldSubgame(const std::string &world_path); diff --git a/src/settings.cpp b/src/settings.cpp index 56ab9e12b..f30ef34e9 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -1039,6 +1039,19 @@ void Settings::deregisterChangedCallback(const std::string &name, } } +void Settings::removeSecureSettings() +{ + for (const auto &name : getNames()) { + if (name.compare(0, 7, "secure.") != 0) + continue; + + errorstream << "Secure setting " << name + << " isn't allowed, so was ignored." + << std::endl; + remove(name); + } +} + void Settings::doCallbacks(const std::string &name) const { MutexAutoLock lock(m_callback_mutex); diff --git a/src/settings.h b/src/settings.h index 7db5539b2..6db2f9481 100644 --- a/src/settings.h +++ b/src/settings.h @@ -207,6 +207,8 @@ public: void deregisterChangedCallback(const std::string &name, SettingsChangedCallback cbf, void *userdata = NULL); + void removeSecureSettings(); + private: /*********************** * Reading and writing * From 2f4037752b023f87ca1f8859a8dce4f833353967 Mon Sep 17 00:00:00 2001 From: HybridDog <3192173+HybridDog@users.noreply.github.com> Date: Tue, 6 Oct 2020 20:49:46 +0200 Subject: [PATCH 097/266] Add minetest.get_artificial_light and minetest.get_natural_light (#5680) Add more detailed light detection functions, a function to get the artificial light (torches) and a function to get the sunlight as seen by the player (you can specify timeofday). Co-authored-by: rubenwardy --- builtin/game/misc.lua | 6 ++ doc/lua_api.txt | 16 ++++ games/devtest/mods/testtools/init.lua | 2 + games/devtest/mods/testtools/light.lua | 22 +++++ .../textures/testtools_lighttool.png | Bin 0 -> 1659 bytes src/script/lua_api/l_env.cpp | 41 +++++++++ src/script/lua_api/l_env.h | 5 ++ src/serverenvironment.cpp | 85 ++++++++++++++++++ src/serverenvironment.h | 3 + 9 files changed, 180 insertions(+) create mode 100644 games/devtest/mods/testtools/light.lua create mode 100644 games/devtest/mods/testtools/textures/testtools_lighttool.png diff --git a/builtin/game/misc.lua b/builtin/game/misc.lua index 341e613c2..96a0a2dda 100644 --- a/builtin/game/misc.lua +++ b/builtin/game/misc.lua @@ -151,6 +151,12 @@ function core.setting_get_pos(name) end +-- See l_env.cpp for the other functions +function core.get_artificial_light(param1) + return math.floor(param1 / 16) +end + + -- To be overriden by protection mods function core.is_protected(pos, name) diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 9e9af20da..c4eb56374 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -4824,6 +4824,22 @@ Environment access * `pos`: The position where to measure the light. * `timeofday`: `nil` for current time, `0` for night, `0.5` for day * Returns a number between `0` and `15` or `nil` + * `nil` is returned e.g. when the map isn't loaded at `pos` +* `minetest.get_natural_light(pos[, timeofday])` + * Figures out the sunlight (or moonlight) value at pos at the given time of + day. + * `pos`: The position of the node + * `timeofday`: `nil` for current time, `0` for night, `0.5` for day + * Returns a number between `0` and `15` or `nil` + * This function tests 203 nodes in the worst case, which happens very + unlikely +* `minetest.get_artificial_light(param1)` + * Calculates the artificial light (light from e.g. torches) value from the + `param1` value. + * `param1`: The param1 value of a `paramtype = "light"` node. + * Returns a number between `0` and `15` + * Currently it's the same as `math.floor(param1 / 16)`, except that it + ensures compatibility. * `minetest.place_node(pos, node)` * Place node with the same effects that a player would cause * `minetest.dig_node(pos)` diff --git a/games/devtest/mods/testtools/init.lua b/games/devtest/mods/testtools/init.lua index df5bc8e55..d578b264a 100644 --- a/games/devtest/mods/testtools/init.lua +++ b/games/devtest/mods/testtools/init.lua @@ -1,6 +1,8 @@ local S = minetest.get_translator("testtools") local F = minetest.formspec_escape +dofile(minetest.get_modpath("testtools") .. "/light.lua") + -- TODO: Add a Node Metadata tool minetest.register_tool("testtools:param2tool", { diff --git a/games/devtest/mods/testtools/light.lua b/games/devtest/mods/testtools/light.lua new file mode 100644 index 000000000..a9458ca6b --- /dev/null +++ b/games/devtest/mods/testtools/light.lua @@ -0,0 +1,22 @@ + +local S = minetest.get_translator("testtools") + +minetest.register_tool("testtools:lighttool", { + description = S("Light tool"), + inventory_image = "testtools_lighttool.png", + groups = { testtool = 1, disable_repair = 1 }, + on_use = function(itemstack, user, pointed_thing) + local pos = pointed_thing.above + if pointed_thing.type ~= "node" or not pos then + return + end + + local node = minetest.get_node(pos) + local time = minetest.get_timeofday() + local sunlight = minetest.get_natural_light(pos) + local artificial = minetest.get_artificial_light(node.param1) + local message = ("param1 0x%02x | time %.5f | sunlight %d | artificial %d") + :format(node.param1, time, sunlight, artificial) + minetest.chat_send_player(user:get_player_name(), message) + end +}) diff --git a/games/devtest/mods/testtools/textures/testtools_lighttool.png b/games/devtest/mods/testtools/textures/testtools_lighttool.png new file mode 100644 index 0000000000000000000000000000000000000000..6f744b7fa46d8a00e6a59c0929a591ea93add744 GIT binary patch literal 1659 zcmV->288*EP) zaB^>EX>4U6ba`-PAZ2)IW&i+q+SOKDvg0ZY{MRaG2_OLy%fURVW(RlqBQWu?bDY?j zGe0+!4HiObx@8=w|NU<8OMi$c;5ELw2`g8IJ#k<6`A;RMXSHs`7Gq2W)v{u*MB^Q*@Fe}Qy zgPp!c9T%mX#4Q6%8%LeZzKWWff?i5)g z8&`@8d*v#Ou3W#XmkMIr)XfiqA8pEScJ-sBstWo+v*-qsS=JDXw_mkILuXPyul@Q~ zSl}`PC`FpvD;P_vfC*atHdw=mY_0MrDo|47NDdcOzdT zZq~C&h42)JRhvjj#d6MT{jd|Qq2$Rs?|ty>1$^`=P;kMA5IFQhE-|rV&w-hRBPSto z;w1=+NR$*YlAeJ+hR9K3j42@`O_VfNQvalu1s7V};+L@SB1>FS*7C_N`y4W7$uXyb zg^C^%B@`}FVoB9P)sv#S>T9T6rN)|?)>@VCYb zrdD8ngs}ZM@m(`j44qZQxhtRrja@S%);a9TbP=kic&eG5CMhV<>z`6>xe)T1paNYY`;ORkx! z4gCq1<_$^4G56jLZu;%qp8Itfe7x>OTKJo7oTY*HdG2oye$4W8gW<<^wbx*ZdzsbL z=@oO9=UKqd_V#ib&i8)A*jVP1m-2!q=#Y83C6`}p;0cHsE@F0g;T+J#{!Z}1d%$Ou zpW}x!1#So3A%lBVUL%7&wJ#{W>Ia2z!QpM7aKYhi3gLpo+sNRix+fGKs|zlx@uZ56 zC&P*#KH9(=ydXb&{WrXD_XxZ?vf#~`)t3IjI*%*QCNTeoYlI`#&nj^Txq;mMa9w;* z;DXNg5OgfL$XkRdL|6u#EY9D`%^q*yfKYo2-0#3ysHUIzt zglR)VP)S2WAaHVTW@&6?004NLeUUv#!$2IxUsH=xst$G#aR^eKS`Za+)G8FALZ}s5 zbuhVpLX(Ch#l=x@EjakGSaoo5*44pP5ClI!oE)7LU8KbSC509-9vt`M-Mz=%J3weu zm}+*71FB{jsYG1NWLL%RR|FA2A4U+BnW@i7QWBoy>mEM7-o<#9_qjhuZ!T{#z$X&V zGTpF@)hJ)cxSZp>#aXS?SnHnrg`vE*vdndw!$@EeOOPN!K@DY8U?E1UMv93P?Z-X* zLylh}mqM;G7&#VDg$mj6ga5(rZq35vgqswK1A!OY{ul+icY$Wzw!e>UyLkfmpMfi_ z?XNa~nNQN|Z7p&H^lt+f*KJMN11@)f!6#idWJmJT6bc34{fxdT3k=)>!8NzH);>-j zfHZZLya5glfw3ZGuY0_^r?an0U60WO;~d-o7e1-RLBt&={?ZX#e6zRbe%kztQDBP)|5LF^%}=d-j{iRZi~l5np8R>8|NGV^2O|3kaNXYA6WtrQzNscirN002ovPDHLk FV1hb0459!4 literal 0 HcmV?d00001 diff --git a/src/script/lua_api/l_env.cpp b/src/script/lua_api/l_env.cpp index 138e88ae8..8d50d664d 100644 --- a/src/script/lua_api/l_env.cpp +++ b/src/script/lua_api/l_env.cpp @@ -407,6 +407,46 @@ int ModApiEnvMod::l_get_node_light(lua_State *L) return 1; } + +// get_natural_light(pos, timeofday) +// pos = {x=num, y=num, z=num} +// timeofday: nil = current time, 0 = night, 0.5 = day +int ModApiEnvMod::l_get_natural_light(lua_State *L) +{ + GET_ENV_PTR; + + v3s16 pos = read_v3s16(L, 1); + + bool is_position_ok; + MapNode n = env->getMap().getNode(pos, &is_position_ok); + if (!is_position_ok) + return 0; + + // If the daylight is 0, nothing needs to be calculated + u8 daylight = n.param1 & 0x0f; + if (daylight == 0) { + lua_pushinteger(L, 0); + return 1; + } + + u32 time_of_day; + if (lua_isnumber(L, 2)) { + time_of_day = 24000.0 * lua_tonumber(L, 2); + time_of_day %= 24000; + } else { + time_of_day = env->getTimeOfDay(); + } + u32 dnr = time_to_daynight_ratio(time_of_day, true); + + // If it's the same as the artificial light, the sunlight needs to be + // searched for because the value may not emanate from the sun + if (daylight == n.param1 >> 4) + daylight = env->findSunlight(pos); + + lua_pushinteger(L, dnr * daylight / 1000); + return 1; +} + // place_node(pos, node) // pos = {x=num, y=num, z=num} int ModApiEnvMod::l_place_node(lua_State *L) @@ -1358,6 +1398,7 @@ void ModApiEnvMod::Initialize(lua_State *L, int top) API_FCT(get_node); API_FCT(get_node_or_nil); API_FCT(get_node_light); + API_FCT(get_natural_light); API_FCT(place_node); API_FCT(dig_node); API_FCT(punch_node); diff --git a/src/script/lua_api/l_env.h b/src/script/lua_api/l_env.h index 07d4d2438..7f212b5fc 100644 --- a/src/script/lua_api/l_env.h +++ b/src/script/lua_api/l_env.h @@ -56,6 +56,11 @@ private: // timeofday: nil = current time, 0 = night, 0.5 = day static int l_get_node_light(lua_State *L); + // get_natural_light(pos, timeofday) + // pos = {x=num, y=num, z=num} + // timeofday: nil = current time, 0 = night, 0.5 = day + static int l_get_natural_light(lua_State *L); + // place_node(pos, node) // pos = {x=num, y=num, z=num} static int l_place_node(lua_State *L); diff --git a/src/serverenvironment.cpp b/src/serverenvironment.cpp index 320042e19..6ef56efc8 100644 --- a/src/serverenvironment.cpp +++ b/src/serverenvironment.cpp @@ -1066,6 +1066,91 @@ bool ServerEnvironment::swapNode(v3s16 p, const MapNode &n) return true; } +u8 ServerEnvironment::findSunlight(v3s16 pos) const +{ + // Directions for neighbouring nodes with specified order + static const v3s16 dirs[] = { + v3s16(-1, 0, 0), v3s16(1, 0, 0), v3s16(0, 0, -1), v3s16(0, 0, 1), + v3s16(0, -1, 0), v3s16(0, 1, 0) + }; + + const NodeDefManager *ndef = m_server->ndef(); + + // found_light remembers the highest known sunlight value at pos + u8 found_light = 0; + + struct stack_entry { + v3s16 pos; + s16 dist; + }; + std::stack stack; + stack.push({pos, 0}); + + std::unordered_map dists; + dists[MapDatabase::getBlockAsInteger(pos)] = 0; + + while (!stack.empty()) { + struct stack_entry e = stack.top(); + stack.pop(); + + v3s16 currentPos = e.pos; + s8 dist = e.dist + 1; + + for (const v3s16& off : dirs) { + v3s16 neighborPos = currentPos + off; + s64 neighborHash = MapDatabase::getBlockAsInteger(neighborPos); + + // Do not walk neighborPos multiple times unless the distance to the start + // position is shorter + auto it = dists.find(neighborHash); + if (it != dists.end() && dist >= it->second) + continue; + + // Position to walk + bool is_position_ok; + MapNode node = m_map->getNode(neighborPos, &is_position_ok); + if (!is_position_ok) { + // This happens very rarely because the map at currentPos is loaded + m_map->emergeBlock(neighborPos, false); + node = m_map->getNode(neighborPos, &is_position_ok); + if (!is_position_ok) + continue; // not generated + } + + const ContentFeatures &def = ndef->get(node); + if (!def.sunlight_propagates) { + // Do not test propagation here again + dists[neighborHash] = -1; + continue; + } + + // Sunlight could have come from here + dists[neighborHash] = dist; + u8 daylight = node.param1 & 0x0f; + + // In the special case where sunlight shines from above and thus + // does not decrease with upwards distance, daylight is always + // bigger than nightlight, which never reaches 15 + int possible_finlight = daylight - dist; + if (possible_finlight <= found_light) { + // Light from here cannot make a brighter light at currentPos than + // found_light + continue; + } + + u8 nightlight = node.param1 >> 4; + if (daylight > nightlight) { + // Found a valid daylight + found_light = possible_finlight; + } else { + // Sunlight may be darker, so walk the neighbours + stack.push({neighborPos, dist}); + } + } + } + return found_light; +} + void ServerEnvironment::clearObjects(ClearObjectsMode mode) { infostream << "ServerEnvironment::clearObjects(): " diff --git a/src/serverenvironment.h b/src/serverenvironment.h index af742e290..cfd5b8f3e 100644 --- a/src/serverenvironment.h +++ b/src/serverenvironment.h @@ -322,6 +322,9 @@ public: bool removeNode(v3s16 p); bool swapNode(v3s16 p, const MapNode &n); + // Find the daylight value at pos with a Depth First Search + u8 findSunlight(v3s16 pos) const; + // Find all active objects inside a radius around a point void getObjectsInsideRadius(std::vector &objects, const v3f &pos, float radius, std::function include_obj_cb) From aae7d4ff8e4e2433b13225269de28e7dc10efe2c Mon Sep 17 00:00:00 2001 From: Hugo Locurcio Date: Tue, 6 Oct 2020 20:50:20 +0200 Subject: [PATCH 098/266] Improve the `fsaa` setting description (#10279) See https://github.com/minetest/minetest/issues/8459. --- builtin/settingtypes.txt | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 7f2d12be5..1c28470d5 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -513,8 +513,13 @@ texture_clean_transparent (Clean transparent textures) bool false # texture autoscaling. texture_min_size (Minimum texture size) int 64 -# Experimental option, might cause visible spaces between blocks -# when set to higher number than 0. +# Use multi-sample antialiasing (MSAA) to smooth out block edges. +# This algorithm smooths out the 3D viewport while keeping the image sharp, +# but it doesn't affect the insides of textures +# (which is especially noticeable with transparent textures). +# This option is experimental and might cause visible spaces between blocks +# when set above 0. +# A restart is required after changing this option. fsaa (FSAA) enum 0 0,1,2,4,8,16 # Undersampling is similar to using a lower screen resolution, but it applies From a37e96eefc646f01f34d0c6d8948d4a9c4062cae Mon Sep 17 00:00:00 2001 From: Zughy <63455151+Zughy@users.noreply.github.com> Date: Tue, 6 Oct 2020 20:51:27 +0200 Subject: [PATCH 099/266] -Wmem-access only called when GCC > 7 (#10453) --- src/client/mapblock_mesh.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/client/mapblock_mesh.cpp b/src/client/mapblock_mesh.cpp index f9332e399..c7790f1e4 100644 --- a/src/client/mapblock_mesh.cpp +++ b/src/client/mapblock_mesh.cpp @@ -394,7 +394,9 @@ static void getNodeVertexDirs(const v3s16 &dir, v3s16 *vertex_dirs) #if defined(__GNUC__) && !defined(__clang__) #pragma GCC diagnostic push +#if __GNUC__ > 7 #pragma GCC diagnostic ignored "-Wclass-memaccess" +#endif #endif memcpy(vertex_dirs, &vertex_dirs_table[idx], 4 * sizeof(v3s16)); #if defined(__GNUC__) && !defined(__clang__) From f0b6f7909a6a760d946209891c3667799874b80a Mon Sep 17 00:00:00 2001 From: HybridDog <3192173+HybridDog@users.noreply.github.com> Date: Thu, 8 Oct 2020 19:44:27 +0200 Subject: [PATCH 100/266] Mention texture modifier colorspace (#10112) --- doc/lua_api.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/lua_api.txt b/doc/lua_api.txt index c4eb56374..60fea3817 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -434,7 +434,9 @@ Texture modifiers ----------------- There are various texture modifiers that can be used -to generate textures on-the-fly. +to let the client generate textures on-the-fly. +The modifiers are applied directly in sRGB colorspace, +i.e. without gamma-correction. ### Texture overlaying From b2f3f663858e6d2a2174066e425bb6f2edea910b Mon Sep 17 00:00:00 2001 From: DS Date: Thu, 8 Oct 2020 19:44:47 +0200 Subject: [PATCH 101/266] Fix box[]es not being clipped (#10473) Fixes a regression of #8676. --- src/gui/guiBox.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/gui/guiBox.cpp b/src/gui/guiBox.cpp index 99a115daf..443f1064f 100644 --- a/src/gui/guiBox.cpp +++ b/src/gui/guiBox.cpp @@ -107,10 +107,11 @@ void GUIBox::draw() video::IVideoDriver *driver = Environment->getVideoDriver(); driver->draw2DRectangle(main_rect, m_colors[0], m_colors[1], m_colors[3], - m_colors[2], nullptr); + m_colors[2], &AbsoluteClippingRect); for (size_t i = 0; i <= 3; i++) - driver->draw2DRectangle(m_bordercolors[i], border_rects[i], nullptr); + driver->draw2DRectangle(m_bordercolors[i], border_rects[i], + &AbsoluteClippingRect); IGUIElement::draw(); } From f3ae45b2b245dd0aeb4a7d9b76afaf078871104c Mon Sep 17 00:00:00 2001 From: DS Date: Fri, 9 Oct 2020 20:11:21 +0200 Subject: [PATCH 102/266] Add a short_description to be used by mods (#8980) --- builtin/game/register.lua | 6 +++ doc/lua_api.txt | 10 +++++ games/devtest/mods/unittests/init.lua | 2 + .../mods/unittests/itemdescription.lua | 44 +++++++++++++++++++ src/inventory.cpp | 14 ++++++ src/inventory.h | 1 + src/itemdef.cpp | 9 +++- src/itemdef.h | 1 + src/script/common/c_content.cpp | 3 ++ src/script/lua_api/l_item.cpp | 11 +++++ src/script/lua_api/l_item.h | 3 ++ 11 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 games/devtest/mods/unittests/itemdescription.lua diff --git a/builtin/game/register.lua b/builtin/game/register.lua index 3de67c04b..f00b76494 100644 --- a/builtin/game/register.lua +++ b/builtin/game/register.lua @@ -118,6 +118,12 @@ function core.register_item(name, itemdef) end itemdef.name = name + -- default description to item name + itemdef.description = itemdef.description or name + -- default short_description to first line of description + itemdef.short_description = itemdef.short_description or + itemdef.description:gsub("\n.*","") + -- Apply defaults and add to registered_* table if itemdef.type == "node" then -- Use the nodebox as selection box if it's not set manually diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 60fea3817..c8b294149 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -2054,6 +2054,8 @@ Some of the values in the key-value store are handled specially: * `description`: Set the item stack's description. Defaults to `idef.description`. +* `short_description`: Set the item stack's short description. Defaults + to `idef.short_description`. * `color`: A `ColorString`, which sets the stack's color. * `palette_index`: If the item has a palette, this is used to get the current color from the palette. @@ -5994,6 +5996,8 @@ an itemstring, a table or `nil`. stack). * `set_metadata(metadata)`: (DEPRECATED) Returns true. * `get_description()`: returns the description shown in inventory list tooltips. +* `get_short_description()`: returns the short description. + * Unlike the description, this does not include new lines. * `clear()`: removes all items from the stack, making it empty. * `replace(item)`: replace the contents of this stack. * `item` can also be an itemstring or table. @@ -7096,6 +7100,12 @@ Used by `minetest.register_node`, `minetest.register_craftitem`, and { description = "Steel Axe", + -- Can contain new lines. "\n" has to be used as new line character. + -- Defaults to the item's name. + + short_description = "Steel Axe", + -- Must not contain new lines. + -- Defaults to the first line of description. groups = {}, -- key = name, value = rating; rating = 1..3. diff --git a/games/devtest/mods/unittests/init.lua b/games/devtest/mods/unittests/init.lua index 6c1728420..12c67f78b 100644 --- a/games/devtest/mods/unittests/init.lua +++ b/games/devtest/mods/unittests/init.lua @@ -5,10 +5,12 @@ dofile(modpath .. "/random.lua") dofile(modpath .. "/player.lua") dofile(modpath .. "/crafting_prepare.lua") dofile(modpath .. "/crafting.lua") +dofile(modpath .. "/itemdescription.lua") if minetest.settings:get_bool("devtest_unittests_autostart", false) then unittests.test_random() unittests.test_crafting() + unittests.test_short_desc() minetest.register_on_joinplayer(function(player) unittests.test_player(player) end) diff --git a/games/devtest/mods/unittests/itemdescription.lua b/games/devtest/mods/unittests/itemdescription.lua new file mode 100644 index 000000000..1d0826545 --- /dev/null +++ b/games/devtest/mods/unittests/itemdescription.lua @@ -0,0 +1,44 @@ +local full_description = "Colorful Pickaxe\nThe best pick." +minetest.register_tool("unittests:colorful_pick", { + description = full_description, + inventory_image = "basetools_mesepick.png", + tool_capabilities = { + full_punch_interval = 1.0, + max_drop_level=3, + groupcaps={ + cracky={times={[1]=2.0, [2]=1.0, [3]=0.5}, uses=20, maxlevel=3}, + crumbly={times={[1]=2.0, [2]=1.0, [3]=0.5}, uses=20, maxlevel=3}, + snappy={times={[1]=2.0, [2]=1.0, [3]=0.5}, uses=20, maxlevel=3} + }, + damage_groups = {fleshy=4}, + }, +}) + +minetest.register_chatcommand("item_description", { + param = "", + description = "Show the short and full description of the wielded item.", + func = function(name) + local player = minetest.get_player_by_name(name) + local item = player:get_wielded_item() + return true, string.format("short_description: %s\ndescription: %s", + item:get_short_description(), item:get_description()) + end +}) + +function unittests.test_short_desc() + local stack = ItemStack("unittests:colorful_pick") + assert(stack:get_short_description() == "Colorful Pickaxe") + assert(stack:get_short_description() == minetest.registered_items["unittests:colorful_pick"].short_description) + assert(stack:get_description() == full_description) + assert(stack:get_description() == minetest.registered_items["unittests:colorful_pick"].description) + + stack:get_meta():set_string("description", "Hello World") + assert(stack:get_short_description() == "Colorful Pickaxe") + assert(stack:get_description() == "Hello World") + + stack:get_meta():set_string("short_description", "Foo Bar") + assert(stack:get_short_description() == "Foo Bar") + assert(stack:get_description() == "Hello World") + + return true +end diff --git a/src/inventory.cpp b/src/inventory.cpp index cf72cb005..1ef9b13cd 100644 --- a/src/inventory.cpp +++ b/src/inventory.cpp @@ -258,6 +258,20 @@ std::string ItemStack::getDescription(IItemDefManager *itemdef) const return desc.empty() ? name : desc; } +std::string ItemStack::getShortDescription(IItemDefManager *itemdef) const +{ + std::string desc = metadata.getString("short_description"); + if (desc.empty()) + desc = getDefinition(itemdef).short_description; + if (!desc.empty()) + return desc; + // no short_description because of old server version or modified builtin + // return first line of description + std::stringstream sstr(getDescription(itemdef)); + std::getline(sstr, desc, '\n'); + return desc; +} + ItemStack ItemStack::addItem(ItemStack newitem, IItemDefManager *itemdef) { diff --git a/src/inventory.h b/src/inventory.h index 67a7859ed..f36bc57cf 100644 --- a/src/inventory.h +++ b/src/inventory.h @@ -49,6 +49,7 @@ struct ItemStack std::string getItemString(bool include_meta = true) const; // Returns the tooltip std::string getDescription(IItemDefManager *itemdef) const; + std::string getShortDescription(IItemDefManager *itemdef) const; /* Quantity methods diff --git a/src/itemdef.cpp b/src/itemdef.cpp index df20bdf15..5fb1e4c47 100644 --- a/src/itemdef.cpp +++ b/src/itemdef.cpp @@ -62,6 +62,7 @@ ItemDefinition& ItemDefinition::operator=(const ItemDefinition &def) type = def.type; name = def.name; description = def.description; + short_description = def.short_description; inventory_image = def.inventory_image; inventory_overlay = def.inventory_overlay; wield_image = def.wield_image; @@ -102,6 +103,7 @@ void ItemDefinition::reset() type = ITEM_NONE; name = ""; description = ""; + short_description = ""; inventory_image = ""; inventory_overlay = ""; wield_image = ""; @@ -162,6 +164,8 @@ void ItemDefinition::serialize(std::ostream &os, u16 protocol_version) const writeARGB8(os, color); os << serializeString16(inventory_overlay); os << serializeString16(wield_overlay); + + os << serializeString16(short_description); } void ItemDefinition::deSerialize(std::istream &is) @@ -213,8 +217,9 @@ void ItemDefinition::deSerialize(std::istream &is) // If you add anything here, insert it primarily inside the try-catch // block to not need to increase the version. - //try { - //} catch(SerializationError &e) {}; + try { + short_description = deSerializeString16(is); + } catch(SerializationError &e) {}; } diff --git a/src/itemdef.h b/src/itemdef.h index f47e6cbe7..ebf0d3527 100644 --- a/src/itemdef.h +++ b/src/itemdef.h @@ -56,6 +56,7 @@ struct ItemDefinition ItemType type; std::string name; // "" = hand std::string description; // Shown in tooltip. + std::string short_description; /* Visual properties diff --git a/src/script/common/c_content.cpp b/src/script/common/c_content.cpp index 774b6a326..147f6e3ed 100644 --- a/src/script/common/c_content.cpp +++ b/src/script/common/c_content.cpp @@ -56,6 +56,7 @@ void read_item_definition(lua_State* L, int index, es_ItemType, ITEM_NONE); getstringfield(L, index, "name", def.name); getstringfield(L, index, "description", def.description); + getstringfield(L, index, "short_description", def.short_description); getstringfield(L, index, "inventory_image", def.inventory_image); getstringfield(L, index, "inventory_overlay", def.inventory_overlay); getstringfield(L, index, "wield_image", def.wield_image); @@ -142,6 +143,8 @@ void push_item_definition_full(lua_State *L, const ItemDefinition &i) lua_setfield(L, -2, "name"); lua_pushstring(L, i.description.c_str()); lua_setfield(L, -2, "description"); + lua_pushstring(L, i.short_description.c_str()); + lua_setfield(L, -2, "short_description"); lua_pushstring(L, type.c_str()); lua_setfield(L, -2, "type"); lua_pushstring(L, i.inventory_image.c_str()); diff --git a/src/script/lua_api/l_item.cpp b/src/script/lua_api/l_item.cpp index d67cab76f..2d1124a4d 100644 --- a/src/script/lua_api/l_item.cpp +++ b/src/script/lua_api/l_item.cpp @@ -193,6 +193,16 @@ int LuaItemStack::l_get_description(lua_State *L) return 1; } +// get_short_description(self) +int LuaItemStack::l_get_short_description(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + LuaItemStack *o = checkobject(L, 1); + std::string desc = o->m_stack.getShortDescription(getGameDef(L)->idef()); + lua_pushstring(L, desc.c_str()); + return 1; +} + // clear(self) -> true int LuaItemStack::l_clear(lua_State *L) { @@ -493,6 +503,7 @@ const luaL_Reg LuaItemStack::methods[] = { luamethod(LuaItemStack, get_metadata), luamethod(LuaItemStack, set_metadata), luamethod(LuaItemStack, get_description), + luamethod(LuaItemStack, get_short_description), luamethod(LuaItemStack, clear), luamethod(LuaItemStack, replace), luamethod(LuaItemStack, to_string), diff --git a/src/script/lua_api/l_item.h b/src/script/lua_api/l_item.h index 98744c071..16878c101 100644 --- a/src/script/lua_api/l_item.h +++ b/src/script/lua_api/l_item.h @@ -72,6 +72,9 @@ private: // get_description(self) static int l_get_description(lua_State *L); + // get_short_description(self) + static int l_get_short_description(lua_State *L); + // clear(self) -> true static int l_clear(lua_State *L); From c61c175e9c648b6e40b85c12940e9b91a52757d7 Mon Sep 17 00:00:00 2001 From: HybridDog <3192173+HybridDog@users.noreply.github.com> Date: Fri, 9 Oct 2020 20:13:42 +0200 Subject: [PATCH 103/266] Add bumpmapping and parallax occlusion testing nodes to devtest (#9242) --- games/devtest/mods/testnodes/mod.conf | 1 + games/devtest/mods/testnodes/textures.lua | 49 ++++++++++++++++++ .../textures/testnodes_height_pyramid.png | Bin 0 -> 90 bytes .../testnodes_height_pyramid_normal.png | Bin 0 -> 239 bytes .../textures/testnodes_parallax_extruded.png | Bin 0 -> 591 bytes .../testnodes_parallax_extruded_normal.png | Bin 0 -> 143 bytes 6 files changed, 50 insertions(+) create mode 100644 games/devtest/mods/testnodes/textures/testnodes_height_pyramid.png create mode 100644 games/devtest/mods/testnodes/textures/testnodes_height_pyramid_normal.png create mode 100644 games/devtest/mods/testnodes/textures/testnodes_parallax_extruded.png create mode 100644 games/devtest/mods/testnodes/textures/testnodes_parallax_extruded_normal.png diff --git a/games/devtest/mods/testnodes/mod.conf b/games/devtest/mods/testnodes/mod.conf index 4824c6ed0..d894c3452 100644 --- a/games/devtest/mods/testnodes/mod.conf +++ b/games/devtest/mods/testnodes/mod.conf @@ -1,2 +1,3 @@ name = testnodes description = Contains a bunch of basic example nodes for demonstrative purposes, development and testing +depends = stairs diff --git a/games/devtest/mods/testnodes/textures.lua b/games/devtest/mods/testnodes/textures.lua index 6ffef8fe9..e0724c229 100644 --- a/games/devtest/mods/testnodes/textures.lua +++ b/games/devtest/mods/testnodes/textures.lua @@ -71,3 +71,52 @@ for a=1,#alphas do groups = { dig_immediate = 3 }, }) end + + +-- Bumpmapping and Parallax Occlusion + +-- This node has a normal map which corresponds to a pyramid with sides tilted +-- by an angle of 45°, i.e. the normal map contains four vectors which point +-- diagonally away from the surface (e.g. (0.7, 0.7, 0)), +-- and the heights in the height map linearly increase towards the centre, +-- so that the surface corresponds to a simple pyramid. +-- The node can help to determine if e.g. tangent space transformations work +-- correctly. +-- If, for example, the light comes from above, then the (tilted) pyramids +-- should look like they're lit from this light direction on all node faces. +-- The white albedo texture has small black indicators which can be used to see +-- how it is transformed ingame (and thus see if there's rotation around the +-- normal vector). +minetest.register_node("testnodes:height_pyramid", { + description = "Bumpmapping and Parallax Occlusion Tester (height pyramid)", + tiles = {"testnodes_height_pyramid.png"}, + groups = {dig_immediate = 3}, +}) + +-- The stairs nodes should help to validate if shading works correctly for +-- rotated nodes (which have rotated textures). +stairs.register_stair_and_slab("height_pyramid", "experimantal:height_pyramid", + {dig_immediate = 3}, + {"testnodes_height_pyramid.png"}, + "Bumpmapping and Parallax Occlusion Tester Stair (height pyramid)", + "Bumpmapping and Parallax Occlusion Tester Slab (height pyramid)") + +-- This node has a simple heightmap for parallax occlusion testing and flat +-- normalmap. +-- When parallax occlusion is enabled, the yellow scrawl should stick out of +-- the texture when viewed at an angle. +minetest.register_node("testnodes:parallax_extruded", { + description = "Parallax Occlusion Tester", + tiles = {"testnodes_parallax_extruded.png"}, + groups = {dig_immediate = 3}, +}) + +-- Analogously to the height pyramid stairs nodes, +-- these nodes should help to validate if parallax occlusion works correctly for +-- rotated nodes (which have rotated textures). +stairs.register_stair_and_slab("parallax_extruded", + "experimantal:parallax_extruded", + {dig_immediate = 3}, + {"testnodes_parallax_extruded.png"}, + "Parallax Occlusion Tester Stair", + "Parallax Occlusion Tester Slab") diff --git a/games/devtest/mods/testnodes/textures/testnodes_height_pyramid.png b/games/devtest/mods/testnodes/textures/testnodes_height_pyramid.png new file mode 100644 index 0000000000000000000000000000000000000000..8c787b7401e1dd936e6e89bf132b9f187cf1d1b7 GIT binary patch literal 90 zcmeAS@N?(olHy`uVBq!ia0vp^0wBx*Bp9q_EZ7UA6g^!WLnJOI|2h9-KBJ_8k%7S< l_h&{)D;%BznT9;_j11Q|ut~+NeZ2vs$~~oxG9vh=Ksi-Ja-z zF6Uhfc!H`Hh$XG#`4Y}!e@IZs$-&^{X6x;I|F{;~J-lZ8BChUq+zxrhT}cw zf(=X36e=`Ma6IrxXz>ro?Z~NPnl5~lZ`vBp9Wt6ci6>(Yr1}?dc^_lavu5(%utdDz m=BEv;-gb_^tyj-;{LVim+=bh?|GX^F2MnIBelF{r5}E+pj#X#? literal 0 HcmV?d00001 diff --git a/games/devtest/mods/testnodes/textures/testnodes_parallax_extruded.png b/games/devtest/mods/testnodes/textures/testnodes_parallax_extruded.png new file mode 100644 index 0000000000000000000000000000000000000000..7e1c323987597e8555e4a74bb637d4705dd2d88c GIT binary patch literal 591 zcmV-V0}3b3I7v6KL@mH@Gv0J4(+ zvY!gFpboR10JM`7w50>JnEaTizT*oMt54M&9ww4jLmKe5|6SkNDwwN2XnFF?&54M>YwwW5XngF(% z0k)bBwwn*OoB+0+0JfbNww?gCpB}cM1mNJ{;^N}t=;-R|>g((4 z?Ck9A?d|XH@AC5U^Yiod_4W4l_V@Sq`uh6&`}_X>{{R2~TWLV^0002qNkl^sfqHC|*%qp+DVE=OwVRQ){8H6qCzBsS8yv zPRFw}Lh#+euxuqHJ@M{BSj%s0U5{ImH6K%b5WcLPhH}i6bL6b*zS!G)=k53BV?k;A zhA%NScwb}y(Q7v+LYiT3v*4umb5tc)Ny6*)Do9HP*a)$tQh_ZQFmV{{z(k#HXqvCy dU*zKm`454QOBEGnk`Vv^002ovPDHLkV1i>NAjALw literal 0 HcmV?d00001 diff --git a/games/devtest/mods/testnodes/textures/testnodes_parallax_extruded_normal.png b/games/devtest/mods/testnodes/textures/testnodes_parallax_extruded_normal.png new file mode 100644 index 0000000000000000000000000000000000000000..b134699d0c82621f7ba10fcbf177f93a74ce7456 GIT binary patch literal 143 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!D3?x-;bCrM;TYyi9YeU0-AX089Hv)< Date: Sun, 11 Oct 2020 13:36:13 +0200 Subject: [PATCH 104/266] Add missing type check to InvRef:set_lists() (#10476) --- src/script/lua_api/l_inventory.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/script/lua_api/l_inventory.cpp b/src/script/lua_api/l_inventory.cpp index e41b5cb41..434d0a76c 100644 --- a/src/script/lua_api/l_inventory.cpp +++ b/src/script/lua_api/l_inventory.cpp @@ -280,6 +280,7 @@ int InvRef::l_set_lists(lua_State *L) Server *server = getServer(L); lua_pushnil(L); + luaL_checktype(L, 2, LUA_TTABLE); while (lua_next(L, 2)) { const char *listname = lua_tostring(L, -2); read_inventory_list(L, -1, tempInv, listname, server); From 80f416d51449ffc6907f7e2b6d1ef935abee5611 Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein Date: Mon, 12 Oct 2020 18:12:04 +0200 Subject: [PATCH 105/266] Added AttachmentFloat --- builtin/client/cheats.lua | 1 + src/client/camera.cpp | 2 +- src/defaultsettings.cpp | 1 + textures/base/pack/menu_bg.png | Bin 266 -> 124 bytes 4 files changed, 3 insertions(+), 1 deletion(-) diff --git a/builtin/client/cheats.lua b/builtin/client/cheats.lua index 06b4cb745..800784597 100644 --- a/builtin/client/cheats.lua +++ b/builtin/client/cheats.lua @@ -23,6 +23,7 @@ core.cheats = { ["Coords"] = "coords", ["Tracers"] = "enable_tracers", ["ESP"] = "enable_esp", + ["AttachmentFloat"] = "float_above_parent", }, ["World"] = { ["FastDig"] = "fastdig", diff --git a/src/client/camera.cpp b/src/client/camera.cpp index abc55e4b7..c9e8fab6a 100644 --- a/src/client/camera.cpp +++ b/src/client/camera.cpp @@ -340,7 +340,7 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 busytime, f32 tool_r // mods expect the player head to be at the parent's position // plus eye height. if (player->getParent()) - player_position = player->getParent()->getPosition(); + player_position = player->getParent()->getPosition() + v3f(0, g_settings->getBool("float_above_parent") ? BS : 0, 0); // 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 diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 91d9d6be6..5e23ffc39 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -94,6 +94,7 @@ void set_default_settings(Settings *settings) settings->setDefault("enable_tracers", "false"); settings->setDefault("enable_esp", "false"); settings->setDefault("no_slow", "false"); + settings->setDefault("float_above_parent", "false"); // Keymap settings->setDefault("remote_port", "30000"); diff --git a/textures/base/pack/menu_bg.png b/textures/base/pack/menu_bg.png index 617d353e0d67a14a2952b2a98375eb8a50b97d79..6f6836e2c60ee94340b2fad6df6c1cd780953507 100644 GIT binary patch delta 106 zcmeBTs+k~>!pO|Pz@TcIs|2K&1AIbUdv^We5pUuHaybh;B8wRqxP?KOkzv*x37{Z* ziKnkC`xACfQC^`-m-!5ULOh->jv*Y^lM@tpfjk2S#^bA6`+zJ4Pgg&ebxsLQ0GW{& ASpWb4 delta 249 zcmV9aC9$LDTl{tl2%o^ClmGdA6W Date: Tue, 13 Oct 2020 00:52:53 +0100 Subject: [PATCH 106/266] Give unnamed world names incrementing numbers. Format 'world' (#10247) Code created with help from GitHub users sirrobzeroone and pauloue, thank you. --- builtin/mainmenu/dlg_create_world.lua | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/builtin/mainmenu/dlg_create_world.lua b/builtin/mainmenu/dlg_create_world.lua index b2e706b6b..7566d2409 100644 --- a/builtin/mainmenu/dlg_create_world.lua +++ b/builtin/mainmenu/dlg_create_world.lua @@ -363,10 +363,18 @@ local function create_world_buttonhandler(this, fields) local gameindex = core.get_textlist_index("games") if gameindex ~= nil then + -- For unnamed worlds use the generated name 'world', + -- where the number increments: it is set to 1 larger than the largest + -- generated name number found. if worldname == "" then - local random_number = math.random(10000, 99999) - local random_world_name = "Unnamed" .. random_number - worldname = random_world_name + local worldnum_max = 0 + for _, world in ipairs(menudata.worldlist:get_list()) do + if world.name:match("^world%d+$") then + local worldnum = tonumber(world.name:sub(6)) + worldnum_max = math.max(worldnum_max, worldnum) + end + end + worldname = "world" .. worldnum_max + 1 end core.settings:set("fixed_map_seed", fields["te_seed"]) From 11f3deb9c4512b918de8f5e778bc8b9535bd62c7 Mon Sep 17 00:00:00 2001 From: JosiahWI <41302989+JosiahWI@users.noreply.github.com> Date: Tue, 13 Oct 2020 10:36:01 -0500 Subject: [PATCH 107/266] Pass ContentFeatures as reference to read_content_features (#10464) --- src/script/common/c_content.cpp | 5 +---- src/script/common/c_content.h | 3 ++- src/script/lua_api/l_item.cpp | 3 ++- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/script/common/c_content.cpp b/src/script/common/c_content.cpp index 147f6e3ed..a95cf94a1 100644 --- a/src/script/common/c_content.cpp +++ b/src/script/common/c_content.cpp @@ -491,13 +491,11 @@ TileDef read_tiledef(lua_State *L, int index, u8 drawtype) } /******************************************************************************/ -ContentFeatures read_content_features(lua_State *L, int index) +void read_content_features(lua_State *L, ContentFeatures &f, int index) { if(index < 0) index = lua_gettop(L) + 1 + index; - ContentFeatures f; - /* Cache existence of some callbacks */ lua_getfield(L, index, "on_construct"); if(!lua_isnil(L, -1)) f.has_on_construct = true; @@ -800,7 +798,6 @@ ContentFeatures read_content_features(lua_State *L, int index) getstringfield(L, index, "node_dig_prediction", f.node_dig_prediction); - return f; } void push_content_features(lua_State *L, const ContentFeatures &c) diff --git a/src/script/common/c_content.h b/src/script/common/c_content.h index 8f32e58eb..29d576355 100644 --- a/src/script/common/c_content.h +++ b/src/script/common/c_content.h @@ -67,7 +67,8 @@ struct collisionMoveResult; extern struct EnumString es_TileAnimationType[]; -ContentFeatures read_content_features (lua_State *L, int index); +void read_content_features (lua_State *L, ContentFeatures &f, + int index); void push_content_features (lua_State *L, const ContentFeatures &c); diff --git a/src/script/lua_api/l_item.cpp b/src/script/lua_api/l_item.cpp index 2d1124a4d..9e0da4034 100644 --- a/src/script/lua_api/l_item.cpp +++ b/src/script/lua_api/l_item.cpp @@ -570,7 +570,8 @@ int ModApiItemMod::l_register_item_raw(lua_State *L) // Read the node definition (content features) and register it if (def.type == ITEM_NODE) { - ContentFeatures f = read_content_features(L, table); + ContentFeatures f; + read_content_features(L, f, table); // when a mod reregisters ignore, only texture changes and such should // be done if (f.name == "ignore") From 521a04222a71325ef217d1214febf5591919e169 Mon Sep 17 00:00:00 2001 From: Lars Date: Mon, 12 Oct 2020 19:47:04 -0700 Subject: [PATCH 108/266] Avoid drawing invisible blocks on the client. --- src/client/clientmap.cpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/client/clientmap.cpp b/src/client/clientmap.cpp index 3124313e7..d372a8e46 100644 --- a/src/client/clientmap.cpp +++ b/src/client/clientmap.cpp @@ -124,12 +124,6 @@ void ClientMap::updateDrawList() v3f camera_position = m_camera_position; v3f camera_direction = m_camera_direction; - f32 camera_fov = m_camera_fov; - - // Use a higher fov to accomodate faster camera movements. - // Blocks are cropped better when they are drawn. - // Or maybe they aren't? Well whatever. - camera_fov *= 1.2; v3s16 cam_pos_nodes = floatToInt(camera_position, BS); v3s16 p_blocks_min; @@ -190,7 +184,7 @@ void ClientMap::updateDrawList() float d = 0.0; if (!isBlockInSight(block->getPos(), camera_position, - camera_direction, camera_fov, range, &d)) + camera_direction, m_camera_fov, range, &d)) continue; From 2341a4aff1242e978d6fad3772d4d4fb015c040d Mon Sep 17 00:00:00 2001 From: Zughy <63455151+Zughy@users.noreply.github.com> Date: Tue, 13 Oct 2020 21:27:52 +0200 Subject: [PATCH 109/266] Add ObjectRef:get_children() (#10480) Co-authored-by: Zughy <4279489-marco_a@users.noreply.gitlab.com> --- doc/lua_api.txt | 2 ++ src/script/lua_api/l_object.cpp | 22 ++++++++++++++++++++++ src/script/lua_api/l_object.h | 3 +++ 3 files changed, 27 insertions(+) diff --git a/doc/lua_api.txt b/doc/lua_api.txt index c8b294149..1631d564c 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -6206,6 +6206,8 @@ object you are working with still exists. should appear in first person. * `get_attach()`: returns parent, bone, position, rotation, forced_visible, or nil if it isn't attached. +* `get_children()`: returns a list of ObjectRefs that are attached to the + object. * `set_detach()` * `set_bone_position(bone, position, rotation)` * `bone`: string diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index 57ed7e2cd..96c8c687c 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -738,6 +738,27 @@ int ObjectRef::l_get_attach(lua_State *L) return 5; } +// get_children(self) +int ObjectRef::l_get_children(lua_State *L) +{ + GET_ENV_PTR; + + ObjectRef *ref = checkobject(L, 1); + ServerActiveObject *sao = getobject(ref); + if (sao == nullptr) + return 0; + + const std::unordered_set child_ids = sao->getAttachmentChildIds(); + int i = 0; + lua_createtable(L, child_ids.size(), 0); + for (const int id : child_ids) { + ServerActiveObject *child = env->getActiveObject(id); + getScriptApiBase(L)->objectrefGetOrCreate(L, child); + lua_rawseti(L, -2, ++i); + } + return 1; +} + // set_detach(self) int ObjectRef::l_set_detach(lua_State *L) { @@ -2337,6 +2358,7 @@ luaL_Reg ObjectRef::methods[] = { luamethod(ObjectRef, get_bone_position), luamethod(ObjectRef, set_attach), luamethod(ObjectRef, get_attach), + luamethod(ObjectRef, get_children), luamethod(ObjectRef, set_detach), luamethod(ObjectRef, set_properties), luamethod(ObjectRef, get_properties), diff --git a/src/script/lua_api/l_object.h b/src/script/lua_api/l_object.h index ca03dfa2e..097a74cae 100644 --- a/src/script/lua_api/l_object.h +++ b/src/script/lua_api/l_object.h @@ -143,6 +143,9 @@ private: // get_attach(self) static int l_get_attach(lua_State *L); + // get_children(self) + static int l_get_children(lua_State *L); + // set_detach(self) static int l_set_detach(lua_State *L); From 05436fb551bb22b0a9c1d9f176a6891aa82d9cb2 Mon Sep 17 00:00:00 2001 From: Maksim Date: Sun, 24 May 2020 12:55:48 +0200 Subject: [PATCH 110/266] Android: get deps as a zip archive and sqlite3 from official source --- build/android/app/build.gradle | 2 +- build/android/build.gradle | 5 +- .../gradle/wrapper/gradle-wrapper.properties | 4 +- build/android/native/build.gradle | 63 +++++++++++++++---- 4 files changed, 57 insertions(+), 17 deletions(-) diff --git a/build/android/app/build.gradle b/build/android/app/build.gradle index e3619af17..8e87424b5 100644 --- a/build/android/app/build.gradle +++ b/build/android/app/build.gradle @@ -2,7 +2,7 @@ apply plugin: 'com.android.application' android { compileSdkVersion 29 buildToolsVersion '29.0.3' - ndkVersion '21.1.6352462' + ndkVersion '21.2.6472646' defaultConfig { applicationId 'net.minetest.minetest' minSdkVersion 16 diff --git a/build/android/build.gradle b/build/android/build.gradle index 8707b563c..4cde1c3d6 100644 --- a/build/android/build.gradle +++ b/build/android/build.gradle @@ -15,8 +15,8 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.6.3' - classpath 'org.ajoberstar.grgit:grgit-gradle:4.0.2' + classpath 'com.android.tools.build:gradle:4.0.0' + classpath 'de.undercouch:gradle-download-task:4.0.4' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } @@ -31,4 +31,5 @@ allprojects { task clean(type: Delete) { delete rootProject.buildDir + delete 'native/deps' } diff --git a/build/android/gradle/wrapper/gradle-wrapper.properties b/build/android/gradle/wrapper/gradle-wrapper.properties index d612cf333..59a00ebac 100644 --- a/build/android/gradle/wrapper/gradle-wrapper.properties +++ b/build/android/gradle/wrapper/gradle-wrapper.properties @@ -1,2 +1,2 @@ -#Mon Apr 06 00:06:16 CEST 2020 -distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-all.zip +#Fri Jun 05 19:18:07 CEST 2020 +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip diff --git a/build/android/native/build.gradle b/build/android/native/build.gradle index cbd50db6a..b072766b0 100644 --- a/build/android/native/build.gradle +++ b/build/android/native/build.gradle @@ -1,16 +1,16 @@ apply plugin: 'com.android.library' -import org.ajoberstar.grgit.Grgit +apply plugin: 'de.undercouch.download' android { compileSdkVersion 29 buildToolsVersion '29.0.3' - ndkVersion '21.1.6352462' + ndkVersion '21.2.6472646' defaultConfig { minSdkVersion 16 targetSdkVersion 29 externalNativeBuild { ndkBuild { - arguments '-j8', + arguments '-j' + Runtime.getRuntime().availableProcessors(), "versionMajor=${versionMajor}", "versionMinor=${versionMinor}", "versionPatch=${versionPatch}", @@ -45,15 +45,54 @@ android { } } -task cloneGitRepo() { - def destination = file('deps') - if(!destination.exists()) { - def grgit = Grgit.clone( - dir: destination, - uri: 'https://github.com/minetest/minetest_android_deps_binaries' - ) - grgit.close() +// get precompiled deps +def folder = 'minetest_android_deps_binaries' + +task downloadDeps(type: Download) { + src 'https://github.com/minetest/' + folder + '/archive/master.zip' + dest new File(buildDir, 'deps.zip') + overwrite false +} + +task getDeps(dependsOn: downloadDeps, type: Copy) { + def deps = file('deps') + def f = file("$buildDir/" + folder + "-master") + + if (!deps.exists() && !f.exists()) { + from zipTree(downloadDeps.dest) + into buildDir + } + + doLast { + if (!deps.exists()) { + file(f).renameTo(file(deps)) + } } } -preBuild.dependsOn cloneGitRepo +// get sqlite +def sqlite_ver = '3320200' +task downloadSqlite(dependsOn: getDeps, type: Download) { + src 'https://www.sqlite.org/2020/sqlite-amalgamation-' + sqlite_ver + '.zip' + dest new File(buildDir, 'sqlite.zip') + overwrite false +} + +task getSqlite(dependsOn: downloadSqlite, type: Copy) { + def sqlite = file('deps/Android/sqlite') + def f = file("$buildDir/sqlite-amalgamation-" + sqlite_ver) + + if (!sqlite.exists() && !f.exists()) { + from zipTree(downloadSqlite.dest) + into buildDir + } + + doLast { + if (!sqlite.exists()) { + file(f).renameTo(file(sqlite)) + } + } +} + +preBuild.dependsOn getDeps +preBuild.dependsOn getSqlite From e831ebd63bd8d173cc3ef10cb7d2ff2ed9f9090d Mon Sep 17 00:00:00 2001 From: Maksim Date: Sun, 24 May 2020 12:57:05 +0200 Subject: [PATCH 111/266] Android: add ci with saving artifacts --- .github/workflows/android.yml | 42 ++++++++++++++++++++++++++++++++++ build/android/app/build.gradle | 5 +++- 2 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/android.yml diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml new file mode 100644 index 000000000..0fcfe2390 --- /dev/null +++ b/.github/workflows/android.yml @@ -0,0 +1,42 @@ +name: android + +# build on c/cpp changes or workflow changes +on: + push: + paths: + - 'lib/**.[ch]' + - 'lib/**.cpp' + - 'src/**.[ch]' + - 'src/**.cpp' + - 'build/android/**' + - '.github/workflows/android.yml' + pull_request: + paths: + - 'lib/**.[ch]' + - 'lib/**.cpp' + - 'src/**.[ch]' + - 'src/**.cpp' + - 'build/android/**' + - '.github/workflows/android.yml' + +jobs: + build: + runs-on: ubuntu-18.04 + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 1.8 + uses: actions/setup-java@v1 + with: + java-version: 1.8 + - name: Build with Gradle + run: cd build/android; ./gradlew assemblerelease + - name: Save armeabi artifact + uses: actions/upload-artifact@v2 + with: + name: Minetest-armeabi-v7a.apk + path: build/android/app/build/outputs/apk/release/app-armeabi-v7a-release-unsigned.apk + - name: Save arm64 artifact + uses: actions/upload-artifact@v2 + with: + name: Minetest-arm64-v8a.apk + path: build/android/app/build/outputs/apk/release/app-arm64-v8a-release-unsigned.apk diff --git a/build/android/app/build.gradle b/build/android/app/build.gradle index 8e87424b5..2c0a02b7f 100644 --- a/build/android/app/build.gradle +++ b/build/android/app/build.gradle @@ -11,8 +11,11 @@ android { versionCode project.versionCode } + // load properties Properties props = new Properties() - props.load(new FileInputStream(file('../local.properties'))) + def propfile = file('../local.properties') + if (propfile.exists()) + props.load(new FileInputStream(propfile)) if (props.getProperty('keystore') != null) { signingConfigs { From 2ca81d679f92ff1f0effccff5d3ee5672871324f Mon Sep 17 00:00:00 2001 From: Maksim Date: Mon, 7 Sep 2020 22:12:31 +0200 Subject: [PATCH 112/266] Android: update gradle, NDK and built tools version --- build/android/app/build.gradle | 6 +++--- build/android/build.gradle | 4 ++-- build/android/gradle/wrapper/gradle-wrapper.properties | 8 ++++++-- build/android/native/build.gradle | 4 ++-- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/build/android/app/build.gradle b/build/android/app/build.gradle index 2c0a02b7f..812726030 100644 --- a/build/android/app/build.gradle +++ b/build/android/app/build.gradle @@ -1,8 +1,8 @@ apply plugin: 'com.android.application' android { compileSdkVersion 29 - buildToolsVersion '29.0.3' - ndkVersion '21.2.6472646' + buildToolsVersion '30.0.2' + ndkVersion '21.3.6528147' defaultConfig { applicationId 'net.minetest.minetest' minSdkVersion 16 @@ -110,5 +110,5 @@ android.applicationVariants.all { variant -> dependencies { implementation project(':native') - implementation 'androidx.appcompat:appcompat:1.1.0' + implementation 'androidx.appcompat:appcompat:1.2.0' } diff --git a/build/android/build.gradle b/build/android/build.gradle index 4cde1c3d6..111a506e1 100644 --- a/build/android/build.gradle +++ b/build/android/build.gradle @@ -15,8 +15,8 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:4.0.0' - classpath 'de.undercouch:gradle-download-task:4.0.4' + classpath 'com.android.tools.build:gradle:4.0.1' + classpath 'de.undercouch:gradle-download-task:4.1.1' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } diff --git a/build/android/gradle/wrapper/gradle-wrapper.properties b/build/android/gradle/wrapper/gradle-wrapper.properties index 59a00ebac..ed85703f3 100644 --- a/build/android/gradle/wrapper/gradle-wrapper.properties +++ b/build/android/gradle/wrapper/gradle-wrapper.properties @@ -1,2 +1,6 @@ -#Fri Jun 05 19:18:07 CEST 2020 -distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip +#Mon Sep 07 22:11:10 CEST 2020 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-all.zip diff --git a/build/android/native/build.gradle b/build/android/native/build.gradle index b072766b0..69e1cf461 100644 --- a/build/android/native/build.gradle +++ b/build/android/native/build.gradle @@ -3,8 +3,8 @@ apply plugin: 'de.undercouch.download' android { compileSdkVersion 29 - buildToolsVersion '29.0.3' - ndkVersion '21.2.6472646' + buildToolsVersion '30.0.2' + ndkVersion '21.3.6528147' defaultConfig { minSdkVersion 16 targetSdkVersion 29 From 7499ebe46af072e911fa4902faf0a7343559d2c3 Mon Sep 17 00:00:00 2001 From: Zughy <63455151+Zughy@users.noreply.github.com> Date: Tue, 13 Oct 2020 21:28:53 +0200 Subject: [PATCH 113/266] Fix float argument check in minetest.set_timeofday() (#10483) Co-authored-by: Zughy <4279489-marco_a@users.noreply.gitlab.com> --- src/script/lua_api/l_env.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/script/lua_api/l_env.cpp b/src/script/lua_api/l_env.cpp index 8d50d664d..021ef2ea7 100644 --- a/src/script/lua_api/l_env.cpp +++ b/src/script/lua_api/l_env.cpp @@ -751,8 +751,9 @@ int ModApiEnvMod::l_set_timeofday(lua_State *L) // Do it float timeofday_f = readParam(L, 1); - sanity_check(timeofday_f >= 0.0 && timeofday_f <= 1.0); - int timeofday_mh = (int)(timeofday_f * 24000.0); + luaL_argcheck(L, timeofday_f >= 0.0f && timeofday_f <= 1.0f, 1, + "value must be between 0 and 1"); + int timeofday_mh = (int)(timeofday_f * 24000.0f); // This should be set directly in the environment but currently // such changes aren't immediately sent to the clients, so call // the server instead. From fc2e120b03e65feb9ccfb92f9d3b68fda230bc7b Mon Sep 17 00:00:00 2001 From: Paramat Date: Tue, 13 Oct 2020 23:57:46 +0100 Subject: [PATCH 114/266] Document how to work with ClangFormat (#10468) To avoid the common misunderstanding where a contributor thinks they must apply the unsuitable formatting requests ClangFormat makes. --- .github/CONTRIBUTING.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index b234fb283..e57ea9f83 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -43,6 +43,14 @@ Contributions are welcome! Here's how you can help: 4. The code's interfaces are well designed, regardless of other aspects that might need more work in the future. 5. It uses protocols and formats which include the required compatibility. +### Important note about automated GitHub checks + +When you submit a pull request, GitHub automatically runs checks on the Minetest Engine combined with your changes. One of these checks is called 'cpp lint / clang format', which checks code formatting. +Because formatting for readability requires human judgement this check often fails and often makes unsuitable formatting requests which make code readability worse. + +If this check fails, look at the details to check for any clear mistakes and correct those. However you should not apply everything ClangFormat requests, ignore requests that make code readability worse and any other clearly unsuitable requests. +Discuss with a core developer if in any doubt and for how to progress. + ## Issues If you experience an issue, we would like to know the details - especially when a stable release is on the way. From 2f871e3b49d87b0ae0e7b52f7ead6d3512727f0f Mon Sep 17 00:00:00 2001 From: Paramat Date: Tue, 13 Oct 2020 23:59:47 +0100 Subject: [PATCH 115/266] Devtest hand tool: Update capabilities, add creative mode capabilities (#10484) --- games/devtest/mods/basetools/init.lua | 60 ++++++++++++++++++++------- 1 file changed, 44 insertions(+), 16 deletions(-) diff --git a/games/devtest/mods/basetools/init.lua b/games/devtest/mods/basetools/init.lua index e4a36ca46..bd7480030 100644 --- a/games/devtest/mods/basetools/init.lua +++ b/games/devtest/mods/basetools/init.lua @@ -6,7 +6,7 @@ Tool types: -* Hand: basic tool/weapon (just for convenience, not optimized for testing) +* Hand: basic tool/weapon (special capabilities in creative mode) * Pickaxe: dig cracky * Axe: dig choppy * Shovel: dig crumbly @@ -24,21 +24,49 @@ Tool materials: ]] -- The hand -minetest.register_item(":", { - type = "none", - wield_image = "wieldhand.png", - wield_scale = {x=1,y=1,z=2.5}, - tool_capabilities = { - full_punch_interval = 1.0, - max_drop_level = 0, - groupcaps = { - crumbly = {times={[3]=1.50}, uses=0, maxlevel=0}, - snappy = {times={[3]=1.50}, uses=0, maxlevel=0}, - oddly_breakable_by_hand = {times={[1]=7.00,[2]=4.00,[3]=2.00}, uses=0, maxlevel=0}, - }, - damage_groups = {fleshy=1}, - } -}) +if minetest.settings:get_bool("creative_mode") then + local digtime = 42 + local caps = {times = {digtime, digtime, digtime}, uses = 0, maxlevel = 256} + + minetest.register_item(":", { + type = "none", + wield_image = "wieldhand.png", + wield_scale = {x = 1, y = 1, z = 2.5}, + range = 10, + tool_capabilities = { + full_punch_interval = 0.5, + max_drop_level = 3, + groupcaps = { + crumbly = caps, + cracky = caps, + snappy = caps, + choppy = caps, + oddly_breakable_by_hand = caps, + -- dig_immediate group doesn't use value 1. Value 3 is instant dig + dig_immediate = + {times = {[2] = digtime, [3] = 0}, uses = 0, maxlevel = 256}, + }, + damage_groups = {fleshy = 10}, + } + }) +else + minetest.register_item(":", { + type = "none", + wield_image = "wieldhand.png", + wield_scale = {x = 1, y = 1, z = 2.5}, + tool_capabilities = { + full_punch_interval = 0.9, + max_drop_level = 0, + groupcaps = { + crumbly = {times = {[2] = 3.00, [3] = 0.70}, uses = 0, maxlevel = 1}, + snappy = {times = {[3] = 0.40}, uses = 0, maxlevel = 1}, + oddly_breakable_by_hand = + {times = {[1] = 3.50, [2] = 2.00, [3] = 0.70}, uses = 0} + }, + damage_groups = {fleshy = 1}, + } + }) +end -- Mese Pickaxe: special tool that digs "everything" instantly minetest.register_tool("basetools:pick_mese", { From f43d1cfa81aa496174af6cdfa648dab9dd17288c Mon Sep 17 00:00:00 2001 From: Lars Date: Tue, 13 Oct 2020 12:38:34 -0700 Subject: [PATCH 116/266] Rely on max_simultaneous_block_sends_per_client to limit blocks sent to the client. --- src/clientiface.cpp | 18 +----------------- src/clientiface.h | 1 - 2 files changed, 1 insertion(+), 18 deletions(-) diff --git a/src/clientiface.cpp b/src/clientiface.cpp index 28a0ee770..a01cba7e0 100644 --- a/src/clientiface.cpp +++ b/src/clientiface.cpp @@ -99,7 +99,6 @@ void RemoteClient::GetNextBlocks ( { // Increment timers m_nothing_to_send_pause_timer -= dtime; - m_nearest_unsent_reset_timer += dtime; if (m_nothing_to_send_pause_timer >= 0) return; @@ -154,14 +153,6 @@ void RemoteClient::GetNextBlocks ( /*infostream<<"m_nearest_unsent_reset_timer=" < 20.0f) { - m_nearest_unsent_reset_timer = 0.0f; - m_nearest_unsent_d = 0; - //infostream<<"Resetting m_nearest_unsent_d for " - // <getPlayerName(peer_id)< d_start + max_d_increment_at_time) - d_max = d_start + max_d_increment_at_time; - // cos(angle between velocity and camera) * |velocity| // Limit to 0.0f in case player moves backwards. f32 dot = rangelim(camera_dir.dotProduct(playerspeed), 0.0f, 300.0f); @@ -238,7 +222,7 @@ void RemoteClient::GetNextBlocks ( const v3s16 cam_pos_nodes = floatToInt(camera_pos, BS); s16 d; - for (d = d_start; d <= d_max; d++) { + for (d = d_start; d <= full_d_max; d++) { /* Get the border/face dot coordinates of a "d-radiused" box diff --git a/src/clientiface.h b/src/clientiface.h index 83fa6fe99..ca2af65fe 100644 --- a/src/clientiface.h +++ b/src/clientiface.h @@ -364,7 +364,6 @@ private: std::set m_blocks_sent; s16 m_nearest_unsent_d = 0; v3s16 m_last_center; - float m_nearest_unsent_reset_timer = 0.0f; const u16 m_max_simul_sends; const float m_min_time_from_building; From bbcd2495444225fd16f61f8a830185ed5b8cf77f Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein Date: Sat, 17 Oct 2020 11:11:22 +0200 Subject: [PATCH 117/266] New Mod System --- builtin/client/chatcommands.lua | 133 ++++++++++++ builtin/client/cheats/chat.lua | 8 + .../client/{cheats.lua => cheats/init.lua} | 38 +++- builtin/client/cheats/inventory.lua | 117 ++++++++++ builtin/client/cheats/movement.lua | 14 ++ builtin/client/cheats/player.lua | 39 ++++ builtin/client/cheats/render.lua | 1 + builtin/client/cheats/world.lua | 53 +++++ builtin/client/init.lua | 2 +- builtin/common/chatcommands.lua | 43 ++++ builtin/common/misc_helpers.lua | 61 ++++++ builtin/mainmenu/tab_credits.lua | 6 +- builtin/settingtypes.txt | 34 ++- clientmods/dragonfire/autoeat/init.lua | 21 -- clientmods/dragonfire/autoeat/mod.conf | 3 - .../dragonfire/autoeat/settingtypes.txt | 1 - clientmods/dragonfire/autosneak/init.lua | 13 -- clientmods/dragonfire/autosneak/mod.conf | 3 - .../dragonfire/autosneak/settingtypes.txt | 1 - clientmods/dragonfire/chat/colors.lua | 33 --- clientmods/dragonfire/chat/init.lua | 13 -- clientmods/dragonfire/chat/leak.lua | 12 -- clientmods/dragonfire/chat/mod.conf | 3 - clientmods/dragonfire/chat/rainbow.lua | 61 ------ clientmods/dragonfire/chat/settingtypes.txt | 6 - clientmods/dragonfire/chat/spam.lua | 12 -- clientmods/dragonfire/chat/status.lua | 7 - clientmods/dragonfire/commands/init.lua | 88 -------- clientmods/dragonfire/commands/mod.conf | 3 - clientmods/dragonfire/inventory/autoeject.lua | 19 -- clientmods/dragonfire/inventory/autotool.lua | 36 ---- .../dragonfire/inventory/enderchest.lua | 27 --- clientmods/dragonfire/inventory/hand.lua | 27 --- clientmods/dragonfire/inventory/init.lua | 11 - clientmods/dragonfire/inventory/invhack.lua | 13 -- clientmods/dragonfire/inventory/mod.conf | 4 - clientmods/dragonfire/inventory/next_item.lua | 18 -- .../dragonfire/inventory/settingtypes.txt | 4 - clientmods/dragonfire/list/init.lua | 47 ---- clientmods/dragonfire/modpack.txt | 1 - clientmods/dragonfire/pathfinding/init.lua | 109 ---------- clientmods/dragonfire/pathfinding/mod.conf | 3 - .../dragonfire/pathfinding/settingtypes.txt | 3 - clientmods/dragonfire/perlin/init.lua | 33 --- clientmods/dragonfire/perlin/mod.conf | 3 - clientmods/dragonfire/perlin/perlin.lua | 144 ------------- clientmods/dragonfire/respawn/init.lua | 47 ---- clientmods/dragonfire/respawn/mod.conf | 4 - .../dragonfire/respawn/settingtypes.txt | 1 - clientmods/dragonfire/schematicas/init.lua | 204 ------------------ clientmods/dragonfire/schematicas/mod.conf | 4 - clientmods/dragonfire/warp/init.lua | 94 -------- clientmods/dragonfire/warp/mod.conf | 3 - clientmods/dragonfire/world/init.lua | 97 --------- clientmods/dragonfire/world/mod.conf | 3 - clientmods/dragonfire/world/settingtypes.txt | 6 - clientmods/mods.conf | 12 -- doc/client_lua_api.txt | 2 + doc/lua_api.txt | 2 + src/client/game.cpp | 46 +++- src/client/game.h | 4 +- src/client/inputhandler.cpp | 4 +- src/client/keys.h | 4 +- src/client/render/core.cpp | 2 +- src/defaultsettings.cpp | 19 +- src/gui/guiKeyChangeMenu.cpp | 12 +- src/script/cpp_api/s_client.cpp | 4 +- src/script/cpp_api/s_client.h | 2 +- 68 files changed, 615 insertions(+), 1292 deletions(-) create mode 100644 builtin/client/cheats/chat.lua rename builtin/client/{cheats.lua => cheats/init.lua} (61%) create mode 100644 builtin/client/cheats/inventory.lua create mode 100644 builtin/client/cheats/movement.lua create mode 100644 builtin/client/cheats/player.lua create mode 100644 builtin/client/cheats/render.lua create mode 100644 builtin/client/cheats/world.lua delete mode 100644 clientmods/dragonfire/autoeat/init.lua delete mode 100644 clientmods/dragonfire/autoeat/mod.conf delete mode 100644 clientmods/dragonfire/autoeat/settingtypes.txt delete mode 100644 clientmods/dragonfire/autosneak/init.lua delete mode 100644 clientmods/dragonfire/autosneak/mod.conf delete mode 100644 clientmods/dragonfire/autosneak/settingtypes.txt delete mode 100644 clientmods/dragonfire/chat/colors.lua delete mode 100644 clientmods/dragonfire/chat/init.lua delete mode 100644 clientmods/dragonfire/chat/leak.lua delete mode 100644 clientmods/dragonfire/chat/mod.conf delete mode 100644 clientmods/dragonfire/chat/rainbow.lua delete mode 100644 clientmods/dragonfire/chat/settingtypes.txt delete mode 100644 clientmods/dragonfire/chat/spam.lua delete mode 100644 clientmods/dragonfire/chat/status.lua delete mode 100644 clientmods/dragonfire/commands/init.lua delete mode 100644 clientmods/dragonfire/commands/mod.conf delete mode 100644 clientmods/dragonfire/inventory/autoeject.lua delete mode 100644 clientmods/dragonfire/inventory/autotool.lua delete mode 100644 clientmods/dragonfire/inventory/enderchest.lua delete mode 100644 clientmods/dragonfire/inventory/hand.lua delete mode 100644 clientmods/dragonfire/inventory/init.lua delete mode 100644 clientmods/dragonfire/inventory/invhack.lua delete mode 100644 clientmods/dragonfire/inventory/mod.conf delete mode 100644 clientmods/dragonfire/inventory/next_item.lua delete mode 100644 clientmods/dragonfire/inventory/settingtypes.txt delete mode 100644 clientmods/dragonfire/list/init.lua delete mode 100644 clientmods/dragonfire/modpack.txt delete mode 100644 clientmods/dragonfire/pathfinding/init.lua delete mode 100644 clientmods/dragonfire/pathfinding/mod.conf delete mode 100644 clientmods/dragonfire/pathfinding/settingtypes.txt delete mode 100644 clientmods/dragonfire/perlin/init.lua delete mode 100644 clientmods/dragonfire/perlin/mod.conf delete mode 100644 clientmods/dragonfire/perlin/perlin.lua delete mode 100644 clientmods/dragonfire/respawn/init.lua delete mode 100644 clientmods/dragonfire/respawn/mod.conf delete mode 100644 clientmods/dragonfire/respawn/settingtypes.txt delete mode 100644 clientmods/dragonfire/schematicas/init.lua delete mode 100644 clientmods/dragonfire/schematicas/mod.conf delete mode 100644 clientmods/dragonfire/warp/init.lua delete mode 100644 clientmods/dragonfire/warp/mod.conf delete mode 100644 clientmods/dragonfire/world/init.lua delete mode 100644 clientmods/dragonfire/world/mod.conf delete mode 100644 clientmods/dragonfire/world/settingtypes.txt delete mode 100644 clientmods/mods.conf diff --git a/builtin/client/chatcommands.lua b/builtin/client/chatcommands.lua index 83b7f7b14..7c3dd521e 100644 --- a/builtin/client/chatcommands.lua +++ b/builtin/client/chatcommands.lua @@ -40,4 +40,137 @@ function core.run_server_chatcommand(cmd, param) core.send_chat_message("/" .. cmd .. " " .. param) end +core.register_chatcommand("say", { + description = "Send raw text", + func = function(text) + core.send_chat_message(text) + return true + end, +}) +core.register_chatcommand("teleport", { + params = ",,", + description = "Teleport to coordinates.", + func = function(param) + local success, pos = core.parse_pos(param) + if success then + core.localplayer:set_pos(pos) + return true, "Teleporting to " .. core.pos_to_string(pos) + end + return false, pos + end, +}) + +core.register_chatcommand("teleportjump", { + params = ",,", + description = "Teleport to relative coordinates.", + func = function(param) + local success, pos = core.parse_relative_pos(param) + if success then + core.localplayer:set_pos(pos) + return true, "Teleporting to " .. core.pos_to_string(pos) + end + return false, pos + end, +}) + +core.register_chatcommand("wielded", { + description = "Print itemstring of wieleded item", + func = function() + return true, core.localplayer:get_wielded_item():get_name() + end +}) + +core.register_chatcommand("disconnect", { + description = "Exit to main menu", + func = function(param) + core.disconnect() + end, +}) + +core.register_chatcommand("players", { + description = "List online players", + func = function(param) + return true, "Online players: " .. table.concat(core.get_player_names(), ", ") + end +}) + +core.register_chatcommand("kill", { + description = "Kill yourself", + func = function() + core.send_damage(10000) + end, +}) + +core.register_chatcommand("set", { + params = "([-n] ) | ", + description = "Set or read client configuration setting", + func = function(param) + local arg, setname, setvalue = string.match(param, "(-[n]) ([^ ]+) (.+)") + if arg and arg == "-n" and setname and setvalue then + core.settings:set(setname, setvalue) + return true, setname .. " = " .. setvalue + end + + setname, setvalue = string.match(param, "([^ ]+) (.+)") + if setname and setvalue then + if not core.settings:get(setname) then + return false, "Failed. Use '.set -n ' to create a new setting." + end + core.settings:set(setname, setvalue) + return true, setname .. " = " .. setvalue + end + + setname = string.match(param, "([^ ]+)") + if setname then + setvalue = core.settings:get(setname) + if not setvalue then + setvalue = "" + end + return true, setname .. " = " .. setvalue + end + + return false, "Invalid parameters (see .help set)." + end, +}) + +core.register_chatcommand("findnodes", { + description = "Scan for one or multible nodes in a radius around you", + param = " [,...]", + func = function(param) + local radius = tonumber(param:split(" ")[1]) + local nodes = param:split(" ")[2]:split(",") + local pos = core.localplayer:get_pos() + local fpos = core.find_node_near(pos, radius, nodes, true) + if fpos then + return true, "Found " .. table.concat(nodes, " or ") .. " at " .. core.pos_to_string(fpos) + end + return false, "None of " .. table.concat(nodes, " or ") .. " found in a radius of " .. tostring(radius) + end, +}) + +core.register_chatcommand("place", { + params = ",,", + description = "Place wielded item", + func = function(param) + local success, pos = core.parse_relative_pos(param) + if success then + cores.place_node(pos) + return true, "Node placed at " .. core.pos_to_string(pos) + end + return false, pos + end, +}) + +core.register_chatcommand("dig", { + params = ",,", + description = "Dig node", + func = function(param) + local success, pos = core.parse_relative_pos(param) + if success then + core.dig_node(pos) + return true, "Node at " .. core.pos_to_string(pos) .. " dug" + end + return false, pos + end, +}) diff --git a/builtin/client/cheats/chat.lua b/builtin/client/cheats/chat.lua new file mode 100644 index 000000000..1b8094768 --- /dev/null +++ b/builtin/client/cheats/chat.lua @@ -0,0 +1,8 @@ +core.register_on_receiving_chat_message(function(message) + if message:sub(1, 1) == "#" and core.settings:get_bool("ignore_status_messages") ~= false then + return true + elseif message:find('\1b@mcl_death_messages\1b') and core.settings:get_bool("mark_deathmessages") ~= false then + core.display_chat_message(core.colorize("#F25819", "[Deathmessage] ") .. message) + return true + end +end) diff --git a/builtin/client/cheats.lua b/builtin/client/cheats/init.lua similarity index 61% rename from builtin/client/cheats.lua rename to builtin/client/cheats/init.lua index 800784597..9eb2594a0 100644 --- a/builtin/client/cheats.lua +++ b/builtin/client/cheats/init.lua @@ -3,16 +3,16 @@ core.cheats = { ["Killaura"] = "killaura", ["AntiKnockback"] = "antiknockback", ["FastHit"] = "spamclick", + ["AttachmentFloat"] = "float_above_parent", }, ["Movement"] = { ["Freecam"] = "freecam", - ["PrivBypass"] = "priv_bypass", ["AutoForward"] = "continuous_forward", ["PitchMove"] = "pitch_move", ["AutoJump"] = "autojump", ["Jesus"] = "jesus", ["NoSlow"] = "no_slow", - + ["AutoSneak"] = "autosneak", }, ["Render"] = { ["Xray"] = "xray", @@ -23,7 +23,6 @@ core.cheats = { ["Coords"] = "coords", ["Tracers"] = "enable_tracers", ["ESP"] = "enable_esp", - ["AttachmentFloat"] = "float_above_parent", }, ["World"] = { ["FastDig"] = "fastdig", @@ -31,9 +30,11 @@ core.cheats = { ["AutoDig"] = "autodig", ["AutoPlace"] = "autoplace", ["InstantBreak"] = "instant_break", - ["IncreasedRange"] = "increase_tool_range", - ["UnlimitedRange"] = "increase_tool_range_plus", - ["PointLiquids"] = "point_liquids", + ["Scaffold"] = "scaffold", + ["ScaffoldPlus"] = "scaffold_plus", + ["BlockWater"] = "block_water", + ["PlaceOnTop"] = "autotnt", + ["Replace"] = "replace" }, ["Exploit"] = { ["EntitySpeed"] = "entity_speed", @@ -42,10 +43,35 @@ core.cheats = { ["Player"] = { ["NoFallDamage"] = "prevent_natural_damage", ["NoForceRotate"] = "no_force_rotate", + ["IncreasedRange"] = "increase_tool_range", + ["UnlimitedRange"] = "increase_tool_range_plus", + ["PointLiquids"] = "point_liquids", + ["PrivBypass"] = "priv_bypass", + ["AutoRespawn"] = "autorespawn", }, + ["Chat"] = { + ["IgnoreStatus"] = "ignore_status_messages", + ["Deathmessages"] = "mark_deathmessages" + }, + ["Inventory"] = { + ["AutoEject"] = "autoeject", + ["AutoTool"] = "autotool", + ["Enderchest"] = function() core.open_enderchest() end, + ["HandSlot"] = function() core.open_handslot() end, + ["NextItem"] = "next_item", + } } function core.register_cheat(cheatname, category, func) core.cheats[category] = core.cheats[category] or {} core.cheats[category][cheatname] = func end + +local cheatpath = core.get_builtin_path() .. "client" .. DIR_DELIM .. "cheats" .. DIR_DELIM + +dofile(cheatpath .. "chat.lua") +dofile(cheatpath .. "inventory.lua") +dofile(cheatpath .. "movement.lua") +dofile(cheatpath .. "player.lua") +dofile(cheatpath .. "render.lua") +dofile(cheatpath .. "world.lua") diff --git a/builtin/client/cheats/inventory.lua b/builtin/client/cheats/inventory.lua new file mode 100644 index 000000000..d12052d7c --- /dev/null +++ b/builtin/client/cheats/inventory.lua @@ -0,0 +1,117 @@ +local elapsed_time = 0 +local tick_time = 0.05 + +core.register_globalstep(function(dtime) + -- AutoEject + if core.settings:get_bool("autoeject") then + local player = core.localplayer + local list = (core.settings:get("eject_items") or ""):split(",") + local inventory = core.get_inventory("current_player") + for index, stack in pairs(inventory.main) do + if table.indexof(list, stack:get_name()) ~= -1 then + local old_index = player:get_wield_index() + player:set_wield_index(index - 1) + core.drop_selected_item() + player:set_wield_index(old_index) + return + end + end + end + -- NextItem + if core.settings:get_bool("next_item") then + elapsed_time = elapsed_time + dtime + if elapsed_time < tick_time then return end + local player = minetest.localplayer + if not player then return end + local item = player:get_wielded_item() + if item:get_count() == 0 then + local index = player:get_wield_index() + player:set_wield_index(index + 1) + end + elapsed_time = 0 + end +end) + +core.register_list_command("eject", "Configure AutoEject", "eject_items") + +-- AutoTool + +local function check_tool(stack, node_groups, old_best_time) + local toolcaps = stack:get_tool_capabilities() + if not toolcaps then return end + local best_time = old_best_time + for group, groupdef in pairs(toolcaps.groupcaps) do + local level = node_groups[group] + if level then + local this_time = groupdef.times[level] + if this_time < best_time then + best_time = this_time + end + end + end + return best_time < old_best_time, best_time +end + +core.register_on_punchnode(function(pos, node) + if not minetest.settings:get_bool("autotool") then return end + local player = minetest.localplayer + local inventory = minetest.get_inventory("current_player") + local node_groups = minetest.get_node_def(node.name).groups + local new_index = player:get_wield_index() + local is_better, best_time = false, math.huge + is_better, best_time = check_tool(player:get_wielded_item(), node_groups, best_time) + is_better, best_time = check_tool(inventory.hand[1], node_groups, best_time) + for index, stack in pairs(inventory.main) do + is_better, best_time = check_tool(stack, node_groups, best_time) + if is_better then + new_index = index - 1 + end + end + player:set_wield_index(new_index) +end) + +-- Enderchest + +function get_itemslot_bg(x, y, w, h) + local out = "" + for i = 0, w - 1, 1 do + for j = 0, h - 1, 1 do + out = out .."image["..x+i..","..y+j..";1,1;mcl_formspec_itemslot.png]" + end + end + return out +end + +local enderchest_formspec = "size[9,8.75]".. + "label[0,0;"..minetest.formspec_escape(minetest.colorize("#313131", "Ender Chest")).."]".. + "list[current_player;enderchest;0,0.5;9,3;]".. + get_itemslot_bg(0,0.5,9,3).. + "label[0,4.0;"..minetest.formspec_escape(minetest.colorize("#313131", "Inventory")).."]".. + "list[current_player;main;0,4.5;9,3;9]".. + get_itemslot_bg(0,4.5,9,3).. + "list[current_player;main;0,7.74;9,1;]".. + get_itemslot_bg(0,7.74,9,1).. + "listring[current_player;enderchest]".. + "listring[current_player;main]" + +function core.open_enderchest() + core.show_formspec("__builtin__:enderchest", enderchest_formspec) +end + +-- HandSlot + +local hand_formspec = "size[9,8.75]".. + "label[0,0;"..minetest.formspec_escape(minetest.colorize("#313131", "Hand")).."]".. + "list[current_player;hand;0,0.5;1,1;]".. + get_itemslot_bg(0,0.5,1,1).. + "label[0,4.0;"..minetest.formspec_escape(minetest.colorize("#313131", "Inventory")).."]".. + "list[current_player;main;0,4.5;9,3;9]".. + get_itemslot_bg(0,4.5,9,3).. + "list[current_player;main;0,7.74;9,1;]".. + get_itemslot_bg(0,7.74,9,1).. + "listring[current_player;hand]".. + "listring[current_player;main]" + +function core.open_handslot() + minetest.show_formspec("__builtin__:hand", hand_formspec) +end diff --git a/builtin/client/cheats/movement.lua b/builtin/client/cheats/movement.lua new file mode 100644 index 000000000..bd9b995ad --- /dev/null +++ b/builtin/client/cheats/movement.lua @@ -0,0 +1,14 @@ +-- autosneak + +local autosneak_was_enabled = false + +core.register_globalstep(function() + if core.settings:get_bool("autosneak") then + core.set_keypress("sneak", true) + autosneak_was_enabled = true + elseif autosneak_was_enabled then + autosneak_was_enabled = false + core.set_keypress("sneak", false) + end +end) + diff --git a/builtin/client/cheats/player.lua b/builtin/client/cheats/player.lua new file mode 100644 index 000000000..499ed47f3 --- /dev/null +++ b/builtin/client/cheats/player.lua @@ -0,0 +1,39 @@ +local death_formspec = "" + .. "size[11,5.5]" + .. "bgcolor[#320000b4;true]" + .. "label[4.85,1.35;" .. "You died" .. "]" + .. "button_exit[2,3;3,0.5;btn_respawn;" .. "Respawn" .. "]" + .. "button_exit[6,3;3,0.5;btn_ghost_mode;" .. "Ghost Mode" .. "]" + .. "set_focus[btn_respawn;true]" + +core.register_on_death(function() + core.display_chat_message("You died at " .. core.pos_to_string(vector.round(core.localplayer:get_pos())) .. ".") + if core.settings:get_bool("autorespawn") then + core.send_respawn() + else + core.show_formspec("__builtin__:death", death_formspec) + end +end) + +core.register_on_formspec_input(function(formname, fields) + if formname == "__builtin__:death" then + if fields.btn_ghost_mode then + core.display_chat_message("You are in ghost mode. Use .respawn to Respawn.") + else + core.send_respawn() + end + end +end) + +core.register_chatcommand("respawn", { + description = "Respawn when in ghost mode", + func = function() + if core.localplayer:get_hp() == 0 then + core.send_respawn() + core.display_chat_message("Respawned.") + else + core.display_chat_message("You are not in ghost mode.") + end + end +}) + diff --git a/builtin/client/cheats/render.lua b/builtin/client/cheats/render.lua new file mode 100644 index 000000000..092c7fefc --- /dev/null +++ b/builtin/client/cheats/render.lua @@ -0,0 +1 @@ +core.register_list_command("xray", "Configure X-Ray", "xray_nodes") diff --git a/builtin/client/cheats/world.lua b/builtin/client/cheats/world.lua new file mode 100644 index 000000000..5b97b206b --- /dev/null +++ b/builtin/client/cheats/world.lua @@ -0,0 +1,53 @@ +core.register_on_dignode(function(pos) + if core.settings:get_bool("replace") then + core.after(0, minetest.place_node, pos) + end +end) + +local etime = 0 + +core.register_globalstep(function(dtime) + etime = etime + dtime + if etime < 1 then return end + local player = core.localplayer + if not player then return end + local pos = player:get_pos() + local item = player:get_wielded_item() + local def = core.get_item_def(item:get_name()) + local nodes_per_tick = tonumber(minetest.settings:get("nodes_per_tick")) or 8 + if item and item:get_count() > 0 and def and def.node_placement_prediction ~= "" then + if core.settings:get_bool("scaffold") then + core.place_node(vector.add(pos, {x = 0, y = -0.6, z = 0})) + elseif core.settings:get_bool("scaffold_plus") then + local z = pos.z + local positions = { + {x = 0, y = -0.6, z = 0}, + {x = 1, y = -0.6, z = 0}, + {x = -1, y = -0.6, z = 0}, + {x = -1, y = -0.6, z = -1}, + {x = 0, y = -0.6, z = -1}, + {x = 1, y = -0.6, z = -1}, + {x = -1, y = -0.6, z = 1}, + {x = 0, y = -0.6, z = 1}, + {x = 1, y = -0.6, z = 1} + } + for i, p in pairs(positions) do + core.place_node(vector.add(pos, p)) + end + elseif core.settings:get_bool("block_water") then + local positions = core.find_nodes_near(pos, 5, {"mcl_core:water_source", "mcl_core:water_floating"}, true) + for i, p in pairs(positions) do + if i > nodes_per_tick then return end + core.place_node(p) + end + elseif core.settings:get_bool("autotnt") then + local positions = core.find_nodes_near_under_air_except(pos, 5, item:get_name(), true) + for i, p in pairs(positions) do + if i > nodes_per_tick then return end + core.place_node(vector.add(p, {x = 0, y = 1, z = 0})) + end + end + end +end) + + diff --git a/builtin/client/init.lua b/builtin/client/init.lua index 44703a57c..40acf3b9b 100644 --- a/builtin/client/init.lua +++ b/builtin/client/init.lua @@ -9,5 +9,5 @@ dofile(commonpath .. "chatcommands.lua") dofile(commonpath .. "vector.lua") dofile(clientpath .. "util.lua") dofile(clientpath .. "chatcommands.lua") -dofile(clientpath .. "cheats.lua") +dofile(clientpath .. "cheats"..DIR_DELIM.."init.lua") diff --git a/builtin/common/chatcommands.lua b/builtin/common/chatcommands.lua index 52edda659..3d04391fb 100644 --- a/builtin/common/chatcommands.lua +++ b/builtin/common/chatcommands.lua @@ -29,6 +29,49 @@ function core.override_chatcommand(name, redefinition) core.registered_chatcommands[name] = chatcommand end +function core.register_list_command(command, desc, setting) + local def = {} + def.description = desc + def.params = "del | add | list" + function def.func(param) + local list = (minetest.settings:get(setting) or ""):split(",") + if param == "list" then + return true, table.concat(list, ", ") + else + local sparam = param:split(" ") + local cmd = sparam[1] + local item = sparam[2] + if cmd == "del" then + if not item then + return false, "Missing item." + end + local i = table.indexof(list, item) + if i == -1 then + return false, item .. " is not on the list." + else + table.remove(list, i) + core.settings:set(setting, table.concat(list, ",")) + return true, "Removed " .. item .. " from the list." + end + elseif cmd == "add" then + if not item then + return false, "Missing item." + end + local i = table.indexof(list, item) + if i ~= -1 then + return false, item .. " is already on the list." + else + table.insert(list, item) + core.settings:set(setting, table.concat(list, ",")) + return true, "Added " .. item .. " to the list." + end + end + end + return false, "Invalid usage. (See /help " .. command .. ")" + end + core.register_chatcommand(command, def) +end + local cmd_marker = "/" local function gettext(...) diff --git a/builtin/common/misc_helpers.lua b/builtin/common/misc_helpers.lua index bd27a01dc..f6ceda54d 100644 --- a/builtin/common/misc_helpers.lua +++ b/builtin/common/misc_helpers.lua @@ -594,6 +594,67 @@ function core.colorize(color, message) return table.concat(lines, "\n") .. core.get_color_escape_sequence("#ffffff") end +local function rgb_to_hex(rgb) + local hexadecimal = '#' + + for key, value in pairs(rgb) do + local hex = '' + + while(value > 0)do + local index = math.fmod(value, 16) + 1 + value = math.floor(value / 16) + hex = string.sub('0123456789ABCDEF', index, index) .. hex + end + + if(string.len(hex) == 0)then + hex = '00' + + elseif(string.len(hex) == 1)then + hex = '0' .. hex + end + + hexadecimal = hexadecimal .. hex + end + + return hexadecimal +end + +local function color_from_hue(hue) + local h = hue / 60 + local c = 255 + local x = (1 - math.abs(h%2 - 1)) * 255 + + local i = math.floor(h); + if (i == 0) then + return rgb_to_hex({c, x, 0}) + elseif (i == 1) then + return rgb_to_hex({x, c, 0}) + elseif (i == 2) then + return rgb_to_hex({0, c, x}) + elseif (i == 3) then + return rgb_to_hex({0, x, c}); + elseif (i == 4) then + return rgb_to_hex({x, 0, c}); + else + return rgb_to_hex({c, 0, x}); + end +end + +function core.rainbow(input) + local step = 360 / input:len() + local hue = 0 + local output = "" + for i = 1, input:len() do + local char = input:sub(i,i) + if char:match("%s") then + output = output .. char + else + output = output .. core.get_color_escape_sequence(color_from_hue(hue)) .. char + end + hue = hue + step + end + return output +end function core.strip_foreground_colors(str) return (str:gsub(ESCAPE_CHAR .. "%(c@[^)]+%)", "")) diff --git a/builtin/mainmenu/tab_credits.lua b/builtin/mainmenu/tab_credits.lua index c568eddb5..4f9f835f7 100644 --- a/builtin/mainmenu/tab_credits.lua +++ b/builtin/mainmenu/tab_credits.lua @@ -17,8 +17,10 @@ -------------------------------------------------------------------------------- local dragonfire_team = { - "Elias Fleckenstein [Main Developer]", - "DerZombiiie [Bots, User Support]", + "Elias Fleckenstein", + "cora", + "system32", + "DerZombiiie", } local core_developers = { diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index a4991cb94..13603c726 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -2217,13 +2217,10 @@ contentdb_flag_blacklist (ContentDB Flag Blacklist) string nonfree, desktop_defa fullbright (Fullbright) bool false -# Enable xray, requires fullbright -xray (X-Ray) bool false +xray (XRay) bool false -# Node to apply xray -xray_node (X-RayTexture) string default:stone +xray_nodes (XRay Nodes) string default:stone,mcl_core:stone -# Make the Client think it has all privs priv_bypass (PrivBypass) bool true fastdig (FastDig) bool false @@ -2246,7 +2243,6 @@ increase_tool_range (IncreasedRange) bool true increase_tool_range_plus (IncreasedRangePlus) bool true -# HUD Flags Bypass hud_flags_bypass (HUDBypass) bool true antiknockback (AntiKnockback) bool false @@ -2274,3 +2270,29 @@ enable_tracers (Tracers) bool false enable_esp (ESP) bool false no_slow (NoSlow) bool false + +ignore_status_messages (IgnoreStatus) bool true + +mark_deathmessages (Deathmessages) bool true + +autosneak (AutoSneak) bool false + +autoeject (AutoEject) bool false + +eject_items (AutoEject Items) string + +autotool (AutoTool) bool false + +autorespawn (AutoRespawn) bool false + +next_item (NextItem) bool false + +scaffold (Scaffold) bool false + +scaffold_plus (ScaffoldPlus) bool false + +block_water (BlockWater) bool false + +autotnt (PlaceOnTop) bool false + +replace (Replace) bool false diff --git a/clientmods/dragonfire/autoeat/init.lua b/clientmods/dragonfire/autoeat/init.lua deleted file mode 100644 index 7435e1796..000000000 --- a/clientmods/dragonfire/autoeat/init.lua +++ /dev/null @@ -1,21 +0,0 @@ -autoeat = {} - -local last_step_eating = false - -minetest.register_on_damage_taken(function() - if not minetest.settings:get_bool("autoeat") then return end - local player = minetest.localplayer - player:set_wield_index(0) - minetest.place_node(player:get_pos()) - autoeat.eating = true -end) - -minetest.register_globalstep(function() - if last_step_eating then - autoeat.eating, last_step_eating = false, false - elseif autoeat.eating then - last_step_eating = true - end -end) - -minetest.register_cheat("AutoEat", "Player", "autoeat") diff --git a/clientmods/dragonfire/autoeat/mod.conf b/clientmods/dragonfire/autoeat/mod.conf deleted file mode 100644 index d6dc9dbc4..000000000 --- a/clientmods/dragonfire/autoeat/mod.conf +++ /dev/null @@ -1,3 +0,0 @@ -name = autoeat -description = Automatically eat when damage was taken, assuming that there is food in the first hotbar slot. This is only useful when used in combination with an afk bot, like schematicas. -author = Fleckenstein diff --git a/clientmods/dragonfire/autoeat/settingtypes.txt b/clientmods/dragonfire/autoeat/settingtypes.txt deleted file mode 100644 index 9614de087..000000000 --- a/clientmods/dragonfire/autoeat/settingtypes.txt +++ /dev/null @@ -1 +0,0 @@ -autoeat (Automatically eat when damage was taken) bool false diff --git a/clientmods/dragonfire/autosneak/init.lua b/clientmods/dragonfire/autosneak/init.lua deleted file mode 100644 index 953155173..000000000 --- a/clientmods/dragonfire/autosneak/init.lua +++ /dev/null @@ -1,13 +0,0 @@ -local was_enabled = false - -minetest.register_globalstep(function() - if minetest.settings:get_bool("autosneak") then - minetest.set_keypress("sneak", true) - was_enabled = true - elseif was_enabled then - was_enabled = false - minetest.set_keypress("sneak", false) - end -end) - -minetest.register_cheat("AutoSneak", "Movement", "autosneak") diff --git a/clientmods/dragonfire/autosneak/mod.conf b/clientmods/dragonfire/autosneak/mod.conf deleted file mode 100644 index f922455ff..000000000 --- a/clientmods/dragonfire/autosneak/mod.conf +++ /dev/null @@ -1,3 +0,0 @@ -name = autosneak -desciption = Adds the AutoSneak feature to dragonfire. -author = Fleckenstein diff --git a/clientmods/dragonfire/autosneak/settingtypes.txt b/clientmods/dragonfire/autosneak/settingtypes.txt deleted file mode 100644 index 787076970..000000000 --- a/clientmods/dragonfire/autosneak/settingtypes.txt +++ /dev/null @@ -1 +0,0 @@ -autosneak (AutoSneak) bool false diff --git a/clientmods/dragonfire/chat/colors.lua b/clientmods/dragonfire/chat/colors.lua deleted file mode 100644 index b3ab596fb..000000000 --- a/clientmods/dragonfire/chat/colors.lua +++ /dev/null @@ -1,33 +0,0 @@ -function chat.send(message) - local starts_with = message:sub(1, 1) - - if starts_with == "/" or starts_with == "." then return end - - local reverse = minetest.settings:get_bool("chat_reverse") - - if reverse then - local msg = "" - for i = 1, #message do - msg = message:sub(i, i) .. msg - end - message = msg - end - - local color = minetest.settings:get("chat_color") - - if color then - local msg - if color == "rainbow" then - msg = chat.rainbow(message) - else - msg = minetest.colorize(color, message) - end - message = msg - end - - minetest.send_chat_message(message) - return true -end - -minetest.register_on_sending_chat_message(chat.send) - diff --git a/clientmods/dragonfire/chat/init.lua b/clientmods/dragonfire/chat/init.lua deleted file mode 100644 index 5086bc570..000000000 --- a/clientmods/dragonfire/chat/init.lua +++ /dev/null @@ -1,13 +0,0 @@ -chat = {} - -local modname = minetest.get_current_modname() -local modpath = minetest.get_modpath(modname) - -chat.rainbow = dofile(modpath .. "/rainbow.lua") - -dofile(modpath .. "/colors.lua") -dofile(modpath .. "/spam.lua") -dofile(modpath .. "/status.lua") -dofile(modpath .. "/leak.lua") - - diff --git a/clientmods/dragonfire/chat/leak.lua b/clientmods/dragonfire/chat/leak.lua deleted file mode 100644 index b99ee0301..000000000 --- a/clientmods/dragonfire/chat/leak.lua +++ /dev/null @@ -1,12 +0,0 @@ -local etime = 0 - -minetest.register_globalstep(function(dtime) - if not minetest.settings:get_bool("leak") then return end - etime = etime + dtime - if etime < 5 then return end - etime = 0 - local player = minetest.localplayer - minetest.send_chat_message(minetest.pos_to_string(vector.floor(player:get_pos()))) -end) - -minetest.register_cheat("Leak", "Player", "leak") diff --git a/clientmods/dragonfire/chat/mod.conf b/clientmods/dragonfire/chat/mod.conf deleted file mode 100644 index ce94dc1bc..000000000 --- a/clientmods/dragonfire/chat/mod.conf +++ /dev/null @@ -1,3 +0,0 @@ -name = chat -author = Fleckenstein -description = The chat modifications of Dragonfireclient diff --git a/clientmods/dragonfire/chat/rainbow.lua b/clientmods/dragonfire/chat/rainbow.lua deleted file mode 100644 index 1519619a7..000000000 --- a/clientmods/dragonfire/chat/rainbow.lua +++ /dev/null @@ -1,61 +0,0 @@ -local function rgb_to_hex(rgb) - local hexadecimal = '#' - - for key, value in pairs(rgb) do - local hex = '' - - while(value > 0)do - local index = math.fmod(value, 16) + 1 - value = math.floor(value / 16) - hex = string.sub('0123456789ABCDEF', index, index) .. hex - end - - if(string.len(hex) == 0)then - hex = '00' - - elseif(string.len(hex) == 1)then - hex = '0' .. hex - end - - hexadecimal = hexadecimal .. hex - end - - return hexadecimal -end - -local function color_from_hue(hue) - local h = hue / 60 - local c = 255 - local x = (1 - math.abs(h%2 - 1)) * 255 - - local i = math.floor(h); - if (i == 0) then - return rgb_to_hex({c, x, 0}) - elseif (i == 1) then - return rgb_to_hex({x, c, 0}) - elseif (i == 2) then - return rgb_to_hex({0, c, x}) - elseif (i == 3) then - return rgb_to_hex({0, x, c}); - elseif (i == 4) then - return rgb_to_hex({x, 0, c}); - else - return rgb_to_hex({c, 0, x}); - end -end - -return function(input) - local step = 360 / input:len() - local hue = 0 - local output = "" - for i = 1, input:len() do - local char = input:sub(i,i) - if char:match("%s") then - output = output .. char - else - output = output .. minetest.get_color_escape_sequence(color_from_hue(hue)) .. char - end - hue = hue + step - end - return output -end diff --git a/clientmods/dragonfire/chat/settingtypes.txt b/clientmods/dragonfire/chat/settingtypes.txt deleted file mode 100644 index 9428dd955..000000000 --- a/clientmods/dragonfire/chat/settingtypes.txt +++ /dev/null @@ -1,6 +0,0 @@ -chat_color (Chat Color) string white -chat_reverse (Reverse Chat messages) bool false -chat_enable_spam (Spam Chat) bool false -chat_spam (Message to spam into Chat) string -ignore_status_messages (Ignore status messages from server) bool true -leak (Frequently leak your coordinates to chat) bool false diff --git a/clientmods/dragonfire/chat/spam.lua b/clientmods/dragonfire/chat/spam.lua deleted file mode 100644 index 991846d33..000000000 --- a/clientmods/dragonfire/chat/spam.lua +++ /dev/null @@ -1,12 +0,0 @@ -local etime = 0 - -minetest.register_globalstep(function(dtime) - etime = etime + dtime - if etime < 10/8 then return end - etime = 0 - local spam = minetest.settings:get("chat_spam") - local enable_spam = minetest.settings:get("chat_enable_spam") - if enable_spam and spam then - local _ = chat.send(spam) or minetest.send_chat_message(spam) - end -end) diff --git a/clientmods/dragonfire/chat/status.lua b/clientmods/dragonfire/chat/status.lua deleted file mode 100644 index ba79048f8..000000000 --- a/clientmods/dragonfire/chat/status.lua +++ /dev/null @@ -1,7 +0,0 @@ -minetest.register_on_receiving_chat_message(function(message) - if message:sub(1, 1) == "#" and minetest.settings:get_bool("ignore_status_messages") ~= false then - return true - end -end) - -minetest.register_cheat("IgnoreStatus", "Player", "ignore_status_messages") diff --git a/clientmods/dragonfire/commands/init.lua b/clientmods/dragonfire/commands/init.lua deleted file mode 100644 index 94c824f14..000000000 --- a/clientmods/dragonfire/commands/init.lua +++ /dev/null @@ -1,88 +0,0 @@ -minetest.register_chatcommand("say", { - description = "Send raw text", - func = function(text) - minetest.send_chat_message(text) - return true - end, -}) - -minetest.register_chatcommand("teleport", { - params = ",,", - description = "Teleport to relative coordinates.", - func = function(param) - local success, pos = minetest.parse_relative_pos(param) - if success then - minetest.localplayer:set_pos(pos) - return true, "Teleporting to " .. minetest.pos_to_string(pos) - end - return false, pos - end, -}) - -minetest.register_chatcommand("wielded", { - description = "Print itemstring of wieleded item", - func = function() - return true, minetest.localplayer:get_wielded_item():get_name() - end -}) - -minetest.register_chatcommand("disconnect", { - description = "Exit to main menu", - func = function(param) - minetest.disconnect() - end, -}) - -minetest.register_chatcommand("players", { - description = "List online players", - func = function(param) - return true, "Online players: " .. table.concat(minetest.get_player_names(), ", ") - end -}) - -minetest.register_chatcommand("kill", { - description = "Kill yourself", - func = function() - minetest.send_damage(minetest.localplayer:get_hp()) - end, -}) - -minetest.register_chatcommand("hop", { - description = "Hop", - func = function() - minetest.set_keypress("jump", true) - end, -}) - -minetest.register_chatcommand("set", { - params = "([-n] ) | ", - description = "Set or read client configuration setting", - func = function(param) - local arg, setname, setvalue = string.match(param, "(-[n]) ([^ ]+) (.+)") - if arg and arg == "-n" and setname and setvalue then - minetest.settings:set(setname, setvalue) - return true, setname .. " = " .. setvalue - end - - setname, setvalue = string.match(param, "([^ ]+) (.+)") - if setname and setvalue then - if not minetest.settings:get(setname) then - return false, "Failed. Use '.set -n ' to create a new setting." - end - minetest.settings:set(setname, setvalue) - return true, setname .. " = " .. setvalue - end - - setname = string.match(param, "([^ ]+)") - if setname then - setvalue = minetest.settings:get(setname) - if not setvalue then - setvalue = "" - end - return true, setname .. " = " .. setvalue - end - - return false, "Invalid parameters (see .help set)." - end, -}) - diff --git a/clientmods/dragonfire/commands/mod.conf b/clientmods/dragonfire/commands/mod.conf deleted file mode 100644 index 48f2d6f25..000000000 --- a/clientmods/dragonfire/commands/mod.conf +++ /dev/null @@ -1,3 +0,0 @@ -name = commands -author = Fleckenstein -description = Misc cheat commands diff --git a/clientmods/dragonfire/inventory/autoeject.lua b/clientmods/dragonfire/inventory/autoeject.lua deleted file mode 100644 index 5e61b1ef9..000000000 --- a/clientmods/dragonfire/inventory/autoeject.lua +++ /dev/null @@ -1,19 +0,0 @@ -minetest.register_globalstep(function() - if minetest.settings:get_bool("autoeject") then - local player = minetest.localplayer - local list = (minetest.settings:get("eject_items") or ""):split(",") - local inventory = minetest.get_inventory("current_player") - for index, stack in pairs(inventory.main) do - if table.indexof(list, stack:get_name()) ~= -1 then - local old_index = player:get_wield_index() - player:set_wield_index(index - 1) - minetest.drop_selected_item() - player:set_wield_index(old_index) - return - end - end - end -end) - -minetest.register_chatcommand("eject", list.new("Configure AutoEject", "eject_items")) -minetest.register_cheat("AutoEject", "Player", "autoeject") diff --git a/clientmods/dragonfire/inventory/autotool.lua b/clientmods/dragonfire/inventory/autotool.lua deleted file mode 100644 index 7df39823f..000000000 --- a/clientmods/dragonfire/inventory/autotool.lua +++ /dev/null @@ -1,36 +0,0 @@ -local function check_tool(stack, node_groups, old_best_time) - local toolcaps = stack:get_tool_capabilities() - if not toolcaps then return end - local best_time = old_best_time - for group, groupdef in pairs(toolcaps.groupcaps) do - local level = node_groups[group] - if level then - local this_time = groupdef.times[level] - if this_time < best_time then - best_time = this_time - end - end - end - return best_time < old_best_time, best_time -end - -minetest.register_on_punchnode(function(pos, node) - if not minetest.settings:get_bool("autotool") then return end - local player = minetest.localplayer - local inventory = minetest.get_inventory("current_player") - local node_groups = minetest.get_node_def(node.name).groups - local new_index = player:get_wield_index() - local is_better, best_time = false, math.huge - is_better, best_time = check_tool(player:get_wielded_item(), node_groups, best_time) - is_better, best_time = check_tool(inventory.hand[1], node_groups, best_time) - for index, stack in pairs(inventory.main) do - is_better, best_time = check_tool(stack, node_groups, best_time) - if is_better then - new_index = index - 1 - end - end - player:set_wield_index(new_index) -end) - -minetest.register_cheat("AutoTool", "Player", "autotool") - diff --git a/clientmods/dragonfire/inventory/enderchest.lua b/clientmods/dragonfire/inventory/enderchest.lua deleted file mode 100644 index 37a4c84f8..000000000 --- a/clientmods/dragonfire/inventory/enderchest.lua +++ /dev/null @@ -1,27 +0,0 @@ -function get_itemslot_bg(x, y, w, h) - local out = "" - for i = 0, w - 1, 1 do - for j = 0, h - 1, 1 do - out = out .."image["..x+i..","..y+j..";1,1;mcl_formspec_itemslot.png]" - end - end - return out -end - -local formspec = "size[9,8.75]".. - "label[0,0;"..minetest.formspec_escape(minetest.colorize("#313131", "Ender Chest")).."]".. - "list[current_player;enderchest;0,0.5;9,3;]".. - get_itemslot_bg(0,0.5,9,3).. - "label[0,4.0;"..minetest.formspec_escape(minetest.colorize("#313131", "Inventory")).."]".. - "list[current_player;main;0,4.5;9,3;9]".. - get_itemslot_bg(0,4.5,9,3).. - "list[current_player;main;0,7.74;9,1;]".. - get_itemslot_bg(0,7.74,9,1).. - "listring[current_player;enderchest]".. - "listring[current_player;main]" - -function minetest.open_special_inventory() - minetest.show_formspec("enderchest:enderchest", formspec) -end - -minetest.register_cheat("Enderchest", "Player", minetest.open_special_inventory) diff --git a/clientmods/dragonfire/inventory/hand.lua b/clientmods/dragonfire/inventory/hand.lua deleted file mode 100644 index 05ec66b5d..000000000 --- a/clientmods/dragonfire/inventory/hand.lua +++ /dev/null @@ -1,27 +0,0 @@ -function get_itemslot_bg(x, y, w, h) - local out = "" - for i = 0, w - 1, 1 do - for j = 0, h - 1, 1 do - out = out .."image["..x+i..","..y+j..";1,1;mcl_formspec_itemslot.png]" - end - end - return out -end - -local formspec = "size[9,8.75]".. - "label[0,0;"..minetest.formspec_escape(minetest.colorize("#313131", "Hand")).."]".. - "list[current_player;hand;0,0.5;1,1;]".. - get_itemslot_bg(0,0.5,1,1).. - "label[0,4.0;"..minetest.formspec_escape(minetest.colorize("#313131", "Inventory")).."]".. - "list[current_player;main;0,4.5;9,3;9]".. - get_itemslot_bg(0,4.5,9,3).. - "list[current_player;main;0,7.74;9,1;]".. - get_itemslot_bg(0,7.74,9,1).. - "listring[current_player;hand]".. - "listring[current_player;main]" - -local function hand() - minetest.show_formspec("inventory:hand", formspec) -end - -minetest.register_cheat("Hand", "Player", hand) diff --git a/clientmods/dragonfire/inventory/init.lua b/clientmods/dragonfire/inventory/init.lua deleted file mode 100644 index ce5ea401c..000000000 --- a/clientmods/dragonfire/inventory/init.lua +++ /dev/null @@ -1,11 +0,0 @@ -inventory_mod = {} - -local modname = minetest.get_current_modname() -local modpath = minetest.get_modpath(modname) - -dofile(modpath .. "/invhack.lua") -dofile(modpath .. "/enderchest.lua") -dofile(modpath .. "/hand.lua") -dofile(modpath .. "/next_item.lua") -dofile(modpath .. "/autotool.lua") -dofile(modpath .. "/autoeject.lua") diff --git a/clientmods/dragonfire/inventory/invhack.lua b/clientmods/dragonfire/inventory/invhack.lua deleted file mode 100644 index eb6f48bf2..000000000 --- a/clientmods/dragonfire/inventory/invhack.lua +++ /dev/null @@ -1,13 +0,0 @@ -minetest.register_chatcommand("invhack", { - func = function(player) - minetest.show_formspec( - "invhack:invhack", - "" - .. "size[8,7.5]" - .. "list[player:" .. player .. ";main;0,3.5;8,4;]" - .. "list[player:" .. player .. ";craft;3,0;3,3;]" - .. "list[player:" .. player .. ";craftpreview;7,1;1,1;]" - ) - end -}) - diff --git a/clientmods/dragonfire/inventory/mod.conf b/clientmods/dragonfire/inventory/mod.conf deleted file mode 100644 index 439319a43..000000000 --- a/clientmods/dragonfire/inventory/mod.conf +++ /dev/null @@ -1,4 +0,0 @@ -name = inventory -author = Fleckenstein -description = The inventory cheats for Dragonfireclient -dependencies = list diff --git a/clientmods/dragonfire/inventory/next_item.lua b/clientmods/dragonfire/inventory/next_item.lua deleted file mode 100644 index 64eb2b7ae..000000000 --- a/clientmods/dragonfire/inventory/next_item.lua +++ /dev/null @@ -1,18 +0,0 @@ -local elapsed_time = 0 -local tick_time = 0.05 - -minetest.register_globalstep(function(dtime) - elapsed_time = elapsed_time + dtime - if elapsed_time < tick_time then return end - local player = minetest.localplayer - if not player then return end - local item = player:get_wielded_item() - if item:get_count() == 0 and minetest.settings:get_bool("next_item") then - local index = player:get_wield_index() - player:set_wield_index(index + 1) - end - elapsed_time = 0 -end) - -minetest.register_cheat("NextItem", "Player", "next_item") - diff --git a/clientmods/dragonfire/inventory/settingtypes.txt b/clientmods/dragonfire/inventory/settingtypes.txt deleted file mode 100644 index 15030466d..000000000 --- a/clientmods/dragonfire/inventory/settingtypes.txt +++ /dev/null @@ -1,4 +0,0 @@ -next_item (NextItem) bool false -autotool (AutoTool) bool false -autoeject (AutoEject) bool false -eject_items (AutoEject Items) string diff --git a/clientmods/dragonfire/list/init.lua b/clientmods/dragonfire/list/init.lua deleted file mode 100644 index 71b9edaa4..000000000 --- a/clientmods/dragonfire/list/init.lua +++ /dev/null @@ -1,47 +0,0 @@ -list = {} - -function list.new(desc, setting) - local def = {} - def.description = desc - def.params = "del | add | list" - function def.func(param) - local list = (minetest.settings:get(setting) or ""):split(",") - if param == "list" then - return true, table.concat(list, ", ") - else - local sparam = param:split(" ") - local cmd = sparam[1] - local item = sparam[2] - if cmd == "del" then - if not item then - return false, "Missing item." - end - local i = table.indexof(list, item) - if i == -1 then - return false, item .. " is not on the list." - else - table.remove(list, i) - minetest.settings:set(setting, table.concat(list, ",")) - return true, "Removed " .. item .. " from the list." - end - elseif cmd == "add" then - if not item then - return false, "Missing item." - end - local i = table.indexof(list, item) - if i ~= -1 then - return false, item .. " is already on the list." - else - table.insert(list, item) - minetest.settings:set(setting, table.concat(list, ",")) - return true, "Added " .. item .. " to the list." - end - end - end - return false, "Invalid usage. (See /help )" - end - return def -end - -minetest.register_chatcommand("xray", list.new("Configure X-Ray", "xray_nodes")) ---minetest.register_chatcommand("Configure Search Nodes", "search_nodes") diff --git a/clientmods/dragonfire/modpack.txt b/clientmods/dragonfire/modpack.txt deleted file mode 100644 index 8d1c8b69c..000000000 --- a/clientmods/dragonfire/modpack.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/clientmods/dragonfire/pathfinding/init.lua b/clientmods/dragonfire/pathfinding/init.lua deleted file mode 100644 index a807faa4c..000000000 --- a/clientmods/dragonfire/pathfinding/init.lua +++ /dev/null @@ -1,109 +0,0 @@ -local positions, index, global_goal - -local function roundvec(v, d) - return vector.divide(vector.round(vector.multiply(v, d)), d) -end - -local function findpath(pos) - global_goal = pos - index = 2 - positions = minetest.find_path( - minetest.localplayer:get_pos(), - pos, - tonumber(minetest.settings:get("goto_max_distance") or 25), - tonumber(minetest.settings:get("goto_max_jump") or 1), - tonumber(minetest.settings:get("goto_max_drop") or minetest.settings:get_bool("prevent_natural_damage") and 1000 or 5) - ) -end - -minetest.register_chatcommand("goto", { - description = "Go to a position (use pathfinding).", - param = "", - func = function(param) - if positions then - return false, "Goto is still active. Use .gotoabort to abort it." - end - local success, pos = minetest.parse_pos(param) - if not success then - return false, pos - end - findpath(pos) - end, -}) - -minetest.register_chatcommand("gotoabort", { - description = "Abort goto.", - param = "", - func = function(param) - if not positions then - return false, "Goto is currently not running (and also not walking haha)" - end - minetest.set_keypress("forward", false) - minetest.set_keypress("sneak", false) - positions, index, global_goal = nil - return true, "Aborted." - end, -}) - -minetest.register_globalstep(function(dtime) - if positions then - minetest.set_keypress("forward", true) - minetest.set_keypress("sneak", false) - local player = minetest.localplayer - local pos = player:get_pos() - local goal, next_goal = positions[index], positions[index+1] - if not goal then - positions, index, global_goal = nil - minetest.set_keypress("forward", false) - minetest.display_chat_message("Reached goal.") - return - end - if next_goal then - local d, dn = vector.subtract(pos, goal), vector.subtract(next_goal, goal) - for k, v in pairs(dn) do - if v ~= 0 and k ~= "y" then - local cv = d[k] - if v > 0 and cv > 0 or v < 0 and cv < 0 then - index = index + 1 - goal = next_goal - end - break - end - end - end - local npos = vector.add(goal, {x = 0, y = 1, z = 0}) - local node = minetest.get_node_or_nil(npos) - if node and node.name ~= air then - minetest.dig_node(npos) - end - local velocity = player:get_velocity() - velocity.y = 0 - if vector.length(velocity) < 0.1 then - findpath(global_goal) - return - end - local distance = vector.distance(pos, goal) - if not next_goal and distance < 1 then - index = index + 1 - end - local direction = vector.direction(pos, vector.new(goal.x, 0, goal.z)) - local yaw = player:get_yaw() % 360 - local goal_yaw = math.deg(math.atan2(-direction.x, direction.z)) % 360 - local diff = math.abs(goal_yaw - yaw) - if diff > 175 and diff < 185 and distance < 1 then - index = index + 1 - elseif diff > 10 and diff < 350 then - if yaw < goal_yaw and diff < 180 or yaw > goal_yaw and diff > 180 then - yaw = yaw + 10 - elseif yaw < goal_yaw and diff > 180 or yaw > goal_yaw and diff < 180 then - yaw = yaw - 10 - end - if diff >= 90 and diff <= 270 then - minetest.set_keypress("sneak", true) - end - player:set_yaw(yaw) - else - player:set_yaw(goal_yaw) - end - end -end) diff --git a/clientmods/dragonfire/pathfinding/mod.conf b/clientmods/dragonfire/pathfinding/mod.conf deleted file mode 100644 index c5a9fe6f5..000000000 --- a/clientmods/dragonfire/pathfinding/mod.conf +++ /dev/null @@ -1,3 +0,0 @@ -name = pathfinding -description = Adds the .goto command! -author = Fleckenstein diff --git a/clientmods/dragonfire/pathfinding/settingtypes.txt b/clientmods/dragonfire/pathfinding/settingtypes.txt deleted file mode 100644 index 2e525ca82..000000000 --- a/clientmods/dragonfire/pathfinding/settingtypes.txt +++ /dev/null @@ -1,3 +0,0 @@ -goto_max_distance (Maximum distance from the search positions to search in) int 25 -goto_max_jump (Jump height) int 1 -goto_max_drop (Maximum drop height) int 5 diff --git a/clientmods/dragonfire/perlin/init.lua b/clientmods/dragonfire/perlin/init.lua deleted file mode 100644 index 4e05c973f..000000000 --- a/clientmods/dragonfire/perlin/init.lua +++ /dev/null @@ -1,33 +0,0 @@ -perlin = dofile(minetest.get_modpath("perlin") .. "/perlin.lua") - -local start, height, stretch - -minetest.register_chatcommand("perlin", { - description = "Start perlin terraforming", - param = " ", - func = function(param) - local sparam = param:split(" ") - start, height, stretch = math.floor(minetest.localplayer:get_pos().y), sparam[1], sparam[2] - end -}) - -minetest.register_chatcommand("perlinstop", { - description = "Abort perlin terraforming", - func = function(param) - start, height, stretch = nil - end -}) - -minetest.register_globalstep(function() - if start then - local player = minetest.localplayer - local pos = vector.floor(player:get_pos()) - for x = pos.x - 1, pos.x + 1 do - for z = pos.z - 1, pos.z + 1 do - local y = math.floor(start + height * perlin:noise(x / stretch, z / stretch)) - local p = vector.new(x, y, z) - minetest.place_node(p) - end - end - end -end) diff --git a/clientmods/dragonfire/perlin/mod.conf b/clientmods/dragonfire/perlin/mod.conf deleted file mode 100644 index 98b845285..000000000 --- a/clientmods/dragonfire/perlin/mod.conf +++ /dev/null @@ -1,3 +0,0 @@ -name = perlin -description = A bot that does terraforming automatically using perlin noise. -author = Fleckenstein diff --git a/clientmods/dragonfire/perlin/perlin.lua b/clientmods/dragonfire/perlin/perlin.lua deleted file mode 100644 index f1872e5c6..000000000 --- a/clientmods/dragonfire/perlin/perlin.lua +++ /dev/null @@ -1,144 +0,0 @@ ---[[ - Implemented as described here: - http://flafla2.github.io/2014/08/09/perlinnoise.html -]]-- - -local perlin = {} -perlin.p = {} - -local bit32 = {} -function bit32.band(a, b) - local result = 0 - local bitval = 1 - while a > 0 and b > 0 do - if a % 2 == 1 and b % 2 == 1 then -- test the rightmost bits - result = result + bitval -- set the current bit - end - bitval = bitval * 2 -- shift left - a = math.floor(a/2) -- shift right - b = math.floor(b/2) - end - return result -end - --- Hash lookup table as defined by Ken Perlin --- This is a randomly arranged array of all numbers from 0-255 inclusive -local permutation = {151,160,137,91,90,15, - 131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23, - 190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33, - 88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166, - 77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244, - 102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196, - 135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123, - 5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42, - 223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9, - 129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228, - 251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107, - 49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254, - 138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180 -} - --- p is used to hash unit cube coordinates to [0, 255] -for i=0,255 do - -- Convert to 0 based index table - perlin.p[i] = permutation[i+1] - -- Repeat the array to avoid buffer overflow in hash function - perlin.p[i+256] = permutation[i+1] -end - --- Return range: [-1, 1] -function perlin:noise(x, y, z) - y = y or 0 - z = z or 0 - - -- Calculate the "unit cube" that the point asked will be located in - local xi = bit32.band(math.floor(x),255) - local yi = bit32.band(math.floor(y),255) - local zi = bit32.band(math.floor(z),255) - - -- Next we calculate the location (from 0 to 1) in that cube - x = x - math.floor(x) - y = y - math.floor(y) - z = z - math.floor(z) - - -- We also fade the location to smooth the result - local u = self.fade(x) - local v = self.fade(y) - local w = self.fade(z) - - -- Hash all 8 unit cube coordinates surrounding input coordinate - local p = self.p - local A, AA, AB, AAA, ABA, AAB, ABB, B, BA, BB, BAA, BBA, BAB, BBB - A = p[xi ] + yi - AA = p[A ] + zi - AB = p[A+1 ] + zi - AAA = p[ AA ] - ABA = p[ AB ] - AAB = p[ AA+1 ] - ABB = p[ AB+1 ] - - B = p[xi+1] + yi - BA = p[B ] + zi - BB = p[B+1 ] + zi - BAA = p[ BA ] - BBA = p[ BB ] - BAB = p[ BA+1 ] - BBB = p[ BB+1 ] - - -- Take the weighted average between all 8 unit cube coordinates - return self.lerp(w, - self.lerp(v, - self.lerp(u, - self:grad(AAA,x,y,z), - self:grad(BAA,x-1,y,z) - ), - self.lerp(u, - self:grad(ABA,x,y-1,z), - self:grad(BBA,x-1,y-1,z) - ) - ), - self.lerp(v, - self.lerp(u, - self:grad(AAB,x,y,z-1), self:grad(BAB,x-1,y,z-1) - ), - self.lerp(u, - self:grad(ABB,x,y-1,z-1), self:grad(BBB,x-1,y-1,z-1) - ) - ) - ) -end - --- Gradient function finds dot product between pseudorandom gradient vector --- and the vector from input coordinate to a unit cube vertex -perlin.dot_product = { - [0x0]=function(x,y,z) return x + y end, - [0x1]=function(x,y,z) return -x + y end, - [0x2]=function(x,y,z) return x - y end, - [0x3]=function(x,y,z) return -x - y end, - [0x4]=function(x,y,z) return x + z end, - [0x5]=function(x,y,z) return -x + z end, - [0x6]=function(x,y,z) return x - z end, - [0x7]=function(x,y,z) return -x - z end, - [0x8]=function(x,y,z) return y + z end, - [0x9]=function(x,y,z) return -y + z end, - [0xA]=function(x,y,z) return y - z end, - [0xB]=function(x,y,z) return -y - z end, - [0xC]=function(x,y,z) return y + x end, - [0xD]=function(x,y,z) return -y + z end, - [0xE]=function(x,y,z) return y - x end, - [0xF]=function(x,y,z) return -y - z end -} -function perlin:grad(hash, x, y, z) - return self.dot_product[bit32.band(hash,0xF)](x,y,z) -end - --- Fade function is used to smooth final output -function perlin.fade(t) - return t * t * t * (t * (t * 6 - 15) + 10) -end - -function perlin.lerp(t, a, b) - return a + t * (b - a) -end - -return perlin diff --git a/clientmods/dragonfire/respawn/init.lua b/clientmods/dragonfire/respawn/init.lua deleted file mode 100644 index 7f4e473ea..000000000 --- a/clientmods/dragonfire/respawn/init.lua +++ /dev/null @@ -1,47 +0,0 @@ -local warp = warp or {set_here = function() return false end} - -local formspec = "" - .. "size[11,5.5]" - .. "bgcolor[#320000b4;true]" - .. "label[4.85,1.35;" .. "You died" .. "]" - .. "button_exit[2,3;3,0.5;btn_respawn;" .. "Respawn" .. "]" - .. "button_exit[6,3;3,0.5;btn_ghost_mode;" .. "Ghost Mode" .. "]" - .. "set_focus[btn_respawn;true]" - -minetest.register_on_death(function() - local warp_success, warp_msg = warp.set_here("death") - if warp_success then - minetest.display_chat_message(warp_msg) - else - minetest.display_chat_message("You died at " .. minetest.pos_to_string(minetest.localplayer:get_pos()) .. ".") - end - if minetest.settings:get_bool("autorespawn") then - minetest.send_respawn() - else - minetest.show_formspec("respawn:death", formspec) - end -end) - -minetest.register_on_formspec_input(function(formname, fields) - if formname == "respawn:death" then - if fields.btn_ghost_mode then - minetest.display_chat_message("You are in ghost mode. Use .respawn to Respawn.") - else - minetest.send_respawn() - end - end -end) - -minetest.register_chatcommand("respawn", { - description = "Respawn when in ghost mode", - func = function() - if minetest.localplayer:get_hp() == 0 then - minetest.send_respawn() - minetest.display_chat_message("Respawned.") - else - minetest.display_chat_message("You are not in ghost mode.") - end - end -}) - -minetest.register_cheat("AutoRespawn", "Player", "autorespawn") diff --git a/clientmods/dragonfire/respawn/mod.conf b/clientmods/dragonfire/respawn/mod.conf deleted file mode 100644 index 8f93a9576..000000000 --- a/clientmods/dragonfire/respawn/mod.conf +++ /dev/null @@ -1,4 +0,0 @@ -name = respawn -author = Fleckenstein -description = Extended respawn behaviour -optional_depends = warp diff --git a/clientmods/dragonfire/respawn/settingtypes.txt b/clientmods/dragonfire/respawn/settingtypes.txt deleted file mode 100644 index d20b8f1dd..000000000 --- a/clientmods/dragonfire/respawn/settingtypes.txt +++ /dev/null @@ -1 +0,0 @@ -autorespawn (AutoRespawn) bool false diff --git a/clientmods/dragonfire/schematicas/init.lua b/clientmods/dragonfire/schematicas/init.lua deleted file mode 100644 index 29e5d4539..000000000 --- a/clientmods/dragonfire/schematicas/init.lua +++ /dev/null @@ -1,204 +0,0 @@ -local autoeat = rawget(_G, "autoeat") or {} -local storage = minetest.get_mod_storage() -local pos1, pos2 -local min, max = math.min, math.max -local building, build_index, build_data, build_pos, just_placed_node, failed_count, out_of_blocks - -minetest.register_chatcommand("pos1", { - description = "Set schematicas position 1 at your current location", - func = function() - pos1 = vector.round(minetest.localplayer:get_pos()) - return true, "Position 1 set to " .. minetest.pos_to_string(pos1) - end -}) - -minetest.register_chatcommand("pos2", { - description = "Set schematicas position 2 at your current location", - func = function() - pos2 = vector.round(minetest.localplayer:get_pos()) - return true, "Position 2 set to " .. minetest.pos_to_string(pos2) - end -}) - - -minetest.register_chatcommand("schemesave", { - description = "Save a schematica", - param = "", - func = function(name) - if not pos1 or not pos2 then - return false, "Position 1 or 2 not set." - end - - local data = {} - - local lx, ly, lz, hx, hy, hz = min(pos1.x, pos2.x), min(pos1.y, pos2.y), min(pos1.z, pos2.z), max(pos1.x, pos2.x), max(pos1.y, pos2.y), max(pos1.z, pos2.z) - - for x = lx, hx do - local rx = x - lx - for y = ly, hy do - local ry = y - ly - for z = lz, hz do - local rz = z - lz - local node = minetest.get_node_or_nil({x = x, y = y, z = z}) - if node and node.name ~= "air" then - table.insert(data, {pos = {x = rx, y = ry, z = rz}, node = node.name}) - end - end - end - end - - storage:set_string(name, minetest.serialize(data)) - return true, "Scheme saved successfully as '" .. name .. "'." - end -}) - -minetest.register_chatcommand("schemebuild", { - description = "Build a schematica", - param = "", - func = function(name) - if not pos1 then - return false, "Position 1 not set." - end - if building then - return false, "Still building a scheme. Use .schemeabort to stop it." - end - local rawdata = storage:get(name) - if not rawdata then - return false, "Schematica '" .. name .. "' not found." - end - building, build_index, build_data, build_pos, just_placed_node, failed_count, out_of_blocks = true, 1, minetest.deserialize(rawdata), vector.new(pos1), false, 0, false - end -}) - -minetest.register_chatcommand("schemerecipe", { - description = "Print the recipe for a schematica", - param = "", - func = function(name) - local rawdata = storage:get(name) - if not rawdata then - return false, "Schematica '" .. name .. "' not found." - end - local data = minetest.deserialize(rawdata) - local sorted = {} - for _, d in ipairs(data) do - - end - end -}) - -minetest.register_chatcommand("schemeresume", { - description = "Resume constructing a schematica", - func = function() - if not build_data then - return false, "Currently not building a scheme." - end - building, out_of_blocks = true, false - return true, "Resumed." - end -}) - -minetest.register_chatcommand("schemepause", { - description = "Pause constructing a schematica", - func = function() - if not build_data then - return false, "Currently not building a scheme." - end - building = false - return true, "Paused." - end -}) - -minetest.register_chatcommand("schemeabort", { - description = "Abort constructing a schematica", - param = "", - func = function() - if not build_data then - return false, "Currently not building a scheme." - end - building, build_index, build_data, build_pos, just_placed_node, failed_count, out_of_blocks = nilw - return true, "Aborted." - end -}) - -minetest.register_chatcommand("schemeskip", { - description = "Skip a step in constructing a schematica", - param = "", - func = function() - if not build_data then - return false, "Currently not building a scheme." - end - building, build_index = true, build_index + 1 - return true, "Skipped." - end -}) - -minetest.register_chatcommand("schemegetindex", { - description = "Output the build index of the schematica", - func = function() - return build_index and true or false, build_index - end -}) - -minetest.register_chatcommand("schemesetindex", { - description = "Set the build index of the schematica", - param = "", - func = function(param) - local index = tonumber(param) - if not index then return false, "Invalid usage." end - build_index = index - return true, "Index Changed" - end -}) - -minetest.register_globalstep(function() - if building and not autoeat.eating then - local data = build_data[build_index] - if not data then - building, build_index, build_data, build_pos, just_placed_node, failed_count, out_of_blocks = nil - minetest.display_chat_message("Completed Schematica.") - return - end - local pos, node = vector.add(build_pos, data.pos), data.node - if just_placed_node then - local map_node = minetest.get_node_or_nil(pos) - if map_node and map_node.name == node then - build_index = build_index + 1 - just_placed_node = false - else - failed_count = failed_count + 1 - end - if failed_count < 10 then - return - end - end - failed_count = 0 - local new_index - local inventory = minetest.get_inventory("current_player").main - for index, stack in ipairs(inventory) do - if minetest.get_item_def(stack:get_name()).node_placement_prediction == node then - new_index = index - 1 - break - end - end - if not new_index then - if not out_of_blocks then - minetest.display_chat_message("Out of blocks for schematica. Missing ressource: '" .. node .. "'. It will resume as soon as you got it or use .schemeskip to skip it.") - minetest.send_chat_message("[Schematicas] Missing ressource: " .. node) - end - out_of_blocks = true - return - end - if out_of_blocks then - minetest.send_chat_message("[Schematicas] Resuming.") - end - out_of_blocks = false - minetest.localplayer:set_wield_index(new_index) - minetest.localplayer:set_pos(minetest.find_node_near(pos, 5, {"air", "ignore", "mcl_core:water_source", "mcl_core:water_flowing"}, false) or pos) - minetest.place_node(pos) - just_placed_node = true - if build_index % 250 == 0 then - minetest.send_chat_message("[Schematicas] " .. build_index .. " of " .. #build_data .. " blocks placed!") - end - end -end) - diff --git a/clientmods/dragonfire/schematicas/mod.conf b/clientmods/dragonfire/schematicas/mod.conf deleted file mode 100644 index 92eda8067..000000000 --- a/clientmods/dragonfire/schematicas/mod.conf +++ /dev/null @@ -1,4 +0,0 @@ -name = schematicas -description = Save structures and recreate them automatically in survival. -author = Fleckenstein -optional_depends = autoeat diff --git a/clientmods/dragonfire/warp/init.lua b/clientmods/dragonfire/warp/init.lua deleted file mode 100644 index 9eca73482..000000000 --- a/clientmods/dragonfire/warp/init.lua +++ /dev/null @@ -1,94 +0,0 @@ -warp = {} - -local storage = minetest.get_mod_storage() - -function warp.set(warp, pos) - if warp == "" or not pos then return false, "Missing parameter." end - local posstr = minetest.pos_to_string(pos) - storage:set_string(warp, posstr) - return true, "Warp " .. warp .. " set to " .. posstr .. "." -end - -function warp.set_here(param) - local success, message = warp.set(param, vector.round(minetest.localplayer:get_pos())) - return success, message -end - -function warp.get(param) - if param == "" then return false, "Missing parameter." end - local pos = storage:get_string(param) - if pos == "" then return false, "Warp " .. param .. " not set." end - return true, "Warp " .. param .. " is set to " .. pos .. ".", minetest.string_to_pos(pos) -end - -function warp.delete(param) - if param == "" then return false, "Missing parameter." end - storage:set_string(param, "") - return true, "Deleted warp " .. param .. "." -end - -minetest.register_chatcommand("setwarp", { - params = "", - description = "Set a warp to your current position.", - func = warp.set_here, -}) - -minetest.register_chatcommand("readwarp", { - params = "", - description = "Print the coordinates of a warp.", - func = warp.get, -}) - -minetest.register_chatcommand("deletewarp", { - params = "", - description = "Delete a warp.", - func = warp.delete, -}) - -minetest.register_chatcommand("listwarps", { - description = "List all warps.", - func = function() - local warps = storage:to_table().fields - local warplist = {} - for warp in pairs(warps) do - table.insert(warplist, warp) - end - if #warplist > 0 then - return true, table.concat(warplist, ", ") - else - return false, "No warps set." - end - end, -}) - -local function do_warp(param) - if param == "" then return false, "Missing parameter." end - local success, pos = minetest.parse_pos(param) - if not success then - local msg - success, msg, pos = warp.get(param) - if not success then - return false, msg - end - end - minetest.localplayer:set_pos(pos) - return true, "Warped to " .. minetest.pos_to_string(pos) -end - -minetest.register_chatcommand("warp", { - params = "|", - description = "Warp to a set warp or a position.", - func = do_warp -}) - -minetest.register_chatcommand("warpandexit", { - params = "|", - description = "Warp to a set warp or a position and exit.", - func = function(param) - local s, m = do_warp(param) - if s then - minetest.disconnect() - end - return s,m - end -}) diff --git a/clientmods/dragonfire/warp/mod.conf b/clientmods/dragonfire/warp/mod.conf deleted file mode 100644 index d014d7566..000000000 --- a/clientmods/dragonfire/warp/mod.conf +++ /dev/null @@ -1,3 +0,0 @@ -name = warp -author = Fleckenstein -description = Set custom warps and use the teleport exploit diff --git a/clientmods/dragonfire/world/init.lua b/clientmods/dragonfire/world/init.lua deleted file mode 100644 index e5e0e2609..000000000 --- a/clientmods/dragonfire/world/init.lua +++ /dev/null @@ -1,97 +0,0 @@ -minetest.register_chatcommand("findnodes", { - description = "Scan for one or multible nodes in a radius around you", - param = " [,...]", - func = function(param) - local radius = tonumber(param:split(" ")[1]) - local nodes = param:split(" ")[2]:split(",") - local pos = minetest.localplayer:get_pos() - local fpos = minetest.find_node_near(pos, radius, nodes, true) - if fpos then - return true, "Found " .. table.concat(nodes, " or ") .. " at " .. minetest.pos_to_string(fpos) - end - return false, "None of " .. table.concat(nodes, " or ") .. " found in a radius of " .. tostring(radius) - end, -}) - -minetest.register_chatcommand("place", { - params = ",,", - description = "Place wielded item", - func = function(param) - local success, pos = minetest.parse_relative_pos(param) - if success then - minetest.place_node(pos) - return true, "Node placed at " .. minetest.pos_to_string(pos) - end - return false, pos - end, -}) - -minetest.register_chatcommand("dig", { - params = ",,", - description = "Dig node", - func = function(param) - local success, pos = minetest.parse_relative_pos(param) - if success then - minetest.dig_node(pos) - return true, "Node at " .. minetest.pos_to_string(pos) .. " dug" - end - return false, pos - end, -}) - -minetest.register_on_dignode(function(pos) - if minetest.settings:get_bool("replace") then - minetest.after(0, minetest.place_node, pos) - end -end) - -local etime = 0 - -minetest.register_globalstep(function(dtime) - etime = etime + dtime - if etime < 1 then return end - local player = minetest.localplayer - if not player then return end - local pos = player:get_pos() - local item = player:get_wielded_item() - local def = minetest.get_item_def(item:get_name()) - local nodes_per_tick = tonumber(minetest.settings:get("nodes_per_tick")) or 8 - if item and item:get_count() > 0 and def and def.node_placement_prediction ~= "" then - if minetest.settings:get_bool("scaffold") then - minetest.place_node(vector.add(pos, {x = 0, y = -0.6, z = 0})) - elseif minetest.settings:get_bool("highway_z") then - local z = pos.z - local positions = { - {x = 0, y = 0, z = z}, - {x = 1, y = 0, z = z}, - {x = 2, y = 1, z = z}, - {x = -2, y = 1, z = z}, - {x = -2, y = 0, z = z}, - {x = -1, y = 0, z = z}, - {x = 2, y = 0, z = z} - } - for i, p in pairs(positions) do - if i > nodes_per_tick then break end - minetest.place_node(p) - end - elseif minetest.settings:get_bool("block_water") then - local positions = minetest.find_nodes_near(pos, 5, {"mcl_core:water_source", "mcl_core:water_floating"}, true) - for i, p in pairs(positions) do - if i > nodes_per_tick then break end - minetest.place_node(p) - end - elseif minetest.settings:get_bool("autotnt") then - local positions = minetest.find_nodes_near_under_air_except(pos, 5, item:get_name(), true) - for i, p in pairs(positions) do - if i > nodes_per_tick then break end - minetest.place_node(vector.add(p, {x = 0, y = 1, z = 0})) - end - end - end -end) - -minetest.register_cheat("Scaffold", "World", "scaffold") -minetest.register_cheat("HighwayZ", "World", "highway_z") -minetest.register_cheat("BlockWater", "World", "block_water") -minetest.register_cheat("AutoTNT", "World", "autotnt") -minetest.register_cheat("Replace", "World", "replace") diff --git a/clientmods/dragonfire/world/mod.conf b/clientmods/dragonfire/world/mod.conf deleted file mode 100644 index ddef2dc87..000000000 --- a/clientmods/dragonfire/world/mod.conf +++ /dev/null @@ -1,3 +0,0 @@ -name = world -desciption = Adds several world interaction bots to dragonfire. -author = Fleckenstein diff --git a/clientmods/dragonfire/world/settingtypes.txt b/clientmods/dragonfire/world/settingtypes.txt deleted file mode 100644 index 8178cc33b..000000000 --- a/clientmods/dragonfire/world/settingtypes.txt +++ /dev/null @@ -1,6 +0,0 @@ -scaffold (Scaffold) bool false -highway_z (HighwayZ) bool false -block_water (BlockWater) bool false -autotnt (AutoTNT) bool false -replace (Replace) bool false -nodes_per_tick (Number of nodes to place per tick) int 8 diff --git a/clientmods/mods.conf b/clientmods/mods.conf deleted file mode 100644 index c1a72da1f..000000000 --- a/clientmods/mods.conf +++ /dev/null @@ -1,12 +0,0 @@ -load_mod_warp = true -load_mod_world = true -load_mod_respawn = true -load_mod_inventory = true -load_mod_commands = true -load_mod_chat = true -load_mod_schematicas = true -load_mod_pathfinding = true -load_mod_autoeat = true -load_mod_perlin = true -load_mod_autosneak = true -load_mod_list = true diff --git a/doc/client_lua_api.txt b/doc/client_lua_api.txt index 85fe1005d..15c0b8a6a 100644 --- a/doc/client_lua_api.txt +++ b/doc/client_lua_api.txt @@ -1480,6 +1480,8 @@ The following functions provide escape sequences: `minetest.get_color_escape_sequence(color) .. message .. minetest.get_color_escape_sequence("#ffffff")` +* `minetest.rainbow(message)`: + * Rainbow colorizes the message. * `minetest.get_background_escape_sequence(color)` * `color` is a [ColorString](#colorstring) * The escape sequence sets the background of the whole text element to diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 353ccc924..27286cd4d 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -2988,6 +2988,8 @@ The following functions provide escape sequences: `minetest.get_color_escape_sequence(color) .. message .. minetest.get_color_escape_sequence("#ffffff")` +* `minetest.rainbow(message)`: + * Rainbow colorizes the message. * `minetest.get_background_escape_sequence(color)` * `color` is a ColorString * The escape sequence sets the background of the whole text element to diff --git a/src/client/game.cpp b/src/client/game.cpp index d983e2bb8..663fbd672 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -1071,8 +1071,8 @@ void Game::processKeyInput() toggleAutoforward(); } else if (wasKeyDown(KeyType::INVENTORY)) { openInventory(); - } else if (wasKeyDown(KeyType::SPECIAL_INVENTORY)) { - openSpecialInventory(); + } else if (wasKeyDown(KeyType::ENDERCHEST)) { + openEnderchest(); } else if (input->cancelPressed()) { #ifdef __ANDROID__ m_android_chat_open = false; @@ -1105,6 +1105,10 @@ void Game::processKeyInput() toggleKillaura(); } else if (wasKeyDown(KeyType::FREECAM)) { toggleFreecam(); + } else if (wasKeyDown(KeyType::SCAFFOLD)) { + toggleScaffold(); + } else if (wasKeyDown(KeyType::NEXT_ITEM)) { + toggleNextItem(); } else if (wasKeyDown(KeyType::SELECT_UP)) { m_cheat_menu->selectUp(); } else if (wasKeyDown(KeyType::SELECT_DOWN)) { @@ -1285,7 +1289,7 @@ void Game::openInventory() } } -void Game::openSpecialInventory() +void Game::openEnderchest() { LocalPlayer *player = client->getEnv().getLocalPlayer(); if (!player || !player->getCAO()) @@ -1294,7 +1298,7 @@ void Game::openSpecialInventory() infostream << "Game: Launching special inventory" << std::endl; if (client->modsLoaded()) - client->getScript()->open_special_inventory(); + client->getScript()->open_enderchest(); } @@ -1428,6 +1432,30 @@ void Game::toggleFreecam() } } +void Game::toggleScaffold() +{ + bool scaffold = ! g_settings->getBool("scaffold"); + g_settings->set("scaffold", bool_to_cstr(scaffold)); + + if (scaffold) { + m_game_ui->showTranslatedStatusText("Scaffold enabled"); + } else { + m_game_ui->showTranslatedStatusText("Scaffold disabled"); + } +} + +void Game::toggleNextItem() +{ + bool next_item = ! g_settings->getBool("next_item"); + g_settings->set("next_item", bool_to_cstr(next_item)); + + if (next_item) { + m_game_ui->showTranslatedStatusText("NextItem enabled"); + } else { + m_game_ui->showTranslatedStatusText("NextItem disabled"); + } +} + void Game::toggleCinematic() { bool cinematic = !g_settings->getBool("cinematic"); @@ -3432,7 +3460,7 @@ void Game::showPauseMenu() "- %s: sneak/go down\n" "- %s: drop item\n" "- %s: inventory\n" - "- %s: special inventory\n" + "- %s: enderchest\n" "- Mouse: turn/look\n" "- Mouse left: dig/punch\n" "- Mouse right: place/use\n" @@ -3440,6 +3468,8 @@ void Game::showPauseMenu() "- %s: chat\n" "- %s: Killaura\n" "- %s: Freecam\n" + "- %s: Scaffold\n" + "- %s: NextItem\n" ); char control_text_buf[600]; @@ -3453,10 +3483,12 @@ void Game::showPauseMenu() GET_KEY_NAME(keymap_sneak), GET_KEY_NAME(keymap_drop), GET_KEY_NAME(keymap_inventory), - GET_KEY_NAME(keymap_special_inventory), + GET_KEY_NAME(keymap_enderchest), GET_KEY_NAME(keymap_chat), GET_KEY_NAME(keymap_toggle_killaura), - GET_KEY_NAME(keymap_toggle_freecam) + GET_KEY_NAME(keymap_toggle_freecam), + GET_KEY_NAME(keymap_toggle_scaffold), + GET_KEY_NAME(keymap_toggle_next_item) ); std::string control_text = std::string(control_text_buf); diff --git a/src/client/game.h b/src/client/game.h index a2a1e7c2d..b8efa3a73 100644 --- a/src/client/game.h +++ b/src/client/game.h @@ -724,7 +724,7 @@ public: void dropSelectedItem(bool single_item = false); void openInventory(); - void openSpecialInventory(); + void openEnderchest(); void openConsole(float scale, const wchar_t *line=NULL); void toggleFreeMove(); void toggleFreeMoveAlt(); @@ -733,6 +733,8 @@ public: void toggleNoClip(); void toggleKillaura(); void toggleFreecam(); + void toggleScaffold(); + void toggleNextItem(); void toggleCinematic(); void toggleAutoforward(); diff --git a/src/client/inputhandler.cpp b/src/client/inputhandler.cpp index edf2056d0..418e41931 100644 --- a/src/client/inputhandler.cpp +++ b/src/client/inputhandler.cpp @@ -42,7 +42,7 @@ void KeyCache::populate() key[KeyType::DROP] = getKeySetting("keymap_drop"); key[KeyType::INVENTORY] = getKeySetting("keymap_inventory"); - key[KeyType::SPECIAL_INVENTORY] = getKeySetting("keymap_special_inventory"); + key[KeyType::ENDERCHEST] = getKeySetting("keymap_enderchest"); key[KeyType::CHAT] = getKeySetting("keymap_chat"); key[KeyType::CMD] = getKeySetting("keymap_cmd"); key[KeyType::CMD_LOCAL] = getKeySetting("keymap_cmd_local"); @@ -75,6 +75,8 @@ void KeyCache::populate() key[KeyType::ZOOM] = getKeySetting("keymap_zoom"); key[KeyType::KILLAURA] = getKeySetting("keymap_toggle_killaura"); key[KeyType::FREECAM] = getKeySetting("keymap_toggle_freecam"); + key[KeyType::SCAFFOLD] = getKeySetting("keymap_toggle_scaffold"); + key[KeyType::NEXT_ITEM] = getKeySetting("keymap_toggle_next_item"); key[KeyType::SELECT_UP] = getKeySetting("keymap_select_up"); key[KeyType::SELECT_DOWN] = getKeySetting("keymap_select_down"); key[KeyType::SELECT_LEFT] = getKeySetting("keymap_select_left"); diff --git a/src/client/keys.h b/src/client/keys.h index b29e232fd..43a032a7b 100644 --- a/src/client/keys.h +++ b/src/client/keys.h @@ -41,7 +41,7 @@ public: // Other DROP, INVENTORY, - SPECIAL_INVENTORY, + ENDERCHEST, CHAT, CMD, CMD_LOCAL, @@ -72,6 +72,8 @@ public: ZOOM, KILLAURA, FREECAM, + SCAFFOLD, + NEXT_ITEM, SELECT_UP, SELECT_DOWN, SELECT_LEFT, diff --git a/src/client/render/core.cpp b/src/client/render/core.cpp index 45fc0d8ec..223af5142 100644 --- a/src/client/render/core.cpp +++ b/src/client/render/core.cpp @@ -69,7 +69,7 @@ void RenderingCore::draw(video::SColor _skycolor, bool _show_hud, bool _show_min draw_crosshair = _draw_crosshair; draw_tracers = _draw_tracers; draw_esp = _draw_esp; - + beforeDraw(); drawAll(); } diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 5e23ffc39..96c4e0688 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -95,7 +95,20 @@ void set_default_settings(Settings *settings) settings->setDefault("enable_esp", "false"); settings->setDefault("no_slow", "false"); settings->setDefault("float_above_parent", "false"); - + settings->setDefault("ignore_status_messages", "true"); + settings->setDefault("mark_deathmessages", "true"); + settings->setDefault("autosneak", "false"); + settings->setDefault("autoeject", "false"); + settings->setDefault("eject_items", ""); + settings->setDefault("autotool", "false"); + settings->setDefault("autorespawn", "false"); + settings->setDefault("next_item", "false"); + settings->setDefault("scaffold", "false"); + settings->setDefault("scaffold_plus", "false"); + settings->setDefault("block_water", "false"); + settings->setDefault("autotnt", "false"); + settings->setDefault("replace", "false"); + // Keymap settings->setDefault("remote_port", "30000"); settings->setDefault("keymap_forward", "KEY_KEY_W"); @@ -108,7 +121,7 @@ void set_default_settings(Settings *settings) settings->setDefault("keymap_drop", "KEY_KEY_Q"); settings->setDefault("keymap_zoom", "KEY_KEY_Z"); settings->setDefault("keymap_inventory", "KEY_KEY_I"); - settings->setDefault("keymap_special_inventory", "KEY_KEY_O"); + settings->setDefault("keymap_enderchest", "KEY_KEY_O"); settings->setDefault("keymap_special1", "KEY_KEY_E"); settings->setDefault("keymap_chat", "KEY_KEY_T"); settings->setDefault("keymap_cmd", "/"); @@ -143,6 +156,8 @@ void set_default_settings(Settings *settings) settings->setDefault("keymap_decrease_viewing_range_min", "-"); settings->setDefault("keymap_toggle_killaura", "KEY_KEY_X"); settings->setDefault("keymap_toggle_freecam", "KEY_KEY_G"); + settings->setDefault("keymap_toggle_scaffold", "KEY_KEY_Y"); + settings->setDefault("keymap_toggle_next_item", "KEY_KEY_U"); settings->setDefault("keymap_select_up", "KEY_UP"); settings->setDefault("keymap_select_down", "KEY_DOWN"); settings->setDefault("keymap_select_left", "KEY_LEFT"); diff --git a/src/gui/guiKeyChangeMenu.cpp b/src/gui/guiKeyChangeMenu.cpp index 1ad09389e..94fe0074a 100644 --- a/src/gui/guiKeyChangeMenu.cpp +++ b/src/gui/guiKeyChangeMenu.cpp @@ -59,7 +59,7 @@ enum GUI_ID_KEY_SNEAK_BUTTON, GUI_ID_KEY_DROP_BUTTON, GUI_ID_KEY_INVENTORY_BUTTON, - GUI_ID_KEY_SPECIAL_INVENTORY_BUTTON, + GUI_ID_KEY_ENDERCHEST_BUTTON, GUI_ID_KEY_HOTBAR_PREV_BUTTON, GUI_ID_KEY_HOTBAR_NEXT_BUTTON, GUI_ID_KEY_MUTE_BUTTON, @@ -79,6 +79,8 @@ enum GUI_ID_KEY_AUTOFWD_BUTTON, GUI_ID_KEY_KILLAURA_BUTTON, GUI_ID_KEY_FREECAM_BUTTON, + GUI_ID_KEY_SCAFFOLD_BUTTON, + GUI_ID_KEY_NEXT_ITEM_BUTTON, GUI_ID_KEY_SELECT_UP_BUTTON, GUI_ID_KEY_SELECT_DOWN_BUTTON, GUI_ID_KEY_SELECT_LEFT_BUTTON, @@ -430,7 +432,7 @@ void GUIKeyChangeMenu::init_keys() this->add_key(GUI_ID_KEY_SNEAK_BUTTON, wgettext("Sneak"), "keymap_sneak"); this->add_key(GUI_ID_KEY_DROP_BUTTON, wgettext("Drop"), "keymap_drop"); this->add_key(GUI_ID_KEY_INVENTORY_BUTTON, wgettext("Inventory"), "keymap_inventory"); - this->add_key(GUI_ID_KEY_SPECIAL_INVENTORY_BUTTON,wgettext("Special Inv."),"keymap_special_inventory"); + this->add_key(GUI_ID_KEY_ENDERCHEST_BUTTON,wgettext("Enderchest"), "keymap_enderchest"); this->add_key(GUI_ID_KEY_HOTBAR_PREV_BUTTON,wgettext("Prev. item"), "keymap_hotbar_previous"); this->add_key(GUI_ID_KEY_HOTBAR_NEXT_BUTTON,wgettext("Next item"), "keymap_hotbar_next"); this->add_key(GUI_ID_KEY_ZOOM_BUTTON, wgettext("Zoom"), "keymap_zoom"); @@ -456,8 +458,10 @@ void GUIKeyChangeMenu::init_keys() this->add_key(GUI_ID_KEY_CHATLOG_BUTTON, wgettext("Toggle chat log"), "keymap_toggle_chat"); this->add_key(GUI_ID_KEY_FOG_BUTTON, wgettext("Toggle fog"), "keymap_toggle_fog"); this->add_key(GUI_ID_KEY_CHEAT_MENU_BUTTON,wgettext("Toggle C. Menu"),"keymap_toggle_cheat_menu"); - this->add_key(GUI_ID_KEY_KILLAURA_BUTTON, wgettext("Toggle Killaura"), "keymap_toggle_killaura"); - this->add_key(GUI_ID_KEY_FREECAM_BUTTON, wgettext("Toggle Freec."), "keymap_toggle_freecam"); + this->add_key(GUI_ID_KEY_KILLAURA_BUTTON, wgettext("Killaura"), "keymap_toggle_killaura"); + this->add_key(GUI_ID_KEY_FREECAM_BUTTON, wgettext("Freecam"), "keymap_toggle_freecam"); + this->add_key(GUI_ID_KEY_SCAFFOLD_BUTTON, wgettext("Scaffold"), "keymap_toggle_scaffold"); + this->add_key(GUI_ID_KEY_NEXT_ITEM_BUTTON, wgettext("NextItem"), "keymap_toggle_next_item"); this->add_key(GUI_ID_KEY_SELECT_UP_BUTTON, wgettext("C. Menu Up"), "keymap_select_up"); this->add_key(GUI_ID_KEY_SELECT_DOWN_BUTTON,wgettext("C. Menu Down"), "keymap_select_down"); this->add_key(GUI_ID_KEY_SELECT_LEFT_BUTTON,wgettext("C. Menu Left"), "keymap_select_left"); diff --git a/src/script/cpp_api/s_client.cpp b/src/script/cpp_api/s_client.cpp index 0d4e21876..dd9019d4d 100644 --- a/src/script/cpp_api/s_client.cpp +++ b/src/script/cpp_api/s_client.cpp @@ -234,7 +234,7 @@ bool ScriptApiClient::on_inventory_open(Inventory *inventory) return readParam(L, -1); } -void ScriptApiClient::open_special_inventory() +void ScriptApiClient::open_enderchest() { SCRIPTAPI_PRECHECKHEADER @@ -243,7 +243,7 @@ void ScriptApiClient::open_special_inventory() lua_insert(L, error_handler); lua_getglobal(L, "core"); - lua_getfield(L, -1, "open_special_inventory"); + lua_getfield(L, -1, "open_enderchest"); if (lua_isfunction(L, -1)) lua_pcall(L, 0, 0, error_handler); } diff --git a/src/script/cpp_api/s_client.h b/src/script/cpp_api/s_client.h index 4cdcb42eb..177dce3ea 100644 --- a/src/script/cpp_api/s_client.h +++ b/src/script/cpp_api/s_client.h @@ -59,7 +59,7 @@ public: bool on_item_use(const ItemStack &item, const PointedThing &pointed); bool on_inventory_open(Inventory *inventory); - void open_special_inventory(); + void open_enderchest(); void setEnv(ClientEnvironment *env); }; From ed22260822086f84016aa8384c3174bfc6d1739d Mon Sep 17 00:00:00 2001 From: Lars Date: Mon, 12 Oct 2020 13:29:31 -0700 Subject: [PATCH 118/266] Remove all bump mapping and parallax occlusion related code. --- builtin/mainmenu/tab_settings.lua | 30 ++---- builtin/settingtypes.txt | 25 ----- .../shaders/nodes_shader/opengl_fragment.glsl | 99 ------------------- .../shaders/nodes_shader/opengl_vertex.glsl | 45 +-------- .../object_shader/opengl_fragment.glsl | 54 ---------- .../shaders/object_shader/opengl_vertex.glsl | 5 - doc/lua_api.txt | 40 +------- src/client/game.cpp | 10 +- src/client/mapblock_mesh.cpp | 41 ++------ src/client/mapblock_mesh.h | 5 +- src/client/mesh_generator_thread.cpp | 6 +- src/client/mesh_generator_thread.h | 1 - src/client/shader.cpp | 29 ------ src/client/wieldmesh.cpp | 2 +- src/defaultsettings.cpp | 6 -- src/nodedef.cpp | 9 -- src/nodedef.h | 1 - 17 files changed, 24 insertions(+), 384 deletions(-) diff --git a/builtin/mainmenu/tab_settings.lua b/builtin/mainmenu/tab_settings.lua index 510346f8d..8a7445394 100644 --- a/builtin/mainmenu/tab_settings.lua +++ b/builtin/mainmenu/tab_settings.lua @@ -187,31 +187,23 @@ local function formspec(tabview, name, tabdata) if shaders_enabled then tab_string = tab_string .. - "checkbox[8.25,0.5;cb_bumpmapping;" .. fgettext("Bump Mapping") .. ";" - .. dump(core.settings:get_bool("enable_bumpmapping")) .. "]" .. - "checkbox[8.25,1;cb_tonemapping;" .. fgettext("Tone Mapping") .. ";" + "checkbox[8.25,0.5;cb_tonemapping;" .. fgettext("Tone Mapping") .. ";" .. dump(core.settings:get_bool("tone_mapping")) .. "]" .. - "checkbox[8.25,1.5;cb_parallax;" .. fgettext("Parallax Occlusion") .. ";" - .. dump(core.settings:get_bool("enable_parallax_occlusion")) .. "]" .. - "checkbox[8.25,2;cb_waving_water;" .. fgettext("Waving Liquids") .. ";" + "checkbox[8.25,1;cb_waving_water;" .. fgettext("Waving Liquids") .. ";" .. dump(core.settings:get_bool("enable_waving_water")) .. "]" .. - "checkbox[8.25,2.5;cb_waving_leaves;" .. fgettext("Waving Leaves") .. ";" + "checkbox[8.25,1.5;cb_waving_leaves;" .. fgettext("Waving Leaves") .. ";" .. dump(core.settings:get_bool("enable_waving_leaves")) .. "]" .. - "checkbox[8.25,3;cb_waving_plants;" .. fgettext("Waving Plants") .. ";" + "checkbox[8.25,2;cb_waving_plants;" .. fgettext("Waving Plants") .. ";" .. dump(core.settings:get_bool("enable_waving_plants")) .. "]" else tab_string = tab_string .. "label[8.38,0.7;" .. core.colorize("#888888", - fgettext("Bump Mapping")) .. "]" .. - "label[8.38,1.2;" .. core.colorize("#888888", fgettext("Tone Mapping")) .. "]" .. - "label[8.38,1.7;" .. core.colorize("#888888", - fgettext("Parallax Occlusion")) .. "]" .. - "label[8.38,2.2;" .. core.colorize("#888888", + "label[8.38,1.2;" .. core.colorize("#888888", fgettext("Waving Liquids")) .. "]" .. - "label[8.38,2.7;" .. core.colorize("#888888", + "label[8.38,1.7;" .. core.colorize("#888888", fgettext("Waving Leaves")) .. "]" .. - "label[8.38,3.2;" .. core.colorize("#888888", + "label[8.38,2.2;" .. core.colorize("#888888", fgettext("Waving Plants")) .. "]" end @@ -263,18 +255,10 @@ local function handle_settings_buttons(this, fields, tabname, tabdata) end return true end - if fields["cb_bumpmapping"] then - core.settings:set("enable_bumpmapping", fields["cb_bumpmapping"]) - return true - end if fields["cb_tonemapping"] then core.settings:set("tone_mapping", fields["cb_tonemapping"]) return true end - if fields["cb_parallax"] then - core.settings:set("enable_parallax_occlusion", fields["cb_parallax"]) - return true - end if fields["cb_waving_water"] then core.settings:set("enable_waving_water", fields["cb_waving_water"]) return true diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 1c28470d5..27f375693 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -546,31 +546,6 @@ shader_path (Shader path) path # enhanced, highlights and shadows are gradually compressed. tone_mapping (Filmic tone mapping) bool false -[***Bumpmapping] - -# Enables bumpmapping for textures. Normalmaps need to be supplied by the texture pack. -# Requires shaders to be enabled. -enable_bumpmapping (Bumpmapping) bool false - -[***Parallax Occlusion] - -# Enables parallax occlusion mapping. -# Requires shaders to be enabled. -enable_parallax_occlusion (Parallax occlusion) bool false - -# 0 = parallax occlusion with slope information (faster). -# 1 = relief mapping (slower, more accurate). -parallax_occlusion_mode (Parallax occlusion mode) int 1 0 1 - -# Number of parallax occlusion iterations. -parallax_occlusion_iterations (Parallax occlusion iterations) int 4 - -# Overall scale of parallax occlusion effect. -parallax_occlusion_scale (Parallax occlusion scale) float 0.08 - -# Overall bias of parallax occlusion effect, usually scale/2. -parallax_occlusion_bias (Parallax occlusion bias) float 0.04 - [***Waving Nodes] # Set to true to enable waving liquids (like water). diff --git a/client/shaders/nodes_shader/opengl_fragment.glsl b/client/shaders/nodes_shader/opengl_fragment.glsl index 437b325d3..36d47d1f5 100644 --- a/client/shaders/nodes_shader/opengl_fragment.glsl +++ b/client/shaders/nodes_shader/opengl_fragment.glsl @@ -1,6 +1,4 @@ uniform sampler2D baseTexture; -uniform sampler2D normalTexture; -uniform sampler2D textureFlags; uniform vec4 skyBgColor; uniform float fogDistance; @@ -17,17 +15,9 @@ varying vec3 vPosition; // cameraOffset + worldPosition (for large coordinates the limits of float // precision must be considered). varying vec3 worldPosition; -varying float area_enable_parallax; varying vec3 eyeVec; -varying vec3 tsEyeVec; -varying vec3 lightVec; -varying vec3 tsLightVec; -bool normalTexturePresent = false; - -const float e = 2.718281828459; -const float BS = 10.0; const float fogStart = FOG_START; const float fogShadingParameter = 1 / ( 1 - fogStart); @@ -63,87 +53,10 @@ vec4 applyToneMapping(vec4 color) } #endif -void get_texture_flags() -{ - vec4 flags = texture2D(textureFlags, vec2(0.0, 0.0)); - if (flags.r > 0.5) { - normalTexturePresent = true; - } -} - -vec4 get_normal_map(vec2 uv) -{ - vec4 bump = texture2D(normalTexture, uv).rgba; - bump.xyz = normalize(bump.xyz * 2.0 - 1.0); - return bump; -} - -float find_intersection(vec2 dp, vec2 ds) -{ - float depth = 1.0; - float best_depth = 0.0; - float size = 0.0625; - for (int i = 0; i < 15; i++) { - depth -= size; - float h = texture2D(normalTexture, dp + ds * depth).a; - if (depth <= h) { - best_depth = depth; - break; - } - } - depth = best_depth; - for (int i = 0; i < 4; i++) { - size *= 0.5; - float h = texture2D(normalTexture,dp + ds * depth).a; - if (depth <= h) { - best_depth = depth; - depth += size; - } else { - depth -= size; - } - } - return best_depth; -} - void main(void) { vec3 color; - vec4 bump; vec2 uv = gl_TexCoord[0].st; - bool use_normalmap = false; - get_texture_flags(); - -#ifdef ENABLE_PARALLAX_OCCLUSION - vec2 eyeRay = vec2 (tsEyeVec.x, -tsEyeVec.y); - const float scale = PARALLAX_OCCLUSION_SCALE / PARALLAX_OCCLUSION_ITERATIONS; - const float bias = PARALLAX_OCCLUSION_BIAS / PARALLAX_OCCLUSION_ITERATIONS; - -#if PARALLAX_OCCLUSION_MODE == 0 - // Parallax occlusion with slope information - if (normalTexturePresent && area_enable_parallax > 0.0) { - for (int i = 0; i < PARALLAX_OCCLUSION_ITERATIONS; i++) { - vec4 normal = texture2D(normalTexture, uv.xy); - float h = normal.a * scale - bias; - uv += h * normal.z * eyeRay; - } - } -#endif -#if PARALLAX_OCCLUSION_MODE == 1 - // Relief mapping - if (normalTexturePresent && area_enable_parallax > 0.0) { - vec2 ds = eyeRay * PARALLAX_OCCLUSION_SCALE; - float dist = find_intersection(uv, ds); - uv += dist * ds; - } -#endif -#endif - -#if USE_NORMALMAPS == 1 - if (normalTexturePresent) { - bump = get_normal_map(uv); - use_normalmap = true; - } -#endif vec4 base = texture2D(baseTexture, uv).rgba; @@ -155,19 +68,7 @@ void main(void) } #endif -#ifdef ENABLE_BUMPMAPPING - if (use_normalmap) { - vec3 L = normalize(lightVec); - vec3 E = normalize(eyeVec); - float specular = pow(clamp(dot(reflect(L, bump.xyz), E), 0.0, 1.0), 1.0); - float diffuse = dot(-E,bump.xyz); - color = (diffuse + 0.1 * specular) * base.rgb; - } else { - color = base.rgb; - } -#else color = base.rgb; -#endif vec4 col = vec4(color.rgb * gl_Color.rgb, 1.0); diff --git a/client/shaders/nodes_shader/opengl_vertex.glsl b/client/shaders/nodes_shader/opengl_vertex.glsl index 0d8d0a2a5..56bff09a8 100644 --- a/client/shaders/nodes_shader/opengl_vertex.glsl +++ b/client/shaders/nodes_shader/opengl_vertex.glsl @@ -18,10 +18,6 @@ varying vec3 vPosition; varying vec3 worldPosition; varying vec3 eyeVec; -varying vec3 lightVec; -varying vec3 tsEyeVec; -varying vec3 tsLightVec; -varying float area_enable_parallax; // Color of the light emitted by the light sources. const vec3 artificialLight = vec3(1.04, 1.04, 1.04); @@ -86,21 +82,9 @@ float snoise(vec3 p) void main(void) { gl_TexCoord[0] = gl_MultiTexCoord0; - //TODO: make offset depending on view angle and parallax uv displacement - //thats for textures that doesnt align vertically, like dirt with grass - //gl_TexCoord[0].y += 0.008; - //Allow parallax/relief mapping only for certain kind of nodes - //Variable is also used to control area of the effect -#if (DRAW_TYPE == NDT_NORMAL || DRAW_TYPE == NDT_LIQUID || DRAW_TYPE == NDT_FLOWINGLIQUID) - area_enable_parallax = 1.0; -#else - area_enable_parallax = 0.0; -#endif - - -float disp_x; -float disp_z; + float disp_x; + float disp_z; // OpenGL < 4.3 does not support continued preprocessor lines #if (MATERIAL_TYPE == TILE_MATERIAL_WAVING_LEAVES && ENABLE_WAVING_LEAVES) || (MATERIAL_TYPE == TILE_MATERIAL_WAVING_PLANTS && ENABLE_WAVING_PLANTS) vec4 pos2 = mWorld * gl_Vertex; @@ -148,32 +132,7 @@ float disp_z; vPosition = gl_Position.xyz; - // Don't generate heightmaps when too far from the eye - float dist = distance (vec3(0.0, 0.0, 0.0), vPosition); - if (dist > 150.0) { - area_enable_parallax = 0.0; - } - - vec3 sunPosition = vec3 (0.0, eyePosition.y * BS + 900.0, 0.0); - - vec3 normal, tangent, binormal; - normal = normalize(gl_NormalMatrix * gl_Normal); - tangent = normalize(gl_NormalMatrix * gl_MultiTexCoord1.xyz); - binormal = normalize(gl_NormalMatrix * gl_MultiTexCoord2.xyz); - - vec3 v; - - lightVec = sunPosition - worldPosition; - v.x = dot(lightVec, tangent); - v.y = dot(lightVec, binormal); - v.z = dot(lightVec, normal); - tsLightVec = normalize (v); - eyeVec = -(gl_ModelViewMatrix * gl_Vertex).xyz; - v.x = dot(eyeVec, tangent); - v.y = dot(eyeVec, binormal); - v.z = dot(eyeVec, normal); - tsEyeVec = normalize (v); // Calculate color. // Red, green and blue components are pre-multiplied with diff --git a/client/shaders/object_shader/opengl_fragment.glsl b/client/shaders/object_shader/opengl_fragment.glsl index a8a43e1e2..86d5c1c92 100644 --- a/client/shaders/object_shader/opengl_fragment.glsl +++ b/client/shaders/object_shader/opengl_fragment.glsl @@ -1,6 +1,4 @@ uniform sampler2D baseTexture; -uniform sampler2D normalTexture; -uniform sampler2D textureFlags; uniform vec4 emissiveColor; uniform vec4 skyBgColor; @@ -12,14 +10,8 @@ varying vec3 vPosition; varying vec3 worldPosition; varying vec3 eyeVec; -varying vec3 lightVec; varying float vIDiff; -bool normalTexturePresent = false; -bool texTileableHorizontal = false; -bool texTileableVertical = false; -bool texSeamless = false; - const float e = 2.718281828459; const float BS = 10.0; const float fogStart = FOG_START; @@ -57,44 +49,10 @@ vec4 applyToneMapping(vec4 color) } #endif -void get_texture_flags() -{ - vec4 flags = texture2D(textureFlags, vec2(0.0, 0.0)); - if (flags.r > 0.5) { - normalTexturePresent = true; - } - if (flags.g > 0.5) { - texTileableHorizontal = true; - } - if (flags.b > 0.5) { - texTileableVertical = true; - } - if (texTileableHorizontal && texTileableVertical) { - texSeamless = true; - } -} - -vec4 get_normal_map(vec2 uv) -{ - vec4 bump = texture2D(normalTexture, uv).rgba; - bump.xyz = normalize(bump.xyz * 2.0 - 1.0); - return bump; -} - void main(void) { vec3 color; - vec4 bump; vec2 uv = gl_TexCoord[0].st; - bool use_normalmap = false; - get_texture_flags(); - -#if USE_NORMALMAPS == 1 - if (normalTexturePresent) { - bump = get_normal_map(uv); - use_normalmap = true; - } -#endif vec4 base = texture2D(baseTexture, uv).rgba; @@ -106,19 +64,7 @@ void main(void) } #endif -#ifdef ENABLE_BUMPMAPPING - if (use_normalmap) { - vec3 L = normalize(lightVec); - vec3 E = normalize(eyeVec); - float specular = pow(clamp(dot(reflect(L, bump.xyz), E), 0.0, 1.0), 1.0); - float diffuse = dot(-E,bump.xyz); - color = (diffuse + 0.1 * specular) * base.rgb; - } else { - color = base.rgb; - } -#else color = base.rgb; -#endif vec4 col = vec4(color.rgb, base.a); diff --git a/client/shaders/object_shader/opengl_vertex.glsl b/client/shaders/object_shader/opengl_vertex.glsl index 968a07e22..f8c1cd932 100644 --- a/client/shaders/object_shader/opengl_vertex.glsl +++ b/client/shaders/object_shader/opengl_vertex.glsl @@ -9,7 +9,6 @@ varying vec3 vPosition; varying vec3 worldPosition; varying vec3 eyeVec; -varying vec3 lightVec; varying float vIDiff; const float e = 2.718281828459; @@ -33,10 +32,6 @@ void main(void) vPosition = gl_Position.xyz; vNormal = gl_Normal; worldPosition = (mWorld * gl_Vertex).xyz; - - vec3 sunPosition = vec3 (0.0, eyePosition.y * BS + 900.0, 0.0); - - lightVec = sunPosition - worldPosition; eyeVec = -(gl_ModelViewMatrix * gl_Vertex).xyz; #if (MATERIAL_TYPE == TILE_MATERIAL_PLAIN) || (MATERIAL_TYPE == TILE_MATERIAL_PLAIN_ALPHA) diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 1631d564c..d3aaa309c 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -401,35 +401,6 @@ stripping out the file extension: * e.g. `foomod_foothing` -Normalmap Textures ------------------- - -If shaders and bumpmapping or parallax occlusion is enabled, Minetest tries -to load normalmaps. -Those image files have to end with `_normal.png` and start with the same name -as their corresponding texture. -For example a normalmap for `foomod_foothing.png` has to be called -`foomod_foothing_normal.png`. - -The sRGB R, G and B colour values of a normalmap pixel are each directly -mapped from `{0, ..., 255}` to `[-1, 1]` and, taken together, -define the normal vector. -The alpha channel defines the heightmap for parallax occlusion. -To be safe, the alpha values should always be bigger than zero -because the colour values, which define the normal vector, -may be undefined for image formats where colour is discarded in fully -transparent pixels. - -Bumpmapping and parallax occlusion are currently experimental features: - -* Bumpmapping in Minetest happens in an obscure way; there are no light sources - defined in the shaders except the sunlight direction. -* Parallax occlusion with relief-mapping mode does not yet work correctly - together with Minetest's Fastfaces. -* The normalmap files must end with `.png`, so other image files are not - supported. - - Texture modifiers ----------------- @@ -834,7 +805,7 @@ Example (colored grass block): -- Overlay tiles: define them in the same style -- The top and bottom tile does not have overlay overlay_tiles = {"", "", - {name = "default_grass_side.png", tileable_vertical = false}}, + {name = "default_grass_side.png"}}, -- Global color, used in inventory color = "green", -- Palette in the world @@ -1204,7 +1175,7 @@ Look for examples in `games/devtest` or `games/minetest_game`. base cube without affecting them. * The base cube texture tiles are defined as normal, the `plantlike` extension uses the defined special tile, for example: - `special_tiles = {{name = "default_papyrus.png", tileable_vertical = true}},` + `special_tiles = {{name = "default_papyrus.png"}},` `*_optional` drawtypes need less rendering time if deactivated (always client-side). @@ -7043,13 +7014,8 @@ Tile definition * `"image.png"` * `{name="image.png", animation={Tile Animation definition}}` -* `{name="image.png", backface_culling=bool, tileable_vertical=bool, - tileable_horizontal=bool, align_style="node"/"world"/"user", scale=int}` +* `{name="image.png", backface_culling=bool, align_style="node"/"world"/"user", scale=int}` * backface culling enabled by default for most nodes - * tileable flags are info for shaders, how they should treat texture - when displacement mapping is used. - Directions are from the point of view of the tile texture, - not the node it's on. * align style determines whether the texture will be rotated with the node or kept aligned with its surroundings. "user" means that client setting will be used, similar to `glasslike_framed_optional`. diff --git a/src/client/game.cpp b/src/client/game.cpp index 54e0c9f20..309a8e194 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -430,8 +430,6 @@ class GameGlobalShaderConstantSetter : public IShaderConstantSetter CachedPixelShaderSetting m_camera_offset_pixel; CachedPixelShaderSetting m_camera_offset_vertex; CachedPixelShaderSetting m_base_texture; - CachedPixelShaderSetting m_normal_texture; - CachedPixelShaderSetting m_texture_flags; Client *m_client; public: @@ -464,8 +462,6 @@ public: m_camera_offset_pixel("cameraOffset"), m_camera_offset_vertex("cameraOffset"), m_base_texture("baseTexture"), - m_normal_texture("normalTexture"), - m_texture_flags("textureFlags"), m_client(client) { g_settings->registerChangedCallback("enable_fog", settingsCallback, this); @@ -553,12 +549,8 @@ public: m_camera_offset_pixel.set(camera_offset_array, services); m_camera_offset_vertex.set(camera_offset_array, services); - SamplerLayer_t base_tex = 0, - normal_tex = 1, - flags_tex = 2; + SamplerLayer_t base_tex = 0; m_base_texture.set(&base_tex, services); - m_normal_texture.set(&normal_tex, services); - m_texture_flags.set(&flags_tex, services); } }; diff --git a/src/client/mapblock_mesh.cpp b/src/client/mapblock_mesh.cpp index c7790f1e4..6a59fabe3 100644 --- a/src/client/mapblock_mesh.cpp +++ b/src/client/mapblock_mesh.cpp @@ -35,11 +35,9 @@ with this program; if not, write to the Free Software Foundation, Inc., MeshMakeData */ -MeshMakeData::MeshMakeData(Client *client, bool use_shaders, - bool use_tangent_vertices): +MeshMakeData::MeshMakeData(Client *client, bool use_shaders): m_client(client), - m_use_shaders(use_shaders), - m_use_tangent_vertices(use_tangent_vertices) + m_use_shaders(use_shaders) {} void MeshMakeData::fillBlockDataBegin(const v3s16 &blockpos) @@ -1016,7 +1014,6 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset): for (auto &m : m_mesh) m = new scene::SMesh(); m_enable_shaders = data->m_use_shaders; - m_use_tangent_vertices = data->m_use_tangent_vertices; m_enable_vbo = g_settings->getBool("enable_vbo"); if (data->m_client->getMinimap()) { @@ -1170,28 +1167,12 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset): scene::SMesh *mesh = (scene::SMesh *)m_mesh[layer]; - // Create meshbuffer, add to mesh - if (m_use_tangent_vertices) { - scene::SMeshBufferTangents *buf = - new scene::SMeshBufferTangents(); - buf->Material = material; - buf->Vertices.reallocate(p.vertices.size()); - buf->Indices.reallocate(p.indices.size()); - for (const video::S3DVertex &v: p.vertices) - buf->Vertices.push_back(video::S3DVertexTangents(v.Pos, v.Color, v.TCoords)); - for (u16 i: p.indices) - buf->Indices.push_back(i); - buf->recalculateBoundingBox(); - mesh->addMeshBuffer(buf); - buf->drop(); - } else { - scene::SMeshBuffer *buf = new scene::SMeshBuffer(); - buf->Material = material; - buf->append(&p.vertices[0], p.vertices.size(), - &p.indices[0], p.indices.size()); - mesh->addMeshBuffer(buf); - buf->drop(); - } + scene::SMeshBuffer *buf = new scene::SMeshBuffer(); + buf->Material = material; + buf->append(&p.vertices[0], p.vertices.size(), + &p.indices[0], p.indices.size()); + mesh->addMeshBuffer(buf); + buf->drop(); } /* @@ -1201,12 +1182,6 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset): translateMesh(m_mesh[layer], intToFloat(data->m_blockpos * MAP_BLOCKSIZE - camera_offset, BS)); - if (m_use_tangent_vertices) { - scene::IMeshManipulator* meshmanip = - RenderingEngine::get_scene_manager()->getMeshManipulator(); - meshmanip->recalculateTangents(m_mesh[layer], true, false, false); - } - if (m_mesh[layer]) { #if 0 // Usually 1-700 faces and 1-7 materials diff --git a/src/client/mapblock_mesh.h b/src/client/mapblock_mesh.h index f8aed40dc..6a9c00ed5 100644 --- a/src/client/mapblock_mesh.h +++ b/src/client/mapblock_mesh.h @@ -45,10 +45,8 @@ struct MeshMakeData Client *m_client; bool m_use_shaders; - bool m_use_tangent_vertices; - MeshMakeData(Client *client, bool use_shaders, - bool use_tangent_vertices = false); + MeshMakeData(Client *client, bool use_shaders); /* Copy block data manually (to allow optimizations by the caller) @@ -136,7 +134,6 @@ private: IShaderSource *m_shdrsrc; bool m_enable_shaders; - bool m_use_tangent_vertices; bool m_enable_vbo; // Must animate() be called before rendering? diff --git a/src/client/mesh_generator_thread.cpp b/src/client/mesh_generator_thread.cpp index 53b980eeb..c8d1cba26 100644 --- a/src/client/mesh_generator_thread.cpp +++ b/src/client/mesh_generator_thread.cpp @@ -52,9 +52,6 @@ MeshUpdateQueue::MeshUpdateQueue(Client *client): m_client(client) { m_cache_enable_shaders = g_settings->getBool("enable_shaders"); - m_cache_use_tangent_vertices = m_cache_enable_shaders && ( - g_settings->getBool("enable_bumpmapping") || - g_settings->getBool("enable_parallax_occlusion")); m_cache_smooth_lighting = g_settings->getBool("smooth_lighting"); m_meshgen_block_cache_size = g_settings->getS32("meshgen_block_cache_size"); } @@ -207,8 +204,7 @@ CachedMapBlockData* MeshUpdateQueue::getCachedBlock(const v3s16 &p) void MeshUpdateQueue::fillDataFromMapBlockCache(QueuedMeshUpdate *q) { - MeshMakeData *data = new MeshMakeData(m_client, m_cache_enable_shaders, - m_cache_use_tangent_vertices); + MeshMakeData *data = new MeshMakeData(m_client, m_cache_enable_shaders); q->data = data; data->fillBlockDataBegin(q->p); diff --git a/src/client/mesh_generator_thread.h b/src/client/mesh_generator_thread.h index 9a42852a3..f3c5e7da8 100644 --- a/src/client/mesh_generator_thread.h +++ b/src/client/mesh_generator_thread.h @@ -88,7 +88,6 @@ private: // TODO: Add callback to update these when g_settings changes bool m_cache_enable_shaders; - bool m_cache_use_tangent_vertices; bool m_cache_smooth_lighting; int m_meshgen_block_cache_size; diff --git a/src/client/shader.cpp b/src/client/shader.cpp index c5fe5dfe0..e2eeb4ab0 100644 --- a/src/client/shader.cpp +++ b/src/client/shader.cpp @@ -688,35 +688,6 @@ ShaderInfo generate_shader(const std::string &name, u8 material_type, u8 drawtyp shaders_header += itos(drawtype); shaders_header += "\n"; - if (g_settings->getBool("enable_bumpmapping")) - shaders_header += "#define ENABLE_BUMPMAPPING\n"; - - if (g_settings->getBool("enable_parallax_occlusion")){ - int mode = g_settings->getFloat("parallax_occlusion_mode"); - float scale = g_settings->getFloat("parallax_occlusion_scale"); - float bias = g_settings->getFloat("parallax_occlusion_bias"); - int iterations = g_settings->getFloat("parallax_occlusion_iterations"); - shaders_header += "#define ENABLE_PARALLAX_OCCLUSION\n"; - shaders_header += "#define PARALLAX_OCCLUSION_MODE "; - shaders_header += itos(mode); - shaders_header += "\n"; - shaders_header += "#define PARALLAX_OCCLUSION_SCALE "; - shaders_header += ftos(scale); - shaders_header += "\n"; - shaders_header += "#define PARALLAX_OCCLUSION_BIAS "; - shaders_header += ftos(bias); - shaders_header += "\n"; - shaders_header += "#define PARALLAX_OCCLUSION_ITERATIONS "; - shaders_header += itos(iterations); - shaders_header += "\n"; - } - - shaders_header += "#define USE_NORMALMAPS "; - if (g_settings->getBool("enable_bumpmapping") || g_settings->getBool("enable_parallax_occlusion")) - shaders_header += "1\n"; - else - shaders_header += "0\n"; - if (g_settings->getBool("enable_waving_water")){ shaders_header += "#define ENABLE_WAVING_WATER 1\n"; shaders_header += "#define WATER_WAVE_HEIGHT "; diff --git a/src/client/wieldmesh.cpp b/src/client/wieldmesh.cpp index 285cc38e5..ad583210a 100644 --- a/src/client/wieldmesh.cpp +++ b/src/client/wieldmesh.cpp @@ -305,7 +305,7 @@ void WieldMeshSceneNode::setExtruded(const std::string &imagename, scene::SMesh *createSpecialNodeMesh(Client *client, content_t id, std::vector *colors, const ContentFeatures &f) { - MeshMakeData mesh_make_data(client, false, false); + MeshMakeData mesh_make_data(client, false); MeshCollector collector; mesh_make_data.setSmoothLighting(false); MapblockMeshGenerator gen(&mesh_make_data, &collector); diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 8f5ed3e17..6c7d4be97 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -251,12 +251,6 @@ void set_default_settings(Settings *settings) settings->setDefault("bilinear_filter", "false"); settings->setDefault("trilinear_filter", "false"); settings->setDefault("tone_mapping", "false"); - settings->setDefault("enable_bumpmapping", "false"); - settings->setDefault("enable_parallax_occlusion", "false"); - settings->setDefault("parallax_occlusion_mode", "1"); - settings->setDefault("parallax_occlusion_iterations", "4"); - settings->setDefault("parallax_occlusion_scale", "0.08"); - settings->setDefault("parallax_occlusion_bias", "0.04"); settings->setDefault("enable_waving_water", "false"); settings->setDefault("water_wave_height", "1.0"); settings->setDefault("water_wave_length", "20.0"); diff --git a/src/nodedef.cpp b/src/nodedef.cpp index 3a5934cf3..5c2e5cd09 100644 --- a/src/nodedef.cpp +++ b/src/nodedef.cpp @@ -266,9 +266,6 @@ void TextureSettings::readSettings() { connected_glass = g_settings->getBool("connected_glass"); opaque_water = g_settings->getBool("opaque_water"); - bool enable_shaders = g_settings->getBool("enable_shaders"); - bool enable_bumpmapping = g_settings->getBool("enable_bumpmapping"); - bool enable_parallax_occlusion = g_settings->getBool("enable_parallax_occlusion"); bool smooth_lighting = g_settings->getBool("smooth_lighting"); enable_mesh_cache = g_settings->getBool("enable_mesh_cache"); enable_minimap = g_settings->getBool("enable_minimap"); @@ -281,8 +278,6 @@ void TextureSettings::readSettings() if (smooth_lighting) enable_mesh_cache = false; - use_normal_texture = enable_shaders && - (enable_bumpmapping || enable_parallax_occlusion); if (leaves_style_str == "fancy") { leaves_style = LEAVES_FANCY; } else if (leaves_style_str == "simple") { @@ -635,10 +630,6 @@ static void fillTileAttribs(ITextureSource *tsrc, TileLayer *layer, if (!tile.world_aligned) layer->scale = 1; - // Normal texture and shader flags texture - if (tsettings.use_normal_texture) { - layer->normal_texture = tsrc->getNormalTexture(tiledef.name); - } layer->flags_texture = tsrc->getShaderFlagsTexture(layer->normal_texture ? true : false); // Material flags diff --git a/src/nodedef.h b/src/nodedef.h index 71c56bda9..66c21cc07 100644 --- a/src/nodedef.h +++ b/src/nodedef.h @@ -158,7 +158,6 @@ public: int node_texture_size; bool opaque_water; bool connected_glass; - bool use_normal_texture; bool enable_mesh_cache; bool enable_minimap; From 738f62421872c0be5fcd483ad4573e5d879e7418 Mon Sep 17 00:00:00 2001 From: Lars Date: Thu, 15 Oct 2020 00:06:37 -0700 Subject: [PATCH 119/266] Periodically release all mesh HW buffers to avoid an Irrlicht bottleneck. --- src/client/game.cpp | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/client/game.cpp b/src/client/game.cpp index 309a8e194..a7e367ae2 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -909,6 +909,7 @@ private: bool m_does_lost_focus_pause_game = false; + int m_reset_HW_buffer_counter = 0; #ifdef __ANDROID__ bool m_cache_hold_aux1; bool m_android_chat_open; @@ -3686,7 +3687,6 @@ void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos, camera->setDigging(0); // Dig animation } - void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, const CameraOrientation &cam) { @@ -3937,6 +3937,27 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, /* End scene */ + if (++m_reset_HW_buffer_counter > 500) { + /* + Periodically remove all mesh HW buffers. + + Work around for a quirk in Irrlicht where a HW buffer is only + released after 20000 iterations (triggered from endScene()). + + Without this, all loaded but unused meshes will retain their HW + buffers for at least 5 minutes, at which point looking up the HW buffers + becomes a bottleneck and the framerate drops (as much as 30%). + + Tests showed that numbers between 50 and 1000 are good, so picked 500. + There are no other public Irrlicht APIs that allow interacting with the + HW buffers without tracking the status of every individual mesh. + + The HW buffers for _visible_ meshes will be reinitialized in the next frame. + */ + infostream << "Game::updateFrame(): Removing all HW buffers." << std::endl; + driver->removeAllHardwareBuffers(); + m_reset_HW_buffer_counter = 0; + } driver->endScene(); stats->drawtime = tt_draw.stop(true); From 847198edb6fb906165bf0b2b9518d0e6a0677722 Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein Date: Sun, 18 Oct 2020 12:06:11 +0200 Subject: [PATCH 120/266] Edited .gitignore properly; fixed armor invulnarability in the server code. --- .gitignore | 2 ++ clientmods/mods_here.txt | 4 ++++ src/server/player_sao.cpp | 2 +- 3 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 clientmods/mods_here.txt diff --git a/.gitignore b/.gitignore index 78b047f32..bfb815bbf 100644 --- a/.gitignore +++ b/.gitignore @@ -53,6 +53,8 @@ build/.cmake/ /worlds /world/ /client/mod_storage/ +/clientmods/* +!/clientmods/mods_here.txt ## Configuration/log files minetest.conf diff --git a/clientmods/mods_here.txt b/clientmods/mods_here.txt new file mode 100644 index 000000000..ea2175f2d --- /dev/null +++ b/clientmods/mods_here.txt @@ -0,0 +1,4 @@ +You can install Minetest or Dragonfire clientmods by copying (and extracting) them into this folder. +To enable them write + load_mod_ = true +in mods.conf in this directory. diff --git a/src/server/player_sao.cpp b/src/server/player_sao.cpp index 67efed210..3d9f08bfa 100644 --- a/src/server/player_sao.cpp +++ b/src/server/player_sao.cpp @@ -459,7 +459,7 @@ void PlayerSAO::setHP(s32 hp, const PlayerHPChangeReason &reason) { s32 oldhp = m_hp; - hp = rangelim(hp, 0, m_prop.hp_max); + //hp = rangelim(hp, 0, m_prop.hp_max); if (oldhp != hp) { s32 hp_change = m_env->getScriptIface()->on_player_hpchange(this, hp - oldhp, reason); From 3b596a96e07bf8f799be6c21a546355980e34b94 Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein Date: Sun, 18 Oct 2020 12:18:22 +0200 Subject: [PATCH 121/266] Fixed Github build problems --- src/script/cpp_api/s_base.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/script/cpp_api/s_base.h b/src/script/cpp_api/s_base.h index e2ef1f874..36331ad37 100644 --- a/src/script/cpp_api/s_base.h +++ b/src/script/cpp_api/s_base.h @@ -164,7 +164,9 @@ private: lua_State *m_luastack = nullptr; IGameDef *m_gamedef = nullptr; +#ifndef SERVER Game *m_game = nullptr; +#endif Environment *m_environment = nullptr; #ifndef SERVER GUIEngine *m_guiengine = nullptr; From 3a718f12b433ef3980c8fc6e29957595110cd8f4 Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein Date: Sun, 18 Oct 2020 12:54:08 +0200 Subject: [PATCH 122/266] Make lint happy; Remove stupid redirector --- src/activeobjectmgr.h | 4 +-- src/client/render/core.h | 5 +-- src/gui/cheatMenu.cpp | 58 +++++++++++++++++++++------------ src/gui/cheatMenu.h | 29 +++++++++-------- src/network/networkprotocol.h | 10 +----- src/network/serveropcodes.cpp | 1 - src/script/cpp_api/s_cheats.cpp | 31 +++++++++--------- src/script/lua_api/l_client.h | 11 ++++--- src/script/lua_api/l_server.cpp | 19 ----------- src/script/lua_api/l_server.h | 3 -- src/server.cpp | 12 ------- src/server.h | 2 -- 12 files changed, 81 insertions(+), 104 deletions(-) diff --git a/src/activeobjectmgr.h b/src/activeobjectmgr.h index 8c4630c79..5139d61dd 100644 --- a/src/activeobjectmgr.h +++ b/src/activeobjectmgr.h @@ -41,12 +41,12 @@ public: m_active_objects.find(id); return (n != m_active_objects.end() ? n->second : nullptr); } - + std::unordered_map getAllActiveObjects() const { return m_active_objects; } - + protected: u16 getFreeId() const { diff --git a/src/client/render/core.h b/src/client/render/core.h index 609892416..90b81b060 100644 --- a/src/client/render/core.h +++ b/src/client/render/core.h @@ -48,7 +48,7 @@ protected: Camera *camera; Minimap *mapper; Hud *hud; - + void updateScreenSize(); virtual void initTextures() {} virtual void clearTextures() {} @@ -72,7 +72,8 @@ public: void initialize(); void draw(video::SColor _skycolor, bool _show_hud, bool _show_minimap, - bool _draw_wield_tool, bool _draw_crosshair, bool _draw_tracers, bool _draw_esp); + bool _draw_wield_tool, bool _draw_crosshair, bool _draw_tracers, + bool _draw_esp); inline v2u32 getVirtualSize() const { return virtual_size; } }; diff --git a/src/gui/cheatMenu.cpp b/src/gui/cheatMenu.cpp index 5707ed696..b22b8da30 100644 --- a/src/gui/cheatMenu.cpp +++ b/src/gui/cheatMenu.cpp @@ -15,15 +15,14 @@ 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. -*/ - +*/ + #include "cheatMenu.h" #include "script/scripting_client.h" #include "client/client.h" #include "client/fontengine.h" -CheatMenu::CheatMenu(Client *client): - m_client(client) +CheatMenu::CheatMenu(Client *client) : m_client(client) { m_font = g_fontengine->getFont(FONT_SIZE_UNSPECIFIED, FM_Fallback); @@ -38,7 +37,8 @@ CheatMenu::CheatMenu(Client *client): m_fontsize.Y = MYMAX(m_fontsize.Y, 1); } -void CheatMenu::drawEntry(video::IVideoDriver* driver, std::string name, int number, bool selected, bool active, CheatMenuEntryType entry_type) +void CheatMenu::drawEntry(video::IVideoDriver* driver, std::string name, int number, + bool selected, bool active, CheatMenuEntryType entry_type) { int x = m_gap, y = m_gap, width = m_entry_width, height = m_entry_height; video::SColor *bgcolor = &m_bg_color, *fontcolor = &m_font_color; @@ -47,7 +47,9 @@ void CheatMenu::drawEntry(video::IVideoDriver* driver, std::string name, int num height = m_head_height; } else { bool is_category = entry_type == CHEAT_MENU_ENTRY_TYPE_CATEGORY; - y += m_gap + m_head_height + (number + (is_category ? 0 : m_selected_category)) * (m_entry_height + m_gap); + y += m_gap + m_head_height + + (number + (is_category ? 0 : m_selected_category)) * + (m_entry_height + m_gap); x += (is_category ? 0 : m_gap + m_entry_width); if (active) bgcolor = &m_active_bg_color; @@ -56,9 +58,12 @@ void CheatMenu::drawEntry(video::IVideoDriver* driver, std::string name, int num } driver->draw2DRectangle(*bgcolor, core::rect(x, y, x + width, y + height)); if (selected) - driver->draw2DRectangleOutline(core::rect(x - 1, y - 1, x + width, y + height), *fontcolor); + driver->draw2DRectangleOutline( + core::rect(x - 1, y - 1, x + width, y + height), + *fontcolor); int fx = x + 5, fy = y + (height - m_fontsize.Y) / 2; - core::rect fontbounds(fx, fy, fx + m_fontsize.X * name.size(), fy + m_fontsize.Y); + core::rect fontbounds( + fx, fy, fx + m_fontsize.X * name.size(), fy + m_fontsize.Y); m_font->draw(name.c_str(), fontbounds, *fontcolor, false, false); } @@ -66,16 +71,22 @@ void CheatMenu::draw(video::IVideoDriver* driver, bool show_debug) { CHEAT_MENU_GET_SCRIPTPTR - if (! show_debug) - drawEntry(driver, "Dragonfireclient", 0, false, false, CHEAT_MENU_ENTRY_TYPE_HEAD); + if (!show_debug) + drawEntry(driver, "Dragonfireclient", 0, false, false, + CHEAT_MENU_ENTRY_TYPE_HEAD); int category_count = 0; - for (auto category = script->m_cheat_categories.begin(); category != script->m_cheat_categories.end(); category++) { + for (auto category = script->m_cheat_categories.begin(); + category != script->m_cheat_categories.end(); category++) { bool is_selected = category_count == m_selected_category; - drawEntry(driver, (*category)->m_name, category_count, is_selected, false, CHEAT_MENU_ENTRY_TYPE_CATEGORY); + drawEntry(driver, (*category)->m_name, category_count, is_selected, false, + CHEAT_MENU_ENTRY_TYPE_CATEGORY); if (is_selected && m_cheat_layer) { int cheat_count = 0; - for (auto cheat = (*category)->m_cheats.begin(); cheat != (*category)->m_cheats.end(); cheat++) { - drawEntry(driver, (*cheat)->m_name, cheat_count, cheat_count == m_selected_cheat, (*cheat)->is_enabled()); + for (auto cheat = (*category)->m_cheats.begin(); + cheat != (*category)->m_cheats.end(); cheat++) { + drawEntry(driver, (*cheat)->m_name, cheat_count, + cheat_count == m_selected_cheat, + (*cheat)->is_enabled()); cheat_count++; } } @@ -87,7 +98,10 @@ void CheatMenu::selectUp() { CHEAT_MENU_GET_SCRIPTPTR - int max = (m_cheat_layer ? script->m_cheat_categories[m_selected_category]->m_cheats.size() : script->m_cheat_categories.size()) - 1; + int max = (m_cheat_layer ? script->m_cheat_categories[m_selected_category] + ->m_cheats.size() + : script->m_cheat_categories.size()) - + 1; int *selected = m_cheat_layer ? &m_selected_cheat : &m_selected_category; --*selected; if (*selected < 0) @@ -98,7 +112,10 @@ void CheatMenu::selectDown() { CHEAT_MENU_GET_SCRIPTPTR - int max = (m_cheat_layer ? script->m_cheat_categories[m_selected_category]->m_cheats.size() : script->m_cheat_categories.size()) - 1; + int max = (m_cheat_layer ? script->m_cheat_categories[m_selected_category] + ->m_cheats.size() + : script->m_cheat_categories.size()) - + 1; int *selected = m_cheat_layer ? &m_selected_cheat : &m_selected_category; ++*selected; if (*selected > max) @@ -114,16 +131,17 @@ void CheatMenu::selectRight() } void CheatMenu::selectLeft() -{ - if (! m_cheat_layer) +{ + if (!m_cheat_layer) return; m_cheat_layer = false; } void CheatMenu::selectConfirm() -{ +{ CHEAT_MENU_GET_SCRIPTPTR if (m_cheat_layer) - script->toggle_cheat(script->m_cheat_categories[m_selected_category]->m_cheats[m_selected_cheat]); + script->toggle_cheat(script->m_cheat_categories[m_selected_category] + ->m_cheats[m_selected_cheat]); } diff --git a/src/gui/cheatMenu.h b/src/gui/cheatMenu.h index aaa433ceb..de369485c 100644 --- a/src/gui/cheatMenu.h +++ b/src/gui/cheatMenu.h @@ -15,14 +15,17 @@ 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_extrabloated.h" #include -#define CHEAT_MENU_GET_SCRIPTPTR ClientScripting *script = m_client->getScript(); if (! script || ! script->m_cheats_loaded) return; +#define CHEAT_MENU_GET_SCRIPTPTR \ + ClientScripting *script = m_client->getScript(); \ + if (! script || ! script->m_cheats_loaded) \ + return; class Client; @@ -31,42 +34,42 @@ typedef enum CHEAT_MENU_ENTRY_TYPE_HEAD, CHEAT_MENU_ENTRY_TYPE_CATEGORY, CHEAT_MENU_ENTRY_TYPE_ENTRY, -} -CheatMenuEntryType; +} CheatMenuEntryType; class CheatMenu { public: CheatMenu(Client* client); - + void draw(video::IVideoDriver* driver, bool show_debug); - - void drawEntry(video::IVideoDriver* driver, std::string name, int number, bool selected, bool active, CheatMenuEntryType entry_type = CHEAT_MENU_ENTRY_TYPE_ENTRY); - + + void drawEntry(video::IVideoDriver* driver, std::string name, int number, + bool selected, bool active, + CheatMenuEntryType entry_type = CHEAT_MENU_ENTRY_TYPE_ENTRY); + void selectUp(); void selectDown(); void selectLeft(); void selectRight(); void selectConfirm(); - + private: bool m_cheat_layer = false; int m_selected_cheat = 0; int m_selected_category = 0; - + int m_head_height = 50; int m_entry_height = 40; int m_entry_width = 200; int m_gap = 3; - + video::SColor m_bg_color = video::SColor(192, 255, 145, 88); video::SColor m_active_bg_color = video::SColor(192, 255, 87, 53); video::SColor m_font_color = video::SColor(255, 0, 0, 0); video::SColor m_selected_font_color = video::SColor(255, 255, 252, 88); - Client *m_client; - + gui::IGUIFont *m_font = nullptr; v2u32 m_fontsize; }; diff --git a/src/network/networkprotocol.h b/src/network/networkprotocol.h index 3ac9b4af8..e57a7a794 100644 --- a/src/network/networkprotocol.h +++ b/src/network/networkprotocol.h @@ -204,8 +204,6 @@ with this program; if not, write to the Free Software Foundation, Inc., PROTOCOL VERSION 39: Updated set_sky packet Adds new sun, moon and stars packets - PROTOCOL VERSION 40: - Added TOCLIENT_REDIRECT */ #define LATEST_PROTOCOL_VERSION 40 @@ -765,13 +763,7 @@ enum ToClientCommand u16 len u8[len] formspec */ - - TOCLIENT_REDIRECT = 0x62, - /* - std::string address - u16 port - */ - + TOCLIENT_NUM_MSG_TYPES = 0x63, }; diff --git a/src/network/serveropcodes.cpp b/src/network/serveropcodes.cpp index 61f0dfeeb..2fc3197c2 100644 --- a/src/network/serveropcodes.cpp +++ b/src/network/serveropcodes.cpp @@ -221,5 +221,4 @@ const ClientCommandFactory clientCommandFactoryTable[TOCLIENT_NUM_MSG_TYPES] = null_command_factory, // 0x5f { "TOSERVER_SRP_BYTES_S_B", 0, true }, // 0x60 { "TOCLIENT_FORMSPEC_PREPEND", 0, true }, // 0x61 - { "TOCLIENT_REDIRECT", 0, true }, // 0x62 }; diff --git a/src/script/cpp_api/s_cheats.cpp b/src/script/cpp_api/s_cheats.cpp index 8f3e4ef14..4ad62f2fe 100644 --- a/src/script/cpp_api/s_cheats.cpp +++ b/src/script/cpp_api/s_cheats.cpp @@ -15,44 +15,42 @@ 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. -*/ +*/ #include "cpp_api/s_cheats.h" #include "cpp_api/s_base.h" #include "cpp_api/s_internal.h" #include "settings.h" -ScriptApiCheatsCheat::ScriptApiCheatsCheat(const std::string &name, const std::string &setting) : - m_name(name), - m_setting(setting), - m_function_ref(0) +ScriptApiCheatsCheat::ScriptApiCheatsCheat( + const std::string &name, const std::string &setting) : + m_name(name), + m_setting(setting), m_function_ref(0) { } ScriptApiCheatsCheat::ScriptApiCheatsCheat(const std::string &name, const int &function) : - m_name(name), - m_setting(""), - m_function_ref(function) + m_name(name), m_setting(""), m_function_ref(function) { } bool ScriptApiCheatsCheat::is_enabled() { try { - return ! m_function_ref && g_settings->getBool(m_setting); + return !m_function_ref && g_settings->getBool(m_setting); } catch (SettingNotFoundException &) { return false; } } void ScriptApiCheatsCheat::toggle(lua_State *L, int error_handler) -{ +{ if (m_function_ref) { lua_rawgeti(L, LUA_REGISTRYINDEX, m_function_ref); lua_pcall(L, 0, 0, error_handler); } else - g_settings->setBool(m_setting, ! is_enabled()); + g_settings->setBool(m_setting, !is_enabled()); } ScriptApiCheatsCategory::ScriptApiCheatsCategory(const std::string &name) : @@ -75,7 +73,8 @@ void ScriptApiCheatsCategory::read_cheats(lua_State *L) if (lua_isstring(L, -1)) cheat = new ScriptApiCheatsCheat(name, lua_tostring(L, -1)); else if (lua_isfunction(L, -1)) { - cheat = new ScriptApiCheatsCheat(name, luaL_ref(L, LUA_REGISTRYINDEX)); + cheat = new ScriptApiCheatsCheat( + name, luaL_ref(L, LUA_REGISTRYINDEX)); lua_pushnil(L); } if (cheat) @@ -93,10 +92,10 @@ ScriptApiCheats::~ScriptApiCheats() void ScriptApiCheats::init_cheats() { SCRIPTAPI_PRECHECKHEADER - + lua_getglobal(L, "core"); lua_getfield(L, -1, "cheats"); - if (! lua_istable(L, -1)) { + if (!lua_istable(L, -1)) { lua_pop(L, 2); return; } @@ -116,10 +115,10 @@ void ScriptApiCheats::init_cheats() void ScriptApiCheats::toggle_cheat(ScriptApiCheatsCheat *cheat) { SCRIPTAPI_PRECHECKHEADER - + PUSH_ERROR_HANDLER(L); int error_handler = lua_gettop(L) - 1; lua_insert(L, error_handler); - + cheat->toggle(L, error_handler); } diff --git a/src/script/lua_api/l_client.h b/src/script/lua_api/l_client.h index ebce7c02a..4e49ad60e 100644 --- a/src/script/lua_api/l_client.h +++ b/src/script/lua_api/l_client.h @@ -104,24 +104,25 @@ private: // get_csm_restrictions() static int l_get_csm_restrictions(lua_State *L); - + // send_damage(damage) static int l_send_damage(lua_State *L); - + // place_node(pos) static int l_place_node(lua_State *L); - + // dig_node(pos) static int l_dig_node(lua_State *L); - + // get_inventory(location) static int l_get_inventory(lua_State *L); - + // set_keypress(key_setting, pressed) static int l_set_keypress(lua_State *L); // drop_selected_item() static int l_drop_selected_item(lua_State *L); + public: static void Initialize(lua_State *L, int top); }; diff --git a/src/script/lua_api/l_server.cpp b/src/script/lua_api/l_server.cpp index befbfb936..64ae924d2 100644 --- a/src/script/lua_api/l_server.cpp +++ b/src/script/lua_api/l_server.cpp @@ -338,23 +338,6 @@ int ModApiServer::l_kick_player(lua_State *L) return 1; } -// redirect_player(name, address, port) -int ModApiServer::l_redirect_player(lua_State *L) -{ - NO_MAP_LOCK_REQUIRED; - const char *name = luaL_checkstring(L, 1); - const char *address = luaL_checkstring(L, 2); - u16 port = luaL_checknumber(L, 3); - RemotePlayer *player = dynamic_cast(getEnv(L))->getPlayer(name); - if (player == NULL) { - lua_pushboolean(L, false); // No such player - return 1; - } - getServer(L)->RedirectPeer(player->getPeerId(), address, port); - lua_pushboolean(L, true); - return 1; -} - // remove_player(name) int ModApiServer::l_remove_player(lua_State *L) { @@ -580,8 +563,6 @@ void ModApiServer::Initialize(lua_State *L, int top) API_FCT(get_ban_description); API_FCT(ban_player); API_FCT(kick_player); - API_FCT(redirect_player); - API_FCT(remove_player); API_FCT(unban_player_or_ip); API_FCT(notify_authentication_modified); diff --git a/src/script/lua_api/l_server.h b/src/script/lua_api/l_server.h index 3b590af9d..938bfa8ef 100644 --- a/src/script/lua_api/l_server.h +++ b/src/script/lua_api/l_server.h @@ -96,9 +96,6 @@ private: // kick_player(name, [message]) -> success static int l_kick_player(lua_State *L); - - // redirect_player(name, address, port) -> success - static int l_redirect_player(lua_State *L); // remove_player(name) static int l_remove_player(lua_State *L); diff --git a/src/server.cpp b/src/server.cpp index b9f6d1b0f..fe2bb3840 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -1376,13 +1376,6 @@ void Server::SendAccessDenied_Legacy(session_t peer_id,const std::wstring &reaso Send(&pkt); } -void Server::SendRedirect(session_t peer_id, const std::string address, u16 port) -{ - NetworkPacket pkt(TOCLIENT_REDIRECT, 0, peer_id); - pkt << address << port; - Send(&pkt); -} - void Server::SendDeathscreen(session_t peer_id, bool set_camera_point_target, v3f camera_point_target) { @@ -2784,11 +2777,6 @@ void Server::DisconnectPeer(session_t peer_id) m_con->DisconnectPeer(peer_id); } -void Server::RedirectPeer(session_t peer_id, const std::string address, u16 port) -{ - SendRedirect(peer_id, address, port); -} - void Server::acceptAuth(session_t peer_id, bool forSudoMode) { if (!forSudoMode) { diff --git a/src/server.h b/src/server.h index 023aee519..f44716531 100644 --- a/src/server.h +++ b/src/server.h @@ -315,7 +315,6 @@ public: void acceptAuth(session_t peer_id, bool forSudoMode); void DenyAccess_Legacy(session_t peer_id, const std::wstring &reason); void DisconnectPeer(session_t peer_id); - void RedirectPeer(session_t peer_id, const std::string address, u16 port); bool getClientConInfo(session_t peer_id, con::rtt_stat_type type, float *retval); bool getClientInfo(session_t peer_id, ClientState *state, u32 *uptime, u8* ser_vers, u16* prot_vers, u8* major, u8* minor, u8* patch, @@ -381,7 +380,6 @@ private: void SendAccessDenied(session_t peer_id, AccessDeniedCode reason, const std::string &custom_reason, bool reconnect = false); void SendAccessDenied_Legacy(session_t peer_id, const std::wstring &reason); - void SendRedirect(session_t peer_id, const std::string address, u16 port); void SendDeathscreen(session_t peer_id, bool set_camera_point_target, v3f camera_point_target); void SendItemDef(session_t peer_id, IItemDefManager *itemdef, u16 protocol_version); From c1aea404b862256e6bf9316eeb8f32c72b78a4c2 Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein Date: Sun, 18 Oct 2020 13:16:09 +0200 Subject: [PATCH 123/266] Lint is bitch --- builtin/common/chatcommands.lua | 76 +++++++++++++++--------------- doc/client_lua_api.txt | 10 +++- src/gui/cheatMenu.cpp | 28 +++++------ src/gui/cheatMenu.h | 14 +++--- src/script/cpp_api/s_cheats.cpp | 10 ++-- src/script/cpp_api/s_cheats.h | 5 +- src/script/lua_api/l_localplayer.h | 8 ++-- 7 files changed, 80 insertions(+), 71 deletions(-) diff --git a/builtin/common/chatcommands.lua b/builtin/common/chatcommands.lua index 3d04391fb..709c3d00a 100644 --- a/builtin/common/chatcommands.lua +++ b/builtin/common/chatcommands.lua @@ -29,47 +29,49 @@ function core.override_chatcommand(name, redefinition) core.registered_chatcommands[name] = chatcommand end -function core.register_list_command(command, desc, setting) - local def = {} - def.description = desc - def.params = "del | add | list" - function def.func(param) - local list = (minetest.settings:get(setting) or ""):split(",") - if param == "list" then - return true, table.concat(list, ", ") - else - local sparam = param:split(" ") - local cmd = sparam[1] - local item = sparam[2] - if cmd == "del" then - if not item then - return false, "Missing item." - end - local i = table.indexof(list, item) - if i == -1 then - return false, item .. " is not on the list." - else - table.remove(list, i) - core.settings:set(setting, table.concat(list, ",")) - return true, "Removed " .. item .. " from the list." - end - elseif cmd == "add" then - if not item then - return false, "Missing item." - end - local i = table.indexof(list, item) - if i ~= -1 then - return false, item .. " is already on the list." - else - table.insert(list, item) - core.settings:set(setting, table.concat(list, ",")) - return true, "Added " .. item .. " to the list." +if INIT == "client" then + function core.register_list_command(command, desc, setting) + local def = {} + def.description = desc + def.params = "del | add | list" + function def.func(param) + local list = (minetest.settings:get(setting) or ""):split(",") + if param == "list" then + return true, table.concat(list, ", ") + else + local sparam = param:split(" ") + local cmd = sparam[1] + local item = sparam[2] + if cmd == "del" then + if not item then + return false, "Missing item." + end + local i = table.indexof(list, item) + if i == -1 then + return false, item .. " is not on the list." + else + table.remove(list, i) + core.settings:set(setting, table.concat(list, ",")) + return true, "Removed " .. item .. " from the list." + end + elseif cmd == "add" then + if not item then + return false, "Missing item." + end + local i = table.indexof(list, item) + if i ~= -1 then + return false, item .. " is already on the list." + else + table.insert(list, item) + core.settings:set(setting, table.concat(list, ",")) + return true, "Added " .. item .. " to the list." + end end end + return false, "Invalid usage. (See /help " .. command .. ")" end - return false, "Invalid usage. (See /help " .. command .. ")" + core.register_chatcommand(command, def) end - core.register_chatcommand(command, def) end local cmd_marker = "/" diff --git a/doc/client_lua_api.txt b/doc/client_lua_api.txt index 15c0b8a6a..599ce0b83 100644 --- a/doc/client_lua_api.txt +++ b/doc/client_lua_api.txt @@ -674,9 +674,8 @@ Minetest namespace reference ### Global callback registration functions Call these functions only at load time! -* `minetest.open_special_inventory()` +* `minetest.open_enderchest()` * This function is called if the client uses the Keybind for it (by default "O") - * It is used for a client provided inventory * You can override it * `minetest.register_globalstep(function(dtime))` * Called every client environment step, usually interval of 0.1s @@ -697,6 +696,13 @@ Call these functions only at load time! * Adds definition to minetest.registered_chatcommands * `minetest.unregister_chatcommand(name)` * Unregisters a chatcommands registered with register_chatcommand. +* `minetest.register_list_command(command, desc, setting)` + * Registers a chatcommand `command` to manage a list that takes the args `del | add | list ` + * The list is stored comma-seperated in `setting` + * `desc` is the description + * `add` adds something to the list + * `del` del removes something from the list + * `list` lists all items on the list * `minetest.register_on_death(function())` * Called when the local player dies * `minetest.register_on_hp_modification(function(hp))` diff --git a/src/gui/cheatMenu.cpp b/src/gui/cheatMenu.cpp index b22b8da30..b103e6301 100644 --- a/src/gui/cheatMenu.cpp +++ b/src/gui/cheatMenu.cpp @@ -37,8 +37,8 @@ CheatMenu::CheatMenu(Client *client) : m_client(client) m_fontsize.Y = MYMAX(m_fontsize.Y, 1); } -void CheatMenu::drawEntry(video::IVideoDriver* driver, std::string name, int number, - bool selected, bool active, CheatMenuEntryType entry_type) +void CheatMenu::drawEntry(video::IVideoDriver *driver, std::string name, int number, + bool selected, bool active, CheatMenuEntryType entry_type) { int x = m_gap, y = m_gap, width = m_entry_width, height = m_entry_height; video::SColor *bgcolor = &m_bg_color, *fontcolor = &m_font_color; @@ -49,7 +49,7 @@ void CheatMenu::drawEntry(video::IVideoDriver* driver, std::string name, int num bool is_category = entry_type == CHEAT_MENU_ENTRY_TYPE_CATEGORY; y += m_gap + m_head_height + (number + (is_category ? 0 : m_selected_category)) * - (m_entry_height + m_gap); + (m_entry_height + m_gap); x += (is_category ? 0 : m_gap + m_entry_width); if (active) bgcolor = &m_active_bg_color; @@ -70,7 +70,7 @@ void CheatMenu::drawEntry(video::IVideoDriver* driver, std::string name, int num void CheatMenu::draw(video::IVideoDriver* driver, bool show_debug) { CHEAT_MENU_GET_SCRIPTPTR - + if (!show_debug) drawEntry(driver, "Dragonfireclient", 0, false, false, CHEAT_MENU_ENTRY_TYPE_HEAD); @@ -97,11 +97,11 @@ void CheatMenu::draw(video::IVideoDriver* driver, bool show_debug) void CheatMenu::selectUp() { CHEAT_MENU_GET_SCRIPTPTR - + int max = (m_cheat_layer ? script->m_cheat_categories[m_selected_category] - ->m_cheats.size() - : script->m_cheat_categories.size()) - - 1; + ->m_cheats.size() + : script->m_cheat_categories.size()) - + 1; int *selected = m_cheat_layer ? &m_selected_cheat : &m_selected_category; --*selected; if (*selected < 0) @@ -111,11 +111,11 @@ void CheatMenu::selectUp() void CheatMenu::selectDown() { CHEAT_MENU_GET_SCRIPTPTR - + int max = (m_cheat_layer ? script->m_cheat_categories[m_selected_category] - ->m_cheats.size() - : script->m_cheat_categories.size()) - - 1; + ->m_cheats.size() + : script->m_cheat_categories.size()) - + 1; int *selected = m_cheat_layer ? &m_selected_cheat : &m_selected_category; ++*selected; if (*selected > max) @@ -140,8 +140,8 @@ void CheatMenu::selectLeft() void CheatMenu::selectConfirm() { CHEAT_MENU_GET_SCRIPTPTR - + if (m_cheat_layer) script->toggle_cheat(script->m_cheat_categories[m_selected_category] - ->m_cheats[m_selected_cheat]); + ->m_cheats[m_selected_cheat]); } diff --git a/src/gui/cheatMenu.h b/src/gui/cheatMenu.h index de369485c..3c4bec471 100644 --- a/src/gui/cheatMenu.h +++ b/src/gui/cheatMenu.h @@ -22,9 +22,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irrlichttypes_extrabloated.h" #include -#define CHEAT_MENU_GET_SCRIPTPTR \ - ClientScripting *script = m_client->getScript(); \ - if (! script || ! script->m_cheats_loaded) \ +#define CHEAT_MENU_GET_SCRIPTPTR \ + ClientScripting *script = m_client->getScript(); \ + if (! script || ! script->m_cheats_loaded) \ return; class Client; @@ -39,11 +39,11 @@ typedef enum class CheatMenu { public: - CheatMenu(Client* client); + CheatMenu(Client *client); - void draw(video::IVideoDriver* driver, bool show_debug); + void draw(video::IVideoDriver *driver, bool show_debug); - void drawEntry(video::IVideoDriver* driver, std::string name, int number, + void drawEntry(video::IVideoDriver *driver, std::string name, int number, bool selected, bool active, CheatMenuEntryType entry_type = CHEAT_MENU_ENTRY_TYPE_ENTRY); @@ -66,7 +66,7 @@ private: video::SColor m_bg_color = video::SColor(192, 255, 145, 88); video::SColor m_active_bg_color = video::SColor(192, 255, 87, 53); video::SColor m_font_color = video::SColor(255, 0, 0, 0); - video::SColor m_selected_font_color = video::SColor(255, 255, 252, 88); + video::SColor m_selected_font_color = video::SColor(255, 255, 252, 88); Client *m_client; diff --git a/src/script/cpp_api/s_cheats.cpp b/src/script/cpp_api/s_cheats.cpp index 4ad62f2fe..45b75ff6d 100644 --- a/src/script/cpp_api/s_cheats.cpp +++ b/src/script/cpp_api/s_cheats.cpp @@ -23,14 +23,14 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "settings.h" ScriptApiCheatsCheat::ScriptApiCheatsCheat( - const std::string &name, const std::string &setting) : + const std::string &name, const std::string &setting) : m_name(name), m_setting(setting), m_function_ref(0) { } -ScriptApiCheatsCheat::ScriptApiCheatsCheat(const std::string &name, const int &function) : +ScriptApiCheatsCheat::ScriptApiCheatsCheat(const std::string &name, const int &function) : m_name(name), m_setting(""), m_function_ref(function) { } @@ -53,8 +53,7 @@ void ScriptApiCheatsCheat::toggle(lua_State *L, int error_handler) g_settings->setBool(m_setting, !is_enabled()); } -ScriptApiCheatsCategory::ScriptApiCheatsCategory(const std::string &name) : - m_name(name) +ScriptApiCheatsCategory::ScriptApiCheatsCategory(const std::string &name) : m_name(name) { } @@ -102,7 +101,8 @@ void ScriptApiCheats::init_cheats() lua_pushnil(L); while (lua_next(L, -2)) { if (lua_istable(L, -1)) { - ScriptApiCheatsCategory *category = new ScriptApiCheatsCategory(lua_tostring(L, -2)); + ScriptApiCheatsCategory *category = + new ScriptApiCheatsCategory(lua_tostring(L, -2)); category->read_cheats(L); m_cheat_categories.push_back(category); } diff --git a/src/script/cpp_api/s_cheats.h b/src/script/cpp_api/s_cheats.h index a93768659..9b5ace3e7 100644 --- a/src/script/cpp_api/s_cheats.h +++ b/src/script/cpp_api/s_cheats.h @@ -31,6 +31,7 @@ public: std::string m_name; bool is_enabled(); void toggle(lua_State *L, int error_handler); + private: std::string m_setting; int m_function_ref; @@ -43,7 +44,7 @@ public: ~ScriptApiCheatsCategory(); std::string m_name; void read_cheats(lua_State *L); - std::vector m_cheats; + std::vector m_cheats; }; class ScriptApiCheats : virtual public ScriptApiBase @@ -53,5 +54,5 @@ public: void init_cheats(); void toggle_cheat(ScriptApiCheatsCheat *cheat); bool m_cheats_loaded = false; - std::vector m_cheat_categories; + std::vector m_cheat_categories; }; diff --git a/src/script/lua_api/l_localplayer.h b/src/script/lua_api/l_localplayer.h index c59cef764..f43eb2a4d 100644 --- a/src/script/lua_api/l_localplayer.h +++ b/src/script/lua_api/l_localplayer.h @@ -34,13 +34,13 @@ private: // get_velocity(self) static int l_get_velocity(lua_State *L); - + // set_velocity(self, vel) static int l_set_velocity(lua_State *L); // get_yaw(self) static int l_get_yaw(lua_State *L); - + // set_yaw(self, yaw) static int l_set_yaw(lua_State *L); @@ -52,7 +52,7 @@ private: // get_wield_index(self) static int l_get_wield_index(lua_State *L); - + // set_wield_index(self) static int l_set_wield_index(lua_State *L); @@ -84,7 +84,7 @@ private: // get_pos(self) static int l_get_pos(lua_State *L); - + // set_pos(self, pos) static int l_set_pos(lua_State *L); From 28a560684b9b9048f8436583805f3f733d7ce829 Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein Date: Sun, 18 Oct 2020 14:16:18 +0200 Subject: [PATCH 124/266] Added the API additions from waspsaliva --- src/client/content_cao.h | 25 ++++ src/gui/cheatMenu.cpp | 6 +- src/gui/cheatMenu.h | 4 +- src/script/cpp_api/s_cheats.h | 4 +- src/script/cpp_api/s_security.cpp | 4 + src/script/lua_api/CMakeLists.txt | 1 + src/script/lua_api/l_client.cpp | 22 +++ src/script/lua_api/l_client.h | 7 +- src/script/lua_api/l_clientobject.cpp | 199 ++++++++++++++++++++++++++ src/script/lua_api/l_clientobject.h | 77 ++++++++++ src/script/lua_api/l_localplayer.cpp | 13 ++ src/script/lua_api/l_localplayer.h | 3 + src/script/lua_api/l_util.cpp | 2 + src/script/scripting_client.cpp | 2 + 14 files changed, 360 insertions(+), 9 deletions(-) create mode 100644 src/script/lua_api/l_clientobject.cpp create mode 100644 src/script/lua_api/l_clientobject.h diff --git a/src/client/content_cao.h b/src/client/content_cao.h index 88aa4870c..56ba8e0ec 100644 --- a/src/client/content_cao.h +++ b/src/client/content_cao.h @@ -169,6 +169,16 @@ public: inline const v3f &getRotation() const { return m_rotation; } + inline const v3f getAcceleration() const + { + return m_acceleration; + } + + inline const v3f getVelocity() const + { + return m_velocity; + } + const bool isImmortal(); scene::ISceneNode *getSceneNode() const; @@ -205,6 +215,16 @@ public: return m_is_local_player; } + inline std::string getName() const + { + return m_name; + } + + inline bool isPlayer() const + { + return m_is_player; + } + inline bool isVisible() const { return m_is_visible; @@ -278,4 +298,9 @@ public: } float m_waiting_for_reattach; + + ObjectProperties *getProperties() + { + return &m_prop; + } }; diff --git a/src/gui/cheatMenu.cpp b/src/gui/cheatMenu.cpp index b103e6301..6589374b1 100644 --- a/src/gui/cheatMenu.cpp +++ b/src/gui/cheatMenu.cpp @@ -48,8 +48,8 @@ void CheatMenu::drawEntry(video::IVideoDriver *driver, std::string name, int num } else { bool is_category = entry_type == CHEAT_MENU_ENTRY_TYPE_CATEGORY; y += m_gap + m_head_height + - (number + (is_category ? 0 : m_selected_category)) * - (m_entry_height + m_gap); + (number + (is_category ? 0 : m_selected_category)) * + (m_entry_height + m_gap); x += (is_category ? 0 : m_gap + m_entry_width); if (active) bgcolor = &m_active_bg_color; @@ -67,7 +67,7 @@ void CheatMenu::drawEntry(video::IVideoDriver *driver, std::string name, int num m_font->draw(name.c_str(), fontbounds, *fontcolor, false, false); } -void CheatMenu::draw(video::IVideoDriver* driver, bool show_debug) +void CheatMenu::draw(video::IVideoDriver *driver, bool show_debug) { CHEAT_MENU_GET_SCRIPTPTR diff --git a/src/gui/cheatMenu.h b/src/gui/cheatMenu.h index 3c4bec471..ea9a9d853 100644 --- a/src/gui/cheatMenu.h +++ b/src/gui/cheatMenu.h @@ -22,9 +22,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irrlichttypes_extrabloated.h" #include -#define CHEAT_MENU_GET_SCRIPTPTR \ +#define CHEAT_MENU_GET_SCRIPTPTR \ ClientScripting *script = m_client->getScript(); \ - if (! script || ! script->m_cheats_loaded) \ + if (!script || !script->m_cheats_loaded) \ return; class Client; diff --git a/src/script/cpp_api/s_cheats.h b/src/script/cpp_api/s_cheats.h index 9b5ace3e7..9f36333b7 100644 --- a/src/script/cpp_api/s_cheats.h +++ b/src/script/cpp_api/s_cheats.h @@ -15,7 +15,7 @@ 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 @@ -44,7 +44,7 @@ public: ~ScriptApiCheatsCategory(); std::string m_name; void read_cheats(lua_State *L); - std::vector m_cheats; + std::vector m_cheats; }; class ScriptApiCheats : virtual public ScriptApiBase diff --git a/src/script/cpp_api/s_security.cpp b/src/script/cpp_api/s_security.cpp index d8ff667eb..9d65819c0 100644 --- a/src/script/cpp_api/s_security.cpp +++ b/src/script/cpp_api/s_security.cpp @@ -287,6 +287,10 @@ void ScriptApiSecurity::initializeSecurityClient() lua_State *L = getStack(); int thread = getThread(L); + // Backup globals to the registry + lua_getglobal(L, "_G"); + lua_rawseti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_GLOBALS_BACKUP); + // create an empty environment createEmptyEnv(L); diff --git a/src/script/lua_api/CMakeLists.txt b/src/script/lua_api/CMakeLists.txt index 32f6a2793..ee94737d6 100644 --- a/src/script/lua_api/CMakeLists.txt +++ b/src/script/lua_api/CMakeLists.txt @@ -28,6 +28,7 @@ set(common_SCRIPT_LUA_API_SRCS set(client_SCRIPT_LUA_API_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/l_camera.cpp ${CMAKE_CURRENT_SOURCE_DIR}/l_client.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/l_clientobject.cpp ${CMAKE_CURRENT_SOURCE_DIR}/l_localplayer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/l_mainmenu.cpp ${CMAKE_CURRENT_SOURCE_DIR}/l_minimap.cpp diff --git a/src/script/lua_api/l_client.cpp b/src/script/lua_api/l_client.cpp index 56ce6471c..9961471ff 100644 --- a/src/script/lua_api/l_client.cpp +++ b/src/script/lua_api/l_client.cpp @@ -30,6 +30,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "cpp_api/s_base.h" #include "gettext.h" #include "l_internal.h" +#include "l_clientobject.h" #include "lua_api/l_nodemeta.h" #include "gui/mainmenumanager.h" #include "map.h" @@ -503,6 +504,26 @@ int ModApiClient::l_drop_selected_item(lua_State *L) return 0; } +// get_objects_inside_radius(pos, radius) +int ModApiClient::l_get_objects_inside_radius(lua_State *L) +{ + ClientEnvironment &env = getClient(L)->getEnv(); + + v3f pos = checkFloatPos(L, 1); + float radius = readParam(L, 2) * BS; + + std::vector objs; + env.getActiveObjects(pos, radius, objs); + + int i = 0; + lua_createtable(L, objs.size(), 0); + for (const auto obj : objs) { + ClientObjectRef::create(L, obj.obj); // TODO: getObjectRefOrCreate + lua_rawseti(L, -2, ++i); + } + return 1; +} + void ModApiClient::Initialize(lua_State *L, int top) { API_FCT(get_current_modname); @@ -536,4 +557,5 @@ void ModApiClient::Initialize(lua_State *L, int top) API_FCT(get_inventory); API_FCT(set_keypress); API_FCT(drop_selected_item); + API_FCT(get_objects_inside_radius); } diff --git a/src/script/lua_api/l_client.h b/src/script/lua_api/l_client.h index 4e49ad60e..5863e5717 100644 --- a/src/script/lua_api/l_client.h +++ b/src/script/lua_api/l_client.h @@ -119,10 +119,13 @@ private: // set_keypress(key_setting, pressed) static int l_set_keypress(lua_State *L); - + // drop_selected_item() static int l_drop_selected_item(lua_State *L); -public: + // get_objects_inside_radius(pos, radius) + static int l_get_objects_inside_radius(lua_State *L); + +public: static void Initialize(lua_State *L, int top); }; diff --git a/src/script/lua_api/l_clientobject.cpp b/src/script/lua_api/l_clientobject.cpp new file mode 100644 index 000000000..2ae614ea0 --- /dev/null +++ b/src/script/lua_api/l_clientobject.cpp @@ -0,0 +1,199 @@ +/* +Dragonfire +Copyright (C) 2020 system32 + +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. +*/ + +#include "lua_api/l_clientobject.h" +#include "l_internal.h" +#include "common/c_converter.h" +#include "client/client.h" +#include "object_properties.h" + +// should prob do some more NULL checking + + +ClientObjectRef *ClientObjectRef::checkobject(lua_State *L, int narg) +{ + luaL_checktype(L, narg, LUA_TUSERDATA); + void *userdata = luaL_checkudata(L, narg, className); + if (!userdata) + luaL_typerror(L, narg, className); + return *(ClientObjectRef**)userdata; +} + +ClientActiveObject *ClientObjectRef::get_cao(ClientObjectRef *ref) +{ + ClientActiveObject *obj = ref->m_object; + return obj; +} + +GenericCAO *ClientObjectRef::get_generic_cao(ClientObjectRef *ref, lua_State *L) +{ + ClientActiveObject *obj = get_cao(ref); + ClientEnvironment &env = getClient(L)->getEnv(); + GenericCAO *gcao = env.getGenericCAO(obj->getId()); + return gcao; +} + +int ClientObjectRef::l_get_pos(lua_State *L) +{ + ClientObjectRef *ref = checkobject(L, 1); + ClientActiveObject *cao = get_cao(ref); + push_v3f(L, cao->getPosition() / BS); + return 1; +} + +int ClientObjectRef::l_get_velocity(lua_State *L) +{ + ClientObjectRef *ref = checkobject(L, 1); + GenericCAO *gcao = get_generic_cao(ref, L); + push_v3f(L, gcao->getVelocity() / BS); + return 1; +} + +int ClientObjectRef::l_get_acceleration(lua_State *L) +{ + ClientObjectRef *ref = checkobject(L, 1); + GenericCAO *gcao = get_generic_cao(ref, L); + push_v3f(L, gcao->getAcceleration() / BS); + return 1; +} + +int ClientObjectRef::l_get_rotation(lua_State *L) +{ + ClientObjectRef *ref = checkobject(L, 1); + GenericCAO *gcao = get_generic_cao(ref, L); + push_v3f(L, gcao->getRotation()); + return 1; +} + +int ClientObjectRef::l_is_player(lua_State *L) +{ + ClientObjectRef *ref = checkobject(L, 1); + GenericCAO *gcao = get_generic_cao(ref, L); + lua_pushboolean(L, gcao->isPlayer()); + return 1; +} + +int ClientObjectRef::l_get_name(lua_State *L) +{ + ClientObjectRef *ref = checkobject(L, 1); + GenericCAO *gcao = get_generic_cao(ref, L); + lua_pushstring(L, gcao->getName().c_str()); + return 1; +} + +int ClientObjectRef::l_get_attach(lua_State *L) +{ + ClientObjectRef *ref = checkobject(L, 1); + GenericCAO *gcao = get_generic_cao(ref, L); + create(L, gcao->getParent()); + return 1; +} + +int ClientObjectRef::l_get_nametag(lua_State *L) +{ + ClientObjectRef *ref = checkobject(L, 1); + GenericCAO *gcao = get_generic_cao(ref, L); + ObjectProperties *props = gcao->getProperties(); + lua_pushstring(L, props->nametag.c_str()); + return 1; +} + +int ClientObjectRef::l_get_item_textures(lua_State *L) +{ + ClientObjectRef *ref = checkobject(L, 1); + GenericCAO *gcao = get_generic_cao(ref, L); + ObjectProperties *props = gcao->getProperties(); + lua_newtable(L); + + for (std::string &texture : props->textures) { + lua_pushstring(L, texture.c_str()); + } + return 1; +} + +int ClientObjectRef::l_get_max_hp(lua_State *L) +{ + ClientObjectRef *ref = checkobject(L, 1); + GenericCAO *gcao = get_generic_cao(ref, L); + ObjectProperties *props = gcao->getProperties(); + lua_pushnumber(L, props->hp_max); + return 1; +} + +ClientObjectRef::ClientObjectRef(ClientActiveObject *object): + m_object(object) +{ +} + +void ClientObjectRef::create(lua_State *L, ClientActiveObject *object) +{ + if (object) { + ClientObjectRef *o = new ClientObjectRef(object); + *(void **)(lua_newuserdata(L, sizeof(void *))) = o; + luaL_getmetatable(L, className); + lua_setmetatable(L, -2); + } +} + +int ClientObjectRef::gc_object(lua_State *L) { + ClientObjectRef *obj = *(ClientObjectRef **)(lua_touserdata(L, 1)); + delete obj; + return 0; +} + +// taken from LuaLocalPlayer +void ClientObjectRef::Register(lua_State *L) +{ + lua_newtable(L); + int methodtable = lua_gettop(L); + luaL_newmetatable(L, className); + int metatable = lua_gettop(L); + + lua_pushliteral(L, "__metatable"); + lua_pushvalue(L, methodtable); + lua_settable(L, metatable); // hide metatable from lua getmetatable() + + lua_pushliteral(L, "__index"); + lua_pushvalue(L, methodtable); + lua_settable(L, metatable); + + lua_pushliteral(L, "__gc"); + lua_pushcfunction(L, gc_object); + lua_settable(L, metatable); + + lua_pop(L, 1); // Drop metatable + + luaL_openlib(L, 0, methods, 0); // fill methodtable + lua_pop(L, 1); // Drop methodtable +} + +const char ClientObjectRef::className[] = "ClientObjectRef"; +luaL_Reg ClientObjectRef::methods[] = { + luamethod(ClientObjectRef, get_pos), + luamethod(ClientObjectRef, get_velocity), + luamethod(ClientObjectRef, get_acceleration), + luamethod(ClientObjectRef, get_rotation), + luamethod(ClientObjectRef, is_player), + luamethod(ClientObjectRef, get_name), + luamethod(ClientObjectRef, get_attach), + luamethod(ClientObjectRef, get_nametag), + luamethod(ClientObjectRef, get_item_textures), + luamethod(ClientObjectRef, get_max_hp), + {0, 0} +}; diff --git a/src/script/lua_api/l_clientobject.h b/src/script/lua_api/l_clientobject.h new file mode 100644 index 000000000..c538c339e --- /dev/null +++ b/src/script/lua_api/l_clientobject.h @@ -0,0 +1,77 @@ +/* +Dragonfire +Copyright (C) 2020 system32 + +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. +*/ + +#include "lua_api/l_base.h" +#include "client/clientobject.h" +#include "client/content_cao.h" + +class ClientObjectRef : public ModApiBase +{ +public: + ClientObjectRef(ClientActiveObject *object); + + ~ClientObjectRef() = default; + + static void Register(lua_State *L); + + static void create(lua_State *L, ClientActiveObject *object); + + static ClientObjectRef *checkobject(lua_State *L, int narg); + +private: + ClientActiveObject *m_object = nullptr; + static const char className[]; + static luaL_Reg methods[]; + + static ClientActiveObject *get_cao(ClientObjectRef *ref); + static GenericCAO *get_generic_cao(ClientObjectRef *ref, lua_State *L); + + static int gc_object(lua_State *L); + + // get_pos(self) + // returns: {x=num, y=num, z=num} + static int l_get_pos(lua_State *L); + + // get_velocity(self) + static int l_get_velocity(lua_State *L); + + // get_acceleration(self) + static int l_get_acceleration(lua_State *L); + + // get_rotation(self) + static int l_get_rotation(lua_State *L); + + // is_player(self) + static int l_is_player(lua_State *L); + + // get_name(self) + static int l_get_name(lua_State *L); + + // get_attach(self) + static int l_get_attach(lua_State *L); + + // get_nametag(self) + static int l_get_nametag(lua_State *L); + + // get_textures(self) + static int l_get_item_textures(lua_State *L); + + // get_hp(self) + static int l_get_max_hp(lua_State *L); +}; diff --git a/src/script/lua_api/l_localplayer.cpp b/src/script/lua_api/l_localplayer.cpp index 8e743c3ab..8057802a4 100644 --- a/src/script/lua_api/l_localplayer.cpp +++ b/src/script/lua_api/l_localplayer.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 "l_clientobject.h" #include "l_localplayer.h" #include "l_internal.h" #include "lua_api/l_item.h" @@ -452,6 +453,17 @@ int LuaLocalPlayer::l_hud_get(lua_State *L) return 1; } +int LuaLocalPlayer::l_get_object(lua_State *L) +{ + LocalPlayer *player = getobject(L, 1); + ClientEnvironment &env = getClient(L)->getEnv(); + ClientActiveObject *obj = env.getGenericCAO(player->getCAO()->getId()); + + ClientObjectRef::create(L, obj); + + return 1; +} + LuaLocalPlayer *LuaLocalPlayer::checkobject(lua_State *L, int narg) { luaL_checktype(L, narg, LUA_TUSERDATA); @@ -546,6 +558,7 @@ const luaL_Reg LuaLocalPlayer::methods[] = { luamethod(LuaLocalPlayer, hud_remove), luamethod(LuaLocalPlayer, hud_change), luamethod(LuaLocalPlayer, hud_get), + luamethod(LuaLocalPlayer, get_object), {0, 0} }; diff --git a/src/script/lua_api/l_localplayer.h b/src/script/lua_api/l_localplayer.h index f43eb2a4d..8daa901e0 100644 --- a/src/script/lua_api/l_localplayer.h +++ b/src/script/lua_api/l_localplayer.h @@ -111,6 +111,9 @@ private: // hud_get(self, id) static int l_hud_get(lua_State *L); + // get_object(self) + static int l_get_object(lua_State *L); + LocalPlayer *m_localplayer = nullptr; public: diff --git a/src/script/lua_api/l_util.cpp b/src/script/lua_api/l_util.cpp index 99a006ec1..4595dc1c1 100644 --- a/src/script/lua_api/l_util.cpp +++ b/src/script/lua_api/l_util.cpp @@ -531,6 +531,8 @@ void ModApiUtil::InitializeClient(lua_State *L, int top) API_FCT(compress); API_FCT(decompress); + API_FCT(request_insecure_environment); + API_FCT(encode_base64); API_FCT(decode_base64); diff --git a/src/script/scripting_client.cpp b/src/script/scripting_client.cpp index 83ec3e777..df6594666 100644 --- a/src/script/scripting_client.cpp +++ b/src/script/scripting_client.cpp @@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "client/game.h" #include "cpp_api/s_internal.h" #include "lua_api/l_client.h" +#include "lua_api/l_clientobject.h" #include "lua_api/l_env.h" #include "lua_api/l_item.h" #include "lua_api/l_itemstackmeta.h" @@ -78,6 +79,7 @@ void ClientScripting::InitializeModApi(lua_State *L, int top) LuaCamera::Register(L); ModChannelRef::Register(L); LuaSettings::Register(L); + ClientObjectRef::Register(L); ModApiItemMod::Initialize(L, top); ModApiUtil::InitializeClient(L, top); From f8777a4fa62d16deb5f05cacdd846e610e8a25ad Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein Date: Sun, 18 Oct 2020 14:20:49 +0200 Subject: [PATCH 125/266] Replaced spaces with tabs --- src/script/lua_api/l_clientobject.cpp | 188 +++++++++++++------------- 1 file changed, 94 insertions(+), 94 deletions(-) diff --git a/src/script/lua_api/l_clientobject.cpp b/src/script/lua_api/l_clientobject.cpp index 2ae614ea0..db3563cc4 100644 --- a/src/script/lua_api/l_clientobject.cpp +++ b/src/script/lua_api/l_clientobject.cpp @@ -28,172 +28,172 @@ with this program; if not, write to the Free Software Foundation, Inc., ClientObjectRef *ClientObjectRef::checkobject(lua_State *L, int narg) { - luaL_checktype(L, narg, LUA_TUSERDATA); - void *userdata = luaL_checkudata(L, narg, className); - if (!userdata) - luaL_typerror(L, narg, className); - return *(ClientObjectRef**)userdata; + luaL_checktype(L, narg, LUA_TUSERDATA); + void *userdata = luaL_checkudata(L, narg, className); + if (!userdata) + luaL_typerror(L, narg, className); + return *(ClientObjectRef**)userdata; } ClientActiveObject *ClientObjectRef::get_cao(ClientObjectRef *ref) { - ClientActiveObject *obj = ref->m_object; - return obj; + ClientActiveObject *obj = ref->m_object; + return obj; } GenericCAO *ClientObjectRef::get_generic_cao(ClientObjectRef *ref, lua_State *L) { - ClientActiveObject *obj = get_cao(ref); - ClientEnvironment &env = getClient(L)->getEnv(); - GenericCAO *gcao = env.getGenericCAO(obj->getId()); - return gcao; + ClientActiveObject *obj = get_cao(ref); + ClientEnvironment &env = getClient(L)->getEnv(); + GenericCAO *gcao = env.getGenericCAO(obj->getId()); + return gcao; } int ClientObjectRef::l_get_pos(lua_State *L) { - ClientObjectRef *ref = checkobject(L, 1); - ClientActiveObject *cao = get_cao(ref); - push_v3f(L, cao->getPosition() / BS); - return 1; + ClientObjectRef *ref = checkobject(L, 1); + ClientActiveObject *cao = get_cao(ref); + push_v3f(L, cao->getPosition() / BS); + return 1; } int ClientObjectRef::l_get_velocity(lua_State *L) { - ClientObjectRef *ref = checkobject(L, 1); - GenericCAO *gcao = get_generic_cao(ref, L); - push_v3f(L, gcao->getVelocity() / BS); - return 1; + ClientObjectRef *ref = checkobject(L, 1); + GenericCAO *gcao = get_generic_cao(ref, L); + push_v3f(L, gcao->getVelocity() / BS); + return 1; } int ClientObjectRef::l_get_acceleration(lua_State *L) { - ClientObjectRef *ref = checkobject(L, 1); - GenericCAO *gcao = get_generic_cao(ref, L); - push_v3f(L, gcao->getAcceleration() / BS); - return 1; + ClientObjectRef *ref = checkobject(L, 1); + GenericCAO *gcao = get_generic_cao(ref, L); + push_v3f(L, gcao->getAcceleration() / BS); + return 1; } int ClientObjectRef::l_get_rotation(lua_State *L) { - ClientObjectRef *ref = checkobject(L, 1); - GenericCAO *gcao = get_generic_cao(ref, L); - push_v3f(L, gcao->getRotation()); - return 1; + ClientObjectRef *ref = checkobject(L, 1); + GenericCAO *gcao = get_generic_cao(ref, L); + push_v3f(L, gcao->getRotation()); + return 1; } int ClientObjectRef::l_is_player(lua_State *L) { - ClientObjectRef *ref = checkobject(L, 1); - GenericCAO *gcao = get_generic_cao(ref, L); - lua_pushboolean(L, gcao->isPlayer()); - return 1; + ClientObjectRef *ref = checkobject(L, 1); + GenericCAO *gcao = get_generic_cao(ref, L); + lua_pushboolean(L, gcao->isPlayer()); + return 1; } int ClientObjectRef::l_get_name(lua_State *L) { - ClientObjectRef *ref = checkobject(L, 1); - GenericCAO *gcao = get_generic_cao(ref, L); - lua_pushstring(L, gcao->getName().c_str()); - return 1; + ClientObjectRef *ref = checkobject(L, 1); + GenericCAO *gcao = get_generic_cao(ref, L); + lua_pushstring(L, gcao->getName().c_str()); + return 1; } int ClientObjectRef::l_get_attach(lua_State *L) { - ClientObjectRef *ref = checkobject(L, 1); - GenericCAO *gcao = get_generic_cao(ref, L); - create(L, gcao->getParent()); - return 1; + ClientObjectRef *ref = checkobject(L, 1); + GenericCAO *gcao = get_generic_cao(ref, L); + create(L, gcao->getParent()); + return 1; } int ClientObjectRef::l_get_nametag(lua_State *L) { - ClientObjectRef *ref = checkobject(L, 1); - GenericCAO *gcao = get_generic_cao(ref, L); - ObjectProperties *props = gcao->getProperties(); - lua_pushstring(L, props->nametag.c_str()); - return 1; + ClientObjectRef *ref = checkobject(L, 1); + GenericCAO *gcao = get_generic_cao(ref, L); + ObjectProperties *props = gcao->getProperties(); + lua_pushstring(L, props->nametag.c_str()); + return 1; } int ClientObjectRef::l_get_item_textures(lua_State *L) { - ClientObjectRef *ref = checkobject(L, 1); - GenericCAO *gcao = get_generic_cao(ref, L); - ObjectProperties *props = gcao->getProperties(); - lua_newtable(L); + ClientObjectRef *ref = checkobject(L, 1); + GenericCAO *gcao = get_generic_cao(ref, L); + ObjectProperties *props = gcao->getProperties(); + lua_newtable(L); - for (std::string &texture : props->textures) { - lua_pushstring(L, texture.c_str()); - } - return 1; + for (std::string &texture : props->textures) { + lua_pushstring(L, texture.c_str()); + } + return 1; } int ClientObjectRef::l_get_max_hp(lua_State *L) { - ClientObjectRef *ref = checkobject(L, 1); - GenericCAO *gcao = get_generic_cao(ref, L); - ObjectProperties *props = gcao->getProperties(); - lua_pushnumber(L, props->hp_max); - return 1; + ClientObjectRef *ref = checkobject(L, 1); + GenericCAO *gcao = get_generic_cao(ref, L); + ObjectProperties *props = gcao->getProperties(); + lua_pushnumber(L, props->hp_max); + return 1; } ClientObjectRef::ClientObjectRef(ClientActiveObject *object): - m_object(object) + m_object(object) { } void ClientObjectRef::create(lua_State *L, ClientActiveObject *object) { - if (object) { - ClientObjectRef *o = new ClientObjectRef(object); - *(void **)(lua_newuserdata(L, sizeof(void *))) = o; - luaL_getmetatable(L, className); - lua_setmetatable(L, -2); - } + if (object) { + ClientObjectRef *o = new ClientObjectRef(object); + *(void **)(lua_newuserdata(L, sizeof(void *))) = o; + luaL_getmetatable(L, className); + lua_setmetatable(L, -2); + } } int ClientObjectRef::gc_object(lua_State *L) { - ClientObjectRef *obj = *(ClientObjectRef **)(lua_touserdata(L, 1)); - delete obj; - return 0; + ClientObjectRef *obj = *(ClientObjectRef **)(lua_touserdata(L, 1)); + delete obj; + return 0; } // taken from LuaLocalPlayer void ClientObjectRef::Register(lua_State *L) { - lua_newtable(L); - int methodtable = lua_gettop(L); - luaL_newmetatable(L, className); - int metatable = lua_gettop(L); + lua_newtable(L); + int methodtable = lua_gettop(L); + luaL_newmetatable(L, className); + int metatable = lua_gettop(L); - lua_pushliteral(L, "__metatable"); - lua_pushvalue(L, methodtable); - lua_settable(L, metatable); // hide metatable from lua getmetatable() + lua_pushliteral(L, "__metatable"); + lua_pushvalue(L, methodtable); + lua_settable(L, metatable); // hide metatable from lua getmetatable() - lua_pushliteral(L, "__index"); - lua_pushvalue(L, methodtable); - lua_settable(L, metatable); + lua_pushliteral(L, "__index"); + lua_pushvalue(L, methodtable); + lua_settable(L, metatable); - lua_pushliteral(L, "__gc"); - lua_pushcfunction(L, gc_object); - lua_settable(L, metatable); + lua_pushliteral(L, "__gc"); + lua_pushcfunction(L, gc_object); + lua_settable(L, metatable); - lua_pop(L, 1); // Drop metatable + lua_pop(L, 1); // Drop metatable - luaL_openlib(L, 0, methods, 0); // fill methodtable - lua_pop(L, 1); // Drop methodtable + luaL_openlib(L, 0, methods, 0); // fill methodtable + lua_pop(L, 1); // Drop methodtable } const char ClientObjectRef::className[] = "ClientObjectRef"; luaL_Reg ClientObjectRef::methods[] = { - luamethod(ClientObjectRef, get_pos), - luamethod(ClientObjectRef, get_velocity), - luamethod(ClientObjectRef, get_acceleration), - luamethod(ClientObjectRef, get_rotation), - luamethod(ClientObjectRef, is_player), - luamethod(ClientObjectRef, get_name), - luamethod(ClientObjectRef, get_attach), - luamethod(ClientObjectRef, get_nametag), - luamethod(ClientObjectRef, get_item_textures), - luamethod(ClientObjectRef, get_max_hp), - {0, 0} + luamethod(ClientObjectRef, get_pos), + luamethod(ClientObjectRef, get_velocity), + luamethod(ClientObjectRef, get_acceleration), + luamethod(ClientObjectRef, get_rotation), + luamethod(ClientObjectRef, is_player), + luamethod(ClientObjectRef, get_name), + luamethod(ClientObjectRef, get_attach), + luamethod(ClientObjectRef, get_nametag), + luamethod(ClientObjectRef, get_item_textures), + luamethod(ClientObjectRef, get_max_hp), + {0, 0} }; From 7ed22368606b49bff31f60d2eaadbb96106ea85b Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein Date: Sun, 18 Oct 2020 14:21:24 +0200 Subject: [PATCH 126/266] Replaced spaces with tabs --- src/script/lua_api/l_clientobject.h | 64 ++++++++++++++--------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/src/script/lua_api/l_clientobject.h b/src/script/lua_api/l_clientobject.h index c538c339e..3022555f5 100644 --- a/src/script/lua_api/l_clientobject.h +++ b/src/script/lua_api/l_clientobject.h @@ -24,54 +24,54 @@ with this program; if not, write to the Free Software Foundation, Inc., class ClientObjectRef : public ModApiBase { public: - ClientObjectRef(ClientActiveObject *object); + ClientObjectRef(ClientActiveObject *object); - ~ClientObjectRef() = default; + ~ClientObjectRef() = default; - static void Register(lua_State *L); + static void Register(lua_State *L); - static void create(lua_State *L, ClientActiveObject *object); + static void create(lua_State *L, ClientActiveObject *object); - static ClientObjectRef *checkobject(lua_State *L, int narg); + static ClientObjectRef *checkobject(lua_State *L, int narg); private: - ClientActiveObject *m_object = nullptr; - static const char className[]; - static luaL_Reg methods[]; + ClientActiveObject *m_object = nullptr; + static const char className[]; + static luaL_Reg methods[]; - static ClientActiveObject *get_cao(ClientObjectRef *ref); - static GenericCAO *get_generic_cao(ClientObjectRef *ref, lua_State *L); + static ClientActiveObject *get_cao(ClientObjectRef *ref); + static GenericCAO *get_generic_cao(ClientObjectRef *ref, lua_State *L); - static int gc_object(lua_State *L); + static int gc_object(lua_State *L); - // get_pos(self) - // returns: {x=num, y=num, z=num} - static int l_get_pos(lua_State *L); + // get_pos(self) + // returns: {x=num, y=num, z=num} + static int l_get_pos(lua_State *L); - // get_velocity(self) - static int l_get_velocity(lua_State *L); + // get_velocity(self) + static int l_get_velocity(lua_State *L); - // get_acceleration(self) - static int l_get_acceleration(lua_State *L); + // get_acceleration(self) + static int l_get_acceleration(lua_State *L); - // get_rotation(self) - static int l_get_rotation(lua_State *L); + // get_rotation(self) + static int l_get_rotation(lua_State *L); - // is_player(self) - static int l_is_player(lua_State *L); + // is_player(self) + static int l_is_player(lua_State *L); - // get_name(self) - static int l_get_name(lua_State *L); + // get_name(self) + static int l_get_name(lua_State *L); - // get_attach(self) - static int l_get_attach(lua_State *L); + // get_attach(self) + static int l_get_attach(lua_State *L); - // get_nametag(self) - static int l_get_nametag(lua_State *L); + // get_nametag(self) + static int l_get_nametag(lua_State *L); - // get_textures(self) - static int l_get_item_textures(lua_State *L); + // get_textures(self) + static int l_get_item_textures(lua_State *L); - // get_hp(self) - static int l_get_max_hp(lua_State *L); + // get_hp(self) + static int l_get_max_hp(lua_State *L); }; From 151e5782e1f1ed02bb12757c8fe95ca6449b1b89 Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein Date: Sun, 18 Oct 2020 14:28:17 +0200 Subject: [PATCH 127/266] Lint is still not happy... --- src/script/cpp_api/s_cheats.cpp | 1 - src/script/lua_api/l_clientobject.cpp | 34 +++++++++++---------------- 2 files changed, 14 insertions(+), 21 deletions(-) diff --git a/src/script/cpp_api/s_cheats.cpp b/src/script/cpp_api/s_cheats.cpp index 45b75ff6d..3d0c5e645 100644 --- a/src/script/cpp_api/s_cheats.cpp +++ b/src/script/cpp_api/s_cheats.cpp @@ -29,7 +29,6 @@ ScriptApiCheatsCheat::ScriptApiCheatsCheat( { } - ScriptApiCheatsCheat::ScriptApiCheatsCheat(const std::string &name, const int &function) : m_name(name), m_setting(""), m_function_ref(function) { diff --git a/src/script/lua_api/l_clientobject.cpp b/src/script/lua_api/l_clientobject.cpp index db3563cc4..2c3b40c55 100644 --- a/src/script/lua_api/l_clientobject.cpp +++ b/src/script/lua_api/l_clientobject.cpp @@ -23,16 +23,13 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "client/client.h" #include "object_properties.h" -// should prob do some more NULL checking - - ClientObjectRef *ClientObjectRef::checkobject(lua_State *L, int narg) { luaL_checktype(L, narg, LUA_TUSERDATA); void *userdata = luaL_checkudata(L, narg, className); if (!userdata) luaL_typerror(L, narg, className); - return *(ClientObjectRef**)userdata; + return *(ClientObjectRef **)userdata; } ClientActiveObject *ClientObjectRef::get_cao(ClientObjectRef *ref) @@ -136,8 +133,7 @@ int ClientObjectRef::l_get_max_hp(lua_State *L) return 1; } -ClientObjectRef::ClientObjectRef(ClientActiveObject *object): - m_object(object) +ClientObjectRef::ClientObjectRef(ClientActiveObject *object) : m_object(object) { } @@ -151,7 +147,8 @@ void ClientObjectRef::create(lua_State *L, ClientActiveObject *object) } } -int ClientObjectRef::gc_object(lua_State *L) { +int ClientObjectRef::gc_object(lua_State *L) +{ ClientObjectRef *obj = *(ClientObjectRef **)(lua_touserdata(L, 1)); delete obj; return 0; @@ -184,16 +181,13 @@ void ClientObjectRef::Register(lua_State *L) } const char ClientObjectRef::className[] = "ClientObjectRef"; -luaL_Reg ClientObjectRef::methods[] = { - luamethod(ClientObjectRef, get_pos), - luamethod(ClientObjectRef, get_velocity), - luamethod(ClientObjectRef, get_acceleration), - luamethod(ClientObjectRef, get_rotation), - luamethod(ClientObjectRef, is_player), - luamethod(ClientObjectRef, get_name), - luamethod(ClientObjectRef, get_attach), - luamethod(ClientObjectRef, get_nametag), - luamethod(ClientObjectRef, get_item_textures), - luamethod(ClientObjectRef, get_max_hp), - {0, 0} -}; +luaL_Reg ClientObjectRef::methods[] = {luamethod(ClientObjectRef, get_pos), + luamethod(ClientObjectRef, get_velocity), + luamethod(ClientObjectRef, get_acceleration), + luamethod(ClientObjectRef, get_rotation), + luamethod(ClientObjectRef, is_player), + luamethod(ClientObjectRef, get_name), + luamethod(ClientObjectRef, get_attach), + luamethod(ClientObjectRef, get_nametag), + luamethod(ClientObjectRef, get_item_textures), + luamethod(ClientObjectRef, get_max_hp), {0, 0}}; From 7e0f8fba02ab66d88925759ca6165a0feecb8c05 Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein Date: Sun, 18 Oct 2020 14:33:13 +0200 Subject: [PATCH 128/266] Another Lint commit --- src/gui/cheatMenu.cpp | 4 ++-- src/script/lua_api/l_clientobject.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/gui/cheatMenu.cpp b/src/gui/cheatMenu.cpp index 6589374b1..b0ffb204b 100644 --- a/src/gui/cheatMenu.cpp +++ b/src/gui/cheatMenu.cpp @@ -48,8 +48,8 @@ void CheatMenu::drawEntry(video::IVideoDriver *driver, std::string name, int num } else { bool is_category = entry_type == CHEAT_MENU_ENTRY_TYPE_CATEGORY; y += m_gap + m_head_height + - (number + (is_category ? 0 : m_selected_category)) * - (m_entry_height + m_gap); + (number + (is_category ? 0 : m_selected_category)) * + (m_entry_height + m_gap); x += (is_category ? 0 : m_gap + m_entry_width); if (active) bgcolor = &m_active_bg_color; diff --git a/src/script/lua_api/l_clientobject.cpp b/src/script/lua_api/l_clientobject.cpp index 2c3b40c55..462de1a09 100644 --- a/src/script/lua_api/l_clientobject.cpp +++ b/src/script/lua_api/l_clientobject.cpp @@ -177,7 +177,7 @@ void ClientObjectRef::Register(lua_State *L) lua_pop(L, 1); // Drop metatable luaL_openlib(L, 0, methods, 0); // fill methodtable - lua_pop(L, 1); // Drop methodtable + lua_pop(L, 1); // Drop methodtable } const char ClientObjectRef::className[] = "ClientObjectRef"; From 1e4f3549292472323d49ffad4e856ba60dd81e0c Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein Date: Sun, 18 Oct 2020 14:36:20 +0200 Subject: [PATCH 129/266] This is the last try for lint... --- src/gui/cheatMenu.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/cheatMenu.cpp b/src/gui/cheatMenu.cpp index b0ffb204b..f796fb0ba 100644 --- a/src/gui/cheatMenu.cpp +++ b/src/gui/cheatMenu.cpp @@ -49,7 +49,7 @@ void CheatMenu::drawEntry(video::IVideoDriver *driver, std::string name, int num bool is_category = entry_type == CHEAT_MENU_ENTRY_TYPE_CATEGORY; y += m_gap + m_head_height + (number + (is_category ? 0 : m_selected_category)) * - (m_entry_height + m_gap); + (m_entry_height + m_gap); x += (is_category ? 0 : m_gap + m_entry_width); if (active) bgcolor = &m_active_bg_color; From 1a7d3d8188d3f484b6c3f0f05fd057d543a34725 Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein Date: Sun, 18 Oct 2020 16:18:07 +0200 Subject: [PATCH 130/266] Extended ClientObjectRef; Improved CrystalPvP --- builtin/client/cheats/combat.lua | 47 +++++++++++++++++++++++++++ builtin/client/cheats/init.lua | 2 ++ builtin/client/util.lua | 18 ++++++++++ builtin/common/misc_helpers.lua | 13 ++++++++ builtin/game/item.lua | 13 -------- builtin/settingtypes.txt | 2 ++ doc/client_lua_api.txt | 35 +++++++++++++++++++- src/defaultsettings.cpp | 1 + src/script/common/c_content.cpp | 7 ++-- src/script/lua_api/l_clientobject.cpp | 28 +++++++++++++++- src/script/lua_api/l_clientobject.h | 7 ++++ 11 files changed, 155 insertions(+), 18 deletions(-) create mode 100644 builtin/client/cheats/combat.lua diff --git a/builtin/client/cheats/combat.lua b/builtin/client/cheats/combat.lua new file mode 100644 index 000000000..25aaaec34 --- /dev/null +++ b/builtin/client/cheats/combat.lua @@ -0,0 +1,47 @@ +local placed_crystal +local switched_to_totem = 0 +local used_sneak = true + +core.register_globalstep(function(dtime) + if not minetest.settings:get_bool("crystal_pvp") then return end + local player = core.localplayer + if not player then return end + local control = player:get_control() + local pointed = core.get_pointed_thing() + local item = player:get_wielded_item():get_name() + if placed_crystal then + if core.switch_to_item("mobs_mc:totem") then + switched_to_totem = 5 + end + placed_crystal = false + elseif switched_to_totem > 0 then + if item ~= "mobs_mc:totem" then + switched_to_totem = 0 + elseif pointed and pointed.type == "object" then + pointed.ref:punch() + switched_to_totem = 0 + else + switched_to_totem = switched_to_totem + end + elseif control.RMB and item == "mcl_end:crystal" then + placed_crystal = true + elseif control.sneak then + if used_sneak then + core.switch_to_item("mobs_mc:totem") + return + end + core.switch_to_item("mcl_end:crystal") + if pointed and pointed.type == "node" then + local pos = core.get_pointed_thing_position(pointed) + local node = core.get_node_or_nil(pos) + if node and (node.name == "mcl_core:obsidian" or node.name == "mcl_core:bedrock") then + core.place_node(pos) + placed_crystal = true + end + end + used_sneak = true + else + used_sneak = false + end +end) + diff --git a/builtin/client/cheats/init.lua b/builtin/client/cheats/init.lua index 9eb2594a0..cdeae78c8 100644 --- a/builtin/client/cheats/init.lua +++ b/builtin/client/cheats/init.lua @@ -4,6 +4,7 @@ core.cheats = { ["AntiKnockback"] = "antiknockback", ["FastHit"] = "spamclick", ["AttachmentFloat"] = "float_above_parent", + ["CrystalPvP"] = "crystal_pvp", }, ["Movement"] = { ["Freecam"] = "freecam", @@ -70,6 +71,7 @@ end local cheatpath = core.get_builtin_path() .. "client" .. DIR_DELIM .. "cheats" .. DIR_DELIM dofile(cheatpath .. "chat.lua") +dofile(cheatpath .. "combat.lua") dofile(cheatpath .. "inventory.lua") dofile(cheatpath .. "movement.lua") dofile(cheatpath .. "player.lua") diff --git a/builtin/client/util.lua b/builtin/client/util.lua index 895609288..4a6a72d72 100644 --- a/builtin/client/util.lua +++ b/builtin/client/util.lua @@ -20,3 +20,21 @@ function core.parse_relative_pos(param) if success then pos = vector.round(vector.add(core.localplayer:get_pos(), pos)) end return success, pos end + +function core.switch_to_item(item) + for index, stack in ipairs(core.get_inventory("current_player").main) do + if stack:get_name() == item then + core.localplayer:set_wield_index(index - 1) + return true + end + end + return false +end + +function core.get_pointed_thing() + local pos = core.camera:get_pos() + local pos2 = vector.add(pos, vector.multiply(core.camera:get_look_dir(), 5)) + local ray = core.raycast(pos, pos2, true, true) + + return ray:next() +end diff --git a/builtin/common/misc_helpers.lua b/builtin/common/misc_helpers.lua index f6ceda54d..64d67bc72 100644 --- a/builtin/common/misc_helpers.lua +++ b/builtin/common/misc_helpers.lua @@ -708,6 +708,19 @@ function core.get_translator(textdomain) return function(str, ...) return core.translate(textdomain or "", str, ...) end end +function core.get_pointed_thing_position(pointed_thing, above) + if pointed_thing.type == "node" then + if above then + -- The position where a node would be placed + return pointed_thing.above + end + -- The position where a node would be dug + return pointed_thing.under + elseif pointed_thing.type == "object" then + return pointed_thing.ref and pointed_thing.ref:get_pos() + end +end + -------------------------------------------------------------------------------- -- Returns the exact coordinate of a pointed surface -------------------------------------------------------------------------------- diff --git a/builtin/game/item.lua b/builtin/game/item.lua index f680ce0d4..2c43a8548 100644 --- a/builtin/game/item.lua +++ b/builtin/game/item.lua @@ -24,19 +24,6 @@ function core.inventorycube(img1, img2, img3) .. "{" .. img3:gsub("%^", "&") end -function core.get_pointed_thing_position(pointed_thing, above) - if pointed_thing.type == "node" then - if above then - -- The position where a node would be placed - return pointed_thing.above - end - -- The position where a node would be dug - return pointed_thing.under - elseif pointed_thing.type == "object" then - return pointed_thing.ref and pointed_thing.ref:get_pos() - end -end - function core.dir_to_facedir(dir, is6d) --account for y if requested if is6d and math.abs(dir.y) > math.abs(dir.x) and math.abs(dir.y) > math.abs(dir.z) then diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 13603c726..f30a16789 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -2296,3 +2296,5 @@ block_water (BlockWater) bool false autotnt (PlaceOnTop) bool false replace (Replace) bool false + +crystal_pvp (CrystalPvP) bool false diff --git a/doc/client_lua_api.txt b/doc/client_lua_api.txt index 599ce0b83..735173580 100644 --- a/doc/client_lua_api.txt +++ b/doc/client_lua_api.txt @@ -219,7 +219,7 @@ For helper functions see "Vector helpers". ### pointed_thing * `{type="nothing"}` * `{type="node", under=pos, above=pos}` -* `{type="object", id=ObjectID}` +* `{type="object", ref=ClientObjectRef}` Flag Specifier Format --------------------- @@ -843,6 +843,13 @@ Call these functions only at load time! * `pos2`: end of the ray * `objects`: if false, only nodes will be returned. Default is `true`. * `liquids`: if false, liquid nodes won't be returned. Default is `false`. +* `minetest.get_pointed_thing()` returns `PointedThing` + * Returns the thing currently pointed by player +* `minetest.get_pointed_thing_position(pointed_thing, above)` + * Returns the position of a `pointed_thing` or `nil` if the `pointed_thing` + does not refer to a node or entity. + * If the optional `above` parameter is true and the `pointed_thing` refers + to a node, then it will return the `above` position of the `pointed_thing`. * `minetest.find_path(pos1,pos2,searchdistance,max_jump,max_drop,algorithm)` * returns table containing path that can be walked on * returns a table of 3D points representing a path from `pos1` to `pos2` or @@ -897,6 +904,9 @@ Call these functions only at load time! ### Client Environment * `minetest.get_player_names()` * Returns list of player names on server (nil if CSM_RF_READ_PLAYERINFO is enabled by server) +* `minetest.get_objects_inside_radius(pos, radius)`: returns a list of + ClientObjectRefs. + * `radius`: using an euclidean metric * `minetest.disconnect()` * Disconnect from the server and exit to main menu. * Returns `false` if the client is already disconnecting otherwise returns `true`. @@ -1002,6 +1012,10 @@ Passed to `HTTPApiTable.fetch` callback. Returned by * e.g. minetest.set_keypress("jump", true) will cause te player to jump until minetest.set_keypress("jump", false) is called or the player presses & releases the space bar himself * `minetest.get_inventory(location)` * Returns the inventory at location +* `minetest.switch_to_item(item)` + * `item` is an Itemstring + * searches to item in inventory, sets the wield index to it if found + * returns true on success, false if item was not found * `minetest.register_cheat(name, category, setting | function)` * Register an entry for the cheat menu * If the Category is nonexistant, it will be created @@ -1307,6 +1321,25 @@ Can be obtained via `minetest.get_meta(pos)`. * `fields`: key-value storage * `inventory`: `{list1 = {}, ...}}` +### ClientObjectRef + +Moving things in the game are generally these. +This is basically a reference to a C++ `GenericCAO`. + +#### Methods + +* `get_pos()`: returns `{x=num, y=num, z=num}` +* `get_velocity()`: returns the velocity, a vector +* `get_acceleration()`: returns the acceleration, a vector +* `get_rotation()`: returns the rotation, a vector (radians) +* `is_player()`: returns true if the object is a player +* `get_attach()`: returns parent or nil if it isn't attached. +* `get_nametag()`: returns the nametag (string) +* `get_item_textures()`: returns the textures +* `get_max_hp()`: returns the maximum heath +* `punch()`: punches the object +* `rightclick()`: rightclicks the object + ### `Raycast` A raycast on the map. It works with selection boxes. diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 96c4e0688..530bfcd8a 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -108,6 +108,7 @@ void set_default_settings(Settings *settings) settings->setDefault("block_water", "false"); settings->setDefault("autotnt", "false"); settings->setDefault("replace", "false"); + settings->setDefault("crystal_pvp", "false"); // Keymap settings->setDefault("remote_port", "30000"); diff --git a/src/script/common/c_content.cpp b/src/script/common/c_content.cpp index 9efa2c57f..7d4c1e748 100644 --- a/src/script/common/c_content.cpp +++ b/src/script/common/c_content.cpp @@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "object_properties.h" #include "collision.h" #include "cpp_api/s_node.h" +#include "lua_api/l_clientobject.h" #include "lua_api/l_object.h" #include "lua_api/l_item.h" #include "common/c_internal.h" @@ -1838,12 +1839,12 @@ void push_pointed_thing(lua_State *L, const PointedThing &pointed, bool csm, lua_setfield(L, -2, "type"); if (csm) { - lua_pushinteger(L, pointed.object_id); - lua_setfield(L, -2, "id"); + ClientObjectRef::create(L, pointed.object_id); } else { push_objectRef(L, pointed.object_id); - lua_setfield(L, -2, "ref"); } + + lua_setfield(L, -2, "ref"); } else { lua_pushstring(L, "nothing"); lua_setfield(L, -2, "type"); diff --git a/src/script/lua_api/l_clientobject.cpp b/src/script/lua_api/l_clientobject.cpp index 462de1a09..90f0bcd15 100644 --- a/src/script/lua_api/l_clientobject.cpp +++ b/src/script/lua_api/l_clientobject.cpp @@ -22,6 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "common/c_converter.h" #include "client/client.h" #include "object_properties.h" +#include "util/pointedthing.h" ClientObjectRef *ClientObjectRef::checkobject(lua_State *L, int narg) { @@ -133,6 +134,24 @@ int ClientObjectRef::l_get_max_hp(lua_State *L) return 1; } +int ClientObjectRef::l_punch(lua_State *L) +{ + ClientObjectRef *ref = checkobject(L, 1); + GenericCAO *gcao = get_generic_cao(ref, L); + PointedThing pointed(gcao->getId(), v3f(0,0,0), v3s16(0,0,0), 0); + getClient(L)->interact(INTERACT_START_DIGGING, pointed); + return 0; +} + +int ClientObjectRef::l_rightclick(lua_State *L) +{ + ClientObjectRef *ref = checkobject(L, 1); + GenericCAO *gcao = get_generic_cao(ref, L); + PointedThing pointed(gcao->getId(), v3f(0,0,0), v3s16(0,0,0), 0); + getClient(L)->interact(INTERACT_PLACE, pointed); + return 0; +} + ClientObjectRef::ClientObjectRef(ClientActiveObject *object) : m_object(object) { } @@ -147,6 +166,11 @@ void ClientObjectRef::create(lua_State *L, ClientActiveObject *object) } } +void ClientObjectRef::create(lua_State *L, s16 id) +{ + create(L, ((ClientEnvironment *)getEnv(L))->getActiveObject(id)); +} + int ClientObjectRef::gc_object(lua_State *L) { ClientObjectRef *obj = *(ClientObjectRef **)(lua_touserdata(L, 1)); @@ -190,4 +214,6 @@ luaL_Reg ClientObjectRef::methods[] = {luamethod(ClientObjectRef, get_pos), luamethod(ClientObjectRef, get_attach), luamethod(ClientObjectRef, get_nametag), luamethod(ClientObjectRef, get_item_textures), - luamethod(ClientObjectRef, get_max_hp), {0, 0}}; + luamethod(ClientObjectRef, get_max_hp), + luamethod(ClientObjectRef, punch), + luamethod(ClientObjectRef, rightclick), {0, 0}}; diff --git a/src/script/lua_api/l_clientobject.h b/src/script/lua_api/l_clientobject.h index 3022555f5..88a6956bc 100644 --- a/src/script/lua_api/l_clientobject.h +++ b/src/script/lua_api/l_clientobject.h @@ -31,6 +31,7 @@ public: static void Register(lua_State *L); static void create(lua_State *L, ClientActiveObject *object); + static void create(lua_State *L, s16 id); static ClientObjectRef *checkobject(lua_State *L, int narg); @@ -74,4 +75,10 @@ private: // get_hp(self) static int l_get_max_hp(lua_State *L); + + // punch(self) + static int l_punch(lua_State *L); + + // rightclick(self) + static int l_rightclick(lua_State *L); }; From 58e6b29d4bb5d4cbaf188bf226e5a59155b4c457 Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein Date: Sun, 18 Oct 2020 16:27:15 +0200 Subject: [PATCH 131/266] Test GitHub Discord Integration --- builtin/client/cheats/combat.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/builtin/client/cheats/combat.lua b/builtin/client/cheats/combat.lua index 25aaaec34..c52e93998 100644 --- a/builtin/client/cheats/combat.lua +++ b/builtin/client/cheats/combat.lua @@ -44,4 +44,3 @@ core.register_globalstep(function(dtime) used_sneak = false end end) - From 35da7306dcd07fd43252468cdf71c9d8c09faeeb Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein Date: Sun, 18 Oct 2020 16:30:04 +0200 Subject: [PATCH 132/266] Test GitHub Discord Integration --- builtin/client/cheats/combat.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/builtin/client/cheats/combat.lua b/builtin/client/cheats/combat.lua index c52e93998..25aaaec34 100644 --- a/builtin/client/cheats/combat.lua +++ b/builtin/client/cheats/combat.lua @@ -44,3 +44,4 @@ core.register_globalstep(function(dtime) used_sneak = false end end) + From db9eee2d80cc2a35bd133473d131f321dc4600c3 Mon Sep 17 00:00:00 2001 From: Paramat Date: Sun, 18 Oct 2020 22:50:31 +0100 Subject: [PATCH 133/266] Contributing doc: Minor improvements and a clarification (#10520) --- .github/CONTRIBUTING.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index e57ea9f83..b01a89509 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -45,11 +45,9 @@ Contributions are welcome! Here's how you can help: ### Important note about automated GitHub checks -When you submit a pull request, GitHub automatically runs checks on the Minetest Engine combined with your changes. One of these checks is called 'cpp lint / clang format', which checks code formatting. -Because formatting for readability requires human judgement this check often fails and often makes unsuitable formatting requests which make code readability worse. +When you submit a pull request, GitHub automatically runs checks on the Minetest Engine combined with your changes. One of these checks is called 'cpp lint / clang format', which checks code formatting. Because formatting for readability requires human judgement this check often fails and often makes unsuitable formatting requests which make code readability worse. -If this check fails, look at the details to check for any clear mistakes and correct those. However you should not apply everything ClangFormat requests, ignore requests that make code readability worse and any other clearly unsuitable requests. -Discuss with a core developer if in any doubt and for how to progress. +If this check fails, look at the details to check for any clear mistakes and correct those. However, you should not apply everything ClangFormat requests. Ignore requests that make code readability worse and any other clearly unsuitable requests. Discuss in the pull request with a core developer about how to progress. ## Issues From f1ff05bf5932a7825509dbe896e60183a96a6d36 Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein Date: Mon, 19 Oct 2020 13:09:38 +0200 Subject: [PATCH 134/266] Added ThroughWalls, InventoryActions API and AutoTotem --- builtin/client/cheats/combat.lua | 72 ++++---- builtin/client/cheats/init.lua | 2 + builtin/client/cheats/inventory.lua | 8 +- builtin/client/util.lua | 19 +- doc/client_lua_api.txt | 46 +++++ src/client/game.cpp | 2 +- src/defaultsettings.cpp | 2 + src/environment.cpp | 6 +- src/raycast.cpp | 5 +- src/raycast.h | 3 +- src/script/lua_api/CMakeLists.txt | 1 + src/script/lua_api/l_inventoryaction.cpp | 216 +++++++++++++++++++++++ src/script/lua_api/l_inventoryaction.h | 72 ++++++++ src/script/scripting_client.cpp | 2 + 14 files changed, 408 insertions(+), 48 deletions(-) create mode 100644 src/script/lua_api/l_inventoryaction.cpp create mode 100644 src/script/lua_api/l_inventoryaction.h diff --git a/builtin/client/cheats/combat.lua b/builtin/client/cheats/combat.lua index 25aaaec34..b497c6c1b 100644 --- a/builtin/client/cheats/combat.lua +++ b/builtin/client/cheats/combat.lua @@ -1,47 +1,57 @@ local placed_crystal local switched_to_totem = 0 local used_sneak = true +local totem_move_action = InventoryAction("move") +totem_move_action:to("current_player", "main", 8) core.register_globalstep(function(dtime) - if not minetest.settings:get_bool("crystal_pvp") then return end local player = core.localplayer if not player then return end local control = player:get_control() local pointed = core.get_pointed_thing() local item = player:get_wielded_item():get_name() - if placed_crystal then - if core.switch_to_item("mobs_mc:totem") then - switched_to_totem = 5 - end - placed_crystal = false - elseif switched_to_totem > 0 then - if item ~= "mobs_mc:totem" then - switched_to_totem = 0 - elseif pointed and pointed.type == "object" then - pointed.ref:punch() - switched_to_totem = 0 + if core.settings:get_bool("crystal_pvp") then + if placed_crystal then + if core.switch_to_item("mobs_mc:totem") then + switched_to_totem = 5 + end + placed_crystal = false + elseif switched_to_totem > 0 then + if item ~= "mobs_mc:totem" then + switched_to_totem = 0 + elseif pointed and pointed.type == "object" then + pointed.ref:punch() + switched_to_totem = 0 + else + switched_to_totem = switched_to_totem + end + elseif control.RMB and item == "mcl_end:crystal" then + placed_crystal = true + elseif control.sneak then + if pointed and pointed.type == "node" and not used_sneak then + local pos = core.get_pointed_thing_position(pointed) + local node = core.get_node_or_nil(pos) + if node and (node.name == "mcl_core:obsidian" or node.name == "mcl_core:bedrock") then + core.switch_to_item("mcl_end:crystal") + core.place_node(pos) + placed_crystal = true + end + end + used_sneak = true else - switched_to_totem = switched_to_totem + used_sneak = false end - elseif control.RMB and item == "mcl_end:crystal" then - placed_crystal = true - elseif control.sneak then - if used_sneak then - core.switch_to_item("mobs_mc:totem") - return - end - core.switch_to_item("mcl_end:crystal") - if pointed and pointed.type == "node" then - local pos = core.get_pointed_thing_position(pointed) - local node = core.get_node_or_nil(pos) - if node and (node.name == "mcl_core:obsidian" or node.name == "mcl_core:bedrock") then - core.place_node(pos) - placed_crystal = true + end + + if core.settings:get_bool("autototem") then + local totem_stack = core.get_inventory("current_player").main[9] + if totem_stack and totem_stack:get_name() ~= "mobs_mc:totem" then + local totem_index = core.find_item("mobs_mc:totem") + if totem_index then + totem_move_action:from("current_player", "main", totem_index - 1) + totem_move_action:apply() + player:set_wield_index(8) end end - used_sneak = true - else - used_sneak = false end end) - diff --git a/builtin/client/cheats/init.lua b/builtin/client/cheats/init.lua index cdeae78c8..a7be83cee 100644 --- a/builtin/client/cheats/init.lua +++ b/builtin/client/cheats/init.lua @@ -5,6 +5,8 @@ core.cheats = { ["FastHit"] = "spamclick", ["AttachmentFloat"] = "float_above_parent", ["CrystalPvP"] = "crystal_pvp", + ["AutoTotem"] = "autototem", + ["ThroughWalls"] = "dont_point_nodes", }, ["Movement"] = { ["Freecam"] = "freecam", diff --git a/builtin/client/cheats/inventory.lua b/builtin/client/cheats/inventory.lua index d12052d7c..faa7d1c0e 100644 --- a/builtin/client/cheats/inventory.lua +++ b/builtin/client/cheats/inventory.lua @@ -1,5 +1,6 @@ local elapsed_time = 0 local tick_time = 0.05 +local drop_action = InventoryAction("drop") core.register_globalstep(function(dtime) -- AutoEject @@ -9,11 +10,8 @@ core.register_globalstep(function(dtime) local inventory = core.get_inventory("current_player") for index, stack in pairs(inventory.main) do if table.indexof(list, stack:get_name()) ~= -1 then - local old_index = player:get_wield_index() - player:set_wield_index(index - 1) - core.drop_selected_item() - player:set_wield_index(old_index) - return + drop_action:from("current_player", "main", index - 1) + drop_action:apply() end end end diff --git a/builtin/client/util.lua b/builtin/client/util.lua index 4a6a72d72..d61b547c6 100644 --- a/builtin/client/util.lua +++ b/builtin/client/util.lua @@ -21,20 +21,27 @@ function core.parse_relative_pos(param) return success, pos end -function core.switch_to_item(item) +function core.find_item(item) for index, stack in ipairs(core.get_inventory("current_player").main) do if stack:get_name() == item then - core.localplayer:set_wield_index(index - 1) - return true + return index end end - return false +end + +function core.switch_to_item(item) + local i = core.find_item(item) + if i then + core.localplayer:set_wield_index(i - 1) + return true + else + return false + end end function core.get_pointed_thing() local pos = core.camera:get_pos() local pos2 = vector.add(pos, vector.multiply(core.camera:get_look_dir(), 5)) - local ray = core.raycast(pos, pos2, true, true) - + local ray = core.raycast(pos, pos2, true, core.settings:get_bool("point_liquids") or core.get_item_def(core.localplayer:get_wielded_item():get_name()).liquids_pointable) return ray:next() end diff --git a/doc/client_lua_api.txt b/doc/client_lua_api.txt index 735173580..50c33940e 100644 --- a/doc/client_lua_api.txt +++ b/doc/client_lua_api.txt @@ -1012,6 +1012,9 @@ Passed to `HTTPApiTable.fetch` callback. Returned by * e.g. minetest.set_keypress("jump", true) will cause te player to jump until minetest.set_keypress("jump", false) is called or the player presses & releases the space bar himself * `minetest.get_inventory(location)` * Returns the inventory at location +* `minetest.find_item(item)` + * finds and an item in the inventory + * returns index on success or nil if item is not found * `minetest.switch_to_item(item)` * `item` is an Itemstring * searches to item in inventory, sets the wield index to it if found @@ -1706,3 +1709,46 @@ Same as `image`, but does not accept a `position`; the position is instead deter -- ^ Uses texture (string) } +### InventoryAction +A reference to a C++ InventoryAction. You can move, drop and craft items in all accessible inventories using InventoryActions. + +#### methods + +* `InventoryAction(type)`: + * creates a new InventoryAction + * type is on of "move", "drop", or "craft", else returns nil +* `apply()`: + * applies the InventoryAction (InventoryActions can be applied multible times) +* `from(inventorylocation, listname, stack)` + * this is valid for move or drop actions + * when `apply()` is called items are moved / dropped from `listname` `inventorylocation` in` at `stack` +* `to(inventorylocation, listname, stack)` + * this is valid for move actions + * when `apply()` is called items are moved to `listname` in`inventorylocation` at `stack` +* `craft(inventoryaction)` + * this is valid for craft actions + * when `apply()` is called a craft event for this inventory will be triggered +* `set_count(count)` + * this is valid for all actions + * it specifies how many items to drop / craft / move + * `0` means move all items + * default count: `0` + +#### example + `local move_act = InventoryAction("move") + move_act:from("current_player", "main", 0) + move_act:to("current_player", "craft", 0) + move_act:set_count(1) + local craft_act = InventoryAction("craft") + craft_act:craft("current_player") + local drop_act = InventoryAction("drop") + drop_act:from("current_player", "craft", 0) + move_act:apply() + craft_act:apply() + drop_act:apply() + ` + * e.g. In first hotbar slot there are tree logs: Move one to craft field, then craft wood out of it and immediately drop it + + + + diff --git a/src/client/game.cpp b/src/client/game.cpp index 663fbd672..d8800d9ea 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -2458,7 +2458,7 @@ PointedThing Game::updatePointedThing( runData.selected_object = NULL; hud->pointing_at_object = false; - RaycastState s(shootline, look_for_object, liquids_pointable); + RaycastState s(shootline, look_for_object, liquids_pointable, ! g_settings->getBool("dont_point_nodes")); PointedThing result; env.continueRaycast(&s, &result); if (result.type == POINTEDTHING_OBJECT) { diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 530bfcd8a..2899f2509 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -109,6 +109,8 @@ void set_default_settings(Settings *settings) settings->setDefault("autotnt", "false"); settings->setDefault("replace", "false"); settings->setDefault("crystal_pvp", "false"); + settings->setDefault("autototem", "false"); + settings->setDefault("dont_point_nodes", "false"); // Keymap settings->setDefault("remote_port", "30000"); diff --git a/src/environment.cpp b/src/environment.cpp index 599d2f726..7acad313e 100644 --- a/src/environment.cpp +++ b/src/environment.cpp @@ -106,8 +106,10 @@ bool Environment::line_of_sight(v3f pos1, v3f pos2, v3s16 *p) Check if a node is pointable */ inline static bool isPointableNode(const MapNode &n, - const NodeDefManager *nodedef , bool liquids_pointable) + const NodeDefManager *nodedef , bool liquids_pointable, bool nodes_pointable) { + if (! nodes_pointable) + return false; const ContentFeatures &features = nodedef->get(n); return features.pointable || ((liquids_pointable || g_settings->getBool("point_liquids")) && features.isLiquid()); @@ -180,7 +182,7 @@ void Environment::continueRaycast(RaycastState *state, PointedThing *result) n = map.getNode(np, &is_valid_position); if (!(is_valid_position && isPointableNode(n, nodedef, - state->m_liquids_pointable))) { + state->m_liquids_pointable, state->m_nodes_pointable))) { continue; } diff --git a/src/raycast.cpp b/src/raycast.cpp index ebc40235d..a1993606f 100644 --- a/src/raycast.cpp +++ b/src/raycast.cpp @@ -57,12 +57,13 @@ bool RaycastSort::operator() (const PointedThing &pt1, RaycastState::RaycastState(const core::line3d &shootline, - bool objects_pointable, bool liquids_pointable) : + bool objects_pointable, bool liquids_pointable, bool nodes_pointable) : m_shootline(shootline), m_iterator(shootline.start / BS, shootline.getVector() / BS), m_previous_node(m_iterator.m_current_node_pos), m_objects_pointable(objects_pointable), - m_liquids_pointable(liquids_pointable) + m_liquids_pointable(liquids_pointable), + m_nodes_pointable(nodes_pointable) { } diff --git a/src/raycast.h b/src/raycast.h index 734efd6ad..4f5ca2a5b 100644 --- a/src/raycast.h +++ b/src/raycast.h @@ -38,7 +38,7 @@ public: * @param liquids pointable if false, liquid nodes won't be found */ RaycastState(const core::line3d &shootline, bool objects_pointable, - bool liquids_pointable); + bool liquids_pointable, bool nodes_pointable = true); //! Shootline of the raycast. core::line3d m_shootline; @@ -55,6 +55,7 @@ public: bool m_objects_pointable; bool m_liquids_pointable; + bool m_nodes_pointable; //! The code needs to search these nodes around the center node. core::aabbox3d m_search_range { 0, 0, 0, 0, 0, 0 }; diff --git a/src/script/lua_api/CMakeLists.txt b/src/script/lua_api/CMakeLists.txt index ee94737d6..f6963f707 100644 --- a/src/script/lua_api/CMakeLists.txt +++ b/src/script/lua_api/CMakeLists.txt @@ -6,6 +6,7 @@ set(common_SCRIPT_LUA_API_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/l_env.cpp ${CMAKE_CURRENT_SOURCE_DIR}/l_http.cpp ${CMAKE_CURRENT_SOURCE_DIR}/l_inventory.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/l_inventoryaction.cpp ${CMAKE_CURRENT_SOURCE_DIR}/l_item.cpp ${CMAKE_CURRENT_SOURCE_DIR}/l_itemstackmeta.cpp ${CMAKE_CURRENT_SOURCE_DIR}/l_mapgen.cpp diff --git a/src/script/lua_api/l_inventoryaction.cpp b/src/script/lua_api/l_inventoryaction.cpp new file mode 100644 index 000000000..516d6d3b2 --- /dev/null +++ b/src/script/lua_api/l_inventoryaction.cpp @@ -0,0 +1,216 @@ +/* +Dragonfire +Copyright (C) 2020 Elias Fleckenstein + +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. +*/ + +#include "l_inventoryaction.h" +#include "l_internal.h" +#include "client/client.h" + +int LuaInventoryAction::gc_object(lua_State *L) +{ + LuaInventoryAction *o = *(LuaInventoryAction **)(lua_touserdata(L, 1)); + delete o; + return 0; +} + +int LuaInventoryAction::mt_tostring(lua_State *L) +{ + LuaInventoryAction *o = checkobject(L, 1); + std::ostringstream os(std::ios::binary); + o->m_action->serialize(os); + lua_pushfstring(L, "InventoryAction(\"%s\")", os.str().c_str()); + return 1; +} + +int LuaInventoryAction::l_apply(lua_State *L) +{ + LuaInventoryAction *o = checkobject(L, 1); + + std::ostringstream os(std::ios::binary); + o->m_action->serialize(os); + + std::istringstream is(os.str(), std::ios_base::binary); + + InventoryAction *a = InventoryAction::deSerialize(is); + + getClient(L)->inventoryAction(a); + return 0; +} + +int LuaInventoryAction::l_from(lua_State *L) +{ + GET_MOVE_ACTION + readFullInventoryLocationInto(L, &act->from_inv, &act->from_list, &act->from_i); + return 0; +} + +int LuaInventoryAction::l_to(lua_State *L) +{ + GET_MOVE_ACTION + readFullInventoryLocationInto(L, &act->to_inv, &act->to_list, &act->to_i); + return 0; +} + +int LuaInventoryAction::l_craft(lua_State *L) +{ + LuaInventoryAction *o = checkobject(L, 1); + + if (o->m_action->getType() != IAction::Craft) + return 0; + + std::string locStr; + InventoryLocation loc; + + locStr = readParam(L, 2); + + try { + loc.deSerialize(locStr); + dynamic_cast(o->m_action)->craft_inv = loc; + } catch (SerializationError &) {} + + return 0; +} + +int LuaInventoryAction::l_set_count(lua_State *L) +{ + LuaInventoryAction *o = checkobject(L, 1); + + s16 count = luaL_checkinteger(L, 2); + + switch (o->m_action->getType()) { + case IAction::Move: + ((IMoveAction *)o->m_action)->count = count; + break; + case IAction::Drop: + ((IDropAction *)o->m_action)->count = count; + break; + case IAction::Craft: + ((ICraftAction *)o->m_action)->count = count; + break; + } + + return 0; +} + +LuaInventoryAction::LuaInventoryAction(const IAction &type) : m_action(nullptr) +{ + switch (type) { + case IAction::Move: + m_action = new IMoveAction(); + break; + case IAction::Drop: + m_action = new IDropAction(); + break; + case IAction::Craft: + m_action = new ICraftAction(); + break; + } +} + +LuaInventoryAction::~LuaInventoryAction() +{ + delete m_action; +} + +void LuaInventoryAction::readFullInventoryLocationInto(lua_State *L, InventoryLocation *loc, std::string *list, s16 *index) +{ + try { + loc->deSerialize(readParam(L, 2)); + std::string l = readParam(L, 3); + *list = l; + *index = luaL_checkinteger(L, 4); + } catch (SerializationError &) {} +} + +int LuaInventoryAction::create_object(lua_State *L) +{ + IAction type; + std::string typeStr; + + typeStr = readParam(L, 1); + + if (typeStr == "move") + type = IAction::Move; + else if (typeStr == "drop") + type = IAction::Drop; + else if (typeStr == "craft") + type = IAction::Craft; + else + return 0; + + LuaInventoryAction *o = new LuaInventoryAction(type); + *(void **)(lua_newuserdata(L, sizeof(void *))) = o; + luaL_getmetatable(L, className); + lua_setmetatable(L, -2); + return 1; +} + +int LuaInventoryAction::create(lua_State *L, const IAction &type) +{ + LuaInventoryAction *o = new LuaInventoryAction(type); + *(void **)(lua_newuserdata(L, sizeof(void *))) = o; + luaL_getmetatable(L, className); + lua_setmetatable(L, -2); + return 1; +} + +LuaInventoryAction *LuaInventoryAction::checkobject(lua_State *L, int narg) +{ + return *(LuaInventoryAction **)luaL_checkudata(L, narg, className); +} + +void LuaInventoryAction::Register(lua_State *L) +{ + lua_newtable(L); + int methodtable = lua_gettop(L); + luaL_newmetatable(L, className); + int metatable = lua_gettop(L); + + lua_pushliteral(L, "__metatable"); + lua_pushvalue(L, methodtable); + lua_settable(L, metatable); + + lua_pushliteral(L, "__index"); + lua_pushvalue(L, methodtable); + lua_settable(L, metatable); + + lua_pushliteral(L, "__gc"); + lua_pushcfunction(L, gc_object); + lua_settable(L, metatable); + + lua_pushliteral(L, "__tostring"); + lua_pushcfunction(L, mt_tostring); + lua_settable(L, metatable); + + lua_pop(L, 1); + + luaL_openlib(L, 0, methods, 0); + lua_pop(L, 1); + + lua_register(L, className, create_object); +} + +const char LuaInventoryAction::className[] = "InventoryAction"; +const luaL_Reg LuaInventoryAction::methods[] = { + luamethod(LuaInventoryAction, apply), + luamethod(LuaInventoryAction, from), + luamethod(LuaInventoryAction, to), + luamethod(LuaInventoryAction, craft), + luamethod(LuaInventoryAction, set_count), + {0,0} +}; diff --git a/src/script/lua_api/l_inventoryaction.h b/src/script/lua_api/l_inventoryaction.h new file mode 100644 index 000000000..c1a96d010 --- /dev/null +++ b/src/script/lua_api/l_inventoryaction.h @@ -0,0 +1,72 @@ +/* +Dragonfire +Copyright (C) 2020 Elias Fleckenstein + +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. +*/ + +#include "inventorymanager.h" +#include "lua_api/l_base.h" + +#define GET_MOVE_ACTION \ + LuaInventoryAction *o = checkobject(L, 1); \ + if (o->m_action->getType() == IAction::Craft) \ + return 0; \ + MoveAction *act = dynamic_cast(o->m_action); + +class LuaInventoryAction : public ModApiBase { +private: + InventoryAction *m_action; + + static void readFullInventoryLocationInto(lua_State *L, InventoryLocation *loc, std::string *list, s16 *index); + + static const char className[]; + static const luaL_Reg methods[]; + + // Exported functions + + // garbage collector + static int gc_object(lua_State *L); + + // __tostring metamethod + static int mt_tostring(lua_State *L); + + // apply(self) + static int l_apply(lua_State *L); + + // from(self, location, list, index) + static int l_from(lua_State *L); + + // to(self, location, list, index) + static int l_to(lua_State *L); + + // craft(self, location) + static int l_craft(lua_State *L); + + // set_count(self, count) + static int l_set_count(lua_State *L); + +public: + LuaInventoryAction(const IAction &type); + ~LuaInventoryAction(); + + // LuaInventoryAction(inventory action type) + // Creates an LuaInventoryAction and leaves it on top of stack + static int create_object(lua_State *L); + // Not callable from Lua + static int create(lua_State *L, const IAction &type); + static LuaInventoryAction* checkobject(lua_State *L, int narg); + static void Register(lua_State *L); +}; diff --git a/src/script/scripting_client.cpp b/src/script/scripting_client.cpp index df6594666..729645678 100644 --- a/src/script/scripting_client.cpp +++ b/src/script/scripting_client.cpp @@ -25,6 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "lua_api/l_client.h" #include "lua_api/l_clientobject.h" #include "lua_api/l_env.h" +#include "lua_api/l_inventoryaction.h" #include "lua_api/l_item.h" #include "lua_api/l_itemstackmeta.h" #include "lua_api/l_minimap.h" @@ -80,6 +81,7 @@ void ClientScripting::InitializeModApi(lua_State *L, int top) ModChannelRef::Register(L); LuaSettings::Register(L); ClientObjectRef::Register(L); + LuaInventoryAction::Register(L); ModApiItemMod::Initialize(L, top); ModApiUtil::InitializeClient(L, top); From b826e3973065a0bb81269c8decb5a33073508164 Mon Sep 17 00:00:00 2001 From: Lars Date: Sun, 18 Oct 2020 16:38:51 -0700 Subject: [PATCH 135/266] Minor clientmap improvements. - Avoid calculating isBlockInSight for blocks without meshes. - Add metric for how many blocks the client has currently loaded. - Make some variables constant. --- src/client/clientmap.cpp | 36 +++++++++++++++++++----------------- src/mapsector.h | 1 + 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/src/client/clientmap.cpp b/src/client/clientmap.cpp index d372a8e46..c8561def6 100644 --- a/src/client/clientmap.cpp +++ b/src/client/clientmap.cpp @@ -122,14 +122,17 @@ void ClientMap::updateDrawList() } m_drawlist.clear(); - v3f camera_position = m_camera_position; - v3f camera_direction = m_camera_direction; + const v3f camera_position = m_camera_position; + const v3f camera_direction = m_camera_direction; + const f32 camera_fov = m_camera_fov; v3s16 cam_pos_nodes = floatToInt(camera_position, BS); v3s16 p_blocks_min; v3s16 p_blocks_max; getBlocksInViewRange(cam_pos_nodes, &p_blocks_min, &p_blocks_max); + // Number of blocks currently loaded by the client + u32 blocks_loaded = 0; // Number of blocks with mesh in rendering range u32 blocks_in_range_with_mesh = 0; // Number of blocks occlusion culled @@ -154,6 +157,7 @@ void ClientMap::updateDrawList() MapSector *sector = sector_it.second; v2s16 sp = sector->getPos(); + blocks_loaded += sector->size(); if (!m_control.range_all) { if (sp.X < p_blocks_min.X || sp.X > p_blocks_max.X || sp.Y < p_blocks_min.Z || sp.Y > p_blocks_max.Z) @@ -175,8 +179,12 @@ void ClientMap::updateDrawList() if not seen on display */ - if (block->mesh) + if (block->mesh) { block->mesh->updateCameraOffset(m_camera_offset); + } else { + // Ignore if mesh doesn't exist + continue; + } float range = 100000 * BS; if (!m_control.range_all) @@ -184,14 +192,7 @@ void ClientMap::updateDrawList() float d = 0.0; if (!isBlockInSight(block->getPos(), camera_position, - camera_direction, m_camera_fov, range, &d)) - continue; - - - /* - Ignore if mesh doesn't exist - */ - if (!block->mesh) + camera_direction, camera_fov, range, &d)) continue; blocks_in_range_with_mesh++; @@ -222,6 +223,7 @@ void ClientMap::updateDrawList() g_profiler->avg("MapBlock meshes in range [#]", blocks_in_range_with_mesh); g_profiler->avg("MapBlocks occlusion culled [#]", blocks_occlusion_culled); g_profiler->avg("MapBlocks drawn [#]", m_drawlist.size()); + g_profiler->avg("MapBlocks loaded [#]", blocks_loaded); } struct MeshBufList @@ -287,13 +289,13 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) /* Get animation parameters */ - float animation_time = m_client->getAnimationTime(); - int crack = m_client->getCrackLevel(); - u32 daynight_ratio = m_client->getEnv().getDayNightRatio(); + const float animation_time = m_client->getAnimationTime(); + const int crack = m_client->getCrackLevel(); + const u32 daynight_ratio = m_client->getEnv().getDayNightRatio(); - v3f camera_position = m_camera_position; - v3f camera_direction = m_camera_direction; - f32 camera_fov = m_camera_fov; + const v3f camera_position = m_camera_position; + const v3f camera_direction = m_camera_direction; + const f32 camera_fov = m_camera_fov; /* Get all blocks and draw all visible ones diff --git a/src/mapsector.h b/src/mapsector.h index dede364f6..ffd4cdd1d 100644 --- a/src/mapsector.h +++ b/src/mapsector.h @@ -62,6 +62,7 @@ public: bool empty() const { return m_blocks.empty(); } + int size() const { return m_blocks.size(); } protected: // The pile of MapBlocks From 660115c1abc76f3d4f6a6597ed0c4737465c6c55 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Mon, 19 Oct 2020 20:38:15 +0200 Subject: [PATCH 136/266] Decouple entity minimap markers from nametags replacing with show_on_minimap property (#10443) --- doc/lua_api.txt | 4 ++++ src/client/camera.h | 2 -- src/client/client.cpp | 2 ++ src/client/content_cao.cpp | 27 +++++++++++++++++++++++++++ src/client/content_cao.h | 4 ++++ src/client/minimap.cpp | 33 +++++++++++++++++++++++++-------- src/client/minimap.h | 11 +++++++++++ src/object_properties.cpp | 8 +++++++- src/object_properties.h | 1 + src/script/common/c_content.cpp | 3 +++ src/server/player_sao.cpp | 1 + 11 files changed, 85 insertions(+), 11 deletions(-) diff --git a/doc/lua_api.txt b/doc/lua_api.txt index d3aaa309c..9a46c7b57 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -6880,6 +6880,10 @@ Player properties need to be saved manually. shaded = true, -- Setting this to 'false' disables diffuse lighting of entity + + show_on_minimap = false, + -- Defaults to true for players, false for other entities. + -- If set to true the entity will show as a marker on the minimap. } Entity definition diff --git a/src/client/camera.h b/src/client/camera.h index 3a59637bc..16a1961be 100644 --- a/src/client/camera.h +++ b/src/client/camera.h @@ -169,8 +169,6 @@ public: void removeNametag(Nametag *nametag); - const std::list &getNametags() { return m_nametags; } - void drawNametags(); inline void addArmInertia(f32 player_yaw); diff --git a/src/client/client.cpp b/src/client/client.cpp index 0bd98b256..af69d0ec9 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -326,6 +326,8 @@ Client::~Client() } delete m_minimap; + m_minimap = nullptr; + delete m_media_downloader; } diff --git a/src/client/content_cao.cpp b/src/client/content_cao.cpp index fae06554a..42184b08f 100644 --- a/src/client/content_cao.cpp +++ b/src/client/content_cao.cpp @@ -47,6 +47,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include #include "client/shader.h" +#include "client/minimap.h" class Settings; struct ToolCapabilities; @@ -566,6 +567,9 @@ void GenericCAO::removeFromScene(bool permanent) m_client->getCamera()->removeNametag(m_nametag); m_nametag = nullptr; } + + if (m_marker && m_client->getMinimap()) + m_client->getMinimap()->removeMarker(&m_marker); } void GenericCAO::addToScene(ITextureSource *tsrc) @@ -794,6 +798,7 @@ void GenericCAO::addToScene(ITextureSource *tsrc) node->setParent(m_matrixnode); updateNametag(); + updateMarker(); updateNodePos(); updateAnimation(); updateBonePosition(); @@ -885,6 +890,23 @@ u16 GenericCAO::getLightPosition(v3s16 *pos) return 3; } +void GenericCAO::updateMarker() +{ + if (!m_prop.show_on_minimap) { + if (m_marker) + m_client->getMinimap()->removeMarker(&m_marker); + return; + } + + if (m_marker) + return; + + scene::ISceneNode *node = getSceneNode(); + if (!node) + return; + m_marker = m_client->getMinimap()->addMarker(node); +} + void GenericCAO::updateNametag() { if (m_is_local_player) // No nametag for local player @@ -1576,6 +1598,8 @@ void GenericCAO::processMessage(const std::string &data) u8 cmd = readU8(is); if (cmd == AO_CMD_SET_PROPERTIES) { ObjectProperties newprops; + newprops.show_on_minimap = m_is_player; // default + newprops.deSerialize(is); // Check what exactly changed @@ -1609,6 +1633,8 @@ void GenericCAO::processMessage(const std::string &data) if ((m_is_player && !m_is_local_player) && m_prop.nametag.empty()) m_prop.nametag = m_name; + if (m_is_local_player) + m_prop.show_on_minimap = false; if (expire_visuals) { expireVisuals(); @@ -1621,6 +1647,7 @@ void GenericCAO::processMessage(const std::string &data) updateTextures(m_current_texture_modifier); } updateNametag(); + updateMarker(); } } else if (cmd == AO_CMD_UPDATE_POSITION) { // Not sent by the server if this object is an attachment. diff --git a/src/client/content_cao.h b/src/client/content_cao.h index daf697767..435fc2931 100644 --- a/src/client/content_cao.h +++ b/src/client/content_cao.h @@ -30,6 +30,7 @@ with this program; if not, write to the Free Software Foundation, Inc., class Camera; class Client; struct Nametag; +struct MinimapMarker; /* SmoothTranslator @@ -84,6 +85,7 @@ private: scene::IBillboardSceneNode *m_spritenode = nullptr; scene::IDummyTransformationSceneNode *m_matrixnode = nullptr; Nametag *m_nametag = nullptr; + MinimapMarker *m_marker = nullptr; v3f m_position = v3f(0.0f, 10.0f * BS, 0); v3f m_velocity; v3f m_acceleration; @@ -254,6 +256,8 @@ public: void updateNametag(); + void updateMarker(); + void updateNodePos(); void step(float dtime, ClientEnvironment *env); diff --git a/src/client/minimap.cpp b/src/client/minimap.cpp index 6bae408b4..d068ad5f7 100644 --- a/src/client/minimap.cpp +++ b/src/client/minimap.cpp @@ -252,6 +252,10 @@ Minimap::~Minimap() driver->removeTexture(data->minimap_overlay_square); driver->removeTexture(data->object_marker_red); + for (MinimapMarker *m : m_markers) + delete m; + m_markers.clear(); + delete data; delete m_minimap_update_thread; } @@ -678,21 +682,34 @@ void Minimap::drawMinimap(core::rect rect) { } } +MinimapMarker* Minimap::addMarker(scene::ISceneNode *parent_node) +{ + MinimapMarker *m = new MinimapMarker(parent_node); + m_markers.push_back(m); + return m; +} + +void Minimap::removeMarker(MinimapMarker **m) +{ + m_markers.remove(*m); + delete *m; + *m = nullptr; +} + void Minimap::updateActiveMarkers() { video::IImage *minimap_mask = data->minimap_shape_round ? data->minimap_mask_round : data->minimap_mask_square; - const std::list &nametags = client->getCamera()->getNametags(); - m_active_markers.clear(); + v3f cam_offset = intToFloat(client->getCamera()->getOffset(), BS); + v3s16 pos_offset = data->pos - v3s16(data->mode.map_size / 2, + data->mode.scan_height / 2, + data->mode.map_size / 2); - for (Nametag *nametag : nametags) { - v3s16 pos = floatToInt(nametag->parent_node->getAbsolutePosition() + - intToFloat(client->getCamera()->getOffset(), BS), BS); - pos -= data->pos - v3s16(data->mode.map_size / 2, - data->mode.scan_height / 2, - data->mode.map_size / 2); + for (MinimapMarker *marker : m_markers) { + v3s16 pos = floatToInt(marker->parent_node->getAbsolutePosition() + + cam_offset, BS) - pos_offset; if (pos.X < 0 || pos.X > data->mode.map_size || pos.Y < 0 || pos.Y > data->mode.scan_height || pos.Z < 0 || pos.Z > data->mode.map_size) { diff --git a/src/client/minimap.h b/src/client/minimap.h index 11374b116..4a2c462f8 100644 --- a/src/client/minimap.h +++ b/src/client/minimap.h @@ -48,6 +48,13 @@ struct MinimapModeDef { u16 scale; }; +struct MinimapMarker { + MinimapMarker(scene::ISceneNode *parent_node): + parent_node(parent_node) + { + } + scene::ISceneNode *parent_node; +}; struct MinimapPixel { //! The topmost node that the minimap displays. MapNode n; @@ -142,6 +149,9 @@ public: scene::SMeshBuffer *getMinimapMeshBuffer(); + MinimapMarker* addMarker(scene::ISceneNode *parent_node); + void removeMarker(MinimapMarker **marker); + void updateActiveMarkers(); void drawMinimap(); void drawMinimap(core::rect rect); @@ -162,5 +172,6 @@ private: u16 m_surface_mode_scan_height; f32 m_angle; std::mutex m_mutex; + std::list m_markers; std::list m_active_markers; }; diff --git a/src/object_properties.cpp b/src/object_properties.cpp index c31c667e7..f31773060 100644 --- a/src/object_properties.cpp +++ b/src/object_properties.cpp @@ -70,6 +70,7 @@ std::string ObjectProperties::dump() os << ", use_texture_alpha=" << use_texture_alpha; os << ", damage_texture_modifier=" << damage_texture_modifier; os << ", shaded=" << shaded; + os << ", show_on_minimap=" << show_on_minimap; return os.str(); } @@ -118,6 +119,7 @@ void ObjectProperties::serialize(std::ostream &os) const writeU8(os, use_texture_alpha); os << serializeString16(damage_texture_modifier); writeU8(os, shaded); + writeU8(os, show_on_minimap); // Add stuff only at the bottom. // Never remove anything, because we don't want new versions of this @@ -174,7 +176,11 @@ void ObjectProperties::deSerialize(std::istream &is) damage_texture_modifier = deSerializeString16(is); u8 tmp = readU8(is); if (is.eof()) - throw SerializationError(""); + return; shaded = tmp; + tmp = readU8(is); + if (is.eof()) + return; + show_on_minimap = tmp; } catch (SerializationError &e) {} } diff --git a/src/object_properties.h b/src/object_properties.h index f010c1daf..adb483527 100644 --- a/src/object_properties.h +++ b/src/object_properties.h @@ -62,6 +62,7 @@ struct ObjectProperties float zoom_fov = 0.0f; bool use_texture_alpha = false; bool shaded = true; + bool show_on_minimap = false; ObjectProperties(); std::string dump(); diff --git a/src/script/common/c_content.cpp b/src/script/common/c_content.cpp index a95cf94a1..e3cb9042e 100644 --- a/src/script/common/c_content.cpp +++ b/src/script/common/c_content.cpp @@ -331,6 +331,7 @@ void read_object_properties(lua_State *L, int index, getfloatfield(L, -1, "zoom_fov", prop->zoom_fov); getboolfield(L, -1, "use_texture_alpha", prop->use_texture_alpha); getboolfield(L, -1, "shaded", prop->shaded); + getboolfield(L, -1, "show_on_minimap", prop->show_on_minimap); getstringfield(L, -1, "damage_texture_modifier", prop->damage_texture_modifier); } @@ -419,6 +420,8 @@ void push_object_properties(lua_State *L, ObjectProperties *prop) lua_setfield(L, -2, "shaded"); lua_pushlstring(L, prop->damage_texture_modifier.c_str(), prop->damage_texture_modifier.size()); lua_setfield(L, -2, "damage_texture_modifier"); + lua_pushboolean(L, prop->show_on_minimap); + lua_setfield(L, -2, "show_on_minimap"); } /******************************************************************************/ diff --git a/src/server/player_sao.cpp b/src/server/player_sao.cpp index 344d18a20..9fb53380c 100644 --- a/src/server/player_sao.cpp +++ b/src/server/player_sao.cpp @@ -55,6 +55,7 @@ PlayerSAO::PlayerSAO(ServerEnvironment *env_, RemotePlayer *player_, session_t p m_prop.backface_culling = false; m_prop.makes_footstep_sound = true; m_prop.stepheight = PLAYER_DEFAULT_STEPHEIGHT * BS; + m_prop.show_on_minimap = true; m_hp = m_prop.hp_max; m_breath = m_prop.breath_max; // Disable zoom in survival mode using a value of 0 From 4f2303849e0f929524695a9e3719ec486b47ddd1 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Mon, 19 Oct 2020 20:38:33 +0200 Subject: [PATCH 137/266] Implement unloading of static_save=false objects according to existing docs (#10485) --- src/server/luaentity_sao.h | 1 + src/server/player_sao.h | 1 + src/server/serveractiveobject.h | 9 +++++++++ src/serverenvironment.cpp | 9 ++++++--- 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/server/luaentity_sao.h b/src/server/luaentity_sao.h index 2520c8f5d..e060aa06d 100644 --- a/src/server/luaentity_sao.h +++ b/src/server/luaentity_sao.h @@ -42,6 +42,7 @@ public: void step(float dtime, bool send_recommended); std::string getClientInitializationData(u16 protocol_version); bool isStaticAllowed() const { return m_prop.static_save; } + bool shouldUnload() const { return true; } void getStaticData(std::string *result) const; u16 punch(v3f dir, const ToolCapabilities *toolcap = nullptr, ServerActiveObject *puncher = nullptr, diff --git a/src/server/player_sao.h b/src/server/player_sao.h index 8571bd4f9..3e178d4fc 100644 --- a/src/server/player_sao.h +++ b/src/server/player_sao.h @@ -83,6 +83,7 @@ public: void addedToEnvironment(u32 dtime_s); void removingFromEnvironment(); bool isStaticAllowed() const { return false; } + bool shouldUnload() const { return false; } std::string getClientInitializationData(u16 protocol_version); void getStaticData(std::string *result) const; void step(float dtime, bool send_recommended); diff --git a/src/server/serveractiveobject.h b/src/server/serveractiveobject.h index 927009aef..2764d159e 100644 --- a/src/server/serveractiveobject.h +++ b/src/server/serveractiveobject.h @@ -125,6 +125,7 @@ public: assert(isStaticAllowed()); *result = ""; } + /* Return false in here to never save and instead remove object on unload. getStaticData() will not be called in that case. @@ -132,6 +133,14 @@ public: virtual bool isStaticAllowed() const {return true;} + /* + Return false here to never unload the object. + isStaticAllowed && shouldUnload -> unload when out of active block range + !isStaticAllowed && shouldUnload -> unload when block is unloaded + */ + virtual bool shouldUnload() const + { return true; } + // Returns tool wear virtual u16 punch(v3f dir, const ToolCapabilities *toolcap = nullptr, diff --git a/src/serverenvironment.cpp b/src/serverenvironment.cpp index 6ef56efc8..d044b003d 100644 --- a/src/serverenvironment.cpp +++ b/src/serverenvironment.cpp @@ -1972,8 +1972,8 @@ void ServerEnvironment::deactivateFarObjects(bool _force_delete) // force_delete might be overriden per object bool force_delete = _force_delete; - // Do not deactivate if static data creation not allowed - if (!force_delete && !obj->isStaticAllowed()) + // Do not deactivate if disallowed + if (!force_delete && !obj->shouldUnload()) return false; // removeRemovedObjects() is responsible for these @@ -2002,7 +2002,10 @@ void ServerEnvironment::deactivateFarObjects(bool _force_delete) } // If block is still active, don't remove - if (!force_delete && m_active_blocks.contains(blockpos_o)) + bool still_active = obj->isStaticAllowed() ? + m_active_blocks.contains(blockpos_o) : + getMap().getBlockNoCreateNoEx(blockpos_o) != nullptr; + if (!force_delete && still_active) return false; verbosestream << "ServerEnvironment::deactivateFarObjects(): " From c7aa92aaed27ad8e10af7463b154b5b580c86da5 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Tue, 20 Oct 2020 16:34:14 +0200 Subject: [PATCH 138/266] Fix show_on_minimap default value for local player fixes #10526 --- src/client/content_cao.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/client/content_cao.cpp b/src/client/content_cao.cpp index 42184b08f..29a3acf25 100644 --- a/src/client/content_cao.cpp +++ b/src/client/content_cao.cpp @@ -354,6 +354,8 @@ void GenericCAO::initialize(const std::string &data) m_is_local_player = true; m_is_visible = false; player->setCAO(this); + + m_prop.show_on_minimap = false; } } From 4d9c9186ce61e11e13bc35ce1a319e568cf08662 Mon Sep 17 00:00:00 2001 From: Paramat Date: Tue, 20 Oct 2020 22:13:27 +0100 Subject: [PATCH 139/266] Devtest: Automatically enable zoom capability (#10493) Make minor improvements to the zoom testing chat command. Delete incorrect line about creative mode from README.md. --- games/devtest/README.md | 1 - games/devtest/mods/util_commands/init.lua | 10 ++++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/games/devtest/README.md b/games/devtest/README.md index 8b71da625..77e722af7 100644 --- a/games/devtest/README.md +++ b/games/devtest/README.md @@ -23,7 +23,6 @@ Basically, just create a world and start. A few important things to note: * Use the `/infplace` command to toggle infinite node placement in-game * Use the Param2 Tool to change the param2 of nodes; it's useful to experiment with the various drawtype test nodes * Check out the game settings and server commands for additional tests and features -* Creative Mode does nothing (apart from default engine behavior) Confused by a certain node or item? Check out for inline code comments. The usages of most tools are explained in their tooltips. diff --git a/games/devtest/mods/util_commands/init.lua b/games/devtest/mods/util_commands/init.lua index f2a155fb2..ca5dca2d9 100644 --- a/games/devtest/mods/util_commands/init.lua +++ b/games/devtest/mods/util_commands/init.lua @@ -36,8 +36,12 @@ minetest.register_chatcommand("hp", { end, }) -minetest.register_chatcommand("zoom", { - params = "[]", +minetest.register_on_joinplayer(function(player) + player:set_properties({zoom_fov = 15}) +end) + +minetest.register_chatcommand("zoomfov", { + params = "[]", description = "Set or display your zoom_fov", func = function(name, param) local player = minetest.get_player_by_name(name) @@ -58,8 +62,6 @@ minetest.register_chatcommand("zoom", { end, }) - - local s_infplace = minetest.settings:get("devtest_infplace") if s_infplace == "true" then infplace = true From 130d476f6a00416809e0cf89adf5f7e8caa3df08 Mon Sep 17 00:00:00 2001 From: realOneplustwo Date: Tue, 20 Oct 2020 14:22:56 -0700 Subject: [PATCH 140/266] Changed Cheat Menu UI --- src/gui/cheatMenu.cpp | 64 ++++++++++++++++++++++++------------------- src/gui/cheatMenu.h | 12 ++++---- 2 files changed, 42 insertions(+), 34 deletions(-) diff --git a/src/gui/cheatMenu.cpp b/src/gui/cheatMenu.cpp index f796fb0ba..c5f01b610 100644 --- a/src/gui/cheatMenu.cpp +++ b/src/gui/cheatMenu.cpp @@ -24,7 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc., CheatMenu::CheatMenu(Client *client) : m_client(client) { - m_font = g_fontengine->getFont(FONT_SIZE_UNSPECIFIED, FM_Fallback); + m_font = g_fontengine->getFont(FONT_SIZE_UNSPECIFIED, FM_Mono); if (!m_font) { errorstream << "CheatMenu: Unable to load fallback font" << std::endl; @@ -38,7 +38,7 @@ CheatMenu::CheatMenu(Client *client) : m_client(client) } void CheatMenu::drawEntry(video::IVideoDriver *driver, std::string name, int number, - bool selected, bool active, CheatMenuEntryType entry_type) + bool selected, bool active, int number2, CheatMenuEntryType entry_type) { int x = m_gap, y = m_gap, width = m_entry_width, height = m_entry_height; video::SColor *bgcolor = &m_bg_color, *fontcolor = &m_font_color; @@ -47,10 +47,8 @@ void CheatMenu::drawEntry(video::IVideoDriver *driver, std::string name, int num height = m_head_height; } else { bool is_category = entry_type == CHEAT_MENU_ENTRY_TYPE_CATEGORY; - y += m_gap + m_head_height + - (number + (is_category ? 0 : m_selected_category)) * - (m_entry_height + m_gap); - x += (is_category ? 0 : m_gap + m_entry_width); + x += m_gap + (is_category? number : number2) * (m_entry_width + m_gap); + y += m_head_height + (is_category ? 0 : number + 1) * (m_entry_height + m_gap); if (active) bgcolor = &m_active_bg_color; if (selected) @@ -72,20 +70,20 @@ void CheatMenu::draw(video::IVideoDriver *driver, bool show_debug) CHEAT_MENU_GET_SCRIPTPTR if (!show_debug) - drawEntry(driver, "Dragonfireclient", 0, false, false, + drawEntry(driver, "Dragonfireclient", 0, false, false, 0, CHEAT_MENU_ENTRY_TYPE_HEAD); int category_count = 0; for (auto category = script->m_cheat_categories.begin(); category != script->m_cheat_categories.end(); category++) { bool is_selected = category_count == m_selected_category; - drawEntry(driver, (*category)->m_name, category_count, is_selected, false, + drawEntry(driver, (*category)->m_name, category_count, is_selected, false, category_count, CHEAT_MENU_ENTRY_TYPE_CATEGORY); if (is_selected && m_cheat_layer) { int cheat_count = 0; for (auto cheat = (*category)->m_cheats.begin(); cheat != (*category)->m_cheats.end(); cheat++) { drawEntry(driver, (*cheat)->m_name, cheat_count, - cheat_count == m_selected_cheat, + cheat_count == m_selected_cheat, category_count, (*cheat)->is_enabled()); cheat_count++; } @@ -94,47 +92,57 @@ void CheatMenu::draw(video::IVideoDriver *driver, bool show_debug) } } -void CheatMenu::selectUp() +void CheatMenu::selectLeft() { CHEAT_MENU_GET_SCRIPTPTR - int max = (m_cheat_layer ? script->m_cheat_categories[m_selected_category] - ->m_cheats.size() - : script->m_cheat_categories.size()) - - 1; - int *selected = m_cheat_layer ? &m_selected_cheat : &m_selected_category; + int max = script->m_cheat_categories.size() - 1; + int *selected = &m_selected_category; --*selected; if (*selected < 0) *selected = max; } -void CheatMenu::selectDown() +void CheatMenu::selectRight() { CHEAT_MENU_GET_SCRIPTPTR - int max = (m_cheat_layer ? script->m_cheat_categories[m_selected_category] - ->m_cheats.size() - : script->m_cheat_categories.size()) - - 1; - int *selected = m_cheat_layer ? &m_selected_cheat : &m_selected_category; + int max = script->m_cheat_categories.size() - 1; + int *selected = &m_selected_category; ++*selected; if (*selected > max) *selected = 0; } -void CheatMenu::selectRight() +void CheatMenu::selectDown() { - if (m_cheat_layer) - return; + CHEAT_MENU_GET_SCRIPTPTR + m_cheat_layer = true; - m_selected_cheat = 0; + + int max = script->m_cheat_categories[m_selected_category]->m_cheats.size(); + int *selected = &m_selected_cheat; + ++*selected; + if (*selected > max) { + *selected = 1; + } } -void CheatMenu::selectLeft() +void CheatMenu::selectUp() { - if (!m_cheat_layer) + if (!m_cheat_layer) { return; - m_cheat_layer = false; + } + + CHEAT_MENU_GET_SCRIPTPTR + + int *selected = &m_selected_cheat; + --*selected; + + if (*selected < 0) { + m_cheat_layer = false; + *selected = 1; + } } void CheatMenu::selectConfirm() diff --git a/src/gui/cheatMenu.h b/src/gui/cheatMenu.h index ea9a9d853..61637b1d4 100644 --- a/src/gui/cheatMenu.h +++ b/src/gui/cheatMenu.h @@ -44,7 +44,7 @@ public: void draw(video::IVideoDriver *driver, bool show_debug); void drawEntry(video::IVideoDriver *driver, std::string name, int number, - bool selected, bool active, + bool selected, bool active, int category_count, CheatMenuEntryType entry_type = CHEAT_MENU_ENTRY_TYPE_ENTRY); void selectUp(); @@ -58,14 +58,14 @@ private: int m_selected_cheat = 0; int m_selected_category = 0; - int m_head_height = 50; - int m_entry_height = 40; + int m_head_height = 20; + int m_entry_height = 20; int m_entry_width = 200; int m_gap = 3; - video::SColor m_bg_color = video::SColor(192, 255, 145, 88); - video::SColor m_active_bg_color = video::SColor(192, 255, 87, 53); - video::SColor m_font_color = video::SColor(255, 0, 0, 0); + video::SColor m_bg_color = video::SColor(39, 38, 51, 173); + video::SColor m_active_bg_color = video::SColor(45, 45, 107, 100); + video::SColor m_font_color = video::SColor(195, 195, 195, 0); video::SColor m_selected_font_color = video::SColor(255, 255, 252, 88); Client *m_client; From f605308ee3da739aee2a690c870c5b567d11d859 Mon Sep 17 00:00:00 2001 From: Josiah VanderZee Date: Tue, 20 Oct 2020 18:28:25 -0500 Subject: [PATCH 141/266] Improve drawEntry. --- src/gui/cheatMenu.cpp | 49 ++++++++++++++++++++++++++++--------------- src/gui/cheatMenu.h | 10 +++++---- 2 files changed, 38 insertions(+), 21 deletions(-) diff --git a/src/gui/cheatMenu.cpp b/src/gui/cheatMenu.cpp index c5f01b610..d72a14480 100644 --- a/src/gui/cheatMenu.cpp +++ b/src/gui/cheatMenu.cpp @@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "script/scripting_client.h" #include "client/client.h" #include "client/fontengine.h" +#include CheatMenu::CheatMenu(Client *client) : m_client(client) { @@ -37,25 +38,39 @@ CheatMenu::CheatMenu(Client *client) : m_client(client) m_fontsize.Y = MYMAX(m_fontsize.Y, 1); } -void CheatMenu::drawEntry(video::IVideoDriver *driver, std::string name, int number, - bool selected, bool active, int number2, CheatMenuEntryType entry_type) +void CheatMenu::drawEntry(video::IVideoDriver *driver, std::string name, + std::size_t column_align_index, std::size_t cheat_entry_index, + bool is_selected, bool is_enabled, CheatMenuEntryType entry_type) { int x = m_gap, y = m_gap, width = m_entry_width, height = m_entry_height; video::SColor *bgcolor = &m_bg_color, *fontcolor = &m_font_color; - if (entry_type == CHEAT_MENU_ENTRY_TYPE_HEAD) { + + // Align with correct column. + x += m_gap + column_align_index * (m_entry_width + m_gap); + + if (is_selected) + fontcolor = &m_selected_font_color; + if (is_enabled) bgcolor = &m_active_bg_color; + + switch (entry_type) + { + case CHEAT_MENU_ENTRY_TYPE_HEAD: height = m_head_height; - } else { - bool is_category = entry_type == CHEAT_MENU_ENTRY_TYPE_CATEGORY; - x += m_gap + (is_category? number : number2) * (m_entry_width + m_gap); - y += m_head_height + (is_category ? 0 : number + 1) * (m_entry_height + m_gap); - if (active) - bgcolor = &m_active_bg_color; - if (selected) - fontcolor = &m_selected_font_color; + break; + case CHEAT_MENU_ENTRY_TYPE_CATEGORY: + y += m_head_height + m_gap; + break; + case CHEAT_MENU_ENTRY_TYPE_ENTRY: + y += m_head_height + (cheat_entry_index + 1) * (m_entry_height + m_gap); + break; + default: + // TODO log an error or something. + break; } + driver->draw2DRectangle(*bgcolor, core::rect(x, y, x + width, y + height)); - if (selected) + if (is_selected) driver->draw2DRectangleOutline( core::rect(x - 1, y - 1, x + width, y + height), *fontcolor); @@ -70,20 +85,20 @@ void CheatMenu::draw(video::IVideoDriver *driver, bool show_debug) CHEAT_MENU_GET_SCRIPTPTR if (!show_debug) - drawEntry(driver, "Dragonfireclient", 0, false, false, 0, + drawEntry(driver, "Dragonfireclient", 0, 0, false, false, CHEAT_MENU_ENTRY_TYPE_HEAD); int category_count = 0; for (auto category = script->m_cheat_categories.begin(); category != script->m_cheat_categories.end(); category++) { bool is_selected = category_count == m_selected_category; - drawEntry(driver, (*category)->m_name, category_count, is_selected, false, category_count, - CHEAT_MENU_ENTRY_TYPE_CATEGORY); + drawEntry(driver, (*category)->m_name, category_count, 0, is_selected, + false, CHEAT_MENU_ENTRY_TYPE_CATEGORY); if (is_selected && m_cheat_layer) { int cheat_count = 0; for (auto cheat = (*category)->m_cheats.begin(); cheat != (*category)->m_cheats.end(); cheat++) { - drawEntry(driver, (*cheat)->m_name, cheat_count, - cheat_count == m_selected_cheat, category_count, + drawEntry(driver, (*cheat)->m_name, category_count, cheat_count, + cheat_count == m_selected_cheat, (*cheat)->is_enabled()); cheat_count++; } diff --git a/src/gui/cheatMenu.h b/src/gui/cheatMenu.h index 61637b1d4..e7fb4dc93 100644 --- a/src/gui/cheatMenu.h +++ b/src/gui/cheatMenu.h @@ -20,6 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once #include "irrlichttypes_extrabloated.h" +#include #include #define CHEAT_MENU_GET_SCRIPTPTR \ @@ -29,12 +30,12 @@ with this program; if not, write to the Free Software Foundation, Inc., class Client; -typedef enum +enum CheatMenuEntryType { CHEAT_MENU_ENTRY_TYPE_HEAD, CHEAT_MENU_ENTRY_TYPE_CATEGORY, CHEAT_MENU_ENTRY_TYPE_ENTRY, -} CheatMenuEntryType; +}; class CheatMenu { @@ -43,8 +44,9 @@ public: void draw(video::IVideoDriver *driver, bool show_debug); - void drawEntry(video::IVideoDriver *driver, std::string name, int number, - bool selected, bool active, int category_count, + void drawEntry(video::IVideoDriver *driver, std::string name, + std::size_t column_align_index, std::size_t cheat_entry_index, + bool is_selected, bool is_enabled, CheatMenuEntryType entry_type = CHEAT_MENU_ENTRY_TYPE_ENTRY); void selectUp(); From b211e90ffcabefc3c75cf00ee053970951f01488 Mon Sep 17 00:00:00 2001 From: Josiah Date: Wed, 21 Oct 2020 09:16:32 -0500 Subject: [PATCH 142/266] Prepare cheatMenu::draw function for easier UI changes. --- src/gui/cheatMenu.cpp | 19 +++++++++---------- src/gui/cheatMenu.h | 5 +++++ 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/gui/cheatMenu.cpp b/src/gui/cheatMenu.cpp index d72a14480..9225414a2 100644 --- a/src/gui/cheatMenu.cpp +++ b/src/gui/cheatMenu.cpp @@ -81,25 +81,24 @@ void CheatMenu::drawEntry(video::IVideoDriver *driver, std::string name, } void CheatMenu::draw(video::IVideoDriver *driver, bool show_debug) -{ - CHEAT_MENU_GET_SCRIPTPTR + + ClientScripting *script{ getScript() }; + if (!script || !script->m_cheats_loaded) if (!show_debug) drawEntry(driver, "Dragonfireclient", 0, 0, false, false, CHEAT_MENU_ENTRY_TYPE_HEAD); int category_count = 0; - for (auto category = script->m_cheat_categories.begin(); - category != script->m_cheat_categories.end(); category++) { + for (const auto &menu_item : m_cheat_categories) { bool is_selected = category_count == m_selected_category; - drawEntry(driver, (*category)->m_name, category_count, 0, is_selected, + drawEntry(driver, menu_item.m_name, category_count, 0, is_selected, false, CHEAT_MENU_ENTRY_TYPE_CATEGORY); if (is_selected && m_cheat_layer) { int cheat_count = 0; - for (auto cheat = (*category)->m_cheats.begin(); - cheat != (*category)->m_cheats.end(); cheat++) { - drawEntry(driver, (*cheat)->m_name, category_count, cheat_count, + for (const auto &sub_menu_item : menu_item.m_cheats) { + drawEntry(driver, sub_menu_item.m_name, category_count, cheat_count, cheat_count == m_selected_cheat, - (*cheat)->is_enabled()); + sub_menu_item.is_enabled()); cheat_count++; } } @@ -132,7 +131,7 @@ void CheatMenu::selectRight() void CheatMenu::selectDown() { CHEAT_MENU_GET_SCRIPTPTR - + m_cheat_layer = true; int max = script->m_cheat_categories[m_selected_category]->m_cheats.size(); diff --git a/src/gui/cheatMenu.h b/src/gui/cheatMenu.h index e7fb4dc93..e42edfbb0 100644 --- a/src/gui/cheatMenu.h +++ b/src/gui/cheatMenu.h @@ -42,6 +42,11 @@ class CheatMenu public: CheatMenu(Client *client); + Client* getScript() + { + return m_client->getScript(); + } + void draw(video::IVideoDriver *driver, bool show_debug); void drawEntry(video::IVideoDriver *driver, std::string name, From 5862410083da5da02a8bc29a76e7bb1f4977e331 Mon Sep 17 00:00:00 2001 From: Josiah Date: Wed, 21 Oct 2020 09:27:39 -0500 Subject: [PATCH 143/266] Add missing return. --- src/gui/cheatMenu.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/gui/cheatMenu.cpp b/src/gui/cheatMenu.cpp index 9225414a2..d5e93038c 100644 --- a/src/gui/cheatMenu.cpp +++ b/src/gui/cheatMenu.cpp @@ -84,10 +84,13 @@ void CheatMenu::draw(video::IVideoDriver *driver, bool show_debug) ClientScripting *script{ getScript() }; if (!script || !script->m_cheats_loaded) + return; + // Draw menu header if debug info is not being drawn. if (!show_debug) drawEntry(driver, "Dragonfireclient", 0, 0, false, false, CHEAT_MENU_ENTRY_TYPE_HEAD); + int category_count = 0; for (const auto &menu_item : m_cheat_categories) { bool is_selected = category_count == m_selected_category; From 1ef72ad9cccd3191b24c064aebe7849a5be6e2e7 Mon Sep 17 00:00:00 2001 From: Josiah Date: Wed, 21 Oct 2020 09:57:08 -0500 Subject: [PATCH 144/266] Fix indentation style. --- src/gui/cheatMenu.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/cheatMenu.cpp b/src/gui/cheatMenu.cpp index d5e93038c..440d3abf6 100644 --- a/src/gui/cheatMenu.cpp +++ b/src/gui/cheatMenu.cpp @@ -86,7 +86,7 @@ void CheatMenu::draw(video::IVideoDriver *driver, bool show_debug) if (!script || !script->m_cheats_loaded) return; - // Draw menu header if debug info is not being drawn. + // Draw menu header if debug info is not being drawn. if (!show_debug) drawEntry(driver, "Dragonfireclient", 0, 0, false, false, CHEAT_MENU_ENTRY_TYPE_HEAD); From aea9b36ef70dd35e8dfa370043ae5e3f3c751cb4 Mon Sep 17 00:00:00 2001 From: realOneplustwo Date: Wed, 21 Oct 2020 09:43:26 -0700 Subject: [PATCH 145/266] Improved Colours --- src/gui/cheatMenu.cpp | 7 ++----- src/gui/cheatMenu.h | 15 ++++++--------- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/src/gui/cheatMenu.cpp b/src/gui/cheatMenu.cpp index d72a14480..548ddea73 100644 --- a/src/gui/cheatMenu.cpp +++ b/src/gui/cheatMenu.cpp @@ -1,17 +1,14 @@ /* Dragonfire Copyright (C) 2020 Elias Fleckenstein - 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. @@ -72,7 +69,7 @@ void CheatMenu::drawEntry(video::IVideoDriver *driver, std::string name, driver->draw2DRectangle(*bgcolor, core::rect(x, y, x + width, y + height)); if (is_selected) driver->draw2DRectangleOutline( - core::rect(x - 1, y - 1, x + width, y + height), + core::rect(x - 2, y - 2, x + width + 1, y + height + 1), *fontcolor); int fx = x + 5, fy = y + (height - m_fontsize.Y) / 2; core::rect fontbounds( @@ -167,4 +164,4 @@ void CheatMenu::selectConfirm() if (m_cheat_layer) script->toggle_cheat(script->m_cheat_categories[m_selected_category] ->m_cheats[m_selected_cheat]); -} +} \ No newline at end of file diff --git a/src/gui/cheatMenu.h b/src/gui/cheatMenu.h index e7fb4dc93..4ffaa177d 100644 --- a/src/gui/cheatMenu.h +++ b/src/gui/cheatMenu.h @@ -1,17 +1,14 @@ /* Dragonfire Copyright (C) 2020 Elias Fleckenstein - 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. @@ -62,16 +59,16 @@ private: int m_head_height = 20; int m_entry_height = 20; - int m_entry_width = 200; + int m_entry_width = 150; int m_gap = 3; - video::SColor m_bg_color = video::SColor(39, 38, 51, 173); - video::SColor m_active_bg_color = video::SColor(45, 45, 107, 100); - video::SColor m_font_color = video::SColor(195, 195, 195, 0); - video::SColor m_selected_font_color = video::SColor(255, 255, 252, 88); + video::SColor m_bg_color = video::SColor(173, 45, 45, 68); + video::SColor m_active_bg_color = video::SColor(210, 0, 0, 0); + video::SColor m_font_color = video::SColor(195, 255, 255, 255); + video::SColor m_selected_font_color = video::SColor(235, 255, 255, 255); Client *m_client; gui::IGUIFont *m_font = nullptr; v2u32 m_fontsize; -}; +}; \ No newline at end of file From 7aff09ab232704b0abc8d95d107c07f67f581e2e Mon Sep 17 00:00:00 2001 From: Josiah Date: Wed, 21 Oct 2020 11:47:38 -0500 Subject: [PATCH 146/266] Fix overindent! --- src/gui/cheatMenu.cpp | 12 ++++++------ src/gui/cheatMenu.h | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/gui/cheatMenu.cpp b/src/gui/cheatMenu.cpp index 440d3abf6..0afe1051a 100644 --- a/src/gui/cheatMenu.cpp +++ b/src/gui/cheatMenu.cpp @@ -39,8 +39,8 @@ CheatMenu::CheatMenu(Client *client) : m_client(client) } void CheatMenu::drawEntry(video::IVideoDriver *driver, std::string name, - std::size_t column_align_index, std::size_t cheat_entry_index, - bool is_selected, bool is_enabled, CheatMenuEntryType entry_type) + std::size_t column_align_index, std::size_t cheat_entry_index, + bool is_selected, bool is_enabled, CheatMenuEntryType entry_type) { int x = m_gap, y = m_gap, width = m_entry_width, height = m_entry_height; video::SColor *bgcolor = &m_bg_color, *fontcolor = &m_font_color; @@ -89,19 +89,19 @@ void CheatMenu::draw(video::IVideoDriver *driver, bool show_debug) // Draw menu header if debug info is not being drawn. if (!show_debug) drawEntry(driver, "Dragonfireclient", 0, 0, false, false, - CHEAT_MENU_ENTRY_TYPE_HEAD); + CHEAT_MENU_ENTRY_TYPE_HEAD); int category_count = 0; for (const auto &menu_item : m_cheat_categories) { bool is_selected = category_count == m_selected_category; drawEntry(driver, menu_item.m_name, category_count, 0, is_selected, - false, CHEAT_MENU_ENTRY_TYPE_CATEGORY); + false, CHEAT_MENU_ENTRY_TYPE_CATEGORY); if (is_selected && m_cheat_layer) { int cheat_count = 0; for (const auto &sub_menu_item : menu_item.m_cheats) { drawEntry(driver, sub_menu_item.m_name, category_count, cheat_count, - cheat_count == m_selected_cheat, - sub_menu_item.is_enabled()); + cheat_count == m_selected_cheat, + sub_menu_item.is_enabled()); cheat_count++; } } diff --git a/src/gui/cheatMenu.h b/src/gui/cheatMenu.h index e42edfbb0..5aba306e0 100644 --- a/src/gui/cheatMenu.h +++ b/src/gui/cheatMenu.h @@ -50,9 +50,9 @@ public: void draw(video::IVideoDriver *driver, bool show_debug); void drawEntry(video::IVideoDriver *driver, std::string name, - std::size_t column_align_index, std::size_t cheat_entry_index, - bool is_selected, bool is_enabled, - CheatMenuEntryType entry_type = CHEAT_MENU_ENTRY_TYPE_ENTRY); + std::size_t column_align_index, std::size_t cheat_entry_index, + bool is_selected, bool is_enabled, + CheatMenuEntryType entry_type = CHEAT_MENU_ENTRY_TYPE_ENTRY); void selectUp(); void selectDown(); From ea88dde4be225d19c12bea4a581aa21f17237070 Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein Date: Wed, 21 Oct 2020 18:51:57 +0200 Subject: [PATCH 147/266] Added Strip, AutoRefill, indexing for InventoryActions and Wield Index starts at 1 now --- builtin/client/chatcommands.lua | 2 +- builtin/client/cheats/combat.lua | 6 +-- builtin/client/cheats/init.lua | 2 + builtin/client/cheats/inventory.lua | 48 ++++++++++++++++++++---- builtin/client/util.lua | 6 +-- builtin/settingtypes.txt | 8 ++++ doc/client_lua_api.txt | 11 +++--- src/defaultsettings.cpp | 2 + src/script/lua_api/l_inventoryaction.cpp | 2 +- src/script/lua_api/l_localplayer.cpp | 4 +- 10 files changed, 68 insertions(+), 23 deletions(-) diff --git a/builtin/client/chatcommands.lua b/builtin/client/chatcommands.lua index 7c3dd521e..8090b2bef 100644 --- a/builtin/client/chatcommands.lua +++ b/builtin/client/chatcommands.lua @@ -155,7 +155,7 @@ core.register_chatcommand("place", { func = function(param) local success, pos = core.parse_relative_pos(param) if success then - cores.place_node(pos) + core.place_node(pos) return true, "Node placed at " .. core.pos_to_string(pos) end return false, pos diff --git a/builtin/client/cheats/combat.lua b/builtin/client/cheats/combat.lua index b497c6c1b..1f9ba9a91 100644 --- a/builtin/client/cheats/combat.lua +++ b/builtin/client/cheats/combat.lua @@ -2,7 +2,7 @@ local placed_crystal local switched_to_totem = 0 local used_sneak = true local totem_move_action = InventoryAction("move") -totem_move_action:to("current_player", "main", 8) +totem_move_action:to("current_player", "main", 9) core.register_globalstep(function(dtime) local player = core.localplayer @@ -48,9 +48,9 @@ core.register_globalstep(function(dtime) if totem_stack and totem_stack:get_name() ~= "mobs_mc:totem" then local totem_index = core.find_item("mobs_mc:totem") if totem_index then - totem_move_action:from("current_player", "main", totem_index - 1) + totem_move_action:from("current_player", "main", totem_index) totem_move_action:apply() - player:set_wield_index(8) + player:set_wield_index(9) end end end diff --git a/builtin/client/cheats/init.lua b/builtin/client/cheats/init.lua index a7be83cee..c579f2b89 100644 --- a/builtin/client/cheats/init.lua +++ b/builtin/client/cheats/init.lua @@ -62,6 +62,8 @@ core.cheats = { ["Enderchest"] = function() core.open_enderchest() end, ["HandSlot"] = function() core.open_handslot() end, ["NextItem"] = "next_item", + ["Strip"] = "strip", + ["AutoRefill"] = "autorefill", } } diff --git a/builtin/client/cheats/inventory.lua b/builtin/client/cheats/inventory.lua index faa7d1c0e..b9943f507 100644 --- a/builtin/client/cheats/inventory.lua +++ b/builtin/client/cheats/inventory.lua @@ -2,15 +2,48 @@ local elapsed_time = 0 local tick_time = 0.05 local drop_action = InventoryAction("drop") +local strip_move_act = InventoryAction("move") +strip_move_act:to("current_player", "craft", 1) +local strip_craft_act = InventoryAction("craft") +strip_craft_act:craft("current_player") +local strip_move_back_act = InventoryAction("move") +strip_move_back_act:from("current_player", "craftresult", 1) + core.register_globalstep(function(dtime) + local player = core.localplayer + if not player then return end + local item = player:get_wielded_item() + local itemdef = core.get_item_def(item:get_name()) + local wieldindex = player:get_wield_index() + -- AutoRefill + if core.settings:get_bool("autorefill") and itemdef then + local space = item:get_free_space() + local i = core.find_item(item:get_name(), wieldindex + 1) + if i and space > 0 then + local move_act = InventoryAction("move") + move_act:to("current_player", "main", wieldindex) + move_act:from("current_player", "main", i) + move_act:set_count(space) + move_act:apply() + end + end + -- Strip + if core.settings:get_bool("strip") then + if itemdef and itemdef.groups.tree and player:get_control().RMB then + strip_move_act:from("current_player", "main", wieldindex) + strip_move_back_act:to("current_player", "main", wieldindex) + strip_move_act:apply() + strip_craft_act:apply() + strip_move_back_act:apply() + end + end -- AutoEject if core.settings:get_bool("autoeject") then - local player = core.localplayer local list = (core.settings:get("eject_items") or ""):split(",") local inventory = core.get_inventory("current_player") for index, stack in pairs(inventory.main) do if table.indexof(list, stack:get_name()) ~= -1 then - drop_action:from("current_player", "main", index - 1) + drop_action:from("current_player", "main", index) drop_action:apply() end end @@ -19,12 +52,8 @@ core.register_globalstep(function(dtime) if core.settings:get_bool("next_item") then elapsed_time = elapsed_time + dtime if elapsed_time < tick_time then return end - local player = minetest.localplayer - if not player then return end - local item = player:get_wielded_item() if item:get_count() == 0 then - local index = player:get_wield_index() - player:set_wield_index(index + 1) + player:set_wield_index(wieldindex + 1) end elapsed_time = 0 end @@ -62,7 +91,7 @@ core.register_on_punchnode(function(pos, node) for index, stack in pairs(inventory.main) do is_better, best_time = check_tool(stack, node_groups, best_time) if is_better then - new_index = index - 1 + new_index = index end end player:set_wield_index(new_index) @@ -113,3 +142,6 @@ local hand_formspec = "size[9,8.75]".. function core.open_handslot() minetest.show_formspec("__builtin__:hand", hand_formspec) end + + + diff --git a/builtin/client/util.lua b/builtin/client/util.lua index d61b547c6..20e0e1d1b 100644 --- a/builtin/client/util.lua +++ b/builtin/client/util.lua @@ -21,9 +21,9 @@ function core.parse_relative_pos(param) return success, pos end -function core.find_item(item) +function core.find_item(item, mini, maxi) for index, stack in ipairs(core.get_inventory("current_player").main) do - if stack:get_name() == item then + if (not mini or index >= mini) and (not maxi or index <= maxi) and stack:get_name() == item then return index end end @@ -32,7 +32,7 @@ end function core.switch_to_item(item) local i = core.find_item(item) if i then - core.localplayer:set_wield_index(i - 1) + core.localplayer:set_wield_index(i) return true else return false diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index f30a16789..a7a92291d 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -2298,3 +2298,11 @@ autotnt (PlaceOnTop) bool false replace (Replace) bool false crystal_pvp (CrystalPvP) bool false + +autototem (AutoTotem) bool false + +dont_point_nodes (ThroughWalls) bool false + +strip (Strip) bool false + +autorefill (AutoRefill) bool false diff --git a/doc/client_lua_api.txt b/doc/client_lua_api.txt index 50c33940e..2daac87fe 100644 --- a/doc/client_lua_api.txt +++ b/doc/client_lua_api.txt @@ -1184,9 +1184,9 @@ Methods: * `get_name()` * returns player name * `get_wield_index()` - * returns the index of the wielded item + * returns the index of the wielded item (starts at 1) * `set_wield_index()` - * sets the index + * sets the index (starts at 1) * `get_wielded_item()` * returns the itemstack the player is holding * `is_attached()` @@ -1717,6 +1717,7 @@ A reference to a C++ InventoryAction. You can move, drop and craft items in all * `InventoryAction(type)`: * creates a new InventoryAction * type is on of "move", "drop", or "craft", else returns nil + * indexing starts at 1 * `apply()`: * applies the InventoryAction (InventoryActions can be applied multible times) * `from(inventorylocation, listname, stack)` @@ -1736,13 +1737,13 @@ A reference to a C++ InventoryAction. You can move, drop and craft items in all #### example `local move_act = InventoryAction("move") - move_act:from("current_player", "main", 0) - move_act:to("current_player", "craft", 0) + move_act:from("current_player", "main", 1) + move_act:to("current_player", "craft", 1) move_act:set_count(1) local craft_act = InventoryAction("craft") craft_act:craft("current_player") local drop_act = InventoryAction("drop") - drop_act:from("current_player", "craft", 0) + drop_act:from("current_player", "craft_result",10) move_act:apply() craft_act:apply() drop_act:apply() diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 2899f2509..6cc28ac32 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -111,6 +111,8 @@ void set_default_settings(Settings *settings) settings->setDefault("crystal_pvp", "false"); settings->setDefault("autototem", "false"); settings->setDefault("dont_point_nodes", "false"); + settings->setDefault("strip", "false"); + settings->setDefault("autorefill", "false"); // Keymap settings->setDefault("remote_port", "30000"); diff --git a/src/script/lua_api/l_inventoryaction.cpp b/src/script/lua_api/l_inventoryaction.cpp index 516d6d3b2..f3037ba83 100644 --- a/src/script/lua_api/l_inventoryaction.cpp +++ b/src/script/lua_api/l_inventoryaction.cpp @@ -133,7 +133,7 @@ void LuaInventoryAction::readFullInventoryLocationInto(lua_State *L, InventoryLo loc->deSerialize(readParam(L, 2)); std::string l = readParam(L, 3); *list = l; - *index = luaL_checkinteger(L, 4); + *index = luaL_checkinteger(L, 4) - 1; } catch (SerializationError &) {} } diff --git a/src/script/lua_api/l_localplayer.cpp b/src/script/lua_api/l_localplayer.cpp index 8057802a4..e40dd7b37 100644 --- a/src/script/lua_api/l_localplayer.cpp +++ b/src/script/lua_api/l_localplayer.cpp @@ -116,7 +116,7 @@ int LuaLocalPlayer::l_get_wield_index(lua_State *L) { LocalPlayer *player = getobject(L, 1); - lua_pushinteger(L, player->getWieldIndex()); + lua_pushinteger(L, player->getWieldIndex() + 1); return 1; } @@ -124,7 +124,7 @@ int LuaLocalPlayer::l_get_wield_index(lua_State *L) int LuaLocalPlayer::l_set_wield_index(lua_State *L) { LocalPlayer *player = getobject(L, 1); - u32 index = luaL_checkinteger(L, 2); + u32 index = luaL_checkinteger(L, 2) - 1; player->setWieldIndex(index); g_game->processItemSelection(&g_game->runData.new_playeritem); From f53396b1523f5adb99fcc9e8fe5262a205217489 Mon Sep 17 00:00:00 2001 From: Zughy <63455151+Zughy@users.noreply.github.com> Date: Wed, 21 Oct 2020 21:42:23 +0200 Subject: [PATCH 148/266] Update jsoncpp to 1.9.4 (#10477) Co-authored-by: Zughy <4279489-marco_a@users.noreply.gitlab.com> --- lib/jsoncpp/json/UPDATING | 2 +- lib/jsoncpp/json/json-forwards.h | 366 ++-- lib/jsoncpp/json/json.h | 1622 +++++++++-------- lib/jsoncpp/jsoncpp.cpp | 2770 +++++++++++++++--------------- 4 files changed, 2475 insertions(+), 2285 deletions(-) diff --git a/lib/jsoncpp/json/UPDATING b/lib/jsoncpp/json/UPDATING index f0226e8ee..0893244cf 100644 --- a/lib/jsoncpp/json/UPDATING +++ b/lib/jsoncpp/json/UPDATING @@ -1,6 +1,6 @@ #!/bin/sh cd .. -git clone https://github.com/open-source-parsers/jsoncpp -b 1.8.3 --depth 1 +git clone https://github.com/open-source-parsers/jsoncpp -b 1.9.4 --depth 1 cd jsoncpp python amalgamate.py cp -R dist/json ../json diff --git a/lib/jsoncpp/json/json-forwards.h b/lib/jsoncpp/json/json-forwards.h index de2e4bd79..d3260c57c 100644 --- a/lib/jsoncpp/json/json-forwards.h +++ b/lib/jsoncpp/json/json-forwards.h @@ -79,6 +79,151 @@ license you like. /// to prevent private header inclusion. #define JSON_IS_AMALGAMATION +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/version.h +// ////////////////////////////////////////////////////////////////////// + +#ifndef JSON_VERSION_H_INCLUDED +#define JSON_VERSION_H_INCLUDED + +// Note: version must be updated in three places when doing a release. This +// annoying process ensures that amalgamate, CMake, and meson all report the +// correct version. +// 1. /meson.build +// 2. /include/json/version.h +// 3. /CMakeLists.txt +// IMPORTANT: also update the SOVERSION!! + +#define JSONCPP_VERSION_STRING "1.9.4" +#define JSONCPP_VERSION_MAJOR 1 +#define JSONCPP_VERSION_MINOR 9 +#define JSONCPP_VERSION_PATCH 3 +#define JSONCPP_VERSION_QUALIFIER +#define JSONCPP_VERSION_HEXA \ + ((JSONCPP_VERSION_MAJOR << 24) | (JSONCPP_VERSION_MINOR << 16) | \ + (JSONCPP_VERSION_PATCH << 8)) + +#ifdef JSONCPP_USING_SECURE_MEMORY +#undef JSONCPP_USING_SECURE_MEMORY +#endif +#define JSONCPP_USING_SECURE_MEMORY 0 +// If non-zero, the library zeroes any memory that it has allocated before +// it frees its memory. + +#endif // JSON_VERSION_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/version.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/allocator.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef JSON_ALLOCATOR_H_INCLUDED +#define JSON_ALLOCATOR_H_INCLUDED + +#include +#include + +#pragma pack(push, 8) + +namespace Json { +template class SecureAllocator { +public: + // Type definitions + using value_type = T; + using pointer = T*; + using const_pointer = const T*; + using reference = T&; + using const_reference = const T&; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + + /** + * Allocate memory for N items using the standard allocator. + */ + pointer allocate(size_type n) { + // allocate using "global operator new" + return static_cast(::operator new(n * sizeof(T))); + } + + /** + * Release memory which was allocated for N items at pointer P. + * + * The memory block is filled with zeroes before being released. + * The pointer argument is tagged as "volatile" to prevent the + * compiler optimizing out this critical step. + */ + void deallocate(volatile pointer p, size_type n) { + std::memset(p, 0, n * sizeof(T)); + // free using "global operator delete" + ::operator delete(p); + } + + /** + * Construct an item in-place at pointer P. + */ + template void construct(pointer p, Args&&... args) { + // construct using "placement new" and "perfect forwarding" + ::new (static_cast(p)) T(std::forward(args)...); + } + + size_type max_size() const { return size_t(-1) / sizeof(T); } + + pointer address(reference x) const { return std::addressof(x); } + + const_pointer address(const_reference x) const { return std::addressof(x); } + + /** + * Destroy an item in-place at pointer P. + */ + void destroy(pointer p) { + // destroy using "explicit destructor" + p->~T(); + } + + // Boilerplate + SecureAllocator() {} + template SecureAllocator(const SecureAllocator&) {} + template struct rebind { using other = SecureAllocator; }; +}; + +template +bool operator==(const SecureAllocator&, const SecureAllocator&) { + return true; +} + +template +bool operator!=(const SecureAllocator&, const SecureAllocator&) { + return false; +} + +} // namespace Json + +#pragma pack(pop) + +#endif // JSON_ALLOCATOR_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/allocator.h +// ////////////////////////////////////////////////////////////////////// + + + + + + // ////////////////////////////////////////////////////////////////////// // Beginning of content of file: include/json/config.h // ////////////////////////////////////////////////////////////////////// @@ -90,19 +235,14 @@ license you like. #ifndef JSON_CONFIG_H_INCLUDED #define JSON_CONFIG_H_INCLUDED -#include -#include //typedef String -#include //typedef int64_t, uint64_t - -/// If defined, indicates that json library is embedded in CppTL library. -//# define JSON_IN_CPPTL 1 - -/// If defined, indicates that json may leverage CppTL library -//# define JSON_USE_CPPTL 1 -/// If defined, indicates that cpptl vector based map should be used instead of -/// std::map -/// as Value container. -//# define JSON_USE_CPPTL_SMALLMAP 1 +#include +#include +#include +#include +#include +#include +#include +#include // If non-zero, the library uses exceptions to report bad input instead of C // assertion macros. The default is to use exceptions. @@ -110,164 +250,132 @@ license you like. #define JSON_USE_EXCEPTION 1 #endif +// Temporary, tracked for removal with issue #982. +#ifndef JSON_USE_NULLREF +#define JSON_USE_NULLREF 1 +#endif + /// If defined, indicates that the source file is amalgamated /// to prevent private header inclusion. /// Remarks: it is automatically defined in the generated amalgamated header. // #define JSON_IS_AMALGAMATION -#ifdef JSON_IN_CPPTL -#include -#ifndef JSON_USE_CPPTL -#define JSON_USE_CPPTL 1 -#endif -#endif - -#ifdef JSON_IN_CPPTL -#define JSON_API CPPTL_API -#elif defined(JSON_DLL_BUILD) +// Export macros for DLL visibility +#if defined(JSON_DLL_BUILD) #if defined(_MSC_VER) || defined(__MINGW32__) #define JSON_API __declspec(dllexport) #define JSONCPP_DISABLE_DLL_INTERFACE_WARNING +#elif defined(__GNUC__) || defined(__clang__) +#define JSON_API __attribute__((visibility("default"))) #endif // if defined(_MSC_VER) + #elif defined(JSON_DLL) #if defined(_MSC_VER) || defined(__MINGW32__) #define JSON_API __declspec(dllimport) #define JSONCPP_DISABLE_DLL_INTERFACE_WARNING #endif // if defined(_MSC_VER) -#endif // ifdef JSON_IN_CPPTL +#endif // ifdef JSON_DLL_BUILD + #if !defined(JSON_API) #define JSON_API #endif +#if defined(_MSC_VER) && _MSC_VER < 1800 +#error \ + "ERROR: Visual Studio 12 (2013) with _MSC_VER=1800 is the oldest supported compiler with sufficient C++11 capabilities" +#endif + +#if defined(_MSC_VER) && _MSC_VER < 1900 +// As recommended at +// https://stackoverflow.com/questions/2915672/snprintf-and-visual-studio-2010 +extern JSON_API int msvc_pre1900_c99_snprintf(char* outBuf, size_t size, + const char* format, ...); +#define jsoncpp_snprintf msvc_pre1900_c99_snprintf +#else +#define jsoncpp_snprintf std::snprintf +#endif + // If JSON_NO_INT64 is defined, then Json only support C++ "int" type for // integer // Storages, and 64 bits integer support is disabled. // #define JSON_NO_INT64 1 -#if defined(_MSC_VER) // MSVC -# if _MSC_VER <= 1200 // MSVC 6 - // Microsoft Visual Studio 6 only support conversion from __int64 to double - // (no conversion from unsigned __int64). -# define JSON_USE_INT64_DOUBLE_CONVERSION 1 - // Disable warning 4786 for VS6 caused by STL (identifier was truncated to '255' - // characters in the debug information) - // All projects I've ever seen with VS6 were using this globally (not bothering - // with pragma push/pop). -# pragma warning(disable : 4786) -# endif // MSVC 6 - -# if _MSC_VER >= 1500 // MSVC 2008 - /// Indicates that the following function is deprecated. -# define JSONCPP_DEPRECATED(message) __declspec(deprecated(message)) -# endif - -#endif // defined(_MSC_VER) - -// In c++11 the override keyword allows you to explicitly define that a function -// is intended to override the base-class version. This makes the code more -// managable and fixes a set of common hard-to-find bugs. -#if __cplusplus >= 201103L -# define JSONCPP_OVERRIDE override -# define JSONCPP_NOEXCEPT noexcept -#elif defined(_MSC_VER) && _MSC_VER > 1600 && _MSC_VER < 1900 -# define JSONCPP_OVERRIDE override -# define JSONCPP_NOEXCEPT throw() -#elif defined(_MSC_VER) && _MSC_VER >= 1900 -# define JSONCPP_OVERRIDE override -# define JSONCPP_NOEXCEPT noexcept -#else -# define JSONCPP_OVERRIDE -# define JSONCPP_NOEXCEPT throw() -#endif - -#ifndef JSON_HAS_RVALUE_REFERENCES - -#if defined(_MSC_VER) && _MSC_VER >= 1600 // MSVC >= 2010 -#define JSON_HAS_RVALUE_REFERENCES 1 -#endif // MSVC >= 2010 +// JSONCPP_OVERRIDE is maintained for backwards compatibility of external tools. +// C++11 should be used directly in JSONCPP. +#define JSONCPP_OVERRIDE override #ifdef __clang__ -#if __has_feature(cxx_rvalue_references) -#define JSON_HAS_RVALUE_REFERENCES 1 -#endif // has_feature - -#elif defined __GNUC__ // not clang (gcc comes later since clang emulates gcc) -#if defined(__GXX_EXPERIMENTAL_CXX0X__) || (__cplusplus >= 201103L) -#define JSON_HAS_RVALUE_REFERENCES 1 -#endif // GXX_EXPERIMENTAL - -#endif // __clang__ || __GNUC__ - -#endif // not defined JSON_HAS_RVALUE_REFERENCES - -#ifndef JSON_HAS_RVALUE_REFERENCES -#define JSON_HAS_RVALUE_REFERENCES 0 +#if __has_extension(attribute_deprecated_with_message) +#define JSONCPP_DEPRECATED(message) __attribute__((deprecated(message))) #endif - -#ifdef __clang__ -# if __has_extension(attribute_deprecated_with_message) -# define JSONCPP_DEPRECATED(message) __attribute__ ((deprecated(message))) -# endif -#elif defined __GNUC__ // not clang (gcc comes later since clang emulates gcc) -# if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5)) -# define JSONCPP_DEPRECATED(message) __attribute__ ((deprecated(message))) -# elif (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1)) -# define JSONCPP_DEPRECATED(message) __attribute__((__deprecated__)) -# endif // GNUC version -#endif // __clang__ || __GNUC__ +#elif defined(__GNUC__) // not clang (gcc comes later since clang emulates gcc) +#if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5)) +#define JSONCPP_DEPRECATED(message) __attribute__((deprecated(message))) +#elif (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1)) +#define JSONCPP_DEPRECATED(message) __attribute__((__deprecated__)) +#endif // GNUC version +#elif defined(_MSC_VER) // MSVC (after clang because clang on Windows emulates + // MSVC) +#define JSONCPP_DEPRECATED(message) __declspec(deprecated(message)) +#endif // __clang__ || __GNUC__ || _MSC_VER #if !defined(JSONCPP_DEPRECATED) #define JSONCPP_DEPRECATED(message) #endif // if !defined(JSONCPP_DEPRECATED) -#if __GNUC__ >= 6 -# define JSON_USE_INT64_DOUBLE_CONVERSION 1 +#if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ >= 6)) +#define JSON_USE_INT64_DOUBLE_CONVERSION 1 #endif #if !defined(JSON_IS_AMALGAMATION) -# include "version.h" - -# if JSONCPP_USING_SECURE_MEMORY -# include "allocator.h" //typedef Allocator -# endif +#include "allocator.h" +#include "version.h" #endif // if !defined(JSON_IS_AMALGAMATION) namespace Json { -typedef int Int; -typedef unsigned int UInt; +using Int = int; +using UInt = unsigned int; #if defined(JSON_NO_INT64) -typedef int LargestInt; -typedef unsigned int LargestUInt; +using LargestInt = int; +using LargestUInt = unsigned int; #undef JSON_HAS_INT64 #else // if defined(JSON_NO_INT64) // For Microsoft Visual use specific types as long long is not supported #if defined(_MSC_VER) // Microsoft Visual Studio -typedef __int64 Int64; -typedef unsigned __int64 UInt64; +using Int64 = __int64; +using UInt64 = unsigned __int64; #else // if defined(_MSC_VER) // Other platforms, use long long -typedef int64_t Int64; -typedef uint64_t UInt64; -#endif // if defined(_MSC_VER) -typedef Int64 LargestInt; -typedef UInt64 LargestUInt; +using Int64 = int64_t; +using UInt64 = uint64_t; +#endif // if defined(_MSC_VER) +using LargestInt = Int64; +using LargestUInt = UInt64; #define JSON_HAS_INT64 #endif // if defined(JSON_NO_INT64) -#if JSONCPP_USING_SECURE_MEMORY -#define JSONCPP_STRING std::basic_string, Json::SecureAllocator > -#define JSONCPP_OSTRINGSTREAM std::basic_ostringstream, Json::SecureAllocator > -#define JSONCPP_OSTREAM std::basic_ostream> -#define JSONCPP_ISTRINGSTREAM std::basic_istringstream, Json::SecureAllocator > -#define JSONCPP_ISTREAM std::istream -#else -#define JSONCPP_STRING std::string -#define JSONCPP_OSTRINGSTREAM std::ostringstream -#define JSONCPP_OSTREAM std::ostream -#define JSONCPP_ISTRINGSTREAM std::istringstream -#define JSONCPP_ISTREAM std::istream -#endif // if JSONCPP_USING_SECURE_MEMORY -} // end namespace Json + +template +using Allocator = + typename std::conditional, + std::allocator>::type; +using String = std::basic_string, Allocator>; +using IStringStream = + std::basic_istringstream; +using OStringStream = + std::basic_ostringstream; +using IStream = std::istream; +using OStream = std::ostream; +} // namespace Json + +// Legacy names (formerly macros). +using JSONCPP_STRING = Json::String; +using JSONCPP_ISTRINGSTREAM = Json::IStringStream; +using JSONCPP_OSTRINGSTREAM = Json::OStringStream; +using JSONCPP_ISTREAM = Json::IStream; +using JSONCPP_OSTREAM = Json::OStream; #endif // JSON_CONFIG_H_INCLUDED @@ -299,17 +407,23 @@ typedef UInt64 LargestUInt; namespace Json { // writer.h +class StreamWriter; +class StreamWriterBuilder; +class Writer; class FastWriter; class StyledWriter; +class StyledStreamWriter; // reader.h class Reader; +class CharReader; +class CharReaderBuilder; -// features.h +// json_features.h class Features; // value.h -typedef unsigned int ArrayIndex; +using ArrayIndex = unsigned int; class StaticString; class Path; class PathArgument; diff --git a/lib/jsoncpp/json/json.h b/lib/jsoncpp/json/json.h index 625ba02e9..3f4813a95 100644 --- a/lib/jsoncpp/json/json.h +++ b/lib/jsoncpp/json/json.h @@ -82,17 +82,25 @@ license you like. // Beginning of content of file: include/json/version.h // ////////////////////////////////////////////////////////////////////// -// DO NOT EDIT. This file (and "version") is generated by CMake. -// Run CMake configure step to update it. #ifndef JSON_VERSION_H_INCLUDED -# define JSON_VERSION_H_INCLUDED +#define JSON_VERSION_H_INCLUDED -# define JSONCPP_VERSION_STRING "1.8.4" -# define JSONCPP_VERSION_MAJOR 1 -# define JSONCPP_VERSION_MINOR 8 -# define JSONCPP_VERSION_PATCH 4 -# define JSONCPP_VERSION_QUALIFIER -# define JSONCPP_VERSION_HEXA ((JSONCPP_VERSION_MAJOR << 24) | (JSONCPP_VERSION_MINOR << 16) | (JSONCPP_VERSION_PATCH << 8)) +// Note: version must be updated in three places when doing a release. This +// annoying process ensures that amalgamate, CMake, and meson all report the +// correct version. +// 1. /meson.build +// 2. /include/json/version.h +// 3. /CMakeLists.txt +// IMPORTANT: also update the SOVERSION!! + +#define JSONCPP_VERSION_STRING "1.9.4" +#define JSONCPP_VERSION_MAJOR 1 +#define JSONCPP_VERSION_MINOR 9 +#define JSONCPP_VERSION_PATCH 3 +#define JSONCPP_VERSION_QUALIFIER +#define JSONCPP_VERSION_HEXA \ + ((JSONCPP_VERSION_MAJOR << 24) | (JSONCPP_VERSION_MINOR << 16) | \ + (JSONCPP_VERSION_PATCH << 8)) #ifdef JSONCPP_USING_SECURE_MEMORY #undef JSONCPP_USING_SECURE_MEMORY @@ -112,6 +120,109 @@ license you like. +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/allocator.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef JSON_ALLOCATOR_H_INCLUDED +#define JSON_ALLOCATOR_H_INCLUDED + +#include +#include + +#pragma pack(push, 8) + +namespace Json { +template class SecureAllocator { +public: + // Type definitions + using value_type = T; + using pointer = T*; + using const_pointer = const T*; + using reference = T&; + using const_reference = const T&; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + + /** + * Allocate memory for N items using the standard allocator. + */ + pointer allocate(size_type n) { + // allocate using "global operator new" + return static_cast(::operator new(n * sizeof(T))); + } + + /** + * Release memory which was allocated for N items at pointer P. + * + * The memory block is filled with zeroes before being released. + * The pointer argument is tagged as "volatile" to prevent the + * compiler optimizing out this critical step. + */ + void deallocate(volatile pointer p, size_type n) { + std::memset(p, 0, n * sizeof(T)); + // free using "global operator delete" + ::operator delete(p); + } + + /** + * Construct an item in-place at pointer P. + */ + template void construct(pointer p, Args&&... args) { + // construct using "placement new" and "perfect forwarding" + ::new (static_cast(p)) T(std::forward(args)...); + } + + size_type max_size() const { return size_t(-1) / sizeof(T); } + + pointer address(reference x) const { return std::addressof(x); } + + const_pointer address(const_reference x) const { return std::addressof(x); } + + /** + * Destroy an item in-place at pointer P. + */ + void destroy(pointer p) { + // destroy using "explicit destructor" + p->~T(); + } + + // Boilerplate + SecureAllocator() {} + template SecureAllocator(const SecureAllocator&) {} + template struct rebind { using other = SecureAllocator; }; +}; + +template +bool operator==(const SecureAllocator&, const SecureAllocator&) { + return true; +} + +template +bool operator!=(const SecureAllocator&, const SecureAllocator&) { + return false; +} + +} // namespace Json + +#pragma pack(pop) + +#endif // JSON_ALLOCATOR_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/allocator.h +// ////////////////////////////////////////////////////////////////////// + + + + + + // ////////////////////////////////////////////////////////////////////// // Beginning of content of file: include/json/config.h // ////////////////////////////////////////////////////////////////////// @@ -123,19 +234,14 @@ license you like. #ifndef JSON_CONFIG_H_INCLUDED #define JSON_CONFIG_H_INCLUDED -#include -#include //typedef String -#include //typedef int64_t, uint64_t - -/// If defined, indicates that json library is embedded in CppTL library. -//# define JSON_IN_CPPTL 1 - -/// If defined, indicates that json may leverage CppTL library -//# define JSON_USE_CPPTL 1 -/// If defined, indicates that cpptl vector based map should be used instead of -/// std::map -/// as Value container. -//# define JSON_USE_CPPTL_SMALLMAP 1 +#include +#include +#include +#include +#include +#include +#include +#include // If non-zero, the library uses exceptions to report bad input instead of C // assertion macros. The default is to use exceptions. @@ -143,164 +249,132 @@ license you like. #define JSON_USE_EXCEPTION 1 #endif +// Temporary, tracked for removal with issue #982. +#ifndef JSON_USE_NULLREF +#define JSON_USE_NULLREF 1 +#endif + /// If defined, indicates that the source file is amalgamated /// to prevent private header inclusion. /// Remarks: it is automatically defined in the generated amalgamated header. // #define JSON_IS_AMALGAMATION -#ifdef JSON_IN_CPPTL -#include -#ifndef JSON_USE_CPPTL -#define JSON_USE_CPPTL 1 -#endif -#endif - -#ifdef JSON_IN_CPPTL -#define JSON_API CPPTL_API -#elif defined(JSON_DLL_BUILD) +// Export macros for DLL visibility +#if defined(JSON_DLL_BUILD) #if defined(_MSC_VER) || defined(__MINGW32__) #define JSON_API __declspec(dllexport) #define JSONCPP_DISABLE_DLL_INTERFACE_WARNING +#elif defined(__GNUC__) || defined(__clang__) +#define JSON_API __attribute__((visibility("default"))) #endif // if defined(_MSC_VER) + #elif defined(JSON_DLL) #if defined(_MSC_VER) || defined(__MINGW32__) #define JSON_API __declspec(dllimport) #define JSONCPP_DISABLE_DLL_INTERFACE_WARNING #endif // if defined(_MSC_VER) -#endif // ifdef JSON_IN_CPPTL +#endif // ifdef JSON_DLL_BUILD + #if !defined(JSON_API) #define JSON_API #endif +#if defined(_MSC_VER) && _MSC_VER < 1800 +#error \ + "ERROR: Visual Studio 12 (2013) with _MSC_VER=1800 is the oldest supported compiler with sufficient C++11 capabilities" +#endif + +#if defined(_MSC_VER) && _MSC_VER < 1900 +// As recommended at +// https://stackoverflow.com/questions/2915672/snprintf-and-visual-studio-2010 +extern JSON_API int msvc_pre1900_c99_snprintf(char* outBuf, size_t size, + const char* format, ...); +#define jsoncpp_snprintf msvc_pre1900_c99_snprintf +#else +#define jsoncpp_snprintf std::snprintf +#endif + // If JSON_NO_INT64 is defined, then Json only support C++ "int" type for // integer // Storages, and 64 bits integer support is disabled. // #define JSON_NO_INT64 1 -#if defined(_MSC_VER) // MSVC -# if _MSC_VER <= 1200 // MSVC 6 - // Microsoft Visual Studio 6 only support conversion from __int64 to double - // (no conversion from unsigned __int64). -# define JSON_USE_INT64_DOUBLE_CONVERSION 1 - // Disable warning 4786 for VS6 caused by STL (identifier was truncated to '255' - // characters in the debug information) - // All projects I've ever seen with VS6 were using this globally (not bothering - // with pragma push/pop). -# pragma warning(disable : 4786) -# endif // MSVC 6 - -# if _MSC_VER >= 1500 // MSVC 2008 - /// Indicates that the following function is deprecated. -# define JSONCPP_DEPRECATED(message) __declspec(deprecated(message)) -# endif - -#endif // defined(_MSC_VER) - -// In c++11 the override keyword allows you to explicitly define that a function -// is intended to override the base-class version. This makes the code more -// managable and fixes a set of common hard-to-find bugs. -#if __cplusplus >= 201103L -# define JSONCPP_OVERRIDE override -# define JSONCPP_NOEXCEPT noexcept -#elif defined(_MSC_VER) && _MSC_VER > 1600 && _MSC_VER < 1900 -# define JSONCPP_OVERRIDE override -# define JSONCPP_NOEXCEPT throw() -#elif defined(_MSC_VER) && _MSC_VER >= 1900 -# define JSONCPP_OVERRIDE override -# define JSONCPP_NOEXCEPT noexcept -#else -# define JSONCPP_OVERRIDE -# define JSONCPP_NOEXCEPT throw() -#endif - -#ifndef JSON_HAS_RVALUE_REFERENCES - -#if defined(_MSC_VER) && _MSC_VER >= 1600 // MSVC >= 2010 -#define JSON_HAS_RVALUE_REFERENCES 1 -#endif // MSVC >= 2010 +// JSONCPP_OVERRIDE is maintained for backwards compatibility of external tools. +// C++11 should be used directly in JSONCPP. +#define JSONCPP_OVERRIDE override #ifdef __clang__ -#if __has_feature(cxx_rvalue_references) -#define JSON_HAS_RVALUE_REFERENCES 1 -#endif // has_feature - -#elif defined __GNUC__ // not clang (gcc comes later since clang emulates gcc) -#if defined(__GXX_EXPERIMENTAL_CXX0X__) || (__cplusplus >= 201103L) -#define JSON_HAS_RVALUE_REFERENCES 1 -#endif // GXX_EXPERIMENTAL - -#endif // __clang__ || __GNUC__ - -#endif // not defined JSON_HAS_RVALUE_REFERENCES - -#ifndef JSON_HAS_RVALUE_REFERENCES -#define JSON_HAS_RVALUE_REFERENCES 0 +#if __has_extension(attribute_deprecated_with_message) +#define JSONCPP_DEPRECATED(message) __attribute__((deprecated(message))) #endif - -#ifdef __clang__ -# if __has_extension(attribute_deprecated_with_message) -# define JSONCPP_DEPRECATED(message) __attribute__ ((deprecated(message))) -# endif -#elif defined __GNUC__ // not clang (gcc comes later since clang emulates gcc) -# if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5)) -# define JSONCPP_DEPRECATED(message) __attribute__ ((deprecated(message))) -# elif (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1)) -# define JSONCPP_DEPRECATED(message) __attribute__((__deprecated__)) -# endif // GNUC version -#endif // __clang__ || __GNUC__ +#elif defined(__GNUC__) // not clang (gcc comes later since clang emulates gcc) +#if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5)) +#define JSONCPP_DEPRECATED(message) __attribute__((deprecated(message))) +#elif (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1)) +#define JSONCPP_DEPRECATED(message) __attribute__((__deprecated__)) +#endif // GNUC version +#elif defined(_MSC_VER) // MSVC (after clang because clang on Windows emulates + // MSVC) +#define JSONCPP_DEPRECATED(message) __declspec(deprecated(message)) +#endif // __clang__ || __GNUC__ || _MSC_VER #if !defined(JSONCPP_DEPRECATED) #define JSONCPP_DEPRECATED(message) #endif // if !defined(JSONCPP_DEPRECATED) -#if __GNUC__ >= 6 -# define JSON_USE_INT64_DOUBLE_CONVERSION 1 +#if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ >= 6)) +#define JSON_USE_INT64_DOUBLE_CONVERSION 1 #endif #if !defined(JSON_IS_AMALGAMATION) -# include "version.h" - -# if JSONCPP_USING_SECURE_MEMORY -# include "allocator.h" //typedef Allocator -# endif +#include "allocator.h" +#include "version.h" #endif // if !defined(JSON_IS_AMALGAMATION) namespace Json { -typedef int Int; -typedef unsigned int UInt; +using Int = int; +using UInt = unsigned int; #if defined(JSON_NO_INT64) -typedef int LargestInt; -typedef unsigned int LargestUInt; +using LargestInt = int; +using LargestUInt = unsigned int; #undef JSON_HAS_INT64 #else // if defined(JSON_NO_INT64) // For Microsoft Visual use specific types as long long is not supported #if defined(_MSC_VER) // Microsoft Visual Studio -typedef __int64 Int64; -typedef unsigned __int64 UInt64; +using Int64 = __int64; +using UInt64 = unsigned __int64; #else // if defined(_MSC_VER) // Other platforms, use long long -typedef int64_t Int64; -typedef uint64_t UInt64; -#endif // if defined(_MSC_VER) -typedef Int64 LargestInt; -typedef UInt64 LargestUInt; +using Int64 = int64_t; +using UInt64 = uint64_t; +#endif // if defined(_MSC_VER) +using LargestInt = Int64; +using LargestUInt = UInt64; #define JSON_HAS_INT64 #endif // if defined(JSON_NO_INT64) -#if JSONCPP_USING_SECURE_MEMORY -#define JSONCPP_STRING std::basic_string, Json::SecureAllocator > -#define JSONCPP_OSTRINGSTREAM std::basic_ostringstream, Json::SecureAllocator > -#define JSONCPP_OSTREAM std::basic_ostream> -#define JSONCPP_ISTRINGSTREAM std::basic_istringstream, Json::SecureAllocator > -#define JSONCPP_ISTREAM std::istream -#else -#define JSONCPP_STRING std::string -#define JSONCPP_OSTRINGSTREAM std::ostringstream -#define JSONCPP_OSTREAM std::ostream -#define JSONCPP_ISTRINGSTREAM std::istringstream -#define JSONCPP_ISTREAM std::istream -#endif // if JSONCPP_USING_SECURE_MEMORY -} // end namespace Json + +template +using Allocator = + typename std::conditional, + std::allocator>::type; +using String = std::basic_string, Allocator>; +using IStringStream = + std::basic_istringstream; +using OStringStream = + std::basic_ostringstream; +using IStream = std::istream; +using OStream = std::ostream; +} // namespace Json + +// Legacy names (formerly macros). +using JSONCPP_STRING = Json::String; +using JSONCPP_ISTRINGSTREAM = Json::IStringStream; +using JSONCPP_OSTRINGSTREAM = Json::OStringStream; +using JSONCPP_ISTREAM = Json::IStream; +using JSONCPP_OSTREAM = Json::OStream; #endif // JSON_CONFIG_H_INCLUDED @@ -332,17 +406,23 @@ typedef UInt64 LargestUInt; namespace Json { // writer.h +class StreamWriter; +class StreamWriterBuilder; +class Writer; class FastWriter; class StyledWriter; +class StyledStreamWriter; // reader.h class Reader; +class CharReader; +class CharReaderBuilder; -// features.h +// json_features.h class Features; // value.h -typedef unsigned int ArrayIndex; +using ArrayIndex = unsigned int; class StaticString; class Path; class PathArgument; @@ -365,7 +445,7 @@ class ValueConstIterator; // ////////////////////////////////////////////////////////////////////// -// Beginning of content of file: include/json/features.h +// Beginning of content of file: include/json/json_features.h // ////////////////////////////////////////////////////////////////////// // Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors @@ -373,8 +453,8 @@ class ValueConstIterator; // recognized in your jurisdiction. // See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE -#ifndef CPPTL_JSON_FEATURES_H_INCLUDED -#define CPPTL_JSON_FEATURES_H_INCLUDED +#ifndef JSON_FEATURES_H_INCLUDED +#define JSON_FEATURES_H_INCLUDED #if !defined(JSON_IS_AMALGAMATION) #include "forwards.h" @@ -411,27 +491,27 @@ public: Features(); /// \c true if comments are allowed. Default: \c true. - bool allowComments_; + bool allowComments_{true}; /// \c true if root must be either an array or an object value. Default: \c /// false. - bool strictRoot_; + bool strictRoot_{false}; /// \c true if dropped null placeholders are allowed. Default: \c false. - bool allowDroppedNullPlaceholders_; + bool allowDroppedNullPlaceholders_{false}; /// \c true if numeric object key are allowed. Default: \c false. - bool allowNumericKeys_; + bool allowNumericKeys_{false}; }; } // namespace Json #pragma pack(pop) -#endif // CPPTL_JSON_FEATURES_H_INCLUDED +#endif // JSON_FEATURES_H_INCLUDED // ////////////////////////////////////////////////////////////////////// -// End of content of file: include/json/features.h +// End of content of file: include/json/json_features.h // ////////////////////////////////////////////////////////////////////// @@ -448,37 +528,48 @@ public: // recognized in your jurisdiction. // See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE -#ifndef CPPTL_JSON_H_INCLUDED -#define CPPTL_JSON_H_INCLUDED +#ifndef JSON_H_INCLUDED +#define JSON_H_INCLUDED #if !defined(JSON_IS_AMALGAMATION) #include "forwards.h" #endif // if !defined(JSON_IS_AMALGAMATION) -#include -#include -#include -#ifndef JSON_USE_CPPTL_SMALLMAP -#include -#else -#include -#endif -#ifdef JSON_USE_CPPTL -#include -#endif - -//Conditional NORETURN attribute on the throw functions would: +// Conditional NORETURN attribute on the throw functions would: // a) suppress false positives from static code analysis // b) possibly improve optimization opportunities. #if !defined(JSONCPP_NORETURN) -# if defined(_MSC_VER) -# define JSONCPP_NORETURN __declspec(noreturn) -# elif defined(__GNUC__) -# define JSONCPP_NORETURN __attribute__ ((__noreturn__)) -# else -# define JSONCPP_NORETURN -# endif +#if defined(_MSC_VER) && _MSC_VER == 1800 +#define JSONCPP_NORETURN __declspec(noreturn) +#else +#define JSONCPP_NORETURN [[noreturn]] #endif +#endif + +// Support for '= delete' with template declarations was a late addition +// to the c++11 standard and is rejected by clang 3.8 and Apple clang 8.2 +// even though these declare themselves to be c++11 compilers. +#if !defined(JSONCPP_TEMPLATE_DELETE) +#if defined(__clang__) && defined(__apple_build_version__) +#if __apple_build_version__ <= 8000042 +#define JSONCPP_TEMPLATE_DELETE +#endif +#elif defined(__clang__) +#if __clang_major__ == 3 && __clang_minor__ <= 8 +#define JSONCPP_TEMPLATE_DELETE +#endif +#endif +#if !defined(JSONCPP_TEMPLATE_DELETE) +#define JSONCPP_TEMPLATE_DELETE = delete +#endif +#endif + +#include +#include +#include +#include +#include +#include // Disable warning C4251: : needs to have dll-interface to // be used by... @@ -493,17 +584,19 @@ public: */ namespace Json { +#if JSON_USE_EXCEPTION /** Base class for all exceptions we throw. * * We use nothing but these internally. Of course, STL can throw others. */ class JSON_API Exception : public std::exception { public: - Exception(JSONCPP_STRING const& msg); - ~Exception() JSONCPP_NOEXCEPT JSONCPP_OVERRIDE; - char const* what() const JSONCPP_NOEXCEPT JSONCPP_OVERRIDE; + Exception(String msg); + ~Exception() noexcept override; + char const* what() const noexcept override; + protected: - JSONCPP_STRING msg_; + String msg_; }; /** Exceptions which the user cannot easily avoid. @@ -514,7 +607,7 @@ protected: */ class JSON_API RuntimeError : public Exception { public: - RuntimeError(JSONCPP_STRING const& msg); + RuntimeError(String const& msg); }; /** Exceptions thrown by JSON_ASSERT/JSON_FAIL macros. @@ -525,13 +618,14 @@ public: */ class JSON_API LogicError : public Exception { public: - LogicError(JSONCPP_STRING const& msg); + LogicError(String const& msg); }; +#endif /// used internally -JSONCPP_NORETURN void throwRuntimeError(JSONCPP_STRING const& msg); +JSONCPP_NORETURN void throwRuntimeError(String const& msg); /// used internally -JSONCPP_NORETURN void throwLogicError(JSONCPP_STRING const& msg); +JSONCPP_NORETURN void throwLogicError(String const& msg); /** \brief Type of the value held by a Value object. */ @@ -554,10 +648,12 @@ enum CommentPlacement { numberOfCommentPlacement }; -//# ifdef JSON_USE_CPPTL -// typedef CppTL::AnyEnumerator EnumMemberNames; -// typedef CppTL::AnyEnumerator EnumValues; -//# endif +/** \brief Type of precision for formatting of real values. + */ +enum PrecisionType { + significantDigits = 0, ///< we set max number of significant digits in string + decimalPlaces ///< we set max number of digits after "." in string +}; /** \brief Lightweight wrapper to tag static string. * @@ -610,7 +706,7 @@ private: * The get() methods can be used to obtain default value in the case the * required element does not exist. * - * It is possible to iterate over the list of a #objectValue values using + * It is possible to iterate over the list of member keys of an object using * the getMemberNames() method. * * \note #Value string-length fit in size_t, but keys must be < 2^30. @@ -621,76 +717,86 @@ private: */ class JSON_API Value { friend class ValueIteratorBase; + public: - typedef std::vector Members; - typedef ValueIterator iterator; - typedef ValueConstIterator const_iterator; - typedef Json::UInt UInt; - typedef Json::Int Int; + using Members = std::vector; + using iterator = ValueIterator; + using const_iterator = ValueConstIterator; + using UInt = Json::UInt; + using Int = Json::Int; #if defined(JSON_HAS_INT64) - typedef Json::UInt64 UInt64; - typedef Json::Int64 Int64; + using UInt64 = Json::UInt64; + using Int64 = Json::Int64; #endif // defined(JSON_HAS_INT64) - typedef Json::LargestInt LargestInt; - typedef Json::LargestUInt LargestUInt; - typedef Json::ArrayIndex ArrayIndex; + using LargestInt = Json::LargestInt; + using LargestUInt = Json::LargestUInt; + using ArrayIndex = Json::ArrayIndex; // Required for boost integration, e. g. BOOST_TEST - typedef std::string value_type; + using value_type = std::string; - static const Value& null; ///< We regret this reference to a global instance; prefer the simpler Value(). - static const Value& nullRef; ///< just a kludge for binary-compatibility; same as null - static Value const& nullSingleton(); ///< Prefer this to null or nullRef. +#if JSON_USE_NULLREF + // Binary compatibility kludges, do not use. + static const Value& null; + static const Value& nullRef; +#endif + + // null and nullRef are deprecated, use this instead. + static Value const& nullSingleton(); /// Minimum signed integer value that can be stored in a Json::Value. - static const LargestInt minLargestInt; + static constexpr LargestInt minLargestInt = + LargestInt(~(LargestUInt(-1) / 2)); /// Maximum signed integer value that can be stored in a Json::Value. - static const LargestInt maxLargestInt; + static constexpr LargestInt maxLargestInt = LargestInt(LargestUInt(-1) / 2); /// Maximum unsigned integer value that can be stored in a Json::Value. - static const LargestUInt maxLargestUInt; + static constexpr LargestUInt maxLargestUInt = LargestUInt(-1); /// Minimum signed int value that can be stored in a Json::Value. - static const Int minInt; + static constexpr Int minInt = Int(~(UInt(-1) / 2)); /// Maximum signed int value that can be stored in a Json::Value. - static const Int maxInt; + static constexpr Int maxInt = Int(UInt(-1) / 2); /// Maximum unsigned int value that can be stored in a Json::Value. - static const UInt maxUInt; + static constexpr UInt maxUInt = UInt(-1); #if defined(JSON_HAS_INT64) /// Minimum signed 64 bits int value that can be stored in a Json::Value. - static const Int64 minInt64; + static constexpr Int64 minInt64 = Int64(~(UInt64(-1) / 2)); /// Maximum signed 64 bits int value that can be stored in a Json::Value. - static const Int64 maxInt64; + static constexpr Int64 maxInt64 = Int64(UInt64(-1) / 2); /// Maximum unsigned 64 bits int value that can be stored in a Json::Value. - static const UInt64 maxUInt64; + static constexpr UInt64 maxUInt64 = UInt64(-1); #endif // defined(JSON_HAS_INT64) - + /// Default precision for real value for string representation. + static constexpr UInt defaultRealPrecision = 17; + // The constant is hard-coded because some compiler have trouble + // converting Value::maxUInt64 to a double correctly (AIX/xlC). + // Assumes that UInt64 is a 64 bits integer. + static constexpr double maxUInt64AsDouble = 18446744073709551615.0; +// Workaround for bug in the NVIDIAs CUDA 9.1 nvcc compiler +// when using gcc and clang backend compilers. CZString +// cannot be defined as private. See issue #486 +#ifdef __NVCC__ +public: +#else private: +#endif #ifndef JSONCPP_DOC_EXCLUDE_IMPLEMENTATION class CZString { public: - enum DuplicationPolicy { - noDuplication = 0, - duplicate, - duplicateOnCopy - }; + enum DuplicationPolicy { noDuplication = 0, duplicate, duplicateOnCopy }; CZString(ArrayIndex index); CZString(char const* str, unsigned length, DuplicationPolicy allocate); CZString(CZString const& other); -#if JSON_HAS_RVALUE_REFERENCES CZString(CZString&& other); -#endif ~CZString(); CZString& operator=(const CZString& other); - -#if JSON_HAS_RVALUE_REFERENCES CZString& operator=(CZString&& other); -#endif bool operator<(CZString const& other) const; bool operator==(CZString const& other) const; ArrayIndex index() const; - //const char* c_str() const; ///< \deprecated + // const char* c_str() const; ///< \deprecated char const* data() const; unsigned length() const; bool isStaticString() const; @@ -699,11 +805,11 @@ private: void swap(CZString& other); struct StringStorage { - unsigned policy_: 2; - unsigned length_: 30; // 1GB max + unsigned policy_ : 2; + unsigned length_ : 30; // 1GB max }; - char const* cstr_; // actually, a prefixed string, unless policy is noDup + char const* cstr_; // actually, a prefixed string, unless policy is noDup union { ArrayIndex index_; StringStorage storage_; @@ -711,29 +817,26 @@ private: }; public: -#ifndef JSON_USE_CPPTL_SMALLMAP typedef std::map ObjectValues; -#else - typedef CppTL::SmallMap ObjectValues; -#endif // ifndef JSON_USE_CPPTL_SMALLMAP #endif // ifndef JSONCPP_DOC_EXCLUDE_IMPLEMENTATION public: - /** \brief Create a default Value of the given type. - - This is a very useful constructor. - To create an empty array, pass arrayValue. - To create an empty object, pass objectValue. - Another Value can then be set to this one by assignment. -This is useful since clear() and resize() will not alter types. - - Examples: -\code -Json::Value null_value; // null -Json::Value arr_value(Json::arrayValue); // [] -Json::Value obj_value(Json::objectValue); // {} -\endcode - */ + /** + * \brief Create a default Value of the given type. + * + * This is a very useful constructor. + * To create an empty array, pass arrayValue. + * To create an empty object, pass objectValue. + * Another Value can then be set to this one by assignment. + * This is useful since clear() and resize() will not alter types. + * + * Examples: + * \code + * Json::Value null_value; // null + * Json::Value arr_value(Json::arrayValue); // [] + * Json::Value obj_value(Json::objectValue); // {} + * \endcode + */ Value(ValueType type = nullValue); Value(Int value); Value(UInt value); @@ -744,38 +847,35 @@ Json::Value obj_value(Json::objectValue); // {} Value(double value); Value(const char* value); ///< Copy til first 0. (NULL causes to seg-fault.) Value(const char* begin, const char* end); ///< Copy all, incl zeroes. - /** \brief Constructs a value from a static string. - + /** + * \brief Constructs a value from a static string. + * * Like other value string constructor but do not duplicate the string for - * internal storage. The given string must remain alive after the call to this - * constructor. + * internal storage. The given string must remain alive after the call to + * this constructor. + * * \note This works only for null-terminated strings. (We cannot change the - * size of this class, so we have nowhere to store the length, - * which might be computed later for various operations.) + * size of this class, so we have nowhere to store the length, which might be + * computed later for various operations.) * * Example of usage: - * \code - * static StaticString foo("some text"); - * Json::Value aValue(foo); - * \endcode + * \code + * static StaticString foo("some text"); + * Json::Value aValue(foo); + * \endcode */ Value(const StaticString& value); - Value(const JSONCPP_STRING& value); ///< Copy data() til size(). Embedded zeroes too. -#ifdef JSON_USE_CPPTL - Value(const CppTL::ConstString& value); -#endif + Value(const String& value); Value(bool value); - /// Deep copy. + Value(std::nullptr_t ptr) = delete; Value(const Value& other); -#if JSON_HAS_RVALUE_REFERENCES - /// Move constructor Value(Value&& other); -#endif ~Value(); - /// Deep copy, then swap(other). - /// \note Over-write existing comments. To preserve comments, use #swapPayload(). - Value& operator=(Value other); + /// \note Overwrite existing comments. To preserve comments, use + /// #swapPayload(). + Value& operator=(const Value& other); + Value& operator=(Value&& other); /// Swap everything. void swap(Value& other); @@ -800,17 +900,14 @@ Json::Value obj_value(Json::objectValue); // {} const char* asCString() const; ///< Embedded zeroes could cause you trouble! #if JSONCPP_USING_SECURE_MEMORY - unsigned getCStringLength() const; //Allows you to understand the length of the CString + unsigned getCStringLength() const; // Allows you to understand the length of + // the CString #endif - JSONCPP_STRING asString() const; ///< Embedded zeroes are possible. + String asString() const; ///< Embedded zeroes are possible. /** Get raw char* of string-value. * \return false if !string. (Seg-fault if str or end are NULL.) */ - bool getString( - char const** begin, char const** end) const; -#ifdef JSON_USE_CPPTL - CppTL::ConstString asConstString() const; -#endif + bool getString(char const** begin, char const** end) const; Int asInt() const; UInt asUInt() const; #if defined(JSON_HAS_INT64) @@ -836,6 +933,10 @@ Json::Value obj_value(Json::objectValue); // {} bool isArray() const; bool isObject() const; + /// The `as` and `is` member function templates and specializations. + template T as() const JSONCPP_TEMPLATE_DELETE; + template bool is() const JSONCPP_TEMPLATE_DELETE; + bool isConvertibleTo(ValueType other) const; /// Number of values in array or object @@ -853,42 +954,33 @@ Json::Value obj_value(Json::objectValue); // {} /// \post type() is unchanged void clear(); - /// Resize the array to size elements. + /// Resize the array to newSize elements. /// New elements are initialized to null. /// May only be called on nullValue or arrayValue. /// \pre type() is arrayValue or nullValue /// \post type() is arrayValue - void resize(ArrayIndex size); + void resize(ArrayIndex newSize); - /// Access an array element (zero based index ). - /// If the array contains less than index element, then null value are - /// inserted - /// in the array so that its size is index+1. + //@{ + /// Access an array element (zero based index). If the array contains less + /// than index element, then null value are inserted in the array so that + /// its size is index+1. /// (You may need to say 'value[0u]' to get your compiler to distinguish - /// this from the operator[] which takes a string.) + /// this from the operator[] which takes a string.) Value& operator[](ArrayIndex index); - - /// Access an array element (zero based index ). - /// If the array contains less than index element, then null value are - /// inserted - /// in the array so that its size is index+1. - /// (You may need to say 'value[0u]' to get your compiler to distinguish - /// this from the operator[] which takes a string.) Value& operator[](int index); + //@} - /// Access an array element (zero based index ) + //@{ + /// Access an array element (zero based index). /// (You may need to say 'value[0u]' to get your compiler to distinguish - /// this from the operator[] which takes a string.) + /// this from the operator[] which takes a string.) const Value& operator[](ArrayIndex index) const; - - /// Access an array element (zero based index ) - /// (You may need to say 'value[0u]' to get your compiler to distinguish - /// this from the operator[] which takes a string.) const Value& operator[](int index) const; + //@} /// If the array contains at least index+1 elements, returns the element - /// value, - /// otherwise returns defaultValue. + /// value, otherwise returns defaultValue. Value get(ArrayIndex index, const Value& defaultValue) const; /// Return true if index < size(). bool isValidIndex(ArrayIndex index) const; @@ -896,61 +988,51 @@ Json::Value obj_value(Json::objectValue); // {} /// /// Equivalent to jsonvalue[jsonvalue.size()] = value; Value& append(const Value& value); - -#if JSON_HAS_RVALUE_REFERENCES Value& append(Value&& value); -#endif + + /// \brief Insert value in array at specific index + bool insert(ArrayIndex index, const Value& newValue); + bool insert(ArrayIndex index, Value&& newValue); /// Access an object value by name, create a null member if it does not exist. /// \note Because of our implementation, keys are limited to 2^30 -1 chars. - /// Exceeding that will cause an exception. + /// Exceeding that will cause an exception. Value& operator[](const char* key); /// Access an object value by name, returns null if there is no member with /// that name. const Value& operator[](const char* key) const; /// Access an object value by name, create a null member if it does not exist. /// \param key may contain embedded nulls. - Value& operator[](const JSONCPP_STRING& key); + Value& operator[](const String& key); /// Access an object value by name, returns null if there is no member with /// that name. /// \param key may contain embedded nulls. - const Value& operator[](const JSONCPP_STRING& key) const; + const Value& operator[](const String& key) const; /** \brief Access an object value by name, create a null member if it does not - exist. - - * If the object has no entry for that name, then the member name used to store - * the new entry is not duplicated. + * exist. + * + * If the object has no entry for that name, then the member name used to + * store the new entry is not duplicated. * Example of use: - * \code - * Json::Value object; - * static const StaticString code("code"); - * object[code] = 1234; - * \endcode + * \code + * Json::Value object; + * static const StaticString code("code"); + * object[code] = 1234; + * \endcode */ Value& operator[](const StaticString& key); -#ifdef JSON_USE_CPPTL - /// Access an object value by name, create a null member if it does not exist. - Value& operator[](const CppTL::ConstString& key); - /// Access an object value by name, returns null if there is no member with - /// that name. - const Value& operator[](const CppTL::ConstString& key) const; -#endif /// Return the member named key if it exist, defaultValue otherwise. /// \note deep copy Value get(const char* key, const Value& defaultValue) const; /// Return the member named key if it exist, defaultValue otherwise. /// \note deep copy /// \note key may contain embedded nulls. - Value get(const char* begin, const char* end, const Value& defaultValue) const; + Value get(const char* begin, const char* end, + const Value& defaultValue) const; /// Return the member named key if it exist, defaultValue otherwise. /// \note deep copy /// \param key may contain embedded nulls. - Value get(const JSONCPP_STRING& key, const Value& defaultValue) const; -#ifdef JSON_USE_CPPTL - /// Return the member named key if it exist, defaultValue otherwise. - /// \note deep copy - Value get(const CppTL::ConstString& key, const Value& defaultValue) const; -#endif + Value get(const String& key, const Value& defaultValue) const; /// Most general and efficient version of isMember()const, get()const, /// and operator[]const /// \note As stated elsewhere, behavior is undefined if (end-begin) >= 2^30 @@ -958,51 +1040,44 @@ Json::Value obj_value(Json::objectValue); // {} /// Most general and efficient version of object-mutators. /// \note As stated elsewhere, behavior is undefined if (end-begin) >= 2^30 /// \return non-zero, but JSON_ASSERT if this is neither object nor nullValue. - Value const* demand(char const* begin, char const* end); + Value* demand(char const* begin, char const* end); /// \brief Remove and return the named member. /// /// Do nothing if it did not exist. - /// \return the removed Value, or null. /// \pre type() is objectValue or nullValue /// \post type() is unchanged - /// \deprecated void removeMember(const char* key); /// Same as removeMember(const char*) /// \param key may contain embedded nulls. - /// \deprecated - void removeMember(const JSONCPP_STRING& key); + void removeMember(const String& key); /// Same as removeMember(const char* begin, const char* end, Value* removed), /// but 'key' is null-terminated. bool removeMember(const char* key, Value* removed); /** \brief Remove the named map member. - - Update 'removed' iff removed. - \param key may contain embedded nulls. - \return true iff removed (no exceptions) - */ - bool removeMember(JSONCPP_STRING const& key, Value* removed); - /// Same as removeMember(JSONCPP_STRING const& key, Value* removed) + * + * Update 'removed' iff removed. + * \param key may contain embedded nulls. + * \return true iff removed (no exceptions) + */ + bool removeMember(String const& key, Value* removed); + /// Same as removeMember(String const& key, Value* removed) bool removeMember(const char* begin, const char* end, Value* removed); /** \brief Remove the indexed array element. - - O(n) expensive operations. - Update 'removed' iff removed. - \return true iff removed (no exceptions) - */ - bool removeIndex(ArrayIndex i, Value* removed); + * + * O(n) expensive operations. + * Update 'removed' iff removed. + * \return true if removed (no exceptions) + */ + bool removeIndex(ArrayIndex index, Value* removed); /// Return true if the object has a member named key. /// \note 'key' must be null-terminated. bool isMember(const char* key) const; /// Return true if the object has a member named key. /// \param key may contain embedded nulls. - bool isMember(const JSONCPP_STRING& key) const; - /// Same as isMember(JSONCPP_STRING const& key)const + bool isMember(const String& key) const; + /// Same as isMember(String const& key)const bool isMember(const char* begin, const char* end) const; -#ifdef JSON_USE_CPPTL - /// Return true if the object has a member named key. - bool isMember(const CppTL::ConstString& key) const; -#endif /// \brief Return a list of the member names. /// @@ -1011,23 +1086,22 @@ Json::Value obj_value(Json::objectValue); // {} /// \post if type() was nullValue, it remains nullValue Members getMemberNames() const; - //# ifdef JSON_USE_CPPTL - // EnumMemberNames enumMemberNames() const; - // EnumValues enumValues() const; - //# endif - /// \deprecated Always pass len. - JSONCPP_DEPRECATED("Use setComment(JSONCPP_STRING const&) instead.") - void setComment(const char* comment, CommentPlacement placement); + JSONCPP_DEPRECATED("Use setComment(String const&) instead.") + void setComment(const char* comment, CommentPlacement placement) { + setComment(String(comment, strlen(comment)), placement); + } /// Comments must be //... or /* ... */ - void setComment(const char* comment, size_t len, CommentPlacement placement); + void setComment(const char* comment, size_t len, CommentPlacement placement) { + setComment(String(comment, len), placement); + } /// Comments must be //... or /* ... */ - void setComment(const JSONCPP_STRING& comment, CommentPlacement placement); + void setComment(String comment, CommentPlacement placement); bool hasComment(CommentPlacement placement) const; /// Include delimiters and embedded newlines. - JSONCPP_STRING getComment(CommentPlacement placement) const; + String getComment(CommentPlacement placement) const; - JSONCPP_STRING toStyledString() const; + String toStyledString() const; const_iterator begin() const; const_iterator end() const; @@ -1043,20 +1117,20 @@ Json::Value obj_value(Json::objectValue); // {} ptrdiff_t getOffsetLimit() const; private: + void setType(ValueType v) { + bits_.value_type_ = static_cast(v); + } + bool isAllocated() const { return bits_.allocated_; } + void setIsAllocated(bool v) { bits_.allocated_ = v; } + void initBasic(ValueType type, bool allocated = false); + void dupPayload(const Value& other); + void releasePayload(); + void dupMeta(const Value& other); Value& resolveReference(const char* key); Value& resolveReference(const char* key, const char* end); - struct CommentInfo { - CommentInfo(); - ~CommentInfo(); - - void setComment(const char* text, size_t len); - - char* comment_; - }; - // struct MemberNamesTransform //{ // typedef const char *result_type; @@ -1071,13 +1145,33 @@ private: LargestUInt uint_; double real_; bool bool_; - char* string_; // actually ptr to unsigned, followed by str, unless !allocated_ + char* string_; // if allocated_, ptr to { unsigned, char[] }. ObjectValues* map_; } value_; - ValueType type_ : 8; - unsigned int allocated_ : 1; // Notes: if declared as bool, bitfield is useless. - // If not allocated_, string_ must be null-terminated. - CommentInfo* comments_; + + struct { + // Really a ValueType, but types should agree for bitfield packing. + unsigned int value_type_ : 8; + // Unless allocated_, string_ must be null-terminated. + unsigned int allocated_ : 1; + } bits_; + + class Comments { + public: + Comments() = default; + Comments(const Comments& that); + Comments(Comments&& that); + Comments& operator=(const Comments& that); + Comments& operator=(Comments&& that); + bool has(CommentPlacement slot) const; + String get(CommentPlacement slot) const; + void set(CommentPlacement slot, String comment); + + private: + using Array = std::array; + std::unique_ptr ptr_; + }; + Comments comments_; // [start, limit) byte offsets in the source JSON text from which this Value // was extracted. @@ -1085,6 +1179,36 @@ private: ptrdiff_t limit_; }; +template <> inline bool Value::as() const { return asBool(); } +template <> inline bool Value::is() const { return isBool(); } + +template <> inline Int Value::as() const { return asInt(); } +template <> inline bool Value::is() const { return isInt(); } + +template <> inline UInt Value::as() const { return asUInt(); } +template <> inline bool Value::is() const { return isUInt(); } + +#if defined(JSON_HAS_INT64) +template <> inline Int64 Value::as() const { return asInt64(); } +template <> inline bool Value::is() const { return isInt64(); } + +template <> inline UInt64 Value::as() const { return asUInt64(); } +template <> inline bool Value::is() const { return isUInt64(); } +#endif + +template <> inline double Value::as() const { return asDouble(); } +template <> inline bool Value::is() const { return isDouble(); } + +template <> inline String Value::as() const { return asString(); } +template <> inline bool Value::is() const { return isString(); } + +/// These `as` specializations are type conversions, and do not have a +/// corresponding `is`. +template <> inline float Value::as() const { return asFloat(); } +template <> inline const char* Value::as() const { + return asCString(); +} + /** \brief Experimental and untested: represents an element of the "path" to * access a node. */ @@ -1095,17 +1219,13 @@ public: PathArgument(); PathArgument(ArrayIndex index); PathArgument(const char* key); - PathArgument(const JSONCPP_STRING& key); + PathArgument(String key); private: - enum Kind { - kindNone = 0, - kindIndex, - kindKey - }; - JSONCPP_STRING key_; - ArrayIndex index_; - Kind kind_; + enum Kind { kindNone = 0, kindIndex, kindKey }; + String key_; + ArrayIndex index_{}; + Kind kind_{kindNone}; }; /** \brief Experimental and untested: represents a "path" to access a node. @@ -1117,12 +1237,11 @@ private: * - ".name1.name2.name3" * - ".[0][1][2].name1[3]" * - ".%" => member name is provided as parameter - * - ".[%]" => index is provied as parameter + * - ".[%]" => index is provided as parameter */ class JSON_API Path { public: - Path(const JSONCPP_STRING& path, - const PathArgument& a1 = PathArgument(), + Path(const String& path, const PathArgument& a1 = PathArgument(), const PathArgument& a2 = PathArgument(), const PathArgument& a3 = PathArgument(), const PathArgument& a4 = PathArgument(), @@ -1135,15 +1254,13 @@ public: Value& make(Value& root) const; private: - typedef std::vector InArgs; - typedef std::vector Args; + using InArgs = std::vector; + using Args = std::vector; - void makePath(const JSONCPP_STRING& path, const InArgs& in); - void addPathInArg(const JSONCPP_STRING& path, - const InArgs& in, - InArgs::const_iterator& itInArg, - PathArgument::Kind kind); - void invalidPath(const JSONCPP_STRING& path, int location); + void makePath(const String& path, const InArgs& in); + void addPathInArg(const String& path, const InArgs& in, + InArgs::const_iterator& itInArg, PathArgument::Kind kind); + static void invalidPath(const String& path, int location); Args args_; }; @@ -1153,10 +1270,10 @@ private: */ class JSON_API ValueIteratorBase { public: - typedef std::bidirectional_iterator_tag iterator_category; - typedef unsigned int size_t; - typedef int difference_type; - typedef ValueIteratorBase SelfType; + using iterator_category = std::bidirectional_iterator_tag; + using size_t = unsigned int; + using difference_type = int; + using SelfType = ValueIteratorBase; bool operator==(const SelfType& other) const { return isEqual(other); } @@ -1170,17 +1287,19 @@ public: /// Value. Value key() const; - /// Return the index of the referenced Value, or -1 if it is not an arrayValue. + /// Return the index of the referenced Value, or -1 if it is not an + /// arrayValue. UInt index() const; /// Return the member name of the referenced Value, or "" if it is not an /// objectValue. /// \note Avoid `c_str()` on result, as embedded zeroes are possible. - JSONCPP_STRING name() const; + String name() const; /// Return the member name of the referenced Value. "" if it is not an /// objectValue. - /// \deprecated This cannot be used for UTF-8 strings, since there can be embedded nulls. + /// \deprecated This cannot be used for UTF-8 strings, since there can be + /// embedded nulls. JSONCPP_DEPRECATED("Use `key = name();` instead.") char const* memberName() const; /// Return the member name of the referenced Value, or NULL if it is not an @@ -1189,7 +1308,14 @@ public: char const* memberName(char const** end) const; protected: - Value& deref() const; + /*! Internal utility functions to assist with implementing + * other iterator functions. The const and non-const versions + * of the "deref" protected methods expose the protected + * current_ member variable in a way that can often be + * optimized away by the compiler. + */ + const Value& deref() const; + Value& deref(); void increment(); @@ -1204,7 +1330,7 @@ protected: private: Value::ObjectValues::iterator current_; // Indicates that iterator is for a null value. - bool isNull_; + bool isNull_{true}; public: // For some reason, BORLAND needs these at the end, rather @@ -1220,20 +1346,21 @@ class JSON_API ValueConstIterator : public ValueIteratorBase { friend class Value; public: - typedef const Value value_type; - //typedef unsigned int size_t; - //typedef int difference_type; - typedef const Value& reference; - typedef const Value* pointer; - typedef ValueConstIterator SelfType; + using value_type = const Value; + // typedef unsigned int size_t; + // typedef int difference_type; + using reference = const Value&; + using pointer = const Value*; + using SelfType = ValueConstIterator; ValueConstIterator(); ValueConstIterator(ValueIterator const& other); private: -/*! \internal Use by Value to create an iterator. - */ + /*! \internal Use by Value to create an iterator. + */ explicit ValueConstIterator(const Value::ObjectValues::iterator& current); + public: SelfType& operator=(const ValueIteratorBase& other); @@ -1270,21 +1397,22 @@ class JSON_API ValueIterator : public ValueIteratorBase { friend class Value; public: - typedef Value value_type; - typedef unsigned int size_t; - typedef int difference_type; - typedef Value& reference; - typedef Value* pointer; - typedef ValueIterator SelfType; + using value_type = Value; + using size_t = unsigned int; + using difference_type = int; + using reference = Value&; + using pointer = Value*; + using SelfType = ValueIterator; ValueIterator(); explicit ValueIterator(const ValueConstIterator& other); ValueIterator(const ValueIterator& other); private: -/*! \internal Use by Value to create an iterator. - */ + /*! \internal Use by Value to create an iterator. + */ explicit ValueIterator(const Value::ObjectValues::iterator& current); + public: SelfType& operator=(const SelfType& other); @@ -1310,27 +1438,26 @@ public: return *this; } - reference operator*() const { return deref(); } - - pointer operator->() const { return &deref(); } + /*! The return value of non-const iterators can be + * changed, so the these functions are not const + * because the returned references/pointers can be used + * to change state of the base class. + */ + reference operator*() { return deref(); } + pointer operator->() { return &deref(); } }; +inline void swap(Value& a, Value& b) { a.swap(b); } + } // namespace Json - -namespace std { -/// Specialize std::swap() for Json::Value. -template<> -inline void swap(Json::Value& a, Json::Value& b) { a.swap(b); } -} - #pragma pack(pop) #if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) #pragma warning(pop) #endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) -#endif // CPPTL_JSON_H_INCLUDED +#endif // JSON_H_INCLUDED // ////////////////////////////////////////////////////////////////////// // End of content of file: include/json/value.h @@ -1350,18 +1477,18 @@ inline void swap(Json::Value& a, Json::Value& b) { a.swap(b); } // recognized in your jurisdiction. // See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE -#ifndef CPPTL_JSON_READER_H_INCLUDED -#define CPPTL_JSON_READER_H_INCLUDED +#ifndef JSON_READER_H_INCLUDED +#define JSON_READER_H_INCLUDED #if !defined(JSON_IS_AMALGAMATION) -#include "features.h" +#include "json_features.h" #include "value.h" #endif // if !defined(JSON_IS_AMALGAMATION) #include #include +#include #include #include -#include // Disable warning C4251: : needs to have dll-interface to // be used by... @@ -1375,132 +1502,130 @@ inline void swap(Json::Value& a, Json::Value& b) { a.swap(b); } namespace Json { /** \brief Unserialize a JSON document into a - *Value. + * Value. * * \deprecated Use CharReader and CharReaderBuilder. */ -class JSONCPP_DEPRECATED("Use CharReader and CharReaderBuilder instead") JSON_API Reader { + +class JSONCPP_DEPRECATED( + "Use CharReader and CharReaderBuilder instead.") JSON_API Reader { public: - typedef char Char; - typedef const Char* Location; + using Char = char; + using Location = const Char*; /** \brief An error tagged with where in the JSON text it was encountered. * * The offsets give the [start, limit) range of bytes within the text. Note * that this is bytes, not codepoints. - * */ struct StructuredError { ptrdiff_t offset_start; ptrdiff_t offset_limit; - JSONCPP_STRING message; + String message; }; - /** \brief Constructs a Reader allowing all features - * for parsing. + /** \brief Constructs a Reader allowing all features for parsing. */ + JSONCPP_DEPRECATED("Use CharReader and CharReaderBuilder instead") Reader(); - /** \brief Constructs a Reader allowing the specified feature set - * for parsing. + /** \brief Constructs a Reader allowing the specified feature set for parsing. */ + JSONCPP_DEPRECATED("Use CharReader and CharReaderBuilder instead") Reader(const Features& features); /** \brief Read a Value from a JSON * document. - * \param document UTF-8 encoded string containing the document to read. - * \param root [out] Contains the root value of the document if it was - * successfully parsed. - * \param collectComments \c true to collect comment and allow writing them - * back during - * serialization, \c false to discard comments. - * This parameter is ignored if - * Features::allowComments_ - * is \c false. + * + * \param document UTF-8 encoded string containing the document + * to read. + * \param[out] root Contains the root value of the document if it + * was successfully parsed. + * \param collectComments \c true to collect comment and allow writing + * them back during serialization, \c false to + * discard comments. This parameter is ignored + * if Features::allowComments_ is \c false. * \return \c true if the document was successfully parsed, \c false if an * error occurred. */ - bool - parse(const std::string& document, Value& root, bool collectComments = true); + bool parse(const std::string& document, Value& root, + bool collectComments = true); /** \brief Read a Value from a JSON - document. - * \param beginDoc Pointer on the beginning of the UTF-8 encoded string of the - document to read. - * \param endDoc Pointer on the end of the UTF-8 encoded string of the - document to read. - * Must be >= beginDoc. - * \param root [out] Contains the root value of the document if it was - * successfully parsed. - * \param collectComments \c true to collect comment and allow writing them - back during - * serialization, \c false to discard comments. - * This parameter is ignored if - Features::allowComments_ - * is \c false. + * document. + * + * \param beginDoc Pointer on the beginning of the UTF-8 encoded + * string of the document to read. + * \param endDoc Pointer on the end of the UTF-8 encoded string + * of the document to read. Must be >= beginDoc. + * \param[out] root Contains the root value of the document if it + * was successfully parsed. + * \param collectComments \c true to collect comment and allow writing + * them back during serialization, \c false to + * discard comments. This parameter is ignored + * if Features::allowComments_ is \c false. * \return \c true if the document was successfully parsed, \c false if an - error occurred. + * error occurred. */ - bool parse(const char* beginDoc, - const char* endDoc, - Value& root, + bool parse(const char* beginDoc, const char* endDoc, Value& root, bool collectComments = true); /// \brief Parse from input stream. /// \see Json::operator>>(std::istream&, Json::Value&). - bool parse(JSONCPP_ISTREAM& is, Value& root, bool collectComments = true); + bool parse(IStream& is, Value& root, bool collectComments = true); /** \brief Returns a user friendly string that list errors in the parsed * document. - * \return Formatted error message with the list of errors with their location - * in - * the parsed document. An empty string is returned if no error - * occurred - * during parsing. + * + * \return Formatted error message with the list of errors with their + * location in the parsed document. An empty string is returned if no error + * occurred during parsing. * \deprecated Use getFormattedErrorMessages() instead (typo fix). */ JSONCPP_DEPRECATED("Use getFormattedErrorMessages() instead.") - JSONCPP_STRING getFormatedErrorMessages() const; + String getFormatedErrorMessages() const; /** \brief Returns a user friendly string that list errors in the parsed * document. - * \return Formatted error message with the list of errors with their location - * in - * the parsed document. An empty string is returned if no error - * occurred - * during parsing. + * + * \return Formatted error message with the list of errors with their + * location in the parsed document. An empty string is returned if no error + * occurred during parsing. */ - JSONCPP_STRING getFormattedErrorMessages() const; + String getFormattedErrorMessages() const; - /** \brief Returns a vector of structured erros encounted while parsing. + /** \brief Returns a vector of structured errors encountered while parsing. + * * \return A (possibly empty) vector of StructuredError objects. Currently - * only one error can be returned, but the caller should tolerate - * multiple - * errors. This can occur if the parser recovers from a non-fatal - * parse error and then encounters additional errors. + * only one error can be returned, but the caller should tolerate multiple + * errors. This can occur if the parser recovers from a non-fatal parse + * error and then encounters additional errors. */ std::vector getStructuredErrors() const; /** \brief Add a semantic error message. - * \param value JSON Value location associated with the error + * + * \param value JSON Value location associated with the error * \param message The error message. - * \return \c true if the error was successfully added, \c false if the - * Value offset exceeds the document size. + * \return \c true if the error was successfully added, \c false if the Value + * offset exceeds the document size. */ - bool pushError(const Value& value, const JSONCPP_STRING& message); + bool pushError(const Value& value, const String& message); /** \brief Add a semantic error message with extra context. - * \param value JSON Value location associated with the error + * + * \param value JSON Value location associated with the error * \param message The error message. - * \param extra Additional JSON Value location to contextualize the error + * \param extra Additional JSON Value location to contextualize the error * \return \c true if the error was successfully added, \c false if either * Value offset exceeds the document size. */ - bool pushError(const Value& value, const JSONCPP_STRING& message, const Value& extra); + bool pushError(const Value& value, const String& message, const Value& extra); /** \brief Return whether there are any errors. - * \return \c true if there are no errors to report \c false if - * errors have occurred. + * + * \return \c true if there are no errors to report \c false if errors have + * occurred. */ bool good() const; @@ -1532,15 +1657,15 @@ private: class ErrorInfo { public: Token token_; - JSONCPP_STRING message_; + String message_; Location extra_; }; - typedef std::deque Errors; + using Errors = std::deque; bool readToken(Token& token); void skipSpaces(); - bool match(Location pattern, int patternLength); + bool match(const Char* pattern, int patternLength); bool readComment(); bool readCStyleComment(); bool readCppStyleComment(); @@ -1552,142 +1677,138 @@ private: bool decodeNumber(Token& token); bool decodeNumber(Token& token, Value& decoded); bool decodeString(Token& token); - bool decodeString(Token& token, JSONCPP_STRING& decoded); + bool decodeString(Token& token, String& decoded); bool decodeDouble(Token& token); bool decodeDouble(Token& token, Value& decoded); - bool decodeUnicodeCodePoint(Token& token, - Location& current, - Location end, + bool decodeUnicodeCodePoint(Token& token, Location& current, Location end, unsigned int& unicode); - bool decodeUnicodeEscapeSequence(Token& token, - Location& current, - Location end, - unsigned int& unicode); - bool addError(const JSONCPP_STRING& message, Token& token, Location extra = 0); + bool decodeUnicodeEscapeSequence(Token& token, Location& current, + Location end, unsigned int& unicode); + bool addError(const String& message, Token& token, Location extra = nullptr); bool recoverFromError(TokenType skipUntilToken); - bool addErrorAndRecover(const JSONCPP_STRING& message, - Token& token, + bool addErrorAndRecover(const String& message, Token& token, TokenType skipUntilToken); void skipUntilSpace(); Value& currentValue(); Char getNextChar(); - void - getLocationLineAndColumn(Location location, int& line, int& column) const; - JSONCPP_STRING getLocationLineAndColumn(Location location) const; + void getLocationLineAndColumn(Location location, int& line, + int& column) const; + String getLocationLineAndColumn(Location location) const; void addComment(Location begin, Location end, CommentPlacement placement); void skipCommentTokens(Token& token); static bool containsNewLine(Location begin, Location end); - static JSONCPP_STRING normalizeEOL(Location begin, Location end); + static String normalizeEOL(Location begin, Location end); - typedef std::stack Nodes; + using Nodes = std::stack; Nodes nodes_; Errors errors_; - JSONCPP_STRING document_; - Location begin_; - Location end_; - Location current_; - Location lastValueEnd_; - Value* lastValue_; - JSONCPP_STRING commentsBefore_; + String document_; + Location begin_{}; + Location end_{}; + Location current_{}; + Location lastValueEnd_{}; + Value* lastValue_{}; + String commentsBefore_; Features features_; - bool collectComments_; -}; // Reader + bool collectComments_{}; +}; // Reader /** Interface for reading JSON from a char array. */ class JSON_API CharReader { public: - virtual ~CharReader() {} + virtual ~CharReader() = default; /** \brief Read a Value from a JSON - document. - * The document must be a UTF-8 encoded string containing the document to read. + * document. The document must be a UTF-8 encoded string containing the + * document to read. * - * \param beginDoc Pointer on the beginning of the UTF-8 encoded string of the - document to read. - * \param endDoc Pointer on the end of the UTF-8 encoded string of the - document to read. - * Must be >= beginDoc. - * \param root [out] Contains the root value of the document if it was - * successfully parsed. - * \param errs [out] Formatted error messages (if not NULL) - * a user friendly string that lists errors in the parsed - * document. + * \param beginDoc Pointer on the beginning of the UTF-8 encoded string + * of the document to read. + * \param endDoc Pointer on the end of the UTF-8 encoded string of the + * document to read. Must be >= beginDoc. + * \param[out] root Contains the root value of the document if it was + * successfully parsed. + * \param[out] errs Formatted error messages (if not NULL) a user + * friendly string that lists errors in the parsed + * document. * \return \c true if the document was successfully parsed, \c false if an - error occurred. + * error occurred. */ - virtual bool parse( - char const* beginDoc, char const* endDoc, - Value* root, JSONCPP_STRING* errs) = 0; + virtual bool parse(char const* beginDoc, char const* endDoc, Value* root, + String* errs) = 0; class JSON_API Factory { public: - virtual ~Factory() {} + virtual ~Factory() = default; /** \brief Allocate a CharReader via operator new(). * \throw std::exception if something goes wrong (e.g. invalid settings) */ virtual CharReader* newCharReader() const = 0; - }; // Factory -}; // CharReader + }; // Factory +}; // CharReader /** \brief Build a CharReader implementation. - -Usage: -\code - using namespace Json; - CharReaderBuilder builder; - builder["collectComments"] = false; - Value value; - JSONCPP_STRING errs; - bool ok = parseFromStream(builder, std::cin, &value, &errs); -\endcode -*/ + * + * Usage: + * \code + * using namespace Json; + * CharReaderBuilder builder; + * builder["collectComments"] = false; + * Value value; + * String errs; + * bool ok = parseFromStream(builder, std::cin, &value, &errs); + * \endcode + */ class JSON_API CharReaderBuilder : public CharReader::Factory { public: // Note: We use a Json::Value so that we can add data-members to this class // without a major version bump. /** Configuration of this builder. - These are case-sensitive. - Available settings (case-sensitive): - - `"collectComments": false or true` - - true to collect comment and allow writing them - back during serialization, false to discard comments. - This parameter is ignored if allowComments is false. - - `"allowComments": false or true` - - true if comments are allowed. - - `"strictRoot": false or true` - - true if root must be either an array or an object value - - `"allowDroppedNullPlaceholders": false or true` - - true if dropped null placeholders are allowed. (See StreamWriterBuilder.) - - `"allowNumericKeys": false or true` - - true if numeric object keys are allowed. - - `"allowSingleQuotes": false or true` - - true if '' are allowed for strings (both keys and values) - - `"stackLimit": integer` - - Exceeding stackLimit (recursive depth of `readValue()`) will - cause an exception. - - This is a security issue (seg-faults caused by deeply nested JSON), - so the default is low. - - `"failIfExtra": false or true` - - If true, `parse()` returns false when extra non-whitespace trails - the JSON value in the input string. - - `"rejectDupKeys": false or true` - - If true, `parse()` returns false when a key is duplicated within an object. - - `"allowSpecialFloats": false or true` - - If true, special float values (NaNs and infinities) are allowed - and their values are lossfree restorable. - - You can examine 'settings_` yourself - to see the defaults. You can also write and read them just like any - JSON Value. - \sa setDefaults() - */ + * These are case-sensitive. + * Available settings (case-sensitive): + * - `"collectComments": false or true` + * - true to collect comment and allow writing them back during + * serialization, false to discard comments. This parameter is ignored + * if allowComments is false. + * - `"allowComments": false or true` + * - true if comments are allowed. + * - `"allowTrailingCommas": false or true` + * - true if trailing commas in objects and arrays are allowed. + * - `"strictRoot": false or true` + * - true if root must be either an array or an object value + * - `"allowDroppedNullPlaceholders": false or true` + * - true if dropped null placeholders are allowed. (See + * StreamWriterBuilder.) + * - `"allowNumericKeys": false or true` + * - true if numeric object keys are allowed. + * - `"allowSingleQuotes": false or true` + * - true if '' are allowed for strings (both keys and values) + * - `"stackLimit": integer` + * - Exceeding stackLimit (recursive depth of `readValue()`) will cause an + * exception. + * - This is a security issue (seg-faults caused by deeply nested JSON), so + * the default is low. + * - `"failIfExtra": false or true` + * - If true, `parse()` returns false when extra non-whitespace trails the + * JSON value in the input string. + * - `"rejectDupKeys": false or true` + * - If true, `parse()` returns false when a key is duplicated within an + * object. + * - `"allowSpecialFloats": false or true` + * - If true, special float values (NaNs and infinities) are allowed and + * their values are lossfree restorable. + * + * You can examine 'settings_` yourself to see the defaults. You can also + * write and read them just like any JSON Value. + * \sa setDefaults() + */ Json::Value settings_; CharReaderBuilder(); - ~CharReaderBuilder() JSONCPP_OVERRIDE; + ~CharReaderBuilder() override; - CharReader* newCharReader() const JSONCPP_OVERRIDE; + CharReader* newCharReader() const override; /** \return true if 'settings' are legal and consistent; * otherwise, indicate bad settings via 'invalid'. @@ -1696,7 +1817,7 @@ public: /** A simple way to update a specific setting. */ - Value& operator[](JSONCPP_STRING key); + Value& operator[](const String& key); /** Called by ctor, but you can use this to reset settings_. * \pre 'settings' != NULL (but Json::null is fine) @@ -1713,39 +1834,37 @@ public: }; /** Consume entire stream and use its begin/end. - * Someday we might have a real StreamReader, but for now this - * is convenient. - */ -bool JSON_API parseFromStream( - CharReader::Factory const&, - JSONCPP_ISTREAM&, - Value* root, std::string* errs); + * Someday we might have a real StreamReader, but for now this + * is convenient. + */ +bool JSON_API parseFromStream(CharReader::Factory const&, IStream&, Value* root, + String* errs); /** \brief Read from 'sin' into 'root'. - - Always keep comments from the input JSON. - - This can be used to read a file into a particular sub-object. - For example: - \code - Json::Value root; - cin >> root["dir"]["file"]; - cout << root; - \endcode - Result: - \verbatim - { - "dir": { - "file": { - // The input stream JSON would be nested here. - } - } - } - \endverbatim - \throw std::exception on parse error. - \see Json::operator<<() -*/ -JSON_API JSONCPP_ISTREAM& operator>>(JSONCPP_ISTREAM&, Value&); + * + * Always keep comments from the input JSON. + * + * This can be used to read a file into a particular sub-object. + * For example: + * \code + * Json::Value root; + * cin >> root["dir"]["file"]; + * cout << root; + * \endcode + * Result: + * \verbatim + * { + * "dir": { + * "file": { + * // The input stream JSON would be nested here. + * } + * } + * } + * \endverbatim + * \throw std::exception on parse error. + * \see Json::operator<<() + */ +JSON_API IStream& operator>>(IStream&, Value&); } // namespace Json @@ -1755,7 +1874,7 @@ JSON_API JSONCPP_ISTREAM& operator>>(JSONCPP_ISTREAM&, Value&); #pragma warning(pop) #endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) -#endif // CPPTL_JSON_READER_H_INCLUDED +#endif // JSON_READER_H_INCLUDED // ////////////////////////////////////////////////////////////////////// // End of content of file: include/json/reader.h @@ -1781,9 +1900,9 @@ JSON_API JSONCPP_ISTREAM& operator>>(JSONCPP_ISTREAM&, Value&); #if !defined(JSON_IS_AMALGAMATION) #include "value.h" #endif // if !defined(JSON_IS_AMALGAMATION) -#include -#include #include +#include +#include // Disable warning C4251: : needs to have dll-interface to // be used by... @@ -1799,31 +1918,31 @@ namespace Json { class Value; /** - -Usage: -\code - using namespace Json; - void writeToStdout(StreamWriter::Factory const& factory, Value const& value) { - std::unique_ptr const writer( - factory.newStreamWriter()); - writer->write(value, &std::cout); - std::cout << std::endl; // add lf and flush - } -\endcode -*/ + * + * Usage: + * \code + * using namespace Json; + * void writeToStdout(StreamWriter::Factory const& factory, Value const& value) + * { std::unique_ptr const writer( factory.newStreamWriter()); + * writer->write(value, &std::cout); + * std::cout << std::endl; // add lf and flush + * } + * \endcode + */ class JSON_API StreamWriter { protected: - JSONCPP_OSTREAM* sout_; // not owned; will not delete + OStream* sout_; // not owned; will not delete public: StreamWriter(); virtual ~StreamWriter(); /** Write Value into document as configured in sub-class. - Do not take ownership of sout, but maintain a reference during function. - \pre sout != NULL - \return zero on success (For now, we always return zero, so check the stream instead.) - \throw std::exception possibly, depending on configuration + * Do not take ownership of sout, but maintain a reference during function. + * \pre sout != NULL + * \return zero on success (For now, we always return zero, so check the + * stream instead.) \throw std::exception possibly, depending on + * configuration */ - virtual int write(Value const& root, JSONCPP_OSTREAM* sout) = 0; + virtual int write(Value const& root, OStream* sout) = 0; /** \brief A simple abstract factory. */ @@ -1834,64 +1953,69 @@ public: * \throw std::exception if something goes wrong (e.g. invalid settings) */ virtual StreamWriter* newStreamWriter() const = 0; - }; // Factory -}; // StreamWriter + }; // Factory +}; // StreamWriter /** \brief Write into stringstream, then return string, for convenience. * A StreamWriter will be created from the factory, used, and then deleted. */ -JSONCPP_STRING JSON_API writeString(StreamWriter::Factory const& factory, Value const& root); - +String JSON_API writeString(StreamWriter::Factory const& factory, + Value const& root); /** \brief Build a StreamWriter implementation. -Usage: -\code - using namespace Json; - Value value = ...; - StreamWriterBuilder builder; - builder["commentStyle"] = "None"; - builder["indentation"] = " "; // or whatever you like - std::unique_ptr writer( - builder.newStreamWriter()); - writer->write(value, &std::cout); - std::cout << std::endl; // add lf and flush -\endcode +* Usage: +* \code +* using namespace Json; +* Value value = ...; +* StreamWriterBuilder builder; +* builder["commentStyle"] = "None"; +* builder["indentation"] = " "; // or whatever you like +* std::unique_ptr writer( +* builder.newStreamWriter()); +* writer->write(value, &std::cout); +* std::cout << std::endl; // add lf and flush +* \endcode */ class JSON_API StreamWriterBuilder : public StreamWriter::Factory { public: // Note: We use a Json::Value so that we can add data-members to this class // without a major version bump. /** Configuration of this builder. - Available settings (case-sensitive): - - "commentStyle": "None" or "All" - - "indentation": "" - - "enableYAMLCompatibility": false or true - - slightly change the whitespace around colons - - "dropNullPlaceholders": false or true - - Drop the "null" string from the writer's output for nullValues. - Strictly speaking, this is not valid JSON. But when the output is being - fed to a browser's JavaScript, it makes for smaller output and the - browser can handle the output just fine. - - "useSpecialFloats": false or true - - If true, outputs non-finite floating point values in the following way: - NaN values as "NaN", positive infinity as "Infinity", and negative infinity - as "-Infinity". + * Available settings (case-sensitive): + * - "commentStyle": "None" or "All" + * - "indentation": "". + * - Setting this to an empty string also omits newline characters. + * - "enableYAMLCompatibility": false or true + * - slightly change the whitespace around colons + * - "dropNullPlaceholders": false or true + * - Drop the "null" string from the writer's output for nullValues. + * Strictly speaking, this is not valid JSON. But when the output is being + * fed to a browser's JavaScript, it makes for smaller output and the + * browser can handle the output just fine. + * - "useSpecialFloats": false or true + * - If true, outputs non-finite floating point values in the following way: + * NaN values as "NaN", positive infinity as "Infinity", and negative + * infinity as "-Infinity". + * - "precision": int + * - Number of precision digits for formatting of real values. + * - "precisionType": "significant"(default) or "decimal" + * - Type of precision for formatting of real values. - You can examine 'settings_` yourself - to see the defaults. You can also write and read them just like any - JSON Value. - \sa setDefaults() - */ + * You can examine 'settings_` yourself + * to see the defaults. You can also write and read them just like any + * JSON Value. + * \sa setDefaults() + */ Json::Value settings_; StreamWriterBuilder(); - ~StreamWriterBuilder() JSONCPP_OVERRIDE; + ~StreamWriterBuilder() override; /** * \throw std::exception if something goes wrong (e.g. invalid settings) */ - StreamWriter* newStreamWriter() const JSONCPP_OVERRIDE; + StreamWriter* newStreamWriter() const override; /** \return true if 'settings' are legal and consistent; * otherwise, indicate bad settings via 'invalid'. @@ -1899,7 +2023,7 @@ public: bool validate(Json::Value* invalid) const; /** A simple way to update a specific setting. */ - Value& operator[](JSONCPP_STRING key); + Value& operator[](const String& key); /** Called by ctor, but you can use this to reset settings_. * \pre 'settings' != NULL (but Json::null is fine) @@ -1916,7 +2040,7 @@ class JSONCPP_DEPRECATED("Use StreamWriter instead") JSON_API Writer { public: virtual ~Writer(); - virtual JSONCPP_STRING write(const Value& root) = 0; + virtual String write(const Value& root) = 0; }; /** \brief Outputs a Value in JSON format @@ -1924,18 +2048,19 @@ public: * * The JSON document is written in a single line. It is not intended for 'human' *consumption, - * but may be usefull to support feature such as RPC where bandwith is limited. + * but may be useful to support feature such as RPC where bandwidth is limited. * \sa Reader, Value * \deprecated Use StreamWriterBuilder. */ #if defined(_MSC_VER) #pragma warning(push) -#pragma warning(disable:4996) // Deriving from deprecated class +#pragma warning(disable : 4996) // Deriving from deprecated class #endif -class JSONCPP_DEPRECATED("Use StreamWriterBuilder instead") JSON_API FastWriter : public Writer { +class JSONCPP_DEPRECATED("Use StreamWriterBuilder instead") JSON_API FastWriter + : public Writer { public: FastWriter(); - ~FastWriter() JSONCPP_OVERRIDE {} + ~FastWriter() override = default; void enableYAMLCompatibility(); @@ -1949,15 +2074,15 @@ public: void omitEndingLineFeed(); public: // overridden from Writer - JSONCPP_STRING write(const Value& root) JSONCPP_OVERRIDE; + String write(const Value& root) override; private: void writeValue(const Value& value); - JSONCPP_STRING document_; - bool yamlCompatibilityEnabled_; - bool dropNullPlaceholders_; - bool omitEndingLineFeed_; + String document_; + bool yamlCompatibilityEnabled_{false}; + bool dropNullPlaceholders_{false}; + bool omitEndingLineFeed_{false}; }; #if defined(_MSC_VER) #pragma warning(pop) @@ -1989,42 +2114,43 @@ private: */ #if defined(_MSC_VER) #pragma warning(push) -#pragma warning(disable:4996) // Deriving from deprecated class +#pragma warning(disable : 4996) // Deriving from deprecated class #endif -class JSONCPP_DEPRECATED("Use StreamWriterBuilder instead") JSON_API StyledWriter : public Writer { +class JSONCPP_DEPRECATED("Use StreamWriterBuilder instead") JSON_API + StyledWriter : public Writer { public: StyledWriter(); - ~StyledWriter() JSONCPP_OVERRIDE {} + ~StyledWriter() override = default; public: // overridden from Writer /** \brief Serialize a Value in JSON format. * \param root Value to serialize. * \return String containing the JSON document that represents the root value. */ - JSONCPP_STRING write(const Value& root) JSONCPP_OVERRIDE; + String write(const Value& root) override; private: void writeValue(const Value& value); void writeArrayValue(const Value& value); bool isMultilineArray(const Value& value); - void pushValue(const JSONCPP_STRING& value); + void pushValue(const String& value); void writeIndent(); - void writeWithIndent(const JSONCPP_STRING& value); + void writeWithIndent(const String& value); void indent(); void unindent(); void writeCommentBeforeValue(const Value& root); void writeCommentAfterValueOnSameLine(const Value& root); - bool hasCommentForValue(const Value& value); - static JSONCPP_STRING normalizeEOL(const JSONCPP_STRING& text); + static bool hasCommentForValue(const Value& value); + static String normalizeEOL(const String& text); - typedef std::vector ChildValues; + using ChildValues = std::vector; ChildValues childValues_; - JSONCPP_STRING document_; - JSONCPP_STRING indentString_; - unsigned int rightMargin_; - unsigned int indentSize_; - bool addChildValues_; + String document_; + String indentString_; + unsigned int rightMargin_{74}; + unsigned int indentSize_{3}; + bool addChildValues_{false}; }; #if defined(_MSC_VER) #pragma warning(pop) @@ -2057,15 +2183,16 @@ private: */ #if defined(_MSC_VER) #pragma warning(push) -#pragma warning(disable:4996) // Deriving from deprecated class +#pragma warning(disable : 4996) // Deriving from deprecated class #endif -class JSONCPP_DEPRECATED("Use StreamWriterBuilder instead") JSON_API StyledStreamWriter { +class JSONCPP_DEPRECATED("Use StreamWriterBuilder instead") JSON_API + StyledStreamWriter { public: -/** - * \param indentation Each level will be indented by this amount extra. - */ - StyledStreamWriter(JSONCPP_STRING indentation = "\t"); - ~StyledStreamWriter() {} + /** + * \param indentation Each level will be indented by this amount extra. + */ + StyledStreamWriter(String indentation = "\t"); + ~StyledStreamWriter() = default; public: /** \brief Serialize a Value in JSON format. @@ -2074,29 +2201,29 @@ public: * \note There is no point in deriving from Writer, since write() should not * return a value. */ - void write(JSONCPP_OSTREAM& out, const Value& root); + void write(OStream& out, const Value& root); private: void writeValue(const Value& value); void writeArrayValue(const Value& value); bool isMultilineArray(const Value& value); - void pushValue(const JSONCPP_STRING& value); + void pushValue(const String& value); void writeIndent(); - void writeWithIndent(const JSONCPP_STRING& value); + void writeWithIndent(const String& value); void indent(); void unindent(); void writeCommentBeforeValue(const Value& root); void writeCommentAfterValueOnSameLine(const Value& root); - bool hasCommentForValue(const Value& value); - static JSONCPP_STRING normalizeEOL(const JSONCPP_STRING& text); + static bool hasCommentForValue(const Value& value); + static String normalizeEOL(const String& text); - typedef std::vector ChildValues; + using ChildValues = std::vector; ChildValues childValues_; - JSONCPP_OSTREAM* document_; - JSONCPP_STRING indentString_; - unsigned int rightMargin_; - JSONCPP_STRING indentation_; + OStream* document_; + String indentString_; + unsigned int rightMargin_{74}; + String indentation_; bool addChildValues_ : 1; bool indented_ : 1; }; @@ -2105,18 +2232,20 @@ private: #endif #if defined(JSON_HAS_INT64) -JSONCPP_STRING JSON_API valueToString(Int value); -JSONCPP_STRING JSON_API valueToString(UInt value); +String JSON_API valueToString(Int value); +String JSON_API valueToString(UInt value); #endif // if defined(JSON_HAS_INT64) -JSONCPP_STRING JSON_API valueToString(LargestInt value); -JSONCPP_STRING JSON_API valueToString(LargestUInt value); -JSONCPP_STRING JSON_API valueToString(double value); -JSONCPP_STRING JSON_API valueToString(bool value); -JSONCPP_STRING JSON_API valueToQuotedString(const char* value); +String JSON_API valueToString(LargestInt value); +String JSON_API valueToString(LargestUInt value); +String JSON_API valueToString( + double value, unsigned int precision = Value::defaultRealPrecision, + PrecisionType precisionType = PrecisionType::significantDigits); +String JSON_API valueToString(bool value); +String JSON_API valueToQuotedString(const char* value); /// \brief Output using the StyledStreamWriter. /// \see Json::operator>>() -JSON_API JSONCPP_OSTREAM& operator<<(JSONCPP_OSTREAM&, const Value& root); +JSON_API OStream& operator<<(OStream&, const Value& root); } // namespace Json @@ -2146,10 +2275,10 @@ JSON_API JSONCPP_OSTREAM& operator<<(JSONCPP_OSTREAM&, const Value& root); // recognized in your jurisdiction. // See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE -#ifndef CPPTL_JSON_ASSERTIONS_H_INCLUDED -#define CPPTL_JSON_ASSERTIONS_H_INCLUDED +#ifndef JSON_ASSERTIONS_H_INCLUDED +#define JSON_ASSERTIONS_H_INCLUDED -#include +#include #include #if !defined(JSON_IS_AMALGAMATION) @@ -2163,38 +2292,45 @@ JSON_API JSONCPP_OSTREAM& operator<<(JSONCPP_OSTREAM&, const Value& root); #if JSON_USE_EXCEPTION // @todo <= add detail about condition in exception -# define JSON_ASSERT(condition) \ - {if (!(condition)) {Json::throwLogicError( "assert json failed" );}} +#define JSON_ASSERT(condition) \ + do { \ + if (!(condition)) { \ + Json::throwLogicError("assert json failed"); \ + } \ + } while (0) -# define JSON_FAIL_MESSAGE(message) \ - { \ - JSONCPP_OSTRINGSTREAM oss; oss << message; \ +#define JSON_FAIL_MESSAGE(message) \ + do { \ + OStringStream oss; \ + oss << message; \ Json::throwLogicError(oss.str()); \ abort(); \ - } + } while (0) #else // JSON_USE_EXCEPTION -# define JSON_ASSERT(condition) assert(condition) +#define JSON_ASSERT(condition) assert(condition) // The call to assert() will show the failure message in debug builds. In // release builds we abort, for a core-dump or debugger. -# define JSON_FAIL_MESSAGE(message) \ +#define JSON_FAIL_MESSAGE(message) \ { \ - JSONCPP_OSTRINGSTREAM oss; oss << message; \ + OStringStream oss; \ + oss << message; \ assert(false && oss.str().c_str()); \ abort(); \ } - #endif #define JSON_ASSERT_MESSAGE(condition, message) \ - if (!(condition)) { \ - JSON_FAIL_MESSAGE(message); \ - } + do { \ + if (!(condition)) { \ + JSON_FAIL_MESSAGE(message); \ + } \ + } while (0) -#endif // CPPTL_JSON_ASSERTIONS_H_INCLUDED +#endif // JSON_ASSERTIONS_H_INCLUDED // ////////////////////////////////////////////////////////////////////// // End of content of file: include/json/assertions.h diff --git a/lib/jsoncpp/jsoncpp.cpp b/lib/jsoncpp/jsoncpp.cpp index 507a1c6ad..30e0a69f7 100644 --- a/lib/jsoncpp/jsoncpp.cpp +++ b/lib/jsoncpp/jsoncpp.cpp @@ -92,6 +92,9 @@ license you like. #ifndef LIB_JSONCPP_JSON_TOOL_H_INCLUDED #define LIB_JSONCPP_JSON_TOOL_H_INCLUDED +#if !defined(JSON_IS_AMALGAMATION) +#include +#endif // Also support old flag NO_LOCALE_SUPPORT #ifdef NO_LOCALE_SUPPORT @@ -109,7 +112,7 @@ license you like. */ namespace Json { -static char getDecimalPoint() { +static inline char getDecimalPoint() { #ifdef JSONCPP_NO_LOCALE_SUPPORT return '\0'; #else @@ -119,8 +122,8 @@ static char getDecimalPoint() { } /// Converts a unicode code-point to UTF-8. -static inline JSONCPP_STRING codePointToUTF8(unsigned int cp) { - JSONCPP_STRING result; +static inline String codePointToUTF8(unsigned int cp) { + String result; // based on description from http://en.wikipedia.org/wiki/UTF-8 @@ -154,7 +157,7 @@ enum { }; // Defines a char buffer for use with uintToString(). -typedef char UIntToStringBuffer[uintToStringBufferSize]; +using UIntToStringBuffer = char[uintToStringBufferSize]; /** Converts an unsigned integer to string. * @param value Unsigned integer to convert to string @@ -174,28 +177,45 @@ static inline void uintToString(LargestUInt value, char*& current) { * We had a sophisticated way, but it did not work in WinCE. * @see https://github.com/open-source-parsers/jsoncpp/pull/9 */ -static inline void fixNumericLocale(char* begin, char* end) { - while (begin < end) { +template Iter fixNumericLocale(Iter begin, Iter end) { + for (; begin != end; ++begin) { if (*begin == ',') { *begin = '.'; } - ++begin; } + return begin; } -static inline void fixNumericLocaleInput(char* begin, char* end) { +template void fixNumericLocaleInput(Iter begin, Iter end) { char decimalPoint = getDecimalPoint(); - if (decimalPoint != '\0' && decimalPoint != '.') { - while (begin < end) { - if (*begin == '.') { - *begin = decimalPoint; - } - ++begin; + if (decimalPoint == '\0' || decimalPoint == '.') { + return; + } + for (; begin != end; ++begin) { + if (*begin == '.') { + *begin = decimalPoint; } } } -} // namespace Json { +/** + * Return iterator that would be the new end of the range [begin,end), if we + * were to delete zeros in the end of string, but not the last zero before '.'. + */ +template Iter fixZerosInTheEnd(Iter begin, Iter end) { + for (; begin != end; --end) { + if (*(end - 1) != '0') { + return end; + } + // Don't delete the last zero before the decimal point. + if (begin != (end - 1) && *(end - 2) == '.') { + return end; + } + } + return end; +} + +} // namespace Json #endif // LIB_JSONCPP_JSON_TOOL_H_INCLUDED @@ -219,69 +239,65 @@ static inline void fixNumericLocaleInput(char* begin, char* end) { // See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE #if !defined(JSON_IS_AMALGAMATION) +#include "json_tool.h" #include #include #include -#include "json_tool.h" #endif // if !defined(JSON_IS_AMALGAMATION) -#include -#include +#include #include #include +#include #include -#include +#include #include #include -#include +#include +#include -#if defined(_MSC_VER) -#if !defined(WINCE) && defined(__STDC_SECURE_LIB__) && _MSC_VER >= 1500 // VC++ 9.0 and above -#define snprintf sprintf_s -#elif _MSC_VER >= 1900 // VC++ 14.0 and above -#define snprintf std::snprintf -#else -#define snprintf _snprintf -#endif -#elif defined(__ANDROID__) || defined(__QNXNTO__) -#define snprintf snprintf -#elif __cplusplus >= 201103L -#if !defined(__MINGW32__) && !defined(__CYGWIN__) -#define snprintf std::snprintf -#endif -#endif +#include +#if __cplusplus >= 201103L -#if defined(__QNXNTO__) +#if !defined(sscanf) #define sscanf std::sscanf #endif -#if defined(_MSC_VER) && _MSC_VER >= 1400 // VC++ 8.0 +#endif //__cplusplus + +#if defined(_MSC_VER) +#if !defined(_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES) +#define _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES 1 +#endif //_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES +#endif //_MSC_VER + +#if defined(_MSC_VER) // Disable warning about strdup being deprecated. #pragma warning(disable : 4996) #endif -// Define JSONCPP_DEPRECATED_STACK_LIMIT as an appropriate integer at compile time to change the stack limit +// Define JSONCPP_DEPRECATED_STACK_LIMIT as an appropriate integer at compile +// time to change the stack limit #if !defined(JSONCPP_DEPRECATED_STACK_LIMIT) #define JSONCPP_DEPRECATED_STACK_LIMIT 1000 #endif -static size_t const stackLimit_g = JSONCPP_DEPRECATED_STACK_LIMIT; // see readValue() +static size_t const stackLimit_g = + JSONCPP_DEPRECATED_STACK_LIMIT; // see readValue() namespace Json { #if __cplusplus >= 201103L || (defined(_CPPLIB_VER) && _CPPLIB_VER >= 520) -typedef std::unique_ptr CharReaderPtr; +using CharReaderPtr = std::unique_ptr; #else -typedef std::auto_ptr CharReaderPtr; +using CharReaderPtr = std::auto_ptr; #endif // Implementation of class Features // //////////////////////////////// -Features::Features() - : allowComments_(true), strictRoot_(false), - allowDroppedNullPlaceholders_(false), allowNumericKeys_(false) {} +Features::Features() = default; -Features Features::all() { return Features(); } +Features Features::all() { return {}; } Features Features::strictMode() { Features features; @@ -296,49 +312,38 @@ Features Features::strictMode() { // //////////////////////////////// bool Reader::containsNewLine(Reader::Location begin, Reader::Location end) { - for (; begin < end; ++begin) - if (*begin == '\n' || *begin == '\r') - return true; - return false; + return std::any_of(begin, end, [](char b) { return b == '\n' || b == '\r'; }); } // Class Reader // ////////////////////////////////////////////////////////////////// -Reader::Reader() - : errors_(), document_(), begin_(), end_(), current_(), lastValueEnd_(), - lastValue_(), commentsBefore_(), features_(Features::all()), - collectComments_() {} +Reader::Reader() : features_(Features::all()) {} -Reader::Reader(const Features& features) - : errors_(), document_(), begin_(), end_(), current_(), lastValueEnd_(), - lastValue_(), commentsBefore_(), features_(features), collectComments_() { -} +Reader::Reader(const Features& features) : features_(features) {} -bool -Reader::parse(const std::string& document, Value& root, bool collectComments) { +bool Reader::parse(const std::string& document, Value& root, + bool collectComments) { document_.assign(document.begin(), document.end()); const char* begin = document_.c_str(); const char* end = begin + document_.length(); return parse(begin, end, root, collectComments); } -bool Reader::parse(std::istream& sin, Value& root, bool collectComments) { - // std::istream_iterator begin(sin); +bool Reader::parse(std::istream& is, Value& root, bool collectComments) { + // std::istream_iterator begin(is); // std::istream_iterator end; // Those would allow streamed input from a file, if parse() were a // template function. - // Since JSONCPP_STRING is reference-counted, this at least does not + // Since String is reference-counted, this at least does not // create an extra copy. - JSONCPP_STRING doc; - std::getline(sin, doc, (char)EOF); + String doc; + std::getline(is, doc, static_cast EOF); return parse(doc.data(), doc.data() + doc.size(), root, collectComments); } -bool Reader::parse(const char* beginDoc, - const char* endDoc, - Value& root, +bool Reader::parse(const char* beginDoc, const char* endDoc, Value& root, bool collectComments) { if (!features_.allowComments_) { collectComments = false; @@ -348,8 +353,8 @@ bool Reader::parse(const char* beginDoc, end_ = endDoc; collectComments_ = collectComments; current_ = begin_; - lastValueEnd_ = 0; - lastValue_ = 0; + lastValueEnd_ = nullptr; + lastValue_ = nullptr; commentsBefore_.clear(); errors_.clear(); while (!nodes_.empty()) @@ -379,9 +384,11 @@ bool Reader::parse(const char* beginDoc, bool Reader::readValue() { // readValue() may call itself only if it calls readObject() or ReadArray(). - // These methods execute nodes_.push() just before and nodes_.pop)() just after calling readValue(). - // parse() executes one nodes_.push(), so > instead of >=. - if (nodes_.size() > stackLimit_g) throwRuntimeError("Exceeded stackLimit in readValue()."); + // These methods execute nodes_.push() just before and nodes_.pop)() just + // after calling readValue(). parse() executes one nodes_.push(), so > instead + // of >=. + if (nodes_.size() > stackLimit_g) + throwRuntimeError("Exceeded stackLimit in readValue()."); Token token; skipCommentTokens(token); @@ -407,30 +414,24 @@ bool Reader::readValue() { case tokenString: successful = decodeString(token); break; - case tokenTrue: - { + case tokenTrue: { Value v(true); currentValue().swapPayload(v); currentValue().setOffsetStart(token.start_ - begin_); currentValue().setOffsetLimit(token.end_ - begin_); - } - break; - case tokenFalse: - { + } break; + case tokenFalse: { Value v(false); currentValue().swapPayload(v); currentValue().setOffsetStart(token.start_ - begin_); currentValue().setOffsetLimit(token.end_ - begin_); - } - break; - case tokenNull: - { + } break; + case tokenNull: { Value v; currentValue().swapPayload(v); currentValue().setOffsetStart(token.start_ - begin_); currentValue().setOffsetLimit(token.end_ - begin_); - } - break; + } break; case tokenArraySeparator: case tokenObjectEnd: case tokenArrayEnd: @@ -536,7 +537,7 @@ bool Reader::readToken(Token& token) { if (!ok) token.type_ = tokenError; token.end_ = current_; - return true; + return ok; } void Reader::skipSpaces() { @@ -549,7 +550,7 @@ void Reader::skipSpaces() { } } -bool Reader::match(Location pattern, int patternLength) { +bool Reader::match(const Char* pattern, int patternLength) { if (end_ - current_ < patternLength) return false; int index = patternLength; @@ -583,16 +584,16 @@ bool Reader::readComment() { return true; } -JSONCPP_STRING Reader::normalizeEOL(Reader::Location begin, Reader::Location end) { - JSONCPP_STRING normalized; +String Reader::normalizeEOL(Reader::Location begin, Reader::Location end) { + String normalized; normalized.reserve(static_cast(end - begin)); Reader::Location current = begin; while (current != end) { char c = *current++; if (c == '\r') { if (current != end && *current == '\n') - // convert dos EOL - ++current; + // convert dos EOL + ++current; // convert Mac EOL normalized += '\n'; } else { @@ -602,12 +603,12 @@ JSONCPP_STRING Reader::normalizeEOL(Reader::Location begin, Reader::Location end return normalized; } -void -Reader::addComment(Location begin, Location end, CommentPlacement placement) { +void Reader::addComment(Location begin, Location end, + CommentPlacement placement) { assert(collectComments_); - const JSONCPP_STRING& normalized = normalizeEOL(begin, end); + const String& normalized = normalizeEOL(begin, end); if (placement == commentAfterOnSameLine) { - assert(lastValue_ != 0); + assert(lastValue_ != nullptr); lastValue_->setComment(normalized, placement); } else { commentsBefore_ += normalized; @@ -640,7 +641,7 @@ bool Reader::readCppStyleComment() { } void Reader::readNumber() { - const char *p = current_; + Location p = current_; char c = '0'; // stopgap for already consumed character // integral part while (c >= '0' && c <= '9') @@ -673,12 +674,12 @@ bool Reader::readString() { return c == '"'; } -bool Reader::readObject(Token& tokenStart) { +bool Reader::readObject(Token& token) { Token tokenName; - JSONCPP_STRING name; + String name; Value init(objectValue); currentValue().swapPayload(init); - currentValue().setOffsetStart(tokenStart.start_ - begin_); + currentValue().setOffsetStart(token.start_ - begin_); while (readToken(tokenName)) { bool initialTokenOk = true; while (tokenName.type_ == tokenComment && initialTokenOk) @@ -695,15 +696,15 @@ bool Reader::readObject(Token& tokenStart) { Value numberName; if (!decodeNumber(tokenName, numberName)) return recoverFromError(tokenObjectEnd); - name = JSONCPP_STRING(numberName.asCString()); + name = numberName.asString(); } else { break; } Token colon; if (!readToken(colon) || colon.type_ != tokenMemberSeparator) { - return addErrorAndRecover( - "Missing ':' after object member name", colon, tokenObjectEnd); + return addErrorAndRecover("Missing ':' after object member name", colon, + tokenObjectEnd); } Value& value = currentValue()[name]; nodes_.push(&value); @@ -716,8 +717,8 @@ bool Reader::readObject(Token& tokenStart) { if (!readToken(comma) || (comma.type_ != tokenObjectEnd && comma.type_ != tokenArraySeparator && comma.type_ != tokenComment)) { - return addErrorAndRecover( - "Missing ',' or '}' in object declaration", comma, tokenObjectEnd); + return addErrorAndRecover("Missing ',' or '}' in object declaration", + comma, tokenObjectEnd); } bool finalizeTokenOk = true; while (comma.type_ == tokenComment && finalizeTokenOk) @@ -725,14 +726,14 @@ bool Reader::readObject(Token& tokenStart) { if (comma.type_ == tokenObjectEnd) return true; } - return addErrorAndRecover( - "Missing '}' or object member name", tokenName, tokenObjectEnd); + return addErrorAndRecover("Missing '}' or object member name", tokenName, + tokenObjectEnd); } -bool Reader::readArray(Token& tokenStart) { +bool Reader::readArray(Token& token) { Value init(arrayValue); currentValue().swapPayload(init); - currentValue().setOffsetStart(tokenStart.start_ - begin_); + currentValue().setOffsetStart(token.start_ - begin_); skipSpaces(); if (current_ != end_ && *current_ == ']') // empty array { @@ -749,19 +750,19 @@ bool Reader::readArray(Token& tokenStart) { if (!ok) // error already set return recoverFromError(tokenArrayEnd); - Token token; + Token currentToken; // Accept Comment after last item in the array. - ok = readToken(token); - while (token.type_ == tokenComment && ok) { - ok = readToken(token); + ok = readToken(currentToken); + while (currentToken.type_ == tokenComment && ok) { + ok = readToken(currentToken); } - bool badTokenType = - (token.type_ != tokenArraySeparator && token.type_ != tokenArrayEnd); + bool badTokenType = (currentToken.type_ != tokenArraySeparator && + currentToken.type_ != tokenArrayEnd); if (!ok || badTokenType) { - return addErrorAndRecover( - "Missing ',' or ']' in array declaration", token, tokenArrayEnd); + return addErrorAndRecover("Missing ',' or ']' in array declaration", + currentToken, tokenArrayEnd); } - if (token.type_ == tokenArrayEnd) + if (currentToken.type_ == tokenArrayEnd) break; } return true; @@ -785,7 +786,8 @@ bool Reader::decodeNumber(Token& token, Value& decoded) { bool isNegative = *current == '-'; if (isNegative) ++current; - // TODO: Help the compiler do the div and mod at compile time or get rid of them. + // TODO: Help the compiler do the div and mod at compile time or get rid of + // them. Value::LargestUInt maxIntegerValue = isNegative ? Value::LargestUInt(Value::maxLargestInt) + 1 : Value::maxLargestUInt; @@ -795,7 +797,7 @@ bool Reader::decodeNumber(Token& token, Value& decoded) { Char c = *current++; if (c < '0' || c > '9') return decodeDouble(token, decoded); - Value::UInt digit(static_cast(c - '0')); + auto digit(static_cast(c - '0')); if (value >= threshold) { // We've hit or exceeded the max value divided by 10 (rounded down). If // a) we've only just touched the limit, b) this is the last digit, and @@ -831,18 +833,17 @@ bool Reader::decodeDouble(Token& token) { bool Reader::decodeDouble(Token& token, Value& decoded) { double value = 0; - JSONCPP_STRING buffer(token.start_, token.end_); - JSONCPP_ISTRINGSTREAM is(buffer); + String buffer(token.start_, token.end_); + IStringStream is(buffer); if (!(is >> value)) - return addError("'" + JSONCPP_STRING(token.start_, token.end_) + - "' is not a number.", - token); + return addError( + "'" + String(token.start_, token.end_) + "' is not a number.", token); decoded = value; return true; } bool Reader::decodeString(Token& token) { - JSONCPP_STRING decoded_string; + String decoded_string; if (!decodeString(token, decoded_string)) return false; Value decoded(decoded_string); @@ -852,7 +853,7 @@ bool Reader::decodeString(Token& token) { return true; } -bool Reader::decodeString(Token& token, JSONCPP_STRING& decoded) { +bool Reader::decodeString(Token& token, String& decoded) { decoded.reserve(static_cast(token.end_ - token.start_ - 2)); Location current = token.start_ + 1; // skip '"' Location end = token.end_ - 1; // do not include '"' @@ -860,7 +861,7 @@ bool Reader::decodeString(Token& token, JSONCPP_STRING& decoded) { Char c = *current++; if (c == '"') break; - else if (c == '\\') { + if (c == '\\') { if (current == end) return addError("Empty escape sequence in string", token, current); Char escape = *current++; @@ -905,10 +906,8 @@ bool Reader::decodeString(Token& token, JSONCPP_STRING& decoded) { return true; } -bool Reader::decodeUnicodeCodePoint(Token& token, - Location& current, - Location end, - unsigned int& unicode) { +bool Reader::decodeUnicodeCodePoint(Token& token, Location& current, + Location end, unsigned int& unicode) { if (!decodeUnicodeEscapeSequence(token, current, end, unicode)) return false; @@ -917,10 +916,9 @@ bool Reader::decodeUnicodeCodePoint(Token& token, if (end - current < 6) return addError( "additional six characters expected to parse unicode surrogate pair.", - token, - current); - unsigned int surrogatePair; + token, current); if (*(current++) == '\\' && *(current++) == 'u') { + unsigned int surrogatePair; if (decodeUnicodeEscapeSequence(token, current, end, surrogatePair)) { unicode = 0x10000 + ((unicode & 0x3FF) << 10) + (surrogatePair & 0x3FF); } else @@ -928,20 +926,17 @@ bool Reader::decodeUnicodeCodePoint(Token& token, } else return addError("expecting another \\u token to begin the second half of " "a unicode surrogate pair", - token, - current); + token, current); } return true; } -bool Reader::decodeUnicodeEscapeSequence(Token& token, - Location& current, +bool Reader::decodeUnicodeEscapeSequence(Token& token, Location& current, Location end, unsigned int& ret_unicode) { if (end - current < 4) return addError( - "Bad unicode escape sequence in string: four digits expected.", - token, + "Bad unicode escape sequence in string: four digits expected.", token, current); int unicode = 0; for (int index = 0; index < 4; ++index) { @@ -956,15 +951,13 @@ bool Reader::decodeUnicodeEscapeSequence(Token& token, else return addError( "Bad unicode escape sequence in string: hexadecimal digit expected.", - token, - current); + token, current); } ret_unicode = static_cast(unicode); return true; } -bool -Reader::addError(const JSONCPP_STRING& message, Token& token, Location extra) { +bool Reader::addError(const String& message, Token& token, Location extra) { ErrorInfo info; info.token_ = token; info.message_ = message; @@ -986,8 +979,7 @@ bool Reader::recoverFromError(TokenType skipUntilToken) { return false; } -bool Reader::addErrorAndRecover(const JSONCPP_STRING& message, - Token& token, +bool Reader::addErrorAndRecover(const String& message, Token& token, TokenType skipUntilToken) { addError(message, token); return recoverFromError(skipUntilToken); @@ -1001,8 +993,7 @@ Reader::Char Reader::getNextChar() { return *current_++; } -void Reader::getLocationLineAndColumn(Location location, - int& line, +void Reader::getLocationLineAndColumn(Location location, int& line, int& column) const { Location current = begin_; Location lastLineStart = current; @@ -1024,25 +1015,22 @@ void Reader::getLocationLineAndColumn(Location location, ++line; } -JSONCPP_STRING Reader::getLocationLineAndColumn(Location location) const { +String Reader::getLocationLineAndColumn(Location location) const { int line, column; getLocationLineAndColumn(location, line, column); char buffer[18 + 16 + 16 + 1]; - snprintf(buffer, sizeof(buffer), "Line %d, Column %d", line, column); + jsoncpp_snprintf(buffer, sizeof(buffer), "Line %d, Column %d", line, column); return buffer; } // Deprecated. Preserved for backward compatibility -JSONCPP_STRING Reader::getFormatedErrorMessages() const { +String Reader::getFormatedErrorMessages() const { return getFormattedErrorMessages(); } -JSONCPP_STRING Reader::getFormattedErrorMessages() const { - JSONCPP_STRING formattedMessage; - for (Errors::const_iterator itError = errors_.begin(); - itError != errors_.end(); - ++itError) { - const ErrorInfo& error = *itError; +String Reader::getFormattedErrorMessages() const { + String formattedMessage; + for (const auto& error : errors_) { formattedMessage += "* " + getLocationLineAndColumn(error.token_.start_) + "\n"; formattedMessage += " " + error.message_ + "\n"; @@ -1055,10 +1043,7 @@ JSONCPP_STRING Reader::getFormattedErrorMessages() const { std::vector Reader::getStructuredErrors() const { std::vector allErrors; - for (Errors::const_iterator itError = errors_.begin(); - itError != errors_.end(); - ++itError) { - const ErrorInfo& error = *itError; + for (const auto& error : errors_) { Reader::StructuredError structured; structured.offset_start = error.token_.start_ - begin_; structured.offset_limit = error.token_.end_ - begin_; @@ -1068,28 +1053,27 @@ std::vector Reader::getStructuredErrors() const { return allErrors; } -bool Reader::pushError(const Value& value, const JSONCPP_STRING& message) { +bool Reader::pushError(const Value& value, const String& message) { ptrdiff_t const length = end_ - begin_; - if(value.getOffsetStart() > length - || value.getOffsetLimit() > length) + if (value.getOffsetStart() > length || value.getOffsetLimit() > length) return false; Token token; token.type_ = tokenError; token.start_ = begin_ + value.getOffsetStart(); - token.end_ = end_ + value.getOffsetLimit(); + token.end_ = begin_ + value.getOffsetLimit(); ErrorInfo info; info.token_ = token; info.message_ = message; - info.extra_ = 0; + info.extra_ = nullptr; errors_.push_back(info); return true; } -bool Reader::pushError(const Value& value, const JSONCPP_STRING& message, const Value& extra) { +bool Reader::pushError(const Value& value, const String& message, + const Value& extra) { ptrdiff_t const length = end_ - begin_; - if(value.getOffsetStart() > length - || value.getOffsetLimit() > length - || extra.getOffsetLimit() > length) + if (value.getOffsetStart() > length || value.getOffsetLimit() > length || + extra.getOffsetLimit() > length) return false; Token token; token.type_ = tokenError; @@ -1103,15 +1087,15 @@ bool Reader::pushError(const Value& value, const JSONCPP_STRING& message, const return true; } -bool Reader::good() const { - return !errors_.size(); -} +bool Reader::good() const { return errors_.empty(); } -// exact copy of Features +// Originally copied from the Features class (now deprecated), used internally +// for features implementation. class OurFeatures { public: static OurFeatures all(); bool allowComments_; + bool allowTrailingCommas_; bool strictRoot_; bool allowDroppedNullPlaceholders_; bool allowNumericKeys_; @@ -1119,42 +1103,36 @@ public: bool failIfExtra_; bool rejectDupKeys_; bool allowSpecialFloats_; - int stackLimit_; -}; // OurFeatures + bool skipBom_; + size_t stackLimit_; +}; // OurFeatures -// exact copy of Implementation of class Features -// //////////////////////////////// - -OurFeatures OurFeatures::all() { return OurFeatures(); } +OurFeatures OurFeatures::all() { return {}; } // Implementation of class Reader // //////////////////////////////// -// exact copy of Reader, renamed to OurReader +// Originally copied from the Reader class (now deprecated), used internally +// for implementing JSON reading. class OurReader { public: - typedef char Char; - typedef const Char* Location; + using Char = char; + using Location = const Char*; struct StructuredError { ptrdiff_t offset_start; ptrdiff_t offset_limit; - JSONCPP_STRING message; + String message; }; - OurReader(OurFeatures const& features); - bool parse(const char* beginDoc, - const char* endDoc, - Value& root, + explicit OurReader(OurFeatures const& features); + bool parse(const char* beginDoc, const char* endDoc, Value& root, bool collectComments = true); - JSONCPP_STRING getFormattedErrorMessages() const; + String getFormattedErrorMessages() const; std::vector getStructuredErrors() const; - bool pushError(const Value& value, const JSONCPP_STRING& message); - bool pushError(const Value& value, const JSONCPP_STRING& message, const Value& extra); - bool good() const; private: - OurReader(OurReader const&); // no impl - void operator=(OurReader const&); // no impl + OurReader(OurReader const&); // no impl + void operator=(OurReader const&); // no impl enum TokenType { tokenEndOfStream = 0, @@ -1186,17 +1164,18 @@ private: class ErrorInfo { public: Token token_; - JSONCPP_STRING message_; + String message_; Location extra_; }; - typedef std::deque Errors; + using Errors = std::deque; bool readToken(Token& token); void skipSpaces(); - bool match(Location pattern, int patternLength); + void skipBom(bool skipBom); + bool match(const Char* pattern, int patternLength); bool readComment(); - bool readCStyleComment(); + bool readCStyleComment(bool* containsNewLineResult); bool readCppStyleComment(); bool readString(); bool readStringSingleQuote(); @@ -1207,68 +1186,57 @@ private: bool decodeNumber(Token& token); bool decodeNumber(Token& token, Value& decoded); bool decodeString(Token& token); - bool decodeString(Token& token, JSONCPP_STRING& decoded); + bool decodeString(Token& token, String& decoded); bool decodeDouble(Token& token); bool decodeDouble(Token& token, Value& decoded); - bool decodeUnicodeCodePoint(Token& token, - Location& current, - Location end, + bool decodeUnicodeCodePoint(Token& token, Location& current, Location end, unsigned int& unicode); - bool decodeUnicodeEscapeSequence(Token& token, - Location& current, - Location end, - unsigned int& unicode); - bool addError(const JSONCPP_STRING& message, Token& token, Location extra = 0); + bool decodeUnicodeEscapeSequence(Token& token, Location& current, + Location end, unsigned int& unicode); + bool addError(const String& message, Token& token, Location extra = nullptr); bool recoverFromError(TokenType skipUntilToken); - bool addErrorAndRecover(const JSONCPP_STRING& message, - Token& token, + bool addErrorAndRecover(const String& message, Token& token, TokenType skipUntilToken); void skipUntilSpace(); Value& currentValue(); Char getNextChar(); - void - getLocationLineAndColumn(Location location, int& line, int& column) const; - JSONCPP_STRING getLocationLineAndColumn(Location location) const; + void getLocationLineAndColumn(Location location, int& line, + int& column) const; + String getLocationLineAndColumn(Location location) const; void addComment(Location begin, Location end, CommentPlacement placement); void skipCommentTokens(Token& token); - static JSONCPP_STRING normalizeEOL(Location begin, Location end); + static String normalizeEOL(Location begin, Location end); static bool containsNewLine(Location begin, Location end); - typedef std::stack Nodes; - Nodes nodes_; - Errors errors_; - JSONCPP_STRING document_; - Location begin_; - Location end_; - Location current_; - Location lastValueEnd_; - Value* lastValue_; - JSONCPP_STRING commentsBefore_; + using Nodes = std::stack; + + Nodes nodes_{}; + Errors errors_{}; + String document_{}; + Location begin_ = nullptr; + Location end_ = nullptr; + Location current_ = nullptr; + Location lastValueEnd_ = nullptr; + Value* lastValue_ = nullptr; + bool lastValueHasAComment_ = false; + String commentsBefore_{}; OurFeatures const features_; - bool collectComments_; -}; // OurReader + bool collectComments_ = false; +}; // OurReader // complete copy of Read impl, for OurReader -bool OurReader::containsNewLine(OurReader::Location begin, OurReader::Location end) { - for (; begin < end; ++begin) - if (*begin == '\n' || *begin == '\r') - return true; - return false; +bool OurReader::containsNewLine(OurReader::Location begin, + OurReader::Location end) { + return std::any_of(begin, end, [](char b) { return b == '\n' || b == '\r'; }); } -OurReader::OurReader(OurFeatures const& features) - : errors_(), document_(), begin_(), end_(), current_(), lastValueEnd_(), - lastValue_(), commentsBefore_(), - features_(features), collectComments_() { -} +OurReader::OurReader(OurFeatures const& features) : features_(features) {} -bool OurReader::parse(const char* beginDoc, - const char* endDoc, - Value& root, - bool collectComments) { +bool OurReader::parse(const char* beginDoc, const char* endDoc, Value& root, + bool collectComments) { if (!features_.allowComments_) { collectComments = false; } @@ -1277,22 +1245,23 @@ bool OurReader::parse(const char* beginDoc, end_ = endDoc; collectComments_ = collectComments; current_ = begin_; - lastValueEnd_ = 0; - lastValue_ = 0; + lastValueEnd_ = nullptr; + lastValue_ = nullptr; commentsBefore_.clear(); errors_.clear(); while (!nodes_.empty()) nodes_.pop(); nodes_.push(&root); + // skip byte order mark if it exists at the beginning of the UTF-8 text. + skipBom(features_.skipBom_); bool successful = readValue(); + nodes_.pop(); Token token; skipCommentTokens(token); - if (features_.failIfExtra_) { - if ((features_.strictRoot_ || token.type_ != tokenError) && token.type_ != tokenEndOfStream) { - addError("Extra non-whitespace after JSON value.", token); - return false; - } + if (features_.failIfExtra_ && (token.type_ != tokenEndOfStream)) { + addError("Extra non-whitespace after JSON value.", token); + return false; } if (collectComments_ && !commentsBefore_.empty()) root.setComment(commentsBefore_, commentAfter); @@ -1314,7 +1283,8 @@ bool OurReader::parse(const char* beginDoc, bool OurReader::readValue() { // To preserve the old behaviour we cast size_t to int. - if (static_cast(nodes_.size()) > features_.stackLimit_) throwRuntimeError("Exceeded stackLimit in readValue()."); + if (nodes_.size() > features_.stackLimit_) + throwRuntimeError("Exceeded stackLimit in readValue()."); Token token; skipCommentTokens(token); bool successful = true; @@ -1339,54 +1309,42 @@ bool OurReader::readValue() { case tokenString: successful = decodeString(token); break; - case tokenTrue: - { + case tokenTrue: { Value v(true); currentValue().swapPayload(v); currentValue().setOffsetStart(token.start_ - begin_); currentValue().setOffsetLimit(token.end_ - begin_); - } - break; - case tokenFalse: - { + } break; + case tokenFalse: { Value v(false); currentValue().swapPayload(v); currentValue().setOffsetStart(token.start_ - begin_); currentValue().setOffsetLimit(token.end_ - begin_); - } - break; - case tokenNull: - { + } break; + case tokenNull: { Value v; currentValue().swapPayload(v); currentValue().setOffsetStart(token.start_ - begin_); currentValue().setOffsetLimit(token.end_ - begin_); - } - break; - case tokenNaN: - { + } break; + case tokenNaN: { Value v(std::numeric_limits::quiet_NaN()); currentValue().swapPayload(v); currentValue().setOffsetStart(token.start_ - begin_); currentValue().setOffsetLimit(token.end_ - begin_); - } - break; - case tokenPosInf: - { + } break; + case tokenPosInf: { Value v(std::numeric_limits::infinity()); currentValue().swapPayload(v); currentValue().setOffsetStart(token.start_ - begin_); currentValue().setOffsetLimit(token.end_ - begin_); - } - break; - case tokenNegInf: - { + } break; + case tokenNegInf: { Value v(-std::numeric_limits::infinity()); currentValue().swapPayload(v); currentValue().setOffsetStart(token.start_ - begin_); currentValue().setOffsetLimit(token.end_ - begin_); - } - break; + } break; case tokenArraySeparator: case tokenObjectEnd: case tokenArrayEnd: @@ -1408,6 +1366,7 @@ bool OurReader::readValue() { if (collectComments_) { lastValueEnd_ = current_; + lastValueHasAComment_ = false; lastValue_ = ¤tValue(); } @@ -1448,10 +1407,13 @@ bool OurReader::readToken(Token& token) { break; case '\'': if (features_.allowSingleQuotes_) { - token.type_ = tokenString; - ok = readStringSingleQuote(); + token.type_ = tokenString; + ok = readStringSingleQuote(); + } else { + // If we don't allow single quotes, this is a failure case. + ok = false; + } break; - } // else fall through case '/': token.type_ = tokenComment; ok = readComment(); @@ -1477,6 +1439,14 @@ bool OurReader::readToken(Token& token) { ok = features_.allowSpecialFloats_ && match("nfinity", 7); } break; + case '+': + if (readNumber(true)) { + token.type_ = tokenNumber; + } else { + token.type_ = tokenPosInf; + ok = features_.allowSpecialFloats_ && match("nfinity", 7); + } + break; case 't': token.type_ = tokenTrue; ok = match("rue", 3); @@ -1521,7 +1491,7 @@ bool OurReader::readToken(Token& token) { if (!ok) token.type_ = tokenError; token.end_ = current_; - return true; + return ok; } void OurReader::skipSpaces() { @@ -1534,7 +1504,17 @@ void OurReader::skipSpaces() { } } -bool OurReader::match(Location pattern, int patternLength) { +void OurReader::skipBom(bool skipBom) { + // The default behavior is to skip BOM. + if (skipBom) { + if ((end_ - begin_) >= 3 && strncmp(begin_, "\xEF\xBB\xBF", 3) == 0) { + begin_ += 3; + current_ = begin_; + } + } +} + +bool OurReader::match(const Char* pattern, int patternLength) { if (end_ - current_ < patternLength) return false; int index = patternLength; @@ -1546,21 +1526,32 @@ bool OurReader::match(Location pattern, int patternLength) { } bool OurReader::readComment() { - Location commentBegin = current_ - 1; - Char c = getNextChar(); + const Location commentBegin = current_ - 1; + const Char c = getNextChar(); bool successful = false; - if (c == '*') - successful = readCStyleComment(); - else if (c == '/') + bool cStyleWithEmbeddedNewline = false; + + const bool isCStyleComment = (c == '*'); + const bool isCppStyleComment = (c == '/'); + if (isCStyleComment) { + successful = readCStyleComment(&cStyleWithEmbeddedNewline); + } else if (isCppStyleComment) { successful = readCppStyleComment(); + } + if (!successful) return false; if (collectComments_) { CommentPlacement placement = commentBefore; - if (lastValueEnd_ && !containsNewLine(lastValueEnd_, commentBegin)) { - if (c != '*' || !containsNewLine(commentBegin, current_)) - placement = commentAfterOnSameLine; + + if (!lastValueHasAComment_) { + if (lastValueEnd_ && !containsNewLine(lastValueEnd_, commentBegin)) { + if (isCppStyleComment || !cStyleWithEmbeddedNewline) { + placement = commentAfterOnSameLine; + lastValueHasAComment_ = true; + } + } } addComment(commentBegin, current_, placement); @@ -1568,16 +1559,17 @@ bool OurReader::readComment() { return true; } -JSONCPP_STRING OurReader::normalizeEOL(OurReader::Location begin, OurReader::Location end) { - JSONCPP_STRING normalized; +String OurReader::normalizeEOL(OurReader::Location begin, + OurReader::Location end) { + String normalized; normalized.reserve(static_cast(end - begin)); OurReader::Location current = begin; while (current != end) { char c = *current++; if (c == '\r') { if (current != end && *current == '\n') - // convert dos EOL - ++current; + // convert dos EOL + ++current; // convert Mac EOL normalized += '\n'; } else { @@ -1587,24 +1579,29 @@ JSONCPP_STRING OurReader::normalizeEOL(OurReader::Location begin, OurReader::Loc return normalized; } -void -OurReader::addComment(Location begin, Location end, CommentPlacement placement) { +void OurReader::addComment(Location begin, Location end, + CommentPlacement placement) { assert(collectComments_); - const JSONCPP_STRING& normalized = normalizeEOL(begin, end); + const String& normalized = normalizeEOL(begin, end); if (placement == commentAfterOnSameLine) { - assert(lastValue_ != 0); + assert(lastValue_ != nullptr); lastValue_->setComment(normalized, placement); } else { commentsBefore_ += normalized; } } -bool OurReader::readCStyleComment() { +bool OurReader::readCStyleComment(bool* containsNewLineResult) { + *containsNewLineResult = false; + while ((current_ + 1) < end_) { Char c = getNextChar(); if (c == '*' && *current_ == '/') break; + if (c == '\n') + *containsNewLineResult = true; } + return getNextChar() == '/'; } @@ -1625,7 +1622,7 @@ bool OurReader::readCppStyleComment() { } bool OurReader::readNumber(bool checkInf) { - const char *p = current_; + Location p = current_; if (checkInf && p != end_ && *p == 'I') { current_ = ++p; return false; @@ -1662,7 +1659,6 @@ bool OurReader::readString() { return c == '"'; } - bool OurReader::readStringSingleQuote() { Char c = 0; while (current_ != end_) { @@ -1675,19 +1671,21 @@ bool OurReader::readStringSingleQuote() { return c == '\''; } -bool OurReader::readObject(Token& tokenStart) { +bool OurReader::readObject(Token& token) { Token tokenName; - JSONCPP_STRING name; + String name; Value init(objectValue); currentValue().swapPayload(init); - currentValue().setOffsetStart(tokenStart.start_ - begin_); + currentValue().setOffsetStart(token.start_ - begin_); while (readToken(tokenName)) { bool initialTokenOk = true; while (tokenName.type_ == tokenComment && initialTokenOk) initialTokenOk = readToken(tokenName); if (!initialTokenOk) break; - if (tokenName.type_ == tokenObjectEnd && name.empty()) // empty object + if (tokenName.type_ == tokenObjectEnd && + (name.empty() || + features_.allowTrailingCommas_)) // empty object or trailing comma return true; name.clear(); if (tokenName.type_ == tokenString) { @@ -1701,17 +1699,17 @@ bool OurReader::readObject(Token& tokenStart) { } else { break; } + if (name.length() >= (1U << 30)) + throwRuntimeError("keylength >= 2^30"); + if (features_.rejectDupKeys_ && currentValue().isMember(name)) { + String msg = "Duplicate key: '" + name + "'"; + return addErrorAndRecover(msg, tokenName, tokenObjectEnd); + } Token colon; if (!readToken(colon) || colon.type_ != tokenMemberSeparator) { - return addErrorAndRecover( - "Missing ':' after object member name", colon, tokenObjectEnd); - } - if (name.length() >= (1U<<30)) throwRuntimeError("keylength >= 2^30"); - if (features_.rejectDupKeys_ && currentValue().isMember(name)) { - JSONCPP_STRING msg = "Duplicate key: '" + name + "'"; - return addErrorAndRecover( - msg, tokenName, tokenObjectEnd); + return addErrorAndRecover("Missing ':' after object member name", colon, + tokenObjectEnd); } Value& value = currentValue()[name]; nodes_.push(&value); @@ -1724,8 +1722,8 @@ bool OurReader::readObject(Token& tokenStart) { if (!readToken(comma) || (comma.type_ != tokenObjectEnd && comma.type_ != tokenArraySeparator && comma.type_ != tokenComment)) { - return addErrorAndRecover( - "Missing ',' or '}' in object declaration", comma, tokenObjectEnd); + return addErrorAndRecover("Missing ',' or '}' in object declaration", + comma, tokenObjectEnd); } bool finalizeTokenOk = true; while (comma.type_ == tokenComment && finalizeTokenOk) @@ -1733,23 +1731,27 @@ bool OurReader::readObject(Token& tokenStart) { if (comma.type_ == tokenObjectEnd) return true; } - return addErrorAndRecover( - "Missing '}' or object member name", tokenName, tokenObjectEnd); + return addErrorAndRecover("Missing '}' or object member name", tokenName, + tokenObjectEnd); } -bool OurReader::readArray(Token& tokenStart) { +bool OurReader::readArray(Token& token) { Value init(arrayValue); currentValue().swapPayload(init); - currentValue().setOffsetStart(tokenStart.start_ - begin_); - skipSpaces(); - if (current_ != end_ && *current_ == ']') // empty array - { - Token endArray; - readToken(endArray); - return true; - } + currentValue().setOffsetStart(token.start_ - begin_); int index = 0; for (;;) { + skipSpaces(); + if (current_ != end_ && *current_ == ']' && + (index == 0 || + (features_.allowTrailingCommas_ && + !features_.allowDroppedNullPlaceholders_))) // empty array or trailing + // comma + { + Token endArray; + readToken(endArray); + return true; + } Value& value = currentValue()[index++]; nodes_.push(&value); bool ok = readValue(); @@ -1757,19 +1759,19 @@ bool OurReader::readArray(Token& tokenStart) { if (!ok) // error already set return recoverFromError(tokenArrayEnd); - Token token; + Token currentToken; // Accept Comment after last item in the array. - ok = readToken(token); - while (token.type_ == tokenComment && ok) { - ok = readToken(token); + ok = readToken(currentToken); + while (currentToken.type_ == tokenComment && ok) { + ok = readToken(currentToken); } - bool badTokenType = - (token.type_ != tokenArraySeparator && token.type_ != tokenArrayEnd); + bool badTokenType = (currentToken.type_ != tokenArraySeparator && + currentToken.type_ != tokenArrayEnd); if (!ok || badTokenType) { - return addErrorAndRecover( - "Missing ',' or ']' in array declaration", token, tokenArrayEnd); + return addErrorAndRecover("Missing ',' or ']' in array declaration", + currentToken, tokenArrayEnd); } - if (token.type_ == tokenArrayEnd) + if (currentToken.type_ == tokenArrayEnd) break; } return true; @@ -1790,38 +1792,78 @@ bool OurReader::decodeNumber(Token& token, Value& decoded) { // larger than the maximum supported value of an integer then // we decode the number as a double. Location current = token.start_; - bool isNegative = *current == '-'; - if (isNegative) + const bool isNegative = *current == '-'; + if (isNegative) { ++current; - // TODO: Help the compiler do the div and mod at compile time or get rid of them. - Value::LargestUInt maxIntegerValue = - isNegative ? Value::LargestUInt(-Value::minLargestInt) - : Value::maxLargestUInt; - Value::LargestUInt threshold = maxIntegerValue / 10; + } + + // We assume we can represent the largest and smallest integer types as + // unsigned integers with separate sign. This is only true if they can fit + // into an unsigned integer. + static_assert(Value::maxLargestInt <= Value::maxLargestUInt, + "Int must be smaller than UInt"); + + // We need to convert minLargestInt into a positive number. The easiest way + // to do this conversion is to assume our "threshold" value of minLargestInt + // divided by 10 can fit in maxLargestInt when absolute valued. This should + // be a safe assumption. + static_assert(Value::minLargestInt <= -Value::maxLargestInt, + "The absolute value of minLargestInt must be greater than or " + "equal to maxLargestInt"); + static_assert(Value::minLargestInt / 10 >= -Value::maxLargestInt, + "The absolute value of minLargestInt must be only 1 magnitude " + "larger than maxLargest Int"); + + static constexpr Value::LargestUInt positive_threshold = + Value::maxLargestUInt / 10; + static constexpr Value::UInt positive_last_digit = Value::maxLargestUInt % 10; + + // For the negative values, we have to be more careful. Since typically + // -Value::minLargestInt will cause an overflow, we first divide by 10 and + // then take the inverse. This assumes that minLargestInt is only a single + // power of 10 different in magnitude, which we check above. For the last + // digit, we take the modulus before negating for the same reason. + static constexpr auto negative_threshold = + Value::LargestUInt(-(Value::minLargestInt / 10)); + static constexpr auto negative_last_digit = + Value::UInt(-(Value::minLargestInt % 10)); + + const Value::LargestUInt threshold = + isNegative ? negative_threshold : positive_threshold; + const Value::UInt max_last_digit = + isNegative ? negative_last_digit : positive_last_digit; + Value::LargestUInt value = 0; while (current < token.end_) { Char c = *current++; if (c < '0' || c > '9') return decodeDouble(token, decoded); - Value::UInt digit(static_cast(c - '0')); + + const auto digit(static_cast(c - '0')); if (value >= threshold) { // We've hit or exceeded the max value divided by 10 (rounded down). If - // a) we've only just touched the limit, b) this is the last digit, and + // a) we've only just touched the limit, meaing value == threshold, + // b) this is the last digit, or // c) it's small enough to fit in that rounding delta, we're okay. // Otherwise treat this number as a double to avoid overflow. if (value > threshold || current != token.end_ || - digit > maxIntegerValue % 10) { + digit > max_last_digit) { return decodeDouble(token, decoded); } } value = value * 10 + digit; } - if (isNegative) - decoded = -Value::LargestInt(value); - else if (value <= Value::LargestUInt(Value::maxInt)) + + if (isNegative) { + // We use the same magnitude assumption here, just in case. + const auto last_digit = static_cast(value % 10); + decoded = -Value::LargestInt(value / 10) * 10 - last_digit; + } else if (value <= Value::LargestUInt(Value::maxLargestInt)) { decoded = Value::LargestInt(value); - else + } else { decoded = value; + } + return true; } @@ -1837,44 +1879,18 @@ bool OurReader::decodeDouble(Token& token) { bool OurReader::decodeDouble(Token& token, Value& decoded) { double value = 0; - const int bufferSize = 32; - int count; - ptrdiff_t const length = token.end_ - token.start_; - - // Sanity check to avoid buffer overflow exploits. - if (length < 0) { - return addError("Unable to parse token length", token); + const String buffer(token.start_, token.end_); + IStringStream is(buffer); + if (!(is >> value)) { + return addError( + "'" + String(token.start_, token.end_) + "' is not a number.", token); } - size_t const ulength = static_cast(length); - - // Avoid using a string constant for the format control string given to - // sscanf, as this can cause hard to debug crashes on OS X. See here for more - // info: - // - // http://developer.apple.com/library/mac/#DOCUMENTATION/DeveloperTools/gcc-4.0.1/gcc/Incompatibilities.html - char format[] = "%lf"; - - if (length <= bufferSize) { - Char buffer[bufferSize + 1]; - memcpy(buffer, token.start_, ulength); - buffer[length] = 0; - fixNumericLocaleInput(buffer, buffer + length); - count = sscanf(buffer, format, &value); - } else { - JSONCPP_STRING buffer(token.start_, token.end_); - count = sscanf(buffer.c_str(), format, &value); - } - - if (count != 1) - return addError("'" + JSONCPP_STRING(token.start_, token.end_) + - "' is not a number.", - token); decoded = value; return true; } bool OurReader::decodeString(Token& token) { - JSONCPP_STRING decoded_string; + String decoded_string; if (!decodeString(token, decoded_string)) return false; Value decoded(decoded_string); @@ -1884,7 +1900,7 @@ bool OurReader::decodeString(Token& token) { return true; } -bool OurReader::decodeString(Token& token, JSONCPP_STRING& decoded) { +bool OurReader::decodeString(Token& token, String& decoded) { decoded.reserve(static_cast(token.end_ - token.start_ - 2)); Location current = token.start_ + 1; // skip '"' Location end = token.end_ - 1; // do not include '"' @@ -1892,7 +1908,7 @@ bool OurReader::decodeString(Token& token, JSONCPP_STRING& decoded) { Char c = *current++; if (c == '"') break; - else if (c == '\\') { + if (c == '\\') { if (current == end) return addError("Empty escape sequence in string", token, current); Char escape = *current++; @@ -1937,10 +1953,8 @@ bool OurReader::decodeString(Token& token, JSONCPP_STRING& decoded) { return true; } -bool OurReader::decodeUnicodeCodePoint(Token& token, - Location& current, - Location end, - unsigned int& unicode) { +bool OurReader::decodeUnicodeCodePoint(Token& token, Location& current, + Location end, unsigned int& unicode) { if (!decodeUnicodeEscapeSequence(token, current, end, unicode)) return false; @@ -1949,10 +1963,9 @@ bool OurReader::decodeUnicodeCodePoint(Token& token, if (end - current < 6) return addError( "additional six characters expected to parse unicode surrogate pair.", - token, - current); - unsigned int surrogatePair; + token, current); if (*(current++) == '\\' && *(current++) == 'u') { + unsigned int surrogatePair; if (decodeUnicodeEscapeSequence(token, current, end, surrogatePair)) { unicode = 0x10000 + ((unicode & 0x3FF) << 10) + (surrogatePair & 0x3FF); } else @@ -1960,20 +1973,17 @@ bool OurReader::decodeUnicodeCodePoint(Token& token, } else return addError("expecting another \\u token to begin the second half of " "a unicode surrogate pair", - token, - current); + token, current); } return true; } -bool OurReader::decodeUnicodeEscapeSequence(Token& token, - Location& current, - Location end, - unsigned int& ret_unicode) { +bool OurReader::decodeUnicodeEscapeSequence(Token& token, Location& current, + Location end, + unsigned int& ret_unicode) { if (end - current < 4) return addError( - "Bad unicode escape sequence in string: four digits expected.", - token, + "Bad unicode escape sequence in string: four digits expected.", token, current); int unicode = 0; for (int index = 0; index < 4; ++index) { @@ -1988,15 +1998,13 @@ bool OurReader::decodeUnicodeEscapeSequence(Token& token, else return addError( "Bad unicode escape sequence in string: hexadecimal digit expected.", - token, - current); + token, current); } ret_unicode = static_cast(unicode); return true; } -bool -OurReader::addError(const JSONCPP_STRING& message, Token& token, Location extra) { +bool OurReader::addError(const String& message, Token& token, Location extra) { ErrorInfo info; info.token_ = token; info.message_ = message; @@ -2018,9 +2026,8 @@ bool OurReader::recoverFromError(TokenType skipUntilToken) { return false; } -bool OurReader::addErrorAndRecover(const JSONCPP_STRING& message, - Token& token, - TokenType skipUntilToken) { +bool OurReader::addErrorAndRecover(const String& message, Token& token, + TokenType skipUntilToken) { addError(message, token); return recoverFromError(skipUntilToken); } @@ -2033,9 +2040,8 @@ OurReader::Char OurReader::getNextChar() { return *current_++; } -void OurReader::getLocationLineAndColumn(Location location, - int& line, - int& column) const { +void OurReader::getLocationLineAndColumn(Location location, int& line, + int& column) const { Location current = begin_; Location lastLineStart = current; line = 0; @@ -2056,20 +2062,17 @@ void OurReader::getLocationLineAndColumn(Location location, ++line; } -JSONCPP_STRING OurReader::getLocationLineAndColumn(Location location) const { +String OurReader::getLocationLineAndColumn(Location location) const { int line, column; getLocationLineAndColumn(location, line, column); char buffer[18 + 16 + 16 + 1]; - snprintf(buffer, sizeof(buffer), "Line %d, Column %d", line, column); + jsoncpp_snprintf(buffer, sizeof(buffer), "Line %d, Column %d", line, column); return buffer; } -JSONCPP_STRING OurReader::getFormattedErrorMessages() const { - JSONCPP_STRING formattedMessage; - for (Errors::const_iterator itError = errors_.begin(); - itError != errors_.end(); - ++itError) { - const ErrorInfo& error = *itError; +String OurReader::getFormattedErrorMessages() const { + String formattedMessage; + for (const auto& error : errors_) { formattedMessage += "* " + getLocationLineAndColumn(error.token_.start_) + "\n"; formattedMessage += " " + error.message_ + "\n"; @@ -2082,10 +2085,7 @@ JSONCPP_STRING OurReader::getFormattedErrorMessages() const { std::vector OurReader::getStructuredErrors() const { std::vector allErrors; - for (Errors::const_iterator itError = errors_.begin(); - itError != errors_.end(); - ++itError) { - const ErrorInfo& error = *itError; + for (const auto& error : errors_) { OurReader::StructuredError structured; structured.offset_start = error.token_.start_ - begin_; structured.offset_limit = error.token_.end_ - begin_; @@ -2095,59 +2095,15 @@ std::vector OurReader::getStructuredErrors() const { return allErrors; } -bool OurReader::pushError(const Value& value, const JSONCPP_STRING& message) { - ptrdiff_t length = end_ - begin_; - if(value.getOffsetStart() > length - || value.getOffsetLimit() > length) - return false; - Token token; - token.type_ = tokenError; - token.start_ = begin_ + value.getOffsetStart(); - token.end_ = end_ + value.getOffsetLimit(); - ErrorInfo info; - info.token_ = token; - info.message_ = message; - info.extra_ = 0; - errors_.push_back(info); - return true; -} - -bool OurReader::pushError(const Value& value, const JSONCPP_STRING& message, const Value& extra) { - ptrdiff_t length = end_ - begin_; - if(value.getOffsetStart() > length - || value.getOffsetLimit() > length - || extra.getOffsetLimit() > length) - return false; - Token token; - token.type_ = tokenError; - token.start_ = begin_ + value.getOffsetStart(); - token.end_ = begin_ + value.getOffsetLimit(); - ErrorInfo info; - info.token_ = token; - info.message_ = message; - info.extra_ = begin_ + extra.getOffsetStart(); - errors_.push_back(info); - return true; -} - -bool OurReader::good() const { - return !errors_.size(); -} - - class OurCharReader : public CharReader { bool const collectComments_; OurReader reader_; + public: - OurCharReader( - bool collectComments, - OurFeatures const& features) - : collectComments_(collectComments) - , reader_(features) - {} - bool parse( - char const* beginDoc, char const* endDoc, - Value* root, JSONCPP_STRING* errs) JSONCPP_OVERRIDE { + OurCharReader(bool collectComments, OurFeatures const& features) + : collectComments_(collectComments), reader_(features) {} + bool parse(char const* beginDoc, char const* endDoc, Value* root, + String* errs) override { bool ok = reader_.parse(beginDoc, endDoc, *root, collectComments_); if (errs) { *errs = reader_.getFormattedErrorMessages(); @@ -2156,67 +2112,64 @@ public: } }; -CharReaderBuilder::CharReaderBuilder() -{ - setDefaults(&settings_); -} -CharReaderBuilder::~CharReaderBuilder() -{} -CharReader* CharReaderBuilder::newCharReader() const -{ +CharReaderBuilder::CharReaderBuilder() { setDefaults(&settings_); } +CharReaderBuilder::~CharReaderBuilder() = default; +CharReader* CharReaderBuilder::newCharReader() const { bool collectComments = settings_["collectComments"].asBool(); OurFeatures features = OurFeatures::all(); features.allowComments_ = settings_["allowComments"].asBool(); + features.allowTrailingCommas_ = settings_["allowTrailingCommas"].asBool(); features.strictRoot_ = settings_["strictRoot"].asBool(); - features.allowDroppedNullPlaceholders_ = settings_["allowDroppedNullPlaceholders"].asBool(); + features.allowDroppedNullPlaceholders_ = + settings_["allowDroppedNullPlaceholders"].asBool(); features.allowNumericKeys_ = settings_["allowNumericKeys"].asBool(); features.allowSingleQuotes_ = settings_["allowSingleQuotes"].asBool(); - features.stackLimit_ = settings_["stackLimit"].asInt(); + + // Stack limit is always a size_t, so we get this as an unsigned int + // regardless of it we have 64-bit integer support enabled. + features.stackLimit_ = static_cast(settings_["stackLimit"].asUInt()); features.failIfExtra_ = settings_["failIfExtra"].asBool(); features.rejectDupKeys_ = settings_["rejectDupKeys"].asBool(); features.allowSpecialFloats_ = settings_["allowSpecialFloats"].asBool(); + features.skipBom_ = settings_["skipBom"].asBool(); return new OurCharReader(collectComments, features); } -static void getValidReaderKeys(std::set* valid_keys) -{ - valid_keys->clear(); - valid_keys->insert("collectComments"); - valid_keys->insert("allowComments"); - valid_keys->insert("strictRoot"); - valid_keys->insert("allowDroppedNullPlaceholders"); - valid_keys->insert("allowNumericKeys"); - valid_keys->insert("allowSingleQuotes"); - valid_keys->insert("stackLimit"); - valid_keys->insert("failIfExtra"); - valid_keys->insert("rejectDupKeys"); - valid_keys->insert("allowSpecialFloats"); -} -bool CharReaderBuilder::validate(Json::Value* invalid) const -{ - Json::Value my_invalid; - if (!invalid) invalid = &my_invalid; // so we do not need to test for NULL - Json::Value& inv = *invalid; - std::set valid_keys; - getValidReaderKeys(&valid_keys); - Value::Members keys = settings_.getMemberNames(); - size_t n = keys.size(); - for (size_t i = 0; i < n; ++i) { - JSONCPP_STRING const& key = keys[i]; - if (valid_keys.find(key) == valid_keys.end()) { - inv[key] = settings_[key]; - } + +bool CharReaderBuilder::validate(Json::Value* invalid) const { + static const auto& valid_keys = *new std::set{ + "collectComments", + "allowComments", + "allowTrailingCommas", + "strictRoot", + "allowDroppedNullPlaceholders", + "allowNumericKeys", + "allowSingleQuotes", + "stackLimit", + "failIfExtra", + "rejectDupKeys", + "allowSpecialFloats", + "skipBom", + }; + for (auto si = settings_.begin(); si != settings_.end(); ++si) { + auto key = si.name(); + if (valid_keys.count(key)) + continue; + if (invalid) + (*invalid)[std::move(key)] = *si; + else + return false; } - return 0u == inv.size(); + return invalid ? invalid->empty() : true; } -Value& CharReaderBuilder::operator[](JSONCPP_STRING key) -{ + +Value& CharReaderBuilder::operator[](const String& key) { return settings_[key]; } // static -void CharReaderBuilder::strictMode(Json::Value* settings) -{ -//! [CharReaderBuilderStrictMode] +void CharReaderBuilder::strictMode(Json::Value* settings) { + //! [CharReaderBuilderStrictMode] (*settings)["allowComments"] = false; + (*settings)["allowTrailingCommas"] = false; (*settings)["strictRoot"] = true; (*settings)["allowDroppedNullPlaceholders"] = false; (*settings)["allowNumericKeys"] = false; @@ -2225,14 +2178,15 @@ void CharReaderBuilder::strictMode(Json::Value* settings) (*settings)["failIfExtra"] = true; (*settings)["rejectDupKeys"] = true; (*settings)["allowSpecialFloats"] = false; -//! [CharReaderBuilderStrictMode] + (*settings)["skipBom"] = true; + //! [CharReaderBuilderStrictMode] } // static -void CharReaderBuilder::setDefaults(Json::Value* settings) -{ -//! [CharReaderBuilderDefaults] +void CharReaderBuilder::setDefaults(Json::Value* settings) { + //! [CharReaderBuilderDefaults] (*settings)["collectComments"] = true; (*settings)["allowComments"] = true; + (*settings)["allowTrailingCommas"] = true; (*settings)["strictRoot"] = false; (*settings)["allowDroppedNullPlaceholders"] = false; (*settings)["allowNumericKeys"] = false; @@ -2241,19 +2195,18 @@ void CharReaderBuilder::setDefaults(Json::Value* settings) (*settings)["failIfExtra"] = false; (*settings)["rejectDupKeys"] = false; (*settings)["allowSpecialFloats"] = false; -//! [CharReaderBuilderDefaults] + (*settings)["skipBom"] = true; + //! [CharReaderBuilderDefaults] } ////////////////////////////////// // global functions -bool parseFromStream( - CharReader::Factory const& fact, JSONCPP_ISTREAM& sin, - Value* root, JSONCPP_STRING* errs) -{ - JSONCPP_OSTRINGSTREAM ssin; +bool parseFromStream(CharReader::Factory const& fact, IStream& sin, Value* root, + String* errs) { + OStringStream ssin; ssin << sin.rdbuf(); - JSONCPP_STRING doc = ssin.str(); + String doc = ssin.str(); char const* begin = doc.data(); char const* end = begin + doc.size(); // Note that we do not actually need a null-terminator. @@ -2261,9 +2214,9 @@ bool parseFromStream( return reader->parse(begin, end, root, errs); } -JSONCPP_ISTREAM& operator>>(JSONCPP_ISTREAM& sin, Value& root) { +IStream& operator>>(IStream& sin, Value& root) { CharReaderBuilder b; - JSONCPP_STRING errs; + String errs; bool ok = parseFromStream(b, sin, &root, &errs); if (!ok) { throwRuntimeError(errs); @@ -2303,31 +2256,21 @@ namespace Json { // ////////////////////////////////////////////////////////////////// // ////////////////////////////////////////////////////////////////// -ValueIteratorBase::ValueIteratorBase() - : current_(), isNull_(true) { -} +ValueIteratorBase::ValueIteratorBase() : current_() {} ValueIteratorBase::ValueIteratorBase( const Value::ObjectValues::iterator& current) : current_(current), isNull_(false) {} -Value& ValueIteratorBase::deref() const { - return current_->second; -} +Value& ValueIteratorBase::deref() { return current_->second; } +const Value& ValueIteratorBase::deref() const { return current_->second; } -void ValueIteratorBase::increment() { - ++current_; -} +void ValueIteratorBase::increment() { ++current_; } -void ValueIteratorBase::decrement() { - --current_; -} +void ValueIteratorBase::decrement() { --current_; } ValueIteratorBase::difference_type ValueIteratorBase::computeDistance(const SelfType& other) const { -#ifdef JSON_USE_CPPTL_SMALLMAP - return other.current_ - current_; -#else // Iterator for null value are initialized using the default // constructor, which initialize current_ to the default // std::map::iterator. As begin() and end() are two instance @@ -2348,7 +2291,6 @@ ValueIteratorBase::computeDistance(const SelfType& other) const { ++myDistance; } return myDistance; -#endif } bool ValueIteratorBase::isEqual(const SelfType& other) const { @@ -2380,12 +2322,13 @@ UInt ValueIteratorBase::index() const { return Value::UInt(-1); } -JSONCPP_STRING ValueIteratorBase::name() const { +String ValueIteratorBase::name() const { char const* keey; char const* end; keey = memberName(&end); - if (!keey) return JSONCPP_STRING(); - return JSONCPP_STRING(keey, end); + if (!keey) + return String(); + return String(keey, end); } char const* ValueIteratorBase::memberName() const { @@ -2396,8 +2339,8 @@ char const* ValueIteratorBase::memberName() const { char const* ValueIteratorBase::memberName(char const** end) const { const char* cname = (*current_).first.data(); if (!cname) { - *end = NULL; - return NULL; + *end = nullptr; + return nullptr; } *end = cname + (*current_).first.length(); return cname; @@ -2411,7 +2354,7 @@ char const* ValueIteratorBase::memberName(char const** end) const { // ////////////////////////////////////////////////////////////////// // ////////////////////////////////////////////////////////////////// -ValueConstIterator::ValueConstIterator() {} +ValueConstIterator::ValueConstIterator() = default; ValueConstIterator::ValueConstIterator( const Value::ObjectValues::iterator& current) @@ -2434,7 +2377,7 @@ operator=(const ValueIteratorBase& other) { // ////////////////////////////////////////////////////////////////// // ////////////////////////////////////////////////////////////////// -ValueIterator::ValueIterator() {} +ValueIterator::ValueIterator() = default; ValueIterator::ValueIterator(const Value::ObjectValues::iterator& current) : ValueIteratorBase(current) {} @@ -2444,8 +2387,7 @@ ValueIterator::ValueIterator(const ValueConstIterator& other) throwRuntimeError("ConstIterator to Iterator should never be allowed."); } -ValueIterator::ValueIterator(const ValueIterator& other) - : ValueIteratorBase(other) {} +ValueIterator::ValueIterator(const ValueIterator& other) = default; ValueIterator& ValueIterator::operator=(const SelfType& other) { copy(other); @@ -2477,20 +2419,54 @@ ValueIterator& ValueIterator::operator=(const SelfType& other) { #include #include #endif // if !defined(JSON_IS_AMALGAMATION) -#include +#include +#include +#include +#include +#include +#include #include #include -#include -#include -#ifdef JSON_USE_CPPTL -#include + +// Provide implementation equivalent of std::snprintf for older _MSC compilers +#if defined(_MSC_VER) && _MSC_VER < 1900 +#include +static int msvc_pre1900_c99_vsnprintf(char* outBuf, size_t size, + const char* format, va_list ap) { + int count = -1; + if (size != 0) + count = _vsnprintf_s(outBuf, size, _TRUNCATE, format, ap); + if (count == -1) + count = _vscprintf(format, ap); + return count; +} + +int JSON_API msvc_pre1900_c99_snprintf(char* outBuf, size_t size, + const char* format, ...) { + va_list ap; + va_start(ap, format); + const int count = msvc_pre1900_c99_vsnprintf(outBuf, size, format, ap); + va_end(ap); + return count; +} +#endif + +// Disable warning C4702 : unreachable code +#if defined(_MSC_VER) +#pragma warning(disable : 4702) #endif -#include // size_t -#include // min() #define JSON_ASSERT_UNREACHABLE assert(false) namespace Json { +template +static std::unique_ptr cloneUnique(const std::unique_ptr& p) { + std::unique_ptr r; + if (p) { + r = std::unique_ptr(new T(*p)); + } + return r; +} // This is a walkaround to avoid the static initialization of Value::null. // kNull must be word-aligned to avoid crashing on ARM. We use an alignment of @@ -2500,50 +2476,34 @@ namespace Json { #else #define ALIGNAS(byte_alignment) #endif -//static const unsigned char ALIGNAS(8) kNull[sizeof(Value)] = { 0 }; -//const unsigned char& kNullRef = kNull[0]; -//const Value& Value::null = reinterpret_cast(kNullRef); -//const Value& Value::nullRef = null; // static -Value const& Value::nullSingleton() -{ - static Value const nullStatic; - return nullStatic; +Value const& Value::nullSingleton() { + static Value const nullStatic; + return nullStatic; } -// for backwards compatibility, we'll leave these global references around, but DO NOT -// use them in JSONCPP library code any more! +#if JSON_USE_NULLREF +// for backwards compatibility, we'll leave these global references around, but +// DO NOT use them in JSONCPP library code any more! +// static Value const& Value::null = Value::nullSingleton(); -Value const& Value::nullRef = Value::nullSingleton(); -const Int Value::minInt = Int(~(UInt(-1) / 2)); -const Int Value::maxInt = Int(UInt(-1) / 2); -const UInt Value::maxUInt = UInt(-1); -#if defined(JSON_HAS_INT64) -const Int64 Value::minInt64 = Int64(~(UInt64(-1) / 2)); -const Int64 Value::maxInt64 = Int64(UInt64(-1) / 2); -const UInt64 Value::maxUInt64 = UInt64(-1); -// The constant is hard-coded because some compiler have trouble -// converting Value::maxUInt64 to a double correctly (AIX/xlC). -// Assumes that UInt64 is a 64 bits integer. -static const double maxUInt64AsDouble = 18446744073709551615.0; -#endif // defined(JSON_HAS_INT64) -const LargestInt Value::minLargestInt = LargestInt(~(LargestUInt(-1) / 2)); -const LargestInt Value::maxLargestInt = LargestInt(LargestUInt(-1) / 2); -const LargestUInt Value::maxLargestUInt = LargestUInt(-1); +// static +Value const& Value::nullRef = Value::nullSingleton(); +#endif #if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) template static inline bool InRange(double d, T min, U max) { // The casts can lose precision, but we are looking only for // an approximate range. Might fail on edge cases though. ~cdunn - //return d >= static_cast(min) && d <= static_cast(max); - return d >= min && d <= max; + return d >= static_cast(min) && d <= static_cast(max); } #else // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) static inline double integerToDouble(Json::UInt64 value) { - return static_cast(Int64(value / 2)) * 2.0 + static_cast(Int64(value & 1)); + return static_cast(Int64(value / 2)) * 2.0 + + static_cast(Int64(value & 1)); } template static inline double integerToDouble(T value) { @@ -2563,19 +2523,16 @@ static inline bool InRange(double d, T min, U max) { * computed using strlen(value). * @return Pointer on the duplicate instance of string. */ -static inline char* duplicateStringValue(const char* value, - size_t length) -{ +static inline char* duplicateStringValue(const char* value, size_t length) { // Avoid an integer overflow in the call to malloc below by limiting length // to a sane value. if (length >= static_cast(Value::maxInt)) length = Value::maxInt - 1; - char* newString = static_cast(malloc(length + 1)); - if (newString == NULL) { - throwRuntimeError( - "in Json::Value::duplicateStringValue(): " - "Failed to allocate string value buffer"); + auto newString = static_cast(malloc(length + 1)); + if (newString == nullptr) { + throwRuntimeError("in Json::Value::duplicateStringValue(): " + "Failed to allocate string value buffer"); } memcpy(newString, value, length); newString[length] = 0; @@ -2584,31 +2541,28 @@ static inline char* duplicateStringValue(const char* value, /* Record the length as a prefix. */ -static inline char* duplicateAndPrefixStringValue( - const char* value, - unsigned int length) -{ +static inline char* duplicateAndPrefixStringValue(const char* value, + unsigned int length) { // Avoid an integer overflow in the call to malloc below by limiting length // to a sane value. - JSON_ASSERT_MESSAGE(length <= static_cast(Value::maxInt) - sizeof(unsigned) - 1U, + JSON_ASSERT_MESSAGE(length <= static_cast(Value::maxInt) - + sizeof(unsigned) - 1U, "in Json::Value::duplicateAndPrefixStringValue(): " "length too big for prefixing"); - unsigned actualLength = length + static_cast(sizeof(unsigned)) + 1U; - char* newString = static_cast(malloc(actualLength)); - if (newString == 0) { - throwRuntimeError( - "in Json::Value::duplicateAndPrefixStringValue(): " - "Failed to allocate string value buffer"); + size_t actualLength = sizeof(length) + length + 1; + auto newString = static_cast(malloc(actualLength)); + if (newString == nullptr) { + throwRuntimeError("in Json::Value::duplicateAndPrefixStringValue(): " + "Failed to allocate string value buffer"); } *reinterpret_cast(newString) = length; memcpy(newString + sizeof(unsigned), value, length); - newString[actualLength - 1U] = 0; // to avoid buffer over-run accidents by users later + newString[actualLength - 1U] = + 0; // to avoid buffer over-run accidents by users later return newString; } -inline static void decodePrefixedString( - bool isPrefixed, char const* prefixed, - unsigned* length, char const** value) -{ +inline static void decodePrefixedString(bool isPrefixed, char const* prefixed, + unsigned* length, char const** value) { if (!isPrefixed) { *length = static_cast(strlen(prefixed)); *value = prefixed; @@ -2617,7 +2571,8 @@ inline static void decodePrefixedString( *value = prefixed + sizeof(unsigned); } } -/** Free the string duplicated by duplicateStringValue()/duplicateAndPrefixStringValue(). +/** Free the string duplicated by + * duplicateStringValue()/duplicateAndPrefixStringValue(). */ #if JSONCPP_USING_SECURE_MEMORY static inline void releasePrefixedStringValue(char* value) { @@ -2630,17 +2585,13 @@ static inline void releasePrefixedStringValue(char* value) { } static inline void releaseStringValue(char* value, unsigned length) { // length==0 => we allocated the strings memory - size_t size = (length==0) ? strlen(value) : length; + size_t size = (length == 0) ? strlen(value) : length; memset(value, 0, size); free(value); } -#else // !JSONCPP_USING_SECURE_MEMORY -static inline void releasePrefixedStringValue(char* value) { - free(value); -} -static inline void releaseStringValue(char* value, unsigned) { - free(value); -} +#else // !JSONCPP_USING_SECURE_MEMORY +static inline void releasePrefixedStringValue(char* value) { free(value); } +static inline void releaseStringValue(char* value, unsigned) { free(value); } #endif // JSONCPP_USING_SECURE_MEMORY } // namespace Json @@ -2659,58 +2610,28 @@ static inline void releaseStringValue(char* value, unsigned) { namespace Json { -Exception::Exception(JSONCPP_STRING const& msg) - : msg_(msg) -{} -Exception::~Exception() JSONCPP_NOEXCEPT -{} -char const* Exception::what() const JSONCPP_NOEXCEPT -{ - return msg_.c_str(); -} -RuntimeError::RuntimeError(JSONCPP_STRING const& msg) - : Exception(msg) -{} -LogicError::LogicError(JSONCPP_STRING const& msg) - : Exception(msg) -{} -JSONCPP_NORETURN void throwRuntimeError(JSONCPP_STRING const& msg) -{ +#if JSON_USE_EXCEPTION +Exception::Exception(String msg) : msg_(std::move(msg)) {} +Exception::~Exception() noexcept = default; +char const* Exception::what() const noexcept { return msg_.c_str(); } +RuntimeError::RuntimeError(String const& msg) : Exception(msg) {} +LogicError::LogicError(String const& msg) : Exception(msg) {} +JSONCPP_NORETURN void throwRuntimeError(String const& msg) { throw RuntimeError(msg); } -JSONCPP_NORETURN void throwLogicError(JSONCPP_STRING const& msg) -{ +JSONCPP_NORETURN void throwLogicError(String const& msg) { throw LogicError(msg); } - -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// class Value::CommentInfo -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// - -Value::CommentInfo::CommentInfo() : comment_(0) -{} - -Value::CommentInfo::~CommentInfo() { - if (comment_) - releaseStringValue(comment_, 0u); +#else // !JSON_USE_EXCEPTION +JSONCPP_NORETURN void throwRuntimeError(String const& msg) { + std::cerr << msg << std::endl; + abort(); } - -void Value::CommentInfo::setComment(const char* text, size_t len) { - if (comment_) { - releaseStringValue(comment_, 0u); - comment_ = 0; - } - JSON_ASSERT(text != 0); - JSON_ASSERT_MESSAGE( - text[0] == '\0' || text[0] == '/', - "in Json::Value::setComment(): Comments must start with /"); - // It seems that /**/ style comments are acceptable as well. - comment_ = duplicateStringValue(text, len); +JSONCPP_NORETURN void throwLogicError(String const& msg) { + std::cerr << msg << std::endl; + abort(); } +#endif // ////////////////////////////////////////////////////////////////// // ////////////////////////////////////////////////////////////////// @@ -2723,36 +2644,44 @@ void Value::CommentInfo::setComment(const char* text, size_t len) { // Notes: policy_ indicates if the string was allocated when // a string is stored. -Value::CZString::CZString(ArrayIndex aindex) : cstr_(0), index_(aindex) {} +Value::CZString::CZString(ArrayIndex index) : cstr_(nullptr), index_(index) {} -Value::CZString::CZString(char const* str, unsigned ulength, DuplicationPolicy allocate) +Value::CZString::CZString(char const* str, unsigned length, + DuplicationPolicy allocate) : cstr_(str) { // allocate != duplicate storage_.policy_ = allocate & 0x3; - storage_.length_ = ulength & 0x3FFFFFFF; + storage_.length_ = length & 0x3FFFFFFF; } Value::CZString::CZString(const CZString& other) { - cstr_ = (other.storage_.policy_ != noDuplication && other.cstr_ != 0 - ? duplicateStringValue(other.cstr_, other.storage_.length_) - : other.cstr_); - storage_.policy_ = static_cast(other.cstr_ - ? (static_cast(other.storage_.policy_) == noDuplication - ? noDuplication : duplicate) - : static_cast(other.storage_.policy_)) & 3U; + cstr_ = (other.storage_.policy_ != noDuplication && other.cstr_ != nullptr + ? duplicateStringValue(other.cstr_, other.storage_.length_) + : other.cstr_); + storage_.policy_ = + static_cast( + other.cstr_ + ? (static_cast(other.storage_.policy_) == + noDuplication + ? noDuplication + : duplicate) + : static_cast(other.storage_.policy_)) & + 3U; storage_.length_ = other.storage_.length_; } -#if JSON_HAS_RVALUE_REFERENCES Value::CZString::CZString(CZString&& other) - : cstr_(other.cstr_), index_(other.index_) { + : cstr_(other.cstr_), index_(other.index_) { other.cstr_ = nullptr; } -#endif Value::CZString::~CZString() { if (cstr_ && storage_.policy_ == duplicate) { - releaseStringValue(const_cast(cstr_), storage_.length_ + 1u); //+1 for null terminating character for sake of completeness but not actually necessary + releaseStringValue(const_cast(cstr_), + storage_.length_ + 1U); // +1 for null terminating + // character for sake of + // completeness but not actually + // necessary } } @@ -2767,36 +2696,39 @@ Value::CZString& Value::CZString::operator=(const CZString& other) { return *this; } -#if JSON_HAS_RVALUE_REFERENCES Value::CZString& Value::CZString::operator=(CZString&& other) { cstr_ = other.cstr_; index_ = other.index_; other.cstr_ = nullptr; return *this; } -#endif bool Value::CZString::operator<(const CZString& other) const { - if (!cstr_) return index_ < other.index_; - //return strcmp(cstr_, other.cstr_) < 0; + if (!cstr_) + return index_ < other.index_; + // return strcmp(cstr_, other.cstr_) < 0; // Assume both are strings. unsigned this_len = this->storage_.length_; unsigned other_len = other.storage_.length_; unsigned min_len = std::min(this_len, other_len); JSON_ASSERT(this->cstr_ && other.cstr_); int comp = memcmp(this->cstr_, other.cstr_, min_len); - if (comp < 0) return true; - if (comp > 0) return false; + if (comp < 0) + return true; + if (comp > 0) + return false; return (this_len < other_len); } bool Value::CZString::operator==(const CZString& other) const { - if (!cstr_) return index_ == other.index_; - //return strcmp(cstr_, other.cstr_) == 0; + if (!cstr_) + return index_ == other.index_; + // return strcmp(cstr_, other.cstr_) == 0; // Assume both are strings. unsigned this_len = this->storage_.length_; unsigned other_len = other.storage_.length_; - if (this_len != other_len) return false; + if (this_len != other_len) + return false; JSON_ASSERT(this->cstr_ && other.cstr_); int comp = memcmp(this->cstr_, other.cstr_, this_len); return comp == 0; @@ -2804,10 +2736,12 @@ bool Value::CZString::operator==(const CZString& other) const { ArrayIndex Value::CZString::index() const { return index_; } -//const char* Value::CZString::c_str() const { return cstr_; } +// const char* Value::CZString::c_str() const { return cstr_; } const char* Value::CZString::data() const { return cstr_; } unsigned Value::CZString::length() const { return storage_.length_; } -bool Value::CZString::isStaticString() const { return storage_.policy_ == noDuplication; } +bool Value::CZString::isStaticString() const { + return storage_.policy_ == noDuplication; +} // ////////////////////////////////////////////////////////////////// // ////////////////////////////////////////////////////////////////// @@ -2821,10 +2755,10 @@ bool Value::CZString::isStaticString() const { return storage_.policy_ == noDupl * memset( this, 0, sizeof(Value) ) * This optimization is used in ValueInternalMap fast allocator. */ -Value::Value(ValueType vtype) { +Value::Value(ValueType type) { static char const emptyString[] = ""; - initBasic(vtype); - switch (vtype) { + initBasic(type); + switch (type) { case nullValue: break; case intValue: @@ -2877,20 +2811,22 @@ Value::Value(double value) { Value::Value(const char* value) { initBasic(stringValue, true); - JSON_ASSERT_MESSAGE(value != NULL, "Null Value Passed to Value Constructor"); - value_.string_ = duplicateAndPrefixStringValue(value, static_cast(strlen(value))); + JSON_ASSERT_MESSAGE(value != nullptr, + "Null Value Passed to Value Constructor"); + value_.string_ = duplicateAndPrefixStringValue( + value, static_cast(strlen(value))); } -Value::Value(const char* beginValue, const char* endValue) { +Value::Value(const char* begin, const char* end) { initBasic(stringValue, true); value_.string_ = - duplicateAndPrefixStringValue(beginValue, static_cast(endValue - beginValue)); + duplicateAndPrefixStringValue(begin, static_cast(end - begin)); } -Value::Value(const JSONCPP_STRING& value) { +Value::Value(const String& value) { initBasic(stringValue, true); - value_.string_ = - duplicateAndPrefixStringValue(value.data(), static_cast(value.length())); + value_.string_ = duplicateAndPrefixStringValue( + value.data(), static_cast(value.length())); } Value::Value(const StaticString& value) { @@ -2898,114 +2834,44 @@ Value::Value(const StaticString& value) { value_.string_ = const_cast(value.c_str()); } -#ifdef JSON_USE_CPPTL -Value::Value(const CppTL::ConstString& value) { - initBasic(stringValue, true); - value_.string_ = duplicateAndPrefixStringValue(value, static_cast(value.length())); -} -#endif - Value::Value(bool value) { initBasic(booleanValue); value_.bool_ = value; } -Value::Value(Value const& other) - : type_(other.type_), allocated_(false) - , - comments_(0), start_(other.start_), limit_(other.limit_) -{ - switch (type_) { - case nullValue: - case intValue: - case uintValue: - case realValue: - case booleanValue: - value_ = other.value_; - break; - case stringValue: - if (other.value_.string_ && other.allocated_) { - unsigned len; - char const* str; - decodePrefixedString(other.allocated_, other.value_.string_, - &len, &str); - value_.string_ = duplicateAndPrefixStringValue(str, len); - allocated_ = true; - } else { - value_.string_ = other.value_.string_; - allocated_ = false; - } - break; - case arrayValue: - case objectValue: - value_.map_ = new ObjectValues(*other.value_.map_); - break; - default: - JSON_ASSERT_UNREACHABLE; - } - if (other.comments_) { - comments_ = new CommentInfo[numberOfCommentPlacement]; - for (int comment = 0; comment < numberOfCommentPlacement; ++comment) { - const CommentInfo& otherComment = other.comments_[comment]; - if (otherComment.comment_) - comments_[comment].setComment( - otherComment.comment_, strlen(otherComment.comment_)); - } - } +Value::Value(const Value& other) { + dupPayload(other); + dupMeta(other); } -#if JSON_HAS_RVALUE_REFERENCES -// Move constructor Value::Value(Value&& other) { initBasic(nullValue); swap(other); } -#endif Value::~Value() { - switch (type_) { - case nullValue: - case intValue: - case uintValue: - case realValue: - case booleanValue: - break; - case stringValue: - if (allocated_) - releasePrefixedStringValue(value_.string_); - break; - case arrayValue: - case objectValue: - delete value_.map_; - break; - default: - JSON_ASSERT_UNREACHABLE; - } - - delete[] comments_; - + releasePayload(); value_.uint_ = 0; } -Value& Value::operator=(Value other) { - swap(other); +Value& Value::operator=(const Value& other) { + Value(other).swap(*this); + return *this; +} + +Value& Value::operator=(Value&& other) { + other.swap(*this); return *this; } void Value::swapPayload(Value& other) { - ValueType temp = type_; - type_ = other.type_; - other.type_ = temp; + std::swap(bits_, other.bits_); std::swap(value_, other.value_); - int temp2 = allocated_; - allocated_ = other.allocated_; - other.allocated_ = temp2 & 0x1; } void Value::copyPayload(const Value& other) { - type_ = other.type_; - value_ = other.value_; - allocated_ = other.allocated_; + releasePayload(); + dupPayload(other); } void Value::swap(Value& other) { @@ -3017,12 +2883,12 @@ void Value::swap(Value& other) { void Value::copy(const Value& other) { copyPayload(other); - comments_ = other.comments_; - start_ = other.start_; - limit_ = other.limit_; + dupMeta(other); } -ValueType Value::type() const { return type_; } +ValueType Value::type() const { + return static_cast(bits_.value_type_); +} int Value::compare(const Value& other) const { if (*this < other) @@ -3033,10 +2899,10 @@ int Value::compare(const Value& other) const { } bool Value::operator<(const Value& other) const { - int typeDelta = type_ - other.type_; + int typeDelta = type() - other.type(); if (typeDelta) - return typeDelta < 0 ? true : false; - switch (type_) { + return typeDelta < 0; + switch (type()) { case nullValue: return false; case intValue: @@ -3047,30 +2913,33 @@ bool Value::operator<(const Value& other) const { return value_.real_ < other.value_.real_; case booleanValue: return value_.bool_ < other.value_.bool_; - case stringValue: - { - if ((value_.string_ == 0) || (other.value_.string_ == 0)) { - if (other.value_.string_) return true; - else return false; + case stringValue: { + if ((value_.string_ == nullptr) || (other.value_.string_ == nullptr)) { + return other.value_.string_ != nullptr; } unsigned this_len; unsigned other_len; char const* this_str; char const* other_str; - decodePrefixedString(this->allocated_, this->value_.string_, &this_len, &this_str); - decodePrefixedString(other.allocated_, other.value_.string_, &other_len, &other_str); + decodePrefixedString(this->isAllocated(), this->value_.string_, &this_len, + &this_str); + decodePrefixedString(other.isAllocated(), other.value_.string_, &other_len, + &other_str); unsigned min_len = std::min(this_len, other_len); JSON_ASSERT(this_str && other_str); int comp = memcmp(this_str, other_str, min_len); - if (comp < 0) return true; - if (comp > 0) return false; + if (comp < 0) + return true; + if (comp > 0) + return false; return (this_len < other_len); } case arrayValue: case objectValue: { - int delta = int(value_.map_->size() - other.value_.map_->size()); - if (delta) - return delta < 0; + auto thisSize = value_.map_->size(); + auto otherSize = other.value_.map_->size(); + if (thisSize != otherSize) + return thisSize < otherSize; return (*value_.map_) < (*other.value_.map_); } default: @@ -3086,14 +2955,9 @@ bool Value::operator>=(const Value& other) const { return !(*this < other); } bool Value::operator>(const Value& other) const { return other < *this; } bool Value::operator==(const Value& other) const { - // if ( type_ != other.type_ ) - // GCC 2.95.3 says: - // attempt to take address of bit-field structure member `Json::Value::type_' - // Beats me, but a temp solves the problem. - int temp = other.type_; - if (type_ != temp) + if (type() != other.type()) return false; - switch (type_) { + switch (type()) { case nullValue: return true; case intValue: @@ -3104,18 +2968,20 @@ bool Value::operator==(const Value& other) const { return value_.real_ == other.value_.real_; case booleanValue: return value_.bool_ == other.value_.bool_; - case stringValue: - { - if ((value_.string_ == 0) || (other.value_.string_ == 0)) { + case stringValue: { + if ((value_.string_ == nullptr) || (other.value_.string_ == nullptr)) { return (value_.string_ == other.value_.string_); } unsigned this_len; unsigned other_len; char const* this_str; char const* other_str; - decodePrefixedString(this->allocated_, this->value_.string_, &this_len, &this_str); - decodePrefixedString(other.allocated_, other.value_.string_, &other_len, &other_str); - if (this_len != other_len) return false; + decodePrefixedString(this->isAllocated(), this->value_.string_, &this_len, + &this_str); + decodePrefixedString(other.isAllocated(), other.value_.string_, &other_len, + &other_str); + if (this_len != other_len) + return false; JSON_ASSERT(this_str && other_str); int comp = memcmp(this_str, other_str, this_len); return comp == 0; @@ -3133,47 +2999,55 @@ bool Value::operator==(const Value& other) const { bool Value::operator!=(const Value& other) const { return !(*this == other); } const char* Value::asCString() const { - JSON_ASSERT_MESSAGE(type_ == stringValue, + JSON_ASSERT_MESSAGE(type() == stringValue, "in Json::Value::asCString(): requires stringValue"); - if (value_.string_ == 0) return 0; + if (value_.string_ == nullptr) + return nullptr; unsigned this_len; char const* this_str; - decodePrefixedString(this->allocated_, this->value_.string_, &this_len, &this_str); + decodePrefixedString(this->isAllocated(), this->value_.string_, &this_len, + &this_str); return this_str; } #if JSONCPP_USING_SECURE_MEMORY unsigned Value::getCStringLength() const { - JSON_ASSERT_MESSAGE(type_ == stringValue, - "in Json::Value::asCString(): requires stringValue"); - if (value_.string_ == 0) return 0; + JSON_ASSERT_MESSAGE(type() == stringValue, + "in Json::Value::asCString(): requires stringValue"); + if (value_.string_ == 0) + return 0; unsigned this_len; char const* this_str; - decodePrefixedString(this->allocated_, this->value_.string_, &this_len, &this_str); + decodePrefixedString(this->isAllocated(), this->value_.string_, &this_len, + &this_str); return this_len; } #endif -bool Value::getString(char const** str, char const** cend) const { - if (type_ != stringValue) return false; - if (value_.string_ == 0) return false; +bool Value::getString(char const** begin, char const** end) const { + if (type() != stringValue) + return false; + if (value_.string_ == nullptr) + return false; unsigned length; - decodePrefixedString(this->allocated_, this->value_.string_, &length, str); - *cend = *str + length; + decodePrefixedString(this->isAllocated(), this->value_.string_, &length, + begin); + *end = *begin + length; return true; } -JSONCPP_STRING Value::asString() const { - switch (type_) { +String Value::asString() const { + switch (type()) { case nullValue: return ""; - case stringValue: - { - if (value_.string_ == 0) return ""; + case stringValue: { + if (value_.string_ == nullptr) + return ""; unsigned this_len; char const* this_str; - decodePrefixedString(this->allocated_, this->value_.string_, &this_len, &this_str); - return JSONCPP_STRING(this_str, this_len); + decodePrefixedString(this->isAllocated(), this->value_.string_, &this_len, + &this_str); + return String(this_str, this_len); } case booleanValue: return value_.bool_ ? "true" : "false"; @@ -3188,18 +3062,8 @@ JSONCPP_STRING Value::asString() const { } } -#ifdef JSON_USE_CPPTL -CppTL::ConstString Value::asConstString() const { - unsigned len; - char const* str; - decodePrefixedString(allocated_, value_.string_, - &len, &str); - return CppTL::ConstString(str, len); -} -#endif - Value::Int Value::asInt() const { - switch (type_) { + switch (type()) { case intValue: JSON_ASSERT_MESSAGE(isInt(), "LargestInt out of Int range"); return Int(value_.int_); @@ -3221,7 +3085,7 @@ Value::Int Value::asInt() const { } Value::UInt Value::asUInt() const { - switch (type_) { + switch (type()) { case intValue: JSON_ASSERT_MESSAGE(isUInt(), "LargestInt out of UInt range"); return UInt(value_.int_); @@ -3245,7 +3109,7 @@ Value::UInt Value::asUInt() const { #if defined(JSON_HAS_INT64) Value::Int64 Value::asInt64() const { - switch (type_) { + switch (type()) { case intValue: return Int64(value_.int_); case uintValue: @@ -3266,7 +3130,7 @@ Value::Int64 Value::asInt64() const { } Value::UInt64 Value::asUInt64() const { - switch (type_) { + switch (type()) { case intValue: JSON_ASSERT_MESSAGE(isUInt64(), "LargestInt out of UInt64 range"); return UInt64(value_.int_); @@ -3304,7 +3168,7 @@ LargestUInt Value::asLargestUInt() const { } double Value::asDouble() const { - switch (type_) { + switch (type()) { case intValue: return static_cast(value_.int_); case uintValue: @@ -3326,7 +3190,7 @@ double Value::asDouble() const { } float Value::asFloat() const { - switch (type_) { + switch (type()) { case intValue: return static_cast(value_.int_); case uintValue: @@ -3341,7 +3205,7 @@ float Value::asFloat() const { case nullValue: return 0.0; case booleanValue: - return value_.bool_ ? 1.0f : 0.0f; + return value_.bool_ ? 1.0F : 0.0F; default: break; } @@ -3349,18 +3213,20 @@ float Value::asFloat() const { } bool Value::asBool() const { - switch (type_) { + switch (type()) { case booleanValue: return value_.bool_; case nullValue: return false; case intValue: - return value_.int_ ? true : false; + return value_.int_ != 0; case uintValue: - return value_.uint_ ? true : false; - case realValue: - // This is kind of strange. Not recommended. - return (value_.real_ != 0.0) ? true : false; + return value_.uint_ != 0; + case realValue: { + // According to JavaScript language zero or NaN is regarded as false + const auto value_classification = std::fpclassify(value_.real_); + return value_classification != FP_ZERO && value_classification != FP_NAN; + } default: break; } @@ -3371,30 +3237,30 @@ bool Value::isConvertibleTo(ValueType other) const { switch (other) { case nullValue: return (isNumeric() && asDouble() == 0.0) || - (type_ == booleanValue && value_.bool_ == false) || - (type_ == stringValue && asString().empty()) || - (type_ == arrayValue && value_.map_->size() == 0) || - (type_ == objectValue && value_.map_->size() == 0) || - type_ == nullValue; + (type() == booleanValue && !value_.bool_) || + (type() == stringValue && asString().empty()) || + (type() == arrayValue && value_.map_->empty()) || + (type() == objectValue && value_.map_->empty()) || + type() == nullValue; case intValue: return isInt() || - (type_ == realValue && InRange(value_.real_, minInt, maxInt)) || - type_ == booleanValue || type_ == nullValue; + (type() == realValue && InRange(value_.real_, minInt, maxInt)) || + type() == booleanValue || type() == nullValue; case uintValue: return isUInt() || - (type_ == realValue && InRange(value_.real_, 0, maxUInt)) || - type_ == booleanValue || type_ == nullValue; + (type() == realValue && InRange(value_.real_, 0, maxUInt)) || + type() == booleanValue || type() == nullValue; case realValue: - return isNumeric() || type_ == booleanValue || type_ == nullValue; + return isNumeric() || type() == booleanValue || type() == nullValue; case booleanValue: - return isNumeric() || type_ == booleanValue || type_ == nullValue; + return isNumeric() || type() == booleanValue || type() == nullValue; case stringValue: - return isNumeric() || type_ == booleanValue || type_ == stringValue || - type_ == nullValue; + return isNumeric() || type() == booleanValue || type() == stringValue || + type() == nullValue; case arrayValue: - return type_ == arrayValue || type_ == nullValue; + return type() == arrayValue || type() == nullValue; case objectValue: - return type_ == objectValue || type_ == nullValue; + return type() == objectValue || type() == nullValue; } JSON_ASSERT_UNREACHABLE; return false; @@ -3402,7 +3268,7 @@ bool Value::isConvertibleTo(ValueType other) const { /// Number of values in array or object ArrayIndex Value::size() const { - switch (type_) { + switch (type()) { case nullValue: case intValue: case uintValue: @@ -3426,20 +3292,19 @@ ArrayIndex Value::size() const { bool Value::empty() const { if (isNull() || isArray() || isObject()) - return size() == 0u; - else - return false; + return size() == 0U; + return false; } -Value::operator bool() const { return ! isNull(); } +Value::operator bool() const { return !isNull(); } void Value::clear() { - JSON_ASSERT_MESSAGE(type_ == nullValue || type_ == arrayValue || - type_ == objectValue, + JSON_ASSERT_MESSAGE(type() == nullValue || type() == arrayValue || + type() == objectValue, "in Json::Value::clear(): requires complex value"); start_ = 0; limit_ = 0; - switch (type_) { + switch (type()) { case arrayValue: case objectValue: value_.map_->clear(); @@ -3450,15 +3315,15 @@ void Value::clear() { } void Value::resize(ArrayIndex newSize) { - JSON_ASSERT_MESSAGE(type_ == nullValue || type_ == arrayValue, + JSON_ASSERT_MESSAGE(type() == nullValue || type() == arrayValue, "in Json::Value::resize(): requires arrayValue"); - if (type_ == nullValue) + if (type() == nullValue) *this = Value(arrayValue); ArrayIndex oldSize = size(); if (newSize == 0) clear(); else if (newSize > oldSize) - (*this)[newSize - 1]; + this->operator[](newSize - 1); else { for (ArrayIndex index = newSize; index < oldSize; ++index) { value_.map_->erase(index); @@ -3469,12 +3334,12 @@ void Value::resize(ArrayIndex newSize) { Value& Value::operator[](ArrayIndex index) { JSON_ASSERT_MESSAGE( - type_ == nullValue || type_ == arrayValue, + type() == nullValue || type() == arrayValue, "in Json::Value::operator[](ArrayIndex): requires arrayValue"); - if (type_ == nullValue) + if (type() == nullValue) *this = Value(arrayValue); CZString key(index); - ObjectValues::iterator it = value_.map_->lower_bound(key); + auto it = value_.map_->lower_bound(key); if (it != value_.map_->end() && (*it).first == key) return (*it).second; @@ -3492,9 +3357,9 @@ Value& Value::operator[](int index) { const Value& Value::operator[](ArrayIndex index) const { JSON_ASSERT_MESSAGE( - type_ == nullValue || type_ == arrayValue, + type() == nullValue || type() == arrayValue, "in Json::Value::operator[](ArrayIndex)const: requires arrayValue"); - if (type_ == nullValue) + if (type() == nullValue) return nullSingleton(); CZString key(index); ObjectValues::const_iterator it = value_.map_->find(key); @@ -3510,26 +3375,85 @@ const Value& Value::operator[](int index) const { return (*this)[ArrayIndex(index)]; } -void Value::initBasic(ValueType vtype, bool allocated) { - type_ = vtype; - allocated_ = allocated; - comments_ = 0; +void Value::initBasic(ValueType type, bool allocated) { + setType(type); + setIsAllocated(allocated); + comments_ = Comments{}; start_ = 0; limit_ = 0; } +void Value::dupPayload(const Value& other) { + setType(other.type()); + setIsAllocated(false); + switch (type()) { + case nullValue: + case intValue: + case uintValue: + case realValue: + case booleanValue: + value_ = other.value_; + break; + case stringValue: + if (other.value_.string_ && other.isAllocated()) { + unsigned len; + char const* str; + decodePrefixedString(other.isAllocated(), other.value_.string_, &len, + &str); + value_.string_ = duplicateAndPrefixStringValue(str, len); + setIsAllocated(true); + } else { + value_.string_ = other.value_.string_; + } + break; + case arrayValue: + case objectValue: + value_.map_ = new ObjectValues(*other.value_.map_); + break; + default: + JSON_ASSERT_UNREACHABLE; + } +} + +void Value::releasePayload() { + switch (type()) { + case nullValue: + case intValue: + case uintValue: + case realValue: + case booleanValue: + break; + case stringValue: + if (isAllocated()) + releasePrefixedStringValue(value_.string_); + break; + case arrayValue: + case objectValue: + delete value_.map_; + break; + default: + JSON_ASSERT_UNREACHABLE; + } +} + +void Value::dupMeta(const Value& other) { + comments_ = other.comments_; + start_ = other.start_; + limit_ = other.limit_; +} + // Access an object value by name, create a null member if it does not exist. // @pre Type of '*this' is object or null. // @param key is null-terminated. Value& Value::resolveReference(const char* key) { JSON_ASSERT_MESSAGE( - type_ == nullValue || type_ == objectValue, + type() == nullValue || type() == objectValue, "in Json::Value::resolveReference(): requires objectValue"); - if (type_ == nullValue) + if (type() == nullValue) *this = Value(objectValue); - CZString actualKey( - key, static_cast(strlen(key)), CZString::noDuplication); // NOTE! - ObjectValues::iterator it = value_.map_->lower_bound(actualKey); + CZString actualKey(key, static_cast(strlen(key)), + CZString::noDuplication); // NOTE! + auto it = value_.map_->lower_bound(actualKey); if (it != value_.map_->end() && (*it).first == actualKey) return (*it).second; @@ -3540,16 +3464,15 @@ Value& Value::resolveReference(const char* key) { } // @param key is not null-terminated. -Value& Value::resolveReference(char const* key, char const* cend) -{ +Value& Value::resolveReference(char const* key, char const* end) { JSON_ASSERT_MESSAGE( - type_ == nullValue || type_ == objectValue, + type() == nullValue || type() == objectValue, "in Json::Value::resolveReference(key, end): requires objectValue"); - if (type_ == nullValue) + if (type() == nullValue) *this = Value(objectValue); - CZString actualKey( - key, static_cast(cend-key), CZString::duplicateOnCopy); - ObjectValues::iterator it = value_.map_->lower_bound(actualKey); + CZString actualKey(key, static_cast(end - key), + CZString::duplicateOnCopy); + auto it = value_.map_->lower_bound(actualKey); if (it != value_.map_->end() && (*it).first == actualKey) return (*it).second; @@ -3566,27 +3489,35 @@ Value Value::get(ArrayIndex index, const Value& defaultValue) const { bool Value::isValidIndex(ArrayIndex index) const { return index < size(); } -Value const* Value::find(char const* key, char const* cend) const -{ - JSON_ASSERT_MESSAGE( - type_ == nullValue || type_ == objectValue, - "in Json::Value::find(key, end, found): requires objectValue or nullValue"); - if (type_ == nullValue) return NULL; - CZString actualKey(key, static_cast(cend-key), CZString::noDuplication); +Value const* Value::find(char const* begin, char const* end) const { + JSON_ASSERT_MESSAGE(type() == nullValue || type() == objectValue, + "in Json::Value::find(begin, end): requires " + "objectValue or nullValue"); + if (type() == nullValue) + return nullptr; + CZString actualKey(begin, static_cast(end - begin), + CZString::noDuplication); ObjectValues::const_iterator it = value_.map_->find(actualKey); - if (it == value_.map_->end()) return NULL; + if (it == value_.map_->end()) + return nullptr; return &(*it).second; } -const Value& Value::operator[](const char* key) const -{ +Value* Value::demand(char const* begin, char const* end) { + JSON_ASSERT_MESSAGE(type() == nullValue || type() == objectValue, + "in Json::Value::demand(begin, end): requires " + "objectValue or nullValue"); + return &resolveReference(begin, end); +} +const Value& Value::operator[](const char* key) const { Value const* found = find(key, key + strlen(key)); - if (!found) return nullSingleton(); + if (!found) + return nullSingleton(); return *found; } -Value const& Value::operator[](JSONCPP_STRING const& key) const -{ +Value const& Value::operator[](const String& key) const { Value const* found = find(key.data(), key.data() + key.length()); - if (!found) return nullSingleton(); + if (!found) + return nullSingleton(); return *found; } @@ -3594,7 +3525,7 @@ Value& Value::operator[](const char* key) { return resolveReference(key, key + strlen(key)); } -Value& Value::operator[](const JSONCPP_STRING& key) { +Value& Value::operator[](const String& key) { return resolveReference(key.data(), key.data() + key.length()); } @@ -3602,178 +3533,140 @@ Value& Value::operator[](const StaticString& key) { return resolveReference(key.c_str()); } -#ifdef JSON_USE_CPPTL -Value& Value::operator[](const CppTL::ConstString& key) { - return resolveReference(key.c_str(), key.end_c_str()); +Value& Value::append(const Value& value) { return append(Value(value)); } + +Value& Value::append(Value&& value) { + JSON_ASSERT_MESSAGE(type() == nullValue || type() == arrayValue, + "in Json::Value::append: requires arrayValue"); + if (type() == nullValue) { + *this = Value(arrayValue); + } + return this->value_.map_->emplace(size(), std::move(value)).first->second; } -Value const& Value::operator[](CppTL::ConstString const& key) const -{ - Value const* found = find(key.c_str(), key.end_c_str()); - if (!found) return nullSingleton(); - return *found; + +bool Value::insert(ArrayIndex index, const Value& newValue) { + return insert(index, Value(newValue)); } -#endif -Value& Value::append(const Value& value) { return (*this)[size()] = value; } +bool Value::insert(ArrayIndex index, Value&& newValue) { + JSON_ASSERT_MESSAGE(type() == nullValue || type() == arrayValue, + "in Json::Value::insert: requires arrayValue"); + ArrayIndex length = size(); + if (index > length) { + return false; + } + for (ArrayIndex i = length; i > index; i--) { + (*this)[i] = std::move((*this)[i - 1]); + } + (*this)[index] = std::move(newValue); + return true; +} -#if JSON_HAS_RVALUE_REFERENCES - Value& Value::append(Value&& value) { return (*this)[size()] = std::move(value); } -#endif - -Value Value::get(char const* key, char const* cend, Value const& defaultValue) const -{ - Value const* found = find(key, cend); +Value Value::get(char const* begin, char const* end, + Value const& defaultValue) const { + Value const* found = find(begin, end); return !found ? defaultValue : *found; } -Value Value::get(char const* key, Value const& defaultValue) const -{ +Value Value::get(char const* key, Value const& defaultValue) const { return get(key, key + strlen(key), defaultValue); } -Value Value::get(JSONCPP_STRING const& key, Value const& defaultValue) const -{ +Value Value::get(String const& key, Value const& defaultValue) const { return get(key.data(), key.data() + key.length(), defaultValue); } - -bool Value::removeMember(const char* key, const char* cend, Value* removed) -{ - if (type_ != objectValue) { +bool Value::removeMember(const char* begin, const char* end, Value* removed) { + if (type() != objectValue) { return false; } - CZString actualKey(key, static_cast(cend-key), CZString::noDuplication); - ObjectValues::iterator it = value_.map_->find(actualKey); + CZString actualKey(begin, static_cast(end - begin), + CZString::noDuplication); + auto it = value_.map_->find(actualKey); if (it == value_.map_->end()) return false; - *removed = it->second; + if (removed) + *removed = std::move(it->second); value_.map_->erase(it); return true; } -bool Value::removeMember(const char* key, Value* removed) -{ +bool Value::removeMember(const char* key, Value* removed) { return removeMember(key, key + strlen(key), removed); } -bool Value::removeMember(JSONCPP_STRING const& key, Value* removed) -{ +bool Value::removeMember(String const& key, Value* removed) { return removeMember(key.data(), key.data() + key.length(), removed); } -void Value::removeMember(const char* key) -{ - JSON_ASSERT_MESSAGE(type_ == nullValue || type_ == objectValue, +void Value::removeMember(const char* key) { + JSON_ASSERT_MESSAGE(type() == nullValue || type() == objectValue, "in Json::Value::removeMember(): requires objectValue"); - if (type_ == nullValue) + if (type() == nullValue) return; CZString actualKey(key, unsigned(strlen(key)), CZString::noDuplication); value_.map_->erase(actualKey); } -void Value::removeMember(const JSONCPP_STRING& key) -{ - removeMember(key.c_str()); -} +void Value::removeMember(const String& key) { removeMember(key.c_str()); } bool Value::removeIndex(ArrayIndex index, Value* removed) { - if (type_ != arrayValue) { + if (type() != arrayValue) { return false; } CZString key(index); - ObjectValues::iterator it = value_.map_->find(key); + auto it = value_.map_->find(key); if (it == value_.map_->end()) { return false; } - *removed = it->second; + if (removed) + *removed = it->second; ArrayIndex oldSize = size(); // shift left all items left, into the place of the "removed" - for (ArrayIndex i = index; i < (oldSize - 1); ++i){ + for (ArrayIndex i = index; i < (oldSize - 1); ++i) { CZString keey(i); (*value_.map_)[keey] = (*this)[i + 1]; } // erase the last one ("leftover") CZString keyLast(oldSize - 1); - ObjectValues::iterator itLast = value_.map_->find(keyLast); + auto itLast = value_.map_->find(keyLast); value_.map_->erase(itLast); return true; } -#ifdef JSON_USE_CPPTL -Value Value::get(const CppTL::ConstString& key, - const Value& defaultValue) const { - return get(key.c_str(), key.end_c_str(), defaultValue); +bool Value::isMember(char const* begin, char const* end) const { + Value const* value = find(begin, end); + return nullptr != value; } -#endif - -bool Value::isMember(char const* key, char const* cend) const -{ - Value const* value = find(key, cend); - return NULL != value; -} -bool Value::isMember(char const* key) const -{ +bool Value::isMember(char const* key) const { return isMember(key, key + strlen(key)); } -bool Value::isMember(JSONCPP_STRING const& key) const -{ +bool Value::isMember(String const& key) const { return isMember(key.data(), key.data() + key.length()); } -#ifdef JSON_USE_CPPTL -bool Value::isMember(const CppTL::ConstString& key) const { - return isMember(key.c_str(), key.end_c_str()); -} -#endif - Value::Members Value::getMemberNames() const { JSON_ASSERT_MESSAGE( - type_ == nullValue || type_ == objectValue, + type() == nullValue || type() == objectValue, "in Json::Value::getMemberNames(), value must be objectValue"); - if (type_ == nullValue) + if (type() == nullValue) return Value::Members(); Members members; members.reserve(value_.map_->size()); ObjectValues::const_iterator it = value_.map_->begin(); ObjectValues::const_iterator itEnd = value_.map_->end(); for (; it != itEnd; ++it) { - members.push_back(JSONCPP_STRING((*it).first.data(), - (*it).first.length())); + members.push_back(String((*it).first.data(), (*it).first.length())); } return members; } -// -//# ifdef JSON_USE_CPPTL -// EnumMemberNames -// Value::enumMemberNames() const -//{ -// if ( type_ == objectValue ) -// { -// return CppTL::Enum::any( CppTL::Enum::transform( -// CppTL::Enum::keys( *(value_.map_), CppTL::Type() ), -// MemberNamesTransform() ) ); -// } -// return EnumMemberNames(); -//} -// -// -// EnumValues -// Value::enumValues() const -//{ -// if ( type_ == objectValue || type_ == arrayValue ) -// return CppTL::Enum::anyValues( *(value_.map_), -// CppTL::Type() ); -// return EnumValues(); -//} -// -//# endif static bool IsIntegral(double d) { double integral_part; return modf(d, &integral_part) == 0.0; } -bool Value::isNull() const { return type_ == nullValue; } +bool Value::isNull() const { return type() == nullValue; } -bool Value::isBool() const { return type_ == booleanValue; } +bool Value::isBool() const { return type() == booleanValue; } bool Value::isInt() const { - switch (type_) { + switch (type()) { case intValue: #if defined(JSON_HAS_INT64) return value_.int_ >= minInt && value_.int_ <= maxInt; @@ -3792,7 +3685,7 @@ bool Value::isInt() const { } bool Value::isUInt() const { - switch (type_) { + switch (type()) { case intValue: #if defined(JSON_HAS_INT64) return value_.int_ >= 0 && LargestUInt(value_.int_) <= LargestUInt(maxUInt); @@ -3816,7 +3709,7 @@ bool Value::isUInt() const { bool Value::isInt64() const { #if defined(JSON_HAS_INT64) - switch (type_) { + switch (type()) { case intValue: return true; case uintValue: @@ -3836,7 +3729,7 @@ bool Value::isInt64() const { bool Value::isUInt64() const { #if defined(JSON_HAS_INT64) - switch (type_) { + switch (type()) { case intValue: return value_.int_ >= 0; case uintValue: @@ -3855,61 +3748,92 @@ bool Value::isUInt64() const { } bool Value::isIntegral() const { - switch (type_) { - case intValue: - case uintValue: - return true; - case realValue: + switch (type()) { + case intValue: + case uintValue: + return true; + case realValue: #if defined(JSON_HAS_INT64) - // Note that maxUInt64 (= 2^64 - 1) is not exactly representable as a - // double, so double(maxUInt64) will be rounded up to 2^64. Therefore we - // require the value to be strictly less than the limit. - return value_.real_ >= double(minInt64) && value_.real_ < maxUInt64AsDouble && IsIntegral(value_.real_); + // Note that maxUInt64 (= 2^64 - 1) is not exactly representable as a + // double, so double(maxUInt64) will be rounded up to 2^64. Therefore we + // require the value to be strictly less than the limit. + return value_.real_ >= double(minInt64) && + value_.real_ < maxUInt64AsDouble && IsIntegral(value_.real_); #else - return value_.real_ >= minInt && value_.real_ <= maxUInt && IsIntegral(value_.real_); + return value_.real_ >= minInt && value_.real_ <= maxUInt && + IsIntegral(value_.real_); #endif // JSON_HAS_INT64 - default: - break; + default: + break; } return false; } -bool Value::isDouble() const { return type_ == intValue || type_ == uintValue || type_ == realValue; } +bool Value::isDouble() const { + return type() == intValue || type() == uintValue || type() == realValue; +} bool Value::isNumeric() const { return isDouble(); } -bool Value::isString() const { return type_ == stringValue; } +bool Value::isString() const { return type() == stringValue; } -bool Value::isArray() const { return type_ == arrayValue; } +bool Value::isArray() const { return type() == arrayValue; } -bool Value::isObject() const { return type_ == objectValue; } +bool Value::isObject() const { return type() == objectValue; } -void Value::setComment(const char* comment, size_t len, CommentPlacement placement) { - if (!comments_) - comments_ = new CommentInfo[numberOfCommentPlacement]; - if ((len > 0) && (comment[len-1] == '\n')) { - // Always discard trailing newline, to aid indentation. - len -= 1; +Value::Comments::Comments(const Comments& that) + : ptr_{cloneUnique(that.ptr_)} {} + +Value::Comments::Comments(Comments&& that) : ptr_{std::move(that.ptr_)} {} + +Value::Comments& Value::Comments::operator=(const Comments& that) { + ptr_ = cloneUnique(that.ptr_); + return *this; +} + +Value::Comments& Value::Comments::operator=(Comments&& that) { + ptr_ = std::move(that.ptr_); + return *this; +} + +bool Value::Comments::has(CommentPlacement slot) const { + return ptr_ && !(*ptr_)[slot].empty(); +} + +String Value::Comments::get(CommentPlacement slot) const { + if (!ptr_) + return {}; + return (*ptr_)[slot]; +} + +void Value::Comments::set(CommentPlacement slot, String comment) { + if (!ptr_) { + ptr_ = std::unique_ptr(new Array()); + } + // check comments array boundry. + if (slot < CommentPlacement::numberOfCommentPlacement) { + (*ptr_)[slot] = std::move(comment); } - comments_[placement].setComment(comment, len); } -void Value::setComment(const char* comment, CommentPlacement placement) { - setComment(comment, strlen(comment), placement); -} - -void Value::setComment(const JSONCPP_STRING& comment, CommentPlacement placement) { - setComment(comment.c_str(), comment.length(), placement); +void Value::setComment(String comment, CommentPlacement placement) { + if (!comment.empty() && (comment.back() == '\n')) { + // Always discard trailing newline, to aid indentation. + comment.pop_back(); + } + JSON_ASSERT(!comment.empty()); + JSON_ASSERT_MESSAGE( + comment[0] == '\0' || comment[0] == '/', + "in Json::Value::setComment(): Comments must start with /"); + comments_.set(placement, std::move(comment)); } bool Value::hasComment(CommentPlacement placement) const { - return comments_ != 0 && comments_[placement].comment_ != 0; + return comments_.has(placement); } -JSONCPP_STRING Value::getComment(CommentPlacement placement) const { - if (hasComment(placement)) - return comments_[placement].comment_; - return ""; +String Value::getComment(CommentPlacement placement) const { + return comments_.get(placement); } void Value::setOffsetStart(ptrdiff_t start) { start_ = start; } @@ -3920,18 +3844,18 @@ ptrdiff_t Value::getOffsetStart() const { return start_; } ptrdiff_t Value::getOffsetLimit() const { return limit_; } -JSONCPP_STRING Value::toStyledString() const { +String Value::toStyledString() const { StreamWriterBuilder builder; - JSONCPP_STRING out = this->hasComment(commentBefore) ? "\n" : ""; + String out = this->hasComment(commentBefore) ? "\n" : ""; out += Json::writeString(builder, *this); - out += "\n"; + out += '\n'; return out; } Value::const_iterator Value::begin() const { - switch (type_) { + switch (type()) { case arrayValue: case objectValue: if (value_.map_) @@ -3940,11 +3864,11 @@ Value::const_iterator Value::begin() const { default: break; } - return const_iterator(); + return {}; } Value::const_iterator Value::end() const { - switch (type_) { + switch (type()) { case arrayValue: case objectValue: if (value_.map_) @@ -3953,11 +3877,11 @@ Value::const_iterator Value::end() const { default: break; } - return const_iterator(); + return {}; } Value::iterator Value::begin() { - switch (type_) { + switch (type()) { case arrayValue: case objectValue: if (value_.map_) @@ -3970,7 +3894,7 @@ Value::iterator Value::begin() { } Value::iterator Value::end() { - switch (type_) { + switch (type()) { case arrayValue: case objectValue: if (value_.map_) @@ -3985,25 +3909,20 @@ Value::iterator Value::end() { // class PathArgument // ////////////////////////////////////////////////////////////////// -PathArgument::PathArgument() : key_(), index_(), kind_(kindNone) {} +PathArgument::PathArgument() = default; PathArgument::PathArgument(ArrayIndex index) - : key_(), index_(index), kind_(kindIndex) {} + : index_(index), kind_(kindIndex) {} -PathArgument::PathArgument(const char* key) - : key_(key), index_(), kind_(kindKey) {} +PathArgument::PathArgument(const char* key) : key_(key), kind_(kindKey) {} -PathArgument::PathArgument(const JSONCPP_STRING& key) - : key_(key.c_str()), index_(), kind_(kindKey) {} +PathArgument::PathArgument(String key) : key_(std::move(key)), kind_(kindKey) {} // class Path // ////////////////////////////////////////////////////////////////// -Path::Path(const JSONCPP_STRING& path, - const PathArgument& a1, - const PathArgument& a2, - const PathArgument& a3, - const PathArgument& a4, +Path::Path(const String& path, const PathArgument& a1, const PathArgument& a2, + const PathArgument& a3, const PathArgument& a4, const PathArgument& a5) { InArgs in; in.reserve(5); @@ -4015,10 +3934,10 @@ Path::Path(const JSONCPP_STRING& path, makePath(path, in); } -void Path::makePath(const JSONCPP_STRING& path, const InArgs& in) { +void Path::makePath(const String& path, const InArgs& in) { const char* current = path.c_str(); const char* end = current + path.length(); - InArgs::const_iterator itInArg = in.begin(); + auto itInArg = in.begin(); while (current != end) { if (*current == '[') { ++current; @@ -4041,13 +3960,12 @@ void Path::makePath(const JSONCPP_STRING& path, const InArgs& in) { const char* beginName = current; while (current != end && !strchr("[.", *current)) ++current; - args_.push_back(JSONCPP_STRING(beginName, current)); + args_.push_back(String(beginName, current)); } } } -void Path::addPathInArg(const JSONCPP_STRING& /*path*/, - const InArgs& in, +void Path::addPathInArg(const String& /*path*/, const InArgs& in, InArgs::const_iterator& itInArg, PathArgument::Kind kind) { if (itInArg == in.end()) { @@ -4059,30 +3977,29 @@ void Path::addPathInArg(const JSONCPP_STRING& /*path*/, } } -void Path::invalidPath(const JSONCPP_STRING& /*path*/, int /*location*/) { +void Path::invalidPath(const String& /*path*/, int /*location*/) { // Error: invalid path. } const Value& Path::resolve(const Value& root) const { const Value* node = &root; - for (Args::const_iterator it = args_.begin(); it != args_.end(); ++it) { - const PathArgument& arg = *it; + for (const auto& arg : args_) { if (arg.kind_ == PathArgument::kindIndex) { if (!node->isArray() || !node->isValidIndex(arg.index_)) { - // Error: unable to resolve path (array value expected at position... - return Value::null; + // Error: unable to resolve path (array value expected at position... ) + return Value::nullSingleton(); } node = &((*node)[arg.index_]); } else if (arg.kind_ == PathArgument::kindKey) { if (!node->isObject()) { // Error: unable to resolve path (object value expected at position...) - return Value::null; + return Value::nullSingleton(); } node = &((*node)[arg.key_]); if (node == &Value::nullSingleton()) { // Error: unable to resolve path (object has no member named '' at // position...) - return Value::null; + return Value::nullSingleton(); } } } @@ -4091,8 +4008,7 @@ const Value& Path::resolve(const Value& root) const { Value Path::resolve(const Value& root, const Value& defaultValue) const { const Value* node = &root; - for (Args::const_iterator it = args_.begin(); it != args_.end(); ++it) { - const PathArgument& arg = *it; + for (const auto& arg : args_) { if (arg.kind_ == PathArgument::kindIndex) { if (!node->isArray() || !node->isValidIndex(arg.index_)) return defaultValue; @@ -4110,8 +4026,7 @@ Value Path::resolve(const Value& root, const Value& defaultValue) const { Value& Path::make(Value& root) const { Value* node = &root; - for (Args::const_iterator it = args_.begin(); it != args_.end(); ++it) { - const PathArgument& arg = *it; + for (const auto& arg : args_) { if (arg.kind_ == PathArgument::kindIndex) { if (!node->isArray()) { // Error: node is not an array at position ... @@ -4148,71 +4063,81 @@ Value& Path::make(Value& root) const { // See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE #if !defined(JSON_IS_AMALGAMATION) -#include #include "json_tool.h" +#include #endif // if !defined(JSON_IS_AMALGAMATION) +#include +#include +#include +#include #include #include +#include #include #include -#include -#include -#include + +#if __cplusplus >= 201103L +#include #include -#if defined(_MSC_VER) && _MSC_VER >= 1200 && _MSC_VER < 1800 // Between VC++ 6.0 and VC++ 11.0 +#if !defined(isnan) +#define isnan std::isnan +#endif + +#if !defined(isfinite) +#define isfinite std::isfinite +#endif + +#else +#include +#include + +#if defined(_MSC_VER) +#if !defined(isnan) +#include +#define isnan _isnan +#endif + +#if !defined(isfinite) #include #define isfinite _finite -#elif defined(__sun) && defined(__SVR4) //Solaris +#endif + +#if !defined(_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES) +#define _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES 1 +#endif //_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES + +#endif //_MSC_VER + +#if defined(__sun) && defined(__SVR4) // Solaris #if !defined(isfinite) #include #define isfinite finite #endif -#elif defined(_AIX) -#if !defined(isfinite) -#include -#define isfinite finite #endif -#elif defined(__hpux) + +#if defined(__hpux) #if !defined(isfinite) #if defined(__ia64) && !defined(finite) -#define isfinite(x) ((sizeof(x) == sizeof(float) ? \ - _Isfinitef(x) : _IsFinite(x))) -#else -#include +#define isfinite(x) \ + ((sizeof(x) == sizeof(float) ? _Isfinitef(x) : _IsFinite(x))) +#endif +#endif +#endif + +#if !defined(isnan) +// IEEE standard states that NaN values will not compare to themselves +#define isnan(x) (x != x) +#endif + +#if !defined(__APPLE__) +#if !defined(isfinite) #define isfinite finite #endif #endif -#else -#include -#if !(defined(__QNXNTO__)) // QNX already defines isfinite -#define isfinite std::isfinite -#endif #endif #if defined(_MSC_VER) -#if !defined(WINCE) && defined(__STDC_SECURE_LIB__) && _MSC_VER >= 1500 // VC++ 9.0 and above -#define snprintf sprintf_s -#elif _MSC_VER >= 1900 // VC++ 14.0 and above -#define snprintf std::snprintf -#else -#define snprintf _snprintf -#endif -#elif defined(__ANDROID__) || defined(__QNXNTO__) -#define snprintf snprintf -#elif __cplusplus >= 201103L -#if !defined(__MINGW32__) && !defined(__CYGWIN__) -#define snprintf std::snprintf -#endif -#endif - -#if defined(__BORLANDC__) -#include -#define isfinite _finite -#define snprintf _snprintf -#endif - -#if defined(_MSC_VER) && _MSC_VER >= 1400 // VC++ 8.0 // Disable warning about strdup being deprecated. #pragma warning(disable : 4996) #endif @@ -4220,12 +4145,12 @@ Value& Path::make(Value& root) const { namespace Json { #if __cplusplus >= 201103L || (defined(_CPPLIB_VER) && _CPPLIB_VER >= 520) -typedef std::unique_ptr StreamWriterPtr; +using StreamWriterPtr = std::unique_ptr; #else -typedef std::auto_ptr StreamWriterPtr; +using StreamWriterPtr = std::auto_ptr; #endif -JSONCPP_STRING valueToString(LargestInt value) { +String valueToString(LargestInt value) { UIntToStringBuffer buffer; char* current = buffer + sizeof(buffer); if (value == Value::minLargestInt) { @@ -4241,7 +4166,7 @@ JSONCPP_STRING valueToString(LargestInt value) { return current; } -JSONCPP_STRING valueToString(LargestUInt value) { +String valueToString(LargestUInt value) { UIntToStringBuffer buffer; char* current = buffer + sizeof(buffer); uintToString(value, current); @@ -4251,67 +4176,70 @@ JSONCPP_STRING valueToString(LargestUInt value) { #if defined(JSON_HAS_INT64) -JSONCPP_STRING valueToString(Int value) { - return valueToString(LargestInt(value)); -} +String valueToString(Int value) { return valueToString(LargestInt(value)); } -JSONCPP_STRING valueToString(UInt value) { - return valueToString(LargestUInt(value)); -} +String valueToString(UInt value) { return valueToString(LargestUInt(value)); } #endif // # if defined(JSON_HAS_INT64) namespace { -JSONCPP_STRING valueToString(double value, bool useSpecialFloats, unsigned int precision) { - // Allocate a buffer that is more than large enough to store the 16 digits of - // precision requested below. - char buffer[36]; - int len = -1; - - char formatString[15]; - snprintf(formatString, sizeof(formatString), "%%.%ug", precision); - +String valueToString(double value, bool useSpecialFloats, + unsigned int precision, PrecisionType precisionType) { // Print into the buffer. We need not request the alternative representation // that always has a decimal point because JSON doesn't distinguish the // concepts of reals and integers. - if (isfinite(value)) { - len = snprintf(buffer, sizeof(buffer), formatString, value); - fixNumericLocale(buffer, buffer + len); - - // try to ensure we preserve the fact that this was given to us as a double on input - if (!strchr(buffer, '.') && !strchr(buffer, 'e')) { - strcat(buffer, ".0"); - } - - } else { - // IEEE standard states that NaN values will not compare to themselves - if (value != value) { - len = snprintf(buffer, sizeof(buffer), useSpecialFloats ? "NaN" : "null"); - } else if (value < 0) { - len = snprintf(buffer, sizeof(buffer), useSpecialFloats ? "-Infinity" : "-1e+9999"); - } else { - len = snprintf(buffer, sizeof(buffer), useSpecialFloats ? "Infinity" : "1e+9999"); - } + if (!isfinite(value)) { + static const char* const reps[2][3] = {{"NaN", "-Infinity", "Infinity"}, + {"null", "-1e+9999", "1e+9999"}}; + return reps[useSpecialFloats ? 0 : 1] + [isnan(value) ? 0 : (value < 0) ? 1 : 2]; + } + + String buffer(size_t(36), '\0'); + while (true) { + int len = jsoncpp_snprintf( + &*buffer.begin(), buffer.size(), + (precisionType == PrecisionType::significantDigits) ? "%.*g" : "%.*f", + precision, value); + assert(len >= 0); + auto wouldPrint = static_cast(len); + if (wouldPrint >= buffer.size()) { + buffer.resize(wouldPrint + 1); + continue; + } + buffer.resize(wouldPrint); + break; + } + + buffer.erase(fixNumericLocale(buffer.begin(), buffer.end()), buffer.end()); + + // strip the zero padding from the right + if (precisionType == PrecisionType::decimalPlaces) { + buffer.erase(fixZerosInTheEnd(buffer.begin(), buffer.end()), buffer.end()); + } + + // try to ensure we preserve the fact that this was given to us as a double on + // input + if (buffer.find('.') == buffer.npos && buffer.find('e') == buffer.npos) { + buffer += ".0"; } - assert(len >= 0); return buffer; } +} // namespace + +String valueToString(double value, unsigned int precision, + PrecisionType precisionType) { + return valueToString(value, false, precision, precisionType); } -JSONCPP_STRING valueToString(double value) { return valueToString(value, false, 17); } +String valueToString(bool value) { return value ? "true" : "false"; } -JSONCPP_STRING valueToString(bool value) { return value ? "true" : "false"; } - -static bool isAnyCharRequiredQuoting(char const* s, size_t n) { +static bool doesAnyCharRequireEscaping(char const* s, size_t n) { assert(s || !n); - char const* const end = s + n; - for (char const* cur = s; cur < end; ++cur) { - if (*cur == '\\' || *cur == '\"' || *cur < ' ' - || static_cast(*cur) < 0x80) - return true; - } - return false; + return std::any_of(s, s + n, [](unsigned char c) { + return c == '\\' || c == '"' || c < 0x20 || c > 0x7F; + }); } static unsigned int utf8ToCodepoint(const char*& s, const char* e) { @@ -4326,8 +4254,8 @@ static unsigned int utf8ToCodepoint(const char*& s, const char* e) { if (e - s < 2) return REPLACEMENT_CHARACTER; - unsigned int calculated = ((firstByte & 0x1F) << 6) - | (static_cast(s[1]) & 0x3F); + unsigned int calculated = + ((firstByte & 0x1F) << 6) | (static_cast(s[1]) & 0x3F); s += 1; // oversized encoded characters are invalid return calculated < 0x80 ? REPLACEMENT_CHARACTER : calculated; @@ -4337,9 +4265,9 @@ static unsigned int utf8ToCodepoint(const char*& s, const char* e) { if (e - s < 3) return REPLACEMENT_CHARACTER; - unsigned int calculated = ((firstByte & 0x0F) << 12) - | ((static_cast(s[1]) & 0x3F) << 6) - | (static_cast(s[2]) & 0x3F); + unsigned int calculated = ((firstByte & 0x0F) << 12) | + ((static_cast(s[1]) & 0x3F) << 6) | + (static_cast(s[2]) & 0x3F); s += 2; // surrogates aren't valid codepoints itself // shouldn't be UTF-8 encoded @@ -4353,10 +4281,10 @@ static unsigned int utf8ToCodepoint(const char*& s, const char* e) { if (e - s < 4) return REPLACEMENT_CHARACTER; - unsigned int calculated = ((firstByte & 0x07) << 24) - | ((static_cast(s[1]) & 0x3F) << 12) - | ((static_cast(s[2]) & 0x3F) << 6) - | (static_cast(s[3]) & 0x3F); + unsigned int calculated = ((firstByte & 0x07) << 18) | + ((static_cast(s[1]) & 0x3F) << 12) | + ((static_cast(s[2]) & 0x3F) << 6) | + (static_cast(s[3]) & 0x3F); s += 3; // oversized encoded characters are invalid return calculated < 0x10000 ? REPLACEMENT_CHARACTER : calculated; @@ -4365,28 +4293,27 @@ static unsigned int utf8ToCodepoint(const char*& s, const char* e) { return REPLACEMENT_CHARACTER; } -static const char hex2[] = - "000102030405060708090a0b0c0d0e0f" - "101112131415161718191a1b1c1d1e1f" - "202122232425262728292a2b2c2d2e2f" - "303132333435363738393a3b3c3d3e3f" - "404142434445464748494a4b4c4d4e4f" - "505152535455565758595a5b5c5d5e5f" - "606162636465666768696a6b6c6d6e6f" - "707172737475767778797a7b7c7d7e7f" - "808182838485868788898a8b8c8d8e8f" - "909192939495969798999a9b9c9d9e9f" - "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf" - "b0b1b2b3b4b5b6b7b8b9babbbcbdbebf" - "c0c1c2c3c4c5c6c7c8c9cacbcccdcecf" - "d0d1d2d3d4d5d6d7d8d9dadbdcdddedf" - "e0e1e2e3e4e5e6e7e8e9eaebecedeeef" - "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"; +static const char hex2[] = "000102030405060708090a0b0c0d0e0f" + "101112131415161718191a1b1c1d1e1f" + "202122232425262728292a2b2c2d2e2f" + "303132333435363738393a3b3c3d3e3f" + "404142434445464748494a4b4c4d4e4f" + "505152535455565758595a5b5c5d5e5f" + "606162636465666768696a6b6c6d6e6f" + "707172737475767778797a7b7c7d7e7f" + "808182838485868788898a8b8c8d8e8f" + "909192939495969798999a9b9c9d9e9f" + "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf" + "b0b1b2b3b4b5b6b7b8b9babbbcbdbebf" + "c0c1c2c3c4c5c6c7c8c9cacbcccdcecf" + "d0d1d2d3d4d5d6d7d8d9dadbdcdddedf" + "e0e1e2e3e4e5e6e7e8e9eaebecedeeef" + "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"; -static JSONCPP_STRING toHex16Bit(unsigned int x) { +static String toHex16Bit(unsigned int x) { const unsigned int hi = (x >> 8) & 0xff; const unsigned int lo = x & 0xff; - JSONCPP_STRING result(4, ' '); + String result(4, ' '); result[0] = hex2[2 * hi]; result[1] = hex2[2 * hi + 1]; result[2] = hex2[2 * lo]; @@ -4394,18 +4321,26 @@ static JSONCPP_STRING toHex16Bit(unsigned int x) { return result; } -static JSONCPP_STRING valueToQuotedStringN(const char* value, unsigned length) { - if (value == NULL) +static void appendRaw(String& result, unsigned ch) { + result += static_cast(ch); +} + +static void appendHex(String& result, unsigned ch) { + result.append("\\u").append(toHex16Bit(ch)); +} + +static String valueToQuotedStringN(const char* value, unsigned length, + bool emitUTF8 = false) { + if (value == nullptr) return ""; - if (!isAnyCharRequiredQuoting(value, length)) - return JSONCPP_STRING("\"") + value + "\""; + if (!doesAnyCharRequireEscaping(value, length)) + return String("\"") + value + "\""; // We have to walk value and escape any special characters. - // Appending to JSONCPP_STRING is not efficient, but this should be rare. + // Appending to String is not efficient, but this should be rare. // (Note: forward slashes are *not* rare, but I am not escaping them.) - JSONCPP_STRING::size_type maxsize = - length * 2 + 3; // allescaped+quotes+NULL - JSONCPP_STRING result; + String::size_type maxsize = length * 2 + 3; // allescaped+quotes+NULL + String result; result.reserve(maxsize); // to avoid lots of mallocs result += "\""; char const* end = value + length; @@ -4441,45 +4376,50 @@ static JSONCPP_STRING valueToQuotedStringN(const char* value, unsigned length) { // Should add a flag to allow this compatibility mode and prevent this // sequence from occurring. default: { - unsigned int cp = utf8ToCodepoint(c, end); - // don't escape non-control characters - // (short escape sequence are applied above) - if (cp < 0x80 && cp >= 0x20) - result += static_cast(cp); - else if (cp < 0x10000) { // codepoint is in Basic Multilingual Plane - result += "\\u"; - result += toHex16Bit(cp); + if (emitUTF8) { + unsigned codepoint = static_cast(*c); + if (codepoint < 0x20) { + appendHex(result, codepoint); + } else { + appendRaw(result, codepoint); } - else { // codepoint is not in Basic Multilingual Plane - // convert to surrogate pair first - cp -= 0x10000; - result += "\\u"; - result += toHex16Bit((cp >> 10) + 0xD800); - result += "\\u"; - result += toHex16Bit((cp & 0x3FF) + 0xDC00); + } else { + unsigned codepoint = utf8ToCodepoint(c, end); // modifies `c` + if (codepoint < 0x20) { + appendHex(result, codepoint); + } else if (codepoint < 0x80) { + appendRaw(result, codepoint); + } else if (codepoint < 0x10000) { + // Basic Multilingual Plane + appendHex(result, codepoint); + } else { + // Extended Unicode. Encode 20 bits as a surrogate pair. + codepoint -= 0x10000; + appendHex(result, 0xd800 + ((codepoint >> 10) & 0x3ff)); + appendHex(result, 0xdc00 + (codepoint & 0x3ff)); } } - break; + } break; } } result += "\""; return result; } -JSONCPP_STRING valueToQuotedString(const char* value) { +String valueToQuotedString(const char* value) { return valueToQuotedStringN(value, static_cast(strlen(value))); } // Class Writer // ////////////////////////////////////////////////////////////////// -Writer::~Writer() {} +Writer::~Writer() = default; // Class FastWriter // ////////////////////////////////////////////////////////////////// FastWriter::FastWriter() - : yamlCompatibilityEnabled_(false), dropNullPlaceholders_(false), - omitEndingLineFeed_(false) {} + + = default; void FastWriter::enableYAMLCompatibility() { yamlCompatibilityEnabled_ = true; } @@ -4487,11 +4427,11 @@ void FastWriter::dropNullPlaceholders() { dropNullPlaceholders_ = true; } void FastWriter::omitEndingLineFeed() { omitEndingLineFeed_ = true; } -JSONCPP_STRING FastWriter::write(const Value& root) { +String FastWriter::write(const Value& root) { document_.clear(); writeValue(root); if (!omitEndingLineFeed_) - document_ += "\n"; + document_ += '\n'; return document_; } @@ -4510,13 +4450,13 @@ void FastWriter::writeValue(const Value& value) { case realValue: document_ += valueToString(value.asDouble()); break; - case stringValue: - { + case stringValue: { // Is NULL possible for value.string_? No. char const* str; char const* end; bool ok = value.getString(&str, &end); - if (ok) document_ += valueToQuotedStringN(str, static_cast(end-str)); + if (ok) + document_ += valueToQuotedStringN(str, static_cast(end - str)); break; } case booleanValue: @@ -4535,12 +4475,12 @@ void FastWriter::writeValue(const Value& value) { case objectValue: { Value::Members members(value.getMemberNames()); document_ += '{'; - for (Value::Members::iterator it = members.begin(); it != members.end(); - ++it) { - const JSONCPP_STRING& name = *it; + for (auto it = members.begin(); it != members.end(); ++it) { + const String& name = *it; if (it != members.begin()) document_ += ','; - document_ += valueToQuotedStringN(name.data(), static_cast(name.length())); + document_ += valueToQuotedStringN(name.data(), + static_cast(name.length())); document_ += yamlCompatibilityEnabled_ ? ": " : ":"; writeValue(value[name]); } @@ -4552,17 +4492,16 @@ void FastWriter::writeValue(const Value& value) { // Class StyledWriter // ////////////////////////////////////////////////////////////////// -StyledWriter::StyledWriter() - : rightMargin_(74), indentSize_(3), addChildValues_() {} +StyledWriter::StyledWriter() = default; -JSONCPP_STRING StyledWriter::write(const Value& root) { +String StyledWriter::write(const Value& root) { document_.clear(); addChildValues_ = false; indentString_.clear(); writeCommentBeforeValue(root); writeValue(root); writeCommentAfterValueOnSameLine(root); - document_ += "\n"; + document_ += '\n'; return document_; } @@ -4580,14 +4519,15 @@ void StyledWriter::writeValue(const Value& value) { case realValue: pushValue(valueToString(value.asDouble())); break; - case stringValue: - { + case stringValue: { // Is NULL possible for value.string_? No. char const* str; char const* end; bool ok = value.getString(&str, &end); - if (ok) pushValue(valueToQuotedStringN(str, static_cast(end-str))); - else pushValue(""); + if (ok) + pushValue(valueToQuotedStringN(str, static_cast(end - str))); + else + pushValue(""); break; } case booleanValue: @@ -4603,9 +4543,9 @@ void StyledWriter::writeValue(const Value& value) { else { writeWithIndent("{"); indent(); - Value::Members::iterator it = members.begin(); + auto it = members.begin(); for (;;) { - const JSONCPP_STRING& name = *it; + const String& name = *it; const Value& childValue = value[name]; writeCommentBeforeValue(childValue); writeWithIndent(valueToQuotedString(name.c_str())); @@ -4675,7 +4615,7 @@ bool StyledWriter::isMultilineArray(const Value& value) { for (ArrayIndex index = 0; index < size && !isMultiLine; ++index) { const Value& childValue = value[index]; isMultiLine = ((childValue.isArray() || childValue.isObject()) && - childValue.size() > 0); + !childValue.empty()); } if (!isMultiLine) // check if line length > max line length { @@ -4695,7 +4635,7 @@ bool StyledWriter::isMultilineArray(const Value& value) { return isMultiLine; } -void StyledWriter::pushValue(const JSONCPP_STRING& value) { +void StyledWriter::pushValue(const String& value) { if (addChildValues_) childValues_.push_back(value); else @@ -4713,12 +4653,12 @@ void StyledWriter::writeIndent() { document_ += indentString_; } -void StyledWriter::writeWithIndent(const JSONCPP_STRING& value) { +void StyledWriter::writeWithIndent(const String& value) { writeIndent(); document_ += value; } -void StyledWriter::indent() { indentString_ += JSONCPP_STRING(indentSize_, ' '); } +void StyledWriter::indent() { indentString_ += String(indentSize_, ' '); } void StyledWriter::unindent() { assert(indentString_.size() >= indentSize_); @@ -4729,20 +4669,19 @@ void StyledWriter::writeCommentBeforeValue(const Value& root) { if (!root.hasComment(commentBefore)) return; - document_ += "\n"; + document_ += '\n'; writeIndent(); - const JSONCPP_STRING& comment = root.getComment(commentBefore); - JSONCPP_STRING::const_iterator iter = comment.begin(); + const String& comment = root.getComment(commentBefore); + String::const_iterator iter = comment.begin(); while (iter != comment.end()) { document_ += *iter; - if (*iter == '\n' && - ((iter+1) != comment.end() && *(iter + 1) == '/')) + if (*iter == '\n' && ((iter + 1) != comment.end() && *(iter + 1) == '/')) writeIndent(); ++iter; } // Comments are stripped of trailing newlines, so add one here - document_ += "\n"; + document_ += '\n'; } void StyledWriter::writeCommentAfterValueOnSameLine(const Value& root) { @@ -4750,9 +4689,9 @@ void StyledWriter::writeCommentAfterValueOnSameLine(const Value& root) { document_ += " " + root.getComment(commentAfterOnSameLine); if (root.hasComment(commentAfter)) { - document_ += "\n"; + document_ += '\n'; document_ += root.getComment(commentAfter); - document_ += "\n"; + document_ += '\n'; } } @@ -4765,22 +4704,23 @@ bool StyledWriter::hasCommentForValue(const Value& value) { // Class StyledStreamWriter // ////////////////////////////////////////////////////////////////// -StyledStreamWriter::StyledStreamWriter(JSONCPP_STRING indentation) - : document_(NULL), rightMargin_(74), indentation_(indentation), - addChildValues_() {} +StyledStreamWriter::StyledStreamWriter(String indentation) + : document_(nullptr), indentation_(std::move(indentation)), + addChildValues_(), indented_(false) {} -void StyledStreamWriter::write(JSONCPP_OSTREAM& out, const Value& root) { +void StyledStreamWriter::write(OStream& out, const Value& root) { document_ = &out; addChildValues_ = false; indentString_.clear(); indented_ = true; writeCommentBeforeValue(root); - if (!indented_) writeIndent(); + if (!indented_) + writeIndent(); indented_ = true; writeValue(root); writeCommentAfterValueOnSameLine(root); *document_ << "\n"; - document_ = NULL; // Forget the stream, for safety. + document_ = nullptr; // Forget the stream, for safety. } void StyledStreamWriter::writeValue(const Value& value) { @@ -4797,14 +4737,15 @@ void StyledStreamWriter::writeValue(const Value& value) { case realValue: pushValue(valueToString(value.asDouble())); break; - case stringValue: - { + case stringValue: { // Is NULL possible for value.string_? No. char const* str; char const* end; bool ok = value.getString(&str, &end); - if (ok) pushValue(valueToQuotedStringN(str, static_cast(end-str))); - else pushValue(""); + if (ok) + pushValue(valueToQuotedStringN(str, static_cast(end - str))); + else + pushValue(""); break; } case booleanValue: @@ -4820,9 +4761,9 @@ void StyledStreamWriter::writeValue(const Value& value) { else { writeWithIndent("{"); indent(); - Value::Members::iterator it = members.begin(); + auto it = members.begin(); for (;;) { - const JSONCPP_STRING& name = *it; + const String& name = *it; const Value& childValue = value[name]; writeCommentBeforeValue(childValue); writeWithIndent(valueToQuotedString(name.c_str())); @@ -4859,7 +4800,8 @@ void StyledStreamWriter::writeArrayValue(const Value& value) { if (hasChildValue) writeWithIndent(childValues_[index]); else { - if (!indented_) writeIndent(); + if (!indented_) + writeIndent(); indented_ = true; writeValue(childValue); indented_ = false; @@ -4894,7 +4836,7 @@ bool StyledStreamWriter::isMultilineArray(const Value& value) { for (ArrayIndex index = 0; index < size && !isMultiLine; ++index) { const Value& childValue = value[index]; isMultiLine = ((childValue.isArray() || childValue.isObject()) && - childValue.size() > 0); + !childValue.empty()); } if (!isMultiLine) // check if line length > max line length { @@ -4914,7 +4856,7 @@ bool StyledStreamWriter::isMultilineArray(const Value& value) { return isMultiLine; } -void StyledStreamWriter::pushValue(const JSONCPP_STRING& value) { +void StyledStreamWriter::pushValue(const String& value) { if (addChildValues_) childValues_.push_back(value); else @@ -4929,8 +4871,9 @@ void StyledStreamWriter::writeIndent() { *document_ << '\n' << indentString_; } -void StyledStreamWriter::writeWithIndent(const JSONCPP_STRING& value) { - if (!indented_) writeIndent(); +void StyledStreamWriter::writeWithIndent(const String& value) { + if (!indented_) + writeIndent(); *document_ << value; indented_ = false; } @@ -4946,13 +4889,13 @@ void StyledStreamWriter::writeCommentBeforeValue(const Value& root) { if (!root.hasComment(commentBefore)) return; - if (!indented_) writeIndent(); - const JSONCPP_STRING& comment = root.getComment(commentBefore); - JSONCPP_STRING::const_iterator iter = comment.begin(); + if (!indented_) + writeIndent(); + const String& comment = root.getComment(commentBefore); + String::const_iterator iter = comment.begin(); while (iter != comment.end()) { *document_ << *iter; - if (*iter == '\n' && - ((iter+1) != comment.end() && *(iter + 1) == '/')) + if (*iter == '\n' && ((iter + 1) != comment.end() && *(iter + 1) == '/')) // writeIndent(); // would include newline *document_ << indentString_; ++iter; @@ -4984,84 +4927,73 @@ bool StyledStreamWriter::hasCommentForValue(const Value& value) { struct CommentStyle { /// Decide whether to write comments. enum Enum { - None, ///< Drop all comments. - Most, ///< Recover odd behavior of previous versions (not implemented yet). - All ///< Keep all comments. + None, ///< Drop all comments. + Most, ///< Recover odd behavior of previous versions (not implemented yet). + All ///< Keep all comments. }; }; -struct BuiltStyledStreamWriter : public StreamWriter -{ - BuiltStyledStreamWriter( - JSONCPP_STRING const& indentation, - CommentStyle::Enum cs, - JSONCPP_STRING const& colonSymbol, - JSONCPP_STRING const& nullSymbol, - JSONCPP_STRING const& endingLineFeedSymbol, - bool useSpecialFloats, - unsigned int precision); - int write(Value const& root, JSONCPP_OSTREAM* sout) JSONCPP_OVERRIDE; +struct BuiltStyledStreamWriter : public StreamWriter { + BuiltStyledStreamWriter(String indentation, CommentStyle::Enum cs, + String colonSymbol, String nullSymbol, + String endingLineFeedSymbol, bool useSpecialFloats, + bool emitUTF8, unsigned int precision, + PrecisionType precisionType); + int write(Value const& root, OStream* sout) override; + private: void writeValue(Value const& value); void writeArrayValue(Value const& value); bool isMultilineArray(Value const& value); - void pushValue(JSONCPP_STRING const& value); + void pushValue(String const& value); void writeIndent(); - void writeWithIndent(JSONCPP_STRING const& value); + void writeWithIndent(String const& value); void indent(); void unindent(); void writeCommentBeforeValue(Value const& root); void writeCommentAfterValueOnSameLine(Value const& root); static bool hasCommentForValue(const Value& value); - typedef std::vector ChildValues; + using ChildValues = std::vector; ChildValues childValues_; - JSONCPP_STRING indentString_; + String indentString_; unsigned int rightMargin_; - JSONCPP_STRING indentation_; + String indentation_; CommentStyle::Enum cs_; - JSONCPP_STRING colonSymbol_; - JSONCPP_STRING nullSymbol_; - JSONCPP_STRING endingLineFeedSymbol_; + String colonSymbol_; + String nullSymbol_; + String endingLineFeedSymbol_; bool addChildValues_ : 1; bool indented_ : 1; bool useSpecialFloats_ : 1; + bool emitUTF8_ : 1; unsigned int precision_; + PrecisionType precisionType_; }; BuiltStyledStreamWriter::BuiltStyledStreamWriter( - JSONCPP_STRING const& indentation, - CommentStyle::Enum cs, - JSONCPP_STRING const& colonSymbol, - JSONCPP_STRING const& nullSymbol, - JSONCPP_STRING const& endingLineFeedSymbol, - bool useSpecialFloats, - unsigned int precision) - : rightMargin_(74) - , indentation_(indentation) - , cs_(cs) - , colonSymbol_(colonSymbol) - , nullSymbol_(nullSymbol) - , endingLineFeedSymbol_(endingLineFeedSymbol) - , addChildValues_(false) - , indented_(false) - , useSpecialFloats_(useSpecialFloats) - , precision_(precision) -{ -} -int BuiltStyledStreamWriter::write(Value const& root, JSONCPP_OSTREAM* sout) -{ + String indentation, CommentStyle::Enum cs, String colonSymbol, + String nullSymbol, String endingLineFeedSymbol, bool useSpecialFloats, + bool emitUTF8, unsigned int precision, PrecisionType precisionType) + : rightMargin_(74), indentation_(std::move(indentation)), cs_(cs), + colonSymbol_(std::move(colonSymbol)), nullSymbol_(std::move(nullSymbol)), + endingLineFeedSymbol_(std::move(endingLineFeedSymbol)), + addChildValues_(false), indented_(false), + useSpecialFloats_(useSpecialFloats), emitUTF8_(emitUTF8), + precision_(precision), precisionType_(precisionType) {} +int BuiltStyledStreamWriter::write(Value const& root, OStream* sout) { sout_ = sout; addChildValues_ = false; indented_ = true; indentString_.clear(); writeCommentBeforeValue(root); - if (!indented_) writeIndent(); + if (!indented_) + writeIndent(); indented_ = true; writeValue(root); writeCommentAfterValueOnSameLine(root); *sout_ << endingLineFeedSymbol_; - sout_ = NULL; + sout_ = nullptr; return 0; } void BuiltStyledStreamWriter::writeValue(Value const& value) { @@ -5076,16 +5008,19 @@ void BuiltStyledStreamWriter::writeValue(Value const& value) { pushValue(valueToString(value.asLargestUInt())); break; case realValue: - pushValue(valueToString(value.asDouble(), useSpecialFloats_, precision_)); + pushValue(valueToString(value.asDouble(), useSpecialFloats_, precision_, + precisionType_)); break; - case stringValue: - { + case stringValue: { // Is NULL is possible for value.string_? No. char const* str; char const* end; bool ok = value.getString(&str, &end); - if (ok) pushValue(valueToQuotedStringN(str, static_cast(end-str))); - else pushValue(""); + if (ok) + pushValue(valueToQuotedStringN(str, static_cast(end - str), + emitUTF8_)); + else + pushValue(""); break; } case booleanValue: @@ -5101,12 +5036,13 @@ void BuiltStyledStreamWriter::writeValue(Value const& value) { else { writeWithIndent("{"); indent(); - Value::Members::iterator it = members.begin(); + auto it = members.begin(); for (;;) { - JSONCPP_STRING const& name = *it; + String const& name = *it; Value const& childValue = value[name]; writeCommentBeforeValue(childValue); - writeWithIndent(valueToQuotedStringN(name.data(), static_cast(name.length()))); + writeWithIndent(valueToQuotedStringN( + name.data(), static_cast(name.length()), emitUTF8_)); *sout_ << colonSymbol_; writeValue(childValue); if (++it == members.end()) { @@ -5140,7 +5076,8 @@ void BuiltStyledStreamWriter::writeArrayValue(Value const& value) { if (hasChildValue) writeWithIndent(childValues_[index]); else { - if (!indented_) writeIndent(); + if (!indented_) + writeIndent(); indented_ = true; writeValue(childValue); indented_ = false; @@ -5158,13 +5095,15 @@ void BuiltStyledStreamWriter::writeArrayValue(Value const& value) { { assert(childValues_.size() == size); *sout_ << "["; - if (!indentation_.empty()) *sout_ << " "; + if (!indentation_.empty()) + *sout_ << " "; for (unsigned index = 0; index < size; ++index) { if (index > 0) *sout_ << ((!indentation_.empty()) ? ", " : ","); *sout_ << childValues_[index]; } - if (!indentation_.empty()) *sout_ << " "; + if (!indentation_.empty()) + *sout_ << " "; *sout_ << "]"; } } @@ -5177,7 +5116,7 @@ bool BuiltStyledStreamWriter::isMultilineArray(Value const& value) { for (ArrayIndex index = 0; index < size && !isMultiLine; ++index) { Value const& childValue = value[index]; isMultiLine = ((childValue.isArray() || childValue.isObject()) && - childValue.size() > 0); + !childValue.empty()); } if (!isMultiLine) // check if line length > max line length { @@ -5197,7 +5136,7 @@ bool BuiltStyledStreamWriter::isMultilineArray(Value const& value) { return isMultiLine; } -void BuiltStyledStreamWriter::pushValue(JSONCPP_STRING const& value) { +void BuiltStyledStreamWriter::pushValue(String const& value) { if (addChildValues_) childValues_.push_back(value); else @@ -5216,8 +5155,9 @@ void BuiltStyledStreamWriter::writeIndent() { } } -void BuiltStyledStreamWriter::writeWithIndent(JSONCPP_STRING const& value) { - if (!indented_) writeIndent(); +void BuiltStyledStreamWriter::writeWithIndent(String const& value) { + if (!indented_) + writeIndent(); *sout_ << value; indented_ = false; } @@ -5230,17 +5170,18 @@ void BuiltStyledStreamWriter::unindent() { } void BuiltStyledStreamWriter::writeCommentBeforeValue(Value const& root) { - if (cs_ == CommentStyle::None) return; + if (cs_ == CommentStyle::None) + return; if (!root.hasComment(commentBefore)) return; - if (!indented_) writeIndent(); - const JSONCPP_STRING& comment = root.getComment(commentBefore); - JSONCPP_STRING::const_iterator iter = comment.begin(); + if (!indented_) + writeIndent(); + const String& comment = root.getComment(commentBefore); + String::const_iterator iter = comment.begin(); while (iter != comment.end()) { *sout_ << *iter; - if (*iter == '\n' && - ((iter+1) != comment.end() && *(iter + 1) == '/')) + if (*iter == '\n' && ((iter + 1) != comment.end() && *(iter + 1) == '/')) // writeIndent(); // would write extra newline *sout_ << indentString_; ++iter; @@ -5248,8 +5189,10 @@ void BuiltStyledStreamWriter::writeCommentBeforeValue(Value const& root) { indented_ = false; } -void BuiltStyledStreamWriter::writeCommentAfterValueOnSameLine(Value const& root) { - if (cs_ == CommentStyle::None) return; +void BuiltStyledStreamWriter::writeCommentAfterValueOnSameLine( + Value const& root) { + if (cs_ == CommentStyle::None) + return; if (root.hasComment(commentAfterOnSameLine)) *sout_ << " " + root.getComment(commentAfterOnSameLine); @@ -5269,28 +5212,19 @@ bool BuiltStyledStreamWriter::hasCommentForValue(const Value& value) { /////////////// // StreamWriter -StreamWriter::StreamWriter() - : sout_(NULL) -{ -} -StreamWriter::~StreamWriter() -{ -} -StreamWriter::Factory::~Factory() -{} -StreamWriterBuilder::StreamWriterBuilder() -{ - setDefaults(&settings_); -} -StreamWriterBuilder::~StreamWriterBuilder() -{} -StreamWriter* StreamWriterBuilder::newStreamWriter() const -{ - JSONCPP_STRING indentation = settings_["indentation"].asString(); - JSONCPP_STRING cs_str = settings_["commentStyle"].asString(); - bool eyc = settings_["enableYAMLCompatibility"].asBool(); - bool dnp = settings_["dropNullPlaceholders"].asBool(); - bool usf = settings_["useSpecialFloats"].asBool(); +StreamWriter::StreamWriter() : sout_(nullptr) {} +StreamWriter::~StreamWriter() = default; +StreamWriter::Factory::~Factory() = default; +StreamWriterBuilder::StreamWriterBuilder() { setDefaults(&settings_); } +StreamWriterBuilder::~StreamWriterBuilder() = default; +StreamWriter* StreamWriterBuilder::newStreamWriter() const { + const String indentation = settings_["indentation"].asString(); + const String cs_str = settings_["commentStyle"].asString(); + const String pt_str = settings_["precisionType"].asString(); + const bool eyc = settings_["enableYAMLCompatibility"].asBool(); + const bool dnp = settings_["dropNullPlaceholders"].asBool(); + const bool usf = settings_["useSpecialFloats"].asBool(); + const bool emitUTF8 = settings_["emitUTF8"].asBool(); unsigned int pre = settings_["precision"].asUInt(); CommentStyle::Enum cs = CommentStyle::All; if (cs_str == "All") { @@ -5300,74 +5234,80 @@ StreamWriter* StreamWriterBuilder::newStreamWriter() const } else { throwRuntimeError("commentStyle must be 'All' or 'None'"); } - JSONCPP_STRING colonSymbol = " : "; + PrecisionType precisionType(significantDigits); + if (pt_str == "significant") { + precisionType = PrecisionType::significantDigits; + } else if (pt_str == "decimal") { + precisionType = PrecisionType::decimalPlaces; + } else { + throwRuntimeError("precisionType must be 'significant' or 'decimal'"); + } + String colonSymbol = " : "; if (eyc) { colonSymbol = ": "; } else if (indentation.empty()) { colonSymbol = ":"; } - JSONCPP_STRING nullSymbol = "null"; + String nullSymbol = "null"; if (dnp) { nullSymbol.clear(); } - if (pre > 17) pre = 17; - JSONCPP_STRING endingLineFeedSymbol; - return new BuiltStyledStreamWriter( - indentation, cs, - colonSymbol, nullSymbol, endingLineFeedSymbol, usf, pre); + if (pre > 17) + pre = 17; + String endingLineFeedSymbol; + return new BuiltStyledStreamWriter(indentation, cs, colonSymbol, nullSymbol, + endingLineFeedSymbol, usf, emitUTF8, pre, + precisionType); } -static void getValidWriterKeys(std::set* valid_keys) -{ - valid_keys->clear(); - valid_keys->insert("indentation"); - valid_keys->insert("commentStyle"); - valid_keys->insert("enableYAMLCompatibility"); - valid_keys->insert("dropNullPlaceholders"); - valid_keys->insert("useSpecialFloats"); - valid_keys->insert("precision"); -} -bool StreamWriterBuilder::validate(Json::Value* invalid) const -{ - Json::Value my_invalid; - if (!invalid) invalid = &my_invalid; // so we do not need to test for NULL - Json::Value& inv = *invalid; - std::set valid_keys; - getValidWriterKeys(&valid_keys); - Value::Members keys = settings_.getMemberNames(); - size_t n = keys.size(); - for (size_t i = 0; i < n; ++i) { - JSONCPP_STRING const& key = keys[i]; - if (valid_keys.find(key) == valid_keys.end()) { - inv[key] = settings_[key]; - } + +bool StreamWriterBuilder::validate(Json::Value* invalid) const { + static const auto& valid_keys = *new std::set{ + "indentation", + "commentStyle", + "enableYAMLCompatibility", + "dropNullPlaceholders", + "useSpecialFloats", + "emitUTF8", + "precision", + "precisionType", + }; + for (auto si = settings_.begin(); si != settings_.end(); ++si) { + auto key = si.name(); + if (valid_keys.count(key)) + continue; + if (invalid) + (*invalid)[std::move(key)] = *si; + else + return false; } - return 0u == inv.size(); + return invalid ? invalid->empty() : true; } -Value& StreamWriterBuilder::operator[](JSONCPP_STRING key) -{ + +Value& StreamWriterBuilder::operator[](const String& key) { return settings_[key]; } // static -void StreamWriterBuilder::setDefaults(Json::Value* settings) -{ +void StreamWriterBuilder::setDefaults(Json::Value* settings) { //! [StreamWriterBuilderDefaults] (*settings)["commentStyle"] = "All"; (*settings)["indentation"] = "\t"; (*settings)["enableYAMLCompatibility"] = false; (*settings)["dropNullPlaceholders"] = false; (*settings)["useSpecialFloats"] = false; + (*settings)["emitUTF8"] = false; (*settings)["precision"] = 17; + (*settings)["precisionType"] = "significant"; //! [StreamWriterBuilderDefaults] } -JSONCPP_STRING writeString(StreamWriter::Factory const& builder, Value const& root) { - JSONCPP_OSTRINGSTREAM sout; - StreamWriterPtr const writer(builder.newStreamWriter()); +String writeString(StreamWriter::Factory const& factory, Value const& root) { + OStringStream sout; + StreamWriterPtr const writer(factory.newStreamWriter()); writer->write(root, &sout); return sout.str(); } -JSONCPP_OSTREAM& operator<<(JSONCPP_OSTREAM& sout, Value const& root) { +OStream& operator<<(OStream& sout, Value const& root) { StreamWriterBuilder builder; StreamWriterPtr const writer(builder.newStreamWriter()); writer->write(root, &sout); From 5c0a57f6068000b0414cf2a3ef76a8bf4852b379 Mon Sep 17 00:00:00 2001 From: Maksim Date: Wed, 21 Oct 2020 21:42:40 +0200 Subject: [PATCH 149/266] Fix Media... 0% on loading screen (#9478) --- src/client/game.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/client/game.cpp b/src/client/game.cpp index a7e367ae2..0380c1661 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -1641,7 +1641,10 @@ bool Game::getServerContent(bool *aborted) std::stringstream message; std::fixed(message); message.precision(0); - message << gettext("Media...") << " " << (client->mediaReceiveProgress()*100) << "%"; + float receive = client->mediaReceiveProgress() * 100; + message << gettext("Media..."); + if (receive > 0) + message << " " << receive << "%"; message.precision(2); if ((USE_CURL == 0) || From 9d370b78da61997f46f228cb3e4b54b8ee9295ff Mon Sep 17 00:00:00 2001 From: DS Date: Wed, 21 Oct 2020 22:05:32 +0200 Subject: [PATCH 150/266] Add documentation to builtin core.run_callbacks (#10494) --- builtin/client/register.lua | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/builtin/client/register.lua b/builtin/client/register.lua index acd36ab36..27a6b02d9 100644 --- a/builtin/client/register.lua +++ b/builtin/client/register.lua @@ -4,6 +4,13 @@ core.callback_origins = {} local getinfo = debug.getinfo debug.getinfo = nil +--- Runs given callbacks. +-- +-- Note: this function is also called from C++ +-- @tparam table callbacks a table with registered callbacks, like `core.registered_on_*` +-- @tparam number mode a RunCallbacksMode, as defined in src/script/common/c_internal.h +-- @param ... arguments for the callback +-- @return depends on mode function core.run_callbacks(callbacks, mode, ...) assert(type(callbacks) == "table") local cb_len = #callbacks From f236476af96cfc468d0a76a2f8215f566d1b7de1 Mon Sep 17 00:00:00 2001 From: Josiah VanderZee Date: Wed, 21 Oct 2020 18:53:38 -0500 Subject: [PATCH 151/266] Fix errors in cheatMenu. --- src/gui/cheatMenu.cpp | 16 ++++++++-------- src/gui/cheatMenu.h | 8 ++++---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/gui/cheatMenu.cpp b/src/gui/cheatMenu.cpp index e37f584fd..9949becc3 100644 --- a/src/gui/cheatMenu.cpp +++ b/src/gui/cheatMenu.cpp @@ -78,7 +78,7 @@ void CheatMenu::drawEntry(video::IVideoDriver *driver, std::string name, } void CheatMenu::draw(video::IVideoDriver *driver, bool show_debug) - +{ ClientScripting *script{ getScript() }; if (!script || !script->m_cheats_loaded) return; @@ -89,16 +89,16 @@ void CheatMenu::draw(video::IVideoDriver *driver, bool show_debug) CHEAT_MENU_ENTRY_TYPE_HEAD); int category_count = 0; - for (const auto &menu_item : m_cheat_categories) { + for (const auto &menu_item : script->m_cheat_categories) { bool is_selected = category_count == m_selected_category; - drawEntry(driver, menu_item.m_name, category_count, 0, is_selected, + drawEntry(driver, menu_item->m_name, category_count, 0, is_selected, false, CHEAT_MENU_ENTRY_TYPE_CATEGORY); if (is_selected && m_cheat_layer) { int cheat_count = 0; - for (const auto &sub_menu_item : menu_item.m_cheats) { - drawEntry(driver, sub_menu_item.m_name, category_count, cheat_count, - cheat_count == m_selected_cheat, - sub_menu_item.is_enabled()); + for (const auto &sub_menu_item : menu_item->m_cheats) { + drawEntry(driver, sub_menu_item->m_name, category_count, + cheat_count, cheat_count == m_selected_cheat, + sub_menu_item->is_enabled()); cheat_count++; } } @@ -166,4 +166,4 @@ void CheatMenu::selectConfirm() if (m_cheat_layer) script->toggle_cheat(script->m_cheat_categories[m_selected_category] ->m_cheats[m_selected_cheat]); -} \ No newline at end of file +} diff --git a/src/gui/cheatMenu.h b/src/gui/cheatMenu.h index 018b63ae0..dfe92798b 100644 --- a/src/gui/cheatMenu.h +++ b/src/gui/cheatMenu.h @@ -16,7 +16,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once +#include "client/client.h" #include "irrlichttypes_extrabloated.h" +#include "script/scripting_client.h" #include #include @@ -25,8 +27,6 @@ with this program; if not, write to the Free Software Foundation, Inc., if (!script || !script->m_cheats_loaded) \ return; -class Client; - enum CheatMenuEntryType { CHEAT_MENU_ENTRY_TYPE_HEAD, @@ -39,7 +39,7 @@ class CheatMenu public: CheatMenu(Client *client); - Client* getScript() + ClientScripting *getScript() { return m_client->getScript(); } @@ -76,4 +76,4 @@ private: gui::IGUIFont *m_font = nullptr; v2u32 m_fontsize; -}; \ No newline at end of file +}; From b29d6bc196b3a6bafa1b200e8944cf277b0bc9c1 Mon Sep 17 00:00:00 2001 From: realOneplustwo Date: Wed, 21 Oct 2020 17:49:17 -0700 Subject: [PATCH 152/266] Make cheat menu color and font configurable via settings --- builtin/settingtypes.txt | 25 ++++++++++++++++++++++ src/defaultsettings.cpp | 7 +++++++ src/gui/cheatMenu.cpp | 45 +++++++++++++++++++++++++++++++++++++++- src/gui/cheatMenu.h | 3 +++ 4 files changed, 79 insertions(+), 1 deletion(-) diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index f30a16789..ebd0ad621 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -2298,3 +2298,28 @@ autotnt (PlaceOnTop) bool false replace (Replace) bool false crystal_pvp (CrystalPvP) bool false + +[Cheat Menu] + +# Font to use for cheat menu +cheat_menu_font (MenuFont) enum FM_Standard, FM_Mono, FM_Fallback, FM_Simple, FM_SimpleMono, FM_MaxMode, FM_Unspecified + +# (RGB value) +m_bg_color (Cell background color) v3f 45 45 68 + +m_bg_color_alpha (Cell background color alpha) int 173 + +# (RGB value) +m_active_bg_color (Active cell background color) v3f 0 0 0 + +m_active_bg_color_alpha (Active cell background color alpha) int 210 + +# (RGB value) +m_font_color (Font color) v3f 255 255 255 + +m_font_color_alpha (Font color alpha) int 195 + +# (RGB value) +m_selected_font_color (Selected font color) v3f 255 255 255 + +m_selected_font_color_alpha (Selected font color alpha) int 235 \ No newline at end of file diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 2899f2509..525f94678 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -63,6 +63,13 @@ void set_default_settings(Settings *settings) settings->setDefault("max_out_chat_queue_size", "20"); settings->setDefault("pause_on_lost_focus", "false"); settings->setDefault("enable_register_confirmation", "true"); + + // Cheat Menu + settings->setDefault("cheat_menu_font", "FM_Mono"); + settings->setDefault("m_bg_color_alpha", "173"); + settings->setDefault("m_active_bg_color_alpha", "210"); + settings->setDefault("m_font_color_alpha", "195"); + settings->setDefault("m_selected_font_color_alpha", "235"); // Cheats settings->setDefault("xray", "false"); diff --git a/src/gui/cheatMenu.cpp b/src/gui/cheatMenu.cpp index 548ddea73..7839caaad 100644 --- a/src/gui/cheatMenu.cpp +++ b/src/gui/cheatMenu.cpp @@ -18,11 +18,54 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "script/scripting_client.h" #include "client/client.h" #include "client/fontengine.h" +#include "settings.h" #include +FontMode fontStringToEnum(std::string str) { + if (str == "FM_Standard") + return FM_Standard; + else if (str == "FM_Mono") + return FM_Mono; + else if (str == "FM_Fallback") + return FM_Fallback; + else if (str == "FM_Simple") + return FM_Simple; + else if (str == "FM_SimpleMono") + return FM_SimpleMono; + else if (str == "FM_MaxMode") + return FM_MaxMode; + else if (str == "FM_Unspecified") + return FM_Unspecified; + else + return FM_Standard; +} + CheatMenu::CheatMenu(Client *client) : m_client(client) { - m_font = g_fontengine->getFont(FONT_SIZE_UNSPECIFIED, FM_Mono); + FontMode fontMode = fontStringToEnum(g_settings->get("cheat_menu_font")); + irr::core::vector3df bg_color; + irr::core::vector3df active_bg_color; + irr::core::vector3df font_color; + irr::core::vector3df selected_font_color; + + g_settings->getV3FNoEx("m_bg_color", bg_color); + g_settings->getV3FNoEx("m_active_bg_color", active_bg_color); + g_settings->getV3FNoEx("m_font_color", font_color); + g_settings->getV3FNoEx("m_selected_font_color", selected_font_color); + + m_bg_color = video::SColor(g_settings->getU32("m_bg_color_alpha"), + bg_color.X, bg_color.Y, bg_color.Z); + + m_active_bg_color = video::SColor(g_settings->getU32("m_active_bg_color_alpha"), + active_bg_color.X, active_bg_color.Y, active_bg_color.Z); + + m_font_color = video::SColor(g_settings->getU32("m_font_color_alpha"), + font_color.X, font_color.Y, font_color.Z); + + m_selected_font_color = video::SColor(g_settings->getU32("m_selected_font_color_alpha"), + selected_font_color.X, selected_font_color.Y, selected_font_color.Z); + + m_font = g_fontengine->getFont(FONT_SIZE_UNSPECIFIED, fontMode); if (!m_font) { errorstream << "CheatMenu: Unable to load fallback font" << std::endl; diff --git a/src/gui/cheatMenu.h b/src/gui/cheatMenu.h index 4ffaa177d..350bf9ac3 100644 --- a/src/gui/cheatMenu.h +++ b/src/gui/cheatMenu.h @@ -17,6 +17,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once #include "irrlichttypes_extrabloated.h" +#include "settings.h" #include #include @@ -67,6 +68,8 @@ private: video::SColor m_font_color = video::SColor(195, 255, 255, 255); video::SColor m_selected_font_color = video::SColor(235, 255, 255, 255); + FontMode fontStringToEnum(std::string str); + Client *m_client; gui::IGUIFont *m_font = nullptr; From 3bdb843f2c11dffd9db6f899c0ba77f52deac290 Mon Sep 17 00:00:00 2001 From: realOneplustwo Date: Wed, 21 Oct 2020 18:00:43 -0700 Subject: [PATCH 153/266] Make cheat menu color and font configurable via settings --- src/gui/cheatMenu.cpp | 44 ++++++++++++++++++++++++++++++++++++++++++- src/gui/cheatMenu.h | 2 ++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/src/gui/cheatMenu.cpp b/src/gui/cheatMenu.cpp index 3f44267e1..27a243357 100644 --- a/src/gui/cheatMenu.cpp +++ b/src/gui/cheatMenu.cpp @@ -20,9 +20,51 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "client/fontengine.h" #include +FontMode fontStringToEnum(std::string str) { + if (str == "FM_Standard") + return FM_Standard; + else if (str == "FM_Mono") + return FM_Mono; + else if (str == "FM_Fallback") + return FM_Fallback; + else if (str == "FM_Simple") + return FM_Simple; + else if (str == "FM_SimpleMono") + return FM_SimpleMono; + else if (str == "FM_MaxMode") + return FM_MaxMode; + else if (str == "FM_Unspecified") + return FM_Unspecified; + else + return FM_Standard; +} + CheatMenu::CheatMenu(Client *client) : m_client(client) { - m_font = g_fontengine->getFont(FONT_SIZE_UNSPECIFIED, FM_Mono); + FontMode fontMode = fontStringToEnum(g_settings->get("cheat_menu_font")); + irr::core::vector3df bg_color; + irr::core::vector3df active_bg_color; + irr::core::vector3df font_color; + irr::core::vector3df selected_font_color; + + g_settings->getV3FNoEx("m_bg_color", bg_color); + g_settings->getV3FNoEx("m_active_bg_color", active_bg_color); + g_settings->getV3FNoEx("m_font_color", font_color); + g_settings->getV3FNoEx("m_selected_font_color", selected_font_color); + + m_bg_color = video::SColor(g_settings->getU32("m_bg_color_alpha"), + bg_color.X, bg_color.Y, bg_color.Z); + + m_active_bg_color = video::SColor(g_settings->getU32("m_active_bg_color_alpha"), + active_bg_color.X, active_bg_color.Y, active_bg_color.Z); + + m_font_color = video::SColor(g_settings->getU32("m_font_color_alpha"), + font_color.X, font_color.Y, font_color.Z); + + m_selected_font_color = video::SColor(g_settings->getU32("m_selected_font_color_alpha"), + selected_font_color.X, selected_font_color.Y, selected_font_color.Z); + + m_font = g_fontengine->getFont(FONT_SIZE_UNSPECIFIED, fontMode); if (!m_font) { errorstream << "CheatMenu: Unable to load fallback font" << std::endl; diff --git a/src/gui/cheatMenu.h b/src/gui/cheatMenu.h index 6ff8d67c6..c25af5ed2 100644 --- a/src/gui/cheatMenu.h +++ b/src/gui/cheatMenu.h @@ -72,6 +72,8 @@ private: video::SColor m_font_color = video::SColor(195, 255, 255, 255); video::SColor m_selected_font_color = video::SColor(235, 255, 255, 255); + FontMode fontStringToEnum(std::string str); + Client *m_client; gui::IGUIFont *m_font = nullptr; From 62cf9b46600ff1f1f2733401f0a670d703fee136 Mon Sep 17 00:00:00 2001 From: realOneplustwo Date: Wed, 21 Oct 2020 19:33:31 -0700 Subject: [PATCH 154/266] Fix compile error --- src/gui/cheatMenu.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui/cheatMenu.cpp b/src/gui/cheatMenu.cpp index 27a243357..b7ce7d634 100644 --- a/src/gui/cheatMenu.cpp +++ b/src/gui/cheatMenu.cpp @@ -14,13 +14,13 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "cheatMenu.h" #include "script/scripting_client.h" #include "client/client.h" #include "client/fontengine.h" +#include "cheatMenu.h" #include -FontMode fontStringToEnum(std::string str) { +FontMode CheatMenu::fontStringToEnum(std::string str) { if (str == "FM_Standard") return FM_Standard; else if (str == "FM_Mono") From 33b2c5f5b1c565171296f26395d803b08f4575e9 Mon Sep 17 00:00:00 2001 From: Zughy <63455151+Zughy@users.noreply.github.com> Date: Thu, 22 Oct 2020 17:18:01 +0200 Subject: [PATCH 155/266] Clean up l_object.cpp (#10512) Co-authored-by: Zughy <4279489-marco_a@users.noreply.gitlab.com> --- doc/lua_api.txt | 48 +- src/script/lua_api/l_object.cpp | 1169 +++++++++++++++---------------- src/script/lua_api/l_object.h | 56 +- 3 files changed, 605 insertions(+), 668 deletions(-) diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 9a46c7b57..daf0da3d2 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -6217,18 +6217,18 @@ object you are working with still exists. * `rot` is a vector (radians). X is pitch (elevation), Y is yaw (heading) and Z is roll (bank). * `get_rotation()`: returns the rotation, a vector (radians) -* `set_yaw(radians)`: sets the yaw (heading). +* `set_yaw(yaw)`: sets the yaw in radians (heading). * `get_yaw()`: returns number in radians * `set_texture_mod(mod)` * Set a texture modifier to the base texture, for sprites and meshes. * When calling `set_texture_mod` again, the previous one is discarded. * `mod` the texture modifier. See [Texture modifiers]. * `get_texture_mod()` returns current texture modifier -* `set_sprite(p, num_frames, framelength, select_x_by_camera)` +* `set_sprite(start_frame, num_frames, framelength, select_x_by_camera)` * Specifies and starts a sprite animation * Animations iterate along the frame `y` position. - * `p`: {x=column number, y=row number}, the coordinate of the first frame - default: `{x=0, y=0}` + * `start_frame`: {x=column number, y=row number}, the coordinate of the + first frame, default: `{x=0, y=0}` * `num_frames`: Total frames in the texture, default: `1` * `framelength`: Time per animated frame in seconds, default: `0.2` * `select_x_by_camera`: Only for visual = `sprite`. Changes the frame `x` @@ -6391,8 +6391,8 @@ object you are working with still exists. nodes square) * `selected_mode` is the mode index to be selected after modes have been changed (0 is the first mode). -* `set_sky(parameters)` - * `parameters` is a table with the following optional fields: +* `set_sky(sky_parameters)` + * `sky_parameters` is a table with the following optional fields: * `base_color`: ColorSpec, changes fog in "skybox" and "plain". * `type`: Available types: * `"regular"`: Uses 0 textures, `base_color` ignored @@ -6436,8 +6436,8 @@ object you are working with still exists. * `get_sky()`: returns base_color, type, table of textures, clouds. * `get_sky_color()`: returns a table with the `sky_color` parameters as in `set_sky`. -* `set_sun(parameters)`: - * `parameters` is a table with the following optional fields: +* `set_sun(sun_parameters)`: + * `sun_parameters` is a table with the following optional fields: * `visible`: Boolean for whether the sun is visible. (default: `true`) * `texture`: A regular texture for the sun. Setting to `""` @@ -6451,8 +6451,8 @@ object you are working with still exists. * `scale`: Float controlling the overall size of the sun. (default: `1`) * `get_sun()`: returns a table with the current sun parameters as in `set_sun`. -* `set_moon(parameters)`: - * `parameters` is a table with the following optional fields: +* `set_moon(moon_parameters)`: + * `moon_parameters` is a table with the following optional fields: * `visible`: Boolean for whether the moon is visible. (default: `true`) * `texture`: A regular texture for the moon. Setting to `""` @@ -6462,8 +6462,8 @@ object you are working with still exists. * `scale`: Float controlling the overall size of the moon (default: `1`) * `get_moon()`: returns a table with the current moon parameters as in `set_moon`. -* `set_stars(parameters)`: - * `parameters` is a table with the following optional fields: +* `set_stars(star_parameters)`: + * `star_parameters` is a table with the following optional fields: * `visible`: Boolean for whether the stars are visible. (default: `true`) * `count`: Integer number to set the number of stars in @@ -6475,8 +6475,8 @@ object you are working with still exists. * `scale`: Float controlling the overall size of the stars (default: `1`) * `get_stars()`: returns a table with the current stars parameters as in `set_stars`. -* `set_clouds(parameters)`: set cloud parameters - * `parameters` is a table with the following optional fields: +* `set_clouds(cloud_parameters)`: set cloud parameters + * `cloud_parameters` is a table with the following optional fields: * `density`: from `0` (no clouds) to `1` (full clouds) (default `0.4`) * `color`: basic cloud color with alpha channel, ColorSpec (default `#fff0f0e5`). @@ -6494,21 +6494,17 @@ object you are working with still exists. amount. * `nil`: Disables override, defaulting to sunlight based on day-night cycle * `get_day_night_ratio()`: returns the ratio or nil if it isn't overridden -* `set_local_animation(stand/idle, walk, dig, walk+dig, frame_speed=frame_speed)`: - set animation for player model in third person view - - set_local_animation({x=0, y=79}, -- stand/idle animation key frames - {x=168, y=187}, -- walk animation key frames - {x=189, y=198}, -- dig animation key frames - {x=200, y=219}, -- walk+dig animation key frames - frame_speed=30) -- animation frame speed -* `get_local_animation()`: returns stand, walk, dig, dig+walk tables and +* `set_local_animation(idle, walk, dig, walk_while_dig, frame_speed)`: + set animation for player model in third person view. + * Every animation equals to a `{x=starting frame, y=ending frame}` table. + * `frame_speed` sets the animations frame speed. Default is 30. +* `get_local_animation()`: returns idle, walk, dig, walk_while_dig tables and `frame_speed`. -* `set_eye_offset({x=0,y=0,z=0},{x=0,y=0,z=0})`: defines offset value for - camera per player. +* `set_eye_offset(firstperson, thirdperson)`: defines offset vectors for camera + per player. * in first person view * in third person view (max. values `{x=-10/10,y=-10,15,z=-5/5}`) -* `get_eye_offset()`: returns `offset_first` and `offset_third` +* `get_eye_offset()`: returns first and third person offsets. * `send_mapblock(blockpos)`: * Sends a server-side loaded mapblock to the player. * Returns `false` if failed. diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index 96c8c687c..c3f0ec8e0 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -44,43 +44,44 @@ ObjectRef* ObjectRef::checkobject(lua_State *L, int narg) { luaL_checktype(L, narg, LUA_TUSERDATA); void *ud = luaL_checkudata(L, narg, className); - if (!ud) luaL_typerror(L, narg, className); + if (ud == nullptr) + luaL_typerror(L, narg, className); return *(ObjectRef**)ud; // unbox pointer } ServerActiveObject* ObjectRef::getobject(ObjectRef *ref) { - ServerActiveObject *co = ref->m_object; - if (co && co->isGone()) - return NULL; - return co; + ServerActiveObject *sao = ref->m_object; + if (sao && sao->isGone()) + return nullptr; + return sao; } LuaEntitySAO* ObjectRef::getluaobject(ObjectRef *ref) { - ServerActiveObject *obj = getobject(ref); - if (obj == NULL) - return NULL; - if (obj->getType() != ACTIVEOBJECT_TYPE_LUAENTITY) - return NULL; - return (LuaEntitySAO*)obj; + ServerActiveObject *sao = getobject(ref); + if (sao == nullptr) + return nullptr; + if (sao->getType() != ACTIVEOBJECT_TYPE_LUAENTITY) + return nullptr; + return (LuaEntitySAO*)sao; } PlayerSAO* ObjectRef::getplayersao(ObjectRef *ref) { - ServerActiveObject *obj = getobject(ref); - if (obj == NULL) - return NULL; - if (obj->getType() != ACTIVEOBJECT_TYPE_PLAYER) - return NULL; - return (PlayerSAO*)obj; + ServerActiveObject *sao = getobject(ref); + if (sao == nullptr) + return nullptr; + if (sao->getType() != ACTIVEOBJECT_TYPE_PLAYER) + return nullptr; + return (PlayerSAO*)sao; } RemotePlayer *ObjectRef::getplayer(ObjectRef *ref) { PlayerSAO *playersao = getplayersao(ref); - if (playersao == NULL) - return NULL; + if (playersao == nullptr) + return nullptr; return playersao->getPlayer(); } @@ -88,9 +89,8 @@ RemotePlayer *ObjectRef::getplayer(ObjectRef *ref) // garbage collector int ObjectRef::gc_object(lua_State *L) { - ObjectRef *o = *(ObjectRef **)(lua_touserdata(L, 1)); - //infostream<<"ObjectRef::gc_object: o="<getType() == ACTIVEOBJECT_TYPE_PLAYER) + if (sao->getType() == ACTIVEOBJECT_TYPE_PLAYER) return 0; - co->clearChildAttachments(); - co->clearParentAttachment(); + sao->clearChildAttachments(); + sao->clearParentAttachment(); - verbosestream << "ObjectRef::l_remove(): id=" << co->getId() << std::endl; - co->m_pending_removal = true; + verbosestream << "ObjectRef::l_remove(): id=" << sao->getId() << std::endl; + sao->m_pending_removal = true; return 0; } // get_pos(self) -// returns: {x=num, y=num, z=num} int ObjectRef::l_get_pos(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - ServerActiveObject *co = getobject(ref); - if (co == NULL) return 0; - push_v3f(L, co->getBasePosition() / BS); + ServerActiveObject *sao = getobject(ref); + if (sao == nullptr) + return 0; + + push_v3f(L, sao->getBasePosition() / BS); return 1; } @@ -131,28 +132,29 @@ int ObjectRef::l_set_pos(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - ServerActiveObject *co = getobject(ref); - if (co == NULL) return 0; - // pos + ServerActiveObject *sao = getobject(ref); + if (sao == nullptr) + return 0; + v3f pos = checkFloatPos(L, 2); - // Do it - co->setPos(pos); + + sao->setPos(pos); return 0; } -// move_to(self, pos, continuous=false) +// move_to(self, pos, continuous) int ObjectRef::l_move_to(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - ServerActiveObject *co = getobject(ref); - if (co == NULL) return 0; - // pos + ServerActiveObject *sao = getobject(ref); + if (sao == nullptr) + return 0; + v3f pos = checkFloatPos(L, 2); - // continuous bool continuous = readParam(L, 3); - // Do it - co->moveTo(pos, continuous); + + sao->moveTo(pos, continuous); return 0; } @@ -162,32 +164,28 @@ int ObjectRef::l_punch(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); ObjectRef *puncher_ref = checkobject(L, 2); - ServerActiveObject *co = getobject(ref); + ServerActiveObject *sao = getobject(ref); ServerActiveObject *puncher = getobject(puncher_ref); - if (!co || !puncher) + if (sao == nullptr || puncher == nullptr) return 0; - v3f dir; - if (lua_type(L, 5) != LUA_TTABLE) - dir = co->getBasePosition() - puncher->getBasePosition(); - else - dir = read_v3f(L, 5); - float time_from_last_punch = 1000000; - if (lua_isnumber(L, 3)) - time_from_last_punch = lua_tonumber(L, 3); - ToolCapabilities toolcap = read_tool_capabilities(L, 4); - dir.normalize(); - u16 src_original_hp = co->getHP(); + float time_from_last_punch = lua_isnil(L, 3) ? + 1000000.0f : readParam(L,3); + ToolCapabilities toolcap = read_tool_capabilities(L, 4); + v3f dir = lua_isnil(L, 5) ? + sao->getBasePosition() - puncher->getBasePosition() : check_v3f(L, 5); + + dir.normalize(); + u16 src_original_hp = sao->getHP(); u16 dst_origin_hp = puncher->getHP(); - // Do it - u16 wear = co->punch(dir, &toolcap, puncher, time_from_last_punch); + u16 wear = sao->punch(dir, &toolcap, puncher, time_from_last_punch); lua_pushnumber(L, wear); // If the punched is a player, and its HP changed - if (src_original_hp != co->getHP() && - co->getType() == ACTIVEOBJECT_TYPE_PLAYER) { - getServer(L)->SendPlayerHPOrDie((PlayerSAO *)co, + if (src_original_hp != sao->getHP() && + sao->getType() == ACTIVEOBJECT_TYPE_PLAYER) { + getServer(L)->SendPlayerHPOrDie((PlayerSAO *)sao, PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, puncher)); } @@ -195,45 +193,38 @@ int ObjectRef::l_punch(lua_State *L) if (dst_origin_hp != puncher->getHP() && puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) { getServer(L)->SendPlayerHPOrDie((PlayerSAO *)puncher, - PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, co)); + PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, sao)); } return 1; } -// right_click(self, clicker); clicker = an another ObjectRef +// right_click(self, clicker) int ObjectRef::l_right_click(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); ObjectRef *ref2 = checkobject(L, 2); - ServerActiveObject *co = getobject(ref); - ServerActiveObject *co2 = getobject(ref2); - if (co == NULL) return 0; - if (co2 == NULL) return 0; - // Do it - co->rightClick(co2); + ServerActiveObject *sao = getobject(ref); + ServerActiveObject *sao2 = getobject(ref2); + if (sao == nullptr || sao2 == nullptr) + return 0; + + sao->rightClick(sao2); return 0; } -// set_hp(self, hp) -// hp = number of hitpoints (2 * number of hearts) -// returns: nil +// set_hp(self, hp, reason) int ObjectRef::l_set_hp(lua_State *L) { NO_MAP_LOCK_REQUIRED; - - // Get Object ObjectRef *ref = checkobject(L, 1); - luaL_checknumber(L, 2); - ServerActiveObject *co = getobject(ref); - if (co == NULL) + ServerActiveObject *sao = getobject(ref); + if (sao == nullptr) return 0; - // Get HP - int hp = lua_tonumber(L, 2); - - // Get Reason + int hp = readParam(L, 2); PlayerHPChangeReason reason(PlayerHPChangeReason::SET_HP); + reason.from_mod = true; if (lua_istable(L, 3)) { lua_pushvalue(L, 3); @@ -248,35 +239,28 @@ int ObjectRef::l_set_hp(lua_State *L) reason.lua_reference = luaL_ref(L, LUA_REGISTRYINDEX); } - // Do it - co->setHP(hp, reason); - if (co->getType() == ACTIVEOBJECT_TYPE_PLAYER) - getServer(L)->SendPlayerHPOrDie((PlayerSAO *)co, reason); - + sao->setHP(hp, reason); + if (sao->getType() == ACTIVEOBJECT_TYPE_PLAYER) + getServer(L)->SendPlayerHPOrDie((PlayerSAO *)sao, reason); if (reason.hasLuaReference()) luaL_unref(L, LUA_REGISTRYINDEX, reason.lua_reference); - - // Return return 0; } // get_hp(self) -// returns: number of hitpoints (2 * number of hearts) -// 0 if not applicable to this type of object int ObjectRef::l_get_hp(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - ServerActiveObject *co = getobject(ref); - if (co == NULL) { + ServerActiveObject *sao = getobject(ref); + if (sao == nullptr) { // Default hp is 1 lua_pushnumber(L, 1); return 1; } - int hp = co->getHP(); - /*infostream<<"ObjectRef::l_get_hp(): id="<getId() - <<" hp="<getHP(); + lua_pushnumber(L, hp); return 1; } @@ -286,11 +270,12 @@ int ObjectRef::l_get_inventory(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - ServerActiveObject *co = getobject(ref); - if (co == NULL) return 0; - // Do it - InventoryLocation loc = co->getInventoryLocation(); - if (getServerInventoryMgr(L)->getInventory(loc) != NULL) + ServerActiveObject *sao = getobject(ref); + if (sao == nullptr) + return 0; + + InventoryLocation loc = sao->getInventoryLocation(); + if (getServerInventoryMgr(L)->getInventory(loc) != nullptr) InvRef::create(L, loc); else lua_pushnil(L); // An object may have no inventory (nil) @@ -302,11 +287,11 @@ int ObjectRef::l_get_wield_list(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - ServerActiveObject *co = getobject(ref); - if (!co) + ServerActiveObject *sao = getobject(ref); + if (sao == nullptr) return 0; - lua_pushstring(L, co->getWieldList().c_str()); + lua_pushstring(L, sao->getWieldList().c_str()); return 1; } @@ -315,11 +300,11 @@ int ObjectRef::l_get_wield_index(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - ServerActiveObject *co = getobject(ref); - if (!co) + ServerActiveObject *sao = getobject(ref); + if (sao == nullptr) return 0; - lua_pushinteger(L, co->getWieldIndex() + 1); + lua_pushinteger(L, sao->getWieldIndex() + 1); return 1; } @@ -328,31 +313,33 @@ int ObjectRef::l_get_wielded_item(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - ServerActiveObject *co = getobject(ref); - if (!co) { + ServerActiveObject *sao = getobject(ref); + if (sao == nullptr) { // Empty ItemStack LuaItemStack::create(L, ItemStack()); return 1; } ItemStack selected_item; - co->getWieldedItem(&selected_item, nullptr); + sao->getWieldedItem(&selected_item, nullptr); LuaItemStack::create(L, selected_item); return 1; } -// set_wielded_item(self, itemstack or itemstring or table or nil) +// set_wielded_item(self, item) int ObjectRef::l_set_wielded_item(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - ServerActiveObject *co = getobject(ref); - if (co == NULL) return 0; - // Do it + ServerActiveObject *sao = getobject(ref); + if (sao == nullptr) + return 0; + ItemStack item = read_item(L, 2, getServer(L)->idef()); - bool success = co->setWieldedItem(item); - if (success && co->getType() == ACTIVEOBJECT_TYPE_PLAYER) { - getServer(L)->SendInventory((PlayerSAO *)co, true); + + bool success = sao->setWieldedItem(item); + if (success && sao->getType() == ACTIVEOBJECT_TYPE_PLAYER) { + getServer(L)->SendInventory((PlayerSAO *)sao, true); } lua_pushboolean(L, success); return 1; @@ -363,12 +350,14 @@ int ObjectRef::l_set_armor_groups(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - ServerActiveObject *co = getobject(ref); - if (co == NULL) return 0; - // Do it + ServerActiveObject *sao = getobject(ref); + if (sao == nullptr) + return 0; + ItemGroupList groups; + read_groups(L, 2, groups); - co->setArmorGroups(groups); + sao->setArmorGroups(groups); return 0; } @@ -377,77 +366,11 @@ int ObjectRef::l_get_armor_groups(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - ServerActiveObject *co = getobject(ref); - if (co == NULL) + ServerActiveObject *sao = getobject(ref); + if (sao == nullptr) return 0; - // Do it - push_groups(L, co->getArmorGroups()); - return 1; -} -// set_physics_override(self, physics_override_speed, physics_override_jump, -// physics_override_gravity, sneak, sneak_glitch, new_move) -int ObjectRef::l_set_physics_override(lua_State *L) -{ - NO_MAP_LOCK_REQUIRED; - ObjectRef *ref = checkobject(L, 1); - PlayerSAO *co = (PlayerSAO *) getobject(ref); - if (co == NULL) return 0; - // Do it - if (lua_istable(L, 2)) { - co->m_physics_override_speed = getfloatfield_default( - L, 2, "speed", co->m_physics_override_speed); - co->m_physics_override_jump = getfloatfield_default( - L, 2, "jump", co->m_physics_override_jump); - co->m_physics_override_gravity = getfloatfield_default( - L, 2, "gravity", co->m_physics_override_gravity); - co->m_physics_override_sneak = getboolfield_default( - L, 2, "sneak", co->m_physics_override_sneak); - co->m_physics_override_sneak_glitch = getboolfield_default( - L, 2, "sneak_glitch", co->m_physics_override_sneak_glitch); - co->m_physics_override_new_move = getboolfield_default( - L, 2, "new_move", co->m_physics_override_new_move); - co->m_physics_override_sent = false; - } else { - // old, non-table format - if (!lua_isnil(L, 2)) { - co->m_physics_override_speed = lua_tonumber(L, 2); - co->m_physics_override_sent = false; - } - if (!lua_isnil(L, 3)) { - co->m_physics_override_jump = lua_tonumber(L, 3); - co->m_physics_override_sent = false; - } - if (!lua_isnil(L, 4)) { - co->m_physics_override_gravity = lua_tonumber(L, 4); - co->m_physics_override_sent = false; - } - } - return 0; -} - -// get_physics_override(self) -int ObjectRef::l_get_physics_override(lua_State *L) -{ - NO_MAP_LOCK_REQUIRED; - ObjectRef *ref = checkobject(L, 1); - PlayerSAO *co = (PlayerSAO *)getobject(ref); - if (co == NULL) - return 0; - // Do it - lua_newtable(L); - lua_pushnumber(L, co->m_physics_override_speed); - lua_setfield(L, -2, "speed"); - lua_pushnumber(L, co->m_physics_override_jump); - lua_setfield(L, -2, "jump"); - lua_pushnumber(L, co->m_physics_override_gravity); - lua_setfield(L, -2, "gravity"); - lua_pushboolean(L, co->m_physics_override_sneak); - lua_setfield(L, -2, "sneak"); - lua_pushboolean(L, co->m_physics_override_sneak_glitch); - lua_setfield(L, -2, "sneak_glitch"); - lua_pushboolean(L, co->m_physics_override_new_move); - lua_setfield(L, -2, "new_move"); + push_groups(L, sao->getArmorGroups()); return 1; } @@ -456,9 +379,10 @@ int ObjectRef::l_set_animation(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - ServerActiveObject *co = getobject(ref); - if (co == NULL) return 0; - // Do it + ServerActiveObject *sao = getobject(ref); + if (sao == nullptr) + return 0; + v2f frames = v2f(1, 1); if (!lua_isnil(L, 2)) frames = readParam(L, 2); @@ -471,7 +395,8 @@ int ObjectRef::l_set_animation(lua_State *L) bool frame_loop = true; if (lua_isboolean(L, 5)) frame_loop = readParam(L, 5); - co->setAnimation(frames, frame_speed, frame_blend, frame_loop); + + sao->setAnimation(frames, frame_speed, frame_blend, frame_loop); return 0; } @@ -480,16 +405,16 @@ int ObjectRef::l_get_animation(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - ServerActiveObject *co = getobject(ref); - if (co == NULL) + ServerActiveObject *sao = getobject(ref); + if (sao == nullptr) return 0; - // Do it + v2f frames = v2f(1,1); float frame_speed = 15; float frame_blend = 0; bool frame_loop = true; - co->getAnimation(&frames, &frame_speed, &frame_blend, &frame_loop); + sao->getAnimation(&frames, &frame_speed, &frame_blend, &frame_loop); push_v2f(L, frames); lua_pushnumber(L, frame_speed); lua_pushnumber(L, frame_blend); @@ -497,23 +422,21 @@ int ObjectRef::l_get_animation(lua_State *L) return 4; } -// set_local_animation(self, {stand/idle}, {walk}, {dig}, {walk+dig}, frame_speed) +// set_local_animation(self, idle, walk, dig, walk_while_dig, frame_speed) int ObjectRef::l_set_local_animation(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); RemotePlayer *player = getplayer(ref); - if (player == NULL) + if (player == nullptr) return 0; - // Do it + v2s32 frames[4]; for (int i=0;i<4;i++) { if (!lua_isnil(L, 2+1)) frames[i] = read_v2s32(L, 2+i); } - float frame_speed = 30; - if (!lua_isnil(L, 6)) - frame_speed = lua_tonumber(L, 6); + float frame_speed = lua_isnil(L, 6) ? 30 : readParam(L, 6); getServer(L)->setLocalPlayerAnimations(player, frames, frame_speed); lua_pushboolean(L, true); @@ -526,7 +449,7 @@ int ObjectRef::l_get_local_animation(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); RemotePlayer *player = getplayer(ref); - if (player == NULL) + if (player == nullptr) return 0; v2s32 frames[4]; @@ -541,27 +464,22 @@ int ObjectRef::l_get_local_animation(lua_State *L) return 5; } -// set_eye_offset(self, v3f first pv, v3f third pv) +// set_eye_offset(self, firstperson, thirdperson) int ObjectRef::l_set_eye_offset(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); RemotePlayer *player = getplayer(ref); - if (player == NULL) + if (player == nullptr) return 0; - // Do it - v3f offset_first = v3f(0, 0, 0); - v3f offset_third = v3f(0, 0, 0); - if (!lua_isnil(L, 2)) - offset_first = read_v3f(L, 2); - if (!lua_isnil(L, 3)) - offset_third = read_v3f(L, 3); + v3f offset_first = read_v3f(L, 2); + v3f offset_third = read_v3f(L, 3); // Prevent abuse of offset values (keep player always visible) offset_third.X = rangelim(offset_third.X,-10,10); offset_third.Z = rangelim(offset_third.Z,-5,5); - /* TODO: if possible: improve the camera colision detetion to allow Y <= -1.5) */ + /* TODO: if possible: improve the camera collision detection to allow Y <= -1.5) */ offset_third.Y = rangelim(offset_third.Y,-10,15); //1.5*BS getServer(L)->setPlayerEyeOffset(player, offset_first, offset_third); @@ -575,9 +493,9 @@ int ObjectRef::l_get_eye_offset(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); RemotePlayer *player = getplayer(ref); - if (player == NULL) + if (player == nullptr) return 0; - // Do it + push_v3f(L, player->eye_offset_first); push_v3f(L, player->eye_offset_third); return 2; @@ -588,14 +506,14 @@ int ObjectRef::l_send_mapblock(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - RemotePlayer *player = getplayer(ref); - if (!player) + if (player == nullptr) return 0; - v3s16 p = read_v3s16(L, 2); + + v3s16 pos = read_v3s16(L, 2); session_t peer_id = player->getPeerId(); - bool r = getServer(L)->SendBlock(peer_id, p); + bool r = getServer(L)->SendBlock(peer_id, pos); lua_pushboolean(L, r); return 1; @@ -606,14 +524,13 @@ int ObjectRef::l_set_animation_frame_speed(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - ServerActiveObject *co = getobject(ref); - if (co == NULL) + ServerActiveObject *sao = getobject(ref); + if (sao == nullptr) return 0; - // Do it if (!lua_isnil(L, 2)) { - float frame_speed = lua_tonumber(L, 2); - co->setAnimationSpeed(frame_speed); + float frame_speed = readParam(L, 2); + sao->setAnimationSpeed(frame_speed); lua_pushboolean(L, true); } else { lua_pushboolean(L, false); @@ -621,24 +538,20 @@ int ObjectRef::l_set_animation_frame_speed(lua_State *L) return 1; } -// set_bone_position(self, std::string bone, v3f position, v3f rotation) +// set_bone_position(self, bone, position, rotation) int ObjectRef::l_set_bone_position(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - ServerActiveObject *co = getobject(ref); - if (co == NULL) return 0; - // Do it - std::string bone; - if (!lua_isnil(L, 2)) - bone = readParam(L, 2); - v3f position = v3f(0, 0, 0); - if (!lua_isnil(L, 3)) - position = check_v3f(L, 3); - v3f rotation = v3f(0, 0, 0); - if (!lua_isnil(L, 4)) - rotation = check_v3f(L, 4); - co->setBonePosition(bone, position, rotation); + ServerActiveObject *sao = getobject(ref); + if (sao == nullptr) + return 0; + + std::string bone = readParam(L, 2); + v3f position = check_v3f(L, 3); + v3f rotation = check_v3f(L, 4); + + sao->setBonePosition(bone, position, rotation); return 0; } @@ -647,17 +560,15 @@ int ObjectRef::l_get_bone_position(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - ServerActiveObject *co = getobject(ref); - if (co == NULL) + ServerActiveObject *sao = getobject(ref); + if (sao == nullptr) return 0; - // Do it - std::string bone; - if (!lua_isnil(L, 2)) - bone = readParam(L, 2); + + std::string bone = readParam(L, 2); v3f position = v3f(0, 0, 0); v3f rotation = v3f(0, 0, 0); - co->getBonePosition(bone, &position, &rotation); + sao->getBonePosition(bone, &position, &rotation); push_v3f(L, position); push_v3f(L, rotation); @@ -668,44 +579,34 @@ int ObjectRef::l_get_bone_position(lua_State *L) int ObjectRef::l_set_attach(lua_State *L) { GET_ENV_PTR; - ObjectRef *ref = checkobject(L, 1); ObjectRef *parent_ref = checkobject(L, 2); - ServerActiveObject *co = getobject(ref); + ServerActiveObject *sao = getobject(ref); ServerActiveObject *parent = getobject(parent_ref); - if (co == NULL) + if (sao == nullptr || parent == nullptr) return 0; - - if (parent == NULL) - return 0; - - if (co == parent) + if (sao == parent) throw LuaError("ObjectRef::set_attach: attaching object to itself is not allowed."); - // Do it int parent_id = 0; std::string bone; v3f position = v3f(0, 0, 0); v3f rotation = v3f(0, 0, 0); bool force_visible; - co->getAttachment(&parent_id, &bone, &position, &rotation, &force_visible); + + sao->getAttachment(&parent_id, &bone, &position, &rotation, &force_visible); if (parent_id) { ServerActiveObject *old_parent = env->getActiveObject(parent_id); - old_parent->removeAttachmentChild(co->getId()); + old_parent->removeAttachmentChild(sao->getId()); } - bone = ""; - if (!lua_isnil(L, 3)) - bone = readParam(L, 3); - position = v3f(0, 0, 0); - if (!lua_isnil(L, 4)) - position = read_v3f(L, 4); - rotation = v3f(0, 0, 0); - if (!lua_isnil(L, 5)) - rotation = read_v3f(L, 5); + bone = readParam(L, 3, ""); + position = read_v3f(L, 4); + rotation = read_v3f(L, 5); force_visible = readParam(L, 6, false); - co->setAttachment(parent->getId(), bone, position, rotation, force_visible); - parent->addAttachmentChild(co->getId()); + + sao->setAttachment(parent->getId(), bone, position, rotation, force_visible); + parent->addAttachmentChild(sao->getId()); return 0; } @@ -713,23 +614,22 @@ int ObjectRef::l_set_attach(lua_State *L) int ObjectRef::l_get_attach(lua_State *L) { GET_ENV_PTR; - ObjectRef *ref = checkobject(L, 1); - ServerActiveObject *co = getobject(ref); - if (co == NULL) + ServerActiveObject *sao = getobject(ref); + if (sao == nullptr) return 0; - // Do it int parent_id = 0; std::string bone; v3f position = v3f(0, 0, 0); v3f rotation = v3f(0, 0, 0); bool force_visible; - co->getAttachment(&parent_id, &bone, &position, &rotation, &force_visible); - if (!parent_id) - return 0; - ServerActiveObject *parent = env->getActiveObject(parent_id); + sao->getAttachment(&parent_id, &bone, &position, &rotation, &force_visible); + if (parent_id == 0) + return 0; + + ServerActiveObject *parent = env->getActiveObject(parent_id); getScriptApiBase(L)->objectrefGetOrCreate(L, parent); lua_pushlstring(L, bone.c_str(), bone.size()); push_v3f(L, position); @@ -742,7 +642,6 @@ int ObjectRef::l_get_attach(lua_State *L) int ObjectRef::l_get_children(lua_State *L) { GET_ENV_PTR; - ObjectRef *ref = checkobject(L, 1); ServerActiveObject *sao = getobject(ref); if (sao == nullptr) @@ -750,6 +649,7 @@ int ObjectRef::l_get_children(lua_State *L) const std::unordered_set child_ids = sao->getAttachmentChildIds(); int i = 0; + lua_createtable(L, child_ids.size(), 0); for (const int id : child_ids) { ServerActiveObject *child = env->getActiveObject(id); @@ -763,13 +663,12 @@ int ObjectRef::l_get_children(lua_State *L) int ObjectRef::l_set_detach(lua_State *L) { GET_ENV_PTR; - ObjectRef *ref = checkobject(L, 1); - ServerActiveObject *co = getobject(ref); - if (co == NULL) + ServerActiveObject *sao = getobject(ref); + if (sao == nullptr) return 0; - co->clearParentAttachment(); + sao->clearParentAttachment(); return 0; } @@ -778,16 +677,16 @@ int ObjectRef::l_set_properties(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - ServerActiveObject *co = getobject(ref); - if (!co) + ServerActiveObject *sao = getobject(ref); + if (sao == nullptr) return 0; - ObjectProperties *prop = co->accessObjectProperties(); - if (!prop) + ObjectProperties *prop = sao->accessObjectProperties(); + if (prop == nullptr) return 0; - read_object_properties(L, 2, co, prop, getServer(L)->idef()); - co->notifyObjectPropertiesModified(); + read_object_properties(L, 2, sao, prop, getServer(L)->idef()); + sao->notifyObjectPropertiesModified(); return 0; } @@ -796,12 +695,14 @@ int ObjectRef::l_get_properties(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - ServerActiveObject *co = getobject(ref); - if (co == NULL) + ServerActiveObject *sao = getobject(ref); + if (sao == nullptr) return 0; - ObjectProperties *prop = co->accessObjectProperties(); - if (!prop) + + ObjectProperties *prop = sao->accessObjectProperties(); + if (prop == nullptr) return 0; + push_object_properties(L, prop); return 1; } @@ -812,7 +713,7 @@ int ObjectRef::l_is_player(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); RemotePlayer *player = getplayer(ref); - lua_pushboolean(L, (player != NULL)); + lua_pushboolean(L, (player != nullptr)); return 1; } @@ -821,12 +722,12 @@ int ObjectRef::l_set_nametag_attributes(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - ServerActiveObject *co = getobject(ref); - - if (co == NULL) + ServerActiveObject *sao = getobject(ref); + if (sao == nullptr) return 0; - ObjectProperties *prop = co->accessObjectProperties(); - if (!prop) + + ObjectProperties *prop = sao->accessObjectProperties(); + if (prop == nullptr) return 0; lua_getfield(L, 2, "color"); @@ -840,7 +741,7 @@ int ObjectRef::l_set_nametag_attributes(lua_State *L) std::string nametag = getstringfield_default(L, 2, "text", ""); prop->nametag = nametag; - co->notifyObjectPropertiesModified(); + sao->notifyObjectPropertiesModified(); lua_pushboolean(L, true); return 1; } @@ -850,11 +751,11 @@ int ObjectRef::l_get_nametag_attributes(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - ServerActiveObject *co = getobject(ref); - - if (co == NULL) + ServerActiveObject *sao = getobject(ref); + if (sao == nullptr) return 0; - ObjectProperties *prop = co->accessObjectProperties(); + + ObjectProperties *prop = sao->accessObjectProperties(); if (!prop) return 0; @@ -870,37 +771,39 @@ int ObjectRef::l_get_nametag_attributes(lua_State *L) /* LuaEntitySAO-only */ -// set_velocity(self, {x=num, y=num, z=num}) +// set_velocity(self, velocity) int ObjectRef::l_set_velocity(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - LuaEntitySAO *co = getluaobject(ref); - if (co == NULL) return 0; - v3f pos = checkFloatPos(L, 2); - // Do it - co->setVelocity(pos); + LuaEntitySAO *sao = getluaobject(ref); + if (sao == nullptr) + return 0; + + v3f vel = checkFloatPos(L, 2); + + sao->setVelocity(vel); return 0; } -// add_velocity(self, {x=num, y=num, z=num}) +// add_velocity(self, velocity) int ObjectRef::l_add_velocity(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - v3f vel = checkFloatPos(L, 2); - - ServerActiveObject *obj = getobject(ref); - if (obj == nullptr) + ServerActiveObject *sao = getobject(ref); + if (sao == nullptr) return 0; - if (obj->getType() == ACTIVEOBJECT_TYPE_LUAENTITY) { - LuaEntitySAO *co = dynamic_cast(obj); - co->addVelocity(vel); - } else if (obj->getType() == ACTIVEOBJECT_TYPE_PLAYER) { - PlayerSAO *player = dynamic_cast(obj); - player->setMaxSpeedOverride(vel); - getServer(L)->SendPlayerSpeed(player->getPeerID(), vel); + v3f vel = checkFloatPos(L, 2); + + if (sao->getType() == ACTIVEOBJECT_TYPE_LUAENTITY) { + LuaEntitySAO *entitysao = dynamic_cast(sao); + entitysao->addVelocity(vel); + } else if (sao->getType() == ACTIVEOBJECT_TYPE_PLAYER) { + PlayerSAO *playersao = dynamic_cast(sao); + playersao->setMaxSpeedOverride(vel); + getServer(L)->SendPlayerSpeed(playersao->getPeerID(), vel); } return 0; @@ -911,18 +814,17 @@ int ObjectRef::l_get_velocity(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - - ServerActiveObject *obj = getobject(ref); - if (obj == nullptr) + ServerActiveObject *sao = getobject(ref); + if (sao == nullptr) return 0; - if (obj->getType() == ACTIVEOBJECT_TYPE_LUAENTITY) { - LuaEntitySAO *co = dynamic_cast(obj); - v3f v = co->getVelocity(); - pushFloatPos(L, v); + if (sao->getType() == ACTIVEOBJECT_TYPE_LUAENTITY) { + LuaEntitySAO *entitysao = dynamic_cast(sao); + v3f vel = entitysao->getVelocity(); + pushFloatPos(L, vel); return 1; - } else if (obj->getType() == ACTIVEOBJECT_TYPE_PLAYER) { - RemotePlayer *player = dynamic_cast(obj)->getPlayer(); + } else if (sao->getType() == ACTIVEOBJECT_TYPE_PLAYER) { + RemotePlayer *player = dynamic_cast(sao)->getPlayer(); push_v3f(L, player->getSpeed() / BS); return 1; } @@ -931,17 +833,18 @@ int ObjectRef::l_get_velocity(lua_State *L) return 1; } -// set_acceleration(self, {x=num, y=num, z=num}) +// set_acceleration(self, acceleration) int ObjectRef::l_set_acceleration(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - LuaEntitySAO *co = getluaobject(ref); - if (co == NULL) return 0; - // pos - v3f pos = checkFloatPos(L, 2); - // Do it - co->setAcceleration(pos); + LuaEntitySAO *entitysao = getluaobject(ref); + if (entitysao == nullptr) + return 0; + + v3f acceleration = checkFloatPos(L, 2); + + entitysao->setAcceleration(acceleration); return 0; } @@ -950,59 +853,61 @@ int ObjectRef::l_get_acceleration(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - LuaEntitySAO *co = getluaobject(ref); - if (co == NULL) return 0; - // Do it - v3f v = co->getAcceleration(); - pushFloatPos(L, v); + LuaEntitySAO *entitysao = getluaobject(ref); + if (entitysao == nullptr) + return 0; + + v3f acceleration = entitysao->getAcceleration(); + pushFloatPos(L, acceleration); return 1; } -// set_rotation(self, {x=num, y=num, z=num}) -// Each 'num' is in radians +// set_rotation(self, rotation) int ObjectRef::l_set_rotation(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - LuaEntitySAO *co = getluaobject(ref); - if (!co) + LuaEntitySAO *entitysao = getluaobject(ref); + if (entitysao == nullptr) return 0; v3f rotation = check_v3f(L, 2) * core::RADTODEG; - co->setRotation(rotation); + + entitysao->setRotation(rotation); return 0; } // get_rotation(self) -// returns: {x=num, y=num, z=num} -// Each 'num' is in radians int ObjectRef::l_get_rotation(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - LuaEntitySAO *co = getluaobject(ref); - if (!co) + LuaEntitySAO *entitysao = getluaobject(ref); + if (entitysao == nullptr) return 0; + v3f rotation = entitysao->getRotation() * core::DEGTORAD; + lua_newtable(L); - v3f rotation = co->getRotation() * core::DEGTORAD; push_v3f(L, rotation); return 1; } -// set_yaw(self, radians) +// set_yaw(self, yaw) int ObjectRef::l_set_yaw(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - LuaEntitySAO *co = getluaobject(ref); + LuaEntitySAO *entitysao = getluaobject(ref); + if (entitysao == nullptr) + return 0; - if (co == NULL) return 0; if (isNaN(L, 2)) throw LuaError("ObjectRef::set_yaw: NaN value is not allowed."); float yaw = readParam(L, 2) * core::RADTODEG; - co->setRotation(v3f(0, yaw, 0)); + + entitysao->setRotation(v3f(0, yaw, 0)); return 0; } @@ -1011,11 +916,12 @@ int ObjectRef::l_get_yaw(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - LuaEntitySAO *co = getluaobject(ref); - if (!co) + LuaEntitySAO *entitysao = getluaobject(ref); + if (entitysao == nullptr) return 0; - float yaw = co->getRotation().Y * core::DEGTORAD; + float yaw = entitysao->getRotation().Y * core::DEGTORAD; + lua_pushnumber(L, yaw); return 1; } @@ -1025,11 +931,13 @@ int ObjectRef::l_set_texture_mod(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - LuaEntitySAO *co = getluaobject(ref); - if (co == NULL) return 0; - // Do it - std::string mod = luaL_checkstring(L, 2); - co->setTextureMod(mod); + LuaEntitySAO *entitysao = getluaobject(ref); + if (entitysao == nullptr) + return 0; + + std::string mod = readParam(L, 2); + + entitysao->setTextureMod(mod); return 0; } @@ -1038,36 +946,31 @@ int ObjectRef::l_get_texture_mod(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - LuaEntitySAO *co = getluaobject(ref); - if (co == NULL) return 0; - // Do it - std::string mod = co->getTextureMod(); + LuaEntitySAO *entitysao = getluaobject(ref); + if (entitysao == nullptr) + return 0; + + std::string mod = entitysao->getTextureMod(); + lua_pushstring(L, mod.c_str()); return 1; } -// set_sprite(self, p={x=0,y=0}, num_frames=1, framelength=0.2, -// select_horiz_by_yawpitch=false) +// set_sprite(self, start_frame, num_frames, framelength, select_x_by_camera) int ObjectRef::l_set_sprite(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - LuaEntitySAO *co = getluaobject(ref); - if (co == NULL) return 0; - // Do it - v2s16 p(0,0); - if (!lua_isnil(L, 2)) - p = readParam(L, 2); - int num_frames = 1; - if (!lua_isnil(L, 3)) - num_frames = lua_tonumber(L, 3); - float framelength = 0.2; - if (!lua_isnil(L, 4)) - framelength = lua_tonumber(L, 4); - bool select_horiz_by_yawpitch = false; - if (!lua_isnil(L, 5)) - select_horiz_by_yawpitch = readParam(L, 5); - co->setSprite(p, num_frames, framelength, select_horiz_by_yawpitch); + LuaEntitySAO *entitysao = getluaobject(ref); + if (entitysao == nullptr) + return 0; + + v2s16 start_frame = lua_isnil(L, 2) ? v2s16(0,0) : readParam(L, 2); + int num_frames = lua_isnil(L, 3) ? 1 : luaL_checkint(L, 3); + float framelength = lua_isnil(L, 4) ? 0.2 : lua_tonumber(L, 4); + bool select_x_by_camera = readParam(L, 5, false); + + entitysao->setSprite(start_frame, num_frames, framelength, select_x_by_camera); return 0; } @@ -1077,11 +980,13 @@ int ObjectRef::l_get_entity_name(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - LuaEntitySAO *co = getluaobject(ref); + LuaEntitySAO *entitysao = getluaobject(ref); log_deprecated(L,"Deprecated call to \"get_entity_name"); - if (co == NULL) return 0; - // Do it - std::string name = co->getName(); + if (entitysao == nullptr) + return 0; + + std::string name = entitysao->getName(); + lua_pushstring(L, name.c_str()); return 1; } @@ -1091,39 +996,27 @@ int ObjectRef::l_get_luaentity(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - LuaEntitySAO *co = getluaobject(ref); - if (co == NULL) return 0; - // Do it - luaentity_get(L, co->getId()); + LuaEntitySAO *entitysao = getluaobject(ref); + if (entitysao == nullptr) + return 0; + + luaentity_get(L, entitysao->getId()); return 1; } /* Player-only */ -// is_player_connected(self) -int ObjectRef::l_is_player_connected(lua_State *L) -{ - NO_MAP_LOCK_REQUIRED; - // This method was once added for a bugfix, but never documented - log_deprecated(L, "is_player_connected is undocumented and " - "will be removed in a future release"); - ObjectRef *ref = checkobject(L, 1); - RemotePlayer *player = getplayer(ref); - lua_pushboolean(L, (player != NULL && player->getPeerId() != PEER_ID_INEXISTENT)); - return 1; -} - // get_player_name(self) int ObjectRef::l_get_player_name(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); RemotePlayer *player = getplayer(ref); - if (player == NULL) { + if (player == nullptr) { lua_pushlstring(L, "", 0); return 1; } - // Do it + lua_pushstring(L, player->getName()); return 1; } @@ -1133,13 +1026,15 @@ int ObjectRef::l_get_look_dir(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - PlayerSAO* co = getplayersao(ref); - if (co == NULL) return 0; - // Do it - float pitch = co->getRadLookPitchDep(); - float yaw = co->getRadYawDep(); + PlayerSAO* playersao = getplayersao(ref); + if (playersao == nullptr) + return 0; + + float pitch = playersao->getRadLookPitchDep(); + float yaw = playersao->getRadYawDep(); v3f v(std::cos(pitch) * std::cos(yaw), std::sin(pitch), std::cos(pitch) * std::sin(yaw)); + push_v3f(L, v); return 1; } @@ -1154,10 +1049,11 @@ int ObjectRef::l_get_look_pitch(lua_State *L) "Deprecated call to get_look_pitch, use get_look_vertical instead"); ObjectRef *ref = checkobject(L, 1); - PlayerSAO* co = getplayersao(ref); - if (co == NULL) return 0; - // Do it - lua_pushnumber(L, co->getRadLookPitchDep()); + PlayerSAO* playersao = getplayersao(ref); + if (playersao == nullptr) + return 0; + + lua_pushnumber(L, playersao->getRadLookPitchDep()); return 1; } @@ -1171,34 +1067,37 @@ int ObjectRef::l_get_look_yaw(lua_State *L) "Deprecated call to get_look_yaw, use get_look_horizontal instead"); ObjectRef *ref = checkobject(L, 1); - PlayerSAO* co = getplayersao(ref); - if (co == NULL) return 0; - // Do it - lua_pushnumber(L, co->getRadYawDep()); + PlayerSAO* playersao = getplayersao(ref); + if (playersao == nullptr) + return 0; + + lua_pushnumber(L, playersao->getRadYawDep()); return 1; } -// get_look_pitch2(self) +// get_look_vertical(self) int ObjectRef::l_get_look_vertical(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - PlayerSAO* co = getplayersao(ref); - if (co == NULL) return 0; - // Do it - lua_pushnumber(L, co->getRadLookPitch()); + PlayerSAO* playersao = getplayersao(ref); + if (playersao == nullptr) + return 0; + + lua_pushnumber(L, playersao->getRadLookPitch()); return 1; } -// get_look_yaw2(self) +// get_look_horizontal(self) int ObjectRef::l_get_look_horizontal(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - PlayerSAO* co = getplayersao(ref); - if (co == NULL) return 0; - // Do it - lua_pushnumber(L, co->getRadRotation().Y); + PlayerSAO* playersao = getplayersao(ref); + if (playersao == nullptr) + return 0; + + lua_pushnumber(L, playersao->getRadRotation().Y); return 1; } @@ -1207,11 +1106,13 @@ int ObjectRef::l_set_look_vertical(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - PlayerSAO* co = getplayersao(ref); - if (co == NULL) return 0; + PlayerSAO* playersao = getplayersao(ref); + if (playersao == nullptr) + return 0; + float pitch = readParam(L, 2) * core::RADTODEG; - // Do it - co->setLookPitchAndSend(pitch); + + playersao->setLookPitchAndSend(pitch); return 1; } @@ -1220,11 +1121,13 @@ int ObjectRef::l_set_look_horizontal(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - PlayerSAO* co = getplayersao(ref); - if (co == NULL) return 0; + PlayerSAO* playersao = getplayersao(ref); + if (playersao == nullptr) + return 0; + float yaw = readParam(L, 2) * core::RADTODEG; - // Do it - co->setPlayerYawAndSend(yaw); + + playersao->setPlayerYawAndSend(yaw); return 1; } @@ -1238,11 +1141,13 @@ int ObjectRef::l_set_look_pitch(lua_State *L) "Deprecated call to set_look_pitch, use set_look_vertical instead."); ObjectRef *ref = checkobject(L, 1); - PlayerSAO* co = getplayersao(ref); - if (co == NULL) return 0; + PlayerSAO* playersao = getplayersao(ref); + if (playersao == nullptr) + return 0; + float pitch = readParam(L, 2) * core::RADTODEG; - // Do it - co->setLookPitchAndSend(pitch); + + playersao->setLookPitchAndSend(pitch); return 1; } @@ -1256,30 +1161,32 @@ int ObjectRef::l_set_look_yaw(lua_State *L) "Deprecated call to set_look_yaw, use set_look_horizontal instead."); ObjectRef *ref = checkobject(L, 1); - PlayerSAO* co = getplayersao(ref); - if (co == NULL) return 0; + PlayerSAO* playersao = getplayersao(ref); + if (playersao == nullptr) + return 0; + float yaw = readParam(L, 2) * core::RADTODEG; - // Do it - co->setPlayerYawAndSend(yaw); + + playersao->setPlayerYawAndSend(yaw); return 1; } -// set_fov(self, degrees[, is_multiplier, transition_time]) +// set_fov(self, degrees, is_multiplier, transition_time) int ObjectRef::l_set_fov(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); RemotePlayer *player = getplayer(ref); - if (!player) + if (player == nullptr) return 0; - player->setFov({ - static_cast(luaL_checknumber(L, 2)), - readParam(L, 3, false), - lua_isnumber(L, 4) ? static_cast(luaL_checknumber(L, 4)) : 0.0f - }); - getServer(L)->SendPlayerFov(player->getPeerId()); + float degrees = static_cast(luaL_checknumber(L, 2)); + bool is_multiplier = readParam(L, 3, false); + float transition_time = lua_isnumber(L, 4) ? + static_cast(luaL_checknumber(L, 4)) : 0.0f; + player->setFov({degrees, is_multiplier, transition_time}); + getServer(L)->SendPlayerFov(player->getPeerId()); return 0; } @@ -1289,14 +1196,14 @@ int ObjectRef::l_get_fov(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); RemotePlayer *player = getplayer(ref); - if (!player) + if (player == nullptr) return 0; PlayerFovSpec fov_spec = player->getFov(); + lua_pushnumber(L, fov_spec.fov); lua_pushboolean(L, fov_spec.is_multiplier); lua_pushnumber(L, fov_spec.transition_time); - return 3; } @@ -1305,11 +1212,13 @@ int ObjectRef::l_set_breath(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - PlayerSAO* co = getplayersao(ref); - if (co == NULL) return 0; - u16 breath = luaL_checknumber(L, 2); - co->setBreath(breath); + PlayerSAO* playersao = getplayersao(ref); + if (playersao == nullptr) + return 0; + u16 breath = luaL_checknumber(L, 2); + + playersao->setBreath(breath); return 0; } @@ -1318,11 +1227,13 @@ int ObjectRef::l_get_breath(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - PlayerSAO* co = getplayersao(ref); - if (co == NULL) return 0; - // Do it - u16 breath = co->getBreath(); - lua_pushinteger (L, breath); + PlayerSAO* playersao = getplayersao(ref); + if (playersao == nullptr) + return 0; + + u16 breath = playersao->getBreath(); + + lua_pushinteger(L, breath); return 1; } @@ -1333,16 +1244,16 @@ int ObjectRef::l_set_attribute(lua_State *L) "Deprecated call to set_attribute, use MetaDataRef methods instead."); ObjectRef *ref = checkobject(L, 1); - PlayerSAO* co = getplayersao(ref); - if (co == NULL) + PlayerSAO* playersao = getplayersao(ref); + if (playersao == nullptr) return 0; std::string attr = luaL_checkstring(L, 2); if (lua_isnil(L, 3)) { - co->getMeta().removeString(attr); + playersao->getMeta().removeString(attr); } else { std::string value = luaL_checkstring(L, 3); - co->getMeta().setString(attr, value); + playersao->getMeta().setString(attr, value); } return 1; } @@ -1354,14 +1265,14 @@ int ObjectRef::l_get_attribute(lua_State *L) "Deprecated call to get_attribute, use MetaDataRef methods instead."); ObjectRef *ref = checkobject(L, 1); - PlayerSAO* co = getplayersao(ref); - if (co == NULL) + PlayerSAO* playersao = getplayersao(ref); + if (playersao == nullptr) return 0; std::string attr = luaL_checkstring(L, 2); std::string value; - if (co->getMeta().getStringToRef(attr, value)) { + if (playersao->getMeta().getStringToRef(attr, value)) { lua_pushstring(L, value.c_str()); return 1; } @@ -1374,11 +1285,11 @@ int ObjectRef::l_get_attribute(lua_State *L) int ObjectRef::l_get_meta(lua_State *L) { ObjectRef *ref = checkobject(L, 1); - PlayerSAO *co = getplayersao(ref); - if (co == NULL) + PlayerSAO *playersao = getplayersao(ref); + if (playersao == nullptr) return 0; - PlayerMetaRef::create(L, &co->getMeta()); + PlayerMetaRef::create(L, &playersao->getMeta()); return 1; } @@ -1389,7 +1300,9 @@ int ObjectRef::l_set_inventory_formspec(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); RemotePlayer *player = getplayer(ref); - if (player == NULL) return 0; + if (player == nullptr) + return 0; + std::string formspec = luaL_checkstring(L, 2); player->inventory_formspec = formspec; @@ -1404,9 +1317,11 @@ int ObjectRef::l_get_inventory_formspec(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); RemotePlayer *player = getplayer(ref); - if (player == NULL) return 0; + if (player == nullptr) + return 0; std::string formspec = player->inventory_formspec; + lua_pushlstring(L, formspec.c_str(), formspec.size()); return 1; } @@ -1417,7 +1332,7 @@ int ObjectRef::l_set_formspec_prepend(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); RemotePlayer *player = getplayer(ref); - if (player == NULL) + if (player == nullptr) return 0; std::string formspec = luaL_checkstring(L, 2); @@ -1428,16 +1343,17 @@ int ObjectRef::l_set_formspec_prepend(lua_State *L) return 1; } -// get_formspec_prepend(self) -> formspec +// get_formspec_prepend(self) int ObjectRef::l_get_formspec_prepend(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); RemotePlayer *player = getplayer(ref); - if (player == NULL) + if (player == nullptr) return 0; std::string formspec = player->formspec_prepend; + lua_pushlstring(L, formspec.c_str(), formspec.size()); return 1; } @@ -1448,7 +1364,7 @@ int ObjectRef::l_get_player_control(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); RemotePlayer *player = getplayer(ref); - if (player == NULL) { + if (player == nullptr) { lua_pushlstring(L, "", 0); return 1; } @@ -1489,22 +1405,73 @@ int ObjectRef::l_get_player_control_bits(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); RemotePlayer *player = getplayer(ref); - if (player == NULL) { + if (player == nullptr) { lua_pushlstring(L, "", 0); return 1; } - // Do it + lua_pushnumber(L, player->keyPressed); return 1; } -// hud_add(self, form) +// set_physics_override(self, override_table) +int ObjectRef::l_set_physics_override(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + ObjectRef *ref = checkobject(L, 1); + PlayerSAO *playersao = (PlayerSAO *) getobject(ref); + if (playersao == nullptr) + return 0; + + luaL_checktype(L, 2, LUA_TTABLE); + playersao->m_physics_override_speed = getfloatfield_default( + L, 2, "speed", playersao->m_physics_override_speed); + playersao->m_physics_override_jump = getfloatfield_default( + L, 2, "jump", playersao->m_physics_override_jump); + playersao->m_physics_override_gravity = getfloatfield_default( + L, 2, "gravity", playersao->m_physics_override_gravity); + playersao->m_physics_override_sneak = getboolfield_default( + L, 2, "sneak", playersao->m_physics_override_sneak); + playersao->m_physics_override_sneak_glitch = getboolfield_default( + L, 2, "sneak_glitch", playersao->m_physics_override_sneak_glitch); + playersao->m_physics_override_new_move = getboolfield_default( + L, 2, "new_move", playersao->m_physics_override_new_move); + playersao->m_physics_override_sent = false; + return 0; +} + +// get_physics_override(self) +int ObjectRef::l_get_physics_override(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + ObjectRef *ref = checkobject(L, 1); + PlayerSAO *playersao = (PlayerSAO *)getobject(ref); + if (playersao == nullptr) + return 0; + + lua_newtable(L); + lua_pushnumber(L, playersao->m_physics_override_speed); + lua_setfield(L, -2, "speed"); + lua_pushnumber(L, playersao->m_physics_override_jump); + lua_setfield(L, -2, "jump"); + lua_pushnumber(L, playersao->m_physics_override_gravity); + lua_setfield(L, -2, "gravity"); + lua_pushboolean(L, playersao->m_physics_override_sneak); + lua_setfield(L, -2, "sneak"); + lua_pushboolean(L, playersao->m_physics_override_sneak_glitch); + lua_setfield(L, -2, "sneak_glitch"); + lua_pushboolean(L, playersao->m_physics_override_new_move); + lua_setfield(L, -2, "new_move"); + return 1; +} + +// hud_add(self, hud) int ObjectRef::l_hud_add(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); RemotePlayer *player = getplayer(ref); - if (player == NULL) + if (player == nullptr) return 0; HudElement *elem = new HudElement; @@ -1526,12 +1493,10 @@ int ObjectRef::l_hud_remove(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); RemotePlayer *player = getplayer(ref); - if (player == NULL) + if (player == nullptr) return 0; - u32 id = -1; - if (!lua_isnil(L, 2)) - id = lua_tonumber(L, 2); + u32 id = luaL_checkint(L, 2); if (!getServer(L)->hudRemove(player, id)) return 0; @@ -1546,17 +1511,17 @@ int ObjectRef::l_hud_change(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); RemotePlayer *player = getplayer(ref); - if (player == NULL) + if (player == nullptr) return 0; - u32 id = lua_isnumber(L, 2) ? lua_tonumber(L, 2) : -1; + u32 id = luaL_checkint(L, 2); - HudElement *e = player->getHud(id); - if (!e) + HudElement *elem = player->getHud(id); + if (elem == nullptr) return 0; - void *value = NULL; - HudElementStat stat = read_hud_change(L, e, &value); + void *value = nullptr; + HudElementStat stat = read_hud_change(L, elem, &value); getServer(L)->hudChange(player, id, stat, value); @@ -1570,15 +1535,16 @@ int ObjectRef::l_hud_get(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); RemotePlayer *player = getplayer(ref); - if (player == NULL) + if (player == nullptr) return 0; - u32 id = lua_tonumber(L, -1); + u32 id = luaL_checkint(L, 2); - HudElement *e = player->getHud(id); - if (!e) + HudElement *elem = player->getHud(id); + if (elem == nullptr) return 0; - push_hud_element(L, e); + + push_hud_element(L, elem); return 1; } @@ -1588,7 +1554,7 @@ int ObjectRef::l_hud_set_flags(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); RemotePlayer *player = getplayer(ref); - if (player == NULL) + if (player == nullptr) return 0; u32 flags = 0; @@ -1609,12 +1575,13 @@ int ObjectRef::l_hud_set_flags(lua_State *L) return 1; } +// hud_get_flags(self) int ObjectRef::l_hud_get_flags(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); RemotePlayer *player = getplayer(ref); - if (player == NULL) + if (player == nullptr) return 0; lua_newtable(L); @@ -1632,7 +1599,6 @@ int ObjectRef::l_hud_get_flags(lua_State *L) lua_setfield(L, -2, "minimap"); lua_pushboolean(L, player->hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE); lua_setfield(L, -2, "minimap_radar"); - return 1; } @@ -1642,10 +1608,10 @@ int ObjectRef::l_hud_set_hotbar_itemcount(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); RemotePlayer *player = getplayer(ref); - if (player == NULL) + if (player == nullptr) return 0; - s32 hotbar_itemcount = lua_tonumber(L, 2); + s32 hotbar_itemcount = luaL_checkint(L, 2); if (!getServer(L)->hudSetHotbarItemcount(player, hotbar_itemcount)) return 0; @@ -1660,7 +1626,7 @@ int ObjectRef::l_hud_get_hotbar_itemcount(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); RemotePlayer *player = getplayer(ref); - if (player == NULL) + if (player == nullptr) return 0; lua_pushnumber(L, player->getHotbarItemcount()); @@ -1673,7 +1639,7 @@ int ObjectRef::l_hud_set_hotbar_image(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); RemotePlayer *player = getplayer(ref); - if (player == NULL) + if (player == nullptr) return 0; std::string name = readParam(L, 2); @@ -1688,10 +1654,11 @@ int ObjectRef::l_hud_get_hotbar_image(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); RemotePlayer *player = getplayer(ref); - if (player == NULL) + if (player == nullptr) return 0; const std::string &name = player->getHotbarImage(); + lua_pushlstring(L, name.c_str(), name.size()); return 1; } @@ -1702,7 +1669,7 @@ int ObjectRef::l_hud_set_hotbar_selected_image(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); RemotePlayer *player = getplayer(ref); - if (player == NULL) + if (player == nullptr) return 0; std::string name = readParam(L, 2); @@ -1717,44 +1684,45 @@ int ObjectRef::l_hud_get_hotbar_selected_image(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); RemotePlayer *player = getplayer(ref); - if (player == NULL) + if (player == nullptr) return 0; const std::string &name = player->getHotbarSelectedImage(); + lua_pushlstring(L, name.c_str(), name.size()); return 1; } -// set_sky(self, {base_color=, type=, textures=, clouds=, sky_colors={}}) +// set_sky(self, sky_parameters) int ObjectRef::l_set_sky(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); RemotePlayer *player = getplayer(ref); - if (!player) + if (player == nullptr) return 0; + SkyboxParams sky_params = player->getSkyParams(); bool is_colorspec = is_color_table(L, 2); - SkyboxParams skybox_params = player->getSkyParams(); if (lua_istable(L, 2) && !is_colorspec) { lua_getfield(L, 2, "base_color"); if (!lua_isnil(L, -1)) - read_color(L, -1, &skybox_params.bgcolor); + read_color(L, -1, &sky_params.bgcolor); lua_pop(L, 1); lua_getfield(L, 2, "type"); if (!lua_isnil(L, -1)) - skybox_params.type = luaL_checkstring(L, -1); + sky_params.type = luaL_checkstring(L, -1); lua_pop(L, 1); lua_getfield(L, 2, "textures"); - skybox_params.textures.clear(); - if (lua_istable(L, -1) && skybox_params.type == "skybox") { + sky_params.textures.clear(); + if (lua_istable(L, -1) && sky_params.type == "skybox") { lua_pushnil(L); while (lua_next(L, -2) != 0) { // Key is at index -2 and value at index -1 - skybox_params.textures.emplace_back(readParam(L, -1)); + sky_params.textures.emplace_back(readParam(L, -1)); // Removes the value, but keeps the key for iteration lua_pop(L, 1); } @@ -1767,56 +1735,56 @@ int ObjectRef::l_set_sky(lua_State *L) using "regular" or "plain" skybox modes as textures aren't needed. */ - if (skybox_params.textures.size() != 6 && skybox_params.textures.size() > 0) + if (sky_params.textures.size() != 6 && sky_params.textures.size() > 0) throw LuaError("Skybox expects 6 textures!"); - skybox_params.clouds = getboolfield_default(L, 2, - "clouds", skybox_params.clouds); + sky_params.clouds = getboolfield_default(L, 2, + "clouds", sky_params.clouds); lua_getfield(L, 2, "sky_color"); if (lua_istable(L, -1)) { lua_getfield(L, -1, "day_sky"); - read_color(L, -1, &skybox_params.sky_color.day_sky); + read_color(L, -1, &sky_params.sky_color.day_sky); lua_pop(L, 1); lua_getfield(L, -1, "day_horizon"); - read_color(L, -1, &skybox_params.sky_color.day_horizon); + read_color(L, -1, &sky_params.sky_color.day_horizon); lua_pop(L, 1); lua_getfield(L, -1, "dawn_sky"); - read_color(L, -1, &skybox_params.sky_color.dawn_sky); + read_color(L, -1, &sky_params.sky_color.dawn_sky); lua_pop(L, 1); lua_getfield(L, -1, "dawn_horizon"); - read_color(L, -1, &skybox_params.sky_color.dawn_horizon); + read_color(L, -1, &sky_params.sky_color.dawn_horizon); lua_pop(L, 1); lua_getfield(L, -1, "night_sky"); - read_color(L, -1, &skybox_params.sky_color.night_sky); + read_color(L, -1, &sky_params.sky_color.night_sky); lua_pop(L, 1); lua_getfield(L, -1, "night_horizon"); - read_color(L, -1, &skybox_params.sky_color.night_horizon); + read_color(L, -1, &sky_params.sky_color.night_horizon); lua_pop(L, 1); lua_getfield(L, -1, "indoors"); - read_color(L, -1, &skybox_params.sky_color.indoors); + read_color(L, -1, &sky_params.sky_color.indoors); lua_pop(L, 1); // Prevent flickering clouds at dawn/dusk: - skybox_params.fog_sun_tint = video::SColor(255, 255, 255, 255); + sky_params.fog_sun_tint = video::SColor(255, 255, 255, 255); lua_getfield(L, -1, "fog_sun_tint"); - read_color(L, -1, &skybox_params.fog_sun_tint); + read_color(L, -1, &sky_params.fog_sun_tint); lua_pop(L, 1); - skybox_params.fog_moon_tint = video::SColor(255, 255, 255, 255); + sky_params.fog_moon_tint = video::SColor(255, 255, 255, 255); lua_getfield(L, -1, "fog_moon_tint"); - read_color(L, -1, &skybox_params.fog_moon_tint); + read_color(L, -1, &sky_params.fog_moon_tint); lua_pop(L, 1); lua_getfield(L, -1, "fog_tint_type"); if (!lua_isnil(L, -1)) - skybox_params.fog_tint_type = luaL_checkstring(L, -1); + sky_params.fog_tint_type = luaL_checkstring(L, -1); lua_pop(L, 1); // Because we need to leave the "sky_color" table. @@ -1832,14 +1800,14 @@ int ObjectRef::l_set_sky(lua_State *L) StarParams star_params = player->getStarParams(); // Prevent erroneous background colors - skybox_params.bgcolor = video::SColor(255, 255, 255, 255); - read_color(L, 2, &skybox_params.bgcolor); + sky_params.bgcolor = video::SColor(255, 255, 255, 255); + read_color(L, 2, &sky_params.bgcolor); - skybox_params.type = luaL_checkstring(L, 3); + sky_params.type = luaL_checkstring(L, 3); // Preserve old behaviour of the sun, moon and stars // when using the old set_sky call. - if (skybox_params.type == "regular") { + if (sky_params.type == "regular") { sun_params.visible = true; sun_params.sunrise_visible = true; moon_params.visible = true; @@ -1851,31 +1819,31 @@ int ObjectRef::l_set_sky(lua_State *L) star_params.visible = false; } - skybox_params.textures.clear(); + sky_params.textures.clear(); if (lua_istable(L, 4)) { lua_pushnil(L); while (lua_next(L, 4) != 0) { // Key at index -2, and value at index -1 if (lua_isstring(L, -1)) - skybox_params.textures.emplace_back(readParam(L, -1)); + sky_params.textures.emplace_back(readParam(L, -1)); else - skybox_params.textures.emplace_back(""); + sky_params.textures.emplace_back(""); // Remove the value, keep the key for the next iteration lua_pop(L, 1); } } - if (skybox_params.type == "skybox" && skybox_params.textures.size() != 6) + if (sky_params.type == "skybox" && sky_params.textures.size() != 6) throw LuaError("Skybox expects 6 textures."); - skybox_params.clouds = true; + sky_params.clouds = true; if (lua_isboolean(L, 5)) - skybox_params.clouds = readParam(L, 5); + sky_params.clouds = readParam(L, 5); getServer(L)->setSun(player, sun_params); getServer(L)->setMoon(player, moon_params); getServer(L)->setStars(player, star_params); } - getServer(L)->setSky(player, skybox_params); + getServer(L)->setSky(player, sky_params); lua_pushboolean(L, true); return 1; } @@ -1886,18 +1854,17 @@ int ObjectRef::l_get_sky(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); RemotePlayer *player = getplayer(ref); - - if (!player) + if (player == nullptr) return 0; - SkyboxParams skybox_params; - skybox_params = player->getSkyParams(); + + SkyboxParams skybox_params = player->getSkyParams(); push_ARGB8(L, skybox_params.bgcolor); lua_pushlstring(L, skybox_params.type.c_str(), skybox_params.type.size()); lua_newtable(L); s16 i = 1; - for (const std::string& texture : skybox_params.textures) { + for (const std::string &texture : skybox_params.textures) { lua_pushlstring(L, texture.c_str(), texture.size()); lua_rawseti(L, -2, i++); } @@ -1911,11 +1878,10 @@ int ObjectRef::l_get_sky_color(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); RemotePlayer *player = getplayer(ref); - - if (!player) + if (player == nullptr) return 0; - const SkyboxParams& skybox_params = player->getSkyParams(); + const SkyboxParams &skybox_params = player->getSkyParams(); lua_newtable(L); if (skybox_params.type == "regular") { @@ -1943,18 +1909,16 @@ int ObjectRef::l_get_sky_color(lua_State *L) return 1; } -// set_sun(self, {visible, texture=, tonemap=, sunrise=, rotation=, scale=}) +// set_sun(self, sun_parameters) int ObjectRef::l_set_sun(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); RemotePlayer *player = getplayer(ref); - if (!player) - return 0; - - if (!lua_istable(L, 2)) + if (player == nullptr) return 0; + luaL_checktype(L, 2, LUA_TTABLE); SunParams sun_params = player->getSunParams(); sun_params.visible = getboolfield_default(L, 2, @@ -1981,8 +1945,9 @@ int ObjectRef::l_get_sun(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); RemotePlayer *player = getplayer(ref); - if (!player) + if (player == nullptr) return 0; + const SunParams &sun_params = player->getSunParams(); lua_newtable(L); @@ -1998,21 +1963,19 @@ int ObjectRef::l_get_sun(lua_State *L) lua_setfield(L, -2, "sunrise_visible"); lua_pushnumber(L, sun_params.scale); lua_setfield(L, -2, "scale"); - return 1; } -// set_moon(self, {visible, texture=, tonemap=, sunrise=, rotation=, scale=}) +// set_moon(self, moon_parameters) int ObjectRef::l_set_moon(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); RemotePlayer *player = getplayer(ref); - if (!player) - return 0; - if (!lua_istable(L, 2)) + if (player == nullptr) return 0; + luaL_checktype(L, 2, LUA_TTABLE); MoonParams moon_params = player->getMoonParams(); moon_params.visible = getboolfield_default(L, 2, @@ -2035,8 +1998,9 @@ int ObjectRef::l_get_moon(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); RemotePlayer *player = getplayer(ref); - if (!player) + if (player == nullptr) return 0; + const MoonParams &moon_params = player->getMoonParams(); lua_newtable(L); @@ -2048,21 +2012,19 @@ int ObjectRef::l_get_moon(lua_State *L) lua_setfield(L, -2, "tonemap"); lua_pushnumber(L, moon_params.scale); lua_setfield(L, -2, "scale"); - return 1; } -// set_stars(self, {visible, count=, starcolor=, rotation=, scale=}) +// set_stars(self, star_parameters) int ObjectRef::l_set_stars(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); RemotePlayer *player = getplayer(ref); - if (!player) - return 0; - if (!lua_istable(L, 2)) + if (player == nullptr) return 0; + luaL_checktype(L, 2, LUA_TTABLE); StarParams star_params = player->getStarParams(); star_params.visible = getboolfield_default(L, 2, @@ -2089,8 +2051,9 @@ int ObjectRef::l_get_stars(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); RemotePlayer *player = getplayer(ref); - if (!player) + if (player == nullptr) return 0; + const StarParams &star_params = player->getStarParams(); lua_newtable(L); @@ -2102,21 +2065,19 @@ int ObjectRef::l_get_stars(lua_State *L) lua_setfield(L, -2, "star_color"); lua_pushnumber(L, star_params.scale); lua_setfield(L, -2, "scale"); - return 1; } -// set_clouds(self, {density=, color=, ambient=, height=, thickness=, speed=}) +// set_clouds(self, cloud_parameters) int ObjectRef::l_set_clouds(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); RemotePlayer *player = getplayer(ref); - if (!player) - return 0; - if (!lua_istable(L, 2)) + if (player == nullptr) return 0; + luaL_checktype(L, 2, LUA_TTABLE); CloudParams cloud_params = player->getCloudParams(); cloud_params.density = getfloatfield_default(L, 2, "density", cloud_params.density); @@ -2152,8 +2113,9 @@ int ObjectRef::l_get_clouds(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); RemotePlayer *player = getplayer(ref); - if (!player) + if (player == nullptr) return 0; + const CloudParams &cloud_params = player->getCloudParams(); lua_newtable(L); @@ -2173,25 +2135,27 @@ int ObjectRef::l_get_clouds(lua_State *L) lua_pushnumber(L, cloud_params.speed.Y); lua_setfield(L, -2, "y"); lua_setfield(L, -2, "speed"); - return 1; } -// override_day_night_ratio(self, brightness=0...1) +// override_day_night_ratio(self, ratio) int ObjectRef::l_override_day_night_ratio(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); RemotePlayer *player = getplayer(ref); - if (player == NULL) + if (player == nullptr) return 0; bool do_override = false; float ratio = 0.0f; + if (!lua_isnil(L, 2)) { do_override = true; ratio = readParam(L, 2); + luaL_argcheck(L, ratio >= 0.0f && ratio <= 1.0f, 1, + "value must be between 0 and 1"); } getServer(L)->overrideDayNightRatio(player, do_override, ratio); @@ -2205,7 +2169,7 @@ int ObjectRef::l_get_day_night_ratio(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); RemotePlayer *player = getplayer(ref); - if (player == NULL) + if (player == nullptr) return 0; bool do_override; @@ -2220,26 +2184,18 @@ int ObjectRef::l_get_day_night_ratio(lua_State *L) return 1; } -// set_minimap_modes(self, modes, modeindex) +// set_minimap_modes(self, modes, selected_mode) int ObjectRef::l_set_minimap_modes(lua_State *L) { NO_MAP_LOCK_REQUIRED; - - // Arg 1 should be a player ObjectRef *ref = checkobject(L, 1); RemotePlayer *player = getplayer(ref); - if (player == NULL) + if (player == nullptr) return 0; - // Arg 2 should be a table (of tables) - if (!lua_istable(L, 2)) { - return 0; - } - - // Arg 3 should be a number - s16 wantedmode = lua_tonumber(L, 3); - + luaL_checktype(L, 2, LUA_TTABLE); std::vector modes; + s16 selected_mode = luaL_checkint(L, 3); lua_pushnil(L); while (lua_next(L, 2) != 0) { @@ -2277,31 +2233,28 @@ int ObjectRef::l_set_minimap_modes(lua_State *L) } lua_pop(L, 1); // Remove key - getServer(L)->SendMinimapModes(player->getPeerId(), modes, wantedmode); + getServer(L)->SendMinimapModes(player->getPeerId(), modes, selected_mode); return 0; } ObjectRef::ObjectRef(ServerActiveObject *object): m_object(object) -{ - //infostream<<"ObjectRef created for id="<getId()< formspec + // get_inventory_formspec(self) static int l_get_inventory_formspec(lua_State *L); // set_formspec_prepend(self, formspec) static int l_set_formspec_prepend(lua_State *L); - // get_formspec_prepend(self) -> formspec + // get_formspec_prepend(self) static int l_get_formspec_prepend(lua_State *L); // get_player_control(self) @@ -321,7 +313,7 @@ private: // hud_get_hotbar_selected_image(self) static int l_hud_get_hotbar_selected_image(lua_State *L); - // set_sky({base_color=, type=, textures=, clouds=, sky_colors={}}) + // set_sky(self, sky_parameters) static int l_set_sky(lua_State *L); // get_sky(self) @@ -330,25 +322,25 @@ private: // get_sky_color(self) static int l_get_sky_color(lua_State* L); - // set_sun(self, {visible, texture=, tonemap=, sunrise=, rotation=, scale=}) + // set_sun(self, sun_parameters) static int l_set_sun(lua_State *L); // get_sun(self) static int l_get_sun(lua_State *L); - // set_moon(self, {visible, texture=, tonemap=, rotation, scale=}) + // set_moon(self, moon_parameters) static int l_set_moon(lua_State *L); // get_moon(self) static int l_get_moon(lua_State *L); - // set_stars(self, {visible, count=, starcolor=, rotation, scale=}) + // set_stars(self, star_parameters) static int l_set_stars(lua_State *L); // get_stars(self) static int l_get_stars(lua_State *L); - // set_clouds(self, {density=, color=, ambient=, height=, thickness=, speed=}) + // set_clouds(self, cloud_parameters) static int l_set_clouds(lua_State *L); // get_clouds(self) @@ -360,13 +352,13 @@ private: // get_day_night_ratio(self) static int l_get_day_night_ratio(lua_State *L); - // set_local_animation(self, {stand/idle}, {walk}, {dig}, {walk+dig}, frame_speed) + // set_local_animation(self, idle, walk, dig, walk_while_dig, frame_speed) static int l_set_local_animation(lua_State *L); // get_local_animation(self) static int l_get_local_animation(lua_State *L); - // set_eye_offset(self, v3f first pv, v3f third pv) + // set_eye_offset(self, firstperson, thirdperson) static int l_set_eye_offset(lua_State *L); // get_eye_offset(self) From 8bc7d49b32afb215d262d8282988adf9e836396c Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein Date: Thu, 22 Oct 2020 20:05:18 +0200 Subject: [PATCH 156/266] Added Nuke --- builtin/client/cheats/init.lua | 1 + builtin/client/cheats/world.lua | 17 +++++++++++++++++ builtin/settingtypes.txt | 2 ++ src/defaultsettings.cpp | 1 + 4 files changed, 21 insertions(+) diff --git a/builtin/client/cheats/init.lua b/builtin/client/cheats/init.lua index c579f2b89..8d634a766 100644 --- a/builtin/client/cheats/init.lua +++ b/builtin/client/cheats/init.lua @@ -38,6 +38,7 @@ core.cheats = { ["BlockWater"] = "block_water", ["PlaceOnTop"] = "autotnt", ["Replace"] = "replace" + ["Nuke"] = "nuke" }, ["Exploit"] = { ["EntitySpeed"] = "entity_speed", diff --git a/builtin/client/cheats/world.lua b/builtin/client/cheats/world.lua index 5b97b206b..6cbdd67fc 100644 --- a/builtin/client/cheats/world.lua +++ b/builtin/client/cheats/world.lua @@ -48,6 +48,23 @@ core.register_globalstep(function(dtime) end end end + if core.settings:get_bool("nuke") then + local i = 0 + for x = pos.x - 5, pos.x + 5 do + for y = pos.y - 5, pos.y + 5 do + for z = pos.z - 5, pos.z + 5 do + local p = vector.new(x, y, z) + local node = core.get_node_or_nil(p) + local def = node and core.get_node_def(node.name) + if def and def.diggable then + if i > nodes_per_tick then return end + core.dig_node(p) + i = i + 1 + end + end + end + end + end end) diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index a7a92291d..15c62ead1 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -2306,3 +2306,5 @@ dont_point_nodes (ThroughWalls) bool false strip (Strip) bool false autorefill (AutoRefill) bool false + +nuke (Nuke) bool false diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 6cc28ac32..bde6505a0 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -113,6 +113,7 @@ void set_default_settings(Settings *settings) settings->setDefault("dont_point_nodes", "false"); settings->setDefault("strip", "false"); settings->setDefault("autorefill", "false"); + settings->setDefault("nuke", "false"); // Keymap settings->setDefault("remote_port", "30000"); From 6652d7ac2a463581aa53c1599b7b93762422ff0f Mon Sep 17 00:00:00 2001 From: realOneplustwo Date: Thu, 22 Oct 2020 17:36:31 -0700 Subject: [PATCH 157/266] Add Block Formspec Hack --- builtin/client/cheats/init.lua | 1 + builtin/settingtypes.txt | 2 ++ src/defaultsettings.cpp | 1 + src/gui/cheatMenu.h | 2 +- src/network/clientpackethandler.cpp | 4 ++++ 5 files changed, 9 insertions(+), 1 deletion(-) diff --git a/builtin/client/cheats/init.lua b/builtin/client/cheats/init.lua index a7be83cee..5e94ed86c 100644 --- a/builtin/client/cheats/init.lua +++ b/builtin/client/cheats/init.lua @@ -51,6 +51,7 @@ core.cheats = { ["PointLiquids"] = "point_liquids", ["PrivBypass"] = "priv_bypass", ["AutoRespawn"] = "autorespawn", + ["BlockFormspec"] = "block_formspec" }, ["Chat"] = { ["IgnoreStatus"] = "ignore_status_messages", diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index ebd0ad621..f13492146 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -2299,6 +2299,8 @@ replace (Replace) bool false crystal_pvp (CrystalPvP) bool false +block_formspec (BlockFormSpec) bool false + [Cheat Menu] # Font to use for cheat menu diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 525f94678..6a37ebab9 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -118,6 +118,7 @@ void set_default_settings(Settings *settings) settings->setDefault("crystal_pvp", "false"); settings->setDefault("autototem", "false"); settings->setDefault("dont_point_nodes", "false"); + settings->setDefault("block_formspec", "false"); // Keymap settings->setDefault("remote_port", "30000"); diff --git a/src/gui/cheatMenu.h b/src/gui/cheatMenu.h index c25af5ed2..513d217b6 100644 --- a/src/gui/cheatMenu.h +++ b/src/gui/cheatMenu.h @@ -72,7 +72,7 @@ private: video::SColor m_font_color = video::SColor(195, 255, 255, 255); video::SColor m_selected_font_color = video::SColor(235, 255, 255, 255); - FontMode fontStringToEnum(std::string str); + FontMode fontStringToEnum(std::string str) Client *m_client; diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index f0fb09fad..f3c2bc134 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -957,6 +957,10 @@ void Client::handleCommand_DetachedInventory(NetworkPacket* pkt) void Client::handleCommand_ShowFormSpec(NetworkPacket* pkt) { + if (g_settings->getBool("block_formspec")) { + return; + } + std::string formspec = pkt->readLongString(); std::string formname; From 8e9e76a5076db0042773c6b925698f018a208b29 Mon Sep 17 00:00:00 2001 From: realOneplustwo Date: Fri, 23 Oct 2020 08:34:58 -0700 Subject: [PATCH 158/266] Revert "Add Block Formspec Hack" This reverts commit 6652d7ac2a463581aa53c1599b7b93762422ff0f. --- builtin/client/cheats/init.lua | 1 - builtin/settingtypes.txt | 2 -- src/defaultsettings.cpp | 1 - src/gui/cheatMenu.h | 2 +- src/network/clientpackethandler.cpp | 4 ---- 5 files changed, 1 insertion(+), 9 deletions(-) diff --git a/builtin/client/cheats/init.lua b/builtin/client/cheats/init.lua index 5e94ed86c..a7be83cee 100644 --- a/builtin/client/cheats/init.lua +++ b/builtin/client/cheats/init.lua @@ -51,7 +51,6 @@ core.cheats = { ["PointLiquids"] = "point_liquids", ["PrivBypass"] = "priv_bypass", ["AutoRespawn"] = "autorespawn", - ["BlockFormspec"] = "block_formspec" }, ["Chat"] = { ["IgnoreStatus"] = "ignore_status_messages", diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index f13492146..ebd0ad621 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -2299,8 +2299,6 @@ replace (Replace) bool false crystal_pvp (CrystalPvP) bool false -block_formspec (BlockFormSpec) bool false - [Cheat Menu] # Font to use for cheat menu diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 6a37ebab9..525f94678 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -118,7 +118,6 @@ void set_default_settings(Settings *settings) settings->setDefault("crystal_pvp", "false"); settings->setDefault("autototem", "false"); settings->setDefault("dont_point_nodes", "false"); - settings->setDefault("block_formspec", "false"); // Keymap settings->setDefault("remote_port", "30000"); diff --git a/src/gui/cheatMenu.h b/src/gui/cheatMenu.h index 513d217b6..c25af5ed2 100644 --- a/src/gui/cheatMenu.h +++ b/src/gui/cheatMenu.h @@ -72,7 +72,7 @@ private: video::SColor m_font_color = video::SColor(195, 255, 255, 255); video::SColor m_selected_font_color = video::SColor(235, 255, 255, 255); - FontMode fontStringToEnum(std::string str) + FontMode fontStringToEnum(std::string str); Client *m_client; diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index f3c2bc134..f0fb09fad 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -957,10 +957,6 @@ void Client::handleCommand_DetachedInventory(NetworkPacket* pkt) void Client::handleCommand_ShowFormSpec(NetworkPacket* pkt) { - if (g_settings->getBool("block_formspec")) { - return; - } - std::string formspec = pkt->readLongString(); std::string formname; From 707c8c1e95d8db2d84909e7957b4dc9138e05599 Mon Sep 17 00:00:00 2001 From: Vitaliy Date: Sun, 25 Oct 2020 20:01:03 +0300 Subject: [PATCH 159/266] Shaders for Android (GLES 2) (#10506) Shader support for OpenGL ES 2 devices (Android) Co-authored-by: sfan5 --- build/android/app/build.gradle | 5 +- builtin/mainmenu/tab_settings.lua | 11 +- builtin/settingtypes.txt | 4 +- .../3d_interlaced_merge/opengl_fragment.glsl | 4 +- .../3d_interlaced_merge/opengl_vertex.glsl | 7 +- .../default_shader/opengl_fragment.glsl | 4 +- .../shaders/default_shader/opengl_vertex.glsl | 8 +- .../minimap_shader/opengl_fragment.glsl | 7 +- .../shaders/minimap_shader/opengl_vertex.glsl | 10 +- .../shaders/nodes_shader/opengl_fragment.glsl | 13 +- .../shaders/nodes_shader/opengl_vertex.glsl | 42 ++-- .../object_shader/opengl_fragment.glsl | 11 +- .../shaders/object_shader/opengl_vertex.glsl | 25 +-- .../selection_shader/opengl_fragment.glsl | 7 +- .../selection_shader/opengl_vertex.glsl | 9 +- games/devtest/mods/basenodes/init.lua | 4 + src/client/shader.cpp | 182 +++++++++++++----- 17 files changed, 233 insertions(+), 120 deletions(-) diff --git a/build/android/app/build.gradle b/build/android/app/build.gradle index 812726030..fccb7b3b4 100644 --- a/build/android/app/build.gradle +++ b/build/android/app/build.gradle @@ -64,10 +64,9 @@ task prepareAssets() { copy { from "${projRoot}/builtin" into "${assetsFolder}/builtin" } - /*copy { - // ToDo: fix Minetest shaders that currently don't work with OpenGL ES + copy { from "${projRoot}/client/shaders" into "${assetsFolder}/client/shaders" - }*/ + } copy { from "../native/deps/Android/Irrlicht/shaders" into "${assetsFolder}/client/shaders/Irrlicht" } diff --git a/builtin/mainmenu/tab_settings.lua b/builtin/mainmenu/tab_settings.lua index 8a7445394..29744048a 100644 --- a/builtin/mainmenu/tab_settings.lua +++ b/builtin/mainmenu/tab_settings.lua @@ -154,15 +154,18 @@ local function formspec(tabview, name, tabdata) "box[8,0;3.75,4.5;#999999]" local video_driver = core.settings:get("video_driver") - local shaders_supported = video_driver == "opengl" - local shaders_enabled = false - if shaders_supported then - shaders_enabled = core.settings:get_bool("enable_shaders") + local shaders_enabled = core.settings:get_bool("enable_shaders") + if video_driver == "opengl" then tab_string = tab_string .. "checkbox[8.25,0;cb_shaders;" .. fgettext("Shaders") .. ";" .. tostring(shaders_enabled) .. "]" + elseif video_driver == "ogles2" then + tab_string = tab_string .. + "checkbox[8.25,0;cb_shaders;" .. fgettext("Shaders (experimental)") .. ";" + .. tostring(shaders_enabled) .. "]" else core.settings:set_bool("enable_shaders", false) + shaders_enabled = false tab_string = tab_string .. "label[8.38,0.2;" .. core.colorize("#888888", fgettext("Shaders (unavailable)")) .. "]" diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 27f375693..36446f808 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -658,8 +658,8 @@ texture_path (Texture path) path # The rendering back-end for Irrlicht. # A restart is required after changing this. # Note: On Android, stick with OGLES1 if unsure! App may fail to start otherwise. -# On other platforms, OpenGL is recommended, and it’s the only driver with -# shader support currently. +# On other platforms, OpenGL is recommended. +# Shaders are supported by OpenGL (desktop only) and OGLES2 (experimental) video_driver (Video driver) enum opengl null,software,burningsvideo,direct3d8,direct3d9,opengl,ogles1,ogles2 # Radius of cloud area stated in number of 64 node cloud squares. diff --git a/client/shaders/3d_interlaced_merge/opengl_fragment.glsl b/client/shaders/3d_interlaced_merge/opengl_fragment.glsl index 25945ad7f..7cba61b39 100644 --- a/client/shaders/3d_interlaced_merge/opengl_fragment.glsl +++ b/client/shaders/3d_interlaced_merge/opengl_fragment.glsl @@ -6,9 +6,11 @@ uniform sampler2D textureFlags; #define rightImage normalTexture #define maskImage textureFlags +varying mediump vec2 varTexCoord; + void main(void) { - vec2 uv = gl_TexCoord[0].st; + vec2 uv = varTexCoord.st; vec4 left = texture2D(leftImage, uv).rgba; vec4 right = texture2D(rightImage, uv).rgba; vec4 mask = texture2D(maskImage, uv).rgba; diff --git a/client/shaders/3d_interlaced_merge/opengl_vertex.glsl b/client/shaders/3d_interlaced_merge/opengl_vertex.glsl index 4e0b2b125..860049481 100644 --- a/client/shaders/3d_interlaced_merge/opengl_vertex.glsl +++ b/client/shaders/3d_interlaced_merge/opengl_vertex.glsl @@ -1,6 +1,7 @@ +varying mediump vec2 varTexCoord; + void main(void) { - gl_TexCoord[0] = gl_MultiTexCoord0; - gl_Position = gl_Vertex; - gl_FrontColor = gl_BackColor = gl_Color; + varTexCoord = inTexCoord0; + gl_Position = inVertexPosition; } diff --git a/client/shaders/default_shader/opengl_fragment.glsl b/client/shaders/default_shader/opengl_fragment.glsl index 925ab6e1d..5018ac6ea 100644 --- a/client/shaders/default_shader/opengl_fragment.glsl +++ b/client/shaders/default_shader/opengl_fragment.glsl @@ -1,4 +1,6 @@ +varying lowp vec4 varColor; + void main(void) { - gl_FragColor = gl_Color; + gl_FragColor = varColor; } diff --git a/client/shaders/default_shader/opengl_vertex.glsl b/client/shaders/default_shader/opengl_vertex.glsl index d0b16c8b0..d95a3c2d3 100644 --- a/client/shaders/default_shader/opengl_vertex.glsl +++ b/client/shaders/default_shader/opengl_vertex.glsl @@ -1,9 +1,7 @@ -uniform mat4 mWorldViewProj; +varying lowp vec4 varColor; void main(void) { - gl_TexCoord[0] = gl_MultiTexCoord0; - gl_Position = mWorldViewProj * gl_Vertex; - - gl_FrontColor = gl_BackColor = gl_Color; + gl_Position = mWorldViewProj * inVertexPosition; + varColor = inVertexColor; } diff --git a/client/shaders/minimap_shader/opengl_fragment.glsl b/client/shaders/minimap_shader/opengl_fragment.glsl index fa4f9cb1a..cef359e8a 100644 --- a/client/shaders/minimap_shader/opengl_fragment.glsl +++ b/client/shaders/minimap_shader/opengl_fragment.glsl @@ -2,9 +2,12 @@ uniform sampler2D baseTexture; uniform sampler2D normalTexture; uniform vec3 yawVec; +varying lowp vec4 varColor; +varying mediump vec2 varTexCoord; + void main (void) { - vec2 uv = gl_TexCoord[0].st; + vec2 uv = varTexCoord.st; //texture sampling rate const float step = 1.0 / 256.0; @@ -27,6 +30,6 @@ void main (void) vec3 color = (1.1 * diffuse + 0.05 * height + 0.5 * specular) * base.rgb; vec4 col = vec4(color.rgb, base.a); - col *= gl_Color; + col *= varColor; gl_FragColor = vec4(col.rgb, base.a); } diff --git a/client/shaders/minimap_shader/opengl_vertex.glsl b/client/shaders/minimap_shader/opengl_vertex.glsl index 88f9356d5..1a9491805 100644 --- a/client/shaders/minimap_shader/opengl_vertex.glsl +++ b/client/shaders/minimap_shader/opengl_vertex.glsl @@ -1,9 +1,11 @@ -uniform mat4 mWorldViewProj; uniform mat4 mWorld; +varying lowp vec4 varColor; +varying mediump vec2 varTexCoord; + void main(void) { - gl_TexCoord[0] = gl_MultiTexCoord0; - gl_Position = mWorldViewProj * gl_Vertex; - gl_FrontColor = gl_BackColor = gl_Color; + varTexCoord = inTexCoord0.st; + gl_Position = mWorldViewProj * inVertexPosition; + varColor = inVertexColor; } diff --git a/client/shaders/nodes_shader/opengl_fragment.glsl b/client/shaders/nodes_shader/opengl_fragment.glsl index 36d47d1f5..82c87073a 100644 --- a/client/shaders/nodes_shader/opengl_fragment.glsl +++ b/client/shaders/nodes_shader/opengl_fragment.glsl @@ -15,11 +15,12 @@ varying vec3 vPosition; // cameraOffset + worldPosition (for large coordinates the limits of float // precision must be considered). varying vec3 worldPosition; - +varying lowp vec4 varColor; +varying mediump vec2 varTexCoord; varying vec3 eyeVec; const float fogStart = FOG_START; -const float fogShadingParameter = 1 / ( 1 - fogStart); +const float fogShadingParameter = 1.0 / ( 1.0 - fogStart); #ifdef ENABLE_TONE_MAPPING @@ -56,13 +57,13 @@ vec4 applyToneMapping(vec4 color) void main(void) { vec3 color; - vec2 uv = gl_TexCoord[0].st; + vec2 uv = varTexCoord.st; vec4 base = texture2D(baseTexture, uv).rgba; - #ifdef USE_DISCARD // If alpha is zero, we can just discard the pixel. This fixes transparency - // on GPUs like GC7000L, where GL_ALPHA_TEST is not implemented in mesa. + // on GPUs like GC7000L, where GL_ALPHA_TEST is not implemented in mesa, + // and also on GLES 2, where GL_ALPHA_TEST is missing entirely. if (base.a == 0.0) { discard; } @@ -70,7 +71,7 @@ void main(void) color = base.rgb; - vec4 col = vec4(color.rgb * gl_Color.rgb, 1.0); + vec4 col = vec4(color.rgb * varColor.rgb, 1.0); #ifdef ENABLE_TONE_MAPPING col = applyToneMapping(col); diff --git a/client/shaders/nodes_shader/opengl_vertex.glsl b/client/shaders/nodes_shader/opengl_vertex.glsl index 56bff09a8..cb344f6e6 100644 --- a/client/shaders/nodes_shader/opengl_vertex.glsl +++ b/client/shaders/nodes_shader/opengl_vertex.glsl @@ -1,4 +1,3 @@ -uniform mat4 mWorldViewProj; uniform mat4 mWorld; // Color of the light emitted by the sun. @@ -16,7 +15,8 @@ varying vec3 vPosition; // cameraOffset + worldPosition (for large coordinates the limits of float // precision must be considered). varying vec3 worldPosition; - +varying lowp vec4 varColor; +varying mediump vec2 varTexCoord; varying vec3 eyeVec; // Color of the light emitted by the light sources. @@ -81,13 +81,13 @@ float snoise(vec3 p) void main(void) { - gl_TexCoord[0] = gl_MultiTexCoord0; + varTexCoord = inTexCoord0.st; float disp_x; float disp_z; // OpenGL < 4.3 does not support continued preprocessor lines #if (MATERIAL_TYPE == TILE_MATERIAL_WAVING_LEAVES && ENABLE_WAVING_LEAVES) || (MATERIAL_TYPE == TILE_MATERIAL_WAVING_PLANTS && ENABLE_WAVING_PLANTS) - vec4 pos2 = mWorld * gl_Vertex; + vec4 pos2 = mWorld * inVertexPosition; float tOffset = (pos2.x + pos2.y) * 0.001 + pos2.z * 0.002; disp_x = (smoothTriangleWave(animationTimer * 23.0 + tOffset) + smoothTriangleWave(animationTimer * 11.0 + tOffset)) * 0.4; @@ -96,43 +96,43 @@ void main(void) smoothTriangleWave(animationTimer * 13.0 + tOffset)) * 0.5; #endif - worldPosition = (mWorld * gl_Vertex).xyz; + worldPosition = (mWorld * inVertexPosition).xyz; // OpenGL < 4.3 does not support continued preprocessor lines #if (MATERIAL_TYPE == TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT || MATERIAL_TYPE == TILE_MATERIAL_WAVING_LIQUID_OPAQUE || MATERIAL_TYPE == TILE_MATERIAL_WAVING_LIQUID_BASIC) && ENABLE_WAVING_WATER // Generate waves with Perlin-type noise. // The constants are calibrated such that they roughly // correspond to the old sine waves. - vec4 pos = gl_Vertex; + vec4 pos = inVertexPosition; vec3 wavePos = worldPosition + cameraOffset; // The waves are slightly compressed along the z-axis to get // wave-fronts along the x-axis. - wavePos.x /= WATER_WAVE_LENGTH * 3; - wavePos.z /= WATER_WAVE_LENGTH * 2; - wavePos.z += animationTimer * WATER_WAVE_SPEED * 10; - pos.y += (snoise(wavePos) - 1) * WATER_WAVE_HEIGHT * 5; + wavePos.x /= WATER_WAVE_LENGTH * 3.0; + wavePos.z /= WATER_WAVE_LENGTH * 2.0; + wavePos.z += animationTimer * WATER_WAVE_SPEED * 10.0; + pos.y += (snoise(wavePos) - 1.0) * WATER_WAVE_HEIGHT * 5.0; gl_Position = mWorldViewProj * pos; #elif MATERIAL_TYPE == TILE_MATERIAL_WAVING_LEAVES && ENABLE_WAVING_LEAVES - vec4 pos = gl_Vertex; + vec4 pos = inVertexPosition; pos.x += disp_x; pos.y += disp_z * 0.1; pos.z += disp_z; gl_Position = mWorldViewProj * pos; #elif MATERIAL_TYPE == TILE_MATERIAL_WAVING_PLANTS && ENABLE_WAVING_PLANTS - vec4 pos = gl_Vertex; - if (gl_TexCoord[0].y < 0.05) { + vec4 pos = inVertexPosition; + if (varTexCoord.y < 0.05) { pos.x += disp_x; pos.z += disp_z; } gl_Position = mWorldViewProj * pos; #else - gl_Position = mWorldViewProj * gl_Vertex; + gl_Position = mWorldViewProj * inVertexPosition; #endif vPosition = gl_Position.xyz; - eyeVec = -(gl_ModelViewMatrix * gl_Vertex).xyz; + eyeVec = -(mWorldView * inVertexPosition).xyz; // Calculate color. // Red, green and blue components are pre-multiplied with @@ -141,16 +141,16 @@ void main(void) // The pre-baked colors are halved to prevent overflow. vec4 color; // The alpha gives the ratio of sunlight in the incoming light. - float nightRatio = 1 - gl_Color.a; - color.rgb = gl_Color.rgb * (gl_Color.a * dayLight.rgb + - nightRatio * artificialLight.rgb) * 2; - color.a = 1; + float nightRatio = 1.0 - inVertexColor.a; + color.rgb = inVertexColor.rgb * (inVertexColor.a * dayLight.rgb + + nightRatio * artificialLight.rgb) * 2.0; + color.a = 1.0; // Emphase blue a bit in darker places // See C++ implementation in mapblock_mesh.cpp final_color_blend() - float brightness = (color.r + color.g + color.b) / 3; + float brightness = (color.r + color.g + color.b) / 3.0; color.b += max(0.0, 0.021 - abs(0.2 * brightness - 0.021) + 0.07 * brightness); - gl_FrontColor = gl_BackColor = clamp(color, 0.0, 1.0); + varColor = clamp(color, 0.0, 1.0); } diff --git a/client/shaders/object_shader/opengl_fragment.glsl b/client/shaders/object_shader/opengl_fragment.glsl index 86d5c1c92..7ac182a63 100644 --- a/client/shaders/object_shader/opengl_fragment.glsl +++ b/client/shaders/object_shader/opengl_fragment.glsl @@ -8,6 +8,8 @@ uniform vec3 eyePosition; varying vec3 vNormal; varying vec3 vPosition; varying vec3 worldPosition; +varying lowp vec4 varColor; +varying mediump vec2 varTexCoord; varying vec3 eyeVec; varying float vIDiff; @@ -15,7 +17,7 @@ varying float vIDiff; const float e = 2.718281828459; const float BS = 10.0; const float fogStart = FOG_START; -const float fogShadingParameter = 1 / ( 1 - fogStart); +const float fogShadingParameter = 1.0 / (1.0 - fogStart); #ifdef ENABLE_TONE_MAPPING @@ -52,13 +54,14 @@ vec4 applyToneMapping(vec4 color) void main(void) { vec3 color; - vec2 uv = gl_TexCoord[0].st; + vec2 uv = varTexCoord.st; vec4 base = texture2D(baseTexture, uv).rgba; #ifdef USE_DISCARD // If alpha is zero, we can just discard the pixel. This fixes transparency - // on GPUs like GC7000L, where GL_ALPHA_TEST is not implemented in mesa. + // on GPUs like GC7000L, where GL_ALPHA_TEST is not implemented in mesa, + // and also on GLES 2, where GL_ALPHA_TEST is missing entirely. if (base.a == 0.0) { discard; } @@ -68,7 +71,7 @@ void main(void) vec4 col = vec4(color.rgb, base.a); - col.rgb *= gl_Color.rgb; + col.rgb *= varColor.rgb; col.rgb *= emissiveColor.rgb * vIDiff; diff --git a/client/shaders/object_shader/opengl_vertex.glsl b/client/shaders/object_shader/opengl_vertex.glsl index f8c1cd932..e44984dc8 100644 --- a/client/shaders/object_shader/opengl_vertex.glsl +++ b/client/shaders/object_shader/opengl_vertex.glsl @@ -1,4 +1,3 @@ -uniform mat4 mWorldViewProj; uniform mat4 mWorld; uniform vec3 eyePosition; @@ -7,6 +6,8 @@ uniform float animationTimer; varying vec3 vNormal; varying vec3 vPosition; varying vec3 worldPosition; +varying lowp vec4 varColor; +varying mediump vec2 varTexCoord; varying vec3 eyeVec; varying float vIDiff; @@ -18,31 +19,31 @@ float directional_ambient(vec3 normal) { vec3 v = normal * normal; - if (normal.y < 0) - return dot(v, vec3(0.670820f, 0.447213f, 0.836660f)); + if (normal.y < 0.0) + return dot(v, vec3(0.670820, 0.447213, 0.836660)); - return dot(v, vec3(0.670820f, 1.000000f, 0.836660f)); + return dot(v, vec3(0.670820, 1.000000, 0.836660)); } void main(void) { - gl_TexCoord[0] = gl_TextureMatrix[0] * gl_MultiTexCoord0; - gl_Position = mWorldViewProj * gl_Vertex; + varTexCoord = (mTexture * inTexCoord0).st; + gl_Position = mWorldViewProj * inVertexPosition; vPosition = gl_Position.xyz; - vNormal = gl_Normal; - worldPosition = (mWorld * gl_Vertex).xyz; - eyeVec = -(gl_ModelViewMatrix * gl_Vertex).xyz; + vNormal = inVertexNormal; + worldPosition = (mWorld * inVertexPosition).xyz; + eyeVec = -(mWorldView * inVertexPosition).xyz; #if (MATERIAL_TYPE == TILE_MATERIAL_PLAIN) || (MATERIAL_TYPE == TILE_MATERIAL_PLAIN_ALPHA) vIDiff = 1.0; #else // This is intentional comparison with zero without any margin. // If normal is not equal to zero exactly, then we assume it's a valid, just not normalized vector - vIDiff = length(gl_Normal) == 0.0 + vIDiff = length(inVertexNormal) == 0.0 ? 1.0 - : directional_ambient(normalize(gl_Normal)); + : directional_ambient(normalize(inVertexNormal)); #endif - gl_FrontColor = gl_BackColor = gl_Color; + varColor = inVertexColor; } diff --git a/client/shaders/selection_shader/opengl_fragment.glsl b/client/shaders/selection_shader/opengl_fragment.glsl index c679d0e12..35b1f8902 100644 --- a/client/shaders/selection_shader/opengl_fragment.glsl +++ b/client/shaders/selection_shader/opengl_fragment.glsl @@ -1,9 +1,12 @@ uniform sampler2D baseTexture; +varying lowp vec4 varColor; +varying mediump vec2 varTexCoord; + void main(void) { - vec2 uv = gl_TexCoord[0].st; + vec2 uv = varTexCoord.st; vec4 color = texture2D(baseTexture, uv); - color.rgb *= gl_Color.rgb; + color.rgb *= varColor.rgb; gl_FragColor = color; } diff --git a/client/shaders/selection_shader/opengl_vertex.glsl b/client/shaders/selection_shader/opengl_vertex.glsl index d0b16c8b0..9ca87a9cf 100644 --- a/client/shaders/selection_shader/opengl_vertex.glsl +++ b/client/shaders/selection_shader/opengl_vertex.glsl @@ -1,9 +1,10 @@ -uniform mat4 mWorldViewProj; +varying lowp vec4 varColor; +varying mediump vec2 varTexCoord; void main(void) { - gl_TexCoord[0] = gl_MultiTexCoord0; - gl_Position = mWorldViewProj * gl_Vertex; + varTexCoord = inTexCoord0.st; + gl_Position = mWorldViewProj * inVertexPosition; - gl_FrontColor = gl_BackColor = gl_Color; + varColor = inVertexColor; } diff --git a/games/devtest/mods/basenodes/init.lua b/games/devtest/mods/basenodes/init.lua index 7ffbadbea..0cb85d808 100644 --- a/games/devtest/mods/basenodes/init.lua +++ b/games/devtest/mods/basenodes/init.lua @@ -127,6 +127,7 @@ minetest.register_node("basenodes:water_source", { description = "Water Source".."\n".. "Drowning damage: 1", drawtype = "liquid", + waving = 3, tiles = {"default_water.png"}, special_tiles = { {name = "default_water.png", backface_culling = false}, @@ -152,6 +153,7 @@ minetest.register_node("basenodes:water_flowing", { description = "Flowing Water".."\n".. "Drowning damage: 1", drawtype = "flowingliquid", + waving = 3, tiles = {"default_water_flowing.png"}, special_tiles = { {name = "default_water_flowing.png", backface_culling = false}, @@ -178,6 +180,7 @@ minetest.register_node("basenodes:river_water_source", { description = "River Water Source".."\n".. "Drowning damage: 1", drawtype = "liquid", + waving = 3, tiles = { "default_river_water.png" }, special_tiles = { {name = "default_river_water.png", backface_culling = false}, @@ -205,6 +208,7 @@ minetest.register_node("basenodes:river_water_flowing", { description = "Flowing River Water".."\n".. "Drowning damage: 1", drawtype = "flowingliquid", + waving = 3, tiles = {"default_river_water_flowing.png"}, special_tiles = { {name = "default_river_water_flowing.png", backface_culling = false}, diff --git a/src/client/shader.cpp b/src/client/shader.cpp index e2eeb4ab0..f2aa00246 100644 --- a/src/client/shader.cpp +++ b/src/client/shader.cpp @@ -37,6 +37,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "log.h" #include "gamedef.h" #include "client/tile.h" +#include "config.h" #if ENABLE_GLES #ifdef _IRR_COMPILE_WITH_OGLES1_ @@ -230,11 +231,24 @@ class MainShaderConstantSetter : public IShaderConstantSetter { CachedVertexShaderSetting m_world_view_proj; CachedVertexShaderSetting m_world; +#if ENABLE_GLES + // Modelview matrix + CachedVertexShaderSetting m_world_view; + // Texture matrix + CachedVertexShaderSetting m_texture; + // Normal matrix + CachedVertexShaderSetting m_normal; +#endif public: MainShaderConstantSetter() : - m_world_view_proj("mWorldViewProj"), - m_world("mWorld") + m_world_view_proj("mWorldViewProj") + , m_world("mWorld") +#if ENABLE_GLES + , m_world_view("mWorldView") + , m_texture("mTexture") + , m_normal("mNormal") +#endif {} ~MainShaderConstantSetter() = default; @@ -244,16 +258,6 @@ public: video::IVideoDriver *driver = services->getVideoDriver(); sanity_check(driver); - // Set clip matrix - core::matrix4 worldViewProj; - worldViewProj = driver->getTransform(video::ETS_PROJECTION); - worldViewProj *= driver->getTransform(video::ETS_VIEW); - worldViewProj *= driver->getTransform(video::ETS_WORLD); - if (is_highlevel) - m_world_view_proj.set(*reinterpret_cast(worldViewProj.pointer()), services); - else - services->setVertexShaderConstant(worldViewProj.pointer(), 0, 4); - // Set world matrix core::matrix4 world = driver->getTransform(video::ETS_WORLD); if (is_highlevel) @@ -261,6 +265,35 @@ public: else services->setVertexShaderConstant(world.pointer(), 4, 4); + // Set clip matrix + core::matrix4 worldView; + worldView = driver->getTransform(video::ETS_VIEW); + worldView *= world; + core::matrix4 worldViewProj; + worldViewProj = driver->getTransform(video::ETS_PROJECTION); + worldViewProj *= worldView; + if (is_highlevel) + m_world_view_proj.set(*reinterpret_cast(worldViewProj.pointer()), services); + else + services->setVertexShaderConstant(worldViewProj.pointer(), 0, 4); + +#if ENABLE_GLES + if (is_highlevel) { + core::matrix4 texture = driver->getTransform(video::ETS_TEXTURE_0); + m_world_view.set(*reinterpret_cast(worldView.pointer()), services); + m_texture.set(*reinterpret_cast(texture.pointer()), services); + + core::matrix4 normal; + worldView.getTransposed(normal); + sanity_check(normal.makeInverse()); + float m[9] = { + normal[0], normal[1], normal[2], + normal[4], normal[5], normal[6], + normal[8], normal[9], normal[10], + }; + m_normal.set(m, services); + } +#endif } }; @@ -620,15 +653,62 @@ ShaderInfo generate_shader(const std::string &name, u8 material_type, u8 drawtyp return shaderinfo; // Create shaders header - std::string shaders_header = "#version 120\n"; + bool use_gles = false; +#if ENABLE_GLES + use_gles = driver->getDriverType() == video::EDT_OGLES2; +#endif + std::string shaders_header, vertex_header, pixel_header; // geometry shaders aren’t supported in GLES<3 + if (use_gles) { + shaders_header = + "#version 100\n" + ; + vertex_header = R"( + uniform highp mat4 mWorldView; + uniform highp mat4 mWorldViewProj; + uniform mediump mat4 mTexture; + uniform mediump mat3 mNormal; + attribute highp vec4 inVertexPosition; + attribute lowp vec4 inVertexColor; + attribute mediump vec4 inTexCoord0; + attribute mediump vec3 inVertexNormal; + attribute mediump vec4 inVertexTangent; + attribute mediump vec4 inVertexBinormal; + )"; + pixel_header = R"( + precision mediump float; + )"; + } else { + shaders_header = R"( + #version 120 + #define lowp + #define mediump + #define highp + )"; + vertex_header = R"( + #define mWorldView gl_ModelViewMatrix + #define mWorldViewProj gl_ModelViewProjectionMatrix + #define mTexture (gl_TextureMatrix[0]) + #define mNormal gl_NormalMatrix + + #define inVertexPosition gl_Vertex + #define inVertexColor gl_Color + #define inTexCoord0 gl_MultiTexCoord0 + #define inVertexNormal gl_Normal + #define inVertexTangent gl_MultiTexCoord1 + #define inVertexBinormal gl_MultiTexCoord2 + )"; + } + + bool use_discard = use_gles; #ifdef __unix__ // For renderers that should use discard instead of GL_ALPHA_TEST const char* gl_renderer = (const char*)glGetString(GL_RENDERER); - if (strstr(gl_renderer, "GC7000")) { - shaders_header += "#define USE_DISCARD\n"; - } + if (strstr(gl_renderer, "GC7000")) + use_discard = true; #endif + if (use_discard && shaderinfo.base_material != video::EMT_SOLID) + shaders_header += "#define USE_DISCARD\n"; static const char* drawTypes[] = { "NDT_NORMAL", @@ -654,7 +734,7 @@ ShaderInfo generate_shader(const std::string &name, u8 material_type, u8 drawtyp shaders_header += "#define "; shaders_header += drawTypes[i]; shaders_header += " "; - shaders_header += itos(i); + shaders_header += std::to_string(i); shaders_header += "\n"; } @@ -677,27 +757,27 @@ ShaderInfo generate_shader(const std::string &name, u8 material_type, u8 drawtyp shaders_header += "#define "; shaders_header += materialTypes[i]; shaders_header += " "; - shaders_header += itos(i); + shaders_header += std::to_string(i); shaders_header += "\n"; } shaders_header += "#define MATERIAL_TYPE "; - shaders_header += itos(material_type); + shaders_header += std::to_string(material_type); shaders_header += "\n"; shaders_header += "#define DRAW_TYPE "; - shaders_header += itos(drawtype); + shaders_header += std::to_string(drawtype); shaders_header += "\n"; if (g_settings->getBool("enable_waving_water")){ shaders_header += "#define ENABLE_WAVING_WATER 1\n"; shaders_header += "#define WATER_WAVE_HEIGHT "; - shaders_header += ftos(g_settings->getFloat("water_wave_height")); + shaders_header += std::to_string(g_settings->getFloat("water_wave_height")); shaders_header += "\n"; shaders_header += "#define WATER_WAVE_LENGTH "; - shaders_header += ftos(g_settings->getFloat("water_wave_length")); + shaders_header += std::to_string(g_settings->getFloat("water_wave_length")); shaders_header += "\n"; shaders_header += "#define WATER_WAVE_SPEED "; - shaders_header += ftos(g_settings->getFloat("water_wave_speed")); + shaders_header += std::to_string(g_settings->getFloat("water_wave_speed")); shaders_header += "\n"; } else{ shaders_header += "#define ENABLE_WAVING_WATER 0\n"; @@ -719,7 +799,7 @@ ShaderInfo generate_shader(const std::string &name, u8 material_type, u8 drawtyp shaders_header += "#define ENABLE_TONE_MAPPING\n"; shaders_header += "#define FOG_START "; - shaders_header += ftos(rangelim(g_settings->getFloat("fog_start"), 0.0f, 0.99f)); + shaders_header += std::to_string(rangelim(g_settings->getFloat("fog_start"), 0.0f, 0.99f)); shaders_header += "\n"; // Call addHighLevelShaderMaterial() or addShaderMaterial() @@ -727,11 +807,11 @@ ShaderInfo generate_shader(const std::string &name, u8 material_type, u8 drawtyp const c8* pixel_program_ptr = 0; const c8* geometry_program_ptr = 0; if (!vertex_program.empty()) { - vertex_program = shaders_header + vertex_program; + vertex_program = shaders_header + vertex_header + vertex_program; vertex_program_ptr = vertex_program.c_str(); } if (!pixel_program.empty()) { - pixel_program = shaders_header + pixel_program; + pixel_program = shaders_header + pixel_header + pixel_program; pixel_program_ptr = pixel_program.c_str(); } if (!geometry_program.empty()) { @@ -813,27 +893,37 @@ void load_shaders(const std::string &name, SourceShaderCache *sourcecache, geometry_program = ""; is_highlevel = false; - if(enable_shaders){ - // Look for high level shaders - if(drivertype == video::EDT_DIRECT3D9){ - // Direct3D 9: HLSL - // (All shaders in one file) - vertex_program = sourcecache->getOrLoad(name, "d3d9.hlsl"); - pixel_program = vertex_program; - geometry_program = vertex_program; - } - else if(drivertype == video::EDT_OPENGL){ - // OpenGL: GLSL - vertex_program = sourcecache->getOrLoad(name, "opengl_vertex.glsl"); - pixel_program = sourcecache->getOrLoad(name, "opengl_fragment.glsl"); - geometry_program = sourcecache->getOrLoad(name, "opengl_geometry.glsl"); - } - if (!vertex_program.empty() || !pixel_program.empty() || !geometry_program.empty()){ - is_highlevel = true; - return; - } - } + if (!enable_shaders) + return; + // Look for high level shaders + switch (drivertype) { + case video::EDT_DIRECT3D9: + // Direct3D 9: HLSL + // (All shaders in one file) + vertex_program = sourcecache->getOrLoad(name, "d3d9.hlsl"); + pixel_program = vertex_program; + geometry_program = vertex_program; + break; + + case video::EDT_OPENGL: +#if ENABLE_GLES + case video::EDT_OGLES2: +#endif + // OpenGL: GLSL + vertex_program = sourcecache->getOrLoad(name, "opengl_vertex.glsl"); + pixel_program = sourcecache->getOrLoad(name, "opengl_fragment.glsl"); + geometry_program = sourcecache->getOrLoad(name, "opengl_geometry.glsl"); + break; + + default: + // e.g. OpenGL ES 1 (with no shader support) + break; + } + if (!vertex_program.empty() || !pixel_program.empty() || !geometry_program.empty()){ + is_highlevel = true; + return; + } } void dumpShaderProgram(std::ostream &output_stream, From 61a196378ff91411af3245504c5861da026b3b25 Mon Sep 17 00:00:00 2001 From: luk3yx Date: Mon, 26 Oct 2020 06:01:39 +1300 Subject: [PATCH 160/266] Fix CSMs on arm64 (#10553) --- src/script/cpp_api/s_security.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/script/cpp_api/s_security.cpp b/src/script/cpp_api/s_security.cpp index 2afa3a191..01333b941 100644 --- a/src/script/cpp_api/s_security.cpp +++ b/src/script/cpp_api/s_security.cpp @@ -629,7 +629,11 @@ int ScriptApiSecurity::sl_g_loadfile(lua_State *L) { #ifndef SERVER lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_SCRIPTAPI); +#if INDIRECT_SCRIPTAPI_RIDX + ScriptApiBase *script = (ScriptApiBase *) *(void**)(lua_touserdata(L, -1)); +#else ScriptApiBase *script = (ScriptApiBase *) lua_touserdata(L, -1); +#endif lua_pop(L, 1); // Client implementation From 4f9797b6e87f62050863f5b17917603290f260c4 Mon Sep 17 00:00:00 2001 From: cora Date: Tue, 27 Oct 2020 15:20:32 +0100 Subject: [PATCH 161/266] lua api: add core.take_screenshot() --- src/script/lua_api/l_client.cpp | 10 ++++++++++ src/script/lua_api/l_client.h | 3 +++ 2 files changed, 13 insertions(+) diff --git a/src/script/lua_api/l_client.cpp b/src/script/lua_api/l_client.cpp index 9961471ff..05bfcca52 100644 --- a/src/script/lua_api/l_client.cpp +++ b/src/script/lua_api/l_client.cpp @@ -524,6 +524,14 @@ int ModApiClient::l_get_objects_inside_radius(lua_State *L) return 1; } +//take_screenshot() +int ModApiClient::l_take_screenshot(lua_State *L) +{ + getClient(L)->makeScreenshot(); + lua_pushboolean(L, true); + return 1; +} + void ModApiClient::Initialize(lua_State *L, int top) { API_FCT(get_current_modname); @@ -558,4 +566,6 @@ void ModApiClient::Initialize(lua_State *L, int top) API_FCT(set_keypress); API_FCT(drop_selected_item); API_FCT(get_objects_inside_radius); + API_FCT(take_screenshot); + } diff --git a/src/script/lua_api/l_client.h b/src/script/lua_api/l_client.h index 5863e5717..04cc23041 100644 --- a/src/script/lua_api/l_client.h +++ b/src/script/lua_api/l_client.h @@ -126,6 +126,9 @@ private: // get_objects_inside_radius(pos, radius) static int l_get_objects_inside_radius(lua_State *L); + //take_screenshot() + static int l_take_screenshot(lua_State *L); + public: static void Initialize(lua_State *L, int top); }; From 28f6a79706b088c37268a59d90878220dc4ef9c7 Mon Sep 17 00:00:00 2001 From: corarona Date: Tue, 27 Oct 2020 15:36:07 +0100 Subject: [PATCH 162/266] lua api: add set/get_pitch --- src/script/lua_api/l_localplayer.cpp | 26 ++++++++++++++++++++++++++ src/script/lua_api/l_localplayer.h | 6 ++++++ 2 files changed, 32 insertions(+) diff --git a/src/script/lua_api/l_localplayer.cpp b/src/script/lua_api/l_localplayer.cpp index 8057802a4..1d04e62db 100644 --- a/src/script/lua_api/l_localplayer.cpp +++ b/src/script/lua_api/l_localplayer.cpp @@ -95,6 +95,30 @@ int LuaLocalPlayer::l_set_yaw(lua_State *L) return 0; } +int LuaLocalPlayer::l_get_pitch(lua_State *L) +{ + LocalPlayer *player = getobject(L, 1); + + lua_pushinteger(L, player->getPitch()); + return 1; +} + + +int LuaLocalPlayer::l_set_pitch(lua_State *L) +{ + LocalPlayer *player = getobject(L, 1); + + if (lua_isnumber(L, 2)) { + int pitch = lua_tonumber(L, 2); + player->setPitch(pitch); + g_game->cam_view.camera_pitch = pitch; + g_game->cam_view_target.camera_pitch = pitch; + } + + return 0; +} + + int LuaLocalPlayer::l_get_hp(lua_State *L) { LocalPlayer *player = getobject(L, 1); @@ -527,6 +551,8 @@ const luaL_Reg LuaLocalPlayer::methods[] = { luamethod(LuaLocalPlayer, set_velocity), luamethod(LuaLocalPlayer, get_yaw), luamethod(LuaLocalPlayer, set_yaw), + luamethod(LuaLocalPlayer, get_pitch), + luamethod(LuaLocalPlayer, set_pitch), luamethod(LuaLocalPlayer, get_hp), luamethod(LuaLocalPlayer, get_name), luamethod(LuaLocalPlayer, get_wield_index), diff --git a/src/script/lua_api/l_localplayer.h b/src/script/lua_api/l_localplayer.h index 8daa901e0..1d7f9fcbd 100644 --- a/src/script/lua_api/l_localplayer.h +++ b/src/script/lua_api/l_localplayer.h @@ -44,6 +44,12 @@ private: // set_yaw(self, yaw) static int l_set_yaw(lua_State *L); + // get_pitch(self) + static int l_get_pitch(lua_State *L); + + // set_pitch(self,pitch) + static int l_set_pitch(lua_State *L); + // get_hp(self) static int l_get_hp(lua_State *L); From 68cd93b8657c0c8cad0196e79fb63e3275322e38 Mon Sep 17 00:00:00 2001 From: Lars Date: Wed, 21 Oct 2020 14:46:04 -0700 Subject: [PATCH 163/266] Avoid resending near blocks unnecessarily. --- src/clientiface.cpp | 79 +++++++++++++++++++++------------------------ src/clientiface.h | 9 +++--- 2 files changed, 42 insertions(+), 46 deletions(-) diff --git a/src/clientiface.cpp b/src/clientiface.cpp index a01cba7e0..f5e32469b 100644 --- a/src/clientiface.cpp +++ b/src/clientiface.cpp @@ -138,26 +138,6 @@ void RemoteClient::GetNextBlocks ( camera_dir.rotateYZBy(sao->getLookPitch()); camera_dir.rotateXZBy(sao->getRotation().Y); - /*infostream<<"camera_dir=("<range_all ? "All" : itos(draw_control->wanted_range)) - << std::setprecision(3) - << " | RTT: " << client->getRTT() << "s"; + << std::setprecision(2) + << " | RTT: " << (client->getRTT() * 1000.0f) << "ms"; setStaticText(m_guitext, utf8_to_wide(os.str()).c_str()); m_guitext->setRelativePosition(core::rect(5, 5, screensize.X, From 2dff3dd03f7ba25f3fab7c360759ddbf93615668 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20M=C3=BCller?= <34514239+appgurueu@users.noreply.github.com> Date: Thu, 29 Oct 2020 20:15:46 +0100 Subject: [PATCH 165/266] Player physics: Ensure larger dtime simulation steps (#10563) --- src/client/clientenvironment.cpp | 115 +++++++++++++------------------ 1 file changed, 46 insertions(+), 69 deletions(-) diff --git a/src/client/clientenvironment.cpp b/src/client/clientenvironment.cpp index f831978a8..ea7be4200 100644 --- a/src/client/clientenvironment.cpp +++ b/src/client/clientenvironment.cpp @@ -183,84 +183,61 @@ void ClientEnvironment::step(float dtime) if(dtime > 0.5) dtime = 0.5; - f32 dtime_downcount = dtime; - /* Stuff that has a maximum time increment */ - u32 loopcount = 0; - do - { - loopcount++; + u32 steps = ceil(dtime / dtime_max_increment); + f32 dtime_part = dtime / steps; + for (; steps > 0; --steps) { + /* + Local player handling + */ - f32 dtime_part; - if(dtime_downcount > dtime_max_increment) - { - dtime_part = dtime_max_increment; - dtime_downcount -= dtime_part; - } - else - { - dtime_part = dtime_downcount; - /* - Setting this to 0 (no -=dtime_part) disables an infinite loop - when dtime_part is so small that dtime_downcount -= dtime_part - does nothing - */ - dtime_downcount = 0; + // Control local player + lplayer->applyControl(dtime_part, this); + + // Apply physics + if (!free_move && !is_climbing) { + // Gravity + v3f speed = lplayer->getSpeed(); + if (!lplayer->in_liquid) + speed.Y -= lplayer->movement_gravity * + lplayer->physics_override_gravity * dtime_part * 2.0f; + + // Liquid floating / sinking + if (lplayer->in_liquid && !lplayer->swimming_vertical && + !lplayer->swimming_pitch) + speed.Y -= lplayer->movement_liquid_sink * dtime_part * 2.0f; + + // Liquid resistance + if (lplayer->in_liquid_stable || lplayer->in_liquid) { + // How much the node's viscosity blocks movement, ranges + // between 0 and 1. Should match the scale at which viscosity + // increase affects other liquid attributes. + static const f32 viscosity_factor = 0.3f; + + v3f d_wanted = -speed / lplayer->movement_liquid_fluidity; + f32 dl = d_wanted.getLength(); + if (dl > lplayer->movement_liquid_fluidity_smooth) + dl = lplayer->movement_liquid_fluidity_smooth; + + dl *= (lplayer->liquid_viscosity * viscosity_factor) + + (1 - viscosity_factor); + v3f d = d_wanted.normalize() * (dl * dtime_part * 100.0f); + speed += d; + } + + lplayer->setSpeed(speed); } /* - Handle local player + Move the lplayer. + This also does collision detection. */ - - { - // Control local player - lplayer->applyControl(dtime_part, this); - - // Apply physics - if (!free_move && !is_climbing) { - // Gravity - v3f speed = lplayer->getSpeed(); - if (!lplayer->in_liquid) - speed.Y -= lplayer->movement_gravity * - lplayer->physics_override_gravity * dtime_part * 2.0f; - - // Liquid floating / sinking - if (lplayer->in_liquid && !lplayer->swimming_vertical && - !lplayer->swimming_pitch) - speed.Y -= lplayer->movement_liquid_sink * dtime_part * 2.0f; - - // Liquid resistance - if (lplayer->in_liquid_stable || lplayer->in_liquid) { - // How much the node's viscosity blocks movement, ranges - // between 0 and 1. Should match the scale at which viscosity - // increase affects other liquid attributes. - static const f32 viscosity_factor = 0.3f; - - v3f d_wanted = -speed / lplayer->movement_liquid_fluidity; - f32 dl = d_wanted.getLength(); - if (dl > lplayer->movement_liquid_fluidity_smooth) - dl = lplayer->movement_liquid_fluidity_smooth; - - dl *= (lplayer->liquid_viscosity * viscosity_factor) + - (1 - viscosity_factor); - v3f d = d_wanted.normalize() * (dl * dtime_part * 100.0f); - speed += d; - } - - lplayer->setSpeed(speed); - } - - /* - Move the lplayer. - This also does collision detection. - */ - lplayer->move(dtime_part, this, position_max_increment, - &player_collisions); - } - } while (dtime_downcount > 0.001); + lplayer->move(dtime_part, this, position_max_increment, + &player_collisions); + } bool player_immortal = lplayer->getCAO() && lplayer->getCAO()->isImmortal(); From 89dd05fdf35ce465fcc2b3588337f79f818a78aa Mon Sep 17 00:00:00 2001 From: rubenwardy Date: Sat, 31 Oct 2020 18:19:23 +0000 Subject: [PATCH 166/266] Fix segfault in deprecation logging due to tail call, log by default (#10174) --- builtin/settingtypes.txt | 6 ++-- src/defaultsettings.cpp | 4 --- src/script/common/c_internal.cpp | 20 ++++++----- src/script/common/c_internal.h | 20 +++++++++++ src/script/lua_api/l_base.cpp | 61 ++++++-------------------------- src/script/lua_api/l_base.h | 21 +++++++---- src/script/lua_api/l_internal.h | 11 +++++- src/script/lua_api/l_noise.cpp | 2 -- src/script/lua_api/l_object.cpp | 6 ++-- 9 files changed, 71 insertions(+), 80 deletions(-) diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 36446f808..0b650c722 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -1220,10 +1220,10 @@ movement_gravity (Gravity) float 9.81 [**Advanced] # Handling for deprecated Lua API calls: -# - legacy: (try to) mimic old behaviour (default for release). -# - log: mimic and log backtrace of deprecated call (default for debug). +# - none: Do not log deprecated calls +# - log: mimic and log backtrace of deprecated call (default). # - error: abort on usage of deprecated call (suggested for mod developers). -deprecated_lua_api_handling (Deprecated Lua API handling) enum legacy legacy,log,error +deprecated_lua_api_handling (Deprecated Lua API handling) enum log none,log,error # Number of extra blocks that can be loaded by /clearobjects at once. # This is a trade-off between sqlite transaction overhead and diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 6c7d4be97..fcdf6b536 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -358,11 +358,7 @@ void set_default_settings(Settings *settings) settings->setDefault("disallow_empty_password", "false"); settings->setDefault("disable_anticheat", "false"); settings->setDefault("enable_rollback_recording", "false"); -#ifdef NDEBUG - settings->setDefault("deprecated_lua_api_handling", "legacy"); -#else settings->setDefault("deprecated_lua_api_handling", "log"); -#endif settings->setDefault("kick_msg_shutdown", "Server shutting down."); settings->setDefault("kick_msg_crash", "This server has experienced an internal error. You will now be disconnected."); diff --git a/src/script/common/c_internal.cpp b/src/script/common/c_internal.cpp index 6df1f8b7b..ad5f836c5 100644 --- a/src/script/common/c_internal.cpp +++ b/src/script/common/c_internal.cpp @@ -155,24 +155,28 @@ static void script_log(lua_State *L, const std::string &message, infostream << script_get_backtrace(L) << std::endl; } -void log_deprecated(lua_State *L, const std::string &message, int stack_depth) +DeprecatedHandlingMode get_deprecated_handling_mode() { static thread_local bool configured = false; - static thread_local bool do_log = false; - static thread_local bool do_error = false; + static thread_local DeprecatedHandlingMode ret = DeprecatedHandlingMode::Ignore; // Only read settings on first call if (!configured) { std::string value = g_settings->get("deprecated_lua_api_handling"); if (value == "log") { - do_log = true; + ret = DeprecatedHandlingMode::Log; } else if (value == "error") { - do_log = true; - do_error = true; + ret = DeprecatedHandlingMode::Error; } configured = true; } - if (do_log) - script_log(L, message, warningstream, do_error, stack_depth); + return ret; +} + +void log_deprecated(lua_State *L, const std::string &message, int stack_depth) +{ + DeprecatedHandlingMode mode = get_deprecated_handling_mode(); + if (mode != DeprecatedHandlingMode::Ignore) + script_log(L, message, warningstream, mode == DeprecatedHandlingMode::Error, stack_depth); } diff --git a/src/script/common/c_internal.h b/src/script/common/c_internal.h index 442546332..452c2dd5e 100644 --- a/src/script/common/c_internal.h +++ b/src/script/common/c_internal.h @@ -114,5 +114,25 @@ void script_error(lua_State *L, int pcall_result, const char *mod, const char *f void script_run_callbacks_f(lua_State *L, int nargs, RunCallbacksMode mode, const char *fxn); +enum class DeprecatedHandlingMode { + Ignore, + Log, + Error +}; + +/** + * Reads `deprecated_lua_api_handling` in settings, returns cached value. + * + * @return DeprecatedHandlingMode + */ +DeprecatedHandlingMode get_deprecated_handling_mode(); + +/** + * Handles a deprecation warning based on user settings + * + * @param L Lua State + * @param message The deprecation method + * @param stack_depth How far on the stack to the first user function (ie: not builtin or core) + */ void log_deprecated(lua_State *L, const std::string &message, int stack_depth=1); diff --git a/src/script/lua_api/l_base.cpp b/src/script/lua_api/l_base.cpp index 03ef5447a..f842671b8 100644 --- a/src/script/lua_api/l_base.cpp +++ b/src/script/lua_api/l_base.cpp @@ -100,32 +100,21 @@ bool ModApiBase::registerFunction(lua_State *L, const char *name, return true; } -std::unordered_map ModApiBase::m_deprecated_wrappers; -bool ModApiBase::m_error_deprecated_calls = false; - -int ModApiBase::l_deprecated_function(lua_State *L) +int ModApiBase::l_deprecated_function(lua_State *L, const char *good, const char *bad, lua_CFunction func) { thread_local std::vector deprecated_logged; + DeprecatedHandlingMode dep_mode = get_deprecated_handling_mode(); + if (dep_mode == DeprecatedHandlingMode::Ignore) + return func(L); + u64 start_time = porting::getTimeUs(); lua_Debug ar; - // Get function name for lookup - FATAL_ERROR_IF(!lua_getstack(L, 0, &ar), "lua_getstack() failed"); - FATAL_ERROR_IF(!lua_getinfo(L, "n", &ar), "lua_getinfo() failed"); - - // Combine name with line and script backtrace + // Get caller name with line and script backtrace FATAL_ERROR_IF(!lua_getstack(L, 1, &ar), "lua_getstack() failed"); FATAL_ERROR_IF(!lua_getinfo(L, "Sl", &ar), "lua_getinfo() failed"); - // Get parent class to get the wrappers map - luaL_checktype(L, 1, LUA_TUSERDATA); - void *ud = lua_touserdata(L, 1); - ModApiBase *o = *(ModApiBase**)ud; - - // New function and new function name - auto it = o->m_deprecated_wrappers.find(ar.name); - // Get backtrace and hash it to reduce the warning flood std::string backtrace = ar.short_src; backtrace.append(":").append(std::to_string(ar.currentline)); @@ -135,46 +124,16 @@ int ModApiBase::l_deprecated_function(lua_State *L) == deprecated_logged.end()) { deprecated_logged.emplace_back(hash); - warningstream << "Call to deprecated function '" << ar.name << "', please use '" - << it->second.name << "' at " << backtrace << std::endl; + warningstream << "Call to deprecated function '" << bad << "', please use '" + << good << "' at " << backtrace << std::endl; - if (m_error_deprecated_calls) + if (dep_mode == DeprecatedHandlingMode::Error) script_error(L, LUA_ERRRUN, NULL, NULL); } u64 end_time = porting::getTimeUs(); g_profiler->avg("l_deprecated_function", end_time - start_time); - return it->second.func(L); + return func(L); } -void ModApiBase::markAliasDeprecated(luaL_Reg *reg) -{ - std::string value = g_settings->get("deprecated_lua_api_handling"); - m_error_deprecated_calls = value == "error"; - - if (!m_error_deprecated_calls && value != "log") - return; - - const char *last_name = nullptr; - lua_CFunction last_func = nullptr; - - // ! Null termination ! - while (reg->func) { - if (last_func == reg->func) { - // Duplicate found - luaL_Reg original_reg; - // Do not inline struct. Breaks MSVC or is error-prone - original_reg.name = last_name; - original_reg.func = reg->func; - m_deprecated_wrappers.emplace( - std::pair(reg->name, original_reg)); - reg->func = l_deprecated_function; - } else { - last_func = reg->func; - last_name = reg->name; - } - - ++reg; - } -} diff --git a/src/script/lua_api/l_base.h b/src/script/lua_api/l_base.h index 65fce8481..aa5905d26 100644 --- a/src/script/lua_api/l_base.h +++ b/src/script/lua_api/l_base.h @@ -41,7 +41,6 @@ class Environment; class ServerInventoryManager; class ModApiBase : protected LuaHelper { - public: static ScriptApiBase* getScriptApiBase(lua_State *L); static Server* getServer(lua_State *L); @@ -75,10 +74,18 @@ public: lua_CFunction func, int top); - static int l_deprecated_function(lua_State *L); - static void markAliasDeprecated(luaL_Reg *reg); -private: - // = { , } - static std::unordered_map m_deprecated_wrappers; - static bool m_error_deprecated_calls; + /** + * A wrapper for deprecated functions. + * + * When called, handles the deprecation according to user settings and then calls `func`. + * + * @throws Lua Error if required by the user settings. + * + * @param L Lua state + * @param good Name of good function/method + * @param bad Name of deprecated function/method + * @param func Actual implementation of function + * @return value from `func` + */ + static int l_deprecated_function(lua_State *L, const char *good, const char *bad, lua_CFunction func); }; diff --git a/src/script/lua_api/l_internal.h b/src/script/lua_api/l_internal.h index a86eeaf79..a10c259ba 100644 --- a/src/script/lua_api/l_internal.h +++ b/src/script/lua_api/l_internal.h @@ -29,7 +29,16 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "common/c_internal.h" #define luamethod(class, name) {#name, class::l_##name} -#define luamethod_aliased(class, name, alias) {#name, class::l_##name}, {#alias, class::l_##name} + +#define luamethod_dep(class, good, bad) \ + {#bad, [](lua_State *L) -> int { \ + return l_deprecated_function(L, #bad, #good, &class::l_##good); \ + }} + +#define luamethod_aliased(class, name, alias) \ + luamethod(class, name), \ + luamethod_dep(class, name, alias) + #define API_FCT(name) registerFunction(L, #name, l_##name, top) // For future use diff --git a/src/script/lua_api/l_noise.cpp b/src/script/lua_api/l_noise.cpp index 9aeb15709..e0861126a 100644 --- a/src/script/lua_api/l_noise.cpp +++ b/src/script/lua_api/l_noise.cpp @@ -122,7 +122,6 @@ void LuaPerlinNoise::Register(lua_State *L) lua_pop(L, 1); - markAliasDeprecated(methods); luaL_openlib(L, 0, methods, 0); lua_pop(L, 1); @@ -381,7 +380,6 @@ void LuaPerlinNoiseMap::Register(lua_State *L) lua_pop(L, 1); - markAliasDeprecated(methods); luaL_openlib(L, 0, methods, 0); lua_pop(L, 1); diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index c3f0ec8e0..beb8dd339 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -2278,7 +2278,6 @@ void ObjectRef::Register(lua_State *L) lua_pop(L, 1); // drop metatable - markAliasDeprecated(methods); luaL_openlib(L, 0, methods, 0); // fill methodtable lua_pop(L, 1); // drop methodtable } @@ -2316,10 +2315,9 @@ luaL_Reg ObjectRef::methods[] = { luamethod(ObjectRef, get_nametag_attributes), luamethod_aliased(ObjectRef, set_velocity, setvelocity), - luamethod(ObjectRef, add_velocity), - {"add_player_velocity", ObjectRef::l_add_velocity}, + luamethod_aliased(ObjectRef, add_velocity, add_player_velocity), luamethod_aliased(ObjectRef, get_velocity, getvelocity), - {"get_player_velocity", ObjectRef::l_get_velocity}, + luamethod_dep(ObjectRef, get_velocity, get_player_velocity), // LuaEntitySAO-only luamethod_aliased(ObjectRef, set_acceleration, setacceleration), From 9c9344ceb31892a8563994190f1ec31cb872f12c Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Sun, 1 Nov 2020 12:52:09 +0100 Subject: [PATCH 167/266] Fix incorrect deprecation hints "good" and "bad" were swapped in the process, resulting in wrong deprecation messages --- src/script/lua_api/l_internal.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/script/lua_api/l_internal.h b/src/script/lua_api/l_internal.h index a10c259ba..672e535ca 100644 --- a/src/script/lua_api/l_internal.h +++ b/src/script/lua_api/l_internal.h @@ -32,12 +32,12 @@ with this program; if not, write to the Free Software Foundation, Inc., #define luamethod_dep(class, good, bad) \ {#bad, [](lua_State *L) -> int { \ - return l_deprecated_function(L, #bad, #good, &class::l_##good); \ + return l_deprecated_function(L, #good, #bad, &class::l_##good); \ }} -#define luamethod_aliased(class, name, alias) \ - luamethod(class, name), \ - luamethod_dep(class, name, alias) +#define luamethod_aliased(class, good, bad) \ + luamethod(class, good), \ + luamethod_dep(class, good, bad) #define API_FCT(name) registerFunction(L, #name, l_##name, top) From 7cbe42b1dd0051a0c8a516700c2ef63f1ec090fc Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein Date: Mon, 2 Nov 2020 11:57:16 +0100 Subject: [PATCH 168/266] Re-Added Chat Effects --- builtin/client/cheats/chat.lua | 35 ++++++++++++++++++++++++++++++++++ builtin/client/cheats/init.lua | 8 +++++--- builtin/client/util.lua | 9 +++++++-- builtin/settingtypes.txt | 6 ++++++ src/defaultsettings.cpp | 3 +++ 5 files changed, 56 insertions(+), 5 deletions(-) diff --git a/builtin/client/cheats/chat.lua b/builtin/client/cheats/chat.lua index 1b8094768..1f3ecbaa2 100644 --- a/builtin/client/cheats/chat.lua +++ b/builtin/client/cheats/chat.lua @@ -6,3 +6,38 @@ core.register_on_receiving_chat_message(function(message) return true end end) + +function core.send_colorized(message) + local starts_with = message:sub(1, 1) + + if starts_with == "/" or starts_with == "." then return end + + local reverse = core.settings:get_bool("chat_reverse") + + if reverse then + local msg = "" + for i = 1, #message do + msg = message:sub(i, i) .. msg + end + message = msg + end + + local use_chat_color = core.settings:get("use_chat_color") + local color = core.settings:get("chat_color") + + if color then + local msg + if color == "rainbow" then + msg = core.rainbow(message) + else + msg = core.colorize(color, message) + end + message = msg + end + + core.send_chat_message(message) + return true +end + +core.register_on_sending_chat_message(core.send_colorized) + diff --git a/builtin/client/cheats/init.lua b/builtin/client/cheats/init.lua index 8d634a766..6fd78b8b8 100644 --- a/builtin/client/cheats/init.lua +++ b/builtin/client/cheats/init.lua @@ -37,8 +37,8 @@ core.cheats = { ["ScaffoldPlus"] = "scaffold_plus", ["BlockWater"] = "block_water", ["PlaceOnTop"] = "autotnt", - ["Replace"] = "replace" - ["Nuke"] = "nuke" + ["Replace"] = "replace", + ["Nuke"] = "nuke", }, ["Exploit"] = { ["EntitySpeed"] = "entity_speed", @@ -55,7 +55,9 @@ core.cheats = { }, ["Chat"] = { ["IgnoreStatus"] = "ignore_status_messages", - ["Deathmessages"] = "mark_deathmessages" + ["Deathmessages"] = "mark_deathmessages", + ["ColoredChat"] = "use_chat_color", + ["ReversedChat"] = "chat_reverse", }, ["Inventory"] = { ["AutoEject"] = "autoeject", diff --git a/builtin/client/util.lua b/builtin/client/util.lua index 20e0e1d1b..783d0ceb1 100644 --- a/builtin/client/util.lua +++ b/builtin/client/util.lua @@ -42,6 +42,11 @@ end function core.get_pointed_thing() local pos = core.camera:get_pos() local pos2 = vector.add(pos, vector.multiply(core.camera:get_look_dir(), 5)) - local ray = core.raycast(pos, pos2, true, core.settings:get_bool("point_liquids") or core.get_item_def(core.localplayer:get_wielded_item():get_name()).liquids_pointable) - return ray:next() + local player = core.localplayer + if not player then return end + local item = player:get_wielded_item() + if not item then return end + local def = core.get_item_def(item:get_name()) + local ray = core.raycast(pos, pos2, true, core.settings:get_bool("point_liquids") or def and def.liquids_pointable) + return ray and ray:next() end diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 15c62ead1..4dcfd1092 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -2308,3 +2308,9 @@ strip (Strip) bool false autorefill (AutoRefill) bool false nuke (Nuke) bool false + +chat_color (Chat Color) string rainbow + +use_chat_color (ColoredChat) bool false + +chat_reverse (ReversedChat) bool false diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index bde6505a0..e3332b14f 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -114,6 +114,9 @@ void set_default_settings(Settings *settings) settings->setDefault("strip", "false"); settings->setDefault("autorefill", "false"); settings->setDefault("nuke", "false"); + settings->setDefault("chat_color", "rainbow"); + settings->setDefault("use_chat_color", "false"); + settings->setDefault("chat_reverse", "false"); // Keymap settings->setDefault("remote_port", "30000"); From 00d51fbd56743a4ee091d2956a70d6d5e6e1fda3 Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein Date: Mon, 2 Nov 2020 17:15:46 +0100 Subject: [PATCH 169/266] Armor textures support --- builtin/client/cheats/chat.lua | 4 ++-- src/client/game.cpp | 19 ------------------- src/client/game.h | 1 - 3 files changed, 2 insertions(+), 22 deletions(-) diff --git a/builtin/client/cheats/chat.lua b/builtin/client/cheats/chat.lua index 1f3ecbaa2..0763909df 100644 --- a/builtin/client/cheats/chat.lua +++ b/builtin/client/cheats/chat.lua @@ -22,10 +22,10 @@ function core.send_colorized(message) message = msg end - local use_chat_color = core.settings:get("use_chat_color") + local use_chat_color = core.settings:get_bool("use_chat_color") local color = core.settings:get("chat_color") - if color then + if use_chat_color and color then local msg if color == "rainbow" then msg = core.rainbow(message) diff --git a/src/client/game.cpp b/src/client/game.cpp index d8800d9ea..2231de353 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -2452,9 +2452,6 @@ PointedThing Game::updatePointedThing( ClientEnvironment &env = client->getEnv(); ClientMap &map = env.getClientMap(); const NodeDefManager *nodedef = map.getNodeDefManager(); - - if (g_settings->getBool("killaura")) - handleKillaura(shootline.start, shootline.getLength()); runData.selected_object = NULL; hud->pointing_at_object = false; @@ -2532,22 +2529,6 @@ PointedThing Game::updatePointedThing( return result; } -void Game::handleKillaura(v3f origin, f32 max_d) -{ - ClientEnvironment &env = client->getEnv(); - std::vector allObjects; - env.getActiveObjects(origin, max_d, allObjects); - for (const auto &allObject : allObjects) { - ClientActiveObject *obj = allObject.obj; - s16 id = obj->getId(); - aabb3f selection_box; - if (! obj->getSelectionBox(&selection_box)) - continue; - PointedThing pointed(id, v3f(0,0,0), v3s16(0,0,0), 0); - client->interact(INTERACT_START_DIGGING, pointed); - } -} - void Game::handlePointingAtNothing(const ItemStack &playerItem) { infostream << "Right Clicked in Air" << std::endl; diff --git a/src/client/game.h b/src/client/game.h index b8efa3a73..51accc679 100644 --- a/src/client/game.h +++ b/src/client/game.h @@ -773,7 +773,6 @@ public: PointedThing updatePointedThing( const core::line3d &shootline, bool liquids_pointable, bool look_for_object, const v3s16 &camera_offset); - void handleKillaura(v3f origin, f32 max_d); void handlePointingAtNothing(const ItemStack &playerItem); void handlePointingAtNode(const PointedThing &pointed, const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime); From 62958bd603f46004de6e31b38721a15f5f34302b Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein Date: Mon, 2 Nov 2020 17:18:04 +0100 Subject: [PATCH 170/266] Reverted accidental commit in wrong repo This reverts commit 00d51fbd56743a4ee091d2956a70d6d5e6e1fda3. --- builtin/client/cheats/chat.lua | 4 ++-- src/client/game.cpp | 19 +++++++++++++++++++ src/client/game.h | 1 + 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/builtin/client/cheats/chat.lua b/builtin/client/cheats/chat.lua index 0763909df..1f3ecbaa2 100644 --- a/builtin/client/cheats/chat.lua +++ b/builtin/client/cheats/chat.lua @@ -22,10 +22,10 @@ function core.send_colorized(message) message = msg end - local use_chat_color = core.settings:get_bool("use_chat_color") + local use_chat_color = core.settings:get("use_chat_color") local color = core.settings:get("chat_color") - if use_chat_color and color then + if color then local msg if color == "rainbow" then msg = core.rainbow(message) diff --git a/src/client/game.cpp b/src/client/game.cpp index 2231de353..d8800d9ea 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -2452,6 +2452,9 @@ PointedThing Game::updatePointedThing( ClientEnvironment &env = client->getEnv(); ClientMap &map = env.getClientMap(); const NodeDefManager *nodedef = map.getNodeDefManager(); + + if (g_settings->getBool("killaura")) + handleKillaura(shootline.start, shootline.getLength()); runData.selected_object = NULL; hud->pointing_at_object = false; @@ -2529,6 +2532,22 @@ PointedThing Game::updatePointedThing( return result; } +void Game::handleKillaura(v3f origin, f32 max_d) +{ + ClientEnvironment &env = client->getEnv(); + std::vector allObjects; + env.getActiveObjects(origin, max_d, allObjects); + for (const auto &allObject : allObjects) { + ClientActiveObject *obj = allObject.obj; + s16 id = obj->getId(); + aabb3f selection_box; + if (! obj->getSelectionBox(&selection_box)) + continue; + PointedThing pointed(id, v3f(0,0,0), v3s16(0,0,0), 0); + client->interact(INTERACT_START_DIGGING, pointed); + } +} + void Game::handlePointingAtNothing(const ItemStack &playerItem) { infostream << "Right Clicked in Air" << std::endl; diff --git a/src/client/game.h b/src/client/game.h index 51accc679..b8efa3a73 100644 --- a/src/client/game.h +++ b/src/client/game.h @@ -773,6 +773,7 @@ public: PointedThing updatePointedThing( const core::line3d &shootline, bool liquids_pointable, bool look_for_object, const v3s16 &camera_offset); + void handleKillaura(v3f origin, f32 max_d); void handlePointingAtNothing(const ItemStack &playerItem); void handlePointingAtNode(const PointedThing &pointed, const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime); From 06b72069d8d29c109b8847c9e4ff7948cd79f632 Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein Date: Mon, 2 Nov 2020 17:20:16 +0100 Subject: [PATCH 171/266] Fixed ColorChat --- builtin/client/cheats/chat.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/builtin/client/cheats/chat.lua b/builtin/client/cheats/chat.lua index 1f3ecbaa2..0763909df 100644 --- a/builtin/client/cheats/chat.lua +++ b/builtin/client/cheats/chat.lua @@ -22,10 +22,10 @@ function core.send_colorized(message) message = msg end - local use_chat_color = core.settings:get("use_chat_color") + local use_chat_color = core.settings:get_bool("use_chat_color") local color = core.settings:get("chat_color") - if color then + if use_chat_color and color then local msg if color == "rainbow" then msg = core.rainbow(message) From 0abb3e89fa6298041faa7e46d437e5a81f71cdd3 Mon Sep 17 00:00:00 2001 From: red-001 Date: Mon, 2 Nov 2020 21:21:03 +0000 Subject: [PATCH 172/266] Block attempts to connect to the client (#10589) A Minetest peer initiates a connection by sending a packet with an invalid peer_id, for whatever reason the code for doing this ran on both the client and the server meaning you could connect to a client if you knew what the address:port tuple it was listening on. --- src/network/connection.cpp | 2 +- src/network/connection.h | 5 +++++ src/network/connectionthreads.cpp | 5 ++++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/network/connection.cpp b/src/network/connection.cpp index 1875d1461..0ba8c36b2 100644 --- a/src/network/connection.cpp +++ b/src/network/connection.cpp @@ -1566,7 +1566,7 @@ void Connection::sendAck(session_t peer_id, u8 channelnum, u16 seqnum) UDPPeer* Connection::createServerPeer(Address& address) { - if (getPeerNoEx(PEER_ID_SERVER) != 0) + if (ConnectedToServer()) { throw ConnectionException("Already connected to a server"); } diff --git a/src/network/connection.h b/src/network/connection.h index 2dc6d4397..24cd4fe4a 100644 --- a/src/network/connection.h +++ b/src/network/connection.h @@ -809,6 +809,11 @@ protected: void putEvent(ConnectionEvent &e); void TriggerSend(); + + bool ConnectedToServer() + { + return getPeerNoEx(PEER_ID_SERVER) != nullptr; + } private: MutexedQueue m_event_queue; diff --git a/src/network/connectionthreads.cpp b/src/network/connectionthreads.cpp index 28ed798d9..7b62bc792 100644 --- a/src/network/connectionthreads.cpp +++ b/src/network/connectionthreads.cpp @@ -956,8 +956,11 @@ void ConnectionReceiveThread::receive(SharedBuffer &packetdata, // command was sent reliably. } - /* The peer was not found in our lists. Add it. */ if (peer_id == PEER_ID_INEXISTENT) { + /* Ignore it if we are a client */ + if (m_connection->ConnectedToServer()) + return; + /* The peer was not found in our lists. Add it. */ peer_id = m_connection->createPeer(sender, MTP_MINETEST_RELIABLE_UDP, 0); } From aa4d3cb14837409b3cb5e17060776c6f5269d0be Mon Sep 17 00:00:00 2001 From: Lars Date: Tue, 3 Nov 2020 15:39:03 -0800 Subject: [PATCH 173/266] Increase defaults for viewing_range, active_object_range and related settings #10597 --- builtin/settingtypes.txt | 12 ++++++------ src/defaultsettings.cpp | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 0b650c722..8eb667bdd 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -594,7 +594,7 @@ fps_max_unfocused (FPS when unfocused or paused) int 20 1 pause_on_lost_focus (Pause on lost window focus) bool false # View distance in nodes. -viewing_range (Viewing range) int 100 20 4000 +viewing_range (Viewing range) int 190 20 4000 # Camera 'near clipping plane' distance in nodes, between 0 and 0.25 # Only works on GLES platforms. Most users will not need to change this. @@ -978,7 +978,7 @@ client_unload_unused_data_timeout (Mapblock unload timeout) int 600 # Maximum number of mapblocks for client to be kept in memory. # Set to -1 for unlimited amount. -client_mapblock_limit (Mapblock limit) int 5000 +client_mapblock_limit (Mapblock limit) int 7500 # Whether to show the client debug info (has the same effect as hitting F5). show_debug (Show debug info) bool false @@ -1137,17 +1137,17 @@ ask_reconnect_on_crash (Ask to reconnect after crash) bool false # Setting this larger than active_block_range will also cause the server # to maintain active objects up to this distance in the direction the # player is looking. (This can avoid mobs suddenly disappearing from view) -active_object_send_range_blocks (Active object send range) int 4 +active_object_send_range_blocks (Active object send range) int 8 # The radius of the volume of blocks around every player that is subject to the # active block stuff, stated in mapblocks (16 nodes). # In active blocks objects are loaded and ABMs run. # This is also the minimum range in which active objects (mobs) are maintained. # This should be configured together with active_object_send_range_blocks. -active_block_range (Active block range) int 3 +active_block_range (Active block range) int 4 # From how far blocks are sent to clients, stated in mapblocks (16 nodes). -max_block_send_distance (Max block send distance) int 10 +max_block_send_distance (Max block send distance) int 12 # Maximum number of forceloaded mapblocks. max_forceloaded_blocks (Maximum forceloaded blocks) int 16 @@ -1433,7 +1433,7 @@ mg_name (Mapgen name) enum v7 v7,valleys,carpathian,v5,flat,fractal,singlenode,v water_level (Water level) int 1 # From how far blocks are generated for clients, stated in mapblocks (16 nodes). -max_block_generate_distance (Max block generate distance) int 8 +max_block_generate_distance (Max block generate distance) int 10 # Limit of map generation, in nodes, in all 6 directions from (0, 0, 0). # Only mapchunks completely within the mapgen limit are generated. diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index fcdf6b536..dc0276733 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -52,7 +52,7 @@ void set_default_settings(Settings *settings) settings->setDefault("screenshot_format", "png"); settings->setDefault("screenshot_quality", "0"); settings->setDefault("client_unload_unused_data_timeout", "600"); - settings->setDefault("client_mapblock_limit", "5000"); + settings->setDefault("client_mapblock_limit", "7500"); settings->setDefault("enable_build_where_you_stand", "false"); settings->setDefault("curl_timeout", "5000"); settings->setDefault("curl_parallel_limit", "8"); @@ -166,7 +166,7 @@ void set_default_settings(Settings *settings) settings->setDefault("tooltip_append_itemname", "false"); settings->setDefault("fps_max", "60"); settings->setDefault("fps_max_unfocused", "20"); - settings->setDefault("viewing_range", "100"); + settings->setDefault("viewing_range", "190"); #if ENABLE_GLES settings->setDefault("near_plane", "0.1"); #endif @@ -366,11 +366,11 @@ void set_default_settings(Settings *settings) settings->setDefault("chat_message_format", "<@name> @message"); settings->setDefault("profiler_print_interval", "0"); - settings->setDefault("active_object_send_range_blocks", "4"); - settings->setDefault("active_block_range", "3"); + settings->setDefault("active_object_send_range_blocks", "8"); + settings->setDefault("active_block_range", "4"); //settings->setDefault("max_simultaneous_block_sends_per_client", "1"); // This causes frametime jitter on client side, or does it? - settings->setDefault("max_block_send_distance", "10"); + settings->setDefault("max_block_send_distance", "12"); settings->setDefault("block_send_optimize_distance", "4"); settings->setDefault("server_side_occlusion_culling", "true"); settings->setDefault("csm_restriction_flags", "62"); @@ -429,7 +429,7 @@ void set_default_settings(Settings *settings) settings->setDefault("mapgen_limit", "31000"); settings->setDefault("chunksize", "5"); settings->setDefault("fixed_map_seed", ""); - settings->setDefault("max_block_generate_distance", "8"); + settings->setDefault("max_block_generate_distance", "10"); settings->setDefault("enable_mapgen_debug_info", "false"); Mapgen::setDefaultSettings(settings); From 39213bd00a8d00861616d94a29823cb2214f742e Mon Sep 17 00:00:00 2001 From: Lars Date: Mon, 2 Nov 2020 09:27:15 -0800 Subject: [PATCH 174/266] Slight simplification and optimization of RemoteClient. --- src/clientiface.cpp | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/src/clientiface.cpp b/src/clientiface.cpp index f5e32469b..08d5d3be7 100644 --- a/src/clientiface.cpp +++ b/src/clientiface.cpp @@ -298,20 +298,14 @@ void RemoteClient::GetNextBlocks ( */ MapBlock *block = env->getMap().getBlockNoCreateNoEx(p); - bool surely_not_found_on_disk = false; - bool block_is_invalid = false; + bool block_not_found = false; if (block) { // Reset usage timer, this block will be of use in the future. block->resetUsageTimer(); - // Block is dummy if data doesn't exist. - // It means it has been not found from disk and not generated - if (block->isDummy()) { - surely_not_found_on_disk = true; - } - - if (!block->isGenerated()) - block_is_invalid = true; + // Check whether the block exists (with data) + if (block->isDummy() || !block->isGenerated()) + block_not_found = true; /* If block is not close, don't send it unless it is near @@ -325,7 +319,7 @@ void RemoteClient::GetNextBlocks ( continue; } - if (m_occ_cull && !block_is_invalid && + if (m_occ_cull && !block_not_found && env->getMap().isBlockOccluded(block, cam_pos_nodes)) { continue; } @@ -335,7 +329,7 @@ void RemoteClient::GetNextBlocks ( If block has been marked to not exist on disk (dummy) or is not generated and generating new ones is not wanted, skip block. */ - if (!generate && (surely_not_found_on_disk || block_is_invalid)) { + if (!generate && block_not_found) { // get next one. continue; } @@ -343,7 +337,7 @@ void RemoteClient::GetNextBlocks ( /* Add inexistent block to emerge queue. */ - if (block == NULL || surely_not_found_on_disk || block_is_invalid) { + if (block == NULL || block_not_found) { if (emerge->enqueueBlockEmerge(peer_id, p, generate)) { if (nearest_emerged_d == -1) nearest_emerged_d = d; From f1d9ac014efc6403e73223f36d3dbb7f5ed50236 Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein Date: Wed, 4 Nov 2020 12:28:00 +0100 Subject: [PATCH 175/266] Moved Killaura to Lua; Added ForceField; Added Friendlist; Added ClientObjectRef:is_local_player(); Documented LocalPlayer:get_object() --- builtin/client/cheats/combat.lua | 22 +++++++++++++++++++++- builtin/client/cheats/init.lua | 1 + builtin/settingtypes.txt | 4 ++++ doc/client_lua_api.txt | 3 +++ src/client/game.cpp | 19 ------------------- src/client/game.h | 1 - src/defaultsettings.cpp | 2 ++ src/script/lua_api/l_clientobject.cpp | 9 +++++++++ src/script/lua_api/l_clientobject.h | 3 +++ 9 files changed, 43 insertions(+), 21 deletions(-) diff --git a/builtin/client/cheats/combat.lua b/builtin/client/cheats/combat.lua index 1f9ba9a91..4904d8c52 100644 --- a/builtin/client/cheats/combat.lua +++ b/builtin/client/cheats/combat.lua @@ -4,13 +4,33 @@ local used_sneak = true local totem_move_action = InventoryAction("move") totem_move_action:to("current_player", "main", 9) +core.register_list_command("friend", "Configure Friend List (friends dont get attacked by Killaura or Forcefield)", "friendlist") + core.register_globalstep(function(dtime) local player = core.localplayer if not player then return end local control = player:get_control() local pointed = core.get_pointed_thing() local item = player:get_wielded_item():get_name() - if core.settings:get_bool("crystal_pvp") then + if core.settings:get_bool("killaura") or core.settings:get_bool("forcefield") and control.LMB then + local friendlist = core.settings:get("friendlist"):split(",") + for _, obj in ipairs(core.get_objects_inside_radius(player:get_pos(), 5)) do + local do_attack = true + if obj:is_local_player() then + do_attack = false + else + for _, friend in ipairs(friendlist) do + if obj:get_name() == friend or obj:get_nametag() == friend then + do_attack = false + break + end + end + end + if do_attack then + obj:punch() + end + end + elseif core.settings:get_bool("crystal_pvp") then if placed_crystal then if core.switch_to_item("mobs_mc:totem") then switched_to_totem = 5 diff --git a/builtin/client/cheats/init.lua b/builtin/client/cheats/init.lua index 6fd78b8b8..466ce4aee 100644 --- a/builtin/client/cheats/init.lua +++ b/builtin/client/cheats/init.lua @@ -1,6 +1,7 @@ core.cheats = { ["Combat"] = { ["Killaura"] = "killaura", + ["Forcefield"] = "forcefield", ["AntiKnockback"] = "antiknockback", ["FastHit"] = "spamclick", ["AttachmentFloat"] = "float_above_parent", diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 4dcfd1092..ec05c6196 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -2314,3 +2314,7 @@ chat_color (Chat Color) string rainbow use_chat_color (ColoredChat) bool false chat_reverse (ReversedChat) bool false + +forcefield (Forcefield) bool false + +friendlist (Killaura / Forcefield Friendlist) string diff --git a/doc/client_lua_api.txt b/doc/client_lua_api.txt index 2daac87fe..c332ca4c3 100644 --- a/doc/client_lua_api.txt +++ b/doc/client_lua_api.txt @@ -1296,6 +1296,8 @@ Methods: * change a value of a previously added HUD element * element `stat` values: `position`, `name`, `scale`, `text`, `number`, `item`, `dir` * Returns `true` on success, otherwise returns `nil` +* `get_object()` + * Returns the ClientObjectRef for the player ### Settings An interface to read config files in the format of `minetest.conf`. @@ -1336,6 +1338,7 @@ This is basically a reference to a C++ `GenericCAO`. * `get_acceleration()`: returns the acceleration, a vector * `get_rotation()`: returns the rotation, a vector (radians) * `is_player()`: returns true if the object is a player +* `is_local_player()`: returns true if the object is the local player * `get_attach()`: returns parent or nil if it isn't attached. * `get_nametag()`: returns the nametag (string) * `get_item_textures()`: returns the textures diff --git a/src/client/game.cpp b/src/client/game.cpp index d8800d9ea..491d55a34 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -2453,9 +2453,6 @@ PointedThing Game::updatePointedThing( ClientMap &map = env.getClientMap(); const NodeDefManager *nodedef = map.getNodeDefManager(); - if (g_settings->getBool("killaura")) - handleKillaura(shootline.start, shootline.getLength()); - runData.selected_object = NULL; hud->pointing_at_object = false; RaycastState s(shootline, look_for_object, liquids_pointable, ! g_settings->getBool("dont_point_nodes")); @@ -2532,22 +2529,6 @@ PointedThing Game::updatePointedThing( return result; } -void Game::handleKillaura(v3f origin, f32 max_d) -{ - ClientEnvironment &env = client->getEnv(); - std::vector allObjects; - env.getActiveObjects(origin, max_d, allObjects); - for (const auto &allObject : allObjects) { - ClientActiveObject *obj = allObject.obj; - s16 id = obj->getId(); - aabb3f selection_box; - if (! obj->getSelectionBox(&selection_box)) - continue; - PointedThing pointed(id, v3f(0,0,0), v3s16(0,0,0), 0); - client->interact(INTERACT_START_DIGGING, pointed); - } -} - void Game::handlePointingAtNothing(const ItemStack &playerItem) { infostream << "Right Clicked in Air" << std::endl; diff --git a/src/client/game.h b/src/client/game.h index b8efa3a73..51accc679 100644 --- a/src/client/game.h +++ b/src/client/game.h @@ -773,7 +773,6 @@ public: PointedThing updatePointedThing( const core::line3d &shootline, bool liquids_pointable, bool look_for_object, const v3s16 &camera_offset); - void handleKillaura(v3f origin, f32 max_d); void handlePointingAtNothing(const ItemStack &playerItem); void handlePointingAtNode(const PointedThing &pointed, const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime); diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index e3332b14f..bcdd80074 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -117,6 +117,8 @@ void set_default_settings(Settings *settings) settings->setDefault("chat_color", "rainbow"); settings->setDefault("use_chat_color", "false"); settings->setDefault("chat_reverse", "false"); + settings->setDefault("forcefield", "false"); + settings->setDefault("friendlist", ""); // Keymap settings->setDefault("remote_port", "30000"); diff --git a/src/script/lua_api/l_clientobject.cpp b/src/script/lua_api/l_clientobject.cpp index 90f0bcd15..d88b538a1 100644 --- a/src/script/lua_api/l_clientobject.cpp +++ b/src/script/lua_api/l_clientobject.cpp @@ -87,6 +87,14 @@ int ClientObjectRef::l_is_player(lua_State *L) return 1; } +int ClientObjectRef::l_is_local_player(lua_State *L) +{ + ClientObjectRef *ref = checkobject(L, 1); + GenericCAO *gcao = get_generic_cao(ref, L); + lua_pushboolean(L, gcao->isLocalPlayer()); + return 1; +} + int ClientObjectRef::l_get_name(lua_State *L) { ClientObjectRef *ref = checkobject(L, 1); @@ -210,6 +218,7 @@ luaL_Reg ClientObjectRef::methods[] = {luamethod(ClientObjectRef, get_pos), luamethod(ClientObjectRef, get_acceleration), luamethod(ClientObjectRef, get_rotation), luamethod(ClientObjectRef, is_player), + luamethod(ClientObjectRef, is_local_player), luamethod(ClientObjectRef, get_name), luamethod(ClientObjectRef, get_attach), luamethod(ClientObjectRef, get_nametag), diff --git a/src/script/lua_api/l_clientobject.h b/src/script/lua_api/l_clientobject.h index 88a6956bc..521591444 100644 --- a/src/script/lua_api/l_clientobject.h +++ b/src/script/lua_api/l_clientobject.h @@ -60,6 +60,9 @@ private: // is_player(self) static int l_is_player(lua_State *L); + + // is_local_player(self) + static int l_is_local_player(lua_State *L); // get_name(self) static int l_get_name(lua_State *L); From a7dc1135e94bde5f8c3385d54388563eaffe7553 Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein Date: Wed, 4 Nov 2020 14:19:15 +0100 Subject: [PATCH 176/266] Added CheatHUD --- builtin/client/cheats/init.lua | 1 + builtin/settingtypes.txt | 2 ++ src/client/game.cpp | 9 +++-- src/defaultsettings.cpp | 1 + src/gui/cheatMenu.cpp | 66 ++++++++++++++++++++++++++++++++++ src/gui/cheatMenu.h | 4 +++ 6 files changed, 80 insertions(+), 3 deletions(-) diff --git a/builtin/client/cheats/init.lua b/builtin/client/cheats/init.lua index 466ce4aee..03fc20c60 100644 --- a/builtin/client/cheats/init.lua +++ b/builtin/client/cheats/init.lua @@ -27,6 +27,7 @@ core.cheats = { ["Coords"] = "coords", ["Tracers"] = "enable_tracers", ["ESP"] = "enable_esp", + ["CheatHUD"] = "cheat_hud", }, ["World"] = { ["FastDig"] = "fastdig", diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index ec05c6196..adb8e7a00 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -2318,3 +2318,5 @@ chat_reverse (ReversedChat) bool false forcefield (Forcefield) bool false friendlist (Killaura / Forcefield Friendlist) string + +cheat_hud (CheatHUD) bool true diff --git a/src/client/game.cpp b/src/client/game.cpp index 491d55a34..479484ae9 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -3206,9 +3206,12 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, Cheat menu */ - if (m_game_ui->m_flags.show_cheat_menu && ! gui_chat_console->isOpen()) - m_cheat_menu->draw(driver, m_game_ui->m_flags.show_debug); - + if (! gui_chat_console->isOpen()) { + if (m_game_ui->m_flags.show_cheat_menu) + m_cheat_menu->draw(driver, m_game_ui->m_flags.show_debug); + if (g_settings->getBool("cheat_hud")) + m_cheat_menu->drawHUD(driver, dtime); + } /* Damage flash */ diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index bcdd80074..9e32a11fe 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -119,6 +119,7 @@ void set_default_settings(Settings *settings) settings->setDefault("chat_reverse", "false"); settings->setDefault("forcefield", "false"); settings->setDefault("friendlist", ""); + settings->setDefault("cheat_hud", "true"); // Keymap settings->setDefault("remote_port", "30000"); diff --git a/src/gui/cheatMenu.cpp b/src/gui/cheatMenu.cpp index f796fb0ba..1485541c2 100644 --- a/src/gui/cheatMenu.cpp +++ b/src/gui/cheatMenu.cpp @@ -94,6 +94,72 @@ void CheatMenu::draw(video::IVideoDriver *driver, bool show_debug) } } +void CheatMenu::drawHUD(video::IVideoDriver *driver, double dtime) +{ + CHEAT_MENU_GET_SCRIPTPTR + + m_rainbow_offset += dtime; + + m_rainbow_offset = fmod(m_rainbow_offset, 6.0f); + + std::vector enabled_cheats; + + int cheat_count = 0; + + for (auto category = script->m_cheat_categories.begin(); category != script->m_cheat_categories.end(); category++) { + for (auto cheat = (*category)->m_cheats.begin(); cheat != (*category)->m_cheats.end(); cheat++) { + if ((*cheat)->is_enabled()) { + enabled_cheats.push_back((*cheat)->m_name); + cheat_count++; + } + } + } + + if (enabled_cheats.empty()) + return; + + std::vector colors; + + for (int i = 0; i < cheat_count; i++) { + video::SColor color; + f32 h = (f32)i * 2.0f / (f32)cheat_count - m_rainbow_offset; + if (h < 0) + h = 6.0f + h; + f32 x = (1 - fabs(fmod(h, 2.0f) - 1.0f)) * 255.0f; + switch((int)h) { + case 0: + color = video::SColor(255, 255, x, 0); break; + case 1: + color = video::SColor(255, x, 255, 0); break; + case 2: + color = video::SColor(255, 0, 255, x); break; + case 3: + color = video::SColor(255, 0, x, 255); break; + case 4: + color = video::SColor(255, x, 0, 255); break; + case 5: + color = video::SColor(255, 255, 0, x); break; + } + colors.push_back(color); + } + + core::dimension2d screensize = driver->getScreenSize(); + + u32 y = 5; + + int i = 0; + for (std::string cheat : enabled_cheats) { + core::dimension2d dim = m_font->getDimension(utf8_to_wide(cheat).c_str()); + u32 x = screensize.Width - 5 - dim.Width; + + core::rect fontbounds(x, y, x + dim.Width, y + dim.Height); + m_font->draw(cheat.c_str(), fontbounds, colors[i], false, false); + + y += dim.Height; + i++; + } +} + void CheatMenu::selectUp() { CHEAT_MENU_GET_SCRIPTPTR diff --git a/src/gui/cheatMenu.h b/src/gui/cheatMenu.h index ea9a9d853..f67cdea5a 100644 --- a/src/gui/cheatMenu.h +++ b/src/gui/cheatMenu.h @@ -42,6 +42,8 @@ public: CheatMenu(Client *client); void draw(video::IVideoDriver *driver, bool show_debug); + + void drawHUD(video::IVideoDriver *driver, double dtime); void drawEntry(video::IVideoDriver *driver, std::string name, int number, bool selected, bool active, @@ -72,4 +74,6 @@ private: gui::IGUIFont *m_font = nullptr; v2u32 m_fontsize; + + float m_rainbow_offset = 0.0; }; From 3c57074150167eecd5589507de8230261799cfa5 Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein <54945686+EliasFleckenstein03@users.noreply.github.com> Date: Wed, 4 Nov 2020 14:33:27 +0100 Subject: [PATCH 177/266] Re-add empty lines --- src/gui/cheatMenu.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/gui/cheatMenu.cpp b/src/gui/cheatMenu.cpp index b7ce7d634..f4f85c7fe 100644 --- a/src/gui/cheatMenu.cpp +++ b/src/gui/cheatMenu.cpp @@ -1,14 +1,17 @@ /* Dragonfire Copyright (C) 2020 Elias Fleckenstein + 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. @@ -208,4 +211,4 @@ void CheatMenu::selectConfirm() if (m_cheat_layer) script->toggle_cheat(script->m_cheat_categories[m_selected_category] ->m_cheats[m_selected_cheat]); -} \ No newline at end of file +} From 61e2b3a33190d5979bc2e36f9734cebd18465f26 Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein <54945686+EliasFleckenstein03@users.noreply.github.com> Date: Wed, 4 Nov 2020 14:34:03 +0100 Subject: [PATCH 178/266] Re-add empty lines --- src/gui/cheatMenu.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/gui/cheatMenu.h b/src/gui/cheatMenu.h index c25af5ed2..f12f10ac0 100644 --- a/src/gui/cheatMenu.h +++ b/src/gui/cheatMenu.h @@ -1,14 +1,17 @@ /* Dragonfire Copyright (C) 2020 Elias Fleckenstein + 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. @@ -78,4 +81,4 @@ private: gui::IGUIFont *m_font = nullptr; v2u32 m_fontsize; -}; \ No newline at end of file +}; From 1799d5aa9ad38b7debbc6b0c4d1e090a68219e1a Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein Date: Wed, 4 Nov 2020 15:33:48 +0100 Subject: [PATCH 179/266] Cheat Menu Improvements Change --- builtin/settingtypes.txt | 20 ++--- fonts/Arimo-Regular-old.ttf | Bin 436876 -> 0 bytes fonts/Arimo-Regular.ttf | Bin 896112 -> 436876 bytes src/defaultsettings.cpp | 14 ++-- src/gui/cheatMenu.cpp | 147 +++++++++++++++--------------------- src/gui/cheatMenu.h | 19 +++-- 6 files changed, 88 insertions(+), 112 deletions(-) delete mode 100644 fonts/Arimo-Regular-old.ttf mode change 100755 => 100644 fonts/Arimo-Regular.ttf diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 620e4b355..74fb5dc92 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -2216,27 +2216,27 @@ contentdb_flag_blacklist (ContentDB Flag Blacklist) string nonfree, desktop_defa [Cheat Menu] # Font to use for cheat menu -cheat_menu_font (MenuFont) enum FM_Standard, FM_Mono, FM_Fallback, FM_Simple, FM_SimpleMono, FM_MaxMode, FM_Unspecified +cheat_menu_font (MenuFont) enum FM_Mono FM_Standard,FM_Mono,FM_Fallback,FM_Simple,FM_SimpleMono,FM_MaxMode,FM_Unspecified # (RGB value) -m_bg_color (Cell background color) v3f 45 45 68 +cheat_menu_bg_color (Cell background color) v3f 255, 145, 88 -m_bg_color_alpha (Cell background color alpha) int 173 +cheat_menu_bg_color_alpha (Cell background color alpha) int 192 # (RGB value) -m_active_bg_color (Active cell background color) v3f 0 0 0 +cheat_menu_active_bg_color (Active cell background color) v3f 255, 87, 53 -m_active_bg_color_alpha (Active cell background color alpha) int 210 +cheat_menu_active_bg_color_alpha (Active cell background color alpha) int 192 # (RGB value) -m_font_color (Font color) v3f 255 255 255 +cheat_menu_font_color (Font color) v3f 0, 0, 0 -m_font_color_alpha (Font color alpha) int 195 +cheat_menu_font_color_alpha (Font color alpha) int 255 # (RGB value) -m_selected_font_color (Selected font color) v3f 255 255 255 +cheat_menu_selected_font_color (Selected font color) v3f 255, 252, 88 -m_selected_font_color_alpha (Selected font color alpha) int 235 +cheat_menu_selected_font_color_alpha (Selected font color alpha) int 255 [Cheats] @@ -2344,4 +2344,4 @@ forcefield (Forcefield) bool false friendlist (Killaura / Forcefield Friendlist) string -cheat_hud (CheatHUD) bool true \ No newline at end of file +cheat_hud (CheatHUD) bool true diff --git a/fonts/Arimo-Regular-old.ttf b/fonts/Arimo-Regular-old.ttf deleted file mode 100644 index 9be443c7df4832028061c8fcd2282ca61e45b27c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 436876 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 diff --git a/fonts/Arimo-Regular.ttf b/fonts/Arimo-Regular.ttf old mode 100755 new mode 100644 index 778d3a059a46e0f7b2e00ebbf448423adc29f7b4..9be443c7df4832028061c8fcd2282ca61e45b27c GIT binary patch literal 436876 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 literal 896112 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 izZn`nOz?XN^OA75uW#iKJ-A=q(jna)sDPbVEdZ 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 z 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

      HjV=~LeHCNlKqss-}1>-P^(Ou7S3OB2T zScDOB$$gCx4&TMk{&?mCaDbKdz9>f%jnY-Z;!nXN-`K$9IUdh>_E~(#fn!HUOz9TN zM_QyUQVuo3UZAz4mH6hlT`?L%`>r{Pi+br222i6}gmV#6JKu;W)`_jhsVmFKG zPh210D!H6?@e6Y)+?<{&bh*}Yz}7OLUH;~u|G4{kXCgruccEZ{y$ZOGaW)zEn|$&r zvh#I=RKgVxk9PyS#Y2!!0@8<#J+NAW|L5{g`~$?)i)!B*5_|I-uCBjR)<3EFCq_A! z1+9S($;y5h>tnG(!@Yjt9LrV1+;i)#4f4A120vSU0Itj_5wqtJJ7=WAPoxtc4EAOF zJvLGN|0pYfTU>Y5<~ZAy3456@B=3Hf{O`P11_Z-vtQ==aKQS86%iv9)e&<(RbDrfv z-`x*!w{s8%nG88KNl;U`xX&~yug11kOxf>ayS+Ch{PY%E=6QK6NY0X9Ib$5{xA<|b z&ZSOQ4ZiA$E59;7^Iw0SK3`kfP$M(8lw}18n@?q)W9Bb2&JbPW4|sw)*C%qdeU)6P z*picOzP{ch2n9tA+L%OfdM$jBdcaJgtdI{B=xf^N{gdxHFYA54>ezSX$eR^j=rNP>x7DxcvP-v8>v5*}}!o=mYzNDeZ&k zl>S;POSB0dwWv?7>e8Za#h0N4N+LO(dR?loC`*C=SVEwI_ecAbF zF(ARG#U8fIu+&zxT;m`Z<6-u)D@pL#cXwV%wIW!vjVvt3oA{!G)J-+tF$ zl&KaJy{g@*V_Hml6Q5?ClEdlm&@=iRBZtT&bu0OSRsYREg?<3ntbF%xC3WX*j2dmX z&TlUN|ZETG9#y@JmFr zjlxI=?Q!ibS|E*LeYB9+Q<}^7rq8cl+9zCKK2pKro1q-nz?--Crzp;kUdelRH zRbj=`r}p9>>dmTGOQG>f!#oZ}ox8}dSS+hgh4CwFnm^ddux=9-wAn~1@z=NzJLVqZ zRsGFah2_xV|L&w3bEeQhNK@_=+A*dIY7oz8Vx8>tt%X5t6sQ1 z9LM)}to`S_f$&b* zp9wVN605?$nqM$#ou_=DlRKloBY&qwcG-8R$u`n3{8H_tSCh;K1&IJq;;P^0NSVbe z(8fcK*{x%+1#`BcAX{w3nCRAQdRjVI+P1|FJ&t?Qchy>z<|4k%ZQJ-oZS~Y=+NZuA z>uY<_O6~7ZZboi(8QG!FYEeu-OPsD+9|=S%_pQC8@QB4i3tMt7`(kf7v~27*C4!b- z?s$0zL0M{|F*t+Qy*{$e0+)+citUP(v#|(&_0YZw`d!Hf)CFi#N{W`J*!a`0{-1Zh zIjQ?puY~y1s`1KLqJWQz=F+`a^1G#p1GFq$avH{MZsA= z|2|I(fR`m(kM5t{2)3vLHj?h>?~(1WNQLjs82XTM!mpJ)_tf8$9sPlx%1OnfZ|-wV zVz6=8c+9@`3syAIQUYb{cP6+e_M55M~wRrWvs>c75c{!qg<`%uM#A7RbXq_n_? z1f<7E&~>%A7X1WKIg&v1&a@9o4L$3x0@WKJyuycdapd^COP*;yT2&uZoA-Rn8Z%K1lsMC7(K z8DFQ0z~dY{BL`*d^Zi~pV0E+#-E*(@$LD@ds^oCoyPpJ_a+v&*C7J@WW4{yOXqz6D z>7*ogp`NHv$=U8hH%~N$BewRuB4Ig;4dXlHGtXK}U9f0XU2=uPK;k!`iBWO~3?N|n zk21dCaQ_KMFXApE9x;>N;P2^~aF`oS-$(9|Ga_wsd*h2aUV|^70fpQZy~7ik?2(R# z`|0;l22&YnH@{gm&j!lzcdsx;u> z0ce@kDAAwg4ogJcE$eK#&%3iVWEydV3Y_EjPYceh*?>iqAj`Y$CoAm(BX-nZ z<;vT0`L^+(YIZ}t3LwkeLW;~afj03H5Sn!ZL&5`F3!c0Z#ALK`F^ZD6J-buxt=v@aV~A$8}9Y&=GVqs_(~!(0S11jDg_bt&C}glz$R%o(ud}_ z;9FyPUyU{Z(UcgCE7cI$h-sD}Xw(YzwGQ$z~t`SY@yC!AI5Dkbos zqO8DY0bkZ<`0tc_`>wXHLc$V6_pOzGBacU93lZqUtQyO}e)JU2OH`@u?l1PIRS#CG z{K9|d9g;LfjS^rOsYnI=9SI>yr>$`|B4#~fTs>)eJ#mv*N#wHaL3H4IsRDqpyU4G0 z@3uRB=VE6ZNF;g@m5KO})|qDg0?s%0k;jePm=?KG+y5PvP0@=2i{8Q(DS^%G4`sG> zY)gK=UP0NGGmoyCtLeI;G0@=%fx!z|5N?pqHC9viLF@aw?lZzqK2qdJlHfGn-E+>G z)#h)}UwZS2MIoI8%V(IE|6KOW{m(A*264)|=$o=eA}q=r8WOA0&a~#?e8rU3*#|gM zB^>TRy25N8OZQ7RJ5Cg&*+LCPxer*j>#%M|S)yf{5UXu>gZCy{5!0*P$JR(}u{R=zC#0nYbz#Uqs6E~B#BHPR33lW?OI zTn)VCcY`mY?OT>?nRT|re&YG?*tUQiIFlB$woVM9#}=Pq|7N>YC&hM^UNk#v!a?Vu zQ}w4U`}-BlW@9G9s-NF$FmnpDkV(QGW|0+;EXkxZlgOTmy?`i1kGHhJ1f#YOfRPwLu3K@td^URFYP`)m z`0V;_-B;R0YdeN-i+pXN=I(|dWe;%fva{YgD}XC1P54rVS1S$U6tPa*h!3>f!UNVy zZeEq@ial2w;<7a3712)#1Gjcd;aA>zyEf7A-*YY6+HIgWxCaeOuf!Yt3vGqlvcsYe zsqA=o=~4-_?3exuz+cqZ$`L!M&`a`2nI&6~`WQ$^CNmqMkA-K-IWMMD?jE6&>^=C+ zk85oLB*A;CJs>B-=lw$Y=BrOQcl{CmeFCgANbKzyNyxdfz^WW?Z4>JN@oa3|m!;#j zIE2MMM)C%$qWD$m5aS@6HQ>G{*utC;Lc{HzBUVnu0s=H!Q~}_*-l@eF$UckmWwD#~ z->&C+9xV;{SP?45Q)A!Od&08gk_z44n_Y#LF<<=}8TZkqnRoS8!GVb-TaQ|7iVrJ) zvi9h=w+PU+&t189)qpErR+J4UwviB9WXg86Ja*l=W588-MIq#0t1l z+skXUY++EV?B1m+m@YWj!>A66)=Td~(~`-VEGc zQ1465v7P%=w$Z&3aE>APQ&qArJ=x>REIlsayOZdJMuv=+^M()PNb7Uu=;g&2OOZXa_j=t=tzGS1r{!qa;6j}e@n(IFK^nb z_oXu3q&x`N1`OK>R8$;+3O;fb-(ZS#*xh+~>_7C@)`$Qd15!Xgj+v18s+H@BYrs4q zsyU(u7PniE`M3fmJnaM3-JT&hn}~puGD6XR)Z>Mqi%uJm;F_H+x&Z4FE3iWaYRQ&I0R+ z0@OBX&~32Ach|Z4Tkmzk)w>H$u(4)gA64F4Ik8J*AU<-8cB~#UGF)Guv3%F669ZWG z5*z-6G{V(lVH3f)MA$6`P|uvS-kk`h!WhH4P?r_koO1vMm=Ui+j;I^k3V7nU;`RFd zih_U0Q6!udiBiK#S@3+VX`(=`_9qhBKa5lCHZt&ov$n;X|JBizz@iC>Fs3{CdfG>R zcl}v+&)l(5fqz!qLG=k)?6f#Lb##lB@Wzr56Qx9Aw1VlC!emta8pk;8{6S8E`}zj{ zG%kD3Fg!TJ*y2-5J0}k#BQ<nOx8cfZ-nma{s{q6c~cw!@K zB??MU0G#h;!eFfM-~5m#7>=9^)DS=SVq7upO|wo3|k@NGA$737V= znJkLG#C*znwd)HkND5bsCw`a;U-FUdv{Z1s!E4_e@W7C|3mQ#pQrn`*F758{i z@pa`!erGLw#f)GBfUI*-a4;ot8ni@4xUdn`^;8um(d$(RW)H=V*u>niJudfz8hI*F zEF_XbcW~+$1RPrpg7z(c8bij|Ul2FxEbqSgfF)vlw+zaM z2v0jeONy5Bj5$2m;(F5^P$wVZ#g=C*P$;OWF{wa%`oJ2`V^URJR(~UZ{Q`c#5ucK) z$V)2V>yaP&Fo^Km2Hk|8E;7S|R?x%zVo#3wqlK0qg;`>}}O z`JNk6Q(S@t4MHX+A-IS*LNrVg6m3Ab_r}Yu^&O~ldt#X6PYFhSSQMExcprF~tOqD-^#~dGJ}tmOMX3ooZp8+;;%@4} zZgja~OzH99b&s8cKkJybBt?YO;!eP>u8vp$mD01tbbK)S$0$u(=Dda-!+hjk>I z3}#<3QI;ffwNhnF(Mm&{(atY2aea~a^hnj zI5T#{XT+h=xzA^`DOM|!H@u8jWoDXa`6RCpplF6g^@2>pOrhOv%XUx<3rG@7y47-H zL{6MDkYBPdSZ6t5_PL+=fNBf^C~hWao^8KtwA$xohJaBM=jh zKFFu=WdzvjP$q!7e6srI=loA*U$TgwZ}8{pZ7JUx*Wq194Gaa`6Uy0Q;1ON#mE8XS z!1>gMjlRlGVk+>FRY}Pc{A6ujPBXcmFTvH{?q@+_-PQvgOHi?8QS7J=o5bpSX?3E^ ziA)o+<~wu5YO4skk>$8uZrUT-7Cd{{kr7Z{wMTWx=<>G3YhG?zQIIE;y0-HQ&s26s zmn6B?T>B^P%)Y7s&N_Gey4nWzG*2l;cFLvUq_c?NOxtHu$E#Eje&e0; z?MQS*wI0v<;l0>DQC-(8F9h{ox2ty>mPMHjN5epy%M|u(9 z1V-b9v#XbL&*dzc)EIK+G64Z1awT?RjXUOEesg^?^&9X0u3Vfhyhn#{2r7w92$9&B zA(}k-|D`Zdv`IZMd2!CrwiX=lk;C&BG^2Dyo_h@xwe87$BT5S_N z`z;exr#e72$KiWk?#>Y{VE6QKD28_YyX^yzaKn1Yqoda$2_S}tC%9sfbU%$Dd^&pv z@{5G}8-3EJcLafQp)xQyX^_gneJlrdc!p)hU+SUe;BI-J;dcy<9f}P~v{T4mEWGh> zT4ZqGQbZz6MB`Dbe4bq3Z0~%u1>N~*RlMV9MLcnWI#S@G__m?SoUv8LNAPLZ>apr; z>b(HD=4)ApbKau$_`~k!>KM9yi>mI3-Fvg2N*KBVMk#U3^oH2K28+I@4fSk_C)lrT z!p}HPNug&cU<*B`w_SDM)$UGTakK+AR-ou&3UyRP)&HK5CZB?Fx37d_vL0 zD?mOqT8}RSe(&?vf|syFA|A+3dD5vitbmVww2oSC@o)Fp#$G1euZ8J^&QWu4$H6z! zUzld>{wsdF6nF*y`V4%#?cV2pq)0IR0Czdyw5uL_xWr#jqiGOiB1;n;u}9o|GwsNN zix>C2`?^)*tQ20G9k!}F_DWoIKVm|7dNw#(>Cf$^54LqNDmEk6>~fQze8!x_7xfTR z?(rDj_B+fF++>V>7m;@L4SIvW-@t0vl?0jY5@OllJML?SF?hhodM=!q_-&Gkjj-UJ z_)i+31S(NS0_$O)YYe7*AdVZvpo|2mUD*eiNa75jCJtF(;bSwVR)6dRWHAJGAep-& zLNR%bU76IGJ1*>6-{$X84WP$?^{ zlGgpyChzZSa3&ujvQMJy@3}vqqETU83;cwqY#{;yh(R$Ki_y25b6kD7`=Idh$Th;I^6@Y;+~j$41T z0C-$*-s5c|-XYyc_JkSPqH?_4zAIIqH1@!&;|>RW99+pGHzaw7Nhw*dre5e+m9K3}ft76v*@hW!>4Gpbc}D~9)DiypMlBfb(2*QeBM*yM z{2QNupZYuDu3i>>aNAC-r;Qyz8f3)$1gEV01_yyOw9huF(r|(&+bq7{;0gLuL&Y0FY?JcGeG0=AGUE73hq}JBxUg533qaB;7>MfeY3Qi0yP;DW{+MG6QBiGdZ zz{~P91RM-|i~m0%o057$152lWl;qP>930@&2#i|s60piJXw+YvnRz?5fyl~ z>E)v3taWnOm)UsD#=2p?Q*wI(2{$kzSOJ)2Vr_S~c{*RW*WaSWOrbv(yO-s)jNzKJ zsR1Jet*(o>PqM91)$ejLV4gB^IJi@uW7A#T93Fl><0w32=>{E=M_Mog9C5RM^_OjU zt5&Wof|tB5NEn=VrUmM~1S??d4(c~wgA$-jNSG&g@aZMwflV@4OOMhGsac{%!*Zhg%2`skqg zqwX<1zn)$+`oBfUE&5&tlR1uIh+a|9`gzZbRtIn%rDzJ4f)my`C!yuY_`O;P6iq|i z(!$eKOoCgl;V$~>Z~J}yj&2Y$hT|_>%M@bIEyq0-w{nlO?U6O3HT%5+o3`D?yEueA z78y(wZ&6`ic;B!Q=O&Ey;dl4j#4TM<{@$u$Y2XvTF_2itC@S``dVEt#HNfqa4g0WO z;Jp@iksaJ-xTTUnzEOha_+3Bj_u1e-IDt$50c^W9Z@@?Q3 z%#Ov#KNUb*mm-AS*s7u;JjCf^x%w^M9Kl}t-S%TP*%%s{H_X}U zRV{)vJlX36O`1ShZ<;ZE{q^)S`vFT5ttS9CrBW3@N>#f?&UVX%*41CJ`<5Z{BvgWP z?vt$=t;KKD2eLlRL-||i>Tkyr+YhIB5D?FN z6KWfrQkPhPG1MWR`i zXAs3S`a)M_YZ)Xs%?iy0JbF8~7)F(_&)kefE06%M3;70Ec(- zOzayBQHFbb13mhp*9~e;Up$}S1|7P^00Bl*eKnsRa>#9F)uh{4Gks(NRYLi+7F{u- zZMM87QOB2<@C?&>l>Ear1di)nm}fkGZzU}GN~6*v`(oD0ui3_9>aTO1?T%Xup|mw) znQc74X|5x2Z`=&WC~r1e*B484*UPwLP2VD`1>}i^*mf(H|#&xv*n(kk6EG(_>)AU z%5Z3uX{!~li6B@5dp2COGpTV_VZ&6{-v@|XFN`r5ooeui?vNEu(fWZTai$IJGSo374CtV z1rM0djzm2|M;`8}GOn$^yF6}hUJdNIm2)$@6ty5VN&PPeglbE0RTe=FgJQYpS;n78=zI0!+vv$qMnbObFl%OIU=IPyKNJv14O- z{<+OJp$E0$ac~IT>=8l`Tue<)Ta-xRy=}N6Q-KQ(BpAq)8o-Z13yb4>Z0yl??mri( z19PCnsWl)%pP7Y|Tf zwG?(fhx?~h9chj5pA|{Nx1R}WeWs;vo?5Pb=G#zdsoDqc9CoTPH{T5_i}^QP;o zrl}sh(CD2UDKnxk_hY<#8d`*=p&a;&Mk`em$4K~caQ>k*z{{w90GbUxXlsfg!EbNSkMFK`2_kKww7~M*lC+<+wh8{7foebzbVNt z`Bud?sj*t*$9(7#N1ou>clLej_eN&Tj|QnI(R-{MTGwFMx^L-o_3MVRuabfb{Fnf( zqtz#O!{=p36)!GXu9|F89#}R|8X|TPPSlK0cN_JD`Av`ZJ6yGdO+e)d8y%&_xoF<~ zy-KX+f?7pe~Ho6xg=_lTB^dsxTm7pIi;m^ZsG4*>(K?bE-xtYle6+d9v& zT-SbG*TbnRazhGB<4bnix7M>B;Uilh;33>Wv?Z$YsU2xcLc46}F1Iym0K5qkxEvdm zv`r#NFtnW-BSZ1Fqm%BdMIRhoWp*&rK$}u06(-JIeDA*CX^o*Z^w<))+c&f&iq}s0 z?g8`A0}zoL)(3a}OYSL2fCinh=?hk6l+YHskX(2qj@e()d_p&NTz_}EDryAU*G(&v zTYqH|7@aZXRoLQsqqS;@KyO-`S|tXYc!GKf%;LWD!jXUTONhj5u4Y*=>$pC+`3*xX z*1UN|i|HrT=LCh5N@r~$IqpVJ<`k`d$A!B*i{l9=@J?L|F+6LH;T-DfulS(ot~@vL zVQhm6Z0obHD^G8Ju`U*%(Tjzoc^F{Xz&H1(Q?91M%u@7}tTsKrX6D-D@oRhTa# zVc0XeW!V&JcRLfN+q2Mw794;S7R^qi~H(Y~pY7zG}cwo-GtisfQo9 zKt6(@F`eZFLR+2{3}DR2f?S*Rpvx4(-J0U~BRA3m#^x$|_j8v_-{tdSW# zKtPVXP)Yhjg#A%o|C?d&IPF-$>bVK6oA&gF65#l$Ap4@6Dh3nUkXDsvAz=jp5jfj_ z_p|yFJ~(Vm0Eor5IdTGcB#arCoKIM{3QkxjG~v<9aKgJh?CxW4Jtu+Mlme@N84P-% zGZ+wrR3|pjqN%^>%fzu7iF|FVSg=dZ5%rx1z(L$r>yxVB( z_1#V}2<=pMu+=peWW)x1?T@FP!d14D!-*aTp~^VNtI(t}0BK8qBpBZdVKM^xayMkyTR&H0XMQxvls;rj8ac5VL;O zzL2q~9tbiMC9*1EIEa6iSC+m2Q!b#3{GRXK6(vgR$PRmW!3 zpNwiiaoIsO;Ew5^&z3JzepEtQ_EXYYX`Mh%!Y$=D3YdA9w9C#hwI^uP( zWecWXf;HN_u)fE-hknao``VhE5@qB$v4z=I@OamrJtNYoC6O@*eIM-Tza;*F@s9m| zj0~RbDISt-i^<9{LJC_ESu%Y16OvY0v$$WsdWOpK!3t2TGTzPz+~J@@Qyk~46Kr=@(-&e2~KhW#UbQ*Wb>A{d}7Eqvt2 z0(?F*mk2(3zOi<@{GecSwA$~xM-RCOMV~=3J&!AGefnVbZ$Vd`I>yIKKLvq01weMH z+SL})B#@tZ_W6$LyZAWvUi4vY&(AeWpEh44oOJAdB8Xt6XO?kb3wrO_cdq+ZJa<;v zihPjn!riQ~4<(Dl`PBiVx*3pMZjS#SO1?yK$>!E?Wj@f#guKR0Pabw=fet9hV*QGa zy&oU#+Jb3r@u!RxUix|HBjsA2TeN449D(Iz1|L3AJ$@ChFBoR?J+3!t3yn>m5fErV zSKdGu2YdAX(qB}RKF#NJ2IH=JYvcctgjP%+E=!NAYhK1JFnH`$$V@xA^7pQ>Cm)eB zTPS7WRmMEK&qPo7VGZQ=X7(5}K`*Nvw0w8Z^|EJf^Ris5g`Uct4&!%OKg{BatV65P zNQ9#9Ljs9q%AsY!UO0nhxT#b1x8-un#=$bT${`X)x9qbxZ;g7_w}2Rx^cyHdHXc3j zS~j2l-SEiv&R@$Ocyf!~+io7VTJWGcu-IpT-qs*g=`(=~RP4AE(mwoYmJ@(Z{ zWY`*`zM^kf$<~F89cQWu7^08UqPx?y#I5bc6QfD?`e_ zd(Ad8Ds`e`GSc+Gc9H}Sje#J#+RB$7E90-Dt&Cq6vJ)Zn#wV4#N21jSK%(ck%@MlL ztN6Bvc$$aCn|%J|(PI*4E~;)Wq@jTfRqI|Yf$SNwF#=?wbF(Lr_xX~_<$8Vxab(GU zwjvfx?<=julKvKZh<|Fg%8ppR>jUS6ckYfp5Mx3ebY~xtvLA4F`C1Fm3tf7yh!l8- zhT(=4N?jBypL$vIfbc-*bo3yPsrfCY_FcS5j^=8AOUDuC6>nzzu3o7`(XUGf>=|3U zAJ$PSLOAE^-JWl`wH7AC7(;RmkmZvYrht7LB$(<%eeSU>ey?ulnLvC^Z&kGD5267V zv4#n?mtZ-YKZ+evv|!}gwbhQ1HwndKAR3r1sx|$qA~gFrY&WAh(}7g5?{P4ng&|bH{>bJE-ZmP=jt?Qy>t5 zqelSD`#(L40|^XpcTRO4@;`KoxfEJ>{a*}V6Iuz)3oS>(pU&?U&{6dS?VW0K6&8En z;60;~r(UP=AP0@pS{l=l+{D4_d1Byh5|LsAF$ogSr}o^Md*C?9mFq8$#&$MUG)cSwIU!<+SUkl9RrQoYHI@a;lbVC%k{x`iV^(8m(u%fhBKlHY}W`DInE+6kcm+_4EqUqW1 zplA2RRFrKW273E&46Vd_x7DKAT19icny-^kZ%4rEethc5Uz_LX#%(yAAlVF`zGO~l#*zbZf^}@{oBC&jWtDmcWs;C6Vmp)S+FWGSaUR4#8VT!szi+Y0nejjw( z9!j6rm`kdh!X~Cwt)hOwx#0Cv03s@Qt54>NLyj5UT9<>9HG?P&Bq`qg1oJcUfHQS) z;c8zU@!aH^?6?Y_j&0=TZD~+pBPKmU;9Yw75A9d_HfBb@u>F0^EQ`K!)}_g>w!5K8 z<=dK6cY5b^b!>yEv-?ul6JXI@Ad?2^p*Ibs@JuvLL@v_W&a4saWx<94nWa5Ub5rdh zFeF-?T{YK;Mek67ZXnDnyv}z?2X@REF>YB7xYarei3M=W62L4*_?g~QlLR>13Q*e) z7u<^0kjBbFp1=;jItby-_OO&a48F2>GtRuGUuNnwq!d;hOF^FA2-FX>3d9Nj9U^vStF~^uT z>qvEmbfC#te*JdGXp;!bPvgjZm!EnxZb9YJW7Bte=!mt@2AcwdK#5Ch)o|Mea?+Mx z*icZf0tOsJ#W03~(hNO9XH8G$H(}+`JFLZXq$yAkmp1UE`_h>e#Wu@Wj)KJu=&FM* z6u#Tot?weeDX=M$0Nwa;4)32nRapSWS)F@9Jc3Zr*)L00?i%YqZ_yMT3(oG#zPDG= z1ReR}w}7VcvS`1?1>R#15;P>zY@q{&u706Jk7{+9+DCZ6$zu7*@CSDSrKQ3dJABp4 z%8!e_l1wZO^|O-cQiJ81Mk2#@TC7IS$*cZddeYo56c+E2wR}QeV&9h4?hg(&w+PB< zbG&HIu~T}Pyq`X>#nj5pm&~m%>_1r|de!G#g9FjPH#J6lKYfHP#P_(3=OQhrSU!Bp zp)3Wt2BQl8Qz@G3bu>S%RTka*o>0d3IukmOrpsxK zNIcl;;iCdnL;zx}bp}Ngkq%YcyelVN^q~jeMHJtKOx9k2j`d42&_lKa$C?pcSg<@- z_^Z1BAHk7!ja)t#L=zcK@~5&QJMC>#$d2xYr0BR|sS5Gbsnu%uX!=RgPRpX;!Dvv| zR5F}#h36LNI#Yj}-iAD(vM<3CY9L1^feFd9b>3)(|Ev?dq2)xw zkwhb*j)J-npn^h|*OElCQN8hYv!d7L%$M|l#!*Q_dGn$zk(_ocUG2BUL-ITGJbqGg z5Dw%(zi&~nV{9Y~<%o`O6Wo!=g|1qXG8$(I+iaERWq04su}Q3*;sT>j5!3AGPyl4) zVd}lv;19;d;f>XvTlMcJ9H>6N)8(}wdFeLXwvQ1Fk*W*Y!qOaBjJB!%t#mFgkriXs*g^ajt03AD zjIfU4@E)80>F&K3p7>aXhD^b&98kkp5ZYY`8qg9<5NP4Md{@i=m-JJ&%|G#&0+T3# z-g$amZmHL>NG)F5_8U0ltb9W%XN)n1#)JQVEdTrCum0;9y)OPQ>+wuzR2+rX8j4kNDZjw?rYRoW9oS;f0{efpTTOtqW>E+N9cC(&!nOSCH z4JFAlv?Sr#6aWUTLQa)-<0VR)@r&20i#M-3GEO|bj}Iefa~RP}9*xqDJs}6%qw=;r{yvRqEPbs$@DJ}a z3^rzQK%wRK8g_n)^2cYgJ@mID@C`Yg{L~RNyzyQAJ+)E0 z_6HqHwu%gN?4>i6M$0n-kt}~7V+B>iI{LHV1~%lvfL`@1#y`D|0{s(6yh zKGEJB(pF(X4iaoxvKe2izv+Kf*8}16f(_Fvp^j;E;vF366$Sv=;K~`i3RlPVQHwrd z!;*VN@O=#SUfS!m<lkMaZ%OQ_jLx^1)GrMbdoMD9?qT;{Rp3fP?E=WAf z=R4>BdOs7=8k>mEeG-qRC-R7aZA>TQK6+|MZ71Q%n2A*u`BPxd4!E?3%rAc2}^~1f5wk$Wi zhu@RuaNc7@@yc%Z1Yg4{U9o!xFJ?OJ-v32pp$>y|tAQ1*15 z?~L1OIs4k5xiy+%3$6MAcG}-?-X)a#U30{@+@E|k=R1@YRrDhO1|rvTHF0!xQ`~_U>UX zSN}p_W()l6&n?l}kx;3VGxqOsRGBq@z4!D@@>P*~k1y-jW8>YSYtm#)OFT6@Fw8{0 z;|R>bPBnnt7;X4=_0e_xojH)cgZFDk^=%Wu3Kd!7%U}t0IaW#{nM6fy0-|Lpe>n$E&A)}B2!{e zsCS*T`dqLzWYJk|kwIrwMcYEBwjjf4-Iloydl4gzth>@$ae7$5IyPtkL}UHm~C)oiyg^=Uce_+EUYmmiiVXN5=9%>zm`E4nrHgn z_$t@cm-TmxtM=+_N!P~zCG7M801a7meGfD!if%a;C-`o)XH}BLU358tdK1ki=U7yn zqKEHo{VF}b$UqBtsJ5}Bz(COG*rP!;8|O4K?si0PL2*|IT312o8Jc32vlvD)5T4N3 zqBOCFRSV8{-rlRK7G?HYq_b-Y{QBO+%|6DeK*6dikPm-P_StPDxGeUs)j?_or%ffm z`dwL)laWptHx)dzi%ao$+7EHDA~khV(1gisbKX*t*c@-QHbs>Z140OsMTn80_+y)L zDSP!vtoMWD*^i~!VAYHQgDoQgI1R1p{j^W>@|RV)U@bKYV9f{BF0~gaaw3M!R?-`Z zXRr|W!j5bpWzvBCZ;O5^w(|U{-L^jn3xUMH@4nMd&1}N*$N{&uBJXi+A#HxspLUKo z6vS)M;{=NEXmX^iuw$p~Lw+y5#(eN(rGJ3+R72{qY^n0g67V59zn zPwWgTkL6WI+@sl$wR%i3eUJpF-3zpAXUEfz)f;j&YhRZvR6$FKCZP-6i?7nq`Jtg`*vNo zJmOZ1?|bnj95TJ9*amlTrvA#$M{UUa7%K~=1=Ad7kFCz_>>#L0TS+Xy4W+2r+KF&* zP+$5QcvoL>VV%+QYg>>gwHbt{NX)<5K|s$ePj_^fteAPlj#;Pg_}%9E&SlyOX@ldn zau_a~U-yWn^|$P3^#qiRhF-8Ma-F&Rq0YY3kz?$liw`a4iL#T9&(!%1S8qDsYZ>|me>G(iT9g$0V>;0! z)G)^lhz&h^L_yjGJomK%%)1RCm9F3l*)ZPT)$l<>?Woac%Mjx+!M{7rIp0>{73Ry% zf!LJ!7$<3Luskh{BmrhHop9r{gdi9^DSPLeUaCDq2m)3 zC$0aAn_7Z`t@~OQN1jvQXeRxcKx@0WZ*K|T@qegHDBOI7_5y^yjo>Exp{MP4{awf zF8OVI$ge%(Ev4zB zv_F&8e7!N>M>|$+Xqy7$E|1bRLT0U{sD#igRHiKufRYUa9^p)(c!Cub>im$8QR4Vu^b2TMD96D@GC9!qabv-c{LKFE=Xd`Ur|`%-2R0btPGA zC4-Kuw9hKyjp{q%Vhb4KsAXxiAb5g6=7mwdnP*N~-`_tp;j{m`;jvG~z(Wj}tdLSs zS*(fd!4zPHykG@>|t03gFKLYUz|P7CdQGt0XrfWulQ7iFH4yFF_Q9bIbnc3QqAF2I7w@ zm`~OgebMkI-E&+zj%JZ_-gCdNKSe9UUXvWG?EuR2K9HwrSZfN(s0q;Xsri=cIl^YWc>iI z+7ZCX8i?mM&yT@_ytPJ=bea(2>*;;yK^xFlSCBv3{Wm`VFSN{soN)vq$v-R|HM8V8nUYVUA#3@WQMsObqiXg~=MfK}%1^dD1J5zAt`T2bz2WQ$ zdMbcVn5dEBDua*RZ%e&ceYd*?0S`D`p!C$9vhowHks>ip;Wj_%*RK}H4e~Q|AgY}c z!>l+y<5~TRH$34%eojs}V$l_oQlvpUWS_SDmD<()kl8zDjqw@V$yqGkFr)lBXOtf% zXQ({9b-nrNDVOec4Fn)w*6g#zFv4y}AK~&{B0i*e%$|J-3upIRU%x6OhIc#)QCxzG7bxG!*UNdraoW&99^E*$rVDmbOdMK=A5%EcpJ zkIH0n$$n}eIAV2znLW{|p2!xQubk2PbfkIfj79%<2$aIIjMcf1-a1sh=DK2Vy5gVaY5AINsJc@D#PnImf70z&ZOYcqj0Bs$oMn ztAp63_c(?TGb?TYVlt;wT$&J6g(2HZ0BM>tuOvK)A{E}{YcFVQ?8FZZVnwI~2py(N zMXgPo*qK=v3b^{}mmad-`UKGvGI~z2l_}|OH`oRh@W-^ zL3CNiqn%H9?{ICPtD+OWf>B6*{;%Sq+n*R~+@HKmRXpPw<3fzJ{M2n0`0M55knBQv zqj@D#(wc5wF~$U)*)5Y!Hs{cDO=91mCH?W;hSDoqg%IQtVqnnP0^vUsq7Wo3VV{Zv z7#Are%G8Ja-kzgnMM)5{eVw)9NeL>}H&05ypeXd2h)3Lq89dnBrl2ricFg{{zz9fL zjk5Ypd?#^G5l9f9AjeT9j5B5TQx4RtNMcvYg?Yw@CIkYUbQ&^R6OA-_=rbqxPd+y^ z0J^MShXepj*t7U1j~EI$PmqKtEmKMOt<@Q7lXE_ei23W`YG^}TDNT8++ z1S^FAXom7Ak9qX~vL*@LYgqatH?#1Wl6AjAIT5LL^ z*!gsMh_uV*E}3I<%)_xaZ`#|PdC+L|bREGU>b852;0RJ#^fO|R1Tv`S$N14`bHMx7 zLilCNlC4MfI>hjizmXY!C8{ftCMg*qUYJ@UK6uE3HrF3ll%5eW=-=f6Um&o@KHM7` zk}6L5C@HVxI?H#7^DG5Z*~Uc|z3Kn*DRbOOf*5QgXkv@^>;+W)@GS7rxEX>7uAGBkEO$0^` znzr^B3tK?8m3b1!VOQPQIPwVZ*L$w> zAr0k|Pkqm}H&W1_@IYSP63Ig=3*W+>65X9mEgOFS`)Vx)gIj1<>d2d{0yZSUI0;&v z4;x@>Tfb#N=?6U_a)cy=0wm>wd;=5uU8Ellh8>Sq`U$(A5y*YKo0|Q^DXZ&c8+X54 z?=*bf>@dgTfqU`*3tPt$ly9_(?ICAvGb3DP&znvDI(~Ly5f@LXis4;WQ@@=KwEC6! zS`QWHKI=hDXam_xb4xxv#HaENecF*P+xjbfJysL?X8*}4y|$OJkfkBoE_FU_~ld0bp%qRHGs zbN$`;Kl#oP@-p|(N2C23=ZHv9jpyNoH<(m7M`i=!t5zwY8ISE!*(8p_7mJbOrq2gx zKr|j|5k>=`5--sRx^a8sx2!QeVBz$>g)Zrdy}8Xf@R$pO>xQrE2Q&TGDGy}9lC4L= zl75O!YjCxWr^h;^!mJS6&@$A{jo)p5CoVDa2^?evBu?vJ-y9e`9q!yX}WuFViY=XzyxpbMPF_#gJUC&-*F5^t0<3_ z{mnj~H3I#KyHElhaH7=#y%*2HrBTS|3H=^=)SQ~_sV+yq(?8C62*Jq~yjkoA{f>J% zjJ!W^-!bKfecsJuWG5HC0!Y4jSJw2D5wps-pS+Q)v43on0Y&sPkBlFqu63@~`lY$Z zg6&Jmq_yW)oSe?4`8OleK*E%!BsrY343Sm7TP9Q6`dfXGc_RMOjHvIQ+!!lhKb8rv z0>>^B-LO-te7=Kdj#htj>+2hN0>^B{o#zqL9V?2L&0p=XpZO+1T2Sc+!FS;SFVx@l zap<+>!XDqUWoVAYcy-~eID&#$?+ec<#J}RtQ6%t`q(i?K^u^Qu&5LVvYGQyIB9iw@ zzght%F9=+1=Zw`#4< zH;&ns6-?;C0BlDYV$Nnv;2=kI$@kRs3v%=P>x}CK78IioAw&yWDwDv~ZBaL2ly152 zX@NGLW1e~i8kXWYA?EXDt0m9G=hp$Bsux>8f>H_1fokLrwMgIw_a@0uPX4D7cS1XG zfe+M{WT2LZPQO~Y3>r^5g*?x}b?Ok{YFlWqWz;z33A=#`Dd88Ll$ZUlmyf0Yb@V_? z%yS=3sq(N0ps}75h%FZC5jEbxJ^BBMXWzjvv3wmmJCCpo`V+z-TlwyDIU^Ow;9O!g z+5Y{q_T@YGHrVPL34Pg~6j6I(QOVlr1qe9tJW;4qOmHCbfpUQwV^9_=v9}Y80up$Y6EF- z4OP#j?Wv!S@=&g6nY3)#W1-cG^*oNZ|0AQ+P@7fyTiRL*`fpysMW4Z5!x7d?OZCFp zbxL`ibsuuZo)%5h21`eY=%Iuyn5Y4$BCwrIPIue!^42C@&vj2Y?yGUM8=YItKsna( zM%VQwuZD$v5Fzb>Ol@=Bd*-}*Js z;JI&8oprC5_AXT5W_g3l+rvNP@HL`dLY^xiI1kvhHG>s*2r6_D+S|U>8@o= zZps|{w^r!p@RE5Xi#^+p!TBf({lt5v_(SM zhIVa~o~_tWq(;V5XL{9?T4-q5Az}QFtH@;a_iXFyjbovL?Wg@v2xJ!Hn)}+9)jC)? zYmY%wi*$l&ADShkJZ- zmlP6Tp&O0Y$Ga{5)YL)Qqz-DcJj>9Qg)Nk5S6TKay2m=(1|Hk0tJM)~uR%%E6xkpP zS`YUZ`7r zP_tIB5~*=rcn-hWa*ipjv1+YvaN$Xzkcj7hLKU{bIX($|bq>(#LO8 zX$Pt}90fxE_%;h|d|=SVrzaAP(U11m^gPV`jhyz3{|VCwhW%@2!U6uD3)4vjX&! z6IcS5a~vUaqkq}VT^-#pVb2xx~S>=;W>K%6p}0bt23bzv*F_RDSj2%yr8C3gPPUdR}L4g%tv z*5Om`=ckd`B*T8 z9Sr(3(lat(2OZau8nS~mJ&P+j^Gp3PGz0(iM`9KbmygeQzN;)K4Ygkv2b_VTjOIWW zeS*LahS-4;q!042vor2J)COYXGf_n@!1R<+jj{<1J9E6}sE21RfloD;u{tXwt0O8B z)cbdu|`Ew|s!)MA;JHd}k{jW7f)?$>WHGRt#F*wsm{o zD>}+nE}vTO=b6~q&l0SeLz8%SVgrL`mM5(?+Xa7Q;6TmR@eZ_uAkijv$R5zJ{xiR= zUWR+D*&|25eC;*x*&>b9j#|0(78q1rtIBU$k*k(G{c3Onwz=HBDssQa2jGhn&dXNn z@8};qDi*Lr3wdfe(gupMf|ttwJqGjRw({g_RmfmsoeS|u?J&M-pOsPrvvS3ocRhc? z9JLWJfo--jPhla#LgPebaIM%dW%6FhTzZbCv_Fd(&f%!j?>E$0bMX|^u49^CC}j2N z0jL3%k&HH}ZmsT+3XzDBp;f~)&3PBYuD$}5Ez}vm^7J_UQ(%&Aq$(eQdHE5~C5-Ve zpt-OY{{gCAgSM=G@F8eMO3?fF^;VwxYkKW(7}^3NYqX!%quSEejHp|()h{#XOnb3r zcmRck?bE&*gf7?CB)C}BrlGe#v6e`RD@t0+_O7{BdjvPQ*|G?enOw#j=K!l`Bs6mO z39q6|#tB{k>Q<0u+`csBr zjJ()t3!K%`a>lVbBC9pfrN`@ukm4)8IA6?-0Q7yIqf&1t$uQV&iHI4argb1-g?@yPiRw$g3jmtYhLw>lwS( zwvxHcK6qL1e4~q&Crsq%%5i%DqTo2^QeAl$=O2Bbkbbe>6{!ag@=yUfkEhz6-YAxh#W!3aJVN`Cu!%)ug zd*!L;L*Im{G&pN<=RD!)&iS+7yk;JtUk3`04GM zQpKg;aV=X|eGD6y?WC&5UxTY3;d{pEuOo#+dLEm{h4-GfzI~H)IEAh(Xb#(tPeumB zhxEO3j_+B)uhsczImdis)q7C){`)=o^}T;RWdl8vCTP1KLnkYGd7_o?GJ-Yf6{CV1Ynt%<~~(Y zV@3lLullkYVB~>oAFEc@-0%@+SgW@pUa}8{bG!y*F<$?Q3%}p>YoCp^J#Z z!jXMau{0up{)~RH$hWv#vpz#@U@8@=+N?!#{f%5u(VDv^MO0%gPpyJ;S&u&F=;^{e zA!Q#4Ii4n5G~~I~f4+f#>+xk1ONOgW1*WP1!ed~64eFopFvdd3ZFr@oanAdwfA*-Hc>0rjmGyb@>`fkBP9hsM<)^9%isnFIB=<`W&UD*kZU8a%v~)Z`}nVwNYe0W#D+ z_$K(npH?W1QlnOjhF|oXoRgYRB3(^o&O;k`orlQYC;vn$UMW@4R*vFRz4eONKSz%J z?A&@uv1n;!NodI9Nb&h;TAGQ$gwC0nR3Az+aqoUHb9%+r2i)NNgdZlpr@?PgvRA&9 zh=&Y#XGv!6-DLnBPm};xQx(TkajXej8g!}gE|vQIlUp8+R*3a)_8qaXo%>WEunbvj zXPoHsv3JOJou8b0I>!HYJ(hkjd-nfZdV}uNY|8HP)Pf*wl9&BS6#Il$N`RB3I}33Z z^8?SE7HfDrX{NpvWt88h*h=qpvUnV+Xsce+L6ENHVFLyw$(m*RCAG0RP;2^nRXtBv~!7{9n)4Ln}o_#$?RV#wt!hE-%^{X!FWqHE;gfq4w#}nh(Q&*<) zRHNAUurxBY0roouXqH90a67HTZ_xm2GJH7ByOFxDsf1B~D~8yIb)zNpJkg?N!KlWe zDYU2$so{GTEjo5>r!F4G_fV>Aj`1)pwzB_sbVNpQl^jNV!52DqN@L4Kx}~PiLWi=Y z{aJsX=7b+v_enaTitW#%ERuwVpF^FJD;jTkU}F9%?06-7;wNyLRseDBdggkyn|JNJ z-s%mMJ0#m?$XK04rqOAjfx+Cie7l~%<4lz>g$3Uto-B=T|NEWa<%yrSHam2Hv+dse z0Dft353Gf;u>7WC^1=tqt8W--XczV;c=xuxPxI?Xi)G3LSi+MsK2v1G`Xd)&Un~s@ zv(7m?huyC?e*pb;TgB-o-$f?mBW*o8fwy}$BX&Uv_@#)wx16?O)R@ylU*7If05f>A zWk1KBZJMDic1O#SUj&K1A{ovkX85fXeMje!;})%l?2_d^#f+*pDLA1B8DI%efJL$o zWudZdE0*&9U1G#dURS&2lcY*&-%tV`=6L*wnmdKiIoj_v_c&EywBt0u%#q@LXZ*)n z*w$a=#lNWW;u3HA9mcq7cw!5Mu2_GLfSWaY)Jnkon+X#+9sXHzxvc9MKHB;Y58sKq z_$ZwJY0gjkVZSu^u?62NcygxC0KIXDD;Parc+dVWuwsv+rNi#5B@$KjQTmDKMY-zC zd!2%n9~Q3E76w3=LJayiQ=AJzNdbVqp>RBU_)T5@v_puR3GcK|B-tT{_xi4uz8Pv{gtR?oD$R~B+Nl&%X=6p%0{zhMtQRR^M#KS(B zg&b?<>nboaLQfTVUFsjdE0i-2ulKctlVIbx<9=Fo+#j1nnyA>GDO6pR%zNE=MW{rG zV0hY2OGPi%`j{2D&JhfKE>w_vjMTm#*l#aB-|0ExHav!oxz9OZuo$(Iy*!u7DV7(S zkOh)(T^e4s-5y`<7>86=Z3m`MknPtvmwk9NgFRCOuJ{AgNZH&&U!e|bN4AT?WeeLM zuQ)krfrii#$jX8H14-}E3dMze@GK9S;Y|Il_;UIFZUulVtC;#T=c(fpUV?jKPqZdx zf-B?RRzcp|qu-0~<$Kq-UfLxeeGi&aVM9qmAF!l|Ex47d`y6}as`OX99ymwU2Yk4l z3vi$DwBLh)HIm0`Tq5s*Z?!wYk`-+1XGvUr!m7{3^X_<=)v$ED`YJ9RBiB*Y6&_L& zC|U1I&cQfhd@nbnbO`hc#NMKZtc2bu-|YyyC9wJ%G*;dWYPQ*jS7RSi)rzBCxFw=9 z-w2ek?`!^%?Tp)e&Iz5`(AJU}m|2ctCi)eJ5iav1D{pk(|3Pa8Y7;jQS^7y53DR#@ z;;1-4j#_n7>||6Dmr%Y+KnNz9aH}>FRA8g8;7G9{M*ceQh5i+Jo^nJaCW!EoElA#Z zQeL*WBt!m2uH70BbeLlCPW0llqR1y+jcNIAQX(7cTfffY0ywX+3fXgT0qu%JG4a{C zjqjv_>%cCyt45o-`@$C-a|;bbL*PVVDoaaXpV3DoBo6kjx0FsitFoj`4a_X;D_uV2 ziR#2!0Ls}T_Ij>#u-=ig>YZIz2G-mL7E#RdF|gqREdZD8*21)>&ao9+feMwZ_kCa? z2i9N=9=3dsX22s(j-3&|-cj`)=a>nw-#YdQD^z@sSz3OCea{JAeKI?~?>Q54>ZhDp zjnE6jj0>d3vf@?g_?X*r)!-8f-GR+mCqN4t z>3Q*2?%Hvb`vZ@CmWGGTMVC-_!sU$Izl|9ag^UP^ctK+L=aX9Oo(o{IA9>hyc;%WfDZNuZv*=2S4Z1BWP@VvD;GZT$VdqEUbH(Jnjksb%aLsLCV{JfC-(k(ZIga)W_hpNm-F*SEMWokcL81)gv&2H~ z0!O74$JlmTzo>b?8zYE&0c+n+j9PQbu!U#bNe)fHz8B1O@AtSh&Yyw1;w)4nN=1$Z zG)@hC@%#brbt@u5$4C_|9BjYI`vLEhv_H%G4KEnq@UED3$$DuDR<%j|pC~)y+ox;I zh*U*uUdFd@5k409bFNjuqja_cq za4G_ci?F~m(94O7r{TjlyV6#O5sCf3tE(@6H%rCq5QXYZD17%n6uQ)eqFm{vFgAY`J%3L$qa5q!P>(47E8T6RF$vF_bf8h2+&%R!x4H9##DBu~Ur zl%SLg##uuFD&PQRBC^>Q)uup$I*NcD42rfp+NE%zi|nhS`GfLG0Lk}`yOU!$G;Vx@HW()m$vQH=uc4zX}!dFl)o$+TtcGH z^{-muqVGeS9AE}wTg1d1DU^G^(~B$LY?#|8%5osOMCUV_tzbdGums}DP4^x@XU6Utjow}! z@y+dP+n1Qc5t2D_P$cI=MTF{kL=fgL8m<^PMqloTgB)8hh{^6e=8Q};ABU?P@!`BI zr|q;@Y|HzUF8**S z%PzL9)ziWwD!0f&c9ZBC<3O`P$Ixo(m!rkr@=VY2+6H-vWeQwd!+~&A>2{!9J7eJC zfIs9O%7X%tvxauI1{U^ELvuryzE)&c)Fqc4a~^u0f`%S9YDb#mpV{q7Pf z!GU9A3<2Arst>pNJ--7j$PDV>m7SvH;y=#M+_D@!37G@)_~TLL!KOGz#9p}! zgXlEzAWOgmaaQ(3CTn)9TQ<=#*|iXy@uc|17j9mgs~j^v2&f<>$fR|`c|16gH>2sUbqYtJnlG2yB)id$%#Oow@M zLucjqYlKe~KpyS7Dc`Iz!@22K4WF2&zy5o1>bj2OA~n_G9;UX&UOw=7FgY29Y?jZg zg20+B&y~g!8s{yx-{O)YZp-?4IJIv_RuV*(aNDoG`7K8jgN0*wR0+mFbCToB-Cn>O z>TkD^7B5u2SBp3F*B0FYS`Y|A(AW!mSY7}FmSoOV{7t&Yn@Jn`JI$e&Zw#mSZT6D$Mk$Hr(2!o!TSa)iQhdgNK^eBf%Mq z<=d5=sYNGFkMfD>J9idL%{15oIejN5^JKx5G=DHR?bZC^i5#-9MKMW`Y#ri{Qk&t) z{J9^I;9pA*Pw^X6d;N}rcasYA6Q^|LyBQ-^uN2J9162$E zEc#}jk-v6B=7^ubd>wADR5JiRJ3ssN3s)Ec?q$Qj1g?H212n)4^t;F?dRII-+R)YC z-Tky791_j>WN6I?$!6a(d$W3^V50(v@gG54qZY2$laIdtU)>S8=_2 zw%ftoZ*Wb5ySoN=3nV0g2tBd+*(S&)&0TmajEyQam~E zm3TlU2J}vp0tlruZX20k>{H3sBLXmI7C^2_TjdPtpreMK0Kj*N4Wd7da0(O0-f&5@ z8VDdGK-C$RfsJpjSwChU%H%;lT$Jf=O{xi3p@Y`ONRf&2{yHjW9|F*Aa5xT=fLuyf z-5Wre#^4#gzzwoC@!rlqN-y5iqtl3%@RCs^KE}W7xIf<2;YBSF11{CPNCuH>7R#sr z%`hIY6XMCz&}877-?Jk;g{x-6^_{T8CKU37xGd{n@kY#g##!O8*=2r7nX?nbcnXRE zQU0vl#WyjAW&2pln}f!54m?8ay6%**81NC3=|bQcPr=PZkiK5cx+|8J8vKHU~2LA{X)pP(oWBiU6$Y;Ms2Q$c82TWs+J}d{>L1lb~I8}TqGQiY; zB=q_@mWe}wN3CK3zfLNk6tMu1J~pi~f+!?qRbXM7nE$6 zx5bw|g5h>+j~Qbp2ez6lpi}uYVVW)DoY4jN5@{fU zH%0{TAYv1%`QT6i(ae7Tw9XLtB;y>VB|ONpu0yL>*$XP_uzt@&=1fmfm%cy#4$78*tA)g4fvZfMB}i!xo>BN&VhJLGs_=qtJm*8(LhUUAwr+xi!1$25c!y zI6^kivLB6=MKA};qitHy7Ov4BAQLXQP_RG&kd%&^k2;S_h?{G{#4hT7viU6m1&5=* z(GFA&a6%HT$2VOm0((gdzqnYn;GNSqp>5KXOCj6uiN~=)JdLhcm%eFtZh%<%pxNo*8{zvZfTo!~ z@(1ji@dT~+e}=>71UQ)NB0X#RjOp80Nj3bDOi(2N1{M&G)E5zR;VZmDGvzzC5tBt1 zm>1ys7V5z$8c=mpg+m-_;vD*8ZpZJKz-y@t9)$tpPu8xibQGq4WGewhu)ew|@L?S+ zuJZf6;6k!p=cf_+jy^1wUAw6eAsH+cP*^5NJNk?dHCAp}?pdF5Wt8K{AZUcqp;;1- zta&xI-E}Sw-1&V=Z8G@XQ&0?;hOTC;w5tgMtDPpd8jW3#7BE0BGij9EN)z5OGPF1h zz2+C$JK^J;1AEju>q6sP9SBsz~|+}z_`EI@h3=ROc_#wusm9&UR60V>i*E{TA;PQaY9dw8I%HP5tnP`oqmQ=TL?JB z27VL^wCyKClqDh{x?ha+_{Df3%K5eJ-vkz(!i86`J%@dPGI|Ok2~%=L3zLVsz70pL z>#W2;V`2*g_)q~b9`#j}(Ch&Td=@DRu4qGRl`kawc7`|o>kP1_&gwuuE;TS88w5p` z02{4ScYKB~ZylbC2%1}?K4Xw_p@Tc5+R^S{>3R;+-!>%s(C8f8Ln=rC8Iqe2JLiEr zybh8VF*wNnkS}CGdKn+cvSDFG6hB z_XJv5tH>QrnkBGm9M|^+C=PfkRl(Tp%INGfD$!*54giU|rp;{7;eNA4NdU8MgA%|N z_bOO4X!M0{d#Db{FTO+yqMANf+k+sg?ZK!Kn86g%n9d;*;-TLl>@rMI07kxbTjE+y zZ)}Rv1OkgyIieNA1ZnGK8!y z?H8A`c8t#=9>6^5S0*p^V~S+P7(rF8*zhohU_}6EON!|C-E?%$t4&EUaH585 zY1yzKLNH^&U7H6A?O?>@{B}I|8_~P@{XfH(@n6ZPH6JAwx2IsZrwL=vacxQ;FVUONgM!0ywJk^we8PoUaa3tP6P12(oP-&Cn>&YAZk) zCaOjYFVo<~xXn}m?1ENk%EHnffFDu%8M1|P5KqrSFaadW8rPzFD&aS z8$fY%A*?X&YI6%Z_yQm_#E8Lo|1#~hL7TB}Kbkqp2m^GXJwRqw=UBEKt9_VbA8;$( zlsGjMFbm_t02@T8?u+|qL}3hYZ?5rnb|7z95LA zr?i}`;P$)=b0Obc{HvTn5g=&zo8=r;9aE{e4!%-J6UKP zHL=t^-_>{6PN4l^o`{JV!(UU!W^Bdf55}*RO<19`)lMq~BVdK26f8l0j;;+ycHKQ2}YCLbv)`oW~x zbii5wm|2i*Jes`dETe)|9>5b-N5>>Dpq5;q*`%7_l0eIkZ^vD1i`5bGU|SqrG+x=8 zZe43MNBJqKzK26&qLsu+9cJn)?^@uG|0a77~_BL;8?qRvB`ZHOHUSDJ9-)-Bu6 zj8wByMo(}d(WR-AsW3Y$kW?oC8hF5=3ouRg!LHK_z{7FbrNkmAYR0&c7h}5cPS1yb z;=@J`ngWQ%+^*ll1C0#UOcycYQ;S+E%HO2&b`6G<1KKZIEQ|?#B*uL+5#wZ3kAoU% z`%hg0>B@ahB5Ig`- zsY=7A4-=!}joEI(bpb}Th5CSlc#Vv!m#=f>kVFzonJ+b5a>M^LdAb+W0~tbkYPd*r z-AWf@yLQ|SdYPvC$^w;#Tm7V(XY)utu&1hM+&I^g6x5c?@1^yt9fBqFeh^BjHIj{S z9!SNs;NQ`P)FB3>D!DkiD2T9t4HAODl{mF8DiI9ue&=LVZ$EdD-N)F^UrUUTwWv+K%d$0I(4!W7QkP!qdJdJFCL>s?wIZKz=#on{_p+P-u345t+>i!qw zTf%=mwhT{_w-I~1tBfHM-M~7?hh`uM1~Qbnp#)KBilO-;u0iOy%a|>8U%yjPbn1^y zI>TegRRh)71dW+|+-OWGRuZvZ9oZd8!}t|U7kNGm14pw+PU4XV#~XW4I5;+8(F&Lz z`0&7#fhqE=VY=^v)xrbBKt6zg4H+*(5d4}Eg2oIw88hV2gy~{k4Hq!6^@a--;nN#7 z@IxJPQ1~V)Ll4aj&oPXGS~R^35NtnS5QcXH$Nb)q`K7MjZ~)c8f+cHg>kZH58l8M8-xPFNK+bKh-+gG|Vv=yD_C}2!&6J7Q#!OIS_8+0)Z!o=NK8L|BVw_M{E>ZG z`CCjr2EW`XCs+$Pf<}QwF;L66OD&mExTepTjN6Ca&vJ-s$^pm8Xy=fY^BG@uU47E@ zoBDzeT-|YW`eqYPSO1jEyqgdBenk#?`zfVoebtYB^|#&}2aqn2S z$A|+cJARy=eaV7$Ct0)=7<22T=U(53Ror?6O^}Gr8FUH`Ri_L zbKy7B6{8-38C&N$C;&#ca)vZHiX3IsAnt2PkGzI6(nsHG{MOMVVlpuxt%trbj!TzD z9Og&>qcM7`rmtiAs^0G?0k#=U_qhb8`oVS3rkp~T`0#gCs9AD_nMDQ|^s>-fR&IqS zX|Ft$Z+P&_NEV-k_+essX{;u2`?+~9|7v#@3#QqsIsWloyepNDmx9=ZD-km-_NxJ=ZM}H%)5xM;+jyBH8ox^1u^VUAEeiM%CbU+DPIWrC{ z{T;83o6hSexK^&&Y;-0p2;&YY7-KSrrX8el1F{BUK|7gk2}VBAqyhl<+DdEorbC1t zUk-N7Q)esdOHn5z$qFzvCFx=`DRC5a#rYlWuG3eOAJ^3ZM-6c~erk*G`DyaPd=q^Y z@Z+mT)uDHM{Kg)9Jd+qRyyxh8XlJ;A6d_c`1*;#;CKbU7VzR`qFpPXCNP% zB4ovRVlPLm^Fx)49w9Z9Azfs_&>!Cq2WaT4tAT4i1>J)m-etf0$HWId6(6!XSc_cn z{nT)$eJP(apF8Heumy64wPPqLUzDI?f)wpkrCHbD$r2Xl{o02h*uLUA#mtMx6 znTsT3oe|~7$Lk`B!z(QNc}4~g??cX(e|ngUDuAW_h4!#}WN{d~#%cU+vzgNmJSl6r zFG;JJ`4k|^;M`Hsj4(NZ|wlD8wSk)3Se-=z6l&gqEEQHCc$smH*%|a zS_8VRs#0X`7}Ny765OU^$&?z&!CkIrgf>9}Hk3#XOYRQtGF5QTF*Y{F`}<8!TLEEc zW)G>O%++9u6_gNMv z00or-4Yv9_5r*$N8Y+dgbUk~~s;o}CQCpx`Rn$O5{(HqjH&#&lgyPhrj~gubcyW#p z#)wZmh_Yl3D^6)qjri+u*6Yr?;nHhufrr)uF!<$b*s8AXz9Vv! z+{((4t5IwHDT)4(rXWI+zxw+R^_nE1%(p!dN`*p*lRI`r2{;+ZbzmXnppuUXLa>D&a^#J54DdiqGU|pmE@NotMK~u~*Z+sU5b`O)FCE`em`8>S>d?}@Tj`Y%A zA!O~#dEd>Zt=4iAs#I%m3>Xqf!^h1ZJ2XHCRG1hS@Lu~Pxw+3L$|_Gk-_ok!P5xXv z#2NHnFajf0z)}qnDzr2DObb|;$vn> z%uJzBCT-X^OU}9g2VympnLdg2JM-RcU~6u;0zbuQBxXyPc<^l{8bb+!j zawNn(zR{u|RtiBfKHV&1?_Wji_O%2qxIt&cM?PplFN!GS)`yjS~Zx8^Lzwz78eQbYr})B1S_aQR(yF$qT-?6sF2 zU?c>b00!E!W(r`NI@kV64&MB(g)cAir+dKTrV_&)y~d-^=W;f_<5oOLc(v+p%D~5^ z(qyEgTz%NYNu}$aUPJo8wf}s_I6{|b0a}2yS#L_w2<5M{Z>n9fd67MO(55|Zyx$t# z5ylzz`!FhHZK3HW949evcv4y5Xv-ZwV+;iBL7nsviktxsCIPX~jCn`*2ypZ*7hsv= z__k@jF{SvG9`FLIghXN60HZVKNF8BaV&wwIj0oz%Jl}%*@>p zIK2D;HtyELsU8IqD5TneUB?AfJ!pYIxr{S5^IEg|*-`>)AX&J8^pN3z?hR0680SnL zxqKT!;QJs_^!(MMEP}vf6(=hoZ z*Cx$009;c9-DK|i{(_+44m7w2 z%bOt(Dj{WTAQ&HRoz@6}F^C`}QQE!tP`0~D?Qo3V_XEaARlt2a3Woj2t2njyQ zM!*?V(_hUxdXNo>x}glm$LKT&*1VZO;Qn9-*MYX9{fC{~@oT`r$G2}tS>eD{YzQ>@ zTrXSU+fED&2a7NX*X09@Enu6r(LHp5W?=UPW#|_fgBH(ERmPAmkO4F`ggyP8Qug<- z9n((N{;6<8MNkDO=FEWU@BQEWtn`QQ1qSJI%XlR9)z4T1y_S|mhrksRFnwERyo9GR z20;^$HLB3bX-EWz{eK*9l_D1s9caNPf3F|5TdX6A>_(?%FR>)1n} zX3wZd=*&FeJPH*<%?_du%e+0;gHy|hs7Vw^1yBVC0(EYK{F`|cZv|}^h}~rXlLp7# zr=0|q|MY=8;p4AHo*MrI}< zCAX^QyR;9|>l?(t=wsC~Vc|V^DmDmBaT6D~*N=J+WPq}JIadAR9EsmGJ&dg4yS~qZ zYsHVUZtzK!G`}~j_~}Z(33cKOc_6ZR=m%=2{8^S(#K}Y@f(WvR%Ts{A^P_a=kvbsn zkO>+S;KmBF-jv}Ar9w@CQfduzgnd%#)5<<1qK(q3_6}09;izR^;U38?6$cUGGbutz zep8${!<(r3Ce+s}ywj{K=+rC7=|&)|i*c8v^(93}O*J~%I;PyvPwv-RNJq{+S%IK# zL^-J;yZe4JV`DdIiS^bkX3FMkE!Krb6|qZ7`~M^k`@YCTh6GL1ce)dZxZ(U5lhl(W%FX9<9>95r(BU?vsyk)QV>O4Hl!Lg4 zGMQfN;!R{nY@D7uWHl)|880-pd>5sRIGQ=K_c@s{N5((u7>x+Y3}*qXg%DPcXt-9y}`E{odwt?uQ$4T0! z(|s37f+o2jnTK zIBFT!bz>A&f+6I|kEJSL9=}f;#9yJ+cCsc$iNZ1Ha+T6$_D}yI>Pg#yvzbX|9i8!> zD5~Szk$yVY!LaUc2p$)~mQs*#PP7Ml;(e zwE~Ei6u=ZGpKjEmNq=`+C4EXDHDh`vzeLM}+*$h(=!4sF`No_DP#|JyK0-U-Uh10M z-K#4zK2Bv60G`#D*9k=%UwK6S`}&P-Fp5?Zd-*f{5%(5H|*r!8}O?1$b)?3iYSb+9$`UyzP z80cUb$3k*>Ug($S{oE1Afs8stU6fUqDA((xOg$x?5^}9O3Fe;k@g91Xz(*N5 znIpOy8&TPqA;_jql}}IsIN%dy;YTh2zO}QOGNp<-Hc!?%)@PeKCSB%9AqY~FCYpJz z*q`NA^+x2_a{-5|?C!8XZRxYHdeO&<)@O_^;jYRfTM?W=`87R0HNfYDMNzQxO8mX* ziVl1(P#3j2YYHt^1|4Hee3UIYO%i1tXLEI$w&jc$x&q0tO|=grYb#~gu5Jz&$WtP5 z)D8A;aY}t3Ct$FSj~iEb)s5$2rp?H<%iE}w5);{biG7HL+C7y@eeYz-9y#fm5&)Okl3W?(0 zJ?)^lI?$}~WupgZ8P9(=JlfKvXz=42U{W598sapNL8ouSv*<-OA{l?EWo2z!L|%ts z=#EZ-*rAwAE8%Rd>-Y1T(xJ9NBb#BIi!@CB?BO>fjuyXIfVw8|>clc<0u?$X-`or9 z#-{K^6BDz{Z_p@LIBIJ~d!poA=z``HoAN9$tzFe~b{e5UBtY;{bkHyeq$K(E_-=Dk zuVL7Xn`mm_hRxd6{xq*(tao#H^<2MFBcv!-AOSrE(vDSwwLJWo$pv0$X%#cv5h?}% z05d#z^<4{K>;&?JRS|@KF##eV82UdJJ~2KSz1Wp^sEY=lJ#@7aj9RL%V2n-Zp1kM9 z;3tu17+itu@xca7`zeNMo$8~vG(84)P4UzqMvHv~j>b*kC$Q`K zHy`ack_3x}_I`p?Hy64zp+Z{ZY*}3i@YQvQ*Mm;+?4?Y~BT zG62%qM)LeefobI8$=|JP7P)i^9(3yWV8AZpNiSh{%GznPQ`Sv6PfEsEM*uN+@PwQ;y@VTs z!<#e1c$E4`r$mWJ8(yY3v|NP4-st#(dzCEbUlHPrkGIjc)On&_5(cWKl&K=1(V#}T zQ|dnoOq*Yd9U!iGF%3A?HMQjwzP|7CHrEn4^lE3ozhWWh5PKu5t#hvbxW3SfD4_YX zZ4K0nffhl;_O~bFIpGKJ>Nu!vmolRkqL86rM9}}L-js^Z{+1DtH8|BuU zCPEV2w;=r$7ymnO8vinJ2D6mizCYyir{bsRdDf`_*Gh}oYK#~E`*Gl#wyw5AKl(Q; z99jdp&c@-w2+5jY(2=!uMMkc!hq#G8kM-Ff2ZxD^re9`yzJAXh_OJAVN?1N9M~UMu z>=SFYBA4aw2ZGhKN$~F4zFrbmD!WM;*P3p?KAw{_gQ}4JzXPZ71JgIMy|``~Q2v@) zU|+GDCM-ukCTyc?`F8C}%!>e?Pce!Zmz z`|U0{20V>*{~tE8uv#X@m%aW?$G9Gh6%CB$<{X7_F}c~pLHs}z(HPF$HafHH`##+%>_H}z~y&(U^p0&q50G- zXxmt~D~$}_a3P#;9Ovxk;LU+=ioTO$-2@SxOND>aMM zxyg$#E@IkzZ4m%kHwlhJ`?OoiqMLRhmq}~b@cloDtJ{2maK`?AN9bf%LOT<>q>yUuzDK)&7d)ES zS;sFO+4(W>82`YE20Lz2_of3{D_DY)!|>GMR8`J2jT%H%Cgut(rMS^j2}Fg)j);Tj z4@9{}1>htPYqV93=z!>^JsgdqsBYg+^WW4pV*@GWV!5E7;(dzp_g(iz7NA?&>{=FL zBXtZw2@*z}cWpMJ8k>LD+i`{h`TiI%F+MAFVwR-QenDGM7%7D>k`~B;?FNS}HDO_k zqVo_UBV!LgCTyESQ^NIprlZ(d2O}zM1dTa9wuN;ztMeNF8649mvi8zzd=q$St963% zeV?~<+8huW%_Qm67o2`Z1o%L2&^sr&BfaWK3|(z?LW{e=oW+?dsG`95RB`8;8LKoG@+n zq@W;xRk!XN9o=5%ri`8pU)n=3-){{BxNcg;Q9fW0L65_KRD6?FH9{H-C_{?YooUHi zu!ddas`%{+Dj&PWT(hk$1Z)q|?PQ(=uz>~YPUM~lqzwZLTq~ii6X!~3+xSumQWCN1 z@AIt7k^IPD;uScvakF2{e%qwWecyS z1>gxb+QbeyfL=CMP`~N_7+Lr<%-6B5v6G6xXrnJmfQw&iB)&;egH<&8sw~ijzaPdI z80)%Q1o#Iwwhh4fL;FJC*qudmQek4NeTON>mwMZS6+9H6N>DX0aO&I&-Y*4J^Gj}U zKQ1%in>ujL=`o1F3koJ@s{6y{m%w3i*c?8dL(MQaG!1~lkr*7AEusW>hvV}oat@J9 z+3_v=&G>F`VWu==MpYORcp;CxX7UDsXV;eF9c8AcL+aS*Xi$I?AZG$4tOuoAJ||-h zE@a20dUPN_qjB>(9gi74C|4IFrPkf-T!kG&oToY7p;UJswb?|7C-uY)DXYeL~WbJFWJi`909k6AZUtIaL86|1eS=W zs9R74=Vai<5%(Y|_XO{|I+_v)3nnMG`s-uXOsT~;6A2$U(3`-UXdzcXW*DGMi5u5Z z!&pjyw%&~UN=rq)VGCFpk2twk$lJ?hLSYAC0`t@1$QgzNHW`pD)zGXhI{ z1PTX7N`q3PcDq7KfXn(BTX!oZzzBNjk#Io|bcQ1vR${|Bq@ub)Nqe)DR4c%frf<{X zg|J!36>`R&B}lgwKpJ>UBBHO-#3v{v>ISgUOO%{TJP6N0i=?S#WN%=|J@AGk1NG$3 z#Pm9rX}&0OppAg4e~^F#zt1N@4GU#Wa^OTx{lV>BTzx^eDg2++aM0#Kf*nd!aaS-SZf2m z!s>RU2mUZvr0c`7izHAd2C$?4+ba2AyCXG6@U$jURH!M}*L%jMOkV*Tj z(u`kZ(D4~cKwCOhjj*J;iLgc))OAt_spyO_pl}g7v_;)Ps=m@|2{;+|5nGfq=uUsJ z1cpRf=|P}W{+jKNw0g)i`=S;UlcGA)fRS4%+)1wQTf?)r=$S}$PzI*P~_ z;NV0sM1Iup)Xb@rzV^_OE6 zLK<#U$v0yOsfIWd8RLkETK;;)=(;}!qVd7zmw84jM{CUzj<6Grf{bJ;Zdd|8pavNL z+Bz(|wj+#=zfApD@sa}WH6tTd`Wi9{a1O}KE{GLxHU`cP-xFU}ZgA8M5du*XU*H8K zew+n2f4iNzg2s1+(e21M2#O#X^<}3G@&I3>WO(ezlQC0ks0N0hrkUgM;F?Qu0f9OO zRE&EvD%lLj#WQKg{QL&*lmYL^wHP^IAs&zQ2r=;M)A}s)u-cX+3U`wOt~puo=YSX? zXfaL>pcdLR@Ac`EX2c6<2k&Zz<~}qWXij4C{5mJG^D7NlK8=tY;Zb^gX@|=Rjh^M* ziN4JI`YeNHY~+%Vn9D&Aba!eF-XXcHyZB_4dU7{AumK}R3Csl!h1R^5Xl$OgDX zQ%(lo6As#R3>{(&PE|+PrlOMpS15% zGN2DNoi90Up){#G)H6*^W8csN>64Wr8E@pDhb@3cpa@!0wS1psq#&!5{JMs&uUgRb52XFVM0kJ(!eZ{j>Z{MqK0=2n7xIuy9S4!A!fqn?UjGuJzA9xL<#jy?AhDy@O z53S#K(E>eC9`I}C64V2DwEp0Hu_I@q2AF?(R^{tstW=WCr=$ z*lev0TEg>`G-OVG*?QvlZ(zexm-VD4pnigyZqsarvSM4r97H1m?=bg(ybT?a=Q$W0 zccsRmGZXclZApvW#0|cvLryqSB1S4T&7zY=F3CC^5wsF)+R|j@7QQ`Nix!q@8~Bdg zLUoTfsDOSWlkcq077N~_^N~mi(dKE!4DWUEN8^TS zb#mGsQi--wJs4c~iX9DG0G+fcP!CPX?=;xlLU`=rr=9H_eOBAKjyNS6a(0Ij@i}iK zPQ^?67rZ}MyG~5lLy~%=RP_vgXKhOOZOjx3#neyJ?;u}r+>9kccONT--91OkjI+r1xxY6l3AZ`&3F1M3Gp?*Rzp z0L;r3FF5526ye13A@bwedDI4eVh06|6<`Ezc1r*brhuz(KkTi48CrVf7Vv%A5~NDY z?7+{qzY{;DKrCuzI3opWp11;_zC27nUPq>bp-PAPu z;P41%x0;s<8sBa!`}2aI197T5UeOp}e6d5`(5TuRebBU8P2Att<{)N(1$x>X9Jw1z<2xLXSBIer+5aB~< zz?4aLXM;}Zz=k+^3R8u=y5q6G=mWYHfgW*9(Ydyprb?NqBAl#%h*w>_?j<{2b>C@4 zKn$Va!-X2kB@h5CMG5=igoQ~Dy6BreZ1oJVJ||q+LQuMgu(XeD`fq-Wfn}nvs`M(I zc=ZFip0q~#te!)qJ`D2UPDlmG}NMssqM)W#)r zNPYOSUAtb-9HYkpqEU%GLV4DV-}SOZZ|=gUA7yT!oSvjAV_@sstJG$!clolKLPyFS zd{?5(T%N^uKGQg07+PXDAa|nC&iGl&(HaS>sX0)N+#?wFRo`$A+VEir*ST(36slqk zk_P#1Hqp=4Rt_~-!b+h;ryaZ<8m*bJ&rW{$Jti_kJ0(X>0X|Pw=t56lF}Pu;pbc^d zHOe3LOvj`)=(rrs=&*h9NA2$_pWvGkLp%hr5s5-Q{7HgW_j5Z|Tz6pZq{Q@yVWk=~Q3esZ*Z?wAZ|G)#op+_O{23or zO6rj`&>|bSr*OfwA_fw2#nyoVuP@np>yDH|z(yGxH3rbM1X_m1lTMo5Io(OBDG!lm zTnd@+*^aUWh2%B+qQ${l0JF}y{P>|cZmkK02cYUJ54T+`BtY-n%|RT`y*(H8Nxyp+OjT#=rkYT z2h(gPo!w~Pr!(6k84OoGEDTmd6&ObDY}YuB3#zC<)YA>(OW_)4&_z2Ft|}i{S8O9+scLVBRGYR z`u8I69s+%*y};?V0Z-?K*@6$`L+A(@==XqE&7ffHpny++bI1`UsbxF?vtdUB(E@Pr z4EtO*FA-`Qi>s+UnpydoUiicsz^c|`T*VqUd zHF!9MF1A~gG{ZxVoQBML*^5^P^@A`F6&uN{_80&J4qpk1jzs|W)*QXfjpeMl*D?Fj2 z3CGj1!XZ7hX0>a`pYTAb!hIp#I`|3!kqmou3TJZHf8Sqfgajx*#UK_1QT&wK|_B~J2hB|3Ak`QTtU5$0Hap$y3 zX{Wv8I)wy^_>>+y?6^xi;_O$n<;$SM=G~eTXq%dbw-_z^I_`7T9kih~NFEfD53Q@+ znDK8&Oh1$sLl`)wCU68S?xA&DL6DO_iE0gfm^My*4REpH*zD1$E&z#mv}6r&l5iC! zj>8HNL@2m)aq!jJ7PXZ8<-}u<*sq~zSf~%Z~#vMwQ9xcr__*bYr4oikh=I_ z(<91Fdtp@3Pz!nZQsEgWr7SRrCV8Ah51pl;hhc35D1p4}Gs3?U83Bu{ zVOGgH*c~;L8~6%7*!;CW29L?ud^`>pHN#r2w&KKESD6Fy@)+Xb9&NGw8QM(0nf(IG zIq@M{5H9w_2Aa9H1;-GBk9^2RRXHJJ|rY(Sy)!`I9>8 z>y(V*Q$9hl^6F#~SWd~0A5{dQ($h6tf+XO7>K~+!{chkk6gn6kb5o#^7O7mZKJ3e` zsdjad8JO6PTA-Q$iI)zAnYqQZ#fwImGaz%!NJ@x0#z$xP#<231D#N>bu%d}~ObCPR4X^Y4bF!BLu zpsZK{i@1&T2!G&?^Zbwk)<1tNSLo}epC!*=g`Mfs8foDjYLJrr$r?83K>NFZfRB!~ zpwEbw!?f>ieX(Skz|^_vIz8wTbmK#-*EYyX!uI_wO_Tf<2GUQHS^y$; zBD)a;sepK7F?~v?sWafmT%L?pbfdsgL0PM1VpI?QhjUT|Kg1R4h;anx$T1-h`ICNC zJx6N#D}e4WXoOLrB2J*D&LJpnU-yFWty@~Rw(e~`*m|+`YU}OR`>l^!pS8XY zf*=WsV47ftV3uI6V1Zyjuw<}ouwt-gux_wnFeum}*e=*5*gY5)>=o=2Ob8|h2M0$7 z#|I||X9nj57Y3IGzYMMoejEHDxHb4kaCdNj@Nn>W@O1Ee@M`dO@P6=7@LBL>ScOxC zGljE-bA>C0tA=Za8-`nj!@|A7G2ysyVt8zLPIz8;Y52?Vs_^>o#qibe-SDIE^C*d? zi zN}iB9@+^-ic`!hq) z>bi=;l6H_ln5n|%q3{OB2Qq81a2tjp*{ab%DjV` zg?s{eCX&dQne%VW(?RPwIYR_ZK`J{cuLNXL~5)KvdF&L_pjP!U-6l-qDnZT2O;aS|wToZu-RO_(N+`tJdDF``w z!q7UXWiEep7~z;3*rE1k28@((DjTjtCpZdj{C_JU$(+g#&jlEL&WqHe1Fo!Lttto=FZ8>W#5?p1=d27ZHK>>dEl<~4XzJ?_K4KX|ZY?JtmV?%;l2x;e0f)nFIg;z= z+H%}lxOJN?C#@NS#Fo?6tieLIoVB(Nwy@>AwP^6TEf=j>!`W@Q%&rXAvE{0D*c5lz za=UfNlqcHq6s@hN+R&D#Z0$JJ{kA+^YxAjhl%Bwt*50j&t+A~UtzoUjdJw|xnq&5!ICjLa;p2O@7`p7x(c>pBW{y2e3|w69t|I`J z6;K-s*wF%NcR?_?wTA#~t+VX1JsXc0J#_c6gZGfr&E@W})`Zqbxj$A;hYp)CaSQhJpqs2Uq z)-h1Vi`d2s)Cs~nggQdt_Xv+Y=IY?q5WzXh@Vu8?8zZvm5gfqZtl3}ckC7)^>x>ya zev9#AC+t4HXT-Rk!9C;04jwXe)Znpu^^Do0$KC9|`xto`iX1PB9Vc!zK(;+B*-*(Vy=)Bc4d*ABnnL`-cPZ$OJyNNhPh^irw{iHNr z2<^^x*;-2k1eFXk1~gWlgUH4iQyL+xjhA~6*9vlHB{^S3yl#*vd_y^3zqN_%H_Xt^8`5`D|Mje!NT=ODK9E_!9RFkUp~X-&b|ijc!3A*u!l^XY~VaF z8tj*A?Ju7mkq3Nrh?pq0-t;eHd~~2Zxvt=yDBKM#i=Cz&B`_ zVCn7=D#wVnh6*(35Yp+qwx59tj$z)wz_K%X81ZPS4_@%0OMtK4Nu&_7av;+j!`0|k;Xc+gL-Yh3?3F8AUtP{tlq1}(32#gXL`ZCMAR#pPkX9E7z@i*& z-vwuaJiU>~Y_wPq_(O0;$lXh<$iY$0n>vS8Lf#{Vqi%BQq-Ky%OM14J5^V?V3ccBv zB5L+US8w|S#b_ud+T*D2(67@=cm1v|6m^s0+Lrp>`t7dOMH`ke`8uJMbm|t;Yf?2i zR)QLx_SUJFzWJiICZ&!4-b&W*kT#+qy`^s(=qB&2I&6Qd$$-;cI==ROtNbpjRXMJ0 z^wcT!PW=tC^mT*ESkaw2diVqzy}k=d-!k-DiPl4n9O&Vm{RWTg*=@pzk>h*z8!>*k zmq?Ur4IMXP*yy2Sdxi`hxBJ);dykh~Xoa4Y_FiS1L7Q*bv;HPMn{Ga6-ObnDVvBzL zdKOw?p`Ha-Sa78Qd#_TK^2ior#*UrXGh&aP@xzDqj2kn0D3TPEHyk{A&k;yqCX60C zbnuYDyNw*WsNf&pvwqL;!TS#F89%0H$e5lHq-MnEp24GgmfL^1o?&B04C&v$e+_?` zB?b=cS$E{by@!vJG;Z+j!-o#(*RQ_2`sj%QWrD!&*>}u-Lr0G588cQ(`wbsDR?wKe z$)k23GkVJfn&?TqehHe zp=YacLn*j570kF5M2KSzvx62j_t&0(^lsz!UU88gJTu0iTBK+7!7KLcq0f!64-W0= z{g47%eS&?c^O*@f8x0*jOmw%gB!m48Mo8LnK4Sd1p(FQjG%1)tV+79DkWSAQqGpn~ zdIMH1&y5F<96V7zqdNFCymwoj(V>Y~I1xiub<#~~8sc2M6US+tg$^3n@X zgN^SL$@-S;^+7|Us-t@=EB&o{J>!u>IApL>w4iZq_pI_aNG`iaMFj2k) zLb}BFOZdBY2bfKdlYE$Y*M3r3ytTA+ga*o6n3t0h>Y-b=n8~d=x^>+<5ni7{qQ|%a z;!^t!SbXV$1D9NG=>dBzyX<14Tu<=(^jW&IfE1}W1L(3YWtaeK&91FGZ+#~NW5(9> zFEMbbflCe;XFI;;C_Q4qjuaam6DIaZA4bW>BoUz=y)vZl>mbZ(k3_ zH~}kha{LGhjcbk>HA-aIv-6c(B;k&p z{&F@}lEDFzQjL&~0a8QA0}|X#Qmn<~e2A2nlmv009F2seo4C6rNIIIJr~jC-!v>5T zvHQ@`sa(_C-czg4R8<@AcQSv)l>;Oc2DaM21>38E7q}7g))Xk11r_W-1wwou)Od z4DwDdqfIl)jPT4dFFmWgPkDBk8=h0GS`Ld3U}_|7uoH_Nq_mwa_a>9egYz13A)tBFCZA!f6d^iS83-r9Q7FWf--uNz5b zj^56ut<9wGx`p(5wrXuHz1wYD+qJfD?awNq>7)-J7GMK+hVj%pn(v$bcp-e?`u zI;C}D>wKBRy{vUY>#5d}tuupI#+FW&x!vPryy-c4Mal)OD_ft)F#e~lOJt_^&erX% zYecrYw@wr3-Yqh|qjgv7ev#O{t@~PUioZYHdO&7@uN7T=AR}>)$#lS@qOG@E@5-z( zdc1pyL5&n$UL<Gj7qK~_;Wc_c6<_-`o9w>9n2eoc&UEDgfbx7+l84-I&hRkmY zvLKh)J@B)A~}T;a_RJ8f+^w-rEN|1Um*h1v|^!_pZU< zU^kfo9})}=_KSqN%3C<4A34R)!8~jY>*nb|JA6y_a?H2_X2baiv`(?o|g3D#r{a3*i!Ii;P!LNg> zgKK0C{<`4$;5RZ8|GVIZ;P)~ge`D~+;HKc_;7`FV!JmV_1b>wo`o9IY1%H=$`hNzu z2X_Q_26xGf{XN0G!F@7s|3L6y@Q}>jKN36|JSKDaPXtc}PsvRFGr_aLb26X*Lhxen zQt)!{ip=c47Q7z35xg0^C3F1m1n&m#$xQzT!M}nJWxoI8;FI7}nf3p7@Okh>@MZ8- z@OAJ_@U6_q1YsCPVH_r58fIZ07GWtX1KQyf;gqsIVCrz1aN2OXaQbkDa7I}zFmpId zIIFA~m_3{$oKsc~^o0Gwxx;zFdBgd_`NIXm1;d5Hg~LU{MZ?9y{^5XdV7Pd=M7U(Q zRJe4wOt@^gT)2F=f~+@KDO@>RMOGcG7Oozy5w01o6|Nnw6RsPs7p@;}AS)3z3O5cn z2?vFnhMR?(hg*bO%G!jj!)?NC!|lTD!yUpM!=1vN!(GB%!@=Qh;qKv(aA>%PtX>!% zjtKXZH4G!eQQ>G=$*_00PdHZAGmH->g!_j3h5O6Oh6BO_!-K+u!$ZPD!^6VE!z02Y z!=u8Z!((L4!*Sv9;R)f1;Ys1i;VI#%;c4ON;ThqX;aTBN!n0*X#81O>!=K5zh@Xe& zhZlqwh8KkwhnL7IiOa%YgqO=&iC=|RgjdRniC>3Thu4JHhS$m3iQk034SyHj5dJ>= zgG^1n7~UBEF}x|fIs8+2OZeyTFX3OqTf@JFw}pQX{}KK(yj>>zo@qTR(``?-o)7N` z?+ot>?+)(??+xz@?++gc9}FJ~9}XW09}OQ19}k}hpA4T0pAMf1pADZ2pATP_rnjue}x~)I*yOSPr^^b&%%F)pNC(BUxr_W zUx(j>-%6McqA-f0Sk`%@Q5NM<5tUIDwWBGbDWj>PsiSG4X=Me-^wA8_jL}Td%+V~- ztkG=I?9m+2oY7oSPt-4(JDMk&H<~Y+KUyGKFj^>DI9eoHG+Hd`9}S2GMvF&F$oi3` zqNSr{qGhAyqUEC%q7|c+qLrgnqE(~SqSd1{qBWznqP3%SqIIM7qV=N-S(a)muqMt|S zM;AmFMi)gFx6X?$i7t&Ui+&MZ9{n=*(s}n&{f-y6F1oH_>mS-$ge> zzmNV9-5C8bx+%Ij`crgE^yla=(O;umqrXMBMSqX}5&bi|J-Q>hGrB9fJGv*jH@Yvn zKYAc~FnTC@IC>;{G2N%U#-S@iGd^XQA{%jm1<>*$;4TUjR=#9Uf%X+IYHn`gn$T#(1W9=6IHP)_Asf_IQqX&Umi4 zC+-)|9nTZb8_yTdA1@Fu7%vnr94`_t8ZQ?2j|ap9^<0IlD*L?Vzm0zv-w^*k{zH6Y{KxpF_~!Ue z@h$P6WRlckcSlVy@+ljV}-lNFK`la-Q{lU0&clhu;d zlQohxleLnylXa4Hll7AIlMRv$lZ}#%lTDIA$)?F>$>zxx$(G4h$=1m>$+pRM$@a+( z$&SfR$0d=wwW?cd}11HW`yqn}-z2|HewW;k{66_Za%1wxh$)A(IB!5kAP5zeL zmi#^WNAl0)_T-M_&g8D-?&O~2-sHaI{^Wt=!Q`Rj;pCCz(d4n@@#Kl*$>gcz>ExN@ z+2pz8`Q(M<#pI>r<>Zy*)#SD0_2iA@&E&1*?c|;0-Q>OG{p5q>U&)8bN6E*@C&{PD zXUV^l&yz2bFO#p5uaj?*Z{>TsK^mq}8mCE`rdgV&MOvm++D@lPr%b0xr%tCyr%k6z zr%z`{XG~{GXHI8HXH92IXHVxy=S=5Hd(wXC-03{&yy<-D{OJPeg6Tr(!s#OEqUmC3 z|8zh)FkL)dB3&|FDqT8VCS5jNE?qudAzd+DDP1{TC0#XLEnPibBV99HD_uKXCtWvP zFI_*~Al)$CDBU>SBpsA)nr@bEo^FwDnQoPCooq_f9hHtw$E16w`=n#jaq0MULb`9dU%G!fF+CtXFg++e zI6WjiG(9XmJUt>kGCe9iIz1*mHa#vqK0P5lF+C|gIXxvkH9ajoJv}2mGd(N)NqTmA zPWsdI-1KMZdFjv7^V18`3)73zi_=TeOVi8JU!<3(zf6CXUXfmzUX}hjy*j-ny*9lr zy*~X-`rGt(=?&@c(?6s)rhiOtN^eg8l-`p5IsHre*Ywu(Z|QC6-_w7j|4eUB??~@V z?@I4Z?@8}X?@RAbA4nfeA4(riA4wlgA4?xkpGcofpGu!jpGluhpG%)lUr1j}UrJw2 zUrAq0UrS$4-$>s~-%8(3-$~z1-%H<5KS=+Tewcogew==iewu!k{yY6V{UZG`{VM%B z{U-f3Yh^(eW>FSrNtR|=mS;s)W>wbCrpTturpl(yrpczwrpu=AmTjJGk!_i6m2I7ElWm)Amu;WzknNc5lO2YAUiNSC_6YiBs(-aEIT|qB0DlW zDmywmCObAeE;~LuAv-ZUDLXkkB|9}cEjv9sBRexYEBi@yc6Lto)9l>rXW4n#&$IKh z3$hEdi?WNeOR`I|%d%f&muJ7sewAI3U71~#{W`lkyC%CfyDqyv`%U)S?04A>+3&MI zWH)Ai%x=nV&i<6$lKnaROZM07*6eTDZQ0+me`Np6ZqM$>?#%AW?#}MX?#=GY?#~{` z9?Txf9?l-g9?c%h9?zc0p3I)gp3a`hp3R=ip3h#$Ud&#~Ud~?0Ud>+1UeDgh-pt<0 z-p=01-p$_2-p@YB{*`^0eUyEieUg2eeU|+@`#k$1`!f3~`#Sq3`!;XoK_2E&9_LA( z=2@QSMPBAr-p;4Ur_86yr_QIzr_HC!r_X1|XUu2HXU=EIXU%8JXV2%z=gjBId-8ty z-1$8Dy!m|j{P_a;g84%E!ucZkqWNNZ|9n6`Fkd`hB409JDqlKZCSNvRE?+)hAzv|H zDPK8XC0{jPEnhufBVRLLD_=WbCto*TFJC|3Am1?GDBn2WBp;M-ns1hGo^O$FnQxVE zoo|zGn{StIpYM?GnD3PDobQtFnh(x*%XiO*{@eU_`3?E+^FQP_=6}p@%5Tp9l;4v7 zIsZ%k*ZkJ}Z~1Nc-}8Uu|IBaC@5t}W@5=Aa@5%4Y@5}GcAIKlfAIcxjAITrhAIl%l zpU9ugpUR)kpUI!ipUa=mU&vp~U&>$3U&&w1U&~+5-^kz0-^$<4-^t(2-^<_6Kgj=; zf0%!if1H1kf0}=m|2zLY|04e~|0@4F|0e&oXca*b7EuuwNs$&=krzc#7FE$MrYNQ? zrYfc`rYWW^rYoi|W+-MXW-4YbW+`SZW-Ded<|yVY<|=xMe#P9yJjJ}le8v350>y&G zLdC+xBE_P`VnzRAKrygbyjY@GvRJBEx>%-IwpgxMzF47Hu~?~CxmcxGwOFlKy;!4I zvskNGyI7}Kw^*-Ozu2JIu-K^BxY(o^RBT#oR%~8uQEXXkRcu{sQ*2voS8QMGQ0!Rj zRP0>rQtVm`E_N$+FNPFDi#>{A#qeT8v1hSYF|rs{j4s9$dl&l@V~cUc_+mn_Z?Rvo ze=)H*pg6ENs5rPdq&T!VtT?-oV}c*oU@#(>?!+|bC>g!^Op0K^Op;h3ziF&3zv(OiuDmrE=wRm2%Z`wQ}`xjdIO$t#a*hopRlBy>k6> zgL1=iqjKYNlX6hGX}MXsdAUWoWw}+kb-7KsZMj{!eYr!qW4TkgbGb{oYdN^wt=zpF zQVuQmD2J89%MsOH?o*B}$CcyD3FW@!e&zn<#PWdh!1AE-;PR02 z(DJbI@bZZA$nvQ2=<=BI*z&mY`0|AE#PX!_9SpKoRsl2)TQ+Z4I=khP*U&~v|zm>O@e=q-0{1^e5!o9e5QQ1e6D=He4%`?e5ri7e5HJ~ ze64)Fe4~7`e5-uBe5ZW3e6M`J{Gj|-`C<7{`EmJ4`DyuC`S0@c@{97z@~iUe@|*J8 zs#OJ5SVdJ_B~@BwRbCZUSyffLnxdMrnyQ+*nx>kzny#9@nxUGpnyH$(nx&exnys3> znxmStnyc!m`c-pR^HlRz^HuX#3sehM3snnOi&TqNi&g!r0oA~2@oI@`$!e);>1vs3 z*=o6J`D%q~#cHK$YD1>>bmOs>NnMItKU^ORKKtOP~BMl zvAU_cx%yLeOZDgKFV$bGTdTiSw^e_y{!#t2y1lxiy0f~gy1Tlky0^Noy1#m$da!z^ zdboO|dbE11dc1m~da`<|db)b1dbWD5dcJz0da-(`dbxU~dbN73dcAt1db4_~db@h3 zdbfJ7dcXRh`d9T~^-=Y4^-1+<^;z}r>htQ0>dWe@>g(#8>Rb6G$Dkdyqjua*+G#s$ z=k20hwySo#Jwf;FWz3Fy<~f-_R{TT+RL_=YcJnk zp}k^zrS{70RobhzS8K1{UZcHcd#(1`?RDDgw%2Q~-`=3TVSA(Y#_dhogW8+6H*0U+ z-lDx_d#m==?QPoIwzq3<-`=6UV|%Ce&h1^=yS4|ncWdw79?~A#-lILNJ-j`ly=Qx` z_Q>|A_UQJQ_TKG%+GE?}+T+_3+WWTmYwzEl*gl|rVEdrgi=;_58mtf;&HzYF%o{ zqJr-99Si5~y-PPph~g5`EYf0QCk7}Y5-KGoDIp*NCJ1)7V1kY9@9WOTNB{rl_0rih z8)v?8&iiw|-Z{^l+#R_)b8~Zd<>uw?&dtx=lUtBmm|K)voVz!7U+(_glHAhVvfT3A zirfRamAMCV59J=tJ(7Dg_gHRKZgp-=Zf$N|Zhh|Y+=kr7+@{>-+!MJixvjZvxhHem zb5G@Vl;rc^VkIn{#7Q4~c} z48>9$#Zv+$QW7Oo3Z+sSrBeoFQWj-X4wa{lq>iGFrdm?RP_3wAsn*nS)bZ2_R2!-- zbs}{VRYkd!NBLAhg;YevRDnvUlc`gvQ>oLa)2VjU8B|IYscNc3m8lx4J=KBgNOht* zQ(dU8R5z+S)r0Cu^`d%HeWQmKOQ=h!%c#q# znbZ~3mDE+#)zmfAwbXUg_0$d2jnqxl&D1RF7V1{&HtKe2HZ_O3gSwNNOWj4yqwc2W zQ}<8{sD;!bYB6;$bsu#RakN>U-)3>PPA)>M->)^$YbY^&9m&^#}DQ^%r>q`UtuK-H>iXH>NA; zCUjG}8Qq+2LFZ_SrfG&|X^!S;ffi|rmT84nX^qxtgEnc4wrPjX(?`-r(MQuQ>0{_t z^s#hn`Z)S{`UJWS-IhL)K8dcPUD~64I-o;3qGP&1C-lkmDfFrIY4quIJNgVdrHgbm zU82i$4c(sZKzF1&(VgiobXU3?-JR}1_oRE#z3D!5U;0eCAKjlGKo6wPq6g7u)928G z>2v8?dI&v~9!3wR&!b1sBk57}XnG7imL5lsr|akm^!fBedJ;XEo6`!cX~i7Nv}TTDj%Q9_+AwXI6Pc5kD#m3z#%BU1WFjVJ3QWSB%$&lU z%ACfW&a`9BU{a>YR5K-}%+xULnGQ@xrW4bd>B4knx-s3E9!yWB7t@>R!}Mj&Wco4v znE}i|<}79qb2f7hGnhG-sbz*RLz!XBaOON_1T&Hu#f)agFk_i<%y_1bnZTUSOk^f8 zlbI>ZRAw48ow|!<}PL)b2l@exrbT6EMyijiW*xJhd7Rn6Y-Bbuo0%t=EzDMC8}lTyoq3Ac z!R%yqF;6qkFwZj2G0!tEFuR#O%!|xkW*_qs^D^@a^D6Ti^E&ee^Ct5a^EUGi^Dgrq z^FH$d^C9yQ^D*-Y^C`2RIlz3ze9nBqe93&pe9atW4l&;_-!k7Z-!nfjKQccthnb(5 zUzlH+-bJ<#U2s@M=#tvuCV@I$f*-`9hb__d~ z9mkGm>(~kG`Rqh?5<8il!cJwUvD4WL*ct4F>_zOw>?Q1_>}Blb>`e9w_Dc3D_G)7?|{ILxb|<@w zeVToSeU^QWeV%=R-OcV{Uu5^P``DM*m)Td?SJ~It*V#ANH`%w?x7l~tciH#Y_t_8F z5802{kJ(SyPuczK0roTYbM_1NOZF@FYxW>}sNyjD4f`$o9s51|1N$TU6MLBbnf-KR3ow&|i7p^PUt>Q+mJJ*Bj zS+S7o#r5X;aDBNmxqe)KZU8rsJBu5{oz0!Y4d%|}YPliYP;MAEoI8&j!Hwibaih60 z+*ocLH=e8GCUEC-6S+y;WNr#Km7B&*=Pux8a2IkHaTjx!aF=qIahG#5xhuFUxvRLV zxofyQ3AdD6#x3Voa1U@Rxd*w2xQDq%xJS9ixK-S0ZVk7VTgR>E9_Kc28@Wx~ zX6^}Y3%8Zq#y!bxub9g{#qHpBRxIasaZhv4aL;njanExvaJwt!aeK&HP=~k|xxL&z z?j`PJ?iKFUiY44@-0R#M6}NG3a&K{ObMJ8Pa_@2Pb02UYavyOYbDvbq=RW23a|gK3 zxX-ySxG%Y{xUac`+#&88?py9V?tAVB?nmw??lAW=_Y3zc_Z#;+_XqbU_ZMGLv5Y^0 zZ@@R?8}W_#O1=r-lyAm2=UebOp5keq;aQ&Jd0yZ}UgBk5shGv9yvFMlvn%HC25<5f zZ}SeH=a1x%;*aKA^2hM4_+$Cj{Biv8{0V#;zAb+ue-dBCyS&Hye87i%#K(MrPxzDh zQ}|Q))A-Z*cKjK9$`|=+zQmXL8ooW>f$zw7;yd$Q_^y06zB}K8@5%S#d-HwxzWkYd zKfXUdfFH=8#Sh}o=Fi~=^XKxl{1AR9Ka3yFpU02jNAjci(fk;GEI*DP&)4x2`1ARR z{3L!dKZT#lPvfWa7w|Lq3;B!qi}_3VOZm(A%lVo775tU_Rs7ZbHT<>wb^P`G4g8J# zP5jOLEdCb$R{l2rc78TLhrffrlb_4q#n0pK=I8VG@C*2b{33oae=mO@e?Py3U&=4z zm-8$52l$ozgZxAM!~7%sqx@t1Dt}pX9gm zPw_kWo%}BTY5p1hS^hcxdHw}{H@}B}k>AVj<6q)m=3n7o-S*Q`(3mt@xLMNfK&_(DfbQ8J@ zJ%pY@FQK>4N9Ze@DfAQi3j>6K!db!~;cVd?VX$znP%8`(h6=-k;lg>s2w|i!N*FDS z5ylGRgz-Y1FhMw9m?%sVCJR%9slqg2x^RIoL%2}5NVr(IM7UJAOt@T_DO@33DO@F7 zEnFj9D_kdBFWey9DBL96EX)#a5pET36K)q~3v+}!ggb?~!d=2V;cj8RaF4J+SSTzK z77OG5uO#E6P_1d5OxcDgcpUq!am_8;bq|!;Z@-^ z;dS8+;Z5N!;cej^;a%Z9;eFu);X~mg;bY+w;ZtG1a6tG>_+0ox_)_>v_*yt991^|} zz7@U`z88KFeiVKZ4hugEzX-nyzX`t!e+YjHe~A_15n=HXEil>REi|xcS#8fPb)nZ93i#1|< zv4hxA>?C#;yNF%IZen+_huBl>CH5Bkh<(K~#eQOcaez2bJWCuTo-Lju4i?W9YsDer zP;r3&lm^V)0(_KJk8WiMUi;CN39Oh!2P>#RtWQ#D~R4#7D))#8u*I zagDfETqmv<9~U=>8^ulHX7LGei?~(XCO#={7oQS$h&#ny;?v?Y;ek$%44~U!YkK#|_Vex127x7o|H}QAz5Ajd&FR4O0LTVs2 zlp0BmrAn!Z)KqFFHJ4gQIf;^JiIG@|lXyvxL`jllNs&}ZlXS_DOv#dL$&vEXk{aSSm<~bh31cbgFckbh^|| zIzvjOqEs!Fq_R{awU;_b9i>iEXQ_+SRq7^nmwHG&rCw5RsgKlGI#cQ=^_K=n1EsU1 zLDJdMInrS1T&Y$XA`O*>NyDY{q!H3cX_Pct8Y7LB#!2I)I%$G*zBEyqBu$p4NK>V0 z(sbzpX@+#6bdhwibcuASbeVLyG*h}lx>CAIx>~wMx>mYQx?Z|Lx>34Gx>=ef-6Gv8 z-6q{G&6eg!cSv_ibEUhadD7j|eCZx(fwWLsBrTTimF|=7mzGFNrDf7`X@&HFv{HId zdPsU$dPI6udQ4g+t(MkEYo&G4dg*a#gS1iFByEESOVZ2IE7GgdYtrk|8`7K7ThiOoJJP$-d(!*T2hxYq zN7BdAC(@_Ve(8Ypne@5zh4iKLmGrfAP&y=iBYi7U+{TbzBzKm($X(@ba(B6h+*9r)_m=y} zedROdesX_#fILt>OCBVjEuSL~md}-I*VX@ z8{`}1o8+72S@JFNt@3U1?ec7Sj(mrFr#x4_OP(j+Ezg(lkr&7dEPo<@D({yM$e+od%U{S}%3sM}%LnB{@;CCg@^|w0@(=Ql z@=x+%`DghT`B(Wj`FHsb`A_*Tr9wGEX`nPz8YzvHN~MX?RB5I(S6V1Jg;HpRQCNjj zctub|MN(u%QB*}!bj46i#Zqj=QS!=>%2CSEN=xM!rIm85(pouAIbJzIX`{4NPE<}( zsuWl86kiFHP>GaSDJY3@vT}-Ys&blgy3$TLLrImQQmvGfvQne8S2`#il}<`$rHj&4 z>85m7dMG`WUP^DJkJ48;Q|YJlR|Y5pm9vyV%Gt^}%3$SOrB)fD3{{3H!s7=*o zYIC)Pno}v2RvDF5Ih9uhRa7NaRuxrMHC0y))l@CjRvk639;qIs9<8=ik5OBx$EvN> zMMjn#shs3)tZsHdu@si&*$)HBpnEvnUONiC~2 zYJ0VV+EMMKc2>KnUDa-CceRJwQ|+bpR{N-Z)ic$8YJYWrI#4}J9i*PEo}&&{&sA&H zA?i?dm^xfNPaUC-R7a_!)iLT=b(}h0ty3qc=c^OdN$O;EiaJ%DrcPHcP-mzYsu!sj ztCy&ks+Xykt25Or)GO7i)T`BN)N9r2)a%t7)Em{C)SJ~=>MiQ6>TT-n>TGq6dWU+a zI#<0*ou}Td&R6eI7pM!>Me1VpUiCipeszhuR9&VnS68SHs4LY6)rZuF)koAv)yLFT z>S}e3x>jAMu2&ydH>excP3mU#33ZFQRo$jOscu)FQg^63)m`e->ND!I>T~Mz>I>>_ zb&vX@x>wz&zNEgazM{UWzNWsezM;OUzNNmczN5aYzNfygexQD+ex!b^exiP=?pF_} zpQ)d#U#MTIU#VZK2h~IBH|n?Qck1`*59*KVPwHXyXZ08LSM@jbcl8hTPxUX2bo0|1 zXbrVST4SwJYoayPnrY3o7FtfDG+JXcR^v2Y6EsniG+9$LRns(GGc;4PG+T4Dymq8^ zly}YbC9$)oAUt4q8X8lh#@5qIK1}Y2CFRT2HN))?4eN_0`VQ`f2^O0op+A zENzf>wswv-SUXp%)rM$8wPD(D?L2LSHc}g9!W3_SGc&$#Gpq;Nx)Fx?@wJF+E zZJIV+yFiDq9M_ZsR)D~%rwR^SuwEMLs+EQ(qwp?4GJ)o`B z9@HMv9@ZYw9@QSxR%xrXHQHKjowisePq=tsT@3Y2RqyYTs$!Yd>f|YCma*wV$Pt;G+t8`cQbYBnjP>=Lj zFX)MWvVMwws(zY&y53GdLr?XhUagn(vRErb} zeS&_zK2e{fPu8dCQ}t>3bo~N-hJK-bk$$m$iGHbmnSQxGQ@=vLQol;STE9lWR=-Za zUcW)VQNKyQS)ZlfqTj0Drr)m5*5~MV=y&RK^}FzAs{c(MRzER(#Z`PmCx9D5- zZTge?cKs=RhrUzar9Z7dqd%)Zr$4X1pzqfA=r8Jf^?mwF`pfz&`m6eD`s?}|`kVS& z`rGe1g7$+O27^fPi8K)cVj5CbXC>qs9$tW8&Mth@!(b4E+bT+ye zU5#!=ccX{V)97XNHu@NSjWdmYMt@^~G0-^67-XDnoMQ|&&NXU{A;wT+m@(Wq&lq8h zG)5VtjWNbpW1KPGs52%Q=Nl7^NycPjiZRugW=uCOFlHDR8W$NC82#1GIkg{ja|mm#xusV#&gE=#tX)7V~_EovDesVykxv= zykfj+yk@*^ykWd)yk)#?ykop;yl1>`d|-TNd}MrVd}4fR>^BY=pBbMUUl?B+Um0H; z2aQ9}U2j2bcrRv&=!}+2%RsVDntF z)*NCEHHVqQ&GXC==16mtIocd!jy1=b@Y&|G9LHt#j>Gw(N-m`lxN=5lj|`GC37e9(NzeAs-%eAIl* zTxG5{*O+U~b>@2WadU&Y(cENiHlHxJm|M+l=9A`j^C@$OxzpTbK5af@K5IT_K5xEY z?l$+BFPeMJedbH%%jPTQtLAIw>*gEgo90{Q+vYpwyXJf5`{oDchvrA-$L1&Ir{;e1 zfccsEx%q|prTLZlwRzAyWPW3QYkp^bZ~kEZX#Qj#Hh(sMF@H6GGk-V#F#k0FvMQ`2 ztOiy?tC7{%sl(yi?;+zv?NQm6ic-qP4$tIBdM&+@In3a!YBt%8+UCtIgjr&_03 zr(5l;Gpy7qTGdv`DqA&Hd#i)h(duM%wz^ndt!`F#tB2Lo>Sgt|`dEFfGp&ABe`|m> z&^pT+WSwoDV-2>>wQ8*))=+DhHQYMS8exsJMp>h+G1gdXoHgF6vnE*QTNABG)?{mn zHPxDCO}8$vW>^#ZBC8?BqHo2^;a zE!M5pZPxA9Y-^5nhjphl*SgD^XWebhx9+hPSPQL1)?({k>pts#Yl*egT4pV`R#*>M zE3F5uhpdOKN32J!$E;P>YHN+P)>>z+w;s1PSR1WP)@JJoYm2qj+GagzZMUAXc33;D zUDngqGuE@#bJp|L3)XIHkM*Lp*V<>jWW8*?V!dj;X1#8`VZCX+WxZ{^W4&v=XT5KI zV0~zPWPNOXVts1uw+>jJS)W^9SYKLSSzlWRtwYu~*0s>lf=+ z>o@Cn>ksQs>o2>)KEiHbH?$ksjqOUiiQUw0W;eH6*g2cBX`8WGo3nXauti(4Wm~aT zTeEfBuua>tZQHT)_L25c_R)4r`xv{GeXQNuKF&VgKEZBdx3y2SPqM3Q*Y<4R4(!m5 z?AR{YiG8wtihZhmnti(6&OXCV?V??6m+Z1#W4E_E*d6Uoc4xbb-PP`9cei`kJ?&n0 zZ@Z7(*FMwkXZN=U*aPje>_PU~_Br-o`&_%$9%2u*huOpJ^Xw7!NPCn$+8$$%wa3}x z?K*pceZD=>o@7t9r`S{NY4&vc0(*vip?#5kv3-essePG!xjoaq!oJeJ%D&pZ#=h3R z&c5Eh!M@SH$-dd1W#3}oYTstxZqK&o*mu}>+H>u@?0NRx_I&#udx5>sUSuz}@3rr< z@3)uOOYLR$a(jjSfW6Xw(0<5%*nY%*)PBrfWv{l^*lX=|_ImqqdxO2v-ehmKpRl*s zTkUQ3llFG|DSL;#)81u2Z9ii_Yd>c{Z@*ygw)faC+I#JN_DlB5_ABM|CtucMQjLEXQ^nC+{5T9OWGCv~-SfS~Ev{F zx;R~(ZccZnhtt#P<@9#?IDMTnoqkS#XMi)%Im;R3ob8ITt&ZIF~w?IhQ*# zohzIxovWOyook$Ho$H+Iog17RotvDSomtK;&aKXE&h5@@XO44+bEh-cxyza7-0jSF z?r|143!O#IV&`7xKIeXCiL=yM<}7zsI1e~0od=zVoQIu9oJXCG z9(Oi48=XzgX6FfKi?h|)<~-?acb;-~I6IwP&eP5_&a=*Q&hyR-&TeOq^P;oY+2_3E zyzIQyzac=yy?8yz9K@yzhMAeCT}SeC&MUeCq6X4mh7VpF3YTUpiko zUpoh#L(VtOx6XIY_s$Q_kIqldVdrP(7w1>!H|KZf59d$kuRLitnQxG9m~WJCoUhC` z$v4e6%Qw%r$mjA@p3XCQHqYhxypR|3QeMt0c{Q))^}LZc^H$!@JNbP6$ox_Hqw_8E z$K+e(kIlEvAD2I#bnj@BZ<{|ce^S0G@8-R{pAYh3KFY`WLO#i#oIfRhYW}qR>G^i~ zGxBM^n6J*4NZ*s1eEWO{((_l=ufye3n|kE*IF{&V$VCr#3EwL>S? zjc-z$T`SWe6NgV7URhgzX_D5BAf0)}G^x$5Evko(nmBaIxbwyipWb5Vzh5`69#%KG zb|`7^FuD2A|9x3m8d^*Kbmg$@QX+n8CpRg>A!$tbx8urk=5ToZrFr>(zukQJ|GsQe z#$yc6u9fBNFAlH2v}phDgR~g&@7K-S|L+epAMw90>j&<7*+jUW-=f36ztLjkzh5`% zFr;>3qmkral^rLK8ar%w<*52gla6@)QF#82+4GOeCafc#bX0b2(6Lj4QKMUQ`uFd) z82#_p*|Yc&u5oV6h>4`N(Ae7X!$u9Q>{2^)%H-jdW9u*Yks_|um0hwa7+Zg7)P;<% z(OB}Y%5K?jjsMSY1!4AEK~&i-`>pZW#Eq|=P)E8s)=d~WykU9#h=#+*k7&{z!>hya zx@W_ytG`g)M@|_(qITkxabs(zOs4AoYgP8jJWu?O=MX~++clNWB>Mt#N z|2riuCjI+$_S6xUD)KA)WWPMQ{?e$=-wQPP?*;0cEfDE{{A=B_rK3iP5H0Yq%Rh7N_K70@89QYG3`HJ5BSg57ySEm<-lx&GwLtR z2mWvWHJ|anFB^}o8$V)FHp)U3t}d<^bs>vXw-Dey*0O+^EyTEAz%{`&#T7fIPz}3; zYZ=#??3%!T0{;p8Ch(iUZvwvw{3h_5z;6P-3H&DTo4{`Zze!Ew?j!3alDAs_{&)5a zDaM?_cM9Jre5df8!gmVaDSW5!ox*nt-zj{j<;H#MeNOq;XA$E?W^{}2U4-u7%n z2;W8cF2Z*azKif(gzqAJ7cuT4=DQm6T@C-$7<)DRSHpib{8z(&HT+k@e>MD9!+$mW zSHpib{8z(&HT;+0zXbm!_%FeK3I0p)UxNP<{FmUr1pg)YFTsBa{!8#*g8vfym*Kw* z|7G|u!+#n6%VA~vEb2$pU$UK2hW9eOm*Kq(?`3!|!+ROt%kW+U?=|pV1MfBPUIXtn z7<~;!UxU%tz<*8BJpDV9WI$Z|33Wt@b6>%KK%Re@54WU?fLNU!@m#z zKK%Re@58?j|33Wt@E^c`0RI8}2k;-jKRE6M7=Hl&0sIH>AHaVA{{j35@E^c`0RJKU zhwvZ5e+d5}{D&AnsP2XEAHshK{~`Q`@E^i|2>&7chwvZ4e+2&^z8ArN1pg8IM;Lzu z{}KF0@E^f{1pg8INAMrPe+2(A{KxPgWBf7v$M7G+e+>UI{KxPg!+#9_G5p8yAH#nP z|1tc7{ayk73-Ay2dtkp;fPb*x1N*%K{1@QA0RIK}2m8GO{1@QA0RLdW2ljgj{6hnH z&;TAZfCmlWK?8Wu03I}e2Myps19;E?9yEXl4d5m4pJM(~j2}9{gAVYZ13c&e4?4hu z4)CA@Jm>%qI>3Vt@Sp=c=m0Op_*3{V!auZt2QA=13wY219<+c5E#N^5c+dhKw15XK z;6V#`&;lN`fCnw$K?``$0v@!02QA=13wY219<+c5_IqHz2ljhlzX$evV7~|Udtko@ z_IqHz2ljhlzX$evV7~|Udtko@mV02i2bOzaxd)beV7UjDdu8n3GUBI<_yNs5(A)#f zJ!$rX6#%*qp!)#251{)1x(}fH0J;yL`vAHRp!)#2 z51{)1x(}fH0J;yL`vAHRp!)#251{)1x(}fH0J;yL`vAHRp!)#251{)1x(}fH0J;yL z`vAHRp!)#251{)1x(}fH0J;yL`vAHRp!)#251{)1x(}fH0J;yL`vAHRp!)#251{)1 zx(}fH0J;yL`vAHRp!)#251{)1x(}fH0J;yL`vAHRp!)#251{)1x(}fH0J;yL`vAHR zp!)#251{)1x(}fH0J;yL`vAHRp!)#251{)1xDSB)0Jsl;`vABf*EF77H@)sC;5Jbdi%QMI}9gh``FyA2;gn zo!r z8A-ls^tZ#toogpds3qr1QCas^x z)i&%?JEaMps$q|jqZ(F|e|k(Bg`Y}tfB)Too{Yf{{$n?<{ojP-hW}?|P00gg)AaYa z>c0}FWZ(biSL<&!uK)c;r15rb;}L%kvhlFtV<*=(!QW~$gM6dm-`^xXt;rDo{$q{D z)DMyLqb5Vl9O_%f{Jo;>fy6(!P9Lt=qW%(r_{vBm6g&-XH5`Y67A6yH9IZy&|CkK)@$@$CcHK7j25*gk;m1K2)*?E~08fb9d= zK7j25*gk;m1K2)*?E~08fb9d=K7j25*gk;m1K2)*?E~08fb9d=K7j25*gk;m1K2)* z?E~08fb9d=eoZF$0u=Ky>Tz~=yb4#4LCd=9|p0DKO>=Ky>Tz~=yb4#4LCd=9|p0DKO> z=Ky>Tz~=yb4#4LCd=9|p0DKO>=Ky>Tz~=yb4#4LCd=9|p0DKO>=Ky>Tz~=yb4#4LC zd=9|p0DKO>=Ky>Tz~=yb4#4LCd=9|p0DKO>=Ky>Tz~=yb4#4LCd=9|p0DKO>=Ky>T zz~=yb4#4LCd=9|p0DKO>=Ky>Tz~=yb4#4LCd=9|p0DKO>=Ky>Tz~=yb4#4LCd=9|p z0DKO>=Ky>TP;dt*xC8Jy0Ivh^IsmT&@H#-j9iZS2P;dt*xC0d20SfK_1$TggJ3zr5 zpx_Qra0e*30~Fi=3hn>}cYuOBK*1fL;0{o52Pn7$6x;y{?f?aMfPy}cYuOBK*1fL;0{o52Pn7$6x;y{?f?aMfPyyrQe+d4E;C~4Ihv0t* z{)gay2>yrQe+d4E;C~4Ihv0t*{)gay2>yrQe+d4E;C~4Ihv0t*{)gay2>yrQe+d4E z;C~4Ihv0t*{)gay2>yrQe+d4E;C~4Ihv0t*{)gay2>yrQe+d4E;C~4Ihv0t*{)gay z2>yrQe+d4E;C~4Ihv0t*{)gay2>yrQe+d4E;C~4Ihv0t*{)gay2>yrQe+d4E;C~4I zhv0t*{)gay2>yrQe+d4E;C~4Ihv0t*{)gay2>yrQeF)x%;C%?*hv0n(-iP3Q2;PU_ zeF)x%;C%?*hv0n(-iP3Q2;PU_eF)x%;C%?*hv0n(-iP3Q2;PU_eF)x%;C%?*hv0n( z-iP3Q2;PU_eF)x%;C%?*hv0n(-iP3Q2;PU_eF)x%;C%?*hv0n(-iP3Q2;PU_eF)x% z;Cl$Zhv0h%u7}`y2(E|VdI+wE;CcwIhv0e$u7}`y2(E|VdI+wE;CcwIhv0e$u7}`y z2(E|VdI+wE;CcwIhv0e$u7}`y2(E|VdI+wE;CcwIhv0e$u7}`y2(E|VdI+wE;CcwI zhv0e$u7}`y2(E|VdI+wE;CcwIhv0e$u7}`y2(E|VdI+wE;CcwIhv0GuE{EW92rh@< zatJPm;Bp8qhv0GuE{EW92rh@+WlK;oJdR-8K zixIdOfr}Bi7=eosxEO(p5x5wEixIdOfr}Bi7=eosxEO(p5x5wEixIdOfr}Bi7=eos zxEO(p5x5wEixIdOfr}Bi7=eosxEO(p5x5wEixIdOfr}Bi7=eosxEO(p5x5wEixIdO zfr}Bi7=eosxEO(p5x5wEixIdOfr}Bi7=eosxEP@>j!+jz;A8|&M&M)wPDbEl1Wrca zWCTt|;A8|&M&M)wPDbEl1WrcaWCTt|;A8|&Mu_(qaTO!3V#HO9xQY>1G2$vlT*Zj1 z7;zONu42ShjJS#sS25x$Mm)ucrx@`RBc5W!Q;c|u5l=DVDMmcSh^H9w6eFHu#8ZrT zilH}S#8r&AiV;^a;wna5#fYmIaTO!3V#HO9xQY>1G2$vlT*Zj17;zONu42ShjJS#s zS25x$MqI^+s~B+=Bd%h^RgAcb5mzzdDn?wzh^rWJ6(g=<#8r&AiV;^a;wna5#fYmI zaTO!3V#HO9xQY>1G2$vlT*Zi^7;zLMj$-JY7cH3>_3h2gT4qF?3K29TY_3h2gT4qF?3K29TYfd;xjBfIMG-ekvf}7m)7@$oB>0`vUTP0r|dwd|yDmFCgC+knan~ z_XXtp0`h$U`M!XBUqHSuAm0~|?+eKH1?2kz@_hmMzJPpRK)x>^-xrYY3&{5cpY*9*w&1?2St@_GSzy@0%4Kwd8(uNR<~3eZahfd;xjB0KHUzUMfH@ z6`+?2&`SmAr2_O)0eYzby;Oi+DnKt4pqC2JM+NAj0(4OUx~KqMRDdojKo=FDiwe+1 z1?ZvzbWs7iC_$Vgh?4|yk|0hJ#7TlUNf0Lq;v_+wB#4s)agrcT62wV@I7tvE3F0I{ zoFs^o1aXoeP7=gPf;dSKCkf&tL7XIrlLT>+AWjm*NrE^@5GM)ZBte`ch?4|yk|0hJ z#7TlUNf0Lq;v_+wB#4s)agrcT62wV@I7y(366m4?ag!i!62wh{xJeK<3F0O}+$4yb z1aXrfZW6>zg1AW#Hwoe2y3F0R~{3M8<1o4xg-bheyB#5U3 z^+tlYN)T5G;wnL0C5Wp8ag`vh62w)4xJnRL$v<(0bGZcPatY4m5}eB=IG0OsE|=h3 zF2T86f^)e9=W+?oNXwShp1GmZFYGv3@DmFU9($SicnOmty@=tY3=tOR;__ z)-T2SrC7fd>zAT_NKrqes2@_)4=L)06!k-j`XNRAkfMG_Q8%Qh8&cE_De8t4bwi4} zAw}Jg;@mXFxoL{?(iG>VDb7n%oR_9JFHLbin&Nyk#rbH8^U)M_NQycnMIDl&4oOjm zq^Ltu)E_D8j}-Msin=33-I1d1NKt2`(EBO$ehR&xLhq;0_bK#!3VokK->1;`DfE2` zeV;<#r_lE)^nD6_pF-aku`i3*mqqN$BKBnw`?83AS;W39VqX?f&la%{i`a)n)UieE z!y@)!5&N)+{Z_<&D`KA&vA>GgUq$SrBKA=c`=y9_vxt3D#QrE^e-yDlil{4#*e6Bo zlOoR7i`Xwk?3W_;OA-5}i2YK;{wQL96tO>w*dImgk0R>PBKAWO`=N+Bw21vs#C|AZ zKNPVail`%tSob2{a_afH4h;=Vw-HTZFBG$c#bua$2?x^dEsOyTT>x!uB zim2;~sOyTT>x!uBim2m}&a5_D<_`m}_6UqZewL6?@0_e;q8CFK1QbZH5?v;&f67*RKx~h!% zFJpeonBOwip^SMhW1h>H=Q8HGjCn3&p39i$GUmCAdZ3K?E@QsSnC~*`fimX3jCn6( z-piQxGUmOEc`sw$%b52v=Dm!0FJs=znD;W~y^MJ;W8TZC2g;}i%BTm*s0YfZ2g;}i z%BTm*s0YfZ2g;}i%BTm*s0YfZ2g;}i%BTm*s0YfZ2g;}i%BTm*s0YfZ2g;}i%BTm* zs0YfZ2g;}i%Gf_;?4L6BPZ|5CjQvx_{wYK6m$8q^*hgjTqcZe<8T+Y>{Zz(&Dq}yD zq4&$s`(^0;GW32KdcO?4UxwZKRc%+Hl9B_rzAF>KRc%+Hl9DzEpAn&TgVm9pXnB2zDD2 z*jT@eXT-+(WjrG`)-U6kTb1#QTrvKPXT-+%GoBF}YqS8L=_`jAz8g__NPN z5gX&r>J(yU<9D<37}w46FS%ypceDIU>}>pQmVb$zjo;1kFR`=nyIKAvb~b*pLH_>l zs>II5PbM68Hh%H|nVscdm;4Vl{Ac->+=l-w{}LPiv;0eJ_|Nh$vEe_^1uSNPBJFR`=vceDIU>}>wsEdLTan}0XUzr@bw-_7zbu`&KE{}LPH&+;#^ zG5#$7x^9+#$rah`Q=P|^___O>>rnmmT zf0HZ5pXFy_WBgfuCN{>OO}KaNTP0U{8_zCY>Ypvw~3AMXZ1F*;XkXli4FhRd5r63=P~4p@n`inu`&MaJcig9 zf2K=`jqzvaF~r9Bv-22YWBs%97-D1nv-21dK3M;(z9%;PXLS^@;XkXVhz}-v$3& z@ZSahUGU!p|6TCk1^->}-v$3&@ZSahUGU!p|6TCk1^->}-v$3&@ZSahUGU!p|6TCk z1^->}-v$3&@ZSahUGU!p|6TCk1^->}-v$3&@ZSahUGU!p|6TCk1^->}-v$3&@ZSah z(TId#FpEDlA|ZAbe`rKP>@5D!h=kbL`lAsEv9t9@BNAd~>yJhx#Lm_qjYx={tv?!( z5d6Y_R(}y2{=t6_{P)0r5B&GQe-Hfkz<&??_rQM-{P)0r5B&GQe-Hfkz<)F%Ax*=w z{O5uH9{BHp{~q}7f&U)(?}7gw`0s)L9{BHp{~q}7f&U)(?}7gw`0s)L9{7(&Bt)iU z`PT#gJ@DTH|2^>E1OGkn-vj?W@ZSUfJ@DTH|2^>E1OL&8gvcVSKltx~`yROOf%_h~ z?}76kIPZb;9ysrT^By?wf%6{t?t$wbxbA`L9=Ps-;~qHff#YZ>LS!4(5ghlxaSt5# zz;O>8_rP%v9QVL+4;=TvaSt5#z;O>8_rP%v9QVL+4;=TvaSt5#z;O>8_rP%v9QVL+ z4;=TvaSt5#z;QGbA+j{%xCf4-p$NH;I0nbjP=wsaJ_5fz@Y@5w(MW{I+)OW^aR{;D z2fRk(5ON=Wz-u%PA@|`2yhh^?av%8uyhh^?azE1xXdFW1HT;9u9(aw$A>{j5XYd-0 zL&$yXSMVB*L&*J1ub^=Vu`|7b#v#OpfAHD^uhB4s$b94}a2gFm$bF0xTtw&i(c{ACFL;Z_Aubw+kSpd9y!F6a54`ojTMxWN z;}G(DSU>OrgzaegxFYD@YVxwJ@D28 zZ$0qV18+U>)~xoq9h zEQHwEzCyDQQf6l3N0Sg@XX8eb5MpQRjiw;Ph97VhO+mo*Q+w53Zsg2>H2ep3x74*cd0cihdyEeilFI2SV&@ozV}3*zgaoq92HhejwzE z@q(-92SV<{Kk91q10nY@esC52K*)WJA6!L05ON>>!BzAFA!R(q53Zsg2)U2>MqQ15 zAml#AkGdNDK*)WJAAChW5ON>ujJg{AKwR_#Ay@bZZ_y8g+=qYg7X3iTefURRjea2H zKIR*BH2Qy#V+rgl@E83*$bHN=_>2A@z{-XZ}xsUk6 zUiQIRAN8^i-umFJ58nEymwnXBKI&y3{Pj^U`{1t+{`#nweel-@e|^-;KKSc{zdm^C zgQq@t>ZAVk!BZbR^-=%&;HeLu`lx?>@YF~B>w~90>R%r`^}$mgJoQok`lx?>aMcG_ zebm1`>R%uAuMf`p;H;1O*9T{PaMnlt>w~jC>R%tc^-=%&sDFLbzdpF@gS$TJUmx|a zkNVdKhkbC^NB!%A!#+6dqyF{5VbT*M%ZsRgeQ+2(L0r-k1m8!Tg2O)QU-ShbKZm#h zkI@%|+(+Di$LI?}?jvr%WAp_f$Aicd;4%7wko)it9-}V^xsN=7`WJmc$bG~O>RN};O@toj0-tQpy;U9d*dl}?dHPa<{FN4^4Zq&PYFN562yrSO4dl}?D zo*Q*<3@*puGTzf5KbP%yyr<#DsDoqF!7;cTgUc~E8iS)T>ev_@jlt0vb!-fd#^7j- zIyMGJV{kM^9UFtAG3wVC{EWfR72>?|(u`AK4D z>xa)z5<80ve14MHSzIKTAAEk2+=qY63qCJNj-BBT^MlVzx+!!$KL1DT?C0?LKVoOk zh0g&JJ9{pC4v^T{bK!G<#Lk{8#W`AvcujGRmLhKPIYDxKoyB8{bF>t3i_Z;`@54Xz zeTs9m6z6Cu&e2ln{S~n)z}Bs(3jQN2i4G* z)z}BsINzzpKB&g|PBr#HHS}gR_CYn`q#AKj{eM;6O_CkSmECc+R9`3-In=n9(1FYs z`Er=y5pJ!iCNvFXCKQlW1tSVTF>4$q+9N@#3(4GsKHSyg01i4DsR+FAnQBL%cY|i^KZO z5HAk#;;?=*tltdr;t($m>o-HZIIQ0c&r62&n_>NCSijkg-@A1g@A)m>^UJa0r}zD$ zJAQlLFS_@$_x++fetX|9y7AU-{Px~o?B$yG{_+^CT=o86bh+yNzvyz+`+w2ps`vk* z%T@3HMVG7I|BEiyy#E)?zj$sJ&+XQ0cJbV9y=E8B?bd5{@!W2`W*5)xo`dY-x!rS+ zT|BpY4zi2qcI!2}=ODXyZWqt()@ye0+%BHmt=H_HgY4F8cI!2}=ODZFn%#Ph_Z;(> zQTwgec+WA99m{X;IYvAG)@!`)7`y(rUbBk}z4w^U^}o2#dyla5iv6 zp6+5iv6p6+5iv6p6+Z^ykI_jcUd zac{@H9rt$J+i`Elz1=#?Zk=Vf&azu)*{!qe)>(GzEW34<9XEH}+;MZq%^f#)+}v?< z$ITr#cih}@bH~jcH+S6JadXGb9XEH}+;MZq%^f#)+}v?<$ITr#cih}@bH~jcH+S6J zadXGb9XEH}+;MZq%^f#)+}v?<$ITr#cih}P@7O)>*gfyqJ@42(?>KPP``vlnqT{Of zyQ4doBdjC6i=O0(S{~f#jx8CCY@7SGx?-_XiJ9hn#tKR=!y#Jje|G4V?@7Vb_ zPd#we```Io`*HQa)dN=#Ts?60z|{j+4_rNP^}y8wR}Wl0aP`2|16L1RJ#h8F)dN=# zTs?60z|{j+4_rNP^}y8wR}Wl0aP`2|16L1RJ#h8F)dN=#Ts`jQSARNpO8DxR9{zOh zmhjat=i%9(el4s0?3Z8vb*^(CxO?F4VV-*6?_s_6uwHvuuRW~S9@c9Q>$QjZ?O}d< znBN}ew}<)dVSany`+@I=`R!qTdzjxIxPRdOf%^yUAGm+u{(<`k?jN{+;QoR82ksxZ zf8hRs`v>kHxPRdOf%^yUAGm+u{(<`k?jN{+;QoR82ksxZf8hRs`v>kHxPRdOf%^yU zAGm+u{(<`k?jN{+;QoR82ksxZf8hRs`v>kHxPRdOf%^yUAGm+u{(<`k?jN{+;QoR8 z2ksxZf8hRs`v>kHxPRdOf%^yUAGm+u{(<`k?jN{+;QoR82ksxZf8hRs`v>kHxPRdO zf%^yUAGm+u{xO%Y{&R1{znzEPZK!{s{(<@j>K~|op#EXDe4zh<{s;OW=zpO9ME_}4 zJ<)%9+hL;r^mgGy|LJXqiT=~ug%ka!Rndw5)2w=;|1_(f=s(S>C;CtHpH@XD`cJE( z6aA-E(TV=ks^~=j=`Dzf{?jVyME_}4J<)%fRZsMvX4Mn@C;Csb>WTgn{ij*=ME{BY z)2w=;|3v?3Rz1;wqW?6jp6EZ(f0|WK^q=TI&8jE*PxPPYKhb}p|1_(f=s(eanpIEq zpXfi$swetS^q*$c6a6RpPqXTY{uBMDS@lH!iT=~9dYV;F^q=TI&8jE*PxPPYKh3Hq z`cL$q=s(eaqW?tyiT)G)C;CtHpXfi)f1>|H|B3z+{U`cQ^q=TI(SM@S2r&!vDI z`7Ml+L7{UCqiB8$qv&%go+JN-Q8fRBQ8fRBQ8fRBQ8fRBQ8G4s&q5>G_bfD`ea}K8 znH|1op%LwS6%x_s6gfxVtB{EHy$XqF->Z;__PshqjCMW>jbxHIAB9G=^HFF->z6_! zn*Ty0n*Ty0n*Ty0n*Ty087taZSVTJ?g+;V>7Z%alU06iB@(PP+{ajc?>*t4m`oqh2 z*$3?B+zX#xeE3_h&WGn;ym|fM1)*sV>sRj|Kjx>Hc_(hcDSvdN%Ac)qd1wk~&1wynwEfAvhX@L-}PYZ--{tJX?{yQ)y>xcgi%+dUJ z+7-=z2kL14J5WdS-+?-s|4zH2`R{-o&3^~%WE=6{0Xv%i4%pHBcfgM3zXNtO{~fTS z`R{-o&3^~%X#P83C%dWFnDpxCUSra$qkD}>ua53DCcQekSAg{D=w4&etD}33Nw1FX zy)3;tSylh^&42vi!;3$E`uyR%q>^~-*p5gdF5QkuVk&+2`(GZj#ofnGA6|U?$kxt~ zW1T;~ee<9G|3AKe_dc8Az5MX{{ioOO&Mn^aQ|*$ZeD$w%F7#Z6b&kG4=Rnat1v0Cn zdF|XMx~Cwi4F2B!?D6BL*FV4b1V4F6zn^sWlT@-^1bI1}UE6PNv(4P&`vNYn68n^Y zU|Bl%$zRd+o%=-d4>NhS(eLvPGcv26FEAssI$FEoW`P@-)%o11hnod%WLD>M{^4eU z8=2KfnA6{Ilg#RGo(7W0bxEf~$>DQCIvt93LOLCab}@E36z%(TIuz}a zKsL$NvFj3KTaZn%b#exE39{vF!k)5Dhw`~of^?FtW9J{~7NnDGozGp8NVg!JWb1sc z{YbYUon-6eFls;2El4NXI-m28bPLi+w$A6gHKK=9T?Lg|AkDp)W5_<8gp{FgQw;$-8 zl+moZt2If{?CFsU8ohq^BH!b~>yLlV-QU|-LTEnhH6jr-dSq;R_x|07r;+K!n{V@v zpWglW^v^F}y#4XbV_g}Xq=DwpGb0T&x>sh>K)-$S?#I_JU%Ywo_SG~0eD~qI*KhNE z623k;`7#}65R?X*-`B)0)I|5nkOmrey)&eNM&CTkmXRMGe@L+1Zzv9y2%4YhwILBS zTDuKbPl=!(U;q62&5Hs+B4~b2CyRs^{^biF4{^$%G}!qbdG=N)Zl z`@pe%AZaz)x3D&4XZzTD4kWQg_ZqabeRQt@NvzR52a;H$=eJKFPbBl^)BC^w^LyW> z4{?I%j!aTnqkAPvWsUYNo?o*kcp{9a=D8`K+-!A!al3S!3_*Xn*^Zqv-7*pEa6)vl;oUvGZ^7 zOg?Mu{0kD~v&PQ9AVEHB?EIU}$Y)JC43~n%Gx@BsI|&xg=^f)pgkXN}#ZVDU_T zYwRw_KFLY6rTFjSGyY`#9$Zw5Z`%PW!cOSd}~>6 z7kRI-8xSng$$O36^)Eb-_nN3y{|gV~y~eKpE!N3Lo{w?UqlTB3b z{9DkICmXx-Z$VFMze&K*T+1RyTI3Q0pcI_7q+*nYQC!5)f^KU`%MmTUI9FQlQpVNNfz>Nh(d9wMO ze+!E8WMk*wf}%Xx%%1eW1x0zXvFm>eit=P*=ih>&JlWXwzXe5kva#!b3ySh&Q?^I{ z3kPn51M+0^Isd|e8{xo>aNtHba3dVJ5f0o42X2G|H^PA%;lQnM;I?maWVYD$-}X(8 z=$`-EzR3~Y^MBhnIilPCTLHnXfZ(=oa^&;&|E)EETLHms-{i=wwDf zP~cW5aBCg&Rw!`WH#zcqz5Z_dCP#Gp|F&;(M0fnT?VB9YJ^!~>FK;an+zJA21p&8K zF>eI{w|$c%vu58LH|;wgd#_vj&PVsW*>^s=KH~@bo2E=s z`{QYU(^qeQ`+5Ja{Y~?8cVGVY)BaVvo95H)>o-6A_Vf0J-A?oQ{QASEe*Wgu$Di^@ z@5}SQU%&cuk57*Z?RlCb-^OBlp2ps5!JenlWr00UqkApb^EA5mLiRj;_QUHpZ}Qaf z-Cw`@=alXOKbxLr;@?iS>1nhtU@*4nY3#nh!~IwPoW-`=oAYpadg%G|=RaiqroC%4 zw(Dtru~&m#PqV_XeEsRe>lZ(Me&3%r8r$_WpZfwvW4oTl?h9lzK7aoMe;JKWZB@>$ zr}=q3U^KStX;wBm8rbzT`li4C=7)YCqp@92^LekTi^v>(^;2I0#%&W;gd z+AB5dD7>1I*(){njsf;cjqVtby;7gOe*2g2Kc{a$J^kyqE{((XOU>{23dUjkrN*x3 zjl=d!eQv+h9G!a8vdeM#HY*x$>$I&iGqYuCe#OOaW_B?YUkt?;Gqa1K*p{kUHtH>H zOVwz%o}t*5sX~3;)|j9Vko{CiZ6!Zi=o(7t*Lm{(_pLC=yrjv zTBGaVR;|%J`L=3}Zu@Q38r`ALR;|&!`EAu2-BVzz)>K;K-%xC;*4TR)*s3+Umw~NX zqkHk&sx`W|zpYxMdkL(DVq3Px&cC79maVDu*8aC;Yc&6cVq3PxuKj(fE?WCNSh8hn z?EIUG*|Ifu{!PVf*_x$7{@o_FY>i$2n~K@8HFoX4xqtRC3$u?uym@Bi%D`qdAQKWD-6yrQ#d>leQfPQRI$9a~fQPWK~<9b049d^0gSw#Kgc zW@2`1ja~E2#O&Dm+>Wg|-hBD-eTdMnW}cM&RKIxn=JWTAw_pDB_#%VdtJg2`H2f8- z^I~l^TdDs3=g%LX*4Ce%9Q^S&|NiyWj@4{wHCtLe=vmE{RJY-u%HQl%uT#cmOFR;46%m#Ep2DkZV&3bQ3u zN@915m@TPN61!W}Y)O@ptWI}`Ql%u?C2F>$N=fW4QG`~dBzBjm*^(+Hu^S=~dNo^G z5qdRSS}mcjW=pHt(hAV4+0qKoD?qPiODjOHW=pHt(rWQ^HCtNod9`@D;`3^@v|2n} z&6ZYtUM-%k_`KrtYVmZ%=hbX!wRpOkEv;rttHsk5pI3ZdEuOCUyqYbo7Ef2Rr4^r7 zv!xZESA1U0mR7T+)#B-DwzLBEYVmY6TUyPQR-|5$dNo^Gk$N>-T9JD7`o(Itw3;og zD7{)dT~XRD$0>?wd_w6JrB{omtJ%_u(kn`@W=pHt(hAY5+0qKpD@3mly+ZT~(JMr+ zW=ktXuVzatM6VFNT0C7LdWGl}qF0DsA$o=A6{1&&ULksg=oO+>?@3Y6-v6riM0bo< zy(hZ&pXxo)z5iA3iS8J!dQWukKh=Apd;hEA6WuXd6`wq1;vb^z{v3P9AG<$Cml1Y< zj_&<$_vh%2KX!kPE+g#z9NqJ8_vbv->iA>#=je_MzK5p_^ z{Z>ou#vQxs-)gDdxMO$yo1xo{J9gJU{@aZ^cGo}t+l@Oz-|pYBJOB7^_wU%9fBd)mckIqT{@eXKcIO}e?f#vo|IR=D+x;H!{%%$)H~ihKR&MyaS*_ggce7f#;qPX(a>L)v zYUPH%o7KteQytX6LLyIHN=@OQIXx#90-wQ|GX&1&U_zndxYhQFIB@`k^g)yfTj zH>;H!{%-iYS*_ggcf;S!6nVqn4SzRNeQy%)dAM-SBq9+YN6wyxs72 z!`lsSH@w~OcEj7vYfT&8+6^>C*gM{AcxzYC*gM|Y6*RiMu`6hF$D0jrH!m-3cx$)N z{9Jiwx6r(6)A8P}q0#)~tzAQ7=MP`)9vXZ3W*5=uj&F7ojph$uH}mTaU+prQcYVs2 z4PQ5W-OR7;KANBF_-^;nX#VkaGrzVgX+G!Q{MxRhDQaK7+m$q$e|)toY3v=}?MfQW zKfZ4Gy5Z}FuXZiX&+~77ZP(Jg%f!F=^@gt-zS_++KgU15Zsyl^HO=SRZ+>l8)7Tqd zZ06T?InBFM&bRsXhOZmGZsyk;zSxQqJ`Spgc8@_J%x|v^Z_`2ciW`4cl z>xQozzS@N~n+#|_zS@N~cGo+;+J!ZC*B`#xg*ESp;j8)eW`1oK)_m^#n_t_7HFo~Z zukFGbJOAd_oB8!-e!ZDrZ|2tn-|gC(ch!3S2fo|AHTK4r1K$U}4}2f^KJb0u`@r{s z?*rclz7KpK_&)G`;QPS$f$sy~2fhz{ANW4-ec=1R_kr&N-v_=Ad>{Be@O|L>!1sag z1K$U}4}2f^KJb0u`@r{s?*rclt`A%vxIS=w;QGMzf$Ia;2d)oXAGkhnec<}Q^?~aH z*9Wc-TpzeTaDCwV!1aOa1J?(xt3vu*71A6#4yi&K-Elw_(&*j?s*pZceKg13|EiBh zcf419G}`rtud0v6-uqwm(db_Ps*grDzE*uSy5o=Pqj|@&_n+#c(XM}dRedye{_$4z z(b)ONTh&Kn*KWL3eKdCd@m4jfyffMMt7aA5_N!(UUH__CMfd(!%__S6u9{VJ{i|jb z-F{ciD!SvpYF61)gn#3YYF4p#{87y+y1ZA-D!T1g%__S6uc}pa`(IV7=(b-~tGpZ9 z{#Vs1y8W-JRdoHUY874os#-s#ekMe^srb+yAOsMYsP|waVr#?SECRqWL%esA?6v_8WgxwThj8 zA9*(Ir49PNmZrT`M18Ls#5Iyi+@#Bik*M)uc}J1^Dq8YRVnX~yZ^+$sw%~9 z{1N}Esua8PFaA|kDR$>y{Hv-`?9RXVS5>9Fv+n$he^phA-T4>)s;U&b^KX4gRi)US zfAO!XO0hft;$KyjVt4+8tlD#gw}{-~-HJOAQeRh44rAAeL;ik*M) zZ>lPtkFXE?IXqud_bBi1_j7fRqWk^SJ&Nx2q3%(1&yTuC(fxkv9!2;1Q1>Xh=SSV6 z=zc$SkDjZ0lw+??b&sNZeW-gB-TqPcD7x3Dx<}EyKGZ#mZvUuz6y57n-J|3x^!iZu zD4KtKQ1>Wy?Z*dok7DN^AJjdHUHkDt-J{s~#|L$fvZWy{f`go z9>uQx_@M4l?D`)c)ICaWhW6uwx<|1)|M;NpQSAC3AJjdH-TB7{b&q1#|M;NpQS8pY z^(S?YvYDCwxBjH=QSAEP`jfgxvFm?vvARdG>woJ{>K?_e|E)i%dz73M{crtA-J{s` zzxY_)quBMo_*mVe*!92lCv}fv*Z<;Ub&s-HoBp@{r0!Ad`rq>(b&q1#|JI|_J&Ilb zTaQxrD0cmCJ!*=Nr}$VMq~zdeKR&C26ub80vpPtzYd=1#gA}{=m*tAq4h z9i$xj_x^`ENU`(p{SS4JV&~ublsZVU^KX4h9i-U#_x^`ENXa$Q|JJ9}L5f}fTc1(~ zDR%vDeM%jq*!92nKh!~rUH@C3QU@t^*T3~Cb&#?-pX(o=)j^8g^^ec$A;s?c$7l7B zVt4)HvwBFeyZ-T6J*4DV>3@7y4=Hy2kI(8N#jgMHSzV;q^*=tVixj*5$7gksviYF? z$7gksV%PupJgrZuFO*FReP29NXDD{x*Lse6L$Uk5=2Pko#qRrB&rxqE_VfEDx9s$j z<9S|kJik|tr=J|p^OECvUF3NB$?-fdIiA-=j;Eg-&-0Swd0piAtDp0{M4#71a@o#) z$&vqZF`EB!F`EB!F`EB!F`EB!F`EB!F`EB!F`EB!F`GW}UoJ-T-+5Cs|K(yd|K(yd z|D88Q^ItAT^ItAT^WS+>viSHf7o+(v7o+*_yeXRhaxt3!axt3!uHQuSUoJ-TUmhlV z(0MNpqn-Dz+eAC>qXK0mxs~(mxs~(cfBZ@|MD=J|MD=|p!}DI(foJ58qI%s7|nlq z7|nl^%cJ=(52N`n52N|-^OvcH2-;pDzw$3yd&|FQ{yRU7=D+-l=D++)mM#B%{~?!Z>9 zcYYepf9I#s{C9mcn*YvEqxtXrG+EUAm*>&^cYYepe|a9we|a9wf8T$M=D$3T=Koio zcYQSWj`PZ1WwYUOM%kMS{B-{DeZhCNuJU>NeZlu^OaHWf z5Ig_)uI5$j+K=yQUd3L%W}9ojuitT9rK{x0Yd5ZE``-RMu4j8&TEFAET37jf?Z)+N z8|vrz#r380-Q?pt-}tW9RqWc0?`mDe&Og2{_^#GfKIb3b7kpRiDxYgNzAyNmT37v^ z_`cx#g6|4lWwUGk@m-;-*!4TUFZjOT`-1NazAyN$uvO|1@Q?2czAJ2%&z*OCU+`UF zt9;HszAJ1MyZ*;_g{@NWz;%c33R}hQ{NsDJpKQP2yTVraIp-hW6}F09|Kq#DRkFM)oL_K$!TAN}7o1;ke!=-{)7Za@^9#-|IKSZhg7XW`FF3#8{DSif&M!E> z;QWH~3(hY%zu^3W^9#-|IKSZhg7XW`FF3#8{DSif&M)}A;P-;x3w|&7z2NtP-`Uo% z{ej;LelPgF;P-;x3w|&7z2JAYMeET!S4mX z7yMrEJKMuv(fx_9v#hpIUCJ?=h4ypm$T9Qm$T9Qm$SE?Kjx_YokvG& ze>oei{pD=5_IDl~t^MU}wDy;?(c0g6^sQZWa@79LqocLIyp7iW@-|xgJCBam{?4PL zwZFWL*8a|;qqV=hy*1gdQZ=$zr*>(hOiqkb(XqW#@+;@0jj zIr@I(LbUT!E<|f*$Msv!7jxvlwsa<*YPb!l;_U|eUcX?jk=h*ft4R=uz?ve%X{+_>| zgv$dA^N|I}^XIY<=ve<*7^8bWvT#JVAG44`x4#mYR(CwTBoL$9UWKh< z@ApyID*C*xbM$>WeqL@mPs!2u>G&D#`*d8qs2-JL+keyX>Y^G{j(*?ub>sR{4Jw~& zPsg8V?dkXv?fZB9iT3?F{zUu!9e*x29e;AvpB;Ci{a(kFXusES<)VsHj()G>O0@IP zI6YcFyDx6s7pgbqbKlqfp?Xv7J^!jVMeFaz=NDC*E;o&jbL7{3bmKl!Jt?0%zwRg1 zlVa!J{iS-+<)-mnj()H4U9|Jo_%6EF?M=t6Xy3cz%te)+9QkS77Oj69w?*q;_opg2 zmz%C@!J!yj;_1LUD2*vK|9_Fb19oM3@zvEi8_BSq!*8awY(c0hfFIxK>7e;G;uY^WSkcn*YXy(fl_qjOM>_;X^L0`tP_L&40(`X#P7c zNAurtIhy~D%hCLIT#n|y<8n0r9hV<29hY--{yRQLJO5pWiFW?G4ioMCH*Sn}{u?(& zJO3Tuqn-bb?+=&8jX7$6$M$+R^>K^0cG-edTFK z_xsAzj_&uBrybqzD^L4Do_3D?zIW~ydD^k_?|zY|9edB0JniV7FL~P0Jzw&)qkF#O zX-D^b$fw*M9elJnh)|cfZKfj$QlRFY>e>?%Xf(w4-~z z?%Xf(v}4!*?iYF5v1`BkMV@x-`rrK`Pdj$)cfZKfevqe~BmeFfdD^k_?|zY|9XtQ- z7kS#T^Y4C0?iYF54|nbtdD_v=zxzd=cI?i-`$e91?5;oei#+YvoqzX>JnaX0 z+Bv%Z+%NL9W9R?Z`=#+v?EH705Uu@Ej|+TZzTwDxyC8m;|ZzkIms`elyV-+5}Z_II8dt^IvZCtCZvei^O(UB8Uh{?2Ej zwZH3^4|kpC=BWLh=SFLP*Ds^Bzw4LL+TZodXzlO%WwiEp{W4nnyM7t1{awF&kf)ua z_ILdHb_+TVHkgFNjVwZHT7XzlO3JX-rZFMqge zJeec^ofk&)-*_^b|HhNi{C8gXa1}3Hjel41!qxb9HU3@23s>Xc)%bT6FI}ou_;{O%@ug0^h@$8EKSL4~$cy=|OU5#f~ zcMaj!l1 z+HJ@?vkuRZtLbFV%3+HJ@?vkuRZtLbFV%3+HJ@?vk zuRZtLb8mdUH$LARpYM&&_r~X~>the{Idkkdu=PI7>(uA{nd52C@w`8CJg=u5&-*jS zb9~709G`ML@6R01@hQji{>?AqsD(Y?;Eeclz_>-^g1UD3VHuYKMX-Ru0?b%W?$=hvV-ttzj`6j^{-w?bp6W{ z%=V!Deb3{f`}>~9MfdkTkBjc_dmb0v@9%kBbicpnanb$$p2tP^`+FXjZAtkzPRR$1 zz1N|9!029w@&TiJ9m)rc?tS3-&h&gIN9`%Uqy3%oJKEnVzoUQsJK9lx$F3daceHkt z-`O@*JIcFge#^UP{aD^b>&J2~n%{CRy7#d>xopGg@4253_p`jVY|qNS`&nLF?E2CD z?730w{JWp!wZ-mxG#-!kJsOWk?@w`1j@sXNJln$Z-*`OQ_ia2Lt^JM1qxo+<9_>16 zJRYt88;?ip|Hk9d{5KxYcC`HWeS&EI8;?ix-*`Nl|Hk9d{5Kwt=D+cHH2;mqqxo+< zo^5dXZ#*8&zc|2ive;dJjmM*1e_g+h*8g3nme&@$_8W)gwZ+cAaadkkwgJ|DnW+_P0E_Z0oH3;=3unlP8zYd%aKbojkeNwO@QE zPcHUe?^ApyPcC-t7vIT~OPUA&;yZbAvFm^FojkeNd%sQbojkeN^}qN|o?Ps`-=_FZ zo?OyO^uPE{o?PtuUwkJ|E_VIj`D(QGcfK0Uf9I>w`oHtlXzlNOb-`ck<-&IsbSkPcC--k9YFqV&@<4acqdOTcKwfg^5kOIe%zBM z7rXxN{4Uz{*ZE!2c=+%9E?WP0ei!Zf>-;X7|IY8CUH_fmMQeZOchUTJ{VkgRzBicd z%K6710(xtQ?@8!wGuKjo~PcC-t$9s8lv1>ox%ae;;`|)0$ zT(*OE|KYtnx!9e5yq6~zyZaCC<;lhF{Nuemx!B!*crQ;b>0tU9*X6^-uAgyTK3weD zh2!$yV%IJlm;aVDGk+hqCvMAk%jepG+Y_g!`O?Jc1lqk$%!?)-PduJ@Jk5&|aQb=I z1r8_BVs~BOaKO**y1>nxu-b7m4W;+5KIbLCdH1{D{q7(B?caX)?|%0W`PX;(|9{B; qpVUA6qB7|NS5TcaQ(||NLKoM-F8G diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 619c26eec..347ab7e9c 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -65,11 +65,15 @@ void set_default_settings(Settings *settings) settings->setDefault("enable_register_confirmation", "true"); // Cheat Menu - settings->setDefault("cheat_menu_font", "FM_Mono"); - settings->setDefault("m_bg_color_alpha", "173"); - settings->setDefault("m_active_bg_color_alpha", "210"); - settings->setDefault("m_font_color_alpha", "195"); - settings->setDefault("m_selected_font_color_alpha", "235"); + settings->setDefault("cheat_menu_font", "FM_Standard"); + settings->setDefault("cheat_menu_bg_color", "(255, 145, 88)"); + settings->setDefault("cheat_menu_bg_color_alpha", "192"); + settings->setDefault("cheat_menu_active_bg_color", "(255, 87, 53)"); + settings->setDefault("cheat_menu_active_bg_color_alpha", "192"); + settings->setDefault("cheat_menu_font_color", "(0, 0, 0)"); + settings->setDefault("cheat_menu_font_color_alpha", "255"); + settings->setDefault("cheat_menu_selected_font_color", "(255, 252, 88)"); + settings->setDefault("cheat_menu_selected_font_color_alpha", "255"); // Cheats settings->setDefault("xray", "false"); diff --git a/src/gui/cheatMenu.cpp b/src/gui/cheatMenu.cpp index 5a7f52477..e66fbc250 100644 --- a/src/gui/cheatMenu.cpp +++ b/src/gui/cheatMenu.cpp @@ -45,32 +45,29 @@ FontMode CheatMenu::fontStringToEnum(std::string str) { CheatMenu::CheatMenu(Client *client) : m_client(client) { FontMode fontMode = fontStringToEnum(g_settings->get("cheat_menu_font")); - irr::core::vector3df bg_color; - irr::core::vector3df active_bg_color; - irr::core::vector3df font_color; - irr::core::vector3df selected_font_color; + v3f bg_color, active_bg_color, font_color, selected_font_color; - g_settings->getV3FNoEx("m_bg_color", bg_color); - g_settings->getV3FNoEx("m_active_bg_color", active_bg_color); - g_settings->getV3FNoEx("m_font_color", font_color); - g_settings->getV3FNoEx("m_selected_font_color", selected_font_color); + bg_color = g_settings->getV3F("cheat_menu_bg_color"); + active_bg_color = g_settings->getV3F("cheat_menu_active_bg_color"); + font_color = g_settings->getV3F("cheat_menu_font_color"); + selected_font_color = g_settings->getV3F("cheat_menu_selected_font_color"); - m_bg_color = video::SColor(g_settings->getU32("m_bg_color_alpha"), + m_bg_color = video::SColor(g_settings->getU32("cheat_menu_bg_color_alpha"), bg_color.X, bg_color.Y, bg_color.Z); - m_active_bg_color = video::SColor(g_settings->getU32("m_active_bg_color_alpha"), + m_active_bg_color = video::SColor(g_settings->getU32("cheat_menu_active_bg_color_alpha"), active_bg_color.X, active_bg_color.Y, active_bg_color.Z); - m_font_color = video::SColor(g_settings->getU32("m_font_color_alpha"), + m_font_color = video::SColor(g_settings->getU32("cheat_menu_font_color_alpha"), font_color.X, font_color.Y, font_color.Z); - m_selected_font_color = video::SColor(g_settings->getU32("m_selected_font_color_alpha"), + m_selected_font_color = video::SColor(g_settings->getU32("cheat_menu_selected_font_color_alpha"), selected_font_color.X, selected_font_color.Y, selected_font_color.Z); m_font = g_fontengine->getFont(FONT_SIZE_UNSPECIFIED, fontMode); if (!m_font) { - errorstream << "CheatMenu: Unable to load fallback font" << std::endl; + errorstream << "CheatMenu: Unable to load font" << std::endl; } else { core::dimension2d dim = m_font->getDimension(L"M"); m_fontsize = v2u32(dim.Width, dim.Height); @@ -80,41 +77,29 @@ CheatMenu::CheatMenu(Client *client) : m_client(client) m_fontsize.Y = MYMAX(m_fontsize.Y, 1); } -void CheatMenu::drawEntry(video::IVideoDriver *driver, std::string name, - std::size_t column_align_index, std::size_t cheat_entry_index, - bool is_selected, bool is_enabled, CheatMenuEntryType entry_type) +void CheatMenu::drawEntry(video::IVideoDriver *driver, std::string name, int number, + bool selected, bool active, CheatMenuEntryType entry_type) { int x = m_gap, y = m_gap, width = m_entry_width, height = m_entry_height; video::SColor *bgcolor = &m_bg_color, *fontcolor = &m_font_color; - - // Align with correct column. - x += m_gap + column_align_index * (m_entry_width + m_gap); - - if (is_selected) - fontcolor = &m_selected_font_color; - if (is_enabled) + if (entry_type == CHEAT_MENU_ENTRY_TYPE_HEAD) { bgcolor = &m_active_bg_color; - - switch (entry_type) - { - case CHEAT_MENU_ENTRY_TYPE_HEAD: height = m_head_height; - break; - case CHEAT_MENU_ENTRY_TYPE_CATEGORY: - y += m_head_height + m_gap; - break; - case CHEAT_MENU_ENTRY_TYPE_ENTRY: - y += m_head_height + (cheat_entry_index + 1) * (m_entry_height + m_gap); - break; - default: - // TODO log an error or something. - break; + } else { + bool is_category = entry_type == CHEAT_MENU_ENTRY_TYPE_CATEGORY; + y += m_gap + m_head_height + + (number + (is_category ? 0 : m_selected_category)) * + (m_entry_height + m_gap); + x += (is_category ? 0 : m_gap + m_entry_width); + if (active) + bgcolor = &m_active_bg_color; + if (selected) + fontcolor = &m_selected_font_color; } - driver->draw2DRectangle(*bgcolor, core::rect(x, y, x + width, y + height)); - if (is_selected) + if (selected) driver->draw2DRectangleOutline( - core::rect(x - 2, y - 2, x + width + 1, y + height + 1), + core::rect(x - 1, y - 1, x + width, y + height), *fontcolor); int fx = x + 5, fy = y + (height - m_fontsize.Y) / 2; core::rect fontbounds( @@ -124,26 +109,24 @@ void CheatMenu::drawEntry(video::IVideoDriver *driver, std::string name, void CheatMenu::draw(video::IVideoDriver *driver, bool show_debug) { - ClientScripting *script{ getScript() }; - if (!script || !script->m_cheats_loaded) - return; + CHEAT_MENU_GET_SCRIPTPTR - // Draw menu header if debug info is not being drawn. if (!show_debug) - drawEntry(driver, "Dragonfireclient", 0, 0, false, false, - CHEAT_MENU_ENTRY_TYPE_HEAD); - + drawEntry(driver, "Dragonfireclient", 0, false, false, + CHEAT_MENU_ENTRY_TYPE_HEAD); int category_count = 0; - for (const auto &menu_item : script->m_cheat_categories) { + for (auto category = script->m_cheat_categories.begin(); + category != script->m_cheat_categories.end(); category++) { bool is_selected = category_count == m_selected_category; - drawEntry(driver, menu_item->m_name, category_count, 0, is_selected, - false, CHEAT_MENU_ENTRY_TYPE_CATEGORY); + drawEntry(driver, (*category)->m_name, category_count, is_selected, false, + CHEAT_MENU_ENTRY_TYPE_CATEGORY); if (is_selected && m_cheat_layer) { int cheat_count = 0; - for (const auto &sub_menu_item : menu_item->m_cheats) { - drawEntry(driver, sub_menu_item->m_name, category_count, - cheat_count, cheat_count == m_selected_cheat, - sub_menu_item->is_enabled()); + for (auto cheat = (*category)->m_cheats.begin(); + cheat != (*category)->m_cheats.end(); cheat++) { + drawEntry(driver, (*cheat)->m_name, cheat_count, + cheat_count == m_selected_cheat, + (*cheat)->is_enabled()); cheat_count++; } } @@ -217,57 +200,47 @@ void CheatMenu::drawHUD(video::IVideoDriver *driver, double dtime) } } -void CheatMenu::selectLeft() +void CheatMenu::selectUp() { CHEAT_MENU_GET_SCRIPTPTR - int max = script->m_cheat_categories.size() - 1; - int *selected = &m_selected_category; + int max = (m_cheat_layer ? script->m_cheat_categories[m_selected_category] + ->m_cheats.size() + : script->m_cheat_categories.size()) - + 1; + int *selected = m_cheat_layer ? &m_selected_cheat : &m_selected_category; --*selected; if (*selected < 0) *selected = max; } -void CheatMenu::selectRight() -{ - CHEAT_MENU_GET_SCRIPTPTR - - int max = script->m_cheat_categories.size() - 1; - int *selected = &m_selected_category; - ++*selected; - if (*selected > max) - *selected = 0; -} - void CheatMenu::selectDown() { CHEAT_MENU_GET_SCRIPTPTR - m_cheat_layer = true; - - int max = script->m_cheat_categories[m_selected_category]->m_cheats.size(); - int *selected = &m_selected_cheat; + int max = (m_cheat_layer ? script->m_cheat_categories[m_selected_category] + ->m_cheats.size() + : script->m_cheat_categories.size()) - + 1; + int *selected = m_cheat_layer ? &m_selected_cheat : &m_selected_category; ++*selected; - if (*selected > max) { - *selected = 1; - } + if (*selected > max) + *selected = 0; } -void CheatMenu::selectUp() +void CheatMenu::selectRight() { - if (!m_cheat_layer) { + if (m_cheat_layer) return; - } + m_cheat_layer = true; + m_selected_cheat = 0; +} - CHEAT_MENU_GET_SCRIPTPTR - - int *selected = &m_selected_cheat; - --*selected; - - if (*selected < 0) { - m_cheat_layer = false; - *selected = 1; - } +void CheatMenu::selectLeft() +{ + if (!m_cheat_layer) + return; + m_cheat_layer = false; } void CheatMenu::selectConfirm() diff --git a/src/gui/cheatMenu.h b/src/gui/cheatMenu.h index bedd7e048..8be73c483 100644 --- a/src/gui/cheatMenu.h +++ b/src/gui/cheatMenu.h @@ -51,9 +51,8 @@ public: void drawHUD(video::IVideoDriver *driver, double dtime); - void drawEntry(video::IVideoDriver *driver, std::string name, - std::size_t column_align_index, std::size_t cheat_entry_index, - bool is_selected, bool is_enabled, + void drawEntry(video::IVideoDriver *driver, std::string name, int number, + bool selected, bool active, CheatMenuEntryType entry_type = CHEAT_MENU_ENTRY_TYPE_ENTRY); void selectUp(); @@ -67,15 +66,15 @@ private: int m_selected_cheat = 0; int m_selected_category = 0; - int m_head_height = 20; - int m_entry_height = 20; - int m_entry_width = 150; + int m_head_height = 50; + int m_entry_height = 40; + int m_entry_width = 200; int m_gap = 3; - video::SColor m_bg_color = video::SColor(173, 45, 45, 68); - video::SColor m_active_bg_color = video::SColor(210, 0, 0, 0); - video::SColor m_font_color = video::SColor(195, 255, 255, 255); - video::SColor m_selected_font_color = video::SColor(235, 255, 255, 255); + video::SColor m_bg_color = video::SColor(192, 255, 145, 88); + video::SColor m_active_bg_color = video::SColor(192, 255, 87, 53); + video::SColor m_font_color = video::SColor(255, 0, 0, 0); + video::SColor m_selected_font_color = video::SColor(255, 255, 252, 88); FontMode fontStringToEnum(std::string str); From c9221730dd08ce6b04bbbfc7a66d2bdee45b4367 Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein Date: Wed, 4 Nov 2020 15:48:00 +0100 Subject: [PATCH 180/266] Updated Cheat Menu Color Design --- src/defaultsettings.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 347ab7e9c..a00d39c30 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -66,14 +66,14 @@ void set_default_settings(Settings *settings) // Cheat Menu settings->setDefault("cheat_menu_font", "FM_Standard"); - settings->setDefault("cheat_menu_bg_color", "(255, 145, 88)"); - settings->setDefault("cheat_menu_bg_color_alpha", "192"); - settings->setDefault("cheat_menu_active_bg_color", "(255, 87, 53)"); - settings->setDefault("cheat_menu_active_bg_color_alpha", "192"); - settings->setDefault("cheat_menu_font_color", "(0, 0, 0)"); - settings->setDefault("cheat_menu_font_color_alpha", "255"); - settings->setDefault("cheat_menu_selected_font_color", "(255, 252, 88)"); - settings->setDefault("cheat_menu_selected_font_color_alpha", "255"); + settings->setDefault("cheat_menu_bg_color", "(45, 45, 68)"); + settings->setDefault("cheat_menu_bg_color_alpha", "173"); + settings->setDefault("cheat_menu_active_bg_color", "(0, 0, 0)"); + settings->setDefault("cheat_menu_active_bg_color_alpha", "210"); + settings->setDefault("cheat_menu_font_color", "(255, 255, 255)"); + settings->setDefault("cheat_menu_font_color_alpha", "195"); + settings->setDefault("cheat_menu_selected_font_color", "(255, 255, 255)"); + settings->setDefault("cheat_menu_selected_font_color_alpha", "235"); // Cheats settings->setDefault("xray", "false"); From 1145b05ea0bda87dc0827821385810eced08f774 Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein Date: Wed, 4 Nov 2020 15:52:37 +0100 Subject: [PATCH 181/266] Updated Credits --- builtin/mainmenu/tab_credits.lua | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/builtin/mainmenu/tab_credits.lua b/builtin/mainmenu/tab_credits.lua index 4f9f835f7..274c8faa5 100644 --- a/builtin/mainmenu/tab_credits.lua +++ b/builtin/mainmenu/tab_credits.lua @@ -17,10 +17,12 @@ -------------------------------------------------------------------------------- local dragonfire_team = { - "Elias Fleckenstein", - "cora", - "system32", - "DerZombiiie", + "Elias Fleckenstein [Main Developer]", + "cora [Core Developer]", + "emilia [Core Developer]", + "oneplustwo [Developer]", + "joshia_wi [Developer]", + "DerZombiiie [User Support]", } local core_developers = { From ad148587dcf5244c2d2011dba339786c765c54c4 Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein Date: Wed, 4 Nov 2020 16:19:54 +0100 Subject: [PATCH 182/266] Make Lint Happy --- src/activeobject.h | 60 +- src/ban.cpp | 24 +- src/chat.cpp | 185 +- src/chat.h | 43 +- src/chat_interface.h | 51 +- src/client/camera.cpp | 334 +-- src/client/camera.h | 94 +- src/client/client.cpp | 788 +++--- src/client/client.h | 275 +- src/client/clientenvironment.cpp | 198 +- src/client/clientenvironment.h | 39 +- src/client/clientlauncher.cpp | 131 +- src/client/clientlauncher.h | 2 +- src/client/clientmap.cpp | 238 +- src/client/clientmap.h | 50 +- src/client/clientmedia.cpp | 193 +- src/client/clientmedia.h | 43 +- src/client/clientobject.cpp | 17 +- src/client/clientobject.h | 27 +- src/client/clouds.cpp | 363 ++- src/client/clouds.h | 42 +- src/client/content_cao.cpp | 796 +++--- src/client/content_cao.h | 97 +- src/client/content_cso.cpp | 36 +- src/client/content_cso.h | 4 +- src/client/content_mapblock.cpp | 965 ++++--- src/client/content_mapblock.h | 61 +- src/client/filecache.cpp | 27 +- src/client/fontengine.cpp | 152 +- src/client/fontengine.h | 54 +- src/client/game.cpp | 1386 +++++----- src/client/game.h | 318 ++- src/client/gameui.cpp | 156 +- src/client/gameui.h | 5 +- src/client/guiscalingfilter.cpp | 68 +- src/client/guiscalingfilter.h | 11 +- src/client/hud.cpp | 642 ++--- src/client/hud.h | 32 +- src/client/imagefilters.cpp | 195 +- src/client/imagefilters.h | 3 +- src/client/inputhandler.h | 22 +- src/client/joystick_controller.cpp | 96 +- src/client/joystick_controller.h | 62 +- src/client/keycode.cpp | 371 ++- src/client/keys.h | 2 +- src/client/localplayer.cpp | 253 +- src/client/localplayer.h | 40 +- src/client/mapblock_mesh.cpp | 645 ++--- src/client/mapblock_mesh.h | 47 +- src/client/mesh.cpp | 1008 ++++---- src/client/mesh.h | 29 +- src/client/mesh_generator_thread.cpp | 113 +- src/client/minimap.cpp | 331 +-- src/client/minimap.h | 34 +- src/client/particles.cpp | 299 +-- src/client/particles.h | 82 +- src/client/render/core.cpp | 37 +- src/client/render/interlaced.cpp | 4 +- src/client/render/plain.cpp | 4 +- src/client/render/sidebyside.cpp | 7 +- src/client/render/stereo.cpp | 4 +- src/client/renderingengine.cpp | 67 +- src/client/renderingengine.h | 6 +- src/client/shader.cpp | 379 ++- src/client/shader.h | 107 +- src/client/sky.cpp | 551 ++-- src/client/sky.h | 55 +- src/client/sound_openal.cpp | 229 +- src/client/tile.cpp | 1016 ++++---- src/client/tile.h | 94 +- src/client/wieldmesh.cpp | 365 +-- src/clientiface.cpp | 228 +- src/clientiface.h | 156 +- src/collision.cpp | 476 ++-- src/collision.h | 23 +- src/config.h | 33 +- src/content_mapnode.cpp | 88 +- src/content_nodemeta.cpp | 113 +- src/content_nodemeta.h | 5 +- src/convert_json.cpp | 12 +- src/convert_json.h | 4 +- src/craftdef.cpp | 368 +-- src/craftdef.h | 139 +- src/database/database-dummy.cpp | 2 - src/database/database-files.cpp | 9 +- src/database/database-leveldb.cpp | 67 +- src/database/database-postgresql.cpp | 429 ++-- src/database/database-postgresql.h | 36 +- src/database/database-redis.cpp | 85 +- src/database/database-sqlite3.cpp | 375 +-- src/database/database-sqlite3.h | 22 +- src/database/database.cpp | 9 +- src/daynightratio.h | 18 +- src/debug.cpp | 55 +- src/debug.h | 72 +- src/defaultsettings.cpp | 53 +- src/emerge.cpp | 254 +- src/emerge.h | 66 +- src/environment.cpp | 211 +- src/exceptions.h | 104 +- src/face_position_cache.cpp | 63 +- src/face_position_cache.h | 3 +- src/filesys.cpp | 282 +-- src/filesys.h | 10 +- src/gamedef.h | 38 +- src/gettext.cpp | 123 +- src/gettext.h | 26 +- src/gui/StyleSpec.h | 31 +- src/gui/cheatMenu.cpp | 79 +- src/gui/cheatMenu.h | 13 +- src/gui/guiAnimatedImage.cpp | 16 +- src/gui/guiAnimatedImage.h | 9 +- src/gui/guiBackgroundImage.cpp | 15 +- src/gui/guiBackgroundImage.h | 5 +- src/gui/guiBox.cpp | 10 +- src/gui/guiBox.h | 2 +- src/gui/guiButton.cpp | 466 ++-- src/gui/guiButton.h | 245 +- src/gui/guiButtonImage.cpp | 20 +- src/gui/guiButtonImage.h | 4 +- src/gui/guiButtonItemImage.cpp | 11 +- src/gui/guiButtonItemImage.h | 4 +- src/gui/guiChatConsole.cpp | 389 ++- src/gui/guiChatConsole.h | 25 +- src/gui/guiConfirmRegistration.cpp | 24 +- src/gui/guiEditBoxWithScrollbar.cpp | 584 +++-- src/gui/guiEditBoxWithScrollbar.h | 35 +- src/gui/guiEngine.cpp | 208 +- src/gui/guiEngine.h | 79 +- src/gui/guiFormSpecMenu.cpp | 2041 ++++++++------- src/gui/guiFormSpecMenu.h | 207 +- src/gui/guiHyperText.cpp | 159 +- src/gui/guiHyperText.h | 7 +- src/gui/guiInventoryList.cpp | 108 +- src/gui/guiInventoryList.h | 42 +- src/gui/guiItemImage.cpp | 11 +- src/gui/guiItemImage.h | 9 +- src/gui/guiKeyChangeMenu.cpp | 289 ++- src/gui/guiMainMenu.h | 6 +- src/gui/guiPasswordChange.cpp | 37 +- src/gui/guiPathSelectMenu.cpp | 14 +- src/gui/guiScrollBar.cpp | 17 +- src/gui/guiSkin.cpp | 473 ++-- src/gui/guiSkin.h | 598 +++-- src/gui/guiTable.cpp | 361 ++- src/gui/guiTable.h | 35 +- src/gui/guiVolumeChange.cpp | 58 +- src/gui/guiVolumeChange.h | 5 +- src/gui/intlGUIEditBox.cpp | 1130 ++++----- src/gui/intlGUIEditBox.h | 282 +-- src/gui/mainmenumanager.h | 48 +- src/gui/modalMenu.h | 8 +- src/gui/touchscreengui.cpp | 676 ++--- src/httpfetch.cpp | 226 +- src/hud.cpp | 69 +- src/hud.h | 37 +- src/inventory.cpp | 320 ++- src/inventory.h | 91 +- src/inventorymanager.cpp | 313 ++- src/inventorymanager.h | 77 +- src/irrlicht_changes/CGUITTFont.cpp | 711 +++--- src/irrlicht_changes/CGUITTFont.h | 687 ++--- src/irrlicht_changes/irrUString.h | 2672 +++++++++----------- src/irrlicht_changes/static_text.cpp | 319 ++- src/irrlicht_changes/static_text.h | 323 ++- src/irrlichttypes.h | 38 +- src/itemdef.cpp | 151 +- src/itemdef.h | 70 +- src/itemstackmetadata.cpp | 7 +- src/light.cpp | 30 +- src/log.cpp | 110 +- src/log.h | 84 +- src/main.cpp | 373 +-- src/map.cpp | 1040 ++++---- src/map.h | 116 +- src/map_settings_manager.cpp | 49 +- src/map_settings_manager.h | 17 +- src/mapblock.cpp | 431 ++-- src/mapblock.h | 239 +- src/mapgen/cavegen.cpp | 430 ++-- src/mapgen/cavegen.h | 25 +- src/mapgen/dungeongen.cpp | 449 ++-- src/mapgen/dungeongen.h | 24 +- src/mapgen/mapgen.cpp | 682 +++-- src/mapgen/mapgen.h | 60 +- src/mapgen/mapgen_carpathian.cpp | 463 ++-- src/mapgen/mapgen_carpathian.h | 33 +- src/mapgen/mapgen_flat.cpp | 205 +- src/mapgen/mapgen_fractal.cpp | 192 +- src/mapgen/mapgen_fractal.h | 4 +- src/mapgen/mapgen_singlenode.cpp | 31 +- src/mapgen/mapgen_v5.cpp | 168 +- src/mapgen/mapgen_v6.cpp | 943 ++++--- src/mapgen/mapgen_v6.h | 26 +- src/mapgen/mapgen_v7.cpp | 427 ++-- src/mapgen/mapgen_v7.h | 16 +- src/mapgen/mapgen_valleys.cpp | 476 ++-- src/mapgen/mapgen_valleys.h | 19 +- src/mapgen/mg_biome.cpp | 175 +- src/mapgen/mg_biome.h | 65 +- src/mapgen/mg_decoration.cpp | 296 +-- src/mapgen/mg_decoration.h | 45 +- src/mapgen/mg_ore.cpp | 421 ++- src/mapgen/mg_ore.h | 91 +- src/mapgen/mg_schematic.cpp | 229 +- src/mapgen/mg_schematic.h | 55 +- src/mapgen/treegen.cpp | 642 +++-- src/mapgen/treegen.h | 104 +- src/mapnode.cpp | 424 ++-- src/mapnode.h | 80 +- src/mapsector.cpp | 18 +- src/mapsector.h | 16 +- src/metadata.h | 17 +- src/network/address.cpp | 2 +- src/network/clientopcodes.cpp | 414 +-- src/network/clientopcodes.h | 11 +- src/network/clientpackethandler.cpp | 610 +++-- src/network/connection.cpp | 525 ++-- src/network/connection.h | 366 +-- src/network/connectionthreads.cpp | 781 +++--- src/network/networkpacket.cpp | 132 +- src/network/networkprotocol.h | 149 +- src/network/serveropcodes.cpp | 396 +-- src/network/serveropcodes.h | 11 +- src/network/serverpackethandler.cpp | 745 +++--- src/nodedef.cpp | 544 ++-- src/nodedef.h | 103 +- src/nodemetadata.cpp | 30 +- src/nodemetadata.h | 27 +- src/nodetimer.cpp | 24 +- src/nodetimer.h | 40 +- src/noise.cpp | 273 +- src/noise.h | 159 +- src/objdef.cpp | 42 +- src/objdef.h | 15 +- src/object_properties.cpp | 26 +- src/particles.cpp | 22 +- src/particles.h | 15 +- src/pathfinder.cpp | 716 +++--- src/pathfinder.h | 22 +- src/player.cpp | 63 +- src/player.h | 68 +- src/porting.cpp | 210 +- src/porting.h | 195 +- src/porting_android.cpp | 134 +- src/porting_android.h | 7 +- src/profiler.h | 14 +- src/raycast.cpp | 56 +- src/raycast.h | 8 +- src/reflowscan.cpp | 63 +- src/reflowscan.h | 3 +- src/remoteplayer.cpp | 85 +- src/rollback.cpp | 617 +++-- src/rollback.h | 82 +- src/rollback_interface.cpp | 81 +- src/rollback_interface.h | 40 +- src/script/common/c_content.cpp | 824 +++--- src/script/common/c_content.h | 152 +- src/script/common/c_converter.cpp | 150 +- src/script/common/c_converter.h | 124 +- src/script/common/c_internal.cpp | 24 +- src/script/common/c_internal.h | 35 +- src/script/common/c_types.cpp | 6 +- src/script/common/c_types.h | 6 +- src/script/cpp_api/s_async.cpp | 44 +- src/script/cpp_api/s_async.h | 14 +- src/script/cpp_api/s_base.cpp | 116 +- src/script/cpp_api/s_base.h | 73 +- src/script/cpp_api/s_client.cpp | 15 +- src/script/cpp_api/s_entity.cpp | 53 +- src/script/cpp_api/s_entity.h | 24 +- src/script/cpp_api/s_env.cpp | 43 +- src/script/cpp_api/s_env.h | 4 +- src/script/cpp_api/s_internal.h | 30 +- src/script/cpp_api/s_inventory.cpp | 84 +- src/script/cpp_api/s_inventory.h | 22 +- src/script/cpp_api/s_item.cpp | 44 +- src/script/cpp_api/s_item.h | 28 +- src/script/cpp_api/s_mainmenu.h | 3 +- src/script/cpp_api/s_node.cpp | 59 +- src/script/cpp_api/s_node.h | 18 +- src/script/cpp_api/s_nodemeta.cpp | 98 +- src/script/cpp_api/s_nodemeta.h | 24 +- src/script/cpp_api/s_player.cpp | 66 +- src/script/cpp_api/s_player.h | 41 +- src/script/cpp_api/s_security.cpp | 360 ++- src/script/cpp_api/s_security.h | 30 +- src/script/cpp_api/s_server.cpp | 27 +- src/script/cpp_api/s_server.h | 20 +- src/script/lua_api/l_areastore.cpp | 55 +- src/script/lua_api/l_base.cpp | 24 +- src/script/lua_api/l_base.h | 39 +- src/script/lua_api/l_client.cpp | 38 +- src/script/lua_api/l_clientobject.cpp | 7 +- src/script/lua_api/l_clientobject.h | 2 +- src/script/lua_api/l_craft.cpp | 188 +- src/script/lua_api/l_craft.h | 13 +- src/script/lua_api/l_env.cpp | 292 +-- src/script/lua_api/l_env.h | 84 +- src/script/lua_api/l_http.cpp | 24 +- src/script/lua_api/l_http.h | 6 +- src/script/lua_api/l_internal.h | 41 +- src/script/lua_api/l_inventory.cpp | 119 +- src/script/lua_api/l_inventory.h | 24 +- src/script/lua_api/l_inventoryaction.cpp | 45 +- src/script/lua_api/l_inventoryaction.h | 34 +- src/script/lua_api/l_item.cpp | 112 +- src/script/lua_api/l_item.h | 20 +- src/script/lua_api/l_itemstackmeta.cpp | 43 +- src/script/lua_api/l_itemstackmeta.h | 10 +- src/script/lua_api/l_localplayer.cpp | 33 +- src/script/lua_api/l_mainmenu.cpp | 349 ++- src/script/lua_api/l_mainmenu.h | 19 +- src/script/lua_api/l_mapgen.cpp | 591 +++-- src/script/lua_api/l_mapgen.h | 2 +- src/script/lua_api/l_metadata.cpp | 14 +- src/script/lua_api/l_minimap.cpp | 40 +- src/script/lua_api/l_nodemeta.cpp | 89 +- src/script/lua_api/l_nodemeta.h | 8 +- src/script/lua_api/l_nodetimer.cpp | 46 +- src/script/lua_api/l_noise.cpp | 120 +- src/script/lua_api/l_object.cpp | 514 ++-- src/script/lua_api/l_object.h | 13 +- src/script/lua_api/l_particles.cpp | 47 +- src/script/lua_api/l_particles.h | 3 +- src/script/lua_api/l_particles_local.cpp | 38 +- src/script/lua_api/l_rollback.cpp | 13 +- src/script/lua_api/l_rollback.h | 3 +- src/script/lua_api/l_server.cpp | 90 +- src/script/lua_api/l_settings.cpp | 105 +- src/script/lua_api/l_sound.cpp | 1 - src/script/lua_api/l_storage.cpp | 43 +- src/script/lua_api/l_util.cpp | 35 +- src/script/lua_api/l_vmanip.cpp | 98 +- src/script/scripting_client.cpp | 3 +- src/script/scripting_client.h | 11 +- src/script/scripting_mainmenu.cpp | 10 +- src/script/scripting_mainmenu.h | 7 +- src/script/scripting_server.cpp | 12 +- src/script/scripting_server.h | 21 +- src/serialization.cpp | 180 +- src/serialization.h | 5 +- src/server.cpp | 1146 ++++----- src/server.h | 219 +- src/server/luaentity_sao.cpp | 198 +- src/server/player_sao.cpp | 123 +- src/server/serveractiveobject.cpp | 11 +- src/server/serveractiveobject.h | 126 +- src/serverenvironment.cpp | 901 ++++--- src/serverenvironment.h | 122 +- src/serverlist.cpp | 79 +- src/serverlist.h | 10 +- src/settings.cpp | 237 +- src/settings.h | 72 +- src/settings_translation_file.cpp | 1014 ++++++-- src/staticobject.cpp | 27 +- src/staticobject.h | 21 +- src/terminal_chat_console.cpp | 330 ++- src/terminal_chat_console.h | 34 +- src/texture_override.cpp | 42 +- src/threading/event.cpp | 1 - src/threading/semaphore.cpp | 72 +- src/threading/thread.cpp | 86 +- src/threading/thread.h | 25 +- src/tileanimation.cpp | 22 +- src/tileanimation.h | 4 +- src/tool.cpp | 38 +- src/tool.h | 54 +- src/translation.cpp | 16 +- src/unittest/test.cpp | 89 +- src/unittest/test.h | 120 +- src/unittest/test_areastore.cpp | 35 +- src/unittest/test_collision.cpp | 293 ++- src/unittest/test_compression.cpp | 67 +- src/unittest/test_connection.cpp | 59 +- src/unittest/test_filepath.cpp | 117 +- src/unittest/test_inventory.cpp | 84 +- src/unittest/test_keycode.cpp | 6 +- src/unittest/test_map_settings_manager.cpp | 39 +- src/unittest/test_noderesolver.cpp | 47 +- src/unittest/test_noise.cpp | 1293 ++++++++-- src/unittest/test_random.cpp | 667 ++++- src/unittest/test_schematic.cpp | 242 +- src/unittest/test_serialization.cpp | 307 ++- src/unittest/test_settings.cpp | 282 +-- src/unittest/test_socket.cpp | 21 +- src/unittest/test_threading.cpp | 21 +- src/unittest/test_utilities.cpp | 197 +- src/unittest/test_voxelalgorithms.cpp | 25 +- src/unittest/test_voxelmanipulator.cpp | 35 +- src/util/areastore.cpp | 90 +- src/util/areastore.h | 57 +- src/util/auth.cpp | 45 +- src/util/auth.h | 23 +- src/util/base64.cpp | 74 +- src/util/base64.h | 6 +- src/util/basic_macros.h | 15 +- src/util/container.h | 56 +- src/util/directiontables.cpp | 171 +- src/util/directiontables.h | 26 +- src/util/enriched_string.cpp | 15 +- src/util/enriched_string.h | 31 +- src/util/md32_common.h | 461 ++-- src/util/numeric.cpp | 48 +- src/util/numeric.h | 137 +- src/util/pointedthing.cpp | 64 +- src/util/pointedthing.h | 5 +- src/util/pointer.h | 103 +- src/util/quicktune.cpp | 22 +- src/util/quicktune.h | 33 +- src/util/quicktune_shortcutter.h | 26 +- src/util/serialize.cpp | 479 ++-- src/util/serialize.h | 278 +- src/util/sha1.cpp | 117 +- src/util/srp.cpp | 354 +-- src/util/srp.h | 21 +- src/util/strfnd.h | 16 +- src/util/string.cpp | 419 +-- src/util/string.h | 149 +- src/util/thread.h | 75 +- src/util/timetaker.cpp | 16 +- src/util/timetaker.h | 11 +- src/version.cpp | 32 +- src/voxel.cpp | 164 +- src/voxel.h | 205 +- src/voxelalgorithms.cpp | 839 +++--- src/voxelalgorithms.h | 27 +- 427 files changed, 36413 insertions(+), 35210 deletions(-) diff --git a/src/activeobject.h b/src/activeobject.h index 85e160d10..50d3525e9 100644 --- a/src/activeobject.h +++ b/src/activeobject.h @@ -23,39 +23,41 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irr_v3d.h" #include - -enum ActiveObjectType { +enum ActiveObjectType +{ ACTIVEOBJECT_TYPE_INVALID = 0, ACTIVEOBJECT_TYPE_TEST = 1, -// Obsolete stuff + // Obsolete stuff ACTIVEOBJECT_TYPE_ITEM = 2, -// ACTIVEOBJECT_TYPE_RAT = 3, -// ACTIVEOBJECT_TYPE_OERKKI1 = 4, -// ACTIVEOBJECT_TYPE_FIREFLY = 5, + // ACTIVEOBJECT_TYPE_RAT = 3, + // ACTIVEOBJECT_TYPE_OERKKI1 = 4, + // ACTIVEOBJECT_TYPE_FIREFLY = 5, ACTIVEOBJECT_TYPE_MOBV2 = 6, -// End obsolete stuff + // End obsolete stuff ACTIVEOBJECT_TYPE_LUAENTITY = 7, -// Special type, not stored as a static object + // Special type, not stored as a static object ACTIVEOBJECT_TYPE_PLAYER = 100, -// Special type, only exists as CAO + // Special type, only exists as CAO ACTIVEOBJECT_TYPE_GENERIC = 101, }; // Other types are defined in content_object.h struct ActiveObjectMessage { - ActiveObjectMessage(u16 id_, bool reliable_=true, const std::string &data_ = "") : - id(id_), - reliable(reliable_), - datastring(data_) - {} + ActiveObjectMessage( + u16 id_, bool reliable_ = true, const std::string &data_ = "") : + id(id_), + reliable(reliable_), datastring(data_) + { + } u16 id; bool reliable; std::string datastring; }; -enum ActiveObjectCommand { +enum ActiveObjectCommand +{ AO_CMD_SET_PROPERTIES, AO_CMD_UPDATE_POSITION, AO_CMD_SET_TEXTURE_MOD, @@ -78,24 +80,14 @@ enum ActiveObjectCommand { class ActiveObject { public: - ActiveObject(u16 id): - m_id(id) - { - } + ActiveObject(u16 id) : m_id(id) {} - u16 getId() const - { - return m_id; - } + u16 getId() const { return m_id; } - void setId(u16 id) - { - m_id = id; - } + void setId(u16 id) { m_id = id; } virtual ActiveObjectType getType() const = 0; - /*! * Returns the collision box of the object. * This box is translated by the object's @@ -105,7 +97,6 @@ public: */ virtual bool getCollisionBox(aabb3f *toset) const = 0; - /*! * Returns the selection box of the object. * This box is not translated when the @@ -115,18 +106,21 @@ public: */ virtual bool getSelectionBox(aabb3f *toset) const = 0; - virtual bool collideWithObjects() const = 0; - virtual void setAttachment(int parent_id, const std::string &bone, v3f position, - v3f rotation) {} + v3f rotation) + { + } virtual void getAttachment(int *parent_id, std::string *bone, v3f *position, - v3f *rotation) const {} + v3f *rotation) const + { + } virtual void clearChildAttachments() {} virtual void clearParentAttachment() {} virtual void addAttachmentChild(int child_id) {} virtual void removeAttachmentChild(int child_id) {} + protected: u16 m_id; // 0 is invalid, "no id" }; diff --git a/src/ban.cpp b/src/ban.cpp index 3decc9666..a6623574f 100644 --- a/src/ban.cpp +++ b/src/ban.cpp @@ -28,14 +28,12 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "log.h" #include "filesys.h" -BanManager::BanManager(const std::string &banfilepath): - m_banfilepath(banfilepath) +BanManager::BanManager(const std::string &banfilepath) : m_banfilepath(banfilepath) { try { load(); - } catch(SerializationError &e) { - infostream << "BanManager: creating " - << m_banfilepath << std::endl; + } catch (SerializationError &e) { + infostream << "BanManager: creating " << m_banfilepath << std::endl; } } @@ -47,10 +45,11 @@ BanManager::~BanManager() void BanManager::load() { MutexAutoLock lock(m_mutex); - infostream<<"BanManager: loading from "< 0 && del_unformatted < m_unformatted.size()) - { + while (count > 0 && del_unformatted < m_unformatted.size()) { ++del_unformatted; // keep m_formatted in sync - if (del_formatted < m_formatted.size()) - { + if (del_formatted < m_formatted.size()) { sanity_check(m_formatted[del_formatted].first); ++del_formatted; @@ -106,7 +103,8 @@ void ChatBuffer::deleteOldest(u32 count) --count; } - m_unformatted.erase(m_unformatted.begin(), m_unformatted.begin() + del_unformatted); + m_unformatted.erase( + m_unformatted.begin(), m_unformatted.begin() + del_unformatted); m_formatted.erase(m_formatted.begin(), m_formatted.begin() + del_formatted); if (at_bottom) @@ -135,16 +133,13 @@ u32 ChatBuffer::getRows() const void ChatBuffer::reformat(u32 cols, u32 rows) { - if (cols == 0 || rows == 0) - { + if (cols == 0 || rows == 0) { // Clear formatted buffer m_cols = 0; m_rows = 0; m_scroll = 0; m_formatted.clear(); - } - else if (cols != m_cols || rows != m_rows) - { + } else if (cols != m_cols || rows != m_rows) { // TODO: Avoid reformatting ALL lines (even invisible ones) // each time the console size changes. @@ -152,21 +147,17 @@ void ChatBuffer::reformat(u32 cols, u32 rows) u32 restore_scroll_unformatted = 0; u32 restore_scroll_formatted = 0; bool at_bottom = (m_scroll == getBottomScrollPos()); - if (!at_bottom) - { - for (s32 i = 0; i < m_scroll; ++i) - { + if (!at_bottom) { + for (s32 i = 0; i < m_scroll; ++i) { if (m_formatted[i].first) ++restore_scroll_unformatted; } } // If number of columns change, reformat everything - if (cols != m_cols) - { + if (cols != m_cols) { m_formatted.clear(); - for (u32 i = 0; i < m_unformatted.size(); ++i) - { + for (u32 i = 0; i < m_unformatted.size(); ++i) { if (i == restore_scroll_unformatted) restore_scroll_formatted = m_formatted.size(); formatChatLine(m_unformatted[i], cols, m_formatted); @@ -178,21 +169,18 @@ void ChatBuffer::reformat(u32 cols, u32 rows) m_rows = rows; // Restore the scroll position - if (at_bottom) - { + if (at_bottom) { scrollBottom(); - } - else - { + } else { scrollAbsolute(restore_scroll_formatted); } } } -const ChatFormattedLine& ChatBuffer::getFormattedLine(u32 row) const +const ChatFormattedLine &ChatBuffer::getFormattedLine(u32 row) const { - s32 index = m_scroll + (s32) row; - if (index >= 0 && index < (s32) m_formatted.size()) + s32 index = m_scroll + (s32)row; + if (index >= 0 && index < (s32)m_formatted.size()) return m_formatted[index]; return m_empty_formatted_line; @@ -225,8 +213,8 @@ void ChatBuffer::scrollTop() m_scroll = getTopScrollPos(); } -u32 ChatBuffer::formatChatLine(const ChatLine& line, u32 cols, - std::vector& destination) const +u32 ChatBuffer::formatChatLine(const ChatLine &line, u32 cols, + std::vector &destination) const { u32 num_added = 0; std::vector next_frags; @@ -240,15 +228,15 @@ u32 ChatBuffer::formatChatLine(const ChatLine& line, u32 cols, if (!line.name.empty()) { temp_frag.text = L"<"; temp_frag.column = 0; - //temp_frag.bold = 0; + // temp_frag.bold = 0; next_frags.push_back(temp_frag); temp_frag.text = line.name; temp_frag.column = 0; - //temp_frag.bold = 1; + // temp_frag.bold = 1; next_frags.push_back(temp_frag); temp_frag.text = L"> "; temp_frag.column = 0; - //temp_frag.bold = 0; + // temp_frag.bold = 0; next_frags.push_back(temp_frag); } @@ -258,46 +246,40 @@ u32 ChatBuffer::formatChatLine(const ChatLine& line, u32 cols, if (line.name.empty()) { // Server messages hanging_indentation = 0; - } else if (name_sanitized.size() + 3 <= cols/2) { + } else if (name_sanitized.size() + 3 <= cols / 2) { // Names shorter than about half the console width hanging_indentation = line.name.size() + 3; } else { // Very long names hanging_indentation = 2; } - //EnrichedString line_text(line.text); + // EnrichedString line_text(line.text); next_line.first = true; bool text_processing = false; // Produce fragments and layout them into lines - while (!next_frags.empty() || in_pos < line.text.size()) - { + while (!next_frags.empty() || in_pos < line.text.size()) { // Layout fragments into lines - while (!next_frags.empty()) - { - ChatFormattedFragment& frag = next_frags[0]; - if (frag.text.size() <= cols - out_column) - { + while (!next_frags.empty()) { + ChatFormattedFragment &frag = next_frags[0]; + if (frag.text.size() <= cols - out_column) { // Fragment fits into current line frag.column = out_column; next_line.fragments.push_back(frag); out_column += frag.text.size(); next_frags.erase(next_frags.begin()); - } - else - { + } else { // Fragment does not fit into current line // So split it up temp_frag.text = frag.text.substr(0, cols - out_column); temp_frag.column = out_column; - //temp_frag.bold = frag.bold; + // temp_frag.bold = frag.bold; next_line.fragments.push_back(temp_frag); frag.text = frag.text.substr(cols - out_column); out_column = cols; } - if (out_column == cols || text_processing) - { + if (out_column == cols || text_processing) { // End the current line destination.push_back(next_line); num_added++; @@ -309,8 +291,7 @@ u32 ChatBuffer::formatChatLine(const ChatLine& line, u32 cols, } // Produce fragment - if (in_pos < line.text.size()) - { + if (in_pos < line.text.size()) { u32 remaining_in_input = line.text.size() - in_pos; u32 remaining_in_output = cols - out_column; @@ -319,8 +300,7 @@ u32 ChatBuffer::formatChatLine(const ChatLine& line, u32 cols, // on a word boundary. u32 frag_length = 1, space_pos = 0; while (frag_length < remaining_in_input && - frag_length < remaining_in_output) - { + frag_length < remaining_in_output) { if (iswspace(line.text.getString()[in_pos + frag_length])) space_pos = frag_length; ++frag_length; @@ -330,7 +310,7 @@ u32 ChatBuffer::formatChatLine(const ChatLine& line, u32 cols, temp_frag.text = line.text.substr(in_pos, frag_length); temp_frag.column = 0; - //temp_frag.bold = 0; + // temp_frag.bold = 0; next_frags.push_back(temp_frag); in_pos += frag_length; text_processing = true; @@ -338,8 +318,7 @@ u32 ChatBuffer::formatChatLine(const ChatLine& line, u32 cols, } // End the last line - if (num_added == 0 || !next_line.fragments.empty()) - { + if (num_added == 0 || !next_line.fragments.empty()) { destination.push_back(next_line); num_added++; } @@ -349,8 +328,8 @@ u32 ChatBuffer::formatChatLine(const ChatLine& line, u32 cols, s32 ChatBuffer::getTopScrollPos() const { - s32 formatted_count = (s32) m_formatted.size(); - s32 rows = (s32) m_rows; + s32 formatted_count = (s32)m_formatted.size(); + s32 rows = (s32)m_rows; if (rows == 0) return 0; @@ -362,8 +341,8 @@ s32 ChatBuffer::getTopScrollPos() const s32 ChatBuffer::getBottomScrollPos() const { - s32 formatted_count = (s32) m_formatted.size(); - s32 rows = (s32) m_rows; + s32 formatted_count = (s32)m_formatted.size(); + s32 rows = (s32)m_rows; if (rows == 0) return 0; @@ -377,10 +356,8 @@ void ChatBuffer::resize(u32 scrollback) deleteOldest(m_unformatted.size() - m_scrollback); } - -ChatPrompt::ChatPrompt(const std::wstring &prompt, u32 history_limit): - m_prompt(prompt), - m_history_limit(history_limit) +ChatPrompt::ChatPrompt(const std::wstring &prompt, u32 history_limit) : + m_prompt(prompt), m_history_limit(history_limit) { } @@ -404,11 +381,10 @@ void ChatPrompt::input(const std::wstring &str) void ChatPrompt::addToHistory(const std::wstring &line) { - if (!line.empty() && - (m_history.size() == 0 || m_history.back() != line)) { + if (!line.empty() && (m_history.size() == 0 || m_history.back() != line)) { // Remove all duplicates - m_history.erase(std::remove(m_history.begin(), m_history.end(), - line), m_history.end()); + m_history.erase(std::remove(m_history.begin(), m_history.end(), line), + m_history.end()); // Push unique line m_history.push_back(line); } @@ -429,7 +405,7 @@ void ChatPrompt::clear() std::wstring ChatPrompt::replace(const std::wstring &line) { std::wstring old_line = m_line; - m_line = line; + m_line = line; m_view = m_cursor = line.size(); clampView(); m_nick_completion_start = 0; @@ -439,8 +415,7 @@ std::wstring ChatPrompt::replace(const std::wstring &line) void ChatPrompt::historyPrev() { - if (m_history_index != 0) - { + if (m_history_index != 0) { --m_history_index; replace(m_history[m_history_index]); } @@ -448,19 +423,16 @@ void ChatPrompt::historyPrev() void ChatPrompt::historyNext() { - if (m_history_index + 1 >= m_history.size()) - { + if (m_history_index + 1 >= m_history.size()) { m_history_index = m_history.size(); replace(L""); - } - else - { + } else { ++m_history_index; replace(m_history[m_history_index]); } } -void ChatPrompt::nickCompletion(const std::list& names, bool backwards) +void ChatPrompt::nickCompletion(const std::list &names, bool backwards) { // Two cases: // (a) m_nick_completion_start == m_nick_completion_end == 0 @@ -474,11 +446,10 @@ void ChatPrompt::nickCompletion(const std::list& names, bool backwa u32 prefix_start = m_nick_completion_start; u32 prefix_end = m_nick_completion_end; bool initial = (prefix_end == 0); - if (initial) - { + if (initial) { // no previous nick completion is active prefix_start = prefix_end = m_cursor; - while (prefix_start > 0 && !iswspace(m_line[prefix_start-1])) + while (prefix_start > 0 && !iswspace(m_line[prefix_start - 1])) --prefix_start; while (prefix_end < m_line.size() && !iswspace(m_line[prefix_end])) ++prefix_end; @@ -504,17 +475,14 @@ void ChatPrompt::nickCompletion(const std::list& names, bool backwa // find a replacement string and the word that will be replaced u32 word_end = prefix_end; u32 replacement_index = 0; - if (!initial) - { + if (!initial) { while (word_end < m_line.size() && !iswspace(m_line[word_end])) ++word_end; std::wstring word = m_line.substr(prefix_start, word_end - prefix_start); // cycle through completions - for (u32 i = 0; i < completions.size(); ++i) - { - if (str_equal(word, completions[i], true)) - { + for (u32 i = 0; i < completions.size(); ++i) { + if (str_equal(word, completions[i], true)) { if (backwards) replacement_index = i + completions.size() - 1; else @@ -539,13 +507,10 @@ void ChatPrompt::nickCompletion(const std::list& names, bool backwa void ChatPrompt::reformat(u32 cols) { - if (cols <= m_prompt.size()) - { + if (cols <= m_prompt.size()) { m_cols = 0; m_view = m_cursor; - } - else - { + } else { s32 length = m_line.size(); bool was_at_end = (m_view + m_cols >= length + 1); m_cols = cols - m_prompt.size(); @@ -638,12 +603,9 @@ void ChatPrompt::cursorOperation(CursorOp op, CursorOpDir dir, CursorOpScope sco void ChatPrompt::clampView() { s32 length = m_line.size(); - if (length + 1 <= m_cols) - { + if (length + 1 <= m_cols) { m_view = 0; - } - else - { + } else { m_view = MYMIN(m_view, length + 1 - m_cols); m_view = MYMIN(m_view, m_cursor); m_view = MYMAX(m_view, m_cursor - m_cols + 1); @@ -651,12 +613,8 @@ void ChatPrompt::clampView() } } - - -ChatBackend::ChatBackend(): - m_console_buffer(500), - m_recent_buffer(6), - m_prompt(L"]", 500) +ChatBackend::ChatBackend() : + m_console_buffer(500), m_recent_buffer(6), m_prompt(L"]", 500) { } @@ -665,8 +623,7 @@ void ChatBackend::addMessage(const std::wstring &name, std::wstring text) // Note: A message may consist of multiple lines, for example the MOTD. text = translate_string(text); WStrfnd fnd(text); - while (!fnd.at_end()) - { + while (!fnd.at_end()) { std::wstring line = fnd.next(L"\n"); m_console_buffer.addLine(name, line); m_recent_buffer.addLine(name, line); @@ -678,13 +635,10 @@ void ChatBackend::addUnparsedMessage(std::wstring message) // TODO: Remove the need to parse chat messages client-side, by sending // separate name and text fields in TOCLIENT_CHAT_MESSAGE. - if (message.size() >= 2 && message[0] == L'<') - { + if (message.size() >= 2 && message[0] == L'<') { std::size_t closing = message.find_first_of(L'>', 1); - if (closing != std::wstring::npos && - closing + 2 <= message.size() && - message[closing+1] == L' ') - { + if (closing != std::wstring::npos && closing + 2 <= message.size() && + message[closing + 1] == L' ') { std::wstring name = message.substr(1, closing - 1); std::wstring text = message.substr(closing + 2); addMessage(name, text); @@ -696,12 +650,12 @@ void ChatBackend::addUnparsedMessage(std::wstring message) addMessage(L"", message); } -ChatBuffer& ChatBackend::getConsoleBuffer() +ChatBuffer &ChatBackend::getConsoleBuffer() { return m_console_buffer; } -ChatBuffer& ChatBackend::getRecentBuffer() +ChatBuffer &ChatBackend::getRecentBuffer() { return m_recent_buffer; } @@ -710,7 +664,7 @@ EnrichedString ChatBackend::getRecentChat() const { EnrichedString result; for (u32 i = 0; i < m_recent_buffer.getLineCount(); ++i) { - const ChatLine& line = m_recent_buffer.getLine(i); + const ChatLine &line = m_recent_buffer.getLine(i); if (i != 0) result += L"\n"; if (!line.name.empty()) { @@ -723,7 +677,7 @@ EnrichedString ChatBackend::getRecentChat() const return result; } -ChatPrompt& ChatBackend::getPrompt() +ChatPrompt &ChatBackend::getPrompt() { return m_prompt; } @@ -743,7 +697,6 @@ void ChatBackend::clearRecentChat() m_recent_buffer.clear(); } - void ChatBackend::applySettings() { u32 recent_lines = g_settings->getU32("recent_chat_messages"); diff --git a/src/chat.h b/src/chat.h index f84ece206..9fe910cc9 100644 --- a/src/chat.h +++ b/src/chat.h @@ -38,15 +38,13 @@ struct ChatLine // message text EnrichedString text; - ChatLine(const std::wstring &a_name, const std::wstring &a_text): - name(a_name), - text(a_text) + ChatLine(const std::wstring &a_name, const std::wstring &a_text) : + name(a_name), text(a_text) { } - ChatLine(const EnrichedString &a_name, const EnrichedString &a_text): - name(a_name), - text(a_text) + ChatLine(const EnrichedString &a_name, const EnrichedString &a_text) : + name(a_name), text(a_text) { } }; @@ -58,7 +56,7 @@ struct ChatFormattedFragment // starting column u32 column; // formatting - //u8 bold:1; + // u8 bold:1; }; struct ChatFormattedLine @@ -85,7 +83,7 @@ public: // Get number of lines currently in buffer. u32 getLineCount() const; // Get reference to i-th chat line. - const ChatLine& getLine(u32 index) const; + const ChatLine &getLine(u32 index) const; // Increase each chat line's age by dtime. void step(f32 dtime); @@ -102,7 +100,7 @@ public: void reformat(u32 cols, u32 rows); // Get formatted line for a given row (0 is top of screen). // Only valid after reformat has been called at least once - const ChatFormattedLine& getFormattedLine(u32 row) const; + const ChatFormattedLine &getFormattedLine(u32 row) const; // Scrolling in formatted buffer (relative) // positive rows == scroll up, negative rows == scroll down void scroll(s32 rows); @@ -116,10 +114,11 @@ public: // Format a chat line for the given number of columns. // Appends the formatted lines to the destination array and // returns the number of formatted lines. - u32 formatChatLine(const ChatLine& line, u32 cols, - std::vector& destination) const; + u32 formatChatLine(const ChatLine &line, u32 cols, + std::vector &destination) const; void resize(u32 scrollback); + protected: s32 getTopScrollPos() const; s32 getBottomScrollPos() const; @@ -159,7 +158,10 @@ public: std::wstring getLine() const { return m_line; } // Get section of line that is currently selected - std::wstring getSelection() const { return m_line.substr(m_cursor, m_cursor_len); } + std::wstring getSelection() const + { + return m_line.substr(m_cursor, m_cursor_len); + } // Clear the current line void clear(); @@ -173,7 +175,7 @@ public: void historyNext(); // Nick completion - void nickCompletion(const std::list& names, bool backwards); + void nickCompletion(const std::list &names, bool backwards); // Update console size and reformat the visible portion of the prompt void reformat(u32 cols); @@ -185,20 +187,23 @@ public: s32 getCursorLength() const { return m_cursor_len; } // Cursor operations - enum CursorOp { + enum CursorOp + { CURSOROP_MOVE, CURSOROP_SELECT, CURSOROP_DELETE }; // Cursor operation direction - enum CursorOpDir { + enum CursorOpDir + { CURSOROP_DIR_LEFT, CURSOROP_DIR_RIGHT }; // Cursor operation scope - enum CursorOpScope { + enum CursorOpScope + { CURSOROP_SCOPE_CHARACTER, CURSOROP_SCOPE_WORD, CURSOROP_SCOPE_LINE, @@ -261,13 +266,13 @@ public: void addUnparsedMessage(std::wstring line); // Get the console buffer - ChatBuffer& getConsoleBuffer(); + ChatBuffer &getConsoleBuffer(); // Get the recent messages buffer - ChatBuffer& getRecentBuffer(); + ChatBuffer &getRecentBuffer(); // Concatenate all recent messages EnrichedString getRecentChat() const; // Get the console prompt - ChatPrompt& getPrompt(); + ChatPrompt &getPrompt(); // Reformat all buffers void reformat(u32 cols, u32 rows); diff --git a/src/chat_interface.h b/src/chat_interface.h index 5dc3d3880..5cbb8a927 100644 --- a/src/chat_interface.h +++ b/src/chat_interface.h @@ -24,56 +24,59 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include "irrlichttypes.h" -enum ChatEventType { +enum ChatEventType +{ CET_CHAT, CET_NICK_ADD, CET_NICK_REMOVE, CET_TIME_INFO, }; -class ChatEvent { +class ChatEvent +{ protected: ChatEvent(ChatEventType a_type) { type = a_type; } + public: ChatEventType type; }; -struct ChatEventTimeInfo : public ChatEvent { - ChatEventTimeInfo( - u64 a_game_time, - u32 a_time) : - ChatEvent(CET_TIME_INFO), - game_time(a_game_time), - time(a_time) - {} +struct ChatEventTimeInfo : public ChatEvent +{ + ChatEventTimeInfo(u64 a_game_time, u32 a_time) : + ChatEvent(CET_TIME_INFO), game_time(a_game_time), time(a_time) + { + } u64 game_time; u32 time; }; -struct ChatEventNick : public ChatEvent { +struct ChatEventNick : public ChatEvent +{ ChatEventNick(ChatEventType a_type, - const std::string &a_nick) : - ChatEvent(a_type), // one of CET_NICK_ADD, CET_NICK_REMOVE - nick(a_nick) - {} + const std::string &a_nick) : + ChatEvent(a_type), // one of CET_NICK_ADD, CET_NICK_REMOVE + nick(a_nick) + { + } std::string nick; }; -struct ChatEventChat : public ChatEvent { - ChatEventChat(const std::string &a_nick, - const std::wstring &an_evt_msg) : - ChatEvent(CET_CHAT), - nick(a_nick), - evt_msg(an_evt_msg) - {} +struct ChatEventChat : public ChatEvent +{ + ChatEventChat(const std::string &a_nick, const std::wstring &an_evt_msg) : + ChatEvent(CET_CHAT), nick(a_nick), evt_msg(an_evt_msg) + { + } std::string nick; std::wstring evt_msg; }; -struct ChatInterface { - MutexedQueue command_queue; // chat backend --> server +struct ChatInterface +{ + MutexedQueue command_queue; // chat backend --> server MutexedQueue outgoing_queue; // server --> chat backend }; diff --git a/src/client/camera.cpp b/src/client/camera.cpp index c9e8fab6a..9d74447d5 100644 --- a/src/client/camera.cpp +++ b/src/client/camera.cpp @@ -22,13 +22,13 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "client.h" #include "config.h" #include "map.h" -#include "clientmap.h" // MapDrawControl +#include "clientmap.h" // MapDrawControl #include "player.h" #include #include "client/renderingengine.h" #include "settings.h" #include "wieldmesh.h" -#include "noise.h" // easeCurve +#include "noise.h" // easeCurve #include "sound.h" #include "event.h" #include "nodedef.h" @@ -43,9 +43,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #define WIELDMESH_AMPLITUDE_X 7.0f #define WIELDMESH_AMPLITUDE_Y 10.0f -Camera::Camera(MapDrawControl &draw_control, Client *client): - m_draw_control(draw_control), - m_client(client) +Camera::Camera(MapDrawControl &draw_control, Client *client) : + m_draw_control(draw_control), m_client(client) { scene::ISceneManager *smgr = RenderingEngine::get_scene_manager(); // note: making the camera node a child of the player node @@ -76,8 +75,8 @@ Camera::Camera(MapDrawControl &draw_control, Client *client): m_cache_view_bobbing_amount = g_settings->getFloat("view_bobbing_amount"); // 45 degrees is the lowest FOV that doesn't cause the server to treat this // as a zoom FOV and load world beyond the set server limits. - m_cache_fov = std::fmax(g_settings->getFloat("fov"), 45.0f); - m_arm_inertia = g_settings->getBool("arm_inertia"); + m_cache_fov = std::fmax(g_settings->getFloat("fov"), 45.0f); + m_arm_inertia = g_settings->getBool("arm_inertia"); m_nametags.clear(); } @@ -103,7 +102,8 @@ void Camera::notifyFovChange() 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; + 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 @@ -118,7 +118,8 @@ void Camera::notifyFovChange() 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; + m_target_fov_degrees = + spec.is_multiplier ? m_cache_fov * spec.fov : spec.fov; } if (spec.transition_time > 0.0f) @@ -162,10 +163,9 @@ inline f32 my_modf(f32 x) void Camera::step(f32 dtime) { - if(m_view_bobbing_fall > 0) - { + if (m_view_bobbing_fall > 0) { m_view_bobbing_fall -= 3 * dtime; - if(m_view_bobbing_fall <= 0) + if (m_view_bobbing_fall <= 0) m_view_bobbing_fall = -1; // Mark the effect as finished } @@ -175,9 +175,8 @@ void Camera::step(f32 dtime) if (m_wield_change_timer >= 0 && was_under_zero) m_wieldnode->setItem(m_wield_item_next, m_client); - if (m_view_bobbing_state != 0) - { - //f32 offset = dtime * m_view_bobbing_speed * 0.035; + if (m_view_bobbing_state != 0) { + // f32 offset = dtime * m_view_bobbing_speed * 0.035; f32 offset = dtime * m_view_bobbing_speed * 0.030; if (m_view_bobbing_state == 2) { // Animation is getting turned off @@ -202,15 +201,15 @@ void Camera::step(f32 dtime) m_view_bobbing_anim = 0; m_view_bobbing_state = 0; } - } - else { + } else { float was = m_view_bobbing_anim; m_view_bobbing_anim = my_modf(m_view_bobbing_anim + offset); bool step = (was == 0 || (was < 0.5f && m_view_bobbing_anim >= 0.5f) || (was > 0.5f && m_view_bobbing_anim <= 0.5f)); - if(step) { - m_client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::VIEW_BOBBING_STEP)); + if (step) { + m_client->getEventManager()->put(new SimpleTriggerEvent( + MtEvent::VIEW_BOBBING_STEP)); } } } @@ -219,18 +218,18 @@ void Camera::step(f32 dtime) f32 offset = dtime * 3.5f; float m_digging_anim_was = m_digging_anim; m_digging_anim += offset; - if (m_digging_anim >= 1) - { + if (m_digging_anim >= 1) { m_digging_anim = 0; m_digging_button = -1; } float lim = 0.15; - if(m_digging_anim_was < lim && m_digging_anim >= lim) - { + if (m_digging_anim_was < lim && m_digging_anim >= lim) { if (m_digging_button == 0) { - m_client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::CAMERA_PUNCH_LEFT)); - } else if(m_digging_button == 1) { - m_client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::CAMERA_PUNCH_RIGHT)); + m_client->getEventManager()->put(new SimpleTriggerEvent( + MtEvent::CAMERA_PUNCH_LEFT)); + } else if (m_digging_button == 1) { + m_client->getEventManager()->put(new SimpleTriggerEvent( + MtEvent::CAMERA_PUNCH_RIGHT)); } } } @@ -259,8 +258,9 @@ static inline v2f dir(const v2f &pos_dist) void Camera::addArmInertia(f32 player_yaw) { - m_cam_vel.X = std::fabs(rangelim(m_last_cam_pos.X - player_yaw, - -100.0f, 100.0f) / 0.016f) * 0.01f; + m_cam_vel.X = std::fabs(rangelim(m_last_cam_pos.X - player_yaw, -100.0f, 100.0f) / + 0.016f) * + 0.01f; m_cam_vel.Y = std::fabs((m_last_cam_pos.Y - m_camera_direction.Y) / 0.016f); f32 gap_X = std::fabs(WIELDMESH_OFFSET_X - m_wieldmesh_offset.X); f32 gap_Y = std::fabs(WIELDMESH_OFFSET_Y - m_wieldmesh_offset.Y); @@ -276,14 +276,17 @@ void Camera::addArmInertia(f32 player_yaw) m_cam_vel_old.X = m_cam_vel.X; f32 acc_X = 0.12f * (m_cam_vel.X - (gap_X * 0.1f)); - m_wieldmesh_offset.X += m_last_cam_pos.X < player_yaw ? acc_X : -acc_X; + m_wieldmesh_offset.X += + m_last_cam_pos.X < player_yaw ? acc_X : -acc_X; if (m_last_cam_pos.X != player_yaw) m_last_cam_pos.X = player_yaw; m_wieldmesh_offset.X = rangelim(m_wieldmesh_offset.X, - WIELDMESH_OFFSET_X - (WIELDMESH_AMPLITUDE_X * 0.5f), - WIELDMESH_OFFSET_X + (WIELDMESH_AMPLITUDE_X * 0.5f)); + WIELDMESH_OFFSET_X - + (WIELDMESH_AMPLITUDE_X * 0.5f), + WIELDMESH_OFFSET_X + + (WIELDMESH_AMPLITUDE_X * 0.5f)); } if (m_cam_vel.Y > 1.0f) { @@ -291,15 +294,18 @@ void Camera::addArmInertia(f32 player_yaw) m_cam_vel_old.Y = m_cam_vel.Y; f32 acc_Y = 0.12f * (m_cam_vel.Y - (gap_Y * 0.1f)); - m_wieldmesh_offset.Y += - m_last_cam_pos.Y > m_camera_direction.Y ? acc_Y : -acc_Y; + m_wieldmesh_offset.Y += m_last_cam_pos.Y > m_camera_direction.Y + ? acc_Y + : -acc_Y; if (m_last_cam_pos.Y != m_camera_direction.Y) m_last_cam_pos.Y = m_camera_direction.Y; m_wieldmesh_offset.Y = rangelim(m_wieldmesh_offset.Y, - WIELDMESH_OFFSET_Y - (WIELDMESH_AMPLITUDE_Y * 0.5f), - WIELDMESH_OFFSET_Y + (WIELDMESH_AMPLITUDE_Y * 0.5f)); + WIELDMESH_OFFSET_Y - + (WIELDMESH_AMPLITUDE_Y * 0.5f), + WIELDMESH_OFFSET_Y + + (WIELDMESH_AMPLITUDE_Y * 0.5f)); } m_arm_dir = dir(m_wieldmesh_offset); @@ -309,27 +315,34 @@ void Camera::addArmInertia(f32 player_yaw) following a vector, with a smooth deceleration factor. */ - f32 dec_X = 0.35f * (std::min(15.0f, m_cam_vel_old.X) * (1.0f + - (1.0f - m_arm_dir.X))) * (gap_X / 20.0f); + f32 dec_X = 0.35f * + (std::min(15.0f, m_cam_vel_old.X) * + (1.0f + (1.0f - m_arm_dir.X))) * + (gap_X / 20.0f); - f32 dec_Y = 0.25f * (std::min(15.0f, m_cam_vel_old.Y) * (1.0f + - (1.0f - m_arm_dir.Y))) * (gap_Y / 15.0f); + f32 dec_Y = 0.25f * + (std::min(15.0f, m_cam_vel_old.Y) * + (1.0f + (1.0f - m_arm_dir.Y))) * + (gap_Y / 15.0f); if (gap_X < 0.1f) m_cam_vel_old.X = 0.0f; - m_wieldmesh_offset.X -= - m_wieldmesh_offset.X > WIELDMESH_OFFSET_X ? dec_X : -dec_X; + m_wieldmesh_offset.X -= m_wieldmesh_offset.X > WIELDMESH_OFFSET_X + ? dec_X + : -dec_X; if (gap_Y < 0.1f) m_cam_vel_old.Y = 0.0f; - m_wieldmesh_offset.Y -= - m_wieldmesh_offset.Y > WIELDMESH_OFFSET_Y ? dec_Y : -dec_Y; + m_wieldmesh_offset.Y -= m_wieldmesh_offset.Y > WIELDMESH_OFFSET_Y + ? dec_Y + : -dec_Y; } } -void Camera::update(LocalPlayer* player, f32 frametime, f32 busytime, f32 tool_reload_ratio) +void Camera::update( + LocalPlayer *player, f32 frametime, f32 busytime, f32 tool_reload_ratio) { // Get player position // Smooth the movement when walking up stairs @@ -340,19 +353,24 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 busytime, f32 tool_r // mods expect the player head to be at the parent's position // plus eye height. if (player->getParent()) - player_position = player->getParent()->getPosition() + v3f(0, g_settings->getBool("float_above_parent") ? BS : 0, 0); + player_position = + player->getParent()->getPosition() + + v3f(0, g_settings->getBool("float_above_parent") ? BS : 0, + 0); - // 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) { + // 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); - player_position.Y = oldy * t + newy * (1-t); + player_position.Y = oldy * t + newy * (1 - t); } // Set player node transformation @@ -361,19 +379,20 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 busytime, f32 tool_r m_playernode->updateAbsolutePosition(); // Get camera tilt timer (hurt animation) - float cameratilt = fabs(fabs(player->hurt_tilt_timer-0.75)-0.75); + float cameratilt = fabs(fabs(player->hurt_tilt_timer - 0.75) - 0.75); // Fall bobbing animation float fall_bobbing = 0; - if(player->camera_impact >= 1 && m_camera_mode < CAMERA_MODE_THIRD) - { - if(m_view_bobbing_fall == -1) // Effect took place and has finished + if (player->camera_impact >= 1 && m_camera_mode < CAMERA_MODE_THIRD) { + if (m_view_bobbing_fall == -1) // Effect took place and has finished player->camera_impact = m_view_bobbing_fall = 0; - else if(m_view_bobbing_fall == 0) // Initialize effect + else if (m_view_bobbing_fall == 0) // Initialize effect m_view_bobbing_fall = 1; // Convert 0 -> 1 to 0 -> 1 -> 0 - fall_bobbing = m_view_bobbing_fall < 0.5 ? m_view_bobbing_fall * 2 : -(m_view_bobbing_fall - 0.5) * 2 + 1; + fall_bobbing = m_view_bobbing_fall < 0.5 + ? m_view_bobbing_fall * 2 + : -(m_view_bobbing_fall - 0.5) * 2 + 1; // Smoothen and invert the above fall_bobbing = sin(fall_bobbing * 0.5 * M_PI) * -1; // Amplify according to the intensity of the impact @@ -394,60 +413,56 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 busytime, f32 tool_r 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)); + cameratilt * player->hurt_tilt_strength)); m_headnode->updateAbsolutePosition(); } // Compute relative camera position and target - v3f rel_cam_pos = v3f(0,0,0); - v3f rel_cam_target = v3f(0,0,1); - v3f rel_cam_up = v3f(0,1,0); + v3f rel_cam_pos = v3f(0, 0, 0); + v3f rel_cam_target = v3f(0, 0, 1); + v3f rel_cam_up = v3f(0, 1, 0); if (m_cache_view_bobbing_amount != 0.0f && m_view_bobbing_anim != 0.0f && - m_camera_mode < CAMERA_MODE_THIRD) { + m_camera_mode < CAMERA_MODE_THIRD) { f32 bobfrac = my_modf(m_view_bobbing_anim * 2); f32 bobdir = (m_view_bobbing_anim < 0.5) ? 1.0 : -1.0; - #if 1 +#if 1 f32 bobknob = 1.2; f32 bobtmp = sin(pow(bobfrac, bobknob) * M_PI); - //f32 bobtmp2 = cos(pow(bobfrac, bobknob) * M_PI); + // f32 bobtmp2 = cos(pow(bobfrac, bobknob) * M_PI); - v3f bobvec = v3f( - 0.3 * bobdir * sin(bobfrac * M_PI), - -0.28 * bobtmp * bobtmp, - 0.); + v3f bobvec = v3f(0.3 * bobdir * sin(bobfrac * M_PI), + -0.28 * bobtmp * bobtmp, 0.); - //rel_cam_pos += 0.2 * bobvec; - //rel_cam_target += 0.03 * bobvec; - //rel_cam_up.rotateXYBy(0.02 * bobdir * bobtmp * M_PI); + // rel_cam_pos += 0.2 * bobvec; + // rel_cam_target += 0.03 * bobvec; + // rel_cam_up.rotateXYBy(0.02 * bobdir * bobtmp * M_PI); float f = 1.0; f *= m_cache_view_bobbing_amount; rel_cam_pos += bobvec * f; - //rel_cam_target += 0.995 * bobvec * f; + // rel_cam_target += 0.995 * bobvec * f; rel_cam_target += bobvec * f; rel_cam_target.Z -= 0.005 * bobvec.Z * f; - //rel_cam_target.X -= 0.005 * bobvec.X * f; - //rel_cam_target.Y -= 0.005 * bobvec.Y * f; + // rel_cam_target.X -= 0.005 * bobvec.X * f; + // rel_cam_target.Y -= 0.005 * bobvec.Y * f; rel_cam_up.rotateXYBy(-0.03 * bobdir * bobtmp * M_PI * f); - #else +#else f32 angle_deg = 1 * bobdir * sin(bobfrac * M_PI); f32 angle_rad = angle_deg * M_PI / 180; f32 r = 0.05; - v3f off = v3f( - r * sin(angle_rad), - r * (cos(angle_rad) - 1), - 0); + v3f off = v3f(r * sin(angle_rad), r * (cos(angle_rad) - 1), 0); rel_cam_pos += off; - //rel_cam_target += off; + // rel_cam_target += off; rel_cam_up.rotateXYBy(angle_deg); - #endif - +#endif } // Compute absolute camera position and target - m_headnode->getAbsoluteTransformation().transformVect(m_camera_position, rel_cam_pos); - m_headnode->getAbsoluteTransformation().rotateVect(m_camera_direction, rel_cam_target - rel_cam_pos); + m_headnode->getAbsoluteTransformation().transformVect( + m_camera_position, rel_cam_pos); + m_headnode->getAbsoluteTransformation().rotateVect( + m_camera_direction, rel_cam_target - rel_cam_pos); v3f abs_cam_up; m_headnode->getAbsoluteTransformation().rotateVect(abs_cam_up, rel_cam_up); @@ -456,8 +471,7 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 busytime, f32 tool_r v3f my_cp = m_camera_position; // Reposition the camera for third person view - if (m_camera_mode > CAMERA_MODE_FIRST) - { + if (m_camera_mode > CAMERA_MODE_FIRST) { if (m_camera_mode == CAMERA_MODE_THIRD_FRONT) m_camera_direction *= -1; @@ -469,41 +483,46 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 busytime, f32 tool_r my_cp.X = m_camera_position.X + m_camera_direction.X * -i; my_cp.Z = m_camera_position.Z + m_camera_direction.Z * -i; if (i > 12) - my_cp.Y = m_camera_position.Y + (m_camera_direction.Y * -i); + my_cp.Y = m_camera_position.Y + + (m_camera_direction.Y * -i); // Prevent camera positioned inside nodes const NodeDefManager *nodemgr = m_client->ndef(); - MapNode n = m_client->getEnv().getClientMap() - .getNode(floatToInt(my_cp, BS)); + MapNode n = m_client->getEnv().getClientMap().getNode( + floatToInt(my_cp, BS)); - const ContentFeatures& features = nodemgr->get(n); + const ContentFeatures &features = nodemgr->get(n); if (features.walkable) { - my_cp.X += m_camera_direction.X*-1*-BS/2; - my_cp.Z += m_camera_direction.Z*-1*-BS/2; - my_cp.Y += m_camera_direction.Y*-1*-BS/2; + my_cp.X += m_camera_direction.X * -1 * -BS / 2; + my_cp.Z += m_camera_direction.Z * -1 * -BS / 2; + my_cp.Y += m_camera_direction.Y * -1 * -BS / 2; abort = true; break; } } // If node blocks camera position don't move y to heigh - if (abort && my_cp.Y > player_position.Y+BS*2) - my_cp.Y = player_position.Y+BS*2; + if (abort && my_cp.Y > player_position.Y + BS * 2) + my_cp.Y = player_position.Y + BS * 2; } // Update offset if too far away from the center of the map - m_camera_offset.X += CAMERA_OFFSET_STEP* - (((s16)(my_cp.X/BS) - m_camera_offset.X)/CAMERA_OFFSET_STEP); - m_camera_offset.Y += CAMERA_OFFSET_STEP* - (((s16)(my_cp.Y/BS) - m_camera_offset.Y)/CAMERA_OFFSET_STEP); - m_camera_offset.Z += CAMERA_OFFSET_STEP* - (((s16)(my_cp.Z/BS) - m_camera_offset.Z)/CAMERA_OFFSET_STEP); + m_camera_offset.X += + CAMERA_OFFSET_STEP * + (((s16)(my_cp.X / BS) - m_camera_offset.X) / CAMERA_OFFSET_STEP); + m_camera_offset.Y += + CAMERA_OFFSET_STEP * + (((s16)(my_cp.Y / BS) - m_camera_offset.Y) / CAMERA_OFFSET_STEP); + m_camera_offset.Z += + CAMERA_OFFSET_STEP * + (((s16)(my_cp.Z / BS) - m_camera_offset.Z) / CAMERA_OFFSET_STEP); // Set camera node transformation - m_cameranode->setPosition(my_cp-intToFloat(m_camera_offset, BS)); + m_cameranode->setPosition(my_cp - intToFloat(m_camera_offset, BS)); m_cameranode->setUpVector(abs_cam_up); // *100.0 helps in large map coordinates - m_cameranode->setTarget(my_cp-intToFloat(m_camera_offset, BS) + 100 * m_camera_direction); + m_cameranode->setTarget(my_cp - intToFloat(m_camera_offset, BS) + + 100 * m_camera_direction); // update the camera position in third-person mode to render blocks behind player // and correctly apply liquid post FX. @@ -523,7 +542,9 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 busytime, f32 tool_r // 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_diff < 0.0f && + m_curr_fov_degrees <= + m_target_fov_degrees)) { m_fov_transition_active = false; m_curr_fov_degrees = m_target_fov_degrees; } @@ -541,10 +562,10 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 busytime, f32 tool_r // FOV and aspect ratio const v2u32 &window_size = RenderingEngine::get_instance()->getWindowSize(); - m_aspect = (f32) window_size.X / (f32) window_size.Y; + m_aspect = (f32)window_size.X / (f32)window_size.Y; m_fov_y = m_curr_fov_degrees * M_PI / 180.0; // Increase vertical FOV on lower aspect ratios (<16:10) - m_fov_y *= core::clamp(sqrt(16./10. / m_aspect), 1.0, 1.4); + 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); @@ -553,29 +574,27 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 busytime, f32 tool_r addArmInertia(player->getYaw()); // Position the wielded item - //v3f wield_position = v3f(45, -35, 65); + // v3f wield_position = v3f(45, -35, 65); v3f wield_position = v3f(m_wieldmesh_offset.X, m_wieldmesh_offset.Y, 65); - //v3f wield_rotation = v3f(-100, 120, -100); + // v3f wield_rotation = v3f(-100, 120, -100); v3f wield_rotation = v3f(-100, 120, -100); - wield_position.Y += fabs(m_wield_change_timer)*320 - 40; - if(m_digging_anim < 0.05 || m_digging_anim > 0.5) - { + wield_position.Y += fabs(m_wield_change_timer) * 320 - 40; + if (m_digging_anim < 0.05 || m_digging_anim > 0.5) { f32 frac = 1.0; - if(m_digging_anim > 0.5) + if (m_digging_anim > 0.5) frac = 2.0 * (m_digging_anim - 0.5); // This value starts from 1 and settles to 0 f32 ratiothing = std::pow((1.0f - tool_reload_ratio), 0.5f); - //f32 ratiothing2 = pow(ratiothing, 0.5f); - f32 ratiothing2 = (easeCurve(ratiothing*0.5))*2.0; + // f32 ratiothing2 = pow(ratiothing, 0.5f); + f32 ratiothing2 = (easeCurve(ratiothing * 0.5)) * 2.0; wield_position.Y -= frac * 25.0 * pow(ratiothing2, 1.7f); - //wield_position.Z += frac * 5.0 * ratiothing2; + // wield_position.Z += frac * 5.0 * ratiothing2; wield_position.X -= frac * 35.0 * pow(ratiothing2, 1.1f); wield_rotation.Y += frac * 70.0 * pow(ratiothing2, 1.4f); - //wield_rotation.X -= frac * 15.0 * pow(ratiothing2, 1.4f); - //wield_rotation.Z += frac * 15.0 * pow(ratiothing2, 1.0f); + // wield_rotation.X -= frac * 15.0 * pow(ratiothing2, 1.4f); + // wield_rotation.Z += frac * 15.0 * pow(ratiothing2, 1.0f); } - if (m_digging_button != -1) - { + if (m_digging_button != -1) { f32 digfrac = m_digging_anim; wield_position.X -= 50 * sin(pow(digfrac, 0.8f) * M_PI); wield_position.Y += 24 * sin(digfrac * 1.8 * M_PI); @@ -590,8 +609,8 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 busytime, f32 tool_r wield_rotation *= core::RADTODEG; } else { f32 bobfrac = my_modf(m_view_bobbing_anim); - wield_position.X -= sin(bobfrac*M_PI*2.0) * 3.0; - wield_position.Y += sin(my_modf(bobfrac*2.0)*M_PI) * 3.0; + wield_position.X -= sin(bobfrac * M_PI * 2.0) * 3.0; + wield_position.Y += sin(my_modf(bobfrac * 2.0) * M_PI) * 3.0; } m_wieldnode->setPosition(wield_position); m_wieldnode->setRotation(wield_rotation); @@ -609,7 +628,8 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 busytime, f32 tool_r const bool movement_Y = fabs(speed.Y) > BS; const bool walking = movement_XZ && player->touching_ground; - const bool swimming = (movement_XZ || player->swimming_vertical) && player->in_liquid; + const bool swimming = + (movement_XZ || player->swimming_vertical) && player->in_liquid; const bool climbing = movement_Y && player->is_climbing; if ((walking || swimming || climbing) && !flying) { // Start animation @@ -628,18 +648,20 @@ void Camera::updateViewingRange() // Ignore near_plane setting on all other platforms to prevent abuse #if ENABLE_GLES - m_cameranode->setNearValue(rangelim( - g_settings->getFloat("near_plane"), 0.0f, 0.25f) * BS); + m_cameranode->setNearValue( + rangelim(g_settings->getFloat("near_plane"), 0.0f, 0.25f) * BS); #else m_cameranode->setNearValue(0.1f * BS); #endif - m_draw_control.wanted_range = std::fmin(adjustDist(viewing_range, getFovMax()), 4000); + m_draw_control.wanted_range = + std::fmin(adjustDist(viewing_range, getFovMax()), 4000); if (m_draw_control.range_all) { m_cameranode->setFarValue(100000.0); return; } - m_cameranode->setFarValue((viewing_range < 2000) ? 2000 * BS : viewing_range * BS); + m_cameranode->setFarValue( + (viewing_range < 2000) ? 2000 * BS : viewing_range * BS); } void Camera::setDigging(s32 button) @@ -660,23 +682,23 @@ void Camera::wield(const ItemStack &item) } } -void Camera::drawWieldedTool(irr::core::matrix4* translation) +void Camera::drawWieldedTool(irr::core::matrix4 *translation) { // Clear Z buffer so that the wielded tool stays in front of world geometry m_wieldmgr->getVideoDriver()->clearZBuffer(); // Draw the wielded node (in a separate scene manager) - scene::ICameraSceneNode* cam = m_wieldmgr->getActiveCamera(); + scene::ICameraSceneNode *cam = m_wieldmgr->getActiveCamera(); cam->setAspectRatio(m_cameranode->getAspectRatio()); - cam->setFOV(72.0*M_PI/180.0); + cam->setFOV(72.0 * M_PI / 180.0); cam->setNearValue(10); cam->setFarValue(1000); - if (translation != NULL) - { + if (translation != NULL) { irr::core::matrix4 startMatrix = cam->getAbsoluteTransformation(); - irr::core::vector3df focusPoint = (cam->getTarget() - - cam->getAbsolutePosition()).setLength(1) - + cam->getAbsolutePosition(); + irr::core::vector3df focusPoint = + (cam->getTarget() - cam->getAbsolutePosition()) + .setLength(1) + + cam->getAbsolutePosition(); irr::core::vector3df camera_pos = (startMatrix * *translation).getTranslation(); @@ -691,8 +713,7 @@ void Camera::drawNametags() core::matrix4 trans = m_cameranode->getProjectionMatrix(); trans *= m_cameranode->getViewMatrix(); - for (std::list::const_iterator - i = m_nametags.begin(); + for (std::list::const_iterator i = m_nametags.begin(); i != m_nametags.end(); ++i) { Nametag *nametag = *i; if (nametag->nametag_color.getAlpha() == 0) { @@ -701,27 +722,34 @@ void Camera::drawNametags() // shadow can remain. continue; } - v3f pos = nametag->parent_node->getAbsolutePosition() + nametag->nametag_pos * BS; - f32 transformed_pos[4] = { pos.X, pos.Y, pos.Z, 1.0f }; + v3f pos = nametag->parent_node->getAbsolutePosition() + + nametag->nametag_pos * BS; + f32 transformed_pos[4] = {pos.X, pos.Y, pos.Z, 1.0f}; trans.multiplyWith1x4Matrix(transformed_pos); if (transformed_pos[3] > 0) { - std::wstring nametag_colorless = - unescape_translate(utf8_to_wide(nametag->nametag_text)); + std::wstring nametag_colorless = unescape_translate( + utf8_to_wide(nametag->nametag_text)); core::dimension2d textsize = - g_fontengine->getFont()->getDimension( - nametag_colorless.c_str()); - f32 zDiv = transformed_pos[3] == 0.0f ? 1.0f : - core::reciprocal(transformed_pos[3]); - v2u32 screensize = RenderingEngine::get_video_driver()->getScreenSize(); + g_fontengine->getFont()->getDimension( + nametag_colorless.c_str()); + f32 zDiv = transformed_pos[3] == 0.0f + ? 1.0f + : core::reciprocal(transformed_pos[3]); + v2u32 screensize = RenderingEngine::get_video_driver() + ->getScreenSize(); v2s32 screen_pos; - screen_pos.X = screensize.X * - (0.5 * transformed_pos[0] * zDiv + 0.5) - textsize.Width / 2; - screen_pos.Y = screensize.Y * - (0.5 - transformed_pos[1] * zDiv * 0.5) - textsize.Height / 2; + screen_pos.X = screensize.X * (0.5 * transformed_pos[0] * zDiv + + 0.5) - + textsize.Width / 2; + screen_pos.Y = screensize.Y * (0.5 - transformed_pos[1] * zDiv * + 0.5) - + textsize.Height / 2; core::rect size(0, 0, textsize.Width, textsize.Height); g_fontengine->getFont()->draw( - translate_string(utf8_to_wide(nametag->nametag_text)).c_str(), - size + screen_pos, nametag->nametag_color); + translate_string( + utf8_to_wide(nametag->nametag_text)) + .c_str(), + size + screen_pos, nametag->nametag_color); } } } diff --git a/src/client/camera.h b/src/client/camera.h index 3a59637bc..bf10329ab 100644 --- a/src/client/camera.h +++ b/src/client/camera.h @@ -31,15 +31,13 @@ struct MapDrawControl; class Client; class WieldMeshSceneNode; -struct Nametag { - Nametag(scene::ISceneNode *a_parent_node, - const std::string &a_nametag_text, - const video::SColor &a_nametag_color, - const v3f &a_nametag_pos): - parent_node(a_parent_node), - nametag_text(a_nametag_text), - nametag_color(a_nametag_color), - nametag_pos(a_nametag_pos) +struct Nametag +{ + Nametag(scene::ISceneNode *a_parent_node, const std::string &a_nametag_text, + const video::SColor &a_nametag_color, const v3f &a_nametag_pos) : + parent_node(a_parent_node), + nametag_text(a_nametag_text), nametag_color(a_nametag_color), + nametag_pos(a_nametag_pos) { } scene::ISceneNode *parent_node; @@ -48,12 +46,17 @@ struct Nametag { v3f nametag_pos; }; -enum CameraMode {CAMERA_MODE_FIRST, CAMERA_MODE_THIRD, CAMERA_MODE_THIRD_FRONT}; +enum CameraMode +{ + CAMERA_MODE_FIRST, + CAMERA_MODE_THIRD, + CAMERA_MODE_THIRD_FRONT +}; /* - Client camera class, manages the player and camera scene nodes, the viewing distance - and performs view bobbing etc. It also displays the wielded tool in front of the - first-person camera. + Client camera class, manages the player and camera scene nodes, the viewing + distance and performs view bobbing etc. It also displays the wielded tool in front of + the first-person camera. */ class Camera { @@ -63,54 +66,30 @@ public: // Get camera scene node. // It has the eye transformation, pitch and view bobbing applied. - inline scene::ICameraSceneNode* getCameraNode() const - { - return m_cameranode; - } + inline scene::ICameraSceneNode *getCameraNode() const { return m_cameranode; } // Get the camera position (in absolute scene coordinates). // This has view bobbing applied. - inline v3f getPosition() const - { - return m_camera_position; - } + inline v3f getPosition() const { return m_camera_position; } // Returns the absolute position of the head SceneNode in the world - inline v3f getHeadPosition() const - { - return m_headnode->getAbsolutePosition(); - } + 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 - { - return m_camera_direction; - } + inline v3f getDirection() const { return m_camera_direction; } // Get the camera offset - inline v3s16 getOffset() const - { - return m_camera_offset; - } + inline v3s16 getOffset() const { return m_camera_offset; } // Horizontal field of view - inline f32 getFovX() const - { - return m_fov_x; - } + inline f32 getFovX() const { return m_fov_x; } // Vertical field of view - inline f32 getFovY() const - { - return m_fov_y; - } + inline f32 getFovY() const { return m_fov_y; } // Get maximum of getFovX() and getFovY() - inline f32 getFovMax() const - { - return MYMAX(m_fov_x, m_fov_y); - } + inline f32 getFovMax() const { return MYMAX(m_fov_x, m_fov_y); } // Notify about new server-sent FOV and initialize smooth FOV transition void notifyFovChange(); @@ -123,7 +102,7 @@ public: // Update the camera from the local player's position. // busytime is used to adjust the viewing range. - void update(LocalPlayer* player, f32 frametime, f32 busytime, + void update(LocalPlayer *player, f32 frametime, f32 busytime, f32 tool_reload_ratio); // Update render distance @@ -139,10 +118,11 @@ public: // Draw the wielded tool. // This has to happen *after* the main scene is drawn. // Warning: This clears the Z buffer. - void drawWieldedTool(irr::core::matrix4* translation=NULL); + void drawWieldedTool(irr::core::matrix4 *translation = NULL); // Toggle the current camera mode - void toggleCameraMode() { + void toggleCameraMode() + { if (m_camera_mode == CAMERA_MODE_FIRST) m_camera_mode = CAMERA_MODE_THIRD; else if (m_camera_mode == CAMERA_MODE_THIRD) @@ -152,20 +132,14 @@ public: } // Set the current camera mode - inline void setCameraMode(CameraMode mode) - { - m_camera_mode = mode; - } + inline void setCameraMode(CameraMode mode) { m_camera_mode = mode; } - //read the current camera mode - inline CameraMode getCameraMode() - { - return m_camera_mode; - } + // read the current camera mode + inline CameraMode getCameraMode() { return m_camera_mode; } Nametag *addNametag(scene::ISceneNode *parent_node, - const std::string &nametag_text, video::SColor nametag_color, - const v3f &pos); + const std::string &nametag_text, video::SColor nametag_color, + const v3f &pos); void removeNametag(Nametag *nametag); @@ -185,7 +159,7 @@ private: WieldMeshSceneNode *m_wieldnode = nullptr; // draw control - MapDrawControl& m_draw_control; + MapDrawControl &m_draw_control; Client *m_client; diff --git a/src/client/client.cpp b/src/client/client.cpp index aa3e257ac..54fbbf8b5 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -59,7 +59,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "chatmessage.h" #include "translation.h" -extern gui::IGUIEnvironment* guienv; +extern gui::IGUIEnvironment *guienv; /* Utility classes @@ -76,10 +76,11 @@ u32 PacketCounter::sum() const 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; + auto name = it.first >= TOCLIENT_NUM_MSG_TYPES + ? "?" + : toClientCommandTable[it.first].name; + o << "cmd " << it.first << " (" << name << ") count " << it.second + << std::endl; } } @@ -87,42 +88,23 @@ void PacketCounter::print(std::ostream &o) const Client */ -Client::Client( - const char *playername, - const std::string &password, - const std::string &address_name, - MapDrawControl &control, - IWritableTextureSource *tsrc, - IWritableShaderSource *shsrc, - IWritableItemDefManager *itemdef, - NodeDefManager *nodedef, - ISoundManager *sound, - MtEventManager *event, - bool ipv6, - GameUI *game_ui -): - m_mesh_update_thread(this), - m_tsrc(tsrc), - m_shsrc(shsrc), - m_itemdef(itemdef), - m_nodedef(nodedef), - m_sound(sound), - m_event(event), - m_env( - new ClientMap(this, control, 666), - tsrc, this - ), - m_particle_manager(&m_env), - m_con(new con::Connection(PROTOCOL_ID, 512, CONNECTION_TIMEOUT, ipv6, this)), - m_address_name(address_name), - m_server_ser_ver(SER_FMT_VER_INVALID), - m_last_chat_message_sent(time(NULL)), - m_password(password), - m_chosen_auth_mech(AUTH_MECHANISM_NONE), - m_media_downloader(new ClientMediaDownloader()), - m_state(LC_Created), - m_game_ui(game_ui), - m_modchannel_mgr(new ModChannelMgr()) +Client::Client(const char *playername, const std::string &password, + const std::string &address_name, MapDrawControl &control, + IWritableTextureSource *tsrc, IWritableShaderSource *shsrc, + IWritableItemDefManager *itemdef, NodeDefManager *nodedef, + ISoundManager *sound, MtEventManager *event, bool ipv6, GameUI *game_ui) : + m_mesh_update_thread(this), + m_tsrc(tsrc), m_shsrc(shsrc), m_itemdef(itemdef), m_nodedef(nodedef), + m_sound(sound), m_event(event), + m_env(new ClientMap(this, control, 666), tsrc, this), + m_particle_manager(&m_env), + m_con(new con::Connection( + PROTOCOL_ID, 512, CONNECTION_TIMEOUT, ipv6, this)), + m_address_name(address_name), m_server_ser_ver(SER_FMT_VER_INVALID), + m_last_chat_message_sent(time(NULL)), m_password(password), + m_chosen_auth_mech(AUTH_MECHANISM_NONE), + m_media_downloader(new ClientMediaDownloader()), m_state(LC_Created), + m_game_ui(game_ui), m_modchannel_mgr(new ModChannelMgr()) { // Add local player m_env.setLocalPlayer(new LocalPlayer(this, playername)); @@ -146,8 +128,8 @@ void Client::loadMods() // TODO Delete this code block when server-sent CSM and verifying of builtin are // complete. if (checkCSMRestrictionFlag(CSMRestrictionFlags::CSM_RF_LOAD_CLIENT_MODS)) { - warningstream << "Client-provided mod loading is disabled by server." << - std::endl; + warningstream << "Client-provided mod loading is disabled by server." + << std::endl; return; } @@ -191,7 +173,8 @@ void Client::loadMods() for (const ModSpec &mod : m_mods) { if (!string_allowed(mod.name, MODNAME_ALLOWED_CHARS)) { throw ModError("Error loading mod \"" + mod.name + - "\": Mod name does not follow naming conventions: " + "\": Mod name does not follow naming " + "conventions: " "Only characters [a-z0-9_] are allowed."); } scanModIntoMemory(mod.name, mod.path); @@ -225,13 +208,14 @@ bool Client::checkBuiltinIntegrity() } void Client::scanModSubfolder(const std::string &mod_name, const std::string &mod_path, - std::string mod_subpath) + std::string mod_subpath) { std::string full_path = mod_path + DIR_DELIM + mod_subpath; std::vector mod = fs::GetDirListing(full_path); for (const fs::DirListNode &j : mod) { if (j.dir) { - scanModSubfolder(mod_name, mod_path, mod_subpath + j.name + DIR_DELIM); + scanModSubfolder(mod_name, mod_path, + mod_subpath + j.name + DIR_DELIM); continue; } std::replace(mod_subpath.begin(), mod_subpath.end(), DIR_DELIM_CHAR, '/'); @@ -239,12 +223,12 @@ void Client::scanModSubfolder(const std::string &mod_name, const std::string &mo std::string real_path = full_path + j.name; std::string vfs_path = mod_name + ":" + mod_subpath + j.name; infostream << "Client::scanModSubfolder(): Loading \"" << real_path - << "\" as \"" << vfs_path << "\"." << std::endl; + << "\" as \"" << vfs_path << "\"." << std::endl; std::ifstream is(real_path, std::ios::binary | std::ios::ate); - if(!is.good()) { + if (!is.good()) { errorstream << "Client::scanModSubfolder(): Can't read file \"" - << real_path << "\"." << std::endl; + << real_path << "\"." << std::endl; continue; } auto size = is.tellg(); @@ -259,23 +243,25 @@ void Client::scanModSubfolder(const std::string &mod_name, const std::string &mo const std::string &Client::getBuiltinLuaPath() { - static const std::string builtin_dir = porting::path_share + DIR_DELIM + "builtin"; + static const std::string builtin_dir = + porting::path_share + DIR_DELIM + "builtin"; return builtin_dir; } const std::string &Client::getClientModsLuaPath() { - static const std::string clientmods_dir = porting::path_share + DIR_DELIM + "clientmods"; + static const std::string clientmods_dir = + porting::path_share + DIR_DELIM + "clientmods"; return clientmods_dir; } -const std::vector& Client::getMods() const +const std::vector &Client::getMods() const { static std::vector client_modspec_temp; return client_modspec_temp; } -const ModSpec* Client::getModSpec(const std::string &modname) const +const ModSpec *Client::getModSpec(const std::string &modname) const { return NULL; } @@ -285,7 +271,7 @@ void Client::Stop() m_shutdown = true; if (m_mods_loaded) m_script->on_shutdown(); - //request all client managed threads to stop + // request all client managed threads to stop m_mesh_update_thread.stop(); // Save local server map if (m_localdb) { @@ -316,7 +302,6 @@ Client::~Client() delete r.mesh; } - delete m_inventory_from_server; // Delete detached inventories @@ -326,7 +311,8 @@ Client::~Client() // cleanup 3d model meshes on client shutdown while (RenderingEngine::get_mesh_cache()->getMeshCount() != 0) { - scene::IAnimatedMesh *mesh = RenderingEngine::get_mesh_cache()->getMeshByIndex(0); + scene::IAnimatedMesh *mesh = + RenderingEngine::get_mesh_cache()->getMeshByIndex(0); if (mesh) RenderingEngine::get_mesh_cache()->removeMesh(mesh); @@ -352,7 +338,7 @@ void Client::step(float dtime) dtime = 2.0; m_animation_time += dtime; - if(m_animation_time > 60.0) + if (m_animation_time > 60.0) m_animation_time -= 60.0; m_time_of_day_update_timer += dtime; @@ -365,14 +351,14 @@ void Client::step(float dtime) { float &counter = m_packetcounter_timer; counter -= dtime; - if(counter <= 0.0f) - { + if (counter <= 0.0f) { counter = 30.0f; u32 sum = m_packetcounter.sum(); float avg = sum / counter; infostream << "Client packetcounter (" << counter << "s): " - << "sum=" << sum << " avg=" << avg << "/s" << std::endl; + << "sum=" << sum << " avg=" << avg << "/s" + << std::endl; m_packetcounter.print(infostream); m_packetcounter.clear(); } @@ -383,19 +369,19 @@ void Client::step(float dtime) static bool initial_step = true; if (initial_step) { initial_step = false; - } - else if(m_state == LC_Created) { + } else if (m_state == LC_Created) { if (m_is_registration_confirmation_state) { // Waiting confirmation return; } float &counter = m_connection_reinit_timer; counter -= dtime; - if(counter <= 0.0) { + if (counter <= 0.0) { counter = 2.0; LocalPlayer *myplayer = m_env.getLocalPlayer(); - FATAL_ERROR_IF(myplayer == NULL, "Local player not found in environment."); + FATAL_ERROR_IF(myplayer == NULL, + "Local player not found in environment."); sendInit(myplayer->getName()); } @@ -412,12 +398,12 @@ void Client::step(float dtime) Run Map's timers and unload unused data */ const float map_timer_and_unload_dtime = 5.25; - if(m_map_timer_and_unload_interval.step(dtime, map_timer_and_unload_dtime)) { + if (m_map_timer_and_unload_interval.step(dtime, map_timer_and_unload_dtime)) { std::vector deleted_blocks; m_env.getMap().timerUpdate(map_timer_and_unload_dtime, - g_settings->getFloat("client_unload_unused_data_timeout"), - g_settings->getS32("client_mapblock_limit"), - &deleted_blocks); + g_settings->getFloat("client_unload_unused_data_timeout"), + g_settings->getS32("client_mapblock_limit"), + &deleted_blocks); /* Send info to server @@ -426,9 +412,9 @@ void Client::step(float dtime) std::vector::iterator i = deleted_blocks.begin(); std::vector sendlist; - for(;;) { - if(sendlist.size() == 255 || i == deleted_blocks.end()) { - if(sendlist.empty()) + for (;;) { + if (sendlist.size() == 255 || i == deleted_blocks.end()) { + if (sendlist.empty()) break; /* [0] u16 command @@ -440,7 +426,7 @@ void Client::step(float dtime) sendDeletedBlocks(sendlist); - if(i == deleted_blocks.end()) + if (i == deleted_blocks.end()) break; sendlist.clear(); @@ -477,7 +463,8 @@ void Client::step(float dtime) if (envEvent.type == CEE_PLAYER_DAMAGE) { u16 damage = envEvent.player_damage.amount; - if (envEvent.player_damage.send_to_server && ! g_settings->getBool("prevent_natural_damage")) + if (envEvent.player_damage.send_to_server && + !g_settings->getBool("prevent_natural_damage")) sendDamage(damage); // Add to ClientEvent queue @@ -493,7 +480,7 @@ void Client::step(float dtime) */ float &counter = m_avg_rtt_timer; counter += dtime; - if(counter >= 10) { + if (counter >= 10) { counter = 0.0; // connectedAndInitialized() is true, peer exists. float avg_rtt = getRTT(); @@ -506,8 +493,7 @@ void Client::step(float dtime) { float &counter = m_playerpos_send_timer; counter += dtime; - if((m_state == LC_Ready) && (counter >= m_recommended_send_interval)) - { + if ((m_state == LC_Ready) && (counter >= m_recommended_send_interval)) { counter = 0.0; sendPlayerPos(); } @@ -519,14 +505,14 @@ void Client::step(float dtime) { int num_processed_meshes = 0; std::vector blocks_to_ack; - while (!m_mesh_update_thread.m_queue_out.empty()) - { + while (!m_mesh_update_thread.m_queue_out.empty()) { num_processed_meshes++; MinimapMapblock *minimap_mapblock = NULL; bool do_mapper_update = true; - MeshUpdateResult r = m_mesh_update_thread.m_queue_out.pop_frontNoEx(); + MeshUpdateResult r = + m_mesh_update_thread.m_queue_out.pop_frontNoEx(); MapBlock *block = m_env.getMap().getBlockNoCreateNoEx(r.p); if (block) { // Delete the old mesh @@ -540,7 +526,8 @@ void Client::step(float dtime) bool is_empty = true; for (int l = 0; l < MAX_TILE_LAYERS; l++) - if (r.mesh->getMesh(l)->getMeshBufferCount() != 0) + if (r.mesh->getMesh(l)->getMeshBufferCount() != + 0) is_empty = false; if (is_empty) @@ -566,12 +553,13 @@ void Client::step(float dtime) } } if (blocks_to_ack.size() > 0) { - // Acknowledge block(s) - sendGotBlocks(blocks_to_ack); + // Acknowledge block(s) + sendGotBlocks(blocks_to_ack); } if (num_processed_meshes > 0) - g_profiler->graphAdd("num_processed_meshes", num_processed_meshes); + g_profiler->graphAdd( + "num_processed_meshes", num_processed_meshes); } /* @@ -600,7 +588,8 @@ void Client::step(float dtime) if (count_after != count_before) { // Do this every seconds after TOCLIENT_INVENTORY - // Reset the locally changed inventory to the authoritative inventory + // Reset the locally changed inventory to the authoritative + // inventory player->inventory = *m_inventory_from_server; m_update_wielded_item = true; } @@ -624,16 +613,17 @@ void Client::step(float dtime) Handle removed remotely initiated sounds */ m_removed_sounds_check_timer += dtime; - if(m_removed_sounds_check_timer >= 2.32) { + if (m_removed_sounds_check_timer >= 2.32) { m_removed_sounds_check_timer = 0; // Find removed sounds and clear references to them std::vector removed_server_ids; - for (std::unordered_map::iterator i = m_sounds_server_to_client.begin(); + for (std::unordered_map::iterator i = + m_sounds_server_to_client.begin(); i != m_sounds_server_to_client.end();) { s32 server_id = i->first; int client_id = i->second; ++i; - if(!m_sound->soundExists(client_id)) { + if (!m_sound->soundExists(client_id)) { m_sounds_server_to_client.erase(server_id); m_sounds_client_to_server.erase(client_id); m_sounds_to_objects.erase(client_id); @@ -642,48 +632,47 @@ void Client::step(float dtime) } // Sync to server - if(!removed_server_ids.empty()) { + if (!removed_server_ids.empty()) { sendRemovedSounds(removed_server_ids); } } m_mod_storage_save_timer -= dtime; if (m_mod_storage_save_timer <= 0.0f) { - m_mod_storage_save_timer = g_settings->getFloat("server_map_save_interval"); + m_mod_storage_save_timer = + g_settings->getFloat("server_map_save_interval"); int n = 0; - for (std::unordered_map::const_iterator - it = m_mod_storages.begin(); it != m_mod_storages.end(); ++it) { + for (std::unordered_map::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; + infostream << "Saved " << n << " modified mod storages." + << std::endl; } // Write server map - if (m_localdb && m_localdb_save_interval.step(dtime, - m_cache_save_interval)) { + if (m_localdb && m_localdb_save_interval.step(dtime, m_cache_save_interval)) { m_localdb->endSave(); m_localdb->beginSave(); } } bool Client::loadMedia(const std::string &data, const std::string &filename, - bool from_media_push) + bool from_media_push) { std::string name; - const char *image_ext[] = { - ".png", ".jpg", ".bmp", ".tga", - ".pcx", ".ppm", ".psd", ".wal", ".rgb", - NULL - }; + const char *image_ext[] = {".png", ".jpg", ".bmp", ".tga", ".pcx", ".ppm", ".psd", + ".wal", ".rgb", NULL}; name = removeStringEnd(filename, image_ext); if (!name.empty()) { TRACESTREAM(<< "Client: Attempting to load image " - << "file \"" << filename << "\"" << std::endl); + << "file \"" << filename << "\"" << std::endl); io::IFileSystem *irrfs = RenderingEngine::get_filesystem(); video::IVideoDriver *vdrv = RenderingEngine::get_video_driver(); @@ -700,8 +689,8 @@ bool Client::loadMedia(const std::string &data, const std::string &filename, // Read image video::IImage *img = vdrv->createImageFromFile(rfile); if (!img) { - errorstream<<"Client: Cannot create image from data of " - <<"file \""<drop(); return false; } @@ -712,62 +701,53 @@ bool Client::loadMedia(const std::string &data, const std::string &filename, return true; } - const char *sound_ext[] = { - ".0.ogg", ".1.ogg", ".2.ogg", ".3.ogg", ".4.ogg", - ".5.ogg", ".6.ogg", ".7.ogg", ".8.ogg", ".9.ogg", - ".ogg", NULL - }; + const char *sound_ext[] = {".0.ogg", ".1.ogg", ".2.ogg", ".3.ogg", ".4.ogg", + ".5.ogg", ".6.ogg", ".7.ogg", ".8.ogg", ".9.ogg", ".ogg", NULL}; name = removeStringEnd(filename, sound_ext); if (!name.empty()) { TRACESTREAM(<< "Client: Attempting to load sound " - << "file \"" << filename << "\"" << std::endl); + << "file \"" << filename << "\"" << std::endl); return m_sound->loadSoundData(name, data); } - const char *model_ext[] = { - ".x", ".b3d", ".md2", ".obj", - NULL - }; + const char *model_ext[] = {".x", ".b3d", ".md2", ".obj", NULL}; name = removeStringEnd(filename, model_ext); if (!name.empty()) { - verbosestream<<"Client: Storing model into memory: " - <<"\""<loadTranslation(data); return true; } - errorstream << "Client: Don't know how to load file \"" - << filename << "\"" << std::endl; + errorstream << "Client: Don't know how to load file \"" << filename << "\"" + << std::endl; return false; } // Virtual methods from con::PeerHandler void Client::peerAdded(con::Peer *peer) { - infostream << "Client::peerAdded(): peer->id=" - << peer->id << std::endl; + infostream << "Client::peerAdded(): peer->id=" << peer->id << std::endl; } void Client::deletingPeer(con::Peer *peer, bool timeout) { infostream << "Client::deletingPeer(): " - "Server Peer is getting deleted " - << "(timeout=" << timeout << ")" << std::endl; + "Server Peer is getting deleted " + << "(timeout=" << timeout << ")" << std::endl; if (timeout) { m_access_denied = true; @@ -789,12 +769,13 @@ void Client::request_media(const std::vector &file_requests) writeU16(os, TOSERVER_REQUEST_MEDIA); size_t file_requests_size = file_requests.size(); - FATAL_ERROR_IF(file_requests_size > 0xFFFF, "Unsupported number of file requests"); + FATAL_ERROR_IF(file_requests_size > 0xFFFF, + "Unsupported number of file requests"); // Packet dynamicly resized NetworkPacket pkt(TOSERVER_REQUEST_MEDIA, 2 + 0); - pkt << (u16) (file_requests_size & 0xFFFF); + pkt << (u16)(file_requests_size & 0xFFFF); for (const std::string &file_request : file_requests) { pkt << file_request; @@ -803,23 +784,20 @@ void Client::request_media(const std::vector &file_requests) Send(&pkt); infostream << "Client: Sending media request list to server (" - << file_requests.size() << " files. packet size)" << std::endl; + << file_requests.size() << " files. packet size)" << std::endl; } -void Client::initLocalMapSaving(const Address &address, - const std::string &hostname, - bool is_local_server) +void Client::initLocalMapSaving( + const Address &address, const std::string &hostname, bool is_local_server) { if (!g_settings->getBool("enable_local_map_saving") || is_local_server) { return; } std::string world_path; -#define set_world_path(hostname) \ - world_path = porting::path_user \ - + DIR_DELIM + "worlds" \ - + DIR_DELIM + "server_" \ - + hostname + "_" + std::to_string(address.getPort()); +#define set_world_path(hostname) \ + world_path = porting::path_user + DIR_DELIM + "worlds" + DIR_DELIM + "server_" + \ + hostname + "_" + std::to_string(address.getPort()); set_world_path(hostname); if (!fs::IsDir(world_path)) { @@ -832,7 +810,8 @@ void Client::initLocalMapSaving(const Address &address, m_localdb = new MapDatabaseSQLite3(world_path); m_localdb->beginSave(); - actionstream << "Local map saving started, map will be saved at '" << world_path << "'" << std::endl; + actionstream << "Local map saving started, map will be saved at '" << world_path + << "'" << std::endl; } void Client::ReceiveAll() @@ -840,12 +819,13 @@ void Client::ReceiveAll() NetworkPacket pkt; u64 start_ms = porting::getTimeMs(); const u64 budget = 100; - for(;;) { + for (;;) { // Limit time even if there would be huge amounts of data to // process if (porting::getTimeMs() > start_ms + budget) { infostream << "Client::ReceiveAll(): " - "Packet processing budget exceeded." << std::endl; + "Packet processing budget exceeded." + << std::endl; break; } @@ -856,15 +836,15 @@ void Client::ReceiveAll() ProcessData(&pkt); } catch (const con::InvalidIncomingDataException &e) { infostream << "Client::ReceiveAll(): " - "InvalidIncomingDataException: what()=" - << e.what() << std::endl; + "InvalidIncomingDataException: what()=" + << e.what() << std::endl; } } } -inline void Client::handleCommand(NetworkPacket* pkt) +inline void Client::handleCommand(NetworkPacket *pkt) { - const ToClientCommandHandler& opHandle = toClientCommandTable[pkt->getCommand()]; + const ToClientCommandHandler &opHandle = toClientCommandTable[pkt->getCommand()]; (this->*opHandle.handler)(pkt); } @@ -873,10 +853,10 @@ inline void Client::handleCommand(NetworkPacket* pkt) */ void Client::ProcessData(NetworkPacket *pkt) { - ToClientCommand command = (ToClientCommand) pkt->getCommand(); + ToClientCommand command = (ToClientCommand)pkt->getCommand(); u32 sender_peer_id = pkt->getPeerId(); - //infostream<<"Client: received command="< &blocks) { NetworkPacket pkt(TOSERVER_DELETEDBLOCKS, 1 + sizeof(v3s16) * blocks.size()); - pkt << (u8) blocks.size(); + pkt << (u8)blocks.size(); for (const v3s16 &block : blocks) { pkt << block; @@ -1122,7 +1099,7 @@ void Client::sendDeletedBlocks(std::vector &blocks) void Client::sendGotBlocks(const std::vector &blocks) { NetworkPacket pkt(TOSERVER_GOTBLOCKS, 1 + 6 * blocks.size()); - pkt << (u8) blocks.size(); + pkt << (u8)blocks.size(); for (const v3s16 &block : blocks) pkt << block; @@ -1136,7 +1113,7 @@ void Client::sendRemovedSounds(std::vector &soundList) NetworkPacket pkt(TOSERVER_REMOVED_SOUNDS, 2 + server_ids * 4); - pkt << (u16) (server_ids & 0xFFFF); + pkt << (u16)(server_ids & 0xFFFF); for (s32 sound_id : soundList) pkt << sound_id; @@ -1144,8 +1121,8 @@ void Client::sendRemovedSounds(std::vector &soundList) Send(&pkt); } -void Client::sendNodemetaFields(v3s16 p, const std::string &formname, - const StringMap &fields) +void Client::sendNodemetaFields( + v3s16 p, const std::string &formname, const StringMap &fields) { size_t fields_size = fields.size(); @@ -1153,7 +1130,7 @@ void Client::sendNodemetaFields(v3s16 p, const std::string &formname, NetworkPacket pkt(TOSERVER_NODEMETA_FIELDS, 0); - pkt << p << formname << (u16) (fields_size & 0xFFFF); + pkt << p << formname << (u16)(fields_size & 0xFFFF); StringMap::const_iterator it; for (it = fields.begin(); it != fields.end(); ++it) { @@ -1166,18 +1143,17 @@ void Client::sendNodemetaFields(v3s16 p, const std::string &formname, Send(&pkt); } -void Client::sendInventoryFields(const std::string &formname, - const StringMap &fields) +void Client::sendInventoryFields(const std::string &formname, const StringMap &fields) { size_t fields_size = fields.size(); FATAL_ERROR_IF(fields_size > 0xFFFF, "Unsupported number of inventory fields"); NetworkPacket pkt(TOSERVER_INVENTORY_FIELDS, 0); - pkt << formname << (u16) (fields_size & 0xFFFF); + pkt << formname << (u16)(fields_size & 0xFFFF); StringMap::const_iterator it; for (it = fields.begin(); it != fields.end(); ++it) { - const std::string &name = it->first; + const std::string &name = it->first; const std::string &value = it->second; pkt << name; pkt.putLongString(value); @@ -1196,7 +1172,7 @@ void Client::sendInventoryAction(InventoryAction *a) std::string s = os.str(); NetworkPacket pkt(TOSERVER_INVENTORY_ACTION, s.size()); - pkt.putRawString(s.c_str(),s.size()); + pkt.putRawString(s.c_str(), s.size()); Send(&pkt); } @@ -1206,8 +1182,9 @@ bool Client::canSendChatMessage() const u32 now = time(NULL); float time_passed = now - m_last_chat_message_sent; - float virt_chat_message_allowance = m_chat_message_allowance + time_passed * - (CLIENT_CHAT_MESSAGE_LIMIT_PER_10S / 8.0f); + float virt_chat_message_allowance = + m_chat_message_allowance + + time_passed * (CLIENT_CHAT_MESSAGE_LIMIT_PER_10S / 8.0f); if (virt_chat_message_allowance < 1.0f) return false; @@ -1223,22 +1200,26 @@ void Client::sendChatMessage(const std::wstring &message) float time_passed = now - m_last_chat_message_sent; m_last_chat_message_sent = time(NULL); - m_chat_message_allowance += time_passed * (CLIENT_CHAT_MESSAGE_LIMIT_PER_10S / 8.0f); + m_chat_message_allowance += + time_passed * (CLIENT_CHAT_MESSAGE_LIMIT_PER_10S / 8.0f); if (m_chat_message_allowance > CLIENT_CHAT_MESSAGE_LIMIT_PER_10S) m_chat_message_allowance = CLIENT_CHAT_MESSAGE_LIMIT_PER_10S; m_chat_message_allowance -= 1.0f; - NetworkPacket pkt(TOSERVER_CHAT_MESSAGE, 2 + message.size() * sizeof(u16)); + NetworkPacket pkt( + TOSERVER_CHAT_MESSAGE, 2 + message.size() * sizeof(u16)); pkt << message; Send(&pkt); - } else if (m_out_chat_queue.size() < (u16) max_queue_size || max_queue_size == -1) { + } else if (m_out_chat_queue.size() < (u16)max_queue_size || + max_queue_size == -1) { m_out_chat_queue.push(message); } else { - infostream << "Could not queue chat message because maximum out chat queue size (" - << max_queue_size << ") is reached." << std::endl; + infostream << "Could not queue chat message because maximum out chat " + "queue size (" + << max_queue_size << ") is reached." << std::endl; } } @@ -1247,8 +1228,8 @@ void Client::clearOutChatQueue() m_out_chat_queue = std::queue(); } -void Client::sendChangePassword(const std::string &oldpassword, - const std::string &newpassword) +void Client::sendChangePassword( + const std::string &oldpassword, const std::string &newpassword) { LocalPlayer *player = m_env.getLocalPlayer(); if (player == NULL) @@ -1260,7 +1241,6 @@ void Client::sendChangePassword(const std::string &oldpassword, startAuth(choseAuthMech(m_sudo_auth_methods)); } - void Client::sendDamage(u16 damage) { NetworkPacket pkt(TOSERVER_DAMAGE, sizeof(u16)); @@ -1279,10 +1259,10 @@ void Client::sendReady() NetworkPacket pkt(TOSERVER_CLIENT_READY, 1 + 1 + 1 + 1 + 2 + sizeof(char) * strlen(g_version_hash) + 2); - pkt << (u8) VERSION_MAJOR << (u8) VERSION_MINOR << (u8) VERSION_PATCH - << (u8) 0 << (u16) strlen(g_version_hash); + pkt << (u8)VERSION_MAJOR << (u8)VERSION_MINOR << (u8)VERSION_PATCH << (u8)0 + << (u16)strlen(g_version_hash); - pkt.putRawString(g_version_hash, (u16) strlen(g_version_hash)); + pkt.putRawString(g_version_hash, (u16)strlen(g_version_hash)); pkt << (u16)FORMSPEC_API_VERSION; Send(&pkt); } @@ -1294,32 +1274,31 @@ void Client::sendPlayerPos(v3f pos) return; ClientMap &map = m_env.getClientMap(); - u8 camera_fov = map.getCameraFov(); + u8 camera_fov = map.getCameraFov(); u8 wanted_range = map.getControl().wanted_range; // Save bandwidth by only updating position when // player is not dead and something changed - // FIXME: This part causes breakages in mods like 3d_armor, and has been commented for now - // if (m_activeobjects_received && player->isDead()) + // FIXME: This part causes breakages in mods like 3d_armor, and has been commented + // for now if (m_activeobjects_received && player->isDead()) // return; - if ( - player->last_position == pos && - player->last_speed == player->getLegitSpeed() && - player->last_pitch == player->getPitch() && - player->last_yaw == player->getYaw() && - player->last_keyPressed == player->keyPressed && - player->last_camera_fov == camera_fov && + if (player->last_position == pos && + player->last_speed == player->getLegitSpeed() && + player->last_pitch == player->getPitch() && + player->last_yaw == player->getYaw() && + player->last_keyPressed == player->keyPressed && + player->last_camera_fov == camera_fov && player->last_wanted_range == wanted_range) return; - player->last_position = pos; - player->last_speed = player->getLegitSpeed(); - player->last_pitch = player->getPitch(); - player->last_yaw = player->getYaw(); - player->last_keyPressed = player->keyPressed; - player->last_camera_fov = camera_fov; + player->last_position = pos; + player->last_speed = player->getLegitSpeed(); + player->last_pitch = player->getPitch(); + player->last_yaw = player->getYaw(); + player->last_keyPressed = player->keyPressed; + player->last_camera_fov = camera_fov; player->last_wanted_range = wanted_range; NetworkPacket pkt(TOSERVER_PLAYERPOS, 12 + 12 + 4 + 4 + 4 + 1 + 1); @@ -1339,12 +1318,11 @@ void Client::sendPlayerPos() void Client::removeNode(v3s16 p) { - std::map modified_blocks; + std::map modified_blocks; try { m_env.getMap().removeNodeAndUpdate(p, modified_blocks); - } - catch(InvalidPositionException &e) { + } catch (InvalidPositionException &e) { } for (const auto &modified_block : modified_blocks) { @@ -1363,7 +1341,7 @@ MapNode Client::CSMGetNode(v3s16 p, bool *is_valid_position) { if (checkCSMRestrictionFlag(CSMRestrictionFlags::CSM_RF_LOOKUP_NODES)) { v3s16 ppos = floatToInt(m_env.getLocalPlayer()->getPosition(), BS); - if ((u32) ppos.getDistanceFrom(p) > m_csm_restriction_noderange) { + if ((u32)ppos.getDistanceFrom(p) > m_csm_restriction_noderange) { *is_valid_position = false; return {}; } @@ -1389,24 +1367,22 @@ v3s16 Client::CSMClampPos(v3s16 pos) return pos; v3s16 ppos = floatToInt(m_env.getLocalPlayer()->getPosition(), BS); const int range = m_csm_restriction_noderange; - return v3s16( - core::clamp(pos.X, (int)ppos.X - range, (int)ppos.X + range), - core::clamp(pos.Y, (int)ppos.Y - range, (int)ppos.Y + range), - core::clamp(pos.Z, (int)ppos.Z - range, (int)ppos.Z + range) - ); + return v3s16(core::clamp(pos.X, (int)ppos.X - range, (int)ppos.X + range), + core::clamp(pos.Y, (int)ppos.Y - range, (int)ppos.Y + range), + core::clamp( + pos.Z, (int)ppos.Z - range, (int)ppos.Z + range)); } void Client::addNode(v3s16 p, MapNode n, bool remove_metadata) { - //TimeTaker timer1("Client::addNode()"); + // TimeTaker timer1("Client::addNode()"); - std::map modified_blocks; + std::map modified_blocks; try { - //TimeTaker timer3("Client::addNode(): addNodeAndUpdate"); + // TimeTaker timer3("Client::addNode(): addNodeAndUpdate"); m_env.getMap().addNodeAndUpdate(p, n, modified_blocks, remove_metadata); - } - catch(InvalidPositionException &e) { + } catch (InvalidPositionException &e) { } for (const auto &modified_block : modified_blocks) { @@ -1450,35 +1426,28 @@ bool Client::updateWieldedItem() return true; } -Inventory* Client::getInventory(const InventoryLocation &loc) +Inventory *Client::getInventory(const InventoryLocation &loc) { - switch(loc.type){ - case InventoryLocation::UNDEFINED: - {} - break; + switch (loc.type) { + case InventoryLocation::UNDEFINED: { + } break; case InventoryLocation::PLAYER: - case InventoryLocation::CURRENT_PLAYER: - { + case InventoryLocation::CURRENT_PLAYER: { LocalPlayer *player = m_env.getLocalPlayer(); assert(player); return &player->inventory; - } - break; - case InventoryLocation::NODEMETA: - { + } break; + case InventoryLocation::NODEMETA: { NodeMetadata *meta = m_env.getMap().getNodeMetadata(loc.p); - if(!meta) + if (!meta) return NULL; return meta->getInventory(); - } - break; - case InventoryLocation::DETACHED: - { + } break; + case InventoryLocation::DETACHED: { if (m_detached_inventories.count(loc.name) == 0) return NULL; return m_detached_inventories[loc.name]; - } - break; + } break; default: FATAL_ERROR("Invalid inventory location type."); break; @@ -1525,13 +1494,11 @@ void Client::setCrack(int level, v3s16 pos) m_crack_level = level; m_crack_pos = pos; - if(old_crack_level >= 0 && (level < 0 || pos != old_crack_pos)) - { + if (old_crack_level >= 0 && (level < 0 || pos != old_crack_pos)) { // remove old crack addUpdateMeshTaskForNode(old_crack_pos, false, true); } - if(level >= 0 && (old_crack_level < 0 || pos != old_crack_pos)) - { + if (level >= 0 && (old_crack_level < 0 || pos != old_crack_pos)) { // add new crack addUpdateMeshTaskForNode(pos, false, true); } @@ -1555,20 +1522,20 @@ bool Client::getChatMessage(std::wstring &res) res = L""; switch (chatMessage->type) { - case CHATMESSAGE_TYPE_RAW: - case CHATMESSAGE_TYPE_ANNOUNCE: - case CHATMESSAGE_TYPE_SYSTEM: + case CHATMESSAGE_TYPE_RAW: + case CHATMESSAGE_TYPE_ANNOUNCE: + case CHATMESSAGE_TYPE_SYSTEM: + res = chatMessage->message; + break; + case CHATMESSAGE_TYPE_NORMAL: { + if (!chatMessage->sender.empty()) + res = L"<" + chatMessage->sender + L"> " + chatMessage->message; + else res = chatMessage->message; - break; - case CHATMESSAGE_TYPE_NORMAL: { - if (!chatMessage->sender.empty()) - res = L"<" + chatMessage->sender + L"> " + chatMessage->message; - else - res = chatMessage->message; - break; - } - default: - break; + break; + } + default: + break; } delete chatMessage; @@ -1603,19 +1570,18 @@ void Client::addUpdateMeshTask(v3s16 p, bool ack_to_server, bool urgent) void Client::addUpdateMeshTaskWithEdge(v3s16 blockpos, bool ack_to_server, bool urgent) { - try{ + try { addUpdateMeshTask(blockpos, ack_to_server, urgent); + } catch (InvalidPositionException &e) { } - catch(InvalidPositionException &e){} // Leading edge - for (int i=0;i<6;i++) - { - try{ + for (int i = 0; i < 6; i++) { + try { v3s16 p = blockpos + g_6dirs[i]; addUpdateMeshTask(p, false, urgent); + } catch (InvalidPositionException &e) { } - catch(InvalidPositionException &e){} } } @@ -1623,56 +1589,56 @@ void Client::addUpdateMeshTaskForNode(v3s16 nodepos, bool ack_to_server, bool ur { { v3s16 p = nodepos; - infostream<<"Client::addUpdateMeshTaskForNode(): " - <<"("<getPosition(), BS)); - + v3s16 currentBlock = getNodeBlockPos( + floatToInt(m_env.getLocalPlayer()->getPosition(), BS)); + for (s16 X = currentBlock.X - 2; X <= currentBlock.X + 2; X++) - for (s16 Y = currentBlock.Y - 2; Y <= currentBlock.Y + 2; Y++) - for (s16 Z = currentBlock.Z - 2; Z <= currentBlock.Z + 2; Z++) - addUpdateMeshTask(v3s16(X, Y, Z), false, true); - - std::map *sectors = m_env.getMap().getSectorsPtr(); - + for (s16 Y = currentBlock.Y - 2; Y <= currentBlock.Y + 2; Y++) + for (s16 Z = currentBlock.Z - 2; Z <= currentBlock.Z + 2; Z++) + addUpdateMeshTask(v3s16(X, Y, Z), false, true); + + std::map *sectors = m_env.getMap().getSectorsPtr(); + for (auto §or_it : *sectors) { MapSector *sector = sector_it.second; MapBlockVect blocks; @@ -1711,46 +1677,48 @@ float Client::mediaReceiveProgress() return 1.0; // downloader only exists when not yet done } -typedef struct TextureUpdateArgs { +typedef struct TextureUpdateArgs +{ gui::IGUIEnvironment *guienv; u64 last_time_ms; u16 last_percent; - const wchar_t* text_base; + const wchar_t *text_base; ITextureSource *tsrc; } TextureUpdateArgs; void texture_update_progress(void *args, u32 progress, u32 max_progress) { - TextureUpdateArgs* targs = (TextureUpdateArgs*) args; - u16 cur_percent = ceil(progress / (double) max_progress * 100.); + TextureUpdateArgs *targs = (TextureUpdateArgs *)args; + u16 cur_percent = ceil(progress / (double)max_progress * 100.); - // update the loading menu -- if neccessary - bool do_draw = false; - u64 time_ms = targs->last_time_ms; - if (cur_percent != targs->last_percent) { - targs->last_percent = cur_percent; - time_ms = porting::getTimeMs(); - // only draw when the user will notice something: - do_draw = (time_ms - targs->last_time_ms > 100); - } + // update the loading menu -- if neccessary + bool do_draw = false; + u64 time_ms = targs->last_time_ms; + if (cur_percent != targs->last_percent) { + targs->last_percent = cur_percent; + time_ms = porting::getTimeMs(); + // only draw when the user will notice something: + do_draw = (time_ms - targs->last_time_ms > 100); + } - if (do_draw) { - targs->last_time_ms = time_ms; - std::basic_stringstream strm; - strm << targs->text_base << " " << targs->last_percent << "%..."; - RenderingEngine::draw_load_screen(strm.str(), targs->guienv, targs->tsrc, 0, - 72 + (u16) ((18. / 100.) * (double) targs->last_percent), true); - } + if (do_draw) { + targs->last_time_ms = time_ms; + std::basic_stringstream strm; + strm << targs->text_base << " " << targs->last_percent << "%..."; + RenderingEngine::draw_load_screen(strm.str(), targs->guienv, targs->tsrc, + 0, 72 + (u16)((18. / 100.) * (double)targs->last_percent), + true); + } } void Client::afterContentReceived() { - infostream<<"Client::afterContentReceived() started"<rebuildImagesAndTextures(); delete[] text; // Rebuild shaders - infostream<<"- Rebuilding shaders"<rebuildShaders(); delete[] text; // Update node aliases - infostream<<"- Updating node aliases"<updateAliases(m_itemdef); 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_itemdef->applyTextureOverrides( + override_source.getItemTextureOverrides()); } m_nodedef->setNodeRegistrationStatus(true); m_nodedef->runNodeResolveCallbacks(); delete[] text; // Update node textures and assign shaders to each tile - infostream<<"- Updating node textures"<updateTextures(this, texture_update_progress, &tu_args); delete[] tu_args.text_base; // Start mesh update thread after setting up content definitions - infostream<<"- Starting mesh update thread"<getPeerStat(PEER_ID_SERVER,con::AVG_RTT); + return m_con->getPeerStat(PEER_ID_SERVER, con::AVG_RTT); } float Client::getCurRate() @@ -1825,7 +1794,7 @@ float Client::getCurRate() void Client::makeScreenshot() { irr::video::IVideoDriver *driver = RenderingEngine::get_video_driver(); - irr::video::IImage* const raw_image = driver->createScreenShot(); + irr::video::IImage *const raw_image = driver->createScreenShot(); if (!raw_image) return; @@ -1841,12 +1810,12 @@ void Client::makeScreenshot() 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"); + 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_base = screenshot_dir + DIR_DELIM + + std::string("screenshot_") + + std::string(timetstamp_c); std::string filename_ext = "." + g_settings->get("screenshot_format"); std::string filename; @@ -1861,18 +1830,20 @@ void Client::makeScreenshot() unsigned serial = 0; while (serial < SCREENSHOT_MAX_SERIAL_TRIES) { - filename = filename_base + (serial > 0 ? ("_" + itos(serial)) : "") + filename_ext; + filename = filename_base + (serial > 0 ? ("_" + itos(serial)) : "") + + filename_ext; std::ifstream tmp(filename.c_str()); if (!tmp.good()) - break; // File did not apparently exist, we'll go with it + break; // File did not apparently exist, we'll go with it serial++; } if (serial == SCREENSHOT_MAX_SERIAL_TRIES) { - infostream << "Could not find suitable filename for screenshot" << std::endl; + infostream << "Could not find suitable filename for screenshot" + << std::endl; } else { - irr::video::IImage* const image = - driver->createImage(video::ECF_R8G8B8, raw_image->getDimension()); + irr::video::IImage *const image = driver->createImage( + video::ECF_R8G8B8, raw_image->getDimension()); if (image) { raw_image->copyTo(image); @@ -1910,32 +1881,32 @@ void Client::showMinimap(const bool show) // IGameDef interface // Under envlock -IItemDefManager* Client::getItemDefManager() +IItemDefManager *Client::getItemDefManager() { return m_itemdef; } -IWritableItemDefManager* Client::getWritableItemDefManager() +IWritableItemDefManager *Client::getWritableItemDefManager() { return m_itemdef; } -const NodeDefManager* Client::getNodeDefManager() +const NodeDefManager *Client::getNodeDefManager() { return m_nodedef; } -NodeDefManager* Client::getWritableNodeDefManager() +NodeDefManager *Client::getWritableNodeDefManager() { return m_nodedef; } -ICraftDefManager* Client::getCraftDefManager() +ICraftDefManager *Client::getCraftDefManager() { return NULL; - //return m_craftdef; + // return m_craftdef; } -ITextureSource* Client::getTextureSource() +ITextureSource *Client::getTextureSource() { return m_tsrc; } -IWritableShaderSource* Client::getShaderSource() +IWritableShaderSource *Client::getShaderSource() { return m_shsrc; } @@ -1943,39 +1914,39 @@ IWritableShaderSource* Client::getShaderSource() u16 Client::allocateUnknownNodeId(const std::string &name) { errorstream << "Client::allocateUnknownNodeId(): " - << "Client cannot allocate node IDs" << std::endl; + << "Client cannot allocate node IDs" << std::endl; FATAL_ERROR("Client allocated unknown node"); return CONTENT_IGNORE; } -ISoundManager* Client::getSoundManager() +ISoundManager *Client::getSoundManager() { return m_sound; } -MtEventManager* Client::getEventManager() +MtEventManager *Client::getEventManager() { return m_event; } -ParticleManager* Client::getParticleManager() +ParticleManager *Client::getParticleManager() { return &m_particle_manager; } -scene::IAnimatedMesh* Client::getMesh(const std::string &filename, bool cache) +scene::IAnimatedMesh *Client::getMesh(const std::string &filename, bool cache) { StringMap::const_iterator it = m_mesh_data.find(filename); if (it == m_mesh_data.end()) { - errorstream << "Client::getMesh(): Mesh not found: \"" << filename - << "\"" << std::endl; + errorstream << "Client::getMesh(): Mesh not found: \"" << filename << "\"" + << std::endl; return NULL; } - const std::string &data = it->second; + const std::string &data = it->second; // Create the mesh, remove it from cache and return it // This allows unique vertex colors and other properties for each instance Buffer data_rw(data.c_str(), data.size()); // Const-incorrect Irrlicht - io::IReadFile *rfile = RenderingEngine::get_filesystem()->createMemoryReadFile( + io::IReadFile *rfile = RenderingEngine::get_filesystem()->createMemoryReadFile( *data_rw, data_rw.getSize(), filename.c_str()); FATAL_ERROR_IF(!rfile, "Could not create/open RAM file"); @@ -1987,7 +1958,7 @@ scene::IAnimatedMesh* Client::getMesh(const std::string &filename, bool cache) return mesh; } -const std::string* Client::getModFile(std::string filename) +const std::string *Client::getModFile(std::string filename) { // strip dir delimiter from beginning of path auto pos = filename.find_first_of(':'); @@ -2008,7 +1979,7 @@ bool Client::registerModStorage(ModMetadata *storage) { if (m_mod_storages.find(storage->getModName()) != m_mod_storages.end()) { errorstream << "Unable to register same mod storage twice. Storage name: " - << storage->getModName() << std::endl; + << storage->getModName() << std::endl; return false; } @@ -2019,7 +1990,7 @@ bool Client::registerModStorage(ModMetadata *storage) void Client::unregisterModStorage(const std::string &name) { std::unordered_map::const_iterator it = - m_mod_storages.find(name); + m_mod_storages.find(name); if (it != m_mod_storages.end()) { // Save unconditionaly on unregistration it->second->save(getModStoragePath()); @@ -2069,19 +2040,20 @@ bool Client::sendModChannelMessage(const std::string &channel, const std::string if (message.size() > STRING_MAX_LEN) { warningstream << "ModChannel message too long, dropping before sending " - << " (" << message.size() << " > " << STRING_MAX_LEN << ", channel: " - << channel << ")" << std::endl; + << " (" << message.size() << " > " << STRING_MAX_LEN + << ", channel: " << channel << ")" << std::endl; return false; } // @TODO: do some client rate limiting - NetworkPacket pkt(TOSERVER_MODCHANNEL_MSG, 2 + channel.size() + 2 + message.size()); + NetworkPacket pkt( + TOSERVER_MODCHANNEL_MSG, 2 + channel.size() + 2 + message.size()); pkt << channel << message; Send(&pkt); return true; } -ModChannel* Client::getModChannel(const std::string &channel) +ModChannel *Client::getModChannel(const std::string &channel) { return m_modchannel_mgr->getModChannel(channel); } diff --git a/src/client/client.h b/src/client/client.h index 7455d78dc..f00c331ab 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -49,7 +49,7 @@ class IWritableTextureSource; class IWritableShaderSource; class ISoundManager; class NodeDefManager; -//class IWritableCraftDefManager; +// class IWritableCraftDefManager; class ClientMediaDownloader; struct MapDrawControl; class ModChannelMgr; @@ -60,11 +60,13 @@ class Minimap; struct MinimapMapblock; class Camera; class NetworkPacket; -namespace con { +namespace con +{ class Connection; } -enum LocalClientState { +enum LocalClientState +{ LC_Created, LC_Init, LC_Ready @@ -88,10 +90,7 @@ public: n->second++; } - void clear() - { - m_packets.clear(); - } + void clear() { m_packets.clear(); } u32 sum() const; void print(std::ostream &o) const; @@ -111,28 +110,21 @@ public: NOTE: Nothing is thread-safe here. */ - Client( - const char *playername, - const std::string &password, - const std::string &address_name, - MapDrawControl &control, - IWritableTextureSource *tsrc, - IWritableShaderSource *shsrc, - IWritableItemDefManager *itemdef, - NodeDefManager *nodedef, - ISoundManager *sound, - MtEventManager *event, - bool ipv6, - GameUI *game_ui - ); + Client(const char *playername, const std::string &password, + const std::string &address_name, MapDrawControl &control, + IWritableTextureSource *tsrc, IWritableShaderSource *shsrc, + IWritableItemDefManager *itemdef, NodeDefManager *nodedef, + ISoundManager *sound, MtEventManager *event, bool ipv6, + GameUI *game_ui); ~Client(); DISABLE_CLASS_COPY(Client); // Load local mods into memory void scanModSubfolder(const std::string &mod_name, const std::string &mod_path, - std::string mod_subpath); - inline void scanModIntoMemory(const std::string &mod_name, const std::string &mod_path) + std::string mod_subpath); + inline void scanModIntoMemory( + const std::string &mod_name, const std::string &mod_path) { scanModSubfolder(mod_name, mod_path, ""); } @@ -142,7 +134,6 @@ public: */ void Stop(); - bool isShutdown(); /* @@ -163,58 +154,58 @@ public: * Command Handlers */ - void handleCommand(NetworkPacket* pkt); + void handleCommand(NetworkPacket *pkt); - void handleCommand_Null(NetworkPacket* pkt) {}; - void handleCommand_Deprecated(NetworkPacket* pkt); - void handleCommand_Hello(NetworkPacket* pkt); - void handleCommand_AuthAccept(NetworkPacket* pkt); - void handleCommand_AcceptSudoMode(NetworkPacket* pkt); - void handleCommand_DenySudoMode(NetworkPacket* pkt); - void handleCommand_AccessDenied(NetworkPacket* pkt); - void handleCommand_RemoveNode(NetworkPacket* pkt); - void handleCommand_AddNode(NetworkPacket* pkt); + void handleCommand_Null(NetworkPacket *pkt){}; + void handleCommand_Deprecated(NetworkPacket *pkt); + void handleCommand_Hello(NetworkPacket *pkt); + void handleCommand_AuthAccept(NetworkPacket *pkt); + void handleCommand_AcceptSudoMode(NetworkPacket *pkt); + void handleCommand_DenySudoMode(NetworkPacket *pkt); + void handleCommand_AccessDenied(NetworkPacket *pkt); + void handleCommand_RemoveNode(NetworkPacket *pkt); + void handleCommand_AddNode(NetworkPacket *pkt); void handleCommand_NodemetaChanged(NetworkPacket *pkt); - void handleCommand_BlockData(NetworkPacket* pkt); - void handleCommand_Inventory(NetworkPacket* pkt); - void handleCommand_TimeOfDay(NetworkPacket* pkt); + void handleCommand_BlockData(NetworkPacket *pkt); + void handleCommand_Inventory(NetworkPacket *pkt); + void handleCommand_TimeOfDay(NetworkPacket *pkt); void handleCommand_ChatMessage(NetworkPacket *pkt); - void handleCommand_ActiveObjectRemoveAdd(NetworkPacket* pkt); - void handleCommand_ActiveObjectMessages(NetworkPacket* pkt); - void handleCommand_Movement(NetworkPacket* pkt); + void handleCommand_ActiveObjectRemoveAdd(NetworkPacket *pkt); + void handleCommand_ActiveObjectMessages(NetworkPacket *pkt); + void handleCommand_Movement(NetworkPacket *pkt); void handleCommand_Fov(NetworkPacket *pkt); - void handleCommand_HP(NetworkPacket* pkt); - void handleCommand_Breath(NetworkPacket* pkt); - void handleCommand_MovePlayer(NetworkPacket* pkt); - void handleCommand_DeathScreen(NetworkPacket* pkt); - void handleCommand_AnnounceMedia(NetworkPacket* pkt); - void handleCommand_Media(NetworkPacket* pkt); - void handleCommand_NodeDef(NetworkPacket* pkt); - void handleCommand_ItemDef(NetworkPacket* pkt); - void handleCommand_PlaySound(NetworkPacket* pkt); - void handleCommand_StopSound(NetworkPacket* pkt); + void handleCommand_HP(NetworkPacket *pkt); + void handleCommand_Breath(NetworkPacket *pkt); + void handleCommand_MovePlayer(NetworkPacket *pkt); + void handleCommand_DeathScreen(NetworkPacket *pkt); + void handleCommand_AnnounceMedia(NetworkPacket *pkt); + void handleCommand_Media(NetworkPacket *pkt); + void handleCommand_NodeDef(NetworkPacket *pkt); + void handleCommand_ItemDef(NetworkPacket *pkt); + void handleCommand_PlaySound(NetworkPacket *pkt); + void handleCommand_StopSound(NetworkPacket *pkt); void handleCommand_FadeSound(NetworkPacket *pkt); - void handleCommand_Privileges(NetworkPacket* pkt); - void handleCommand_InventoryFormSpec(NetworkPacket* pkt); - void handleCommand_DetachedInventory(NetworkPacket* pkt); - void handleCommand_ShowFormSpec(NetworkPacket* pkt); - void handleCommand_SpawnParticle(NetworkPacket* pkt); - void handleCommand_AddParticleSpawner(NetworkPacket* pkt); - void handleCommand_DeleteParticleSpawner(NetworkPacket* pkt); - void handleCommand_HudAdd(NetworkPacket* pkt); - void handleCommand_HudRemove(NetworkPacket* pkt); - void handleCommand_HudChange(NetworkPacket* pkt); - void handleCommand_HudSetFlags(NetworkPacket* pkt); - void handleCommand_HudSetParam(NetworkPacket* pkt); - void handleCommand_HudSetSky(NetworkPacket* pkt); - void handleCommand_HudSetSun(NetworkPacket* pkt); - void handleCommand_HudSetMoon(NetworkPacket* pkt); - void handleCommand_HudSetStars(NetworkPacket* pkt); - void handleCommand_CloudParams(NetworkPacket* pkt); - void handleCommand_OverrideDayNightRatio(NetworkPacket* pkt); - void handleCommand_LocalPlayerAnimations(NetworkPacket* pkt); - void handleCommand_EyeOffset(NetworkPacket* pkt); - void handleCommand_UpdatePlayerList(NetworkPacket* pkt); + void handleCommand_Privileges(NetworkPacket *pkt); + void handleCommand_InventoryFormSpec(NetworkPacket *pkt); + void handleCommand_DetachedInventory(NetworkPacket *pkt); + void handleCommand_ShowFormSpec(NetworkPacket *pkt); + void handleCommand_SpawnParticle(NetworkPacket *pkt); + void handleCommand_AddParticleSpawner(NetworkPacket *pkt); + void handleCommand_DeleteParticleSpawner(NetworkPacket *pkt); + void handleCommand_HudAdd(NetworkPacket *pkt); + void handleCommand_HudRemove(NetworkPacket *pkt); + void handleCommand_HudChange(NetworkPacket *pkt); + void handleCommand_HudSetFlags(NetworkPacket *pkt); + void handleCommand_HudSetParam(NetworkPacket *pkt); + void handleCommand_HudSetSky(NetworkPacket *pkt); + void handleCommand_HudSetSun(NetworkPacket *pkt); + void handleCommand_HudSetMoon(NetworkPacket *pkt); + void handleCommand_HudSetStars(NetworkPacket *pkt); + void handleCommand_CloudParams(NetworkPacket *pkt); + void handleCommand_OverrideDayNightRatio(NetworkPacket *pkt); + void handleCommand_LocalPlayerAnimations(NetworkPacket *pkt); + void handleCommand_EyeOffset(NetworkPacket *pkt); + void handleCommand_UpdatePlayerList(NetworkPacket *pkt); void handleCommand_ModChannelMsg(NetworkPacket *pkt); void handleCommand_ModChannelSignal(NetworkPacket *pkt); void handleCommand_SrpBytesSandB(NetworkPacket *pkt); @@ -225,31 +216,30 @@ public: void ProcessData(NetworkPacket *pkt); - void Send(NetworkPacket* pkt); + void Send(NetworkPacket *pkt); void interact(InteractAction action, const PointedThing &pointed); - void sendNodemetaFields(v3s16 p, const std::string &formname, - const StringMap &fields); - void sendInventoryFields(const std::string &formname, - const StringMap &fields); + void sendNodemetaFields( + v3s16 p, const std::string &formname, const StringMap &fields); + void sendInventoryFields(const std::string &formname, const StringMap &fields); void sendInventoryAction(InventoryAction *a); void sendChatMessage(const std::wstring &message); void clearOutChatQueue(); - void sendChangePassword(const std::string &oldpassword, - const std::string &newpassword); + void sendChangePassword( + const std::string &oldpassword, const std::string &newpassword); void sendDamage(u16 damage); void sendRespawn(); void sendReady(); - ClientEnvironment& getEnv() { return m_env; } + ClientEnvironment &getEnv() { return m_env; } ITextureSource *tsrc() { return getTextureSource(); } ISoundManager *sound() { return getSoundManager(); } static const std::string &getBuiltinLuaPath(); static const std::string &getClientModsLuaPath(); const std::vector &getMods() const override; - const ModSpec* getModSpec(const std::string &modname) const override; + const ModSpec *getModSpec(const std::string &modname) const override; // Causes urgent mesh updates (unlike Map::add/removeNodeWithEvent) void removeNode(v3s16 p); @@ -268,7 +258,7 @@ public: bool updateWieldedItem(); /* InventoryManager interface */ - Inventory* getInventory(const InventoryLocation &loc) override; + Inventory *getInventory(const InventoryLocation &loc) override; void inventoryAction(InventoryAction *a) override; // Send the item number 'item' as player item to the server @@ -288,29 +278,40 @@ public: u16 getHP(); bool checkPrivilege(const std::string &priv) const - { return g_settings->getBool("priv_bypass") ? true : (m_privileges.count(priv) != 0); } + { + return g_settings->getBool("priv_bypass") + ? true + : (m_privileges.count(priv) != 0); + } const std::unordered_set &getPrivilegeList() const - { return m_privileges; } + { + return m_privileges; + } bool getChatMessage(std::wstring &message); - void typeChatMessage(const std::wstring& message); + void typeChatMessage(const std::wstring &message); - u64 getMapSeed(){ return m_map_seed; } + u64 getMapSeed() { return m_map_seed; } - void addUpdateMeshTask(v3s16 blockpos, bool ack_to_server=false, bool urgent=false); + void addUpdateMeshTask( + v3s16 blockpos, bool ack_to_server = false, bool urgent = false); // Including blocks at appropriate edges - void addUpdateMeshTaskWithEdge(v3s16 blockpos, bool ack_to_server=false, bool urgent=false); - void addUpdateMeshTaskForNode(v3s16 nodepos, bool ack_to_server=false, bool urgent=false); + void addUpdateMeshTaskWithEdge( + v3s16 blockpos, bool ack_to_server = false, bool urgent = false); + void addUpdateMeshTaskForNode( + v3s16 nodepos, bool ack_to_server = false, bool urgent = false); void updateAllMapBlocks(); void updateCameraOffset(v3s16 camera_offset) - { m_mesh_update_thread.m_camera_offset = camera_offset; } + { + m_mesh_update_thread.m_camera_offset = camera_offset; + } bool hasClientEvents() const { return !m_client_event_queue.empty(); } // Get event from queue. If queue is empty, it triggers an assertion failure. - ClientEvent * getClientEvent(); + ClientEvent *getClientEvent(); bool accessDenied() const { return m_access_denied; } @@ -326,17 +327,12 @@ public: // disconnect client when CSM failed. const std::string &accessDeniedReason() const { return m_access_denied_reason; } - const bool itemdefReceived() const - { return m_itemdef_received; } - const bool nodedefReceived() const - { return m_nodedef_received; } - const bool mediaReceived() const - { return !m_media_downloader; } - const bool activeObjectsReceived() const - { return m_activeobjects_received; } + const bool itemdefReceived() const { return m_itemdef_received; } + const bool nodedefReceived() const { return m_nodedef_received; } + const bool mediaReceived() const { return !m_media_downloader; } + const bool activeObjectsReceived() const { return m_activeobjects_received; } - u16 getProtoVersion() - { return m_proto_ver; } + u16 getProtoVersion() { return m_proto_ver; } bool connectedToServer(); void confirmRegistration(); @@ -350,28 +346,29 @@ public: float getRTT(); float getCurRate(); - Minimap* getMinimap() { return m_minimap; } - void setCamera(Camera* camera) { m_camera = camera; } + Minimap *getMinimap() { return m_minimap; } + void setCamera(Camera *camera) { m_camera = camera; } - Camera* getCamera () { return m_camera; } + Camera *getCamera() { return m_camera; } bool shouldShowMinimap() const; // IGameDef interface - IItemDefManager* getItemDefManager() override; - IWritableItemDefManager* getWritableItemDefManager() override; - const NodeDefManager* getNodeDefManager() override; - NodeDefManager* getWritableNodeDefManager() override; - ICraftDefManager* getCraftDefManager() override; - ITextureSource* getTextureSource(); - virtual IWritableShaderSource* getShaderSource(); + IItemDefManager *getItemDefManager() override; + IWritableItemDefManager *getWritableItemDefManager() override; + const NodeDefManager *getNodeDefManager() override; + NodeDefManager *getWritableNodeDefManager() override; + ICraftDefManager *getCraftDefManager() override; + ITextureSource *getTextureSource(); + virtual IWritableShaderSource *getShaderSource(); u16 allocateUnknownNodeId(const std::string &name) override; - virtual ISoundManager* getSoundManager(); - MtEventManager* getEventManager(); - virtual ParticleManager* getParticleManager(); - bool checkLocalPrivilege(const std::string &priv){ return checkPrivilege(priv); } - virtual scene::IAnimatedMesh* getMesh(const std::string &filename, bool cache = false); - const std::string* getModFile(std::string filename); + virtual ISoundManager *getSoundManager(); + MtEventManager *getEventManager(); + virtual ParticleManager *getParticleManager(); + bool checkLocalPrivilege(const std::string &priv) { return checkPrivilege(priv); } + virtual scene::IAnimatedMesh *getMesh( + const std::string &filename, bool cache = false); + const std::string *getModFile(std::string filename); std::string getModStoragePath() const override; bool registerModStorage(ModMetadata *meta) override; @@ -380,7 +377,7 @@ 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 from_media_push = false); + bool from_media_push = false); // Send a request for conventional media transfer void request_media(const std::vector &file_requests); @@ -388,10 +385,7 @@ public: void makeScreenshot(); - inline void pushToChatQueue(ChatMessage *cec) - { - m_chat_queue.push(cec); - } + inline void pushToChatQueue(ChatMessage *cec) { m_chat_queue.push(cec); } ClientScripting *getScript() { return m_script; } const bool modsLoaded() const { return m_mods_loaded; } @@ -402,26 +396,17 @@ public: const Address getServerAddress(); - const std::string &getAddressName() const - { - return m_address_name; - } + const std::string &getAddressName() const { return m_address_name; } - inline u64 getCSMRestrictionFlags() const - { - return m_csm_restriction_flags; - } + inline u64 getCSMRestrictionFlags() const { return m_csm_restriction_flags; } inline bool checkCSMRestrictionFlag(CSMRestrictionFlags flag) const { - //return m_csm_restriction_flags & flag; + // return m_csm_restriction_flags & flag; return false; } - u32 getCSMNodeRangeLimit() const - { - return m_csm_restriction_noderange; - } + u32 getCSMNodeRangeLimit() const { return m_csm_restriction_noderange; } inline std::unordered_map &getHUDTranslationMap() { @@ -430,19 +415,19 @@ public: bool joinModChannel(const std::string &channel) override; bool leaveModChannel(const std::string &channel) override; - bool sendModChannelMessage(const std::string &channel, - const std::string &message) override; + bool sendModChannelMessage( + const std::string &channel, const std::string &message) override; ModChannel *getModChannel(const std::string &channel) override; const std::string &getFormspecPrepend() const { return m_env.getLocalPlayer()->formspec_prepend; } - + void sendPlayerPos(v3f pos); void sendPlayerPos(); MeshUpdateThread m_mesh_update_thread; - + private: void loadMods(); bool checkBuiltinIntegrity(); @@ -451,13 +436,11 @@ private: void peerAdded(con::Peer *peer) override; void deletingPeer(con::Peer *peer, bool timeout) override; - void initLocalMapSaving(const Address &address, - const std::string &hostname, + void initLocalMapSaving(const Address &address, const std::string &hostname, bool is_local_server); void ReceiveAll(); - void deleteAuthData(); // helper method shared with clientpackethandler static AuthMechanism choseAuthMech(const u32 mechs); @@ -470,8 +453,7 @@ private: void sendRemovedSounds(std::vector &soundList); // Helper function - inline std::string getPlayerName() - { return m_env.getLocalPlayer()->getName(); } + inline std::string getPlayerName() { return m_env.getLocalPlayer()->getName(); } bool canSendChatMessage() const; @@ -488,7 +470,6 @@ private: ISoundManager *m_sound; MtEventManager *m_event; - ClientEnvironment m_env; ParticleManager m_particle_manager; std::unique_ptr m_con; @@ -516,8 +497,8 @@ private: int m_crack_level = -1; v3s16 m_crack_pos; // 0 <= m_daynight_i < DAYNIGHT_CACHE_COUNT - //s32 m_daynight_i; - //u32 m_daynight_ratio; + // s32 m_daynight_i; + // u32 m_daynight_ratio; std::queue m_out_chat_queue; u32 m_last_chat_message_sent; float m_chat_message_allowance = 5.0f; @@ -576,7 +557,7 @@ private: // Detached inventories // key = name - std::unordered_map m_detached_inventories; + std::unordered_map m_detached_inventories; // Storage for mesh data for creating multiple instances of the same mesh StringMap m_mesh_data; diff --git a/src/client/clientenvironment.cpp b/src/client/clientenvironment.cpp index 3f82bd316..2f95177f3 100644 --- a/src/client/clientenvironment.cpp +++ b/src/client/clientenvironment.cpp @@ -45,9 +45,7 @@ with this program; if not, write to the Free Software Foundation, Inc., class CAOShaderConstantSetter : public IShaderConstantSetter { public: - CAOShaderConstantSetter(): - m_emissive_color_setting("emissiveColor") - {} + CAOShaderConstantSetter() : m_emissive_color_setting("emissiveColor") {} ~CAOShaderConstantSetter() override = default; @@ -61,15 +59,15 @@ public: video::SColorf emissive_color(m_emissive_color); float as_array[4] = { - emissive_color.r, - emissive_color.g, - emissive_color.b, - emissive_color.a, + emissive_color.r, + emissive_color.g, + emissive_color.b, + emissive_color.a, }; m_emissive_color_setting.set(as_array, services); } - void onSetMaterial(const video::SMaterial& material) override + void onSetMaterial(const video::SMaterial &material) override { m_emissive_color = material.EmissiveColor; } @@ -82,25 +80,19 @@ private: class CAOShaderConstantSetterFactory : public IShaderConstantSetterFactory { public: - CAOShaderConstantSetterFactory() - {} + CAOShaderConstantSetterFactory() {} - virtual IShaderConstantSetter* create() - { - return new CAOShaderConstantSetter(); - } + virtual IShaderConstantSetter *create() { return new CAOShaderConstantSetter(); } }; /* ClientEnvironment */ -ClientEnvironment::ClientEnvironment(ClientMap *map, - ITextureSource *texturesource, Client *client): - Environment(client), - m_map(map), - m_texturesource(texturesource), - m_client(client) +ClientEnvironment::ClientEnvironment( + ClientMap *map, ITextureSource *texturesource, Client *client) : + Environment(client), + m_map(map), m_texturesource(texturesource), m_client(client) { auto *shdrsrc = m_client->getShaderSource(); shdrsrc->addShaderConstantSetterFactory(new CAOShaderConstantSetterFactory()); @@ -120,12 +112,12 @@ ClientEnvironment::~ClientEnvironment() delete m_local_player; } -Map & ClientEnvironment::getMap() +Map &ClientEnvironment::getMap() { return *m_map; } -ClientMap & ClientEnvironment::getClientMap() +ClientMap &ClientEnvironment::getClientMap() { return *m_map; } @@ -135,8 +127,7 @@ void ClientEnvironment::setLocalPlayer(LocalPlayer *player) /* It is a failure if already is a local player */ - FATAL_ERROR_IF(m_local_player != NULL, - "Local player already allocated"); + FATAL_ERROR_IF(m_local_player != NULL, "Local player already allocated"); m_local_player = player; } @@ -166,21 +157,21 @@ void ClientEnvironment::step(float dtime) /* Maximum position increment */ - //f32 position_max_increment = 0.05*BS; - f32 position_max_increment = 0.1*BS; + // f32 position_max_increment = 0.05*BS; + f32 position_max_increment = 0.1 * BS; // Maximum time increment (for collision detection etc) // time = distance / speed f32 dtime_max_increment = 1; - if(player_speed > 0.001) + if (player_speed > 0.001) dtime_max_increment = position_max_increment / player_speed; // Maximum time increment is 10ms or lower - if(dtime_max_increment > 0.01) + if (dtime_max_increment > 0.01) dtime_max_increment = 0.01; // Don't allow overly huge dtime - if(dtime > 0.5) + if (dtime > 0.5) dtime = 0.5; f32 dtime_downcount = dtime; @@ -190,23 +181,19 @@ void ClientEnvironment::step(float dtime) */ u32 loopcount = 0; - do - { + do { loopcount++; f32 dtime_part; - if(dtime_downcount > dtime_max_increment) - { + if (dtime_downcount > dtime_max_increment) { dtime_part = dtime_max_increment; dtime_downcount -= dtime_part; - } - else - { + } else { dtime_part = dtime_downcount; /* - Setting this to 0 (no -=dtime_part) disables an infinite loop - when dtime_part is so small that dtime_downcount -= dtime_part - does nothing + Setting this to 0 (no -=dtime_part) disables an infinite + loop when dtime_part is so small that dtime_downcount -= + dtime_part does nothing */ dtime_downcount = 0; } @@ -220,33 +207,40 @@ void ClientEnvironment::step(float dtime) lplayer->applyControl(dtime_part, this); // Apply physics - if (!free_move && !is_climbing && ! g_settings->getBool("freecam")) { + if (!free_move && !is_climbing && + !g_settings->getBool("freecam")) { // Gravity v3f speed = lplayer->getSpeed(); if (!lplayer->in_liquid) speed.Y -= lplayer->movement_gravity * - lplayer->physics_override_gravity * dtime_part * 2.0f; + lplayer->physics_override_gravity * + dtime_part * 2.0f; // Liquid floating / sinking if (lplayer->in_liquid && !lplayer->swimming_vertical && !lplayer->swimming_pitch) - speed.Y -= lplayer->movement_liquid_sink * dtime_part * 2.0f; + speed.Y -= lplayer->movement_liquid_sink * + dtime_part * 2.0f; // Liquid resistance if (lplayer->in_liquid_stable || lplayer->in_liquid) { - // How much the node's viscosity blocks movement, ranges - // between 0 and 1. Should match the scale at which viscosity - // increase affects other liquid attributes. + // How much the node's viscosity blocks movement, + // ranges between 0 and 1. Should match the scale + // at which viscosity increase affects other + // liquid attributes. static const f32 viscosity_factor = 0.3f; - v3f d_wanted = -speed / lplayer->movement_liquid_fluidity; + v3f d_wanted = -speed / + lplayer->movement_liquid_fluidity; f32 dl = d_wanted.getLength(); if (dl > lplayer->movement_liquid_fluidity_smooth) dl = lplayer->movement_liquid_fluidity_smooth; - dl *= (lplayer->liquid_viscosity * viscosity_factor) + - (1 - viscosity_factor); - v3f d = d_wanted.normalize() * (dl * dtime_part * 100.0f); + dl *= (lplayer->liquid_viscosity * + viscosity_factor) + + (1 - viscosity_factor); + v3f d = d_wanted.normalize() * + (dl * dtime_part * 100.0f); speed += d; } @@ -258,14 +252,15 @@ void ClientEnvironment::step(float dtime) This also does collision detection. */ lplayer->move(dtime_part, this, position_max_increment, - &player_collisions); + &player_collisions); } } while (dtime_downcount > 0.001); bool player_immortal = lplayer->getCAO() && lplayer->getCAO()->isImmortal(); for (const CollisionInfo &info : player_collisions) { - v3f speed_diff = info.new_speed - info.old_speed;; + v3f speed_diff = info.new_speed - info.old_speed; + ; // Handle only fall damage // (because otherwise walking against something in fast_move kills you) if (speed_diff.Y < 0 || info.old_speed.Y >= 0) @@ -273,12 +268,12 @@ void ClientEnvironment::step(float dtime) // Get rid of other components speed_diff.X = 0; speed_diff.Z = 0; - f32 pre_factor = 1; // 1 hp per node/s - f32 tolerance = BS*14; // 5 without damage - f32 post_factor = 1; // 1 hp per node/s + f32 pre_factor = 1; // 1 hp per node/s + f32 tolerance = BS * 14; // 5 without damage + f32 post_factor = 1; // 1 hp per node/s if (info.type == COLLISION_NODE) { - const ContentFeatures &f = m_client->ndef()-> - get(m_map->getNode(info.node_p)); + const ContentFeatures &f = m_client->ndef()->get( + m_map->getNode(info.node_p)); // Determine fall damage multiplier int addp = itemgroup_get(f.groups, "fall_damage_add_percent"); pre_factor = 1.0f + (float)addp / 100.0f; @@ -289,8 +284,8 @@ void ClientEnvironment::step(float dtime) u16 damage = (u16)MYMIN(damage_f + 0.5, U16_MAX); if (damage != 0) { damageLocalPlayer(damage, true); - m_client->getEventManager()->put( - new SimpleTriggerEvent(MtEvent::PLAYER_FALLING_DAMAGE)); + m_client->getEventManager()->put(new SimpleTriggerEvent( + MtEvent::PLAYER_FALLING_DAMAGE)); } } } @@ -319,7 +314,8 @@ void ClientEnvironment::step(float dtime) */ bool update_lighting = m_active_object_light_update_interval.step(dtime, 0.21); - auto cb_state = [this, dtime, update_lighting, day_night_ratio] (ClientActiveObject *cao) { + auto cb_state = [this, dtime, update_lighting, day_night_ratio]( + ClientActiveObject *cao) { // Step object cao->step(dtime, this); @@ -337,11 +333,10 @@ void ClientEnvironment::step(float dtime) ClientSimpleObject *simple = *i; simple->step(dtime); - if(simple->m_to_be_removed) { + if (simple->m_to_be_removed) { delete simple; i = m_simple_objects.erase(i); - } - else { + } else { ++i; } } @@ -352,20 +347,18 @@ void ClientEnvironment::addSimpleObject(ClientSimpleObject *simple) m_simple_objects.push_back(simple); } -GenericCAO* ClientEnvironment::getGenericCAO(u16 id) +GenericCAO *ClientEnvironment::getGenericCAO(u16 id) { ClientActiveObject *obj = getActiveObject(id); if (obj && obj->getType() == ACTIVEOBJECT_TYPE_GENERIC) - return (GenericCAO*) obj; + return (GenericCAO *)obj; return NULL; } -bool isFreeClientActiveObjectId(const u16 id, - ClientActiveObjectMap &objects) +bool isFreeClientActiveObjectId(const u16 id, ClientActiveObjectMap &objects) { return id != 0 && objects.find(id) == objects.end(); - } u16 getFreeClientActiveObjectId(ClientActiveObjectMap &objects) @@ -373,8 +366,8 @@ u16 getFreeClientActiveObjectId(ClientActiveObjectMap &objects) // try to reuse id's as late as possible static u16 last_used_id = 0; u16 startid = last_used_id; - for(;;) { - last_used_id ++; + for (;;) { + last_used_id++; if (isFreeClientActiveObjectId(last_used_id, objects)) return last_used_id; @@ -396,33 +389,27 @@ u16 ClientEnvironment::addActiveObject(ClientActiveObject *object) return object->getId(); } -void ClientEnvironment::addActiveObject(u16 id, u8 type, - const std::string &init_data) +void ClientEnvironment::addActiveObject(u16 id, u8 type, const std::string &init_data) { - ClientActiveObject* obj = - ClientActiveObject::create((ActiveObjectType) type, m_client, this); - if(obj == NULL) - { - infostream<<"ClientEnvironment::addActiveObject(): " - <<"id="<setId(id); - try - { + try { obj->initialize(init_data); - } - catch(SerializationError &e) - { - errorstream<<"ClientEnvironment::addActiveObject():" - <<" id="<processMessage(data); } catch (SerializationError &e) { - errorstream<<"ClientEnvironment::processActiveObjectMessage():" - << " id=" << id << " type=" << obj->getType() - << " SerializationError in processMessage(): " << e.what() - << std::endl; + errorstream << "ClientEnvironment::processActiveObjectMessage():" + << " id=" << id << " type=" << obj->getType() + << " SerializationError in processMessage(): " << e.what() + << std::endl; } } @@ -513,12 +499,12 @@ ClientEnvEvent ClientEnvironment::getClientEnvEvent() } void ClientEnvironment::getSelectedActiveObjects( - const core::line3d &shootline_on_map, - std::vector &objects) + const core::line3d &shootline_on_map, + std::vector &objects) { std::vector allObjects; - getActiveObjects(shootline_on_map.start, - shootline_on_map.getLength() + 10.0f, allObjects); + getActiveObjects(shootline_on_map.start, shootline_on_map.getLength() + 10.0f, + allObjects); const v3f line_vector = shootline_on_map.getVector(); for (const auto &allObject : allObjects) { @@ -528,15 +514,17 @@ void ClientEnvironment::getSelectedActiveObjects( continue; const v3f &pos = obj->getPosition(); - aabb3f offsetted_box(selection_box.MinEdge + pos, - selection_box.MaxEdge + pos); + aabb3f offsetted_box( + selection_box.MinEdge + pos, selection_box.MaxEdge + pos); v3f current_intersection; v3s16 current_normal; if (boxLineCollision(offsetted_box, shootline_on_map.start, line_vector, - ¤t_intersection, ¤t_normal)) { - objects.emplace_back((s16) obj->getId(), current_intersection, current_normal, - (current_intersection - shootline_on_map.start).getLengthSQ()); + ¤t_intersection, ¤t_normal)) { + objects.emplace_back((s16)obj->getId(), current_intersection, + current_normal, + (current_intersection - shootline_on_map.start) + .getLengthSQ()); } } } diff --git a/src/client/clientenvironment.h b/src/client/clientenvironment.h index 52d999c99..a24ea209a 100644 --- a/src/client/clientenvironment.h +++ b/src/client/clientenvironment.h @@ -49,25 +49,27 @@ enum ClientEnvEventType struct ClientEnvEvent { ClientEnvEventType type; - union { - //struct{ + union + { + // struct{ //} none; - struct{ + struct + { u16 amount; bool send_to_server; } player_damage; }; }; -typedef std::unordered_map ClientActiveObjectMap; +typedef std::unordered_map ClientActiveObjectMap; class ClientEnvironment : public Environment { public: ClientEnvironment(ClientMap *map, ITextureSource *texturesource, Client *client); ~ClientEnvironment(); - Map & getMap(); - ClientMap & getClientMap(); + Map &getMap(); + ClientMap &getClientMap(); Client *getGameDef() { return m_client; } void setScript(ClientScripting *script) { m_script = script; } @@ -87,13 +89,13 @@ public: ActiveObjects */ - GenericCAO* getGenericCAO(u16 id); - ClientActiveObject* getActiveObject(u16 id) + GenericCAO *getGenericCAO(u16 id); + ClientActiveObject *getActiveObject(u16 id) { return m_ao_manager.getActiveObject(id); } - - std::unordered_map getAllActiveObjects() + + std::unordered_map getAllActiveObjects() { return m_ao_manager.getAllActiveObjects(); } @@ -117,7 +119,7 @@ public: Callbacks for activeobjects */ - void damageLocalPlayer(u16 damage, bool handle_hp=true); + void damageLocalPlayer(u16 damage, bool handle_hp = true); /* Client likes to call these @@ -125,7 +127,7 @@ public: // Get all nearby objects void getActiveObjects(const v3f &origin, f32 max_d, - std::vector &dest) + std::vector &dest) { return m_ao_manager.getActiveObjects(origin, max_d, dest); } @@ -135,17 +137,18 @@ public: // Get event from queue. If queue is empty, it triggers an assertion failure. ClientEnvEvent getClientEnvEvent(); - virtual void getSelectedActiveObjects( - const core::line3d &shootline_on_map, - std::vector &objects - ); + virtual void getSelectedActiveObjects(const core::line3d &shootline_on_map, + std::vector &objects); const std::list &getPlayerNames() { return m_player_names; } void addPlayerName(const std::string &name) { m_player_names.push_back(name); } void removePlayerName(const std::string &name) { m_player_names.remove(name); } void updateCameraOffset(const v3s16 &camera_offset) - { m_camera_offset = camera_offset; } + { + m_camera_offset = camera_offset; + } v3s16 getCameraOffset() const { return m_camera_offset; } + private: ClientMap *m_map; LocalPlayer *m_local_player = nullptr; @@ -153,7 +156,7 @@ private: Client *m_client; ClientScripting *m_script = nullptr; client::ActiveObjectMgr m_ao_manager; - std::vector m_simple_objects; + std::vector m_simple_objects; std::queue m_client_event_queue; IntervalLimiter m_active_object_light_update_interval; std::list m_player_names; diff --git a/src/client/clientlauncher.cpp b/src/client/clientlauncher.cpp index ce16797e6..b96c7e7f6 100644 --- a/src/client/clientlauncher.cpp +++ b/src/client/clientlauncher.cpp @@ -36,10 +36,10 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "network/networkexceptions.h" #if USE_SOUND - #include "sound_openal.h" +#include "sound_openal.h" #endif #ifdef __ANDROID__ - #include "porting.h" +#include "porting.h" #endif /* mainmenumanager.h @@ -87,7 +87,6 @@ ClientLauncher::~ClientLauncher() #endif } - bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args) { /* This function is called when a client must be started. @@ -95,7 +94,7 @@ bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args) * - Singleplayer (address but map provided) * - Join server (no map but address provided) * - Local server (for main menu only) - */ + */ init_args(start_data, cmd_args); @@ -131,7 +130,7 @@ bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args) This changes the minimum allowed number of vertices in a VBO. Default is 500. */ - //driver->setMinHardwareBufferVertexCount(50); + // driver->setMinHardwareBufferVertexCount(50); // Create game callback for menus g_gamecallback = new MainGameCallback(); @@ -140,8 +139,8 @@ bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args) init_input(); - RenderingEngine::get_scene_manager()->getParameters()-> - setAttribute(scene::ALLOW_ZWRITE_ON_TRANSPARENT, true); + RenderingEngine::get_scene_manager()->getParameters()->setAttribute( + scene::ALLOW_ZWRITE_ON_TRANSPARENT, true); guienv = RenderingEngine::get_gui_env(); skin = RenderingEngine::get_gui_env()->getSkin(); @@ -178,7 +177,8 @@ bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args) g_fontengine = new FontEngine(g_settings, guienv); FATAL_ERROR_IF(g_fontengine == NULL, "Font engine creation failed."); -#if (IRRLICHT_VERSION_MAJOR >= 1 && IRRLICHT_VERSION_MINOR >= 8) || IRRLICHT_VERSION_MAJOR >= 2 +#if (IRRLICHT_VERSION_MAJOR >= 1 && IRRLICHT_VERSION_MINOR >= 8) || \ + IRRLICHT_VERSION_MAJOR >= 2 // Irrlicht 1.8 input colours skin->setColor(gui::EGDC_EDITABLE, video::SColor(255, 128, 128, 128)); skin->setColor(gui::EGDC_FOCUSED_EDITABLE, video::SColor(255, 96, 134, 49)); @@ -186,12 +186,13 @@ bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args) // Create the menu clouds if (!g_menucloudsmgr) - g_menucloudsmgr = RenderingEngine::get_scene_manager()->createNewSceneManager(); + g_menucloudsmgr = RenderingEngine::get_scene_manager() + ->createNewSceneManager(); if (!g_menuclouds) g_menuclouds = new Clouds(g_menucloudsmgr, -1, rand()); g_menuclouds->setHeight(100.0f); g_menuclouds->update(v3f(0, 0, 0), video::SColor(255, 240, 240, 255)); - scene::ICameraSceneNode* camera; + scene::ICameraSceneNode *camera; camera = g_menucloudsmgr->addCameraSceneNode(NULL, v3f(0, 0, 0), v3f(0, 60, 100)); camera->setFarValue(10000); @@ -214,17 +215,17 @@ bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args) bool retval = true; bool *kill = porting::signal_handler_killstatus(); - while (RenderingEngine::run() && !*kill && - !g_gamecallback->shutdown_requested) { + while (RenderingEngine::run() && !*kill && !g_gamecallback->shutdown_requested) { // Set the window caption const wchar_t *text = wgettext("Main Menu"); - RenderingEngine::get_raw_device()-> - setWindowCaption((utf8_to_wide(PROJECT_NAME_C) + - L" " + utf8_to_wide(g_version_hash) + - L" [" + text + L"]").c_str()); + RenderingEngine::get_raw_device()->setWindowCaption( + (utf8_to_wide(PROJECT_NAME_C) + L" " + + utf8_to_wide(g_version_hash) + L" [" + + text + L"]") + .c_str()); delete[] text; - try { // This is used for catching disconnects + try { // This is used for catching disconnects RenderingEngine::get_gui_env()->clear(); @@ -233,11 +234,11 @@ bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args) custom gui elements directly on the screen. Otherwise they won't be automatically drawn. */ - guiroot = RenderingEngine::get_gui_env()->addStaticText(L"", - core::rect(0, 0, 10000, 10000)); + guiroot = RenderingEngine::get_gui_env()->addStaticText( + L"", core::rect(0, 0, 10000, 10000)); - bool game_has_run = launch_game(error_message, reconnect_requested, - start_data, cmd_args); + bool game_has_run = launch_game(error_message, + reconnect_requested, start_data, cmd_args); // Reset the reconnect_requested flag reconnect_requested = false; @@ -258,26 +259,23 @@ bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args) // Break out of menu-game loop to shut down cleanly if (!RenderingEngine::get_raw_device()->run() || *kill) { if (!g_settings_path.empty()) - g_settings->updateConfigFile(g_settings_path.c_str()); + g_settings->updateConfigFile( + g_settings_path.c_str()); break; } RenderingEngine::get_video_driver()->setTextureCreationFlag( - video::ETCF_CREATE_MIP_MAPS, g_settings->getBool("mip_map")); + video::ETCF_CREATE_MIP_MAPS, + g_settings->getBool("mip_map")); #ifdef HAVE_TOUCHSCREENGUI - receiver->m_touchscreengui = new TouchScreenGUI(RenderingEngine::get_raw_device(), receiver); + receiver->m_touchscreengui = new TouchScreenGUI( + RenderingEngine::get_raw_device(), receiver); g_touchscreengui = receiver->m_touchscreengui; #endif - the_game( - kill, - input, - start_data, - error_message, - chat_backend, - &reconnect_requested - ); + the_game(kill, input, start_data, error_message, chat_backend, + &reconnect_requested); RenderingEngine::get_scene_manager()->clear(); #ifdef HAVE_TOUCHSCREENGUI @@ -286,7 +284,7 @@ bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args) receiver->m_touchscreengui = NULL; #endif - } //try + } // try catch (con::PeerNotFoundException &e) { error_message = gettext("Connection error (timed out?)"); errorstream << error_message << std::endl; @@ -304,8 +302,8 @@ bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args) // If no main menu, show error and exit if (skip_main_menu) { if (!error_message.empty()) { - verbosestream << "error_message = " - << error_message << std::endl; + verbosestream << "error_message = " << error_message + << std::endl; retval = false; } break; @@ -339,8 +337,8 @@ void ClientLauncher::init_args(GameStartData &start_data, const Settings &cmd_ar list_video_modes = cmd_args.getFlag("videomodes"); - random_input = g_settings->getBool("random_input") - || cmd_args.getFlag("random-input"); + random_input = g_settings->getBool("random_input") || + cmd_args.getFlag("random-input"); } bool ClientLauncher::init_engine() @@ -372,14 +370,14 @@ void ClientLauncher::init_input() } input->joystick.onJoystickConnect(joystick_infos); } else { - errorstream << "Could not activate joystick support." << std::endl; + errorstream << "Could not activate joystick support." + << std::endl; } } } -bool ClientLauncher::launch_game(std::string &error_message, - bool reconnect_requested, GameStartData &start_data, - const Settings &cmd_args) +bool ClientLauncher::launch_game(std::string &error_message, bool reconnect_requested, + GameStartData &start_data, const Settings &cmd_args) { // Prepare and check the start data to launch a game std::string error_message_lua = error_message; @@ -394,8 +392,8 @@ bool ClientLauncher::launch_game(std::string &error_message, getline(passfile, start_data.password); } else { error_message = gettext("Provided password file " - "failed to open: ") - + cmd_args.get("password-file"); + "failed to open: ") + + cmd_args.get("password-file"); errorstream << error_message << std::endl; return false; } @@ -410,7 +408,7 @@ bool ClientLauncher::launch_game(std::string &error_message, spec.gameid = getWorldGameId(spec.path, true); spec.name = _("[--world parameter]"); - if (spec.gameid.empty()) { // Create new + if (spec.gameid.empty()) { // Create new spec.gameid = g_settings->get("default_game"); spec.name += " [new]"; } @@ -424,11 +422,11 @@ bool ClientLauncher::launch_game(std::string &error_message, // Initialize menu data // TODO: Re-use existing structs (GameStartData) MainMenuData menudata; - menudata.address = start_data.address; - menudata.name = start_data.name; - menudata.password = start_data.password; - menudata.port = itos(start_data.socket_port); - menudata.script_data.errormessage = error_message_lua; + menudata.address = start_data.address; + menudata.name = start_data.name; + menudata.password = start_data.password; + menudata.port = itos(start_data.socket_port); + menudata.script_data.errormessage = error_message_lua; menudata.script_data.reconnect_requested = reconnect_requested; main_menu(&menudata); @@ -438,8 +436,9 @@ bool ClientLauncher::launch_game(std::string &error_message, return false; if (!menudata.script_data.errormessage.empty()) { - /* The calling function will pass this back into this function upon the - * next iteration (if any) causing it to be displayed by the GUI + /* The calling function will pass this back into this function + * upon the next iteration (if any) causing it to be displayed by + * the GUI */ error_message = menudata.script_data.errormessage; return false; @@ -466,7 +465,7 @@ bool ClientLauncher::launch_game(std::string &error_message, server_description = menudata.serverdescription; start_data.local_server = !menudata.simple_singleplayer_mode && - start_data.address.empty(); + start_data.address.empty(); } if (!RenderingEngine::run()) @@ -487,9 +486,9 @@ bool ClientLauncher::launch_game(std::string &error_message, g_settings->set("name", start_data.name); if (!start_data.address.empty()) { ServerListSpec server; - server["name"] = server_name; - server["address"] = start_data.address; - server["port"] = itos(start_data.socket_port); + server["name"] = server_name; + server["address"] = start_data.address; + server["port"] = itos(start_data.socket_port); server["description"] = server_description; ServerList::insert(server); } @@ -503,21 +502,21 @@ bool ClientLauncher::launch_game(std::string &error_message, } auto &worldspec = start_data.world_spec; - infostream << "Selected world: " << worldspec.name - << " [" << worldspec.path << "]" << std::endl; + infostream << "Selected world: " << worldspec.name << " [" << worldspec.path + << "]" << std::endl; if (start_data.address.empty()) { // For singleplayer and local server if (worldspec.path.empty()) { error_message = gettext("No world selected and no address " - "provided. Nothing to do."); + "provided. Nothing to do."); errorstream << error_message << std::endl; return false; } if (!fs::PathExists(worldspec.path)) { - error_message = gettext("Provided world path doesn't exist: ") - + worldspec.path; + error_message = gettext("Provided world path doesn't exist: ") + + worldspec.path; errorstream << error_message << std::endl; return false; } @@ -525,8 +524,8 @@ bool ClientLauncher::launch_game(std::string &error_message, // Load gamespec for required game start_data.game_spec = findWorldSubgame(worldspec.path); if (!start_data.game_spec.isValid()) { - error_message = gettext("Could not find or load game \"") - + worldspec.gameid + "\""; + error_message = gettext("Could not find or load game \"") + + worldspec.gameid + "\""; errorstream << error_message << std::endl; return false; } @@ -601,7 +600,7 @@ void ClientLauncher::speed_tests() for (u32 i = 0; i < ii; i++) { tempstring2 += "asd"; } - for (u32 i = 0; i < ii+1; i++) { + for (u32 i = 0; i < ii + 1; i++) { tempstring += "asd"; if (tempstring == tempstring2) break; @@ -610,7 +609,7 @@ void ClientLauncher::speed_tests() } infostream << "All of the following tests should take around 100ms each." - << std::endl; + << std::endl; { TimeTaker timer("Testing floating-point conversion speed"); @@ -640,7 +639,7 @@ void ClientLauncher::speed_tests() const s16 ii = 300; for (s16 y = 0; y < ii; y++) { for (s16 x = 0; x < ii; x++) { - map1[v2s16(x, y)] = tempf; + map1[v2s16(x, y)] = tempf; tempf += 1; } } @@ -666,7 +665,7 @@ void ClientLauncher::speed_tests() } } // Do at least 10ms - while(timer.getTimerTime() < 10); + while (timer.getTimerTime() < 10); u32 dtime = timer.stop(); u32 per_ms = n / dtime; diff --git a/src/client/clientlauncher.h b/src/client/clientlauncher.h index b280d8e6b..1828272b1 100644 --- a/src/client/clientlauncher.h +++ b/src/client/clientlauncher.h @@ -40,7 +40,7 @@ private: void init_input(); bool launch_game(std::string &error_message, bool reconnect_requested, - GameStartData &start_data, const Settings &cmd_args); + GameStartData &start_data, const Settings &cmd_args); void main_menu(MainMenuData *menudata); diff --git a/src/client/clientmap.cpp b/src/client/clientmap.cpp index d41b66741..dea354c96 100644 --- a/src/client/clientmap.cpp +++ b/src/client/clientmap.cpp @@ -26,24 +26,20 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "mapblock.h" #include "profiler.h" #include "settings.h" -#include "camera.h" // CameraModes +#include "camera.h" // CameraModes #include "util/basic_macros.h" #include #include "client/renderingengine.h" -ClientMap::ClientMap( - Client *client, - MapDrawControl &control, - s32 id -): - Map(dout_client, client), - scene::ISceneNode(RenderingEngine::get_scene_manager()->getRootSceneNode(), - RenderingEngine::get_scene_manager(), id), - m_client(client), - m_control(control) +ClientMap::ClientMap(Client *client, MapDrawControl &control, s32 id) : + Map(dout_client, client), + scene::ISceneNode( + RenderingEngine::get_scene_manager()->getRootSceneNode(), + RenderingEngine::get_scene_manager(), id), + m_client(client), m_control(control) { - m_box = aabb3f(-BS*1000000,-BS*1000000,-BS*1000000, - BS*1000000,BS*1000000,BS*1000000); + m_box = aabb3f(-BS * 1000000, -BS * 1000000, -BS * 1000000, BS * 1000000, + BS * 1000000, BS * 1000000); /* TODO: Add a callback function so these can be updated when a setting * changes. At this point in time it doesn't matter (e.g. /set @@ -54,13 +50,12 @@ ClientMap::ClientMap( * (as opposed to the this local caching). This can be addressed in * a later release. */ - m_cache_trilinear_filter = g_settings->getBool("trilinear_filter"); - m_cache_bilinear_filter = g_settings->getBool("bilinear_filter"); + m_cache_trilinear_filter = g_settings->getBool("trilinear_filter"); + m_cache_bilinear_filter = g_settings->getBool("bilinear_filter"); m_cache_anistropic_filter = g_settings->getBool("anisotropic_filter"); - } -MapSector * ClientMap::emergeSector(v2s16 p2d) +MapSector *ClientMap::emergeSector(v2s16 p2d) { // Check that it doesn't exist already MapSector *sector = getSectorNoGenerate(p2d); @@ -76,8 +71,7 @@ MapSector * ClientMap::emergeSector(v2s16 p2d) void ClientMap::OnRegisterSceneNode() { - if(IsVisible) - { + if (IsVisible) { SceneManager->registerNodeForRendering(this, scene::ESNRP_SOLID); SceneManager->registerNodeForRendering(this, scene::ESNRP_TRANSPARENT); } @@ -85,29 +79,23 @@ void ClientMap::OnRegisterSceneNode() ISceneNode::OnRegisterSceneNode(); } -void ClientMap::getBlocksInViewRange(v3s16 cam_pos_nodes, - v3s16 *p_blocks_min, v3s16 *p_blocks_max) +void ClientMap::getBlocksInViewRange( + v3s16 cam_pos_nodes, v3s16 *p_blocks_min, v3s16 *p_blocks_max) { v3s16 box_nodes_d = m_control.wanted_range * v3s16(1, 1, 1); // Define p_nodes_min/max as v3s32 because 'cam_pos_nodes -/+ box_nodes_d' // can exceed the range of v3s16 when a large view range is used near the // world edges. - v3s32 p_nodes_min( - cam_pos_nodes.X - box_nodes_d.X, - cam_pos_nodes.Y - box_nodes_d.Y, - cam_pos_nodes.Z - box_nodes_d.Z); - v3s32 p_nodes_max( - cam_pos_nodes.X + box_nodes_d.X, - cam_pos_nodes.Y + box_nodes_d.Y, - cam_pos_nodes.Z + box_nodes_d.Z); + v3s32 p_nodes_min(cam_pos_nodes.X - box_nodes_d.X, + cam_pos_nodes.Y - box_nodes_d.Y, cam_pos_nodes.Z - box_nodes_d.Z); + v3s32 p_nodes_max(cam_pos_nodes.X + box_nodes_d.X, + cam_pos_nodes.Y + box_nodes_d.Y, cam_pos_nodes.Z + box_nodes_d.Z); // Take a fair amount as we will be dropping more out later // Umm... these additions are a bit strange but they are needed. - *p_blocks_min = v3s16( - p_nodes_min.X / MAP_BLOCKSIZE - 3, + *p_blocks_min = v3s16(p_nodes_min.X / MAP_BLOCKSIZE - 3, p_nodes_min.Y / MAP_BLOCKSIZE - 3, p_nodes_min.Z / MAP_BLOCKSIZE - 3); - *p_blocks_max = v3s16( - p_nodes_max.X / MAP_BLOCKSIZE + 1, + *p_blocks_max = v3s16(p_nodes_max.X / MAP_BLOCKSIZE + 1, p_nodes_max.Y / MAP_BLOCKSIZE + 1, p_nodes_max.Z / MAP_BLOCKSIZE + 1); } @@ -146,14 +134,13 @@ void ClientMap::updateDrawList() bool occlusion_culling_enabled = true; if (g_settings->getBool("free_move") && g_settings->getBool("noclip")) { MapNode n = getNode(cam_pos_nodes); - if (n.getContent() == CONTENT_IGNORE || - m_nodedef->get(n).solidness == 2) + if (n.getContent() == CONTENT_IGNORE || m_nodedef->get(n).solidness == 2) occlusion_culling_enabled = false; } // Uncomment to debug occluded blocks in the wireframe mode // TODO: Include this as a flag for an extended debugging setting - //if (occlusion_culling_enabled && m_control.show_wireframe) + // if (occlusion_culling_enabled && m_control.show_wireframe) // occlusion_culling_enabled = porting::getTimeS() & 1; for (const auto §or_it : m_sectors) { @@ -190,10 +177,9 @@ void ClientMap::updateDrawList() float d = 0.0; if (!isBlockInSight(block->getPos(), camera_position, - camera_direction, camera_fov, range, &d)) + camera_direction, camera_fov, range, &d)) continue; - /* Ignore if mesh doesn't exist */ @@ -206,7 +192,9 @@ void ClientMap::updateDrawList() Occlusion culling */ if ((!m_control.range_all && d > m_control.wanted_range * BS) || - (occlusion_culling_enabled && isBlockOccluded(block, cam_pos_nodes))) { + (occlusion_culling_enabled && + isBlockOccluded(block, + cam_pos_nodes))) { blocks_occlusion_culled++; continue; } @@ -233,7 +221,7 @@ void ClientMap::updateDrawList() struct MeshBufList { video::SMaterial m; - std::vector bufs; + std::vector bufs; }; struct MeshBufListList @@ -257,8 +245,8 @@ struct MeshBufListList std::vector &list = lists[layer]; const video::SMaterial &m = buf->getMaterial(); for (MeshBufList &l : list) { - // comparing a full material is quite expensive so we don't do it if - // not even first texture is equal + // comparing a full material is quite expensive so we don't do it + // if not even first texture is equal if (l.m.TextureLayer[0].Texture != m.TextureLayer[0].Texture) continue; @@ -274,7 +262,7 @@ struct MeshBufListList } }; -void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) +void ClientMap::renderMap(video::IVideoDriver *driver, s32 pass) { bool is_transparent_pass = pass == scene::ESNRP_TRANSPARENT; @@ -309,7 +297,7 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) // For limiting number of mesh animations per frame u32 mesh_animate_count = 0; - //u32 mesh_animate_count_far = 0; + // u32 mesh_animate_count_far = 0; /* Draw the selected MapBlocks @@ -325,22 +313,23 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) continue; float d = 0.0; - if (!isBlockInSight(block->getPos(), camera_position, - camera_direction, camera_fov, 100000 * BS, &d)) + if (!isBlockInSight(block->getPos(), camera_position, camera_direction, + camera_fov, 100000 * BS, &d)) continue; // Mesh animation if (pass == scene::ESNRP_SOLID) { - //MutexAutoLock lock(block->mesh_mutex); + // MutexAutoLock lock(block->mesh_mutex); MapBlockMesh *mapBlockMesh = block->mesh; assert(mapBlockMesh); // Pretty random but this should work somewhat nicely bool faraway = d >= BS * 50; if (mapBlockMesh->isAnimationForced() || !faraway || - mesh_animate_count < (m_control.range_all ? 200 : 50)) { + mesh_animate_count < (m_control.range_all ? 200 + : 50)) { - bool animated = mapBlockMesh->animate(faraway, animation_time, - crack, daynight_ratio); + bool animated = mapBlockMesh->animate(faraway, + animation_time, crack, daynight_ratio); if (animated) mesh_animate_count++; } else { @@ -352,7 +341,7 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) Get the meshbuffers of the block */ { - //MutexAutoLock lock(block->mesh_mutex); + // MutexAutoLock lock(block->mesh_mutex); MapBlockMesh *mapBlockMesh = block->mesh; assert(mapBlockMesh); @@ -365,23 +354,30 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) for (u32 i = 0; i < c; i++) { scene::IMeshBuffer *buf = mesh->getMeshBuffer(i); - video::SMaterial& material = buf->getMaterial(); - video::IMaterialRenderer* rnd = - driver->getMaterialRenderer(material.MaterialType); + video::SMaterial &material = buf->getMaterial(); + video::IMaterialRenderer *rnd = + driver->getMaterialRenderer( + material.MaterialType); bool transparent = (rnd && rnd->isTransparent()); if (transparent == is_transparent_pass) { if (buf->getVertexCount() == 0) - errorstream << "Block [" << analyze_block(block) - << "] contains an empty meshbuf" << std::endl; + errorstream << "Block [" + << analyze_block(block) + << "] contains an " + "empty meshbuf" + << std::endl; - material.setFlag(video::EMF_TRILINEAR_FILTER, - m_cache_trilinear_filter); - material.setFlag(video::EMF_BILINEAR_FILTER, - m_cache_bilinear_filter); - material.setFlag(video::EMF_ANISOTROPIC_FILTER, - m_cache_anistropic_filter); + material.setFlag( + video::EMF_TRILINEAR_FILTER, + m_cache_trilinear_filter); + material.setFlag( + video::EMF_BILINEAR_FILTER, + m_cache_bilinear_filter); + material.setFlag( + video::EMF_ANISOTROPIC_FILTER, + m_cache_anistropic_filter); material.setFlag(video::EMF_WIREFRAME, - m_control.show_wireframe); + m_control.show_wireframe); drawbufs.add(buf, layer); } @@ -397,8 +393,9 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) for (MeshBufList &list : lists) { // Check and abort if the machine is swapping a lot if (draw.getTimerTime() > 2000) { - infostream << "ClientMap::renderMap(): Rendering took >2s, " << - "returning." << std::endl; + infostream << "ClientMap::renderMap(): Rendering took " + ">2s, " + << "returning." << std::endl; return; } driver->setMaterial(list.m); @@ -420,9 +417,9 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) } static bool getVisibleBrightness(Map *map, const v3f &p0, v3f dir, float step, - float step_multiplier, float start_distance, float end_distance, - const NodeDefManager *ndef, u32 daylight_factor, float sunlight_min_d, - int *result, bool *sunlight_seen) + float step_multiplier, float start_distance, float end_distance, + const NodeDefManager *ndef, u32 daylight_factor, float sunlight_min_d, + int *result, bool *sunlight_seen) { int brightness_sum = 0; int brightness_count = 0; @@ -438,7 +435,7 @@ static bool getVisibleBrightness(Map *map, const v3f &p0, v3f dir, float step, { v3s16 p = floatToInt(p0 /*+ dir * 3*BS*/, BS); MapNode n = map->getNode(p); - if(ndef->get(n).param_type == CPT_LIGHT && + if (ndef->get(n).param_type == CPT_LIGHT && !ndef->get(n).sunlight_propagates) allow_allowing_non_sunlight_propagates = true; } @@ -446,14 +443,14 @@ static bool getVisibleBrightness(Map *map, const v3f &p0, v3f dir, float step, { v3s16 p = floatToInt(pf, BS); MapNode n = map->getNode(p); - if(n.getContent() == CONTENT_IGNORE){ - float newd = 2*BS; - pf = p0 + dir * 2*newd; + if (n.getContent() == CONTENT_IGNORE) { + float newd = 2 * BS; + pf = p0 + dir * 2 * newd; distance = newd; sunlight_min_d = 0; } } - for (int i=0; distance < end_distance; i++) { + for (int i = 0; distance < end_distance; i++) { pf += dir * step; distance += step; step *= step_multiplier; @@ -468,10 +465,10 @@ static bool getVisibleBrightness(Map *map, const v3f &p0, v3f dir, float step, if (ndef->get(n).param_type != CPT_LIGHT || (!ndef->get(n).sunlight_propagates && - !allow_non_sunlight_propagates)){ + !allow_non_sunlight_propagates)) { nonlight_seen = true; noncount++; - if(noncount >= 4) + if (noncount >= 4) break; continue; } @@ -484,7 +481,7 @@ static bool getVisibleBrightness(Map *map, const v3f &p0, v3f dir, float step, brightness_count++; } *result = 0; - if(brightness_count == 0) + if (brightness_count == 0) return false; *result = brightness_sum / brightness_count; /*std::cerr<<"Sampled "< 35*BS) - sunlight_min_d = 35*BS; + float sunlight_min_d = max_d * 0.8; + if (sunlight_min_d > 35 * BS) + sunlight_min_d = 35 * BS; std::vector values; - for(u32 i=0; i a; - a.buildRotateFromTo(v3f(0,1,0), z_dir); + a.buildRotateFromTo(v3f(0, 1, 0), z_dir); v3f dir = m_camera_direction; a.rotateVect(dir); int br = 0; - float step = BS*1.5; - if(max_d > 35*BS) + float step = BS * 1.5; + if (max_d > 35 * BS) step = max_d / 35 * 1.5; float off = step * z_offsets[i]; bool sunlight_seen_now = false; - bool ok = getVisibleBrightness(this, m_camera_position, dir, - step, 1.0, max_d*0.6+off, max_d, m_nodedef, daylight_factor, - sunlight_min_d, - &br, &sunlight_seen_now); - if(sunlight_seen_now) + bool ok = getVisibleBrightness(this, m_camera_position, dir, step, 1.0, + max_d * 0.6 + off, max_d, m_nodedef, daylight_factor, + sunlight_min_d, &br, &sunlight_seen_now); + if (sunlight_seen_now) sunlight_seen_count++; - if(!ok) + if (!ok) continue; values.push_back(br); // Don't try too much if being in the sun is clear - if(sunlight_seen_count >= 20) + if (sunlight_seen_count >= 20) break; } int brightness_sum = 0; int brightness_count = 0; std::sort(values.begin(), values.end()); u32 num_values_to_use = values.size(); - if(num_values_to_use >= 10) - num_values_to_use -= num_values_to_use/2; - else if(num_values_to_use >= 7) - num_values_to_use -= num_values_to_use/3; + if (num_values_to_use >= 10) + num_values_to_use -= num_values_to_use / 2; + else if (num_values_to_use >= 7) + num_values_to_use -= num_values_to_use / 3; u32 first_value_i = (values.size() - num_values_to_use) / 2; - for (u32 i=first_value_i; i < first_value_i + num_values_to_use; i++) { + for (u32 i = first_value_i; i < first_value_i + num_values_to_use; i++) { brightness_sum += values[i]; brightness_count++; } int ret = 0; - if(brightness_count == 0){ + if (brightness_count == 0) { MapNode n = getNode(floatToInt(m_camera_position, BS)); - if(m_nodedef->get(n).param_type == CPT_LIGHT){ + if (m_nodedef->get(n).param_type == CPT_LIGHT) { ret = decode_light(n.getLightBlend(daylight_factor, m_nodedef)); } else { ret = oldvalue; @@ -586,27 +578,25 @@ void ClientMap::renderPostFx(CameraMode cam_mode) // - If the player is in a solid node, make everything black. // - If the player is in liquid, draw a semi-transparent overlay. // - Do not if player is in third person mode - const ContentFeatures& features = m_nodedef->get(n); + const ContentFeatures &features = m_nodedef->get(n); video::SColor post_effect_color = features.post_effect_color; - if(features.solidness == 2 && !((g_settings->getBool("noclip") || g_settings->getBool("freecam")) && - m_client->checkLocalPrivilege("noclip")) && - cam_mode == CAMERA_MODE_FIRST) - { + if (features.solidness == 2 && + !((g_settings->getBool("noclip") || + g_settings->getBool("freecam")) && + m_client->checkLocalPrivilege("noclip")) && + cam_mode == CAMERA_MODE_FIRST) { post_effect_color = video::SColor(255, 0, 0, 0); } - if (post_effect_color.getAlpha() != 0) - { + if (post_effect_color.getAlpha() != 0) { // Draw a full-screen rectangle - video::IVideoDriver* driver = SceneManager->getVideoDriver(); + video::IVideoDriver *driver = SceneManager->getVideoDriver(); v2u32 ss = driver->getScreenSize(); - core::rect rect(0,0, ss.X, ss.Y); + core::rect rect(0, 0, ss.X, ss.Y); driver->draw2DRectangle(post_effect_color, rect); } } void ClientMap::PrintInfo(std::ostream &out) { - out<<"ClientMap: "; + out << "ClientMap: "; } - - diff --git a/src/client/clientmap.h b/src/client/clientmap.h index 172e3a1d6..23eb561f8 100644 --- a/src/client/clientmap.h +++ b/src/client/clientmap.h @@ -47,23 +47,13 @@ class ITextureSource; class ClientMap : public Map, public scene::ISceneNode { public: - ClientMap( - Client *client, - MapDrawControl &control, - s32 id - ); + ClientMap(Client *client, MapDrawControl &control, s32 id); virtual ~ClientMap() = default; - s32 mapType() const - { - return MAPTYPE_CLIENT; - } + s32 mapType() const { return MAPTYPE_CLIENT; } - void drop() - { - ISceneNode::drop(); - } + void drop() { ISceneNode::drop(); } void updateCamera(const v3f &pos, const v3f &dir, f32 fov, const v3s16 &offset) { @@ -76,9 +66,9 @@ public: /* Forcefully get a sector from somewhere */ - MapSector * emergeSector(v2s16 p); + MapSector *emergeSector(v2s16 p); - //void deSerializeSector(v2s16 p2d, std::istream &is); + // void deSerializeSector(v2s16 p2d, std::istream &is); /* ISceneNode methods @@ -88,45 +78,43 @@ public: virtual void render() { - video::IVideoDriver* driver = SceneManager->getVideoDriver(); + video::IVideoDriver *driver = SceneManager->getVideoDriver(); driver->setTransform(video::ETS_WORLD, AbsoluteTransformation); renderMap(driver, SceneManager->getSceneNodeRenderPass()); } - virtual const aabb3f &getBoundingBox() const - { - return m_box; - } + virtual const aabb3f &getBoundingBox() const { return m_box; } - void getBlocksInViewRange(v3s16 cam_pos_nodes, - v3s16 *p_blocks_min, v3s16 *p_blocks_max); + void getBlocksInViewRange( + v3s16 cam_pos_nodes, v3s16 *p_blocks_min, v3s16 *p_blocks_max); void updateDrawList(); - void renderMap(video::IVideoDriver* driver, s32 pass); + void renderMap(video::IVideoDriver *driver, s32 pass); - int getBackgroundBrightness(float max_d, u32 daylight_factor, - int oldvalue, bool *sunlight_seen_result); + int getBackgroundBrightness(float max_d, u32 daylight_factor, int oldvalue, + bool *sunlight_seen_result); void renderPostFx(CameraMode cam_mode); // For debug printing virtual void PrintInfo(std::ostream &out); - const MapDrawControl & getControl() const { return m_control; } + const MapDrawControl &getControl() const { return m_control; } f32 getCameraFov() const { return m_camera_fov; } + private: Client *m_client; - aabb3f m_box = aabb3f(-BS * 1000000, -BS * 1000000, -BS * 1000000, - BS * 1000000, BS * 1000000, BS * 1000000); + aabb3f m_box = aabb3f(-BS * 1000000, -BS * 1000000, -BS * 1000000, BS * 1000000, + BS * 1000000, BS * 1000000); MapDrawControl &m_control; - v3f m_camera_position = v3f(0,0,0); - v3f m_camera_direction = v3f(0,0,1); + v3f m_camera_position = v3f(0, 0, 0); + v3f m_camera_direction = v3f(0, 0, 1); f32 m_camera_fov = M_PI; v3s16 m_camera_offset; - std::map m_drawlist; + std::map m_drawlist; std::set m_last_drawn_sectors; diff --git a/src/client/clientmedia.cpp b/src/client/clientmedia.cpp index 8cd3b6bcc..1cf18b84b 100644 --- a/src/client/clientmedia.cpp +++ b/src/client/clientmedia.cpp @@ -48,9 +48,8 @@ bool clientMediaUpdateCache(const std::string &raw_hash, const std::string &file ClientMediaDownloader */ -ClientMediaDownloader::ClientMediaDownloader(): - m_media_cache(getMediaCacheDir()), - m_httpfetch_caller(HTTPFETCH_DISCARD) +ClientMediaDownloader::ClientMediaDownloader() : + m_media_cache(getMediaCacheDir()), m_httpfetch_caller(HTTPFETCH_DISCARD) { } @@ -73,24 +72,21 @@ void ClientMediaDownloader::addFile(const std::string &name, const std::string & // if name was already announced, ignore the new announcement if (m_files.count(name) != 0) { errorstream << "Client: ignoring duplicate media announcement " - << "sent by server: \"" << name << "\"" - << std::endl; + << "sent by server: \"" << name << "\"" << std::endl; return; } // if name is empty or contains illegal characters, ignore the file if (name.empty() || !string_allowed(name, TEXTURENAME_ALLOWED_CHARS)) { errorstream << "Client: ignoring illegal file name " - << "sent by server: \"" << name << "\"" - << std::endl; + << "sent by server: \"" << name << "\"" << std::endl; return; } // length of sha1 must be exactly 20 (160 bits), else ignore the file if (sha1.size() != 20) { errorstream << "Client: ignoring illegal SHA1 sent by server: " - << hex_encode(sha1) << " \"" << name << "\"" - << std::endl; + << hex_encode(sha1) << " \"" << name << "\"" << std::endl; return; } @@ -103,13 +99,13 @@ void ClientMediaDownloader::addFile(const std::string &name, const std::string & void ClientMediaDownloader::addRemoteServer(const std::string &baseurl) { - assert(!m_initial_step_done); // pre-condition + assert(!m_initial_step_done); // pre-condition - #ifdef USE_CURL +#ifdef USE_CURL if (g_settings->getBool("enable_remote_media_server")) { - infostream << "Client: Adding remote server \"" - << baseurl << "\" for media download" << std::endl; + infostream << "Client: Adding remote server \"" << baseurl + << "\" for media download" << std::endl; RemoteServerStatus *remote = new RemoteServerStatus(); remote->baseurl = baseurl; @@ -117,13 +113,12 @@ void ClientMediaDownloader::addRemoteServer(const std::string &baseurl) m_remotes.push_back(remote); } - #else +#else - infostream << "Client: Ignoring remote server \"" - << baseurl << "\" because cURL support is not compiled in" - << std::endl; + infostream << "Client: Ignoring remote server \"" << baseurl + << "\" because cURL support is not compiled in" << std::endl; - #endif +#endif } void ClientMediaDownloader::step(Client *client) @@ -158,9 +153,10 @@ void ClientMediaDownloader::step(Client *client) if (m_httpfetch_active == 0) { if (m_uncached_received_count < m_uncached_count) { infostream << "Client: Failed to remote-fetch " - << (m_uncached_count-m_uncached_received_count) - << " files. Requesting them" - << " the usual way." << std::endl; + << (m_uncached_count - + m_uncached_received_count) + << " files. Requesting them" + << " the usual way." << std::endl; } startConventionalTransfers(client); } @@ -181,8 +177,8 @@ void ClientMediaDownloader::initialStep(Client *client) // If found in cache, try to load it from there if (found_in_cache) { - bool success = checkAndLoad(name, sha1, - tmp_os.str(), true, client); + bool success = checkAndLoad( + name, sha1, tmp_os.str(), true, client); if (success) { filestatus->received = true; m_uncached_count--; @@ -197,9 +193,8 @@ void ClientMediaDownloader::initialStep(Client *client) bool did = fs::CreateAllDirs(getMediaCacheDir()); if (!did) { errorstream << "Client: " - << "Could not create media cache directory: " - << getMediaCacheDir() - << std::endl; + << "Could not create media cache directory: " + << getMediaCacheDir() << std::endl; } } @@ -210,8 +205,7 @@ void ClientMediaDownloader::initialStep(Client *client) // reduce the size of the compiled code if (!USE_CURL || m_uncached_count == 0 || m_remotes.empty()) { startConventionalTransfers(client); - } - else { + } else { // Otherwise start off by requesting each server's sha1 set // This is the first time we use httpfetch, so alloc a caller ID @@ -251,27 +245,26 @@ void ClientMediaDownloader::initialStep(Client *client) RemoteServerStatus *remote = m_remotes[i]; actionstream << "Client: Contacting remote server \"" - << remote->baseurl << "\"" << std::endl; + << remote->baseurl << "\"" << std::endl; HTTPFetchRequest fetch_request; - fetch_request.url = - remote->baseurl + MTHASHSET_FILE_NAME; + fetch_request.url = remote->baseurl + MTHASHSET_FILE_NAME; fetch_request.caller = m_httpfetch_caller; fetch_request.request_id = m_httpfetch_next_id; // == i fetch_request.timeout = m_httpfetch_timeout; fetch_request.connect_timeout = m_httpfetch_timeout; fetch_request.post_data = required_hash_set; fetch_request.extra_headers.emplace_back( - "Content-Type: application/octet-stream"); + "Content-Type: application/octet-stream"); // Encapsulate possible IPv6 plain address in [] std::string addr = client->getAddressName(); if (addr.find(':', 0) != std::string::npos) addr = '[' + addr + ']'; fetch_request.extra_headers.emplace_back( - std::string("Referer: minetest://") + - addr + ":" + - std::to_string(client->getServerAddress().getPort())); + std::string("Referer: minetest://") + addr + ":" + + std::to_string(client->getServerAddress() + .getPort())); httpfetch_async(fetch_request); @@ -282,8 +275,7 @@ void ClientMediaDownloader::initialStep(Client *client) } } -void ClientMediaDownloader::remoteHashSetReceived( - const HTTPFetchResult &fetch_result) +void ClientMediaDownloader::remoteHashSetReceived(const HTTPFetchResult &fetch_result) { u32 remote_id = fetch_result.request_id; assert(remote_id < m_remotes.size()); @@ -303,25 +295,23 @@ void ClientMediaDownloader::remoteHashSetReceived( // available on this server, add this server // to the available_remotes array - for(std::map::iterator - it = m_files.upper_bound(m_name_bound); + for (std::map::iterator it = + m_files.upper_bound(m_name_bound); it != m_files.end(); ++it) { FileStatus *f = it->second; if (!f->received && sha1_set.count(f->sha1)) f->available_remotes.push_back(remote_id); } - } - catch (SerializationError &e) { - infostream << "Client: Remote server \"" - << remote->baseurl << "\" sent invalid hash set: " - << e.what() << std::endl; + } catch (SerializationError &e) { + infostream << "Client: Remote server \"" << remote->baseurl + << "\" sent invalid hash set: " << e.what() + << std::endl; } } } void ClientMediaDownloader::remoteMediaReceived( - const HTTPFetchResult &fetch_result, - Client *client) + const HTTPFetchResult &fetch_result, Client *client) { // Some remote server sent us a file. // -> decrement number of active fetches @@ -331,7 +321,7 @@ void ClientMediaDownloader::remoteMediaReceived( std::string name; { std::unordered_map::iterator it = - m_remote_file_transfers.find(fetch_result.request_id); + m_remote_file_transfers.find(fetch_result.request_id); assert(it != m_remote_file_transfers.end()); name = it->second; m_remote_file_transfers.erase(it); @@ -351,8 +341,8 @@ void ClientMediaDownloader::remoteMediaReceived( // If fetch succeeded, try to load media file if (fetch_result.succeeded) { - bool success = checkAndLoad(name, filestatus->sha1, - fetch_result.data, false, client); + bool success = checkAndLoad( + name, filestatus->sha1, fetch_result.data, false, client); if (success) { filestatus->received = true; assert(m_uncached_received_count < m_uncached_count); @@ -389,19 +379,17 @@ s32 ClientMediaDownloader::selectRemoteServer(FileStatus *filestatus) } } - filestatus->available_remotes.erase( - filestatus->available_remotes.begin() + best); + filestatus->available_remotes.erase(filestatus->available_remotes.begin() + best); return best_remote_id; - } void ClientMediaDownloader::startRemoteMediaTransfers() { bool changing_name_bound = true; - for (std::map::iterator - files_iter = m_files.upper_bound(m_name_bound); + for (std::map::iterator files_iter = + m_files.upper_bound(m_name_bound); files_iter != m_files.end(); ++files_iter) { // Abort if active fetch limit is exceeded @@ -417,28 +405,25 @@ void ClientMediaDownloader::startRemoteMediaTransfers() s32 remote_id = selectRemoteServer(filestatus); if (remote_id >= 0) { // Found a server, so start fetching - RemoteServerStatus *remote = - m_remotes[remote_id]; + RemoteServerStatus *remote = m_remotes[remote_id]; std::string url = remote->baseurl + - hex_encode(filestatus->sha1); + hex_encode(filestatus->sha1); verbosestream << "Client: " - << "Requesting remote media file " - << "\"" << name << "\" " - << "\"" << url << "\"" << std::endl; + << "Requesting remote media file " + << "\"" << name << "\" " + << "\"" << url << "\"" << std::endl; HTTPFetchRequest fetch_request; fetch_request.url = url; fetch_request.caller = m_httpfetch_caller; fetch_request.request_id = m_httpfetch_next_id; fetch_request.timeout = 0; // no data timeout! - fetch_request.connect_timeout = - m_httpfetch_timeout; + fetch_request.connect_timeout = m_httpfetch_timeout; httpfetch_async(fetch_request); m_remote_file_transfers.insert(std::make_pair( - m_httpfetch_next_id, - name)); + m_httpfetch_next_id, name)); filestatus->current_remote = remote_id; remote->active_count++; @@ -447,24 +432,21 @@ void ClientMediaDownloader::startRemoteMediaTransfers() } } - if (filestatus->received || - (filestatus->current_remote < 0 && - !m_outstanding_hash_sets)) { + if (filestatus->received || (filestatus->current_remote < 0 && + !m_outstanding_hash_sets)) { // If we arrive here, we conclusively know that we // won't fetch this file from a remote server in the // future. So update the name bound if possible. if (changing_name_bound) m_name_bound = name; - } - else + } else changing_name_bound = false; } - } void ClientMediaDownloader::startConventionalTransfers(Client *client) { - assert(m_httpfetch_active == 0); // pre-condition + assert(m_httpfetch_active == 0); // pre-condition if (m_uncached_received_count != m_uncached_count) { // Some media files have not been received yet, use the @@ -474,24 +456,21 @@ void ClientMediaDownloader::startConventionalTransfers(Client *client) if (!file.second->received) file_requests.push_back(file.first); } - assert((s32) file_requests.size() == + assert((s32)file_requests.size() == m_uncached_count - m_uncached_received_count); client->request_media(file_requests); } } void ClientMediaDownloader::conventionalTransferDone( - const std::string &name, - const std::string &data, - Client *client) + const std::string &name, const std::string &data, Client *client) { // Check that file was announced - std::map::iterator - file_iter = m_files.find(name); + std::map::iterator file_iter = m_files.find(name); if (file_iter == m_files.end()) { errorstream << "Client: server sent media file that was" - << "not announced, ignoring it: \"" << name << "\"" - << std::endl; + << "not announced, ignoring it: \"" << name << "\"" + << std::endl; return; } FileStatus *filestatus = file_iter->second; @@ -500,8 +479,7 @@ void ClientMediaDownloader::conventionalTransferDone( // Check that file hasn't already been received if (filestatus->received) { errorstream << "Client: server sent media file that we already" - << "received, ignoring it: \"" << name << "\"" - << std::endl; + << "received, ignoring it: \"" << name << "\"" << std::endl; return; } @@ -517,8 +495,7 @@ void ClientMediaDownloader::conventionalTransferDone( checkAndLoad(name, filestatus->sha1, data, false, client); } -bool ClientMediaDownloader::checkAndLoad( - const std::string &name, const std::string &sha1, +bool ClientMediaDownloader::checkAndLoad(const std::string &name, const std::string &sha1, const std::string &data, bool is_from_cache, Client *client) { const char *cached_or_received = is_from_cache ? "cached" : "received"; @@ -531,18 +508,16 @@ bool ClientMediaDownloader::checkAndLoad( SHA1 data_sha1_calculator; data_sha1_calculator.addBytes(data.c_str(), data.size()); unsigned char *data_tmpdigest = data_sha1_calculator.getDigest(); - data_sha1.assign((char*) data_tmpdigest, 20); + data_sha1.assign((char *)data_tmpdigest, 20); free(data_tmpdigest); } // Check that received file matches announced checksum if (data_sha1 != sha1) { std::string data_sha1_hex = hex_encode(data_sha1); - infostream << "Client: " - << cached_or_received_uc << " media file " - << sha1_hex << " \"" << name << "\" " - << "mismatches actual checksum " << data_sha1_hex - << std::endl; + infostream << "Client: " << cached_or_received_uc << " media file " + << sha1_hex << " \"" << name << "\" " + << "mismatches actual checksum " << data_sha1_hex << std::endl; return false; } @@ -550,16 +525,15 @@ bool ClientMediaDownloader::checkAndLoad( bool success = client->loadMedia(data, name); if (!success) { infostream << "Client: " - << "Failed to load " << cached_or_received << " media: " - << sha1_hex << " \"" << name << "\"" - << std::endl; + << "Failed to load " << cached_or_received + << " media: " << sha1_hex << " \"" << name << "\"" + << std::endl; return false; } verbosestream << "Client: " - << "Loaded " << cached_or_received << " media: " - << sha1_hex << " \"" << name << "\"" - << std::endl; + << "Loaded " << cached_or_received << " media: " << sha1_hex + << " \"" << name << "\"" << std::endl; // Update cache (unless we just loaded the file from the cache) if (!is_from_cache) @@ -586,15 +560,15 @@ std::string ClientMediaDownloader::serializeRequiredHashSet() std::ostringstream os(std::ios::binary); writeU32(os, MTHASHSET_FILE_SIGNATURE); // signature - writeU16(os, 1); // version + writeU16(os, 1); // version // Write list of hashes of files that have not been // received (found in cache) yet - for (std::map::iterator - it = m_files.begin(); + for (std::map::iterator it = m_files.begin(); it != m_files.end(); ++it) { if (!it->second->received) { - FATAL_ERROR_IF(it->second->sha1.size() != 20, "Invalid SHA1 size"); + FATAL_ERROR_IF(it->second->sha1.size() != 20, + "Invalid SHA1 size"); os << it->second->sha1; } } @@ -602,29 +576,26 @@ std::string ClientMediaDownloader::serializeRequiredHashSet() return os.str(); } -void ClientMediaDownloader::deSerializeHashSet(const std::string &data, - std::set &result) +void ClientMediaDownloader::deSerializeHashSet( + const std::string &data, std::set &result) { if (data.size() < 6 || data.size() % 20 != 6) { - throw SerializationError( - "ClientMediaDownloader::deSerializeHashSet: " - "invalid hash set file size"); + throw SerializationError("ClientMediaDownloader::deSerializeHashSet: " + "invalid hash set file size"); } - const u8 *data_cstr = (const u8*) data.c_str(); + const u8 *data_cstr = (const u8 *)data.c_str(); u32 signature = readU32(&data_cstr[0]); if (signature != MTHASHSET_FILE_SIGNATURE) { - throw SerializationError( - "ClientMediaDownloader::deSerializeHashSet: " - "invalid hash set file signature"); + throw SerializationError("ClientMediaDownloader::deSerializeHashSet: " + "invalid hash set file signature"); } u16 version = readU16(&data_cstr[4]); if (version != 1) { - throw SerializationError( - "ClientMediaDownloader::deSerializeHashSet: " - "unsupported hash set file version"); + throw SerializationError("ClientMediaDownloader::deSerializeHashSet: " + "unsupported hash set file version"); } for (u32 pos = 6; pos < data.size(); pos += 20) { diff --git a/src/client/clientmedia.h b/src/client/clientmedia.h index 5a918535b..6a52c551d 100644 --- a/src/client/clientmedia.h +++ b/src/client/clientmedia.h @@ -35,8 +35,7 @@ struct HTTPFetchResult; // 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); +bool clientMediaUpdateCache(const std::string &raw_hash, const std::string &filedata); class ClientMediaDownloader { @@ -44,22 +43,21 @@ public: ClientMediaDownloader(); ~ClientMediaDownloader(); - float getProgress() const { + float getProgress() const + { if (m_uncached_count >= 1) - return 1.0f * m_uncached_received_count / - m_uncached_count; + return 1.0f * m_uncached_received_count / m_uncached_count; return 0.0f; } - bool isStarted() const { - return m_initial_step_done; - } + bool isStarted() const { return m_initial_step_done; } // If this returns true, the downloader is done and can be deleted - bool isDone() const { + bool isDone() const + { return m_initial_step_done && - m_uncached_received_count == m_uncached_count; + m_uncached_received_count == m_uncached_count; } // Add a file to the list of required file (but don't fetch it yet) @@ -82,44 +80,42 @@ public: // Must be called for each file received through TOCLIENT_MEDIA void conventionalTransferDone( - const std::string &name, - const std::string &data, - Client *client); + const std::string &name, const std::string &data, Client *client); private: - struct FileStatus { + struct FileStatus + { bool received; std::string sha1; s32 current_remote; std::vector available_remotes; }; - struct RemoteServerStatus { + struct RemoteServerStatus + { std::string baseurl; s32 active_count; }; void initialStep(Client *client); void remoteHashSetReceived(const HTTPFetchResult &fetch_result); - void remoteMediaReceived(const HTTPFetchResult &fetch_result, - Client *client); + void remoteMediaReceived(const HTTPFetchResult &fetch_result, Client *client); s32 selectRemoteServer(FileStatus *filestatus); void startRemoteMediaTransfers(); void startConventionalTransfers(Client *client); bool checkAndLoad(const std::string &name, const std::string &sha1, - const std::string &data, bool is_from_cache, - Client *client); + const std::string &data, bool is_from_cache, Client *client); std::string serializeRequiredHashSet(); - static void deSerializeHashSet(const std::string &data, - std::set &result); + static void deSerializeHashSet( + const std::string &data, std::set &result); // Maps filename to file status - std::map m_files; + std::map m_files; // Array of remote media servers - std::vector m_remotes; + std::vector m_remotes; // Filesystem-based media cache FileCache m_media_cache; @@ -148,5 +144,4 @@ private: // don't need to be looked at again // (use m_files.upper_bound(m_name_bound) to get an iterator) std::string m_name_bound = ""; - }; diff --git a/src/client/clientobject.cpp b/src/client/clientobject.cpp index f4b69201b..af8b35da1 100644 --- a/src/client/clientobject.cpp +++ b/src/client/clientobject.cpp @@ -25,11 +25,8 @@ with this program; if not, write to the Free Software Foundation, Inc., ClientActiveObject */ -ClientActiveObject::ClientActiveObject(u16 id, Client *client, - ClientEnvironment *env): - ActiveObject(id), - m_client(client), - m_env(env) +ClientActiveObject::ClientActiveObject(u16 id, Client *client, ClientEnvironment *env) : + ActiveObject(id), m_client(client), m_env(env) { } @@ -38,15 +35,15 @@ ClientActiveObject::~ClientActiveObject() removeFromScene(true); } -ClientActiveObject* ClientActiveObject::create(ActiveObjectType type, - Client *client, ClientEnvironment *env) +ClientActiveObject *ClientActiveObject::create( + ActiveObjectType type, Client *client, ClientEnvironment *env) { // Find factory function auto n = m_types.find(type); if (n == m_types.end()) { // If factory is not found, just return. - warningstream << "ClientActiveObject: No factory for type=" - << (int)type << std::endl; + warningstream << "ClientActiveObject: No factory for type=" << (int)type + << std::endl; return NULL; } @@ -62,5 +59,3 @@ void ClientActiveObject::registerType(u16 type, Factory f) return; m_types[type] = f; } - - diff --git a/src/client/clientobject.h b/src/client/clientobject.h index ecd8059ef..8e59b0a91 100644 --- a/src/client/clientobject.h +++ b/src/client/clientobject.h @@ -24,7 +24,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include - class ClientEnvironment; class ITextureSource; class Client; @@ -48,16 +47,20 @@ public: virtual bool getSelectionBox(aabb3f *toset) const { return false; } virtual bool collideWithObjects() const { return false; } virtual const v3f getPosition() const { return v3f(0.0f); } - virtual scene::ISceneNode *getSceneNode() const - { return NULL; } + virtual scene::ISceneNode *getSceneNode() const { return NULL; } virtual scene::IAnimatedMeshSceneNode *getAnimatedMeshSceneNode() const - { return NULL; } + { + return NULL; + } virtual bool isLocalPlayer() const { return false; } virtual ClientActiveObject *getParent() const { return nullptr; }; virtual const std::unordered_set &getAttachmentChildIds() const - { static std::unordered_set rv; return rv; } - virtual void updateAttachments() {}; + { + static std::unordered_set rv; + return rv; + } + virtual void updateAttachments(){}; virtual bool doShowSelectionBox() { return true; } @@ -77,12 +80,15 @@ public: virtual void initialize(const std::string &data) {} // Create a certain type of ClientActiveObject - static ClientActiveObject *create(ActiveObjectType type, Client *client, - ClientEnvironment *env); + static ClientActiveObject *create( + ActiveObjectType type, Client *client, ClientEnvironment *env); // If returns true, punch will not be sent to the server virtual bool directReportPunch(v3f dir, const ItemStack *punchitem = nullptr, - float time_from_last_punch = 1000000) { return false; } + float time_from_last_punch = 1000000) + { + return false; + } protected: // Used for creating objects based on type @@ -90,6 +96,7 @@ protected: static void registerType(u16 type, Factory f); Client *m_client; ClientEnvironment *m_env; + private: // Used for creating objects based on type static std::unordered_map m_types; @@ -106,7 +113,7 @@ public: d = a_d; } - bool operator < (const DistanceSortedActiveObject &other) const + bool operator<(const DistanceSortedActiveObject &other) const { return d < other.d; } diff --git a/src/client/clouds.cpp b/src/client/clouds.cpp index 887a62f25..e59eb8b4d 100644 --- a/src/client/clouds.cpp +++ b/src/client/clouds.cpp @@ -26,7 +26,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "settings.h" #include - // Menu clouds are created later class Clouds; Clouds *g_menuclouds = NULL; @@ -40,48 +39,43 @@ static void cloud_3d_setting_changed(const std::string &settingname, void *data) ((Clouds *)data)->readSettings(); } -Clouds::Clouds(scene::ISceneManager* mgr, - s32 id, - u32 seed -): - scene::ISceneNode(mgr->getRootSceneNode(), mgr, id), - m_seed(seed) +Clouds::Clouds(scene::ISceneManager *mgr, s32 id, u32 seed) : + scene::ISceneNode(mgr->getRootSceneNode(), mgr, id), m_seed(seed) { m_material.setFlag(video::EMF_LIGHTING, false); - //m_material.setFlag(video::EMF_BACK_FACE_CULLING, false); + // m_material.setFlag(video::EMF_BACK_FACE_CULLING, false); m_material.setFlag(video::EMF_BACK_FACE_CULLING, true); m_material.setFlag(video::EMF_BILINEAR_FILTER, false); m_material.setFlag(video::EMF_FOG_ENABLE, true); m_material.setFlag(video::EMF_ANTI_ALIASING, true); - //m_material.MaterialType = video::EMT_TRANSPARENT_VERTEX_ALPHA; + // m_material.MaterialType = video::EMT_TRANSPARENT_VERTEX_ALPHA; m_material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; - m_params.height = 120; - m_params.density = 0.4f; - m_params.thickness = 16.0f; - m_params.color_bright = video::SColor(229, 240, 240, 255); + m_params.height = 120; + m_params.density = 0.4f; + m_params.thickness = 16.0f; + m_params.color_bright = video::SColor(229, 240, 240, 255); m_params.color_ambient = video::SColor(255, 0, 0, 0); - m_params.speed = v2f(0.0f, -2.0f); + m_params.speed = v2f(0.0f, -2.0f); readSettings(); - g_settings->registerChangedCallback("enable_3d_clouds", - &cloud_3d_setting_changed, this); + g_settings->registerChangedCallback( + "enable_3d_clouds", &cloud_3d_setting_changed, this); updateBox(); } Clouds::~Clouds() { - g_settings->deregisterChangedCallback("enable_3d_clouds", - &cloud_3d_setting_changed, this); + g_settings->deregisterChangedCallback( + "enable_3d_clouds", &cloud_3d_setting_changed, this); } void Clouds::OnRegisterSceneNode() { - if(IsVisible) - { + if (IsVisible) { SceneManager->registerNodeForRendering(this, scene::ESNRP_TRANSPARENT); - //SceneManager->registerNodeForRendering(this, scene::ESNRP_SOLID); + // SceneManager->registerNodeForRendering(this, scene::ESNRP_SOLID); } ISceneNode::OnRegisterSceneNode(); @@ -93,10 +87,10 @@ void Clouds::render() if (m_params.density <= 0.0f) return; // no need to do anything - video::IVideoDriver* driver = SceneManager->getVideoDriver(); + video::IVideoDriver *driver = SceneManager->getVideoDriver(); - if(SceneManager->getSceneNodeRenderPass() != scene::ESNRP_TRANSPARENT) - //if(SceneManager->getSceneNodeRenderPass() != scene::ESNRP_SOLID) + if (SceneManager->getSceneNodeRenderPass() != scene::ESNRP_TRANSPARENT) + // if(SceneManager->getSceneNodeRenderPass() != scene::ESNRP_SOLID) return; ScopeProfiler sp(g_profiler, "Clouds::render()", SPT_AVG); @@ -121,15 +115,14 @@ void Clouds::render() v2f center_of_drawing_in_noise_f = -cloud_origin_from_camera_f; // The integer center point of drawing in the noise v2s16 center_of_drawing_in_noise_i( - std::floor(center_of_drawing_in_noise_f.X / cloud_size), - std::floor(center_of_drawing_in_noise_f.Y / cloud_size) - ); + std::floor(center_of_drawing_in_noise_f.X / cloud_size), + std::floor(center_of_drawing_in_noise_f.Y / cloud_size)); // The world position of the integer center point of drawing in the noise - v2f world_center_of_drawing_in_noise_f = v2f( - center_of_drawing_in_noise_i.X * cloud_size, - center_of_drawing_in_noise_i.Y * cloud_size - ) + m_origin; + v2f world_center_of_drawing_in_noise_f = + v2f(center_of_drawing_in_noise_i.X * cloud_size, + center_of_drawing_in_noise_i.Y * cloud_size) + + m_origin; /*video::SColor c_top(128,b*240,b*240,b*255); video::SColor c_side_1(128,b*230,b*230,b*255); @@ -154,185 +147,185 @@ void Clouds::render() video::SColor c_bottom = c_bottom_f.toSColor(); // Get fog parameters for setting them back later - video::SColor fog_color(0,0,0,0); + video::SColor fog_color(0, 0, 0, 0); video::E_FOG_TYPE fog_type = video::EFT_FOG_LINEAR; f32 fog_start = 0; f32 fog_end = 0; f32 fog_density = 0; bool fog_pixelfog = false; bool fog_rangefog = false; - driver->getFog(fog_color, fog_type, fog_start, fog_end, fog_density, - fog_pixelfog, fog_rangefog); + driver->getFog(fog_color, fog_type, fog_start, fog_end, fog_density, fog_pixelfog, + fog_rangefog); // Set our own fog driver->setFog(fog_color, fog_type, cloud_full_radius * 0.5, - cloud_full_radius*1.2, fog_density, fog_pixelfog, fog_rangefog); + cloud_full_radius * 1.2, fog_density, fog_pixelfog, fog_rangefog); // Read noise bool *grid = new bool[m_cloud_radius_i * 2 * m_cloud_radius_i * 2]; - - for(s16 zi = -m_cloud_radius_i; zi < m_cloud_radius_i; zi++) { - u32 si = (zi + m_cloud_radius_i) * m_cloud_radius_i * 2 + m_cloud_radius_i; + for (s16 zi = -m_cloud_radius_i; zi < m_cloud_radius_i; zi++) { + u32 si = (zi + m_cloud_radius_i) * m_cloud_radius_i * 2 + + m_cloud_radius_i; for (s16 xi = -m_cloud_radius_i; xi < m_cloud_radius_i; xi++) { u32 i = si + xi; - grid[i] = gridFilled( - xi + center_of_drawing_in_noise_i.X, - zi + center_of_drawing_in_noise_i.Y - ); + grid[i] = gridFilled(xi + center_of_drawing_in_noise_i.X, + zi + center_of_drawing_in_noise_i.Y); } } -#define GETINDEX(x, z, radius) (((z)+(radius))*(radius)*2 + (x)+(radius)) -#define INAREA(x, z, radius) \ +#define GETINDEX(x, z, radius) (((z) + (radius)) * (radius)*2 + (x) + (radius)) +#define INAREA(x, z, radius) \ ((x) >= -(radius) && (x) < (radius) && (z) >= -(radius) && (z) < (radius)) - for (s16 zi0= -m_cloud_radius_i; zi0 < m_cloud_radius_i; zi0++) - for (s16 xi0= -m_cloud_radius_i; xi0 < m_cloud_radius_i; xi0++) - { - s16 zi = zi0; - s16 xi = xi0; - // Draw from front to back (needed for transparency) - /*if(zi <= 0) - zi = -m_cloud_radius_i - zi; - if(xi <= 0) - xi = -m_cloud_radius_i - xi;*/ - // Draw from back to front - if(zi >= 0) - zi = m_cloud_radius_i - zi - 1; - if(xi >= 0) - xi = m_cloud_radius_i - xi - 1; + for (s16 zi0 = -m_cloud_radius_i; zi0 < m_cloud_radius_i; zi0++) + for (s16 xi0 = -m_cloud_radius_i; xi0 < m_cloud_radius_i; xi0++) { + s16 zi = zi0; + s16 xi = xi0; + // Draw from front to back (needed for transparency) + /*if(zi <= 0) + zi = -m_cloud_radius_i - zi; + if(xi <= 0) + xi = -m_cloud_radius_i - xi;*/ + // Draw from back to front + if (zi >= 0) + zi = m_cloud_radius_i - zi - 1; + if (xi >= 0) + xi = m_cloud_radius_i - xi - 1; - u32 i = GETINDEX(xi, zi, m_cloud_radius_i); + u32 i = GETINDEX(xi, zi, m_cloud_radius_i); - if (!grid[i]) - continue; + if (!grid[i]) + continue; - v2f p0 = v2f(xi,zi)*cloud_size + world_center_of_drawing_in_noise_f; + v2f p0 = v2f(xi, zi) * cloud_size + + world_center_of_drawing_in_noise_f; - video::S3DVertex v[4] = { - video::S3DVertex(0,0,0, 0,0,0, c_top, 0, 1), - video::S3DVertex(0,0,0, 0,0,0, c_top, 1, 1), - video::S3DVertex(0,0,0, 0,0,0, c_top, 1, 0), - video::S3DVertex(0,0,0, 0,0,0, c_top, 0, 0) - }; + video::S3DVertex v[4] = { + video::S3DVertex(0, 0, 0, 0, 0, 0, c_top, 0, 1), + video::S3DVertex(0, 0, 0, 0, 0, 0, c_top, 1, 1), + video::S3DVertex(0, 0, 0, 0, 0, 0, c_top, 1, 0), + video::S3DVertex(0, 0, 0, 0, 0, 0, c_top, 0, 0)}; - /*if(zi <= 0 && xi <= 0){ - v[0].Color.setBlue(255); - v[1].Color.setBlue(255); - v[2].Color.setBlue(255); - v[3].Color.setBlue(255); - }*/ + /*if(zi <= 0 && xi <= 0){ + v[0].Color.setBlue(255); + v[1].Color.setBlue(255); + v[2].Color.setBlue(255); + v[3].Color.setBlue(255); + }*/ - f32 rx = cloud_size / 2.0f; - // if clouds are flat, the top layer should be at the given height - f32 ry = m_enable_3d ? m_params.thickness * BS : 0.0f; - f32 rz = cloud_size / 2; + f32 rx = cloud_size / 2.0f; + // if clouds are flat, the top layer should be at the given height + f32 ry = m_enable_3d ? m_params.thickness * BS : 0.0f; + f32 rz = cloud_size / 2; - for(int i=0; idrawVertexPrimitiveList(v, 4, indices, 2, + video::EVT_STANDARD, scene::EPT_TRIANGLES, + video::EIT_16BIT); } - - v3f pos(p0.X, m_params.height * BS, p0.Y); - pos -= intToFloat(m_camera_offset, BS); - - for (video::S3DVertex &vertex : v) - vertex.Pos += pos; - u16 indices[] = {0,1,2,2,3,0}; - driver->drawVertexPrimitiveList(v, 4, indices, 2, - video::EVT_STANDARD, scene::EPT_TRIANGLES, video::EIT_16BIT); } - } delete[] grid; // Restore fog settings - driver->setFog(fog_color, fog_type, fog_start, fog_end, fog_density, - fog_pixelfog, fog_rangefog); + driver->setFog(fog_color, fog_type, fog_start, fog_end, fog_density, fog_pixelfog, + fog_rangefog); } void Clouds::step(float dtime) @@ -344,11 +337,17 @@ void Clouds::update(const v3f &camera_p, const video::SColorf &color_diffuse) { m_camera_pos = camera_p; m_color.r = MYMIN(MYMAX(color_diffuse.r * m_params.color_bright.getRed(), - m_params.color_ambient.getRed()), 255) / 255.0f; + m_params.color_ambient.getRed()), + 255) / + 255.0f; m_color.g = MYMIN(MYMAX(color_diffuse.g * m_params.color_bright.getGreen(), - m_params.color_ambient.getGreen()), 255) / 255.0f; + m_params.color_ambient.getGreen()), + 255) / + 255.0f; m_color.b = MYMIN(MYMAX(color_diffuse.b * m_params.color_bright.getBlue(), - m_params.color_ambient.getBlue()), 255) / 255.0f; + m_params.color_ambient.getBlue()), + 255) / + 255.0f; m_color.a = m_params.color_bright.getAlpha() / 255.0f; // is the camera inside the cloud mesh? @@ -358,8 +357,10 @@ void Clouds::update(const v3f &camera_p, const video::SColorf &color_diffuse) if (camera_height >= m_box.MinEdge.Y && camera_height <= m_box.MaxEdge.Y) { v2f camera_in_noise; - camera_in_noise.X = floor((camera_p.X - m_origin.X) / cloud_size + 0.5); - camera_in_noise.Y = floor((camera_p.Z - m_origin.Y) / cloud_size + 0.5); + camera_in_noise.X = floor( + (camera_p.X - m_origin.X) / cloud_size + 0.5); + camera_in_noise.Y = floor( + (camera_p.Z - m_origin.Y) / cloud_size + 0.5); bool filled = gridFilled(camera_in_noise.X, camera_in_noise.Y); m_camera_inside_cloud = filled; } @@ -375,10 +376,8 @@ void Clouds::readSettings() bool Clouds::gridFilled(int x, int y) const { float cloud_size_noise = cloud_size / (BS * 200.f); - float noise = noise2d_perlin( - (float)x * cloud_size_noise, - (float)y * cloud_size_noise, - m_seed, 3, 0.5); + float noise = noise2d_perlin((float)x * cloud_size_noise, + (float)y * cloud_size_noise, m_seed, 3, 0.5); // normalize to 0..1 (given 3 octaves) static constexpr const float noise_bound = 1.0f + 0.5f + 0.25f; float density = noise / noise_bound * 0.5f + 0.5f; diff --git a/src/client/clouds.h b/src/client/clouds.h index a4d810faa..ba371de7d 100644 --- a/src/client/clouds.h +++ b/src/client/clouds.h @@ -29,16 +29,19 @@ class Clouds; extern Clouds *g_menuclouds; // Scene manager used for menu clouds -namespace irr{namespace scene{class ISceneManager;}} +namespace irr +{ +namespace scene +{ +class ISceneManager; +} +} extern irr::scene::ISceneManager *g_menucloudsmgr; class Clouds : public scene::ISceneNode { public: - Clouds(scene::ISceneManager* mgr, - s32 id, - u32 seed - ); + Clouds(scene::ISceneManager *mgr, s32 id, u32 seed); ~Clouds(); @@ -50,20 +53,11 @@ public: virtual void render(); - virtual const aabb3f &getBoundingBox() const - { - return m_box; - } + virtual const aabb3f &getBoundingBox() const { return m_box; } - virtual u32 getMaterialCount() const - { - return 1; - } + virtual u32 getMaterialCount() const { return 1; } - virtual video::SMaterial& getMaterial(u32 i) - { - return m_material; - } + virtual video::SMaterial &getMaterial(u32 i) { return m_material; } /* Other stuff @@ -103,10 +97,7 @@ public: updateBox(); } - void setSpeed(v2f speed) - { - m_params.speed = speed; - } + void setSpeed(v2f speed) { m_params.speed = speed; } void setThickness(float thickness) { @@ -121,10 +112,12 @@ public: private: void updateBox() { - float height_bs = m_params.height * BS; + float height_bs = m_params.height * BS; float thickness_bs = m_params.thickness * BS; - m_box = aabb3f(-BS * 1000000.0f, height_bs - BS * m_camera_offset.Y, -BS * 1000000.0f, - BS * 1000000.0f, height_bs + thickness_bs - BS * m_camera_offset.Y, BS * 1000000.0f); + m_box = aabb3f(-BS * 1000000.0f, height_bs - BS * m_camera_offset.Y, + -BS * 1000000.0f, BS * 1000000.0f, + height_bs + thickness_bs - BS * m_camera_offset.Y, + BS * 1000000.0f); } bool gridFilled(int x, int y) const; @@ -140,5 +133,4 @@ private: video::SColorf m_color = video::SColorf(1.0f, 1.0f, 1.0f, 1.0f); CloudParams m_params; bool m_camera_inside_cloud = false; - }; diff --git a/src/client/content_cao.cpp b/src/client/content_cao.cpp index cf671d5ca..36a47d57c 100644 --- a/src/client/content_cao.cpp +++ b/src/client/content_cao.cpp @@ -53,8 +53,7 @@ struct ToolCapabilities; std::unordered_map ClientActiveObject::m_types; -template -void SmoothTranslator::init(T current) +template void SmoothTranslator::init(T current) { val_old = current; val_current = current; @@ -64,8 +63,9 @@ void SmoothTranslator::init(T current) aim_is_end = true; } -template -void SmoothTranslator::update(T new_target, bool is_end_position, float update_interval) +template +void SmoothTranslator::update( + T new_target, bool is_end_position, float update_interval) { aim_is_end = is_end_position; val_old = val_current; @@ -81,8 +81,7 @@ void SmoothTranslator::update(T new_target, bool is_end_position, float updat anim_time_counter = 0; } -template -void SmoothTranslator::translate(f32 dtime) +template void SmoothTranslator::translate(f32 dtime) { anim_time_counter = anim_time_counter + dtime; T val_diff = val_target - val_old; @@ -110,8 +109,7 @@ void SmoothTranslatorWrapped::translate(f32 dtime) // Move a bit less than should, to avoid oscillation moveratio = std::min(moveratio * 0.8f, move_end); - wrappedApproachShortest(val_current, val_target, - val_diff * moveratio, 360.f); + wrappedApproachShortest(val_current, val_target, val_diff * moveratio, 360.f); } void SmoothTranslatorWrappedv3f::translate(f32 dtime) @@ -139,26 +137,26 @@ void SmoothTranslatorWrappedv3f::translate(f32 dtime) // Move a bit less than should, to avoid oscillation moveratio = std::min(moveratio * 0.8f, move_end); - wrappedApproachShortest(val_current.X, val_target.X, - val_diff_v3f.X * moveratio, 360.f); + wrappedApproachShortest( + val_current.X, val_target.X, val_diff_v3f.X * moveratio, 360.f); - wrappedApproachShortest(val_current.Y, val_target.Y, - val_diff_v3f.Y * moveratio, 360.f); + wrappedApproachShortest( + val_current.Y, val_target.Y, val_diff_v3f.Y * moveratio, 360.f); - wrappedApproachShortest(val_current.Z, val_target.Z, - val_diff_v3f.Z * moveratio, 360.f); + wrappedApproachShortest( + val_current.Z, val_target.Z, val_diff_v3f.Z * moveratio, 360.f); } /* Other stuff */ -static void setBillboardTextureMatrix(scene::IBillboardSceneNode *bill, - float txs, float tys, int col, int row) +static void setBillboardTextureMatrix( + scene::IBillboardSceneNode *bill, float txs, float tys, int col, int row) { - video::SMaterial& material = bill->getMaterial(0); - core::matrix4& matrix = material.getTextureMatrix(0); - matrix.setTextureTranslate(txs*col, tys*row); + video::SMaterial &material = bill->getMaterial(0); + core::matrix4 &matrix = material.getTextureMatrix(0); + matrix.setTextureTranslate(txs * col, tys * row); matrix.setTextureScale(txs, tys); } @@ -181,12 +179,9 @@ public: TestCAO(Client *client, ClientEnvironment *env); virtual ~TestCAO() = default; - ActiveObjectType getType() const - { - return ACTIVEOBJECT_TYPE_TEST; - } + ActiveObjectType getType() const { return ACTIVEOBJECT_TYPE_TEST; } - static ClientActiveObject* create(Client *client, ClientEnvironment *env); + static ClientActiveObject *create(Client *client, ClientEnvironment *env); void addToScene(ITextureSource *tsrc); void removeFromScene(bool permanent); @@ -198,6 +193,7 @@ public: void processMessage(const std::string &data); bool getCollisionBox(aabb3f *toset) const { return false; } + private: scene::IMeshSceneNode *m_node; v3f m_position; @@ -206,40 +202,38 @@ private: // Prototype TestCAO proto_TestCAO(NULL, NULL); -TestCAO::TestCAO(Client *client, ClientEnvironment *env): - ClientActiveObject(0, client, env), - m_node(NULL), - m_position(v3f(0,10*BS,0)) +TestCAO::TestCAO(Client *client, ClientEnvironment *env) : + ClientActiveObject(0, client, env), m_node(NULL), + m_position(v3f(0, 10 * BS, 0)) { ClientActiveObject::registerType(getType(), create); } -ClientActiveObject* TestCAO::create(Client *client, ClientEnvironment *env) +ClientActiveObject *TestCAO::create(Client *client, ClientEnvironment *env) { return new TestCAO(client, env); } void TestCAO::addToScene(ITextureSource *tsrc) { - if(m_node != NULL) + if (m_node != NULL) return; - //video::IVideoDriver* driver = smgr->getVideoDriver(); + // video::IVideoDriver* driver = smgr->getVideoDriver(); scene::SMesh *mesh = new scene::SMesh(); scene::IMeshBuffer *buf = new scene::SMeshBuffer(); - video::SColor c(255,255,255,255); - video::S3DVertex vertices[4] = - { - video::S3DVertex(-BS/2,-BS/4,0, 0,0,0, c, 0,1), - video::S3DVertex(BS/2,-BS/4,0, 0,0,0, c, 1,1), - video::S3DVertex(BS/2,BS/4,0, 0,0,0, c, 1,0), - video::S3DVertex(-BS/2,BS/4,0, 0,0,0, c, 0,0), + video::SColor c(255, 255, 255, 255); + video::S3DVertex vertices[4] = { + video::S3DVertex(-BS / 2, -BS / 4, 0, 0, 0, 0, c, 0, 1), + video::S3DVertex(BS / 2, -BS / 4, 0, 0, 0, 0, c, 1, 1), + video::S3DVertex(BS / 2, BS / 4, 0, 0, 0, 0, c, 1, 0), + video::S3DVertex(-BS / 2, BS / 4, 0, 0, 0, 0, c, 0, 0), }; - u16 indices[] = {0,1,2,2,3,0}; + u16 indices[] = {0, 1, 2, 2, 3, 0}; buf->append(vertices, 4, indices, 6); // Set material - buf->getMaterial().setFlag(video::EMF_LIGHTING, true); // false + buf->getMaterial().setFlag(video::EMF_LIGHTING, true); // false buf->getMaterial().setFlag(video::EMF_BACK_FACE_CULLING, false); buf->getMaterial().setTexture(0, tsrc->getTextureForMesh("rat.png")); buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false); @@ -272,15 +266,14 @@ void TestCAO::updateNodePos() return; m_node->setPosition(m_position); - //m_node->setRotation(v3f(0, 45, 0)); + // m_node->setRotation(v3f(0, 45, 0)); } void TestCAO::step(float dtime, ClientEnvironment *env) { - if(m_node) - { + if (m_node) { v3f rot = m_node->getRotation(); - //infostream<<"dtime="<>cmd; - if(cmd == 0) - { + is >> cmd; + if (cmd == 0) { v3f newpos; - is>>newpos.X; - is>>newpos.Y; - is>>newpos.Z; + is >> newpos.X; + is >> newpos.Y; + is >> newpos.Z; m_position = newpos; updateNodePos(); } @@ -309,7 +301,7 @@ void TestCAO::processMessage(const std::string &data) #include "clientobject.h" -GenericCAO::GenericCAO(Client *client, ClientEnvironment *env): +GenericCAO::GenericCAO(Client *client, ClientEnvironment *env) : ClientActiveObject(0, client, env) { if (client == NULL) { @@ -321,9 +313,8 @@ GenericCAO::GenericCAO(Client *client, ClientEnvironment *env): bool GenericCAO::getCollisionBox(aabb3f *toset) const { - if (m_prop.physical) - { - //update collision box + if (m_prop.physical) { + // update collision box toset->MinEdge = m_prop.collisionbox.MinEdge * BS; toset->MaxEdge = m_prop.collisionbox.MaxEdge * BS; @@ -343,7 +334,7 @@ bool GenericCAO::collideWithObjects() const void GenericCAO::initialize(const std::string &data) { - infostream<<"GenericCAO: Got init data"<getCameraOffset(); return m_matrixnode->getAbsolutePosition() + - intToFloat(camera_offset, BS); + intToFloat(camera_offset, BS); } return m_position; @@ -461,7 +451,8 @@ void GenericCAO::setChildrenVisible(bool toset) } } -void GenericCAO::setAttachment(int parent_id, const std::string &bone, v3f position, v3f rotation) +void GenericCAO::setAttachment( + int parent_id, const std::string &bone, v3f position, v3f rotation) { int old_parent = m_attachment_parent_id; m_attachment_parent_id = parent_id; @@ -479,12 +470,12 @@ void GenericCAO::setAttachment(int parent_id, const std::string &bone, v3f posit if (parent) parent->addAttachmentChild(m_id); } - + updateAttachments(); } -void GenericCAO::getAttachment(int *parent_id, std::string *bone, v3f *position, - v3f *rotation) const +void GenericCAO::getAttachment( + int *parent_id, std::string *bone, v3f *position, v3f *rotation) const { *parent_id = m_attachment_parent_id; *bone = m_attachment_bone; @@ -523,10 +514,10 @@ void GenericCAO::removeAttachmentChild(int child_id) m_attachment_child_ids.erase(child_id); } -ClientActiveObject* GenericCAO::getParent() const +ClientActiveObject *GenericCAO::getParent() const { - return m_attachment_parent_id ? m_env->getActiveObject(m_attachment_parent_id) : - nullptr; + return m_attachment_parent_id ? m_env->getActiveObject(m_attachment_parent_id) + : nullptr; } void GenericCAO::removeFromScene(bool permanent) @@ -544,7 +535,7 @@ void GenericCAO::removeFromScene(bool permanent) m_meshnode->remove(); m_meshnode->drop(); m_meshnode = nullptr; - } else if (m_animated_meshnode) { + } else if (m_animated_meshnode) { m_animated_meshnode->remove(); m_animated_meshnode->drop(); m_animated_meshnode = nullptr; @@ -590,26 +581,30 @@ void GenericCAO::addToScene(ITextureSource *tsrc) MaterialType material_type; if (m_prop.shaded && m_prop.glow == 0) - material_type = (m_prop.use_texture_alpha) ? - TILE_MATERIAL_ALPHA : TILE_MATERIAL_BASIC; + 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; + 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); + 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) ? - video::EMT_TRANSPARENT_ALPHA_CHANNEL : video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; + m_material_type = + (m_prop.use_texture_alpha) + ? video::EMT_TRANSPARENT_ALPHA_CHANNEL + : video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; } auto grabMatrixNode = [this] { - m_matrixnode = RenderingEngine::get_scene_manager()-> - addDummyTransformationSceneNode(); + m_matrixnode = RenderingEngine::get_scene_manager() + ->addDummyTransformationSceneNode(); m_matrixnode->grab(); }; - auto setSceneNodeMaterial = [this] (scene::ISceneNode *node) { + auto setSceneNodeMaterial = [this](scene::ISceneNode *node) { node->setMaterialFlag(video::EMF_LIGHTING, false); node->setMaterialFlag(video::EMF_BILINEAR_FILTER, false); node->setMaterialFlag(video::EMF_FOG_ENABLE, true); @@ -623,21 +618,22 @@ void GenericCAO::addToScene(ITextureSource *tsrc) if (m_prop.visual == "sprite") { grabMatrixNode(); - m_spritenode = RenderingEngine::get_scene_manager()->addBillboardSceneNode( - m_matrixnode, v2f(1, 1), v3f(0,0,0), -1); + m_spritenode = RenderingEngine::get_scene_manager() + ->addBillboardSceneNode(m_matrixnode, + v2f(1, 1), v3f(0, 0, 0), + -1); m_spritenode->grab(); - m_spritenode->setMaterialTexture(0, - tsrc->getTextureForMesh("unknown_node.png")); + m_spritenode->setMaterialTexture( + 0, tsrc->getTextureForMesh("unknown_node.png")); setSceneNodeMaterial(m_spritenode); - m_spritenode->setSize(v2f(m_prop.visual_size.X, - m_prop.visual_size.Y) * BS); + m_spritenode->setSize( + v2f(m_prop.visual_size.X, m_prop.visual_size.Y) * BS); { const float txs = 1.0 / 1; const float tys = 1.0 / 1; - setBillboardTextureMatrix(m_spritenode, - txs, tys, 0, 0); + setBillboardTextureMatrix(m_spritenode, txs, tys, 0, 0); } } else if (m_prop.visual == "upright_sprite") { grabMatrixNode(); @@ -649,17 +645,17 @@ void GenericCAO::addToScene(ITextureSource *tsrc) { // Front scene::IMeshBuffer *buf = new scene::SMeshBuffer(); video::S3DVertex vertices[4] = { - video::S3DVertex(-dx, -dy, 0, 0,0,1, c, 1,1), - video::S3DVertex( dx, -dy, 0, 0,0,1, c, 0,1), - video::S3DVertex( dx, dy, 0, 0,0,1, c, 0,0), - video::S3DVertex(-dx, dy, 0, 0,0,1, c, 1,0), + video::S3DVertex(-dx, -dy, 0, 0, 0, 1, c, 1, 1), + video::S3DVertex(dx, -dy, 0, 0, 0, 1, c, 0, 1), + video::S3DVertex(dx, dy, 0, 0, 0, 1, c, 0, 0), + video::S3DVertex(-dx, dy, 0, 0, 0, 1, c, 1, 0), }; if (m_is_player) { // Move minimal Y position to 0 (feet position) for (video::S3DVertex &vertex : vertices) vertex.Pos.Y += dy; } - u16 indices[] = {0,1,2,2,3,0}; + u16 indices[] = {0, 1, 2, 2, 3, 0}; buf->append(vertices, 4, indices, 6); // Set material buf->getMaterial().setFlag(video::EMF_LIGHTING, false); @@ -669,8 +665,10 @@ void GenericCAO::addToScene(ITextureSource *tsrc) if (m_enable_shaders) { buf->getMaterial().EmissiveColor = c; - buf->getMaterial().setFlag(video::EMF_GOURAUD_SHADING, false); - buf->getMaterial().setFlag(video::EMF_NORMALIZE_NORMALS, true); + buf->getMaterial().setFlag( + video::EMF_GOURAUD_SHADING, false); + buf->getMaterial().setFlag( + video::EMF_NORMALIZE_NORMALS, true); } // Add to mesh @@ -680,17 +678,17 @@ void GenericCAO::addToScene(ITextureSource *tsrc) { // Back scene::IMeshBuffer *buf = new scene::SMeshBuffer(); video::S3DVertex vertices[4] = { - video::S3DVertex( dx,-dy, 0, 0,0,-1, c, 1,1), - video::S3DVertex(-dx,-dy, 0, 0,0,-1, c, 0,1), - video::S3DVertex(-dx, dy, 0, 0,0,-1, c, 0,0), - video::S3DVertex( dx, dy, 0, 0,0,-1, c, 1,0), + video::S3DVertex(dx, -dy, 0, 0, 0, -1, c, 1, 1), + video::S3DVertex(-dx, -dy, 0, 0, 0, -1, c, 0, 1), + video::S3DVertex(-dx, dy, 0, 0, 0, -1, c, 0, 0), + video::S3DVertex(dx, dy, 0, 0, 0, -1, c, 1, 0), }; if (m_is_player) { // Move minimal Y position to 0 (feet position) for (video::S3DVertex &vertex : vertices) vertex.Pos.Y += dy; } - u16 indices[] = {0,1,2,2,3,0}; + u16 indices[] = {0, 1, 2, 2, 3, 0}; buf->append(vertices, 4, indices, 6); // Set material buf->getMaterial().setFlag(video::EMF_LIGHTING, false); @@ -700,16 +698,18 @@ void GenericCAO::addToScene(ITextureSource *tsrc) if (m_enable_shaders) { buf->getMaterial().EmissiveColor = c; - buf->getMaterial().setFlag(video::EMF_GOURAUD_SHADING, false); - buf->getMaterial().setFlag(video::EMF_NORMALIZE_NORMALS, true); + buf->getMaterial().setFlag( + video::EMF_GOURAUD_SHADING, false); + buf->getMaterial().setFlag( + video::EMF_NORMALIZE_NORMALS, true); } // Add to mesh mesh->addMeshBuffer(buf); buf->drop(); } - m_meshnode = RenderingEngine::get_scene_manager()-> - addMeshSceneNode(mesh, m_matrixnode); + m_meshnode = RenderingEngine::get_scene_manager()->addMeshSceneNode( + mesh, m_matrixnode); m_meshnode->grab(); mesh->drop(); // Set it to use the materials of the meshbuffers directly. @@ -717,47 +717,54 @@ void GenericCAO::addToScene(ITextureSource *tsrc) m_meshnode->setReadOnlyMaterials(true); } else if (m_prop.visual == "cube") { grabMatrixNode(); - scene::IMesh *mesh = createCubeMesh(v3f(BS,BS,BS)); - m_meshnode = RenderingEngine::get_scene_manager()-> - addMeshSceneNode(mesh, m_matrixnode); + scene::IMesh *mesh = createCubeMesh(v3f(BS, BS, BS)); + m_meshnode = RenderingEngine::get_scene_manager()->addMeshSceneNode( + mesh, m_matrixnode); m_meshnode->grab(); mesh->drop(); m_meshnode->setScale(m_prop.visual_size); - m_meshnode->setMaterialFlag(video::EMF_BACK_FACE_CULLING, - m_prop.backface_culling); + m_meshnode->setMaterialFlag( + video::EMF_BACK_FACE_CULLING, m_prop.backface_culling); setSceneNodeMaterial(m_meshnode); } else if (m_prop.visual == "mesh") { grabMatrixNode(); scene::IAnimatedMesh *mesh = m_client->getMesh(m_prop.mesh, true); if (mesh) { - m_animated_meshnode = RenderingEngine::get_scene_manager()-> - addAnimatedMeshSceneNode(mesh, m_matrixnode); + m_animated_meshnode = + RenderingEngine::get_scene_manager() + ->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); + 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 + ->animateJoints(); // Needed for some animations m_animated_meshnode->setScale(m_prop.visual_size); // set vertex colors to ensure alpha is set - setMeshColor(m_animated_meshnode->getMesh(), video::SColor(0xFFFFFFFF)); + setMeshColor(m_animated_meshnode->getMesh(), + video::SColor(0xFFFFFFFF)); - setAnimatedMeshColor(m_animated_meshnode, video::SColor(0xFFFFFFFF)); + setAnimatedMeshColor( + m_animated_meshnode, video::SColor(0xFFFFFFFF)); setSceneNodeMaterial(m_animated_meshnode); m_animated_meshnode->setMaterialFlag(video::EMF_BACK_FACE_CULLING, - m_prop.backface_culling); + m_prop.backface_culling); } else - errorstream<<"GenericCAO::addToScene(): Could not load mesh "<idef(); item = ItemStack(m_prop.textures[0], 1, 0, idef); } } else { - infostream << "serialized form: " << m_prop.wield_item << std::endl; + infostream << "serialized form: " << m_prop.wield_item + << std::endl; item.deSerialize(m_prop.wield_item, m_client->idef()); } m_wield_meshnode = new WieldMeshSceneNode( - RenderingEngine::get_scene_manager(), -1); - m_wield_meshnode->setItem(item, m_client, - (m_prop.visual == "wielditem")); + RenderingEngine::get_scene_manager(), -1); + m_wield_meshnode->setItem(item, m_client, (m_prop.visual == "wielditem")); m_wield_meshnode->setScale(m_prop.visual_size / 2.0f); m_wield_meshnode->setColor(video::SColor(0xFFFFFFFF)); } else { - infostream<<"GenericCAO::addToScene(): \""<getBool("fullbright")) light_at_pos = 255; - + 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()); + u8 this_light = n.getLightBlend( + day_night_ratio, m_client->ndef()); light_at_pos = MYMAX(light_at_pos, this_light); pos_ok = true; } @@ -891,7 +899,8 @@ u16 GenericCAO::getLightPosition(v3s16 *pos) void GenericCAO::updateNametag() { - if (m_is_local_player && ! g_settings->getBool("freecam")) // No nametag for local player + if (m_is_local_player && + !g_settings->getBool("freecam")) // No nametag for local player return; if (m_prop.nametag.empty()) { @@ -911,8 +920,8 @@ void GenericCAO::updateNametag() 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); + 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; @@ -930,11 +939,11 @@ void GenericCAO::updateNodePos() if (node) { v3s16 camera_offset = m_env->getCameraOffset(); - v3f pos = pos_translator.val_current - - intToFloat(camera_offset, BS); + v3f pos = pos_translator.val_current - intToFloat(camera_offset, BS); getPosRotMatrix().setTranslation(pos); if (node != m_spritenode) { // rotate if not a sprite - v3f rot = m_is_local_player ? -m_rotation : -rot_translator.val_current; + v3f rot = m_is_local_player ? -m_rotation + : -rot_translator.val_current; setPitchYawRoll(getPosRotMatrix(), rot); } } @@ -947,7 +956,7 @@ void GenericCAO::step(float dtime, ClientEnvironment *env) LocalPlayer *player = m_env->getLocalPlayer(); m_position = player->getLegitPosition(); pos_translator.val_current = m_position; - if (! g_settings->getBool("freecam")) { + if (!g_settings->getBool("freecam")) { m_rotation.Y = wrapDegrees_0_360(player->getYaw()); rot_translator.val_current = m_rotation; } @@ -955,39 +964,43 @@ void GenericCAO::step(float dtime, ClientEnvironment *env) if (m_is_visible) { int old_anim = player->last_animation; float old_anim_speed = player->last_animation_speed; - m_velocity = v3f(0,0,0); - m_acceleration = v3f(0,0,0); + m_velocity = v3f(0, 0, 0); + m_acceleration = v3f(0, 0, 0); const PlayerControl &controls = player->getPlayerControl(); bool walking = false; - if ((controls.up || controls.down || controls.left || controls.right || - controls.forw_move_joystick_axis != 0.f || - controls.sidew_move_joystick_axis != 0.f) && ! g_settings->getBool("freecam")) + if ((controls.up || controls.down || controls.left || + controls.right || + controls.forw_move_joystick_axis != 0.f || + controls.sidew_move_joystick_axis != 0.f) && + !g_settings->getBool("freecam")) walking = true; f32 new_speed = player->local_animation_speed; - v2s32 new_anim = v2s32(0,0); + v2s32 new_anim = v2s32(0, 0); bool allow_update = false; // increase speed if using fast or flying fast - if((g_settings->getBool("fast_move") && - m_client->checkLocalPrivilege("fast")) && + if ((g_settings->getBool("fast_move") && + m_client->checkLocalPrivilege("fast")) && (controls.aux1 || - (!player->touching_ground && - g_settings->getBool("free_move") && - m_client->checkLocalPrivilege("fly")))) - new_speed *= 1.5; + (!player->touching_ground && + g_settings->getBool( + "free_move") && + m_client->checkLocalPrivilege( + "fly")))) + new_speed *= 1.5; // slowdown speed if sneeking - if (controls.sneak && walking && ! g_settings->getBool("no_slow")) + if (controls.sneak && walking && !g_settings->getBool("no_slow")) new_speed /= 2; if (walking && (controls.LMB || controls.RMB)) { new_anim = player->local_animations[3]; player->last_animation = WD_ANIM; - } else if(walking) { + } else if (walking) { new_anim = player->local_animations[1]; player->last_animation = WALK_ANIM; - } else if(controls.LMB || controls.RMB) { + } else if (controls.LMB || controls.RMB) { new_anim = player->local_animations[2]; player->last_animation = DIG_ANIM; } @@ -1010,10 +1023,9 @@ void GenericCAO::step(float dtime, ClientEnvironment *env) // Update local player animations if ((player->last_animation != old_anim || - m_animation_speed != old_anim_speed) && - player->last_animation != NO_ANIM && allow_update) - updateAnimation(); - + m_animation_speed != old_anim_speed) && + player->last_animation != NO_ANIM && allow_update) + updateAnimation(); } } @@ -1026,17 +1038,20 @@ void GenericCAO::step(float dtime, ClientEnvironment *env) ClientActiveObject *obj = m_env->getActiveObject(cao_id); if (obj) { scene::ISceneNode *child_node = obj->getSceneNode(); - // The node's parent is always an IDummyTraformationSceneNode, - // so we need to reparent that one instead. + // The node's parent is always an + // IDummyTraformationSceneNode, so we need to reparent + // that one instead. if (child_node) - child_node->getParent()->setParent(m_smgr->getRootSceneNode()); + child_node->getParent()->setParent( + m_smgr->getRootSceneNode()); } } removeFromScene(false); addToScene(m_client->tsrc()); - // Attachments, part 2: Now that the parent has been refreshed, put its attachments back + // Attachments, part 2: Now that the parent has been refreshed, put its + // attachments back for (u16 cao_id : m_attachment_child_ids) { ClientActiveObject *obj = m_env->getActiveObject(cao_id); if (obj) @@ -1049,31 +1064,31 @@ void GenericCAO::step(float dtime, ClientEnvironment *env) if (node) node->setVisible(m_is_visible); - if(getParent() != NULL) // Attachments should be glued to their parent by Irrlicht + if (getParent() != + NULL) // Attachments should be glued to their parent by Irrlicht { // Set these for later m_position = getPosition(); - m_velocity = v3f(0,0,0); - m_acceleration = v3f(0,0,0); + m_velocity = v3f(0, 0, 0); + m_acceleration = v3f(0, 0, 0); pos_translator.val_current = m_position; pos_translator.val_target = m_position; } else { rot_translator.translate(dtime); v3f lastpos = pos_translator.val_current; - if(m_prop.physical) - { + if (m_prop.physical) { aabb3f box = m_prop.collisionbox; box.MinEdge *= BS; box.MaxEdge *= BS; collisionMoveResult moveresult; - f32 pos_max_d = BS*0.125; // Distance per iteration + f32 pos_max_d = BS * 0.125; // Distance per iteration v3f p_pos = m_position; v3f p_velocity = m_velocity; - moveresult = collisionMoveSimple(env,env->getGameDef(), - pos_max_d, box, m_prop.stepheight, dtime, - &p_pos, &p_velocity, m_acceleration, - this, m_prop.collideWithObjects); + moveresult = collisionMoveSimple(env, env->getGameDef(), + pos_max_d, box, m_prop.stepheight, dtime, &p_pos, + &p_velocity, m_acceleration, this, + m_prop.collideWithObjects); // Apply results m_position = p_pos; m_velocity = p_velocity; @@ -1081,7 +1096,8 @@ void GenericCAO::step(float dtime, ClientEnvironment *env) bool is_end_position = moveresult.collides; pos_translator.update(m_position, is_end_position, dtime); } else { - m_position += dtime * m_velocity + 0.5 * dtime * dtime * m_acceleration; + m_position += dtime * m_velocity + + 0.5 * dtime * dtime * m_acceleration; m_velocity += dtime * m_acceleration; pos_translator.update(m_position, pos_translator.aim_is_end, pos_translator.anim_time); @@ -1095,56 +1111,62 @@ void GenericCAO::step(float dtime, ClientEnvironment *env) m_step_distance_counter = 0.0f; if (!m_is_local_player && m_prop.makes_footstep_sound) { const NodeDefManager *ndef = m_client->ndef(); - v3s16 p = floatToInt(getPosition() + - v3f(0.0f, (m_prop.collisionbox.MinEdge.Y - 0.5f) * BS, 0.0f), BS); + v3s16 p = floatToInt( + getPosition() + v3f(0.0f, + (m_prop.collisionbox.MinEdge.Y - + 0.5f) * + BS, + 0.0f), + BS); MapNode n = m_env->getMap().getNode(p); SimpleSoundSpec spec = ndef->get(n).sound_footstep; // Reduce footstep gain, as non-local-player footsteps are // somehow louder. spec.gain *= 0.6f; - m_client->sound()->playSoundAt(spec, false, getPosition()); + m_client->sound()->playSoundAt( + spec, false, getPosition()); } } } m_anim_timer += dtime; - if(m_anim_timer >= m_anim_framelength) - { + if (m_anim_timer >= m_anim_framelength) { m_anim_timer -= m_anim_framelength; m_anim_frame++; - if(m_anim_frame >= m_anim_num_frames) + if (m_anim_frame >= m_anim_num_frames) m_anim_frame = 0; } updateTexturePos(); - if(m_reset_textures_timer >= 0) - { + if (m_reset_textures_timer >= 0) { m_reset_textures_timer -= dtime; - if(m_reset_textures_timer <= 0) { + if (m_reset_textures_timer <= 0) { m_reset_textures_timer = -1; updateTextures(m_previous_texture_modifier); } } if (!getParent() && std::fabs(m_prop.automatic_rotate) > 0.001) { - // This is the child node's rotation. It is only used for automatic_rotate. + // 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); + 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 && (fabs(m_velocity.Z) > 0.001 || fabs(m_velocity.X) > 0.001)) { - float target_yaw = atan2(m_velocity.Z, m_velocity.X) * 180 / M_PI - + m_prop.automatic_face_movement_dir_offset; + float target_yaw = atan2(m_velocity.Z, m_velocity.X) * 180 / M_PI + + m_prop.automatic_face_movement_dir_offset; float max_rotation_per_sec = m_prop.automatic_face_movement_max_rotation_per_sec; if (max_rotation_per_sec > 0) { wrappedApproachShortest(m_rotation.Y, target_yaw, - dtime * max_rotation_per_sec, 360.f); + dtime * max_rotation_per_sec, 360.f); } else { // Negative values of max_rotation_per_sec mean disabled. m_rotation.Y = target_yaw; @@ -1169,14 +1191,13 @@ void GenericCAO::step(float dtime, ClientEnvironment *env) void GenericCAO::updateTexturePos() { - if(m_spritenode) - { - scene::ICameraSceneNode* camera = + if (m_spritenode) { + scene::ICameraSceneNode *camera = m_spritenode->getSceneManager()->getActiveCamera(); - if(!camera) + if (!camera) return; - v3f cam_to_entity = m_spritenode->getAbsolutePosition() - - camera->getAbsolutePosition(); + v3f cam_to_entity = m_spritenode->getAbsolutePosition() - + camera->getAbsolutePosition(); cam_to_entity.normalize(); int row = m_tx_basepos.Y; @@ -1188,17 +1209,17 @@ void GenericCAO::updateTexturePos() else if (cam_to_entity.Y < -0.75) col += 4; else { - float mob_dir = - atan2(cam_to_entity.Z, cam_to_entity.X) / M_PI * 180.; + float mob_dir = atan2(cam_to_entity.Z, cam_to_entity.X) / + M_PI * 180.; float dir = mob_dir - m_rotation.Y; dir = wrapDegrees_180(dir); if (std::fabs(wrapDegrees_180(dir - 0)) <= 45.1f) col += 2; - else if(std::fabs(wrapDegrees_180(dir - 90)) <= 45.1f) + else if (std::fabs(wrapDegrees_180(dir - 90)) <= 45.1f) col += 3; - else if(std::fabs(wrapDegrees_180(dir - 180)) <= 45.1f) + else if (std::fabs(wrapDegrees_180(dir - 180)) <= 45.1f) col += 0; - else if(std::fabs(wrapDegrees_180(dir + 90)) <= 45.1f) + else if (std::fabs(wrapDegrees_180(dir + 90)) <= 45.1f) col += 1; else col += 4; @@ -1235,87 +1256,106 @@ void GenericCAO::updateTextures(std::string mod) texturestring += mod; m_spritenode->getMaterial(0).MaterialType = m_material_type; m_spritenode->getMaterial(0).MaterialTypeParam = 0.5f; - m_spritenode->setMaterialTexture(0, - tsrc->getTextureForMesh(texturestring)); + m_spritenode->setMaterialTexture( + 0, tsrc->getTextureForMesh(texturestring)); - // This allows setting per-material colors. However, until a real lighting - // system is added, the code below will have no effect. Once MineTest - // has directional lighting, it should work automatically. + // This allows setting per-material colors. However, until a real + // lighting system is added, the code below will have no effect. + // Once MineTest has directional lighting, it should work + // automatically. if (!m_prop.colors.empty()) { - m_spritenode->getMaterial(0).AmbientColor = m_prop.colors[0]; - m_spritenode->getMaterial(0).DiffuseColor = m_prop.colors[0]; - m_spritenode->getMaterial(0).SpecularColor = m_prop.colors[0]; + m_spritenode->getMaterial(0).AmbientColor = + m_prop.colors[0]; + m_spritenode->getMaterial(0).DiffuseColor = + m_prop.colors[0]; + m_spritenode->getMaterial(0).SpecularColor = + m_prop.colors[0]; } - m_spritenode->getMaterial(0).setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter); - m_spritenode->getMaterial(0).setFlag(video::EMF_BILINEAR_FILTER, use_bilinear_filter); - m_spritenode->getMaterial(0).setFlag(video::EMF_ANISOTROPIC_FILTER, use_anisotropic_filter); + m_spritenode->getMaterial(0).setFlag(video::EMF_TRILINEAR_FILTER, + use_trilinear_filter); + m_spritenode->getMaterial(0).setFlag( + video::EMF_BILINEAR_FILTER, use_bilinear_filter); + m_spritenode->getMaterial(0).setFlag( + video::EMF_ANISOTROPIC_FILTER, + use_anisotropic_filter); } } if (m_animated_meshnode) { if (m_prop.visual == "mesh") { for (u32 i = 0; i < m_prop.textures.size() && - i < m_animated_meshnode->getMaterialCount(); ++i) { + i < m_animated_meshnode->getMaterialCount(); + ++i) { std::string texturestring = m_prop.textures[i]; if (texturestring.empty()) - continue; // Empty texture string means don't modify that material + continue; // Empty texture string means don't + // modify that material texturestring += mod; - video::ITexture* texture = tsrc->getTextureForMesh(texturestring); + video::ITexture *texture = + tsrc->getTextureForMesh(texturestring); if (!texture) { - errorstream<<"GenericCAO::updateTextures(): Could not load texture "<getMaterial(i); + video::SMaterial &material = + m_animated_meshnode->getMaterial(i); material.MaterialType = m_material_type; material.MaterialTypeParam = 0.5f; material.TextureLayer[0].Texture = texture; material.setFlag(video::EMF_LIGHTING, true); material.setFlag(video::EMF_BILINEAR_FILTER, false); - material.setFlag(video::EMF_BACK_FACE_CULLING, m_prop.backface_culling); + material.setFlag(video::EMF_BACK_FACE_CULLING, + m_prop.backface_culling); // don't filter low-res textures, makes them look blurry // player models have a res of 64 - const core::dimension2d &size = texture->getOriginalSize(); + const core::dimension2d &size = + texture->getOriginalSize(); const u32 res = std::min(size.Height, size.Width); use_trilinear_filter &= res > 64; use_bilinear_filter &= res > 64; - m_animated_meshnode->getMaterial(i) - .setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter); - m_animated_meshnode->getMaterial(i) - .setFlag(video::EMF_BILINEAR_FILTER, use_bilinear_filter); - m_animated_meshnode->getMaterial(i) - .setFlag(video::EMF_ANISOTROPIC_FILTER, use_anisotropic_filter); + m_animated_meshnode->getMaterial(i).setFlag( + video::EMF_TRILINEAR_FILTER, + use_trilinear_filter); + m_animated_meshnode->getMaterial(i).setFlag( + video::EMF_BILINEAR_FILTER, + use_bilinear_filter); + m_animated_meshnode->getMaterial(i).setFlag( + video::EMF_ANISOTROPIC_FILTER, + use_anisotropic_filter); } for (u32 i = 0; i < m_prop.colors.size() && - i < m_animated_meshnode->getMaterialCount(); ++i) - { - // This allows setting per-material colors. However, until a real lighting - // system is added, the code below will have no effect. Once MineTest - // has directional lighting, it should work automatically. - m_animated_meshnode->getMaterial(i).AmbientColor = m_prop.colors[i]; - m_animated_meshnode->getMaterial(i).DiffuseColor = m_prop.colors[i]; - m_animated_meshnode->getMaterial(i).SpecularColor = m_prop.colors[i]; + i < m_animated_meshnode->getMaterialCount(); + ++i) { + // This allows setting per-material colors. However, until + // a real lighting system is added, the code below will + // have no effect. Once MineTest has directional lighting, + // it should work automatically. + m_animated_meshnode->getMaterial(i).AmbientColor = + m_prop.colors[i]; + m_animated_meshnode->getMaterial(i).DiffuseColor = + m_prop.colors[i]; + m_animated_meshnode->getMaterial(i).SpecularColor = + m_prop.colors[i]; } } } - if(m_meshnode) - { - if(m_prop.visual == "cube") - { - for (u32 i = 0; i < 6; ++i) - { + if (m_meshnode) { + if (m_prop.visual == "cube") { + for (u32 i = 0; i < 6; ++i) { std::string texturestring = "unknown_node.png"; - if(m_prop.textures.size() > i) + if (m_prop.textures.size() > i) texturestring = m_prop.textures[i]; texturestring += mod; - // Set material flags and texture - video::SMaterial& material = m_meshnode->getMaterial(i); + video::SMaterial &material = m_meshnode->getMaterial(i); material.MaterialType = m_material_type; material.MaterialTypeParam = 0.5f; material.setFlag(video::EMF_LIGHTING, false); @@ -1324,19 +1364,28 @@ void GenericCAO::updateTextures(std::string mod) tsrc->getTextureForMesh(texturestring)); material.getTextureMatrix(0).makeIdentity(); - // This allows setting per-material colors. However, until a real lighting - // system is added, the code below will have no effect. Once MineTest - // has directional lighting, it should work automatically. - if(m_prop.colors.size() > i) - { - m_meshnode->getMaterial(i).AmbientColor = m_prop.colors[i]; - m_meshnode->getMaterial(i).DiffuseColor = m_prop.colors[i]; - m_meshnode->getMaterial(i).SpecularColor = m_prop.colors[i]; + // This allows setting per-material colors. However, until + // a real lighting system is added, the code below will + // have no effect. Once MineTest has directional lighting, + // it should work automatically. + if (m_prop.colors.size() > i) { + m_meshnode->getMaterial(i).AmbientColor = + m_prop.colors[i]; + m_meshnode->getMaterial(i).DiffuseColor = + m_prop.colors[i]; + m_meshnode->getMaterial(i).SpecularColor = + m_prop.colors[i]; } - m_meshnode->getMaterial(i).setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter); - m_meshnode->getMaterial(i).setFlag(video::EMF_BILINEAR_FILTER, use_bilinear_filter); - m_meshnode->getMaterial(i).setFlag(video::EMF_ANISOTROPIC_FILTER, use_anisotropic_filter); + m_meshnode->getMaterial(i).setFlag( + video::EMF_TRILINEAR_FILTER, + use_trilinear_filter); + m_meshnode->getMaterial(i).setFlag( + video::EMF_BILINEAR_FILTER, + use_bilinear_filter); + m_meshnode->getMaterial(i).setFlag( + video::EMF_ANISOTROPIC_FILTER, + use_anisotropic_filter); } } else if (m_prop.visual == "upright_sprite") { scene::IMesh *mesh = m_meshnode->getMesh(); @@ -1346,21 +1395,28 @@ void GenericCAO::updateTextures(std::string mod) tname = m_prop.textures[0]; tname += mod; scene::IMeshBuffer *buf = mesh->getMeshBuffer(0); - buf->getMaterial().setTexture(0, - tsrc->getTextureForMesh(tname)); + buf->getMaterial().setTexture( + 0, tsrc->getTextureForMesh(tname)); - // This allows setting per-material colors. However, until a real lighting - // system is added, the code below will have no effect. Once MineTest - // has directional lighting, it should work automatically. - if(!m_prop.colors.empty()) { - buf->getMaterial().AmbientColor = m_prop.colors[0]; - buf->getMaterial().DiffuseColor = m_prop.colors[0]; - buf->getMaterial().SpecularColor = m_prop.colors[0]; + // This allows setting per-material colors. However, until + // a real lighting system is added, the code below will + // have no effect. Once MineTest has directional lighting, + // it should work automatically. + if (!m_prop.colors.empty()) { + buf->getMaterial().AmbientColor = + m_prop.colors[0]; + buf->getMaterial().DiffuseColor = + m_prop.colors[0]; + buf->getMaterial().SpecularColor = + m_prop.colors[0]; } - buf->getMaterial().setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter); - buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, use_bilinear_filter); - buf->getMaterial().setFlag(video::EMF_ANISOTROPIC_FILTER, use_anisotropic_filter); + buf->getMaterial().setFlag(video::EMF_TRILINEAR_FILTER, + use_trilinear_filter); + buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, + use_bilinear_filter); + buf->getMaterial().setFlag(video::EMF_ANISOTROPIC_FILTER, + use_anisotropic_filter); } { std::string tname = "unknown_object.png"; @@ -1370,25 +1426,35 @@ void GenericCAO::updateTextures(std::string mod) tname = m_prop.textures[0]; tname += mod; scene::IMeshBuffer *buf = mesh->getMeshBuffer(1); - buf->getMaterial().setTexture(0, - tsrc->getTextureForMesh(tname)); + buf->getMaterial().setTexture( + 0, tsrc->getTextureForMesh(tname)); - // This allows setting per-material colors. However, until a real lighting - // system is added, the code below will have no effect. Once MineTest - // has directional lighting, it should work automatically. + // This allows setting per-material colors. However, until + // a real lighting system is added, the code below will + // have no effect. Once MineTest has directional lighting, + // it should work automatically. if (m_prop.colors.size() >= 2) { - buf->getMaterial().AmbientColor = m_prop.colors[1]; - buf->getMaterial().DiffuseColor = m_prop.colors[1]; - buf->getMaterial().SpecularColor = m_prop.colors[1]; + buf->getMaterial().AmbientColor = + m_prop.colors[1]; + buf->getMaterial().DiffuseColor = + m_prop.colors[1]; + buf->getMaterial().SpecularColor = + m_prop.colors[1]; } else if (!m_prop.colors.empty()) { - buf->getMaterial().AmbientColor = m_prop.colors[0]; - buf->getMaterial().DiffuseColor = m_prop.colors[0]; - buf->getMaterial().SpecularColor = m_prop.colors[0]; + buf->getMaterial().AmbientColor = + m_prop.colors[0]; + buf->getMaterial().DiffuseColor = + m_prop.colors[0]; + buf->getMaterial().SpecularColor = + m_prop.colors[0]; } - buf->getMaterial().setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter); - buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, use_bilinear_filter); - buf->getMaterial().setFlag(video::EMF_ANISOTROPIC_FILTER, use_anisotropic_filter); + buf->getMaterial().setFlag(video::EMF_TRILINEAR_FILTER, + use_trilinear_filter); + buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, + use_bilinear_filter); + buf->getMaterial().setFlag(video::EMF_ANISOTROPIC_FILTER, + use_anisotropic_filter); } // Set mesh color (only if lighting is disabled) if (!m_prop.colors.empty() && m_glow < 0) @@ -1403,13 +1469,15 @@ void GenericCAO::updateAnimation() return; if (m_animated_meshnode->getStartFrame() != m_animation_range.X || - m_animated_meshnode->getEndFrame() != m_animation_range.Y) - m_animated_meshnode->setFrameLoop(m_animation_range.X, m_animation_range.Y); + m_animated_meshnode->getEndFrame() != m_animation_range.Y) + m_animated_meshnode->setFrameLoop( + m_animation_range.X, m_animation_range.Y); if (m_animated_meshnode->getAnimationSpeed() != m_animation_speed) m_animated_meshnode->setAnimationSpeed(m_animation_speed); m_animated_meshnode->setTransitionTime(m_animation_blend); // Requires Irrlicht 1.8 or greater -#if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR >= 8) || IRRLICHT_VERSION_MAJOR > 1 +#if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR >= 8) || \ + IRRLICHT_VERSION_MAJOR > 1 if (m_animated_meshnode->getLoopMode() != m_animation_loop) m_animated_meshnode->setLoopMode(m_animation_loop); #endif @@ -1428,10 +1496,13 @@ void GenericCAO::updateBonePosition() if (m_bone_position.empty() || !m_animated_meshnode) return; - m_animated_meshnode->setJointMode(irr::scene::EJUOR_CONTROL); // To write positions to the mesh on render + m_animated_meshnode->setJointMode( + irr::scene::EJUOR_CONTROL); // To write positions to the mesh on + // render for (auto &it : m_bone_position) { std::string bone_name = it.first; - irr::scene::IBoneSceneNode* bone = m_animated_meshnode->getJointNode(bone_name.c_str()); + irr::scene::IBoneSceneNode *bone = + m_animated_meshnode->getJointNode(bone_name.c_str()); if (bone) { bone->setPosition(it.second.X); bone->setRotation(it.second.Y); @@ -1444,7 +1515,8 @@ void GenericCAO::updateBonePosition() if (!bone) continue; - //If bone is manually positioned there is no need to perform the bug check + // 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()) { @@ -1456,9 +1528,10 @@ void GenericCAO::updateBonePosition() 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. + // 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) { @@ -1504,25 +1577,26 @@ void GenericCAO::updateAttachments() v3f old_pos = getPosition(); m_matrixnode->setParent(m_smgr->getRootSceneNode()); - getPosRotMatrix().setTranslation(old_pos - intToFloat(camera_offset, BS)); + getPosRotMatrix().setTranslation( + old_pos - intToFloat(camera_offset, BS)); m_matrixnode->updateAbsolutePosition(); } - } - else // Attach + } else // Attach { parent->updateAttachments(); scene::ISceneNode *parent_node = parent->getSceneNode(); scene::IAnimatedMeshSceneNode *parent_animated_mesh_node = parent->getAnimatedMeshSceneNode(); if (parent_animated_mesh_node && !m_attachment_bone.empty()) { - parent_node = parent_animated_mesh_node->getJointNode(m_attachment_bone.c_str()); + parent_node = parent_animated_mesh_node->getJointNode( + m_attachment_bone.c_str()); } if (m_matrixnode && parent_node) { m_matrixnode->setParent(parent_node); parent_node->updateAbsolutePosition(); getPosRotMatrix().setTranslation(m_attachment_position); - //setPitchYawRoll(getPosRotMatrix(), m_attachment_rotation); + // setPitchYawRoll(getPosRotMatrix(), m_attachment_rotation); // use Irrlicht eulers instead getPosRotMatrix().setRotationDegrees(m_attachment_rotation); m_matrixnode->updateAbsolutePosition(); @@ -1542,23 +1616,20 @@ bool GenericCAO::visualExpiryRequired(const ObjectProperties &new_) const */ bool uses_legacy_texture = new_.wield_item.empty() && - (new_.visual == "wielditem" || new_.visual == "item"); + (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); + 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"< 0) { m_reset_textures_timer = -1; updateTextures(m_previous_texture_modifier); @@ -1670,9 +1744,7 @@ void GenericCAO::processMessage(const std::string &data) bool sneak_glitch = !readU8(is); bool new_move = !readU8(is); - - if(m_is_local_player) - { + if (m_is_local_player) { LocalPlayer *player = m_env->getLocalPlayer(); player->physics_override_speed = override_speed; player->physics_override_jump = override_jump; @@ -1688,31 +1760,33 @@ void GenericCAO::processMessage(const std::string &data) m_animation_range = v2s32((s32)range.X, (s32)range.Y); m_animation_speed = readF32(is); m_animation_blend = readF32(is); - // these are sent inverted so we get true when the server sends nothing + // these are sent inverted so we get true when the server sends + // nothing m_animation_loop = !readU8(is); updateAnimation(); } else { LocalPlayer *player = m_env->getLocalPlayer(); - if(player->last_animation == NO_ANIM) - { + if (player->last_animation == NO_ANIM) { m_animation_range = v2s32((s32)range.X, (s32)range.Y); m_animation_speed = readF32(is); m_animation_blend = readF32(is); - // these are sent inverted so we get true when the server sends nothing + // these are sent inverted so we get true when the server + // sends nothing m_animation_loop = !readU8(is); } // update animation only if local animations present // and received animation is unknown (except idle animation) bool is_known = false; - for (int i = 1;i<4;i++) - { - if(m_animation_range.Y == player->local_animations[i].Y) + for (int i = 1; i < 4; i++) { + if (m_animation_range.Y == player->local_animations[i].Y) is_known = true; } - if(!is_known || - (player->local_animations[1].Y + player->local_animations[2].Y < 1)) - { - updateAnimation(); + if (!is_known || + (player->local_animations[1].Y + + player->local_animations[2] + .Y < + 1)) { + updateAnimation(); } } } else if (cmd == AO_CMD_SET_ANIMATION_SPEED) { @@ -1747,21 +1821,23 @@ void GenericCAO::processMessage(const std::string &data) if (m_is_local_player) m_env->getLocalPlayer()->hp = m_hp; - if (damage > 0) - { - if (m_hp == 0) - { + if (damage > 0) { + if (m_hp == 0) { // TODO: Execute defined fast response // As there is no definition, make a smoke puff - ClientSimpleObject *simple = createSmokePuff( - m_smgr, m_env, m_position, - v2f(m_prop.visual_size.X, m_prop.visual_size.Y) * BS); + ClientSimpleObject *simple = createSmokePuff(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 && !m_prop.damage_texture_modifier.empty()) { + } else if (m_reset_textures_timer < 0 && + !m_prop.damage_texture_modifier.empty()) { m_reset_textures_timer = 0.05; - if(damage >= 2) + if (damage >= 2) m_reset_textures_timer += 0.05 * damage; - updateTextures(m_current_texture_modifier + m_prop.damage_texture_modifier); + updateTextures(m_current_texture_modifier + + m_prop.damage_texture_modifier); } } @@ -1775,8 +1851,7 @@ void GenericCAO::processMessage(const std::string &data) } else if (cmd == AO_CMD_UPDATE_ARMOR_GROUPS) { m_armor_groups.clear(); int armor_groups_size = readU16(is); - for(int i=0; igetToolCapabilities(m_client->idef()); PunchDamageResult result = getPunchDamage( - m_armor_groups, - toolcap, - punchitem, - time_from_last_punch); + m_armor_groups, toolcap, punchitem, time_from_last_punch); - if(result.did_punch && result.damage != 0) - { - if(result.damage < m_hp) - { + if (result.did_punch && result.damage != 0) { + if (result.damage < m_hp) { m_hp -= result.damage; } else { m_hp = 0; // TODO: Execute defined fast response // As there is no definition, make a smoke puff - ClientSimpleObject *simple = createSmokePuff( - m_smgr, m_env, m_position, - v2f(m_prop.visual_size.X, m_prop.visual_size.Y) * BS); + ClientSimpleObject *simple = createSmokePuff(m_smgr, m_env, + m_position, + v2f(m_prop.visual_size.X, m_prop.visual_size.Y) * + BS); m_env->addSimpleObject(simple); } - if (m_reset_textures_timer < 0 && !m_prop.damage_texture_modifier.empty()) { + 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 + m_prop.damage_texture_modifier); + updateTextures(m_current_texture_modifier + + m_prop.damage_texture_modifier); } } @@ -1838,14 +1911,13 @@ bool GenericCAO::directReportPunch(v3f dir, const ItemStack *punchitem, std::string GenericCAO::debugInfoText() { std::ostringstream os(std::ios::binary); - os<<"GenericCAO hp="<first<<"="<second<<", "; + os << "GenericCAO hp=" << m_hp << "\n"; + os << "armor={"; + for (ItemGroupList::const_iterator i = m_armor_groups.begin(); + i != m_armor_groups.end(); ++i) { + os << i->first << "=" << i->second << ", "; } - os<<"}"; + os << "}"; return os.str(); } diff --git a/src/client/content_cao.h b/src/client/content_cao.h index 56ba8e0ec..9ab5d710d 100644 --- a/src/client/content_cao.h +++ b/src/client/content_cao.h @@ -35,8 +35,7 @@ struct Nametag; SmoothTranslator */ -template -struct SmoothTranslator +template struct SmoothTranslator { T val_old; T val_current; @@ -50,7 +49,7 @@ struct SmoothTranslator void init(T current); void update(T new_target, bool is_end_position = false, - float update_interval = -1); + float update_interval = -1); void translate(f32 dtime); }; @@ -77,7 +76,8 @@ private: // scene::ISceneManager *m_smgr = nullptr; Client *m_client = nullptr; - aabb3f m_selection_box = aabb3f(-BS/3.,-BS/3.,-BS/3., BS/3.,BS/3.,BS/3.); + aabb3f m_selection_box = + aabb3f(-BS / 3., -BS / 3., -BS / 3., BS / 3., BS / 3., BS / 3.); scene::IMeshSceneNode *m_meshnode = nullptr; scene::IAnimatedMeshSceneNode *m_animated_meshnode = nullptr; WieldMeshSceneNode *m_wield_meshnode = nullptr; @@ -92,7 +92,7 @@ private: SmoothTranslator pos_translator; SmoothTranslatorWrappedv3f rot_translator; // Spritesheet/animation stuff - v2f m_tx_size = v2f(1,1); + v2f m_tx_size = v2f(1, 1); v2s16 m_tx_basepos; bool m_initial_tx_basepos_set = false; bool m_tx_select_horiz_by_yawpitch = false; @@ -137,19 +137,13 @@ public: ~GenericCAO(); - static ClientActiveObject* create(Client *client, ClientEnvironment *env) + static ClientActiveObject *create(Client *client, ClientEnvironment *env) { return new GenericCAO(client, env); } - inline ActiveObjectType getType() const - { - return ACTIVEOBJECT_TYPE_GENERIC; - } - inline const ItemGroupList &getGroups() const - { - return m_armor_groups; - } + inline ActiveObjectType getType() const { return ACTIVEOBJECT_TYPE_GENERIC; } + inline const ItemGroupList &getGroups() const { return m_armor_groups; } void initialize(const std::string &data); void processInitData(const std::string &data); @@ -162,22 +156,13 @@ public: const v3f getPosition() const; - void setPosition(const v3f &pos) - { - pos_translator.val_current = pos; - } + void setPosition(const v3f &pos) { pos_translator.val_current = pos; } inline const v3f &getRotation() const { return m_rotation; } - inline const v3f getAcceleration() const - { - return m_acceleration; - } + inline const v3f getAcceleration() const { return m_acceleration; } - inline const v3f getVelocity() const - { - return m_velocity; - } + inline const v3f getVelocity() const { return m_velocity; } const bool isImmortal(); @@ -205,38 +190,21 @@ public: return &m_matrixnode->getAbsoluteTransformation(); } - inline f32 getStepHeight() const - { - return m_prop.stepheight; - } + inline f32 getStepHeight() const { return m_prop.stepheight; } - inline bool isLocalPlayer() const - { - return m_is_local_player; - } + inline bool isLocalPlayer() const { return m_is_local_player; } - inline std::string getName() const - { - return m_name; - } + inline std::string getName() const { return m_name; } - inline bool isPlayer() const - { - return m_is_player; - } + inline bool isPlayer() const { return m_is_player; } - inline bool isVisible() const - { - return m_is_visible; - } + inline bool isVisible() const { return m_is_visible; } - inline void setVisible(bool toset) - { - m_is_visible = toset; - } + inline void setVisible(bool toset) { m_is_visible = toset; } void setChildrenVisible(bool toset); - void setAttachment(int parent_id, const std::string &bone, v3f position, v3f rotation); + void setAttachment(int parent_id, const std::string &bone, v3f position, + v3f rotation); void getAttachment(int *parent_id, std::string *bone, v3f *position, v3f *rotation) const; void clearChildAttachments(); @@ -246,17 +214,16 @@ public: ClientActiveObject *getParent() const; int getParentId() const { return m_attachment_parent_id; } const std::unordered_set &getAttachmentChildIds() const - { return m_attachment_child_ids; } + { + return m_attachment_child_ids; + } void updateAttachments(); void removeFromScene(bool permanent); void addToScene(ITextureSource *tsrc); - inline void expireVisuals() - { - m_visuals_expired = true; - } + inline void expireVisuals() { m_visuals_expired = true; } void updateLight(u32 day_night_ratio); @@ -287,20 +254,14 @@ public: void processMessage(const std::string &data); - bool directReportPunch(v3f dir, const ItemStack *punchitem=NULL, - float time_from_last_punch=1000000); + bool directReportPunch(v3f dir, const ItemStack *punchitem = NULL, + float time_from_last_punch = 1000000); std::string debugInfoText(); - std::string infoText() - { - return m_prop.infotext; - } - + std::string infoText() { return m_prop.infotext; } + float m_waiting_for_reattach; - - ObjectProperties *getProperties() - { - return &m_prop; - } + + ObjectProperties *getProperties() { return &m_prop; } }; diff --git a/src/client/content_cso.cpp b/src/client/content_cso.cpp index f9641afbe..f9b02e823 100644 --- a/src/client/content_cso.cpp +++ b/src/client/content_cso.cpp @@ -24,25 +24,26 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "client.h" #include "map.h" -class SmokePuffCSO: public ClientSimpleObject +class SmokePuffCSO : public ClientSimpleObject { float m_age = 0.0f; scene::IBillboardSceneNode *m_spritenode = nullptr; + public: - SmokePuffCSO(scene::ISceneManager *smgr, - ClientEnvironment *env, const v3f &pos, const v2f &size) + SmokePuffCSO(scene::ISceneManager *smgr, ClientEnvironment *env, const v3f &pos, + const v2f &size) { - infostream<<"SmokePuffCSO: constructing"<addBillboardSceneNode( - NULL, v2f(1,1), pos, -1); - m_spritenode->setMaterialTexture(0, - env->getGameDef()->tsrc()->getTextureForMesh("smoke_puff.png")); + infostream << "SmokePuffCSO: constructing" << std::endl; + m_spritenode = smgr->addBillboardSceneNode(NULL, v2f(1, 1), pos, -1); + m_spritenode->setMaterialTexture( + 0, env->getGameDef()->tsrc()->getTextureForMesh( + "smoke_puff.png")); m_spritenode->setMaterialFlag(video::EMF_LIGHTING, false); m_spritenode->setMaterialFlag(video::EMF_BILINEAR_FILTER, false); - //m_spritenode->setMaterialType(video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF); + // m_spritenode->setMaterialType(video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF); m_spritenode->setMaterialType(video::EMT_TRANSPARENT_ALPHA_CHANNEL); m_spritenode->setMaterialFlag(video::EMF_FOG_ENABLE, true); - m_spritenode->setColor(video::SColor(255,0,0,0)); + m_spritenode->setColor(video::SColor(255, 0, 0, 0)); m_spritenode->setVisible(true); m_spritenode->setSize(size); /* Update brightness */ @@ -50,28 +51,27 @@ public: bool pos_ok; MapNode n = env->getMap().getNode(floatToInt(pos, BS), &pos_ok); light = pos_ok ? decode_light(n.getLightBlend(env->getDayNightRatio(), - env->getGameDef()->ndef())) - : 64; - video::SColor color(255,light,light,light); + env->getGameDef()->ndef())) + : 64; + video::SColor color(255, light, light, light); m_spritenode->setColor(color); } virtual ~SmokePuffCSO() { - infostream<<"SmokePuffCSO: destructing"<remove(); } void step(float dtime) { m_age += dtime; - if(m_age > 1.0){ + if (m_age > 1.0) { m_to_be_removed = true; } } }; -ClientSimpleObject* createSmokePuff(scene::ISceneManager *smgr, - ClientEnvironment *env, v3f pos, v2f size) +ClientSimpleObject *createSmokePuff( + scene::ISceneManager *smgr, ClientEnvironment *env, v3f pos, v2f size) { return new SmokePuffCSO(smgr, env, pos, size); } - diff --git a/src/client/content_cso.h b/src/client/content_cso.h index cc9213175..ccdb00610 100644 --- a/src/client/content_cso.h +++ b/src/client/content_cso.h @@ -22,5 +22,5 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irrlichttypes_extrabloated.h" #include "clientsimpleobject.h" -ClientSimpleObject* createSmokePuff(scene::ISceneManager *smgr, - ClientEnvironment *env, v3f pos, v2f size); +ClientSimpleObject *createSmokePuff( + scene::ISceneManager *smgr, ClientEnvironment *env, v3f pos, v2f size); diff --git a/src/client/content_mapblock.cpp b/src/client/content_mapblock.cpp index 3d06584c4..c97963ede 100644 --- a/src/client/content_mapblock.cpp +++ b/src/client/content_mapblock.cpp @@ -45,14 +45,14 @@ with this program; if not, write to the Free Software Foundation, Inc., #define FRAMED_NEIGHBOR_COUNT 18 static const v3s16 light_dirs[8] = { - v3s16(-1, -1, -1), - v3s16(-1, -1, 1), - v3s16(-1, 1, -1), - v3s16(-1, 1, 1), - v3s16( 1, -1, -1), - v3s16( 1, -1, 1), - v3s16( 1, 1, -1), - v3s16( 1, 1, 1), + v3s16(-1, -1, -1), + v3s16(-1, -1, 1), + v3s16(-1, 1, -1), + v3s16(-1, 1, 1), + v3s16(1, -1, -1), + v3s16(1, -1, 1), + v3s16(1, 1, -1), + v3s16(1, 1, 1), }; // Standard index set to make a quad on 4 vertices @@ -62,14 +62,15 @@ const std::string MapblockMeshGenerator::raillike_groupname = "connect_to_railli MapblockMeshGenerator::MapblockMeshGenerator(MeshMakeData *input, MeshCollector *output) { - data = input; + data = input; collector = output; - nodedef = data->m_client->ndef(); + nodedef = data->m_client->ndef(); meshmanip = RenderingEngine::get_scene_manager()->getMeshManipulator(); enable_mesh_cache = g_settings->getBool("enable_mesh_cache") && - !data->m_smooth_lighting; // Mesh cache is not supported with smooth lighting + !data->m_smooth_lighting; // Mesh cache is not supported with + // smooth lighting blockpos_nodes = data->m_blockpos * MAP_BLOCKSIZE; } @@ -120,11 +121,11 @@ void MapblockMeshGenerator::getSpecialTile(int index, TileSpec *tile, bool apply top_layer->material_flags |= MATERIAL_FLAG_CRACK; } -void MapblockMeshGenerator::drawQuad(v3f *coords, const v3s16 &normal, - float vertical_tiling) +void MapblockMeshGenerator::drawQuad( + v3f *coords, const v3s16 &normal, float vertical_tiling) { - const v2f tcoords[4] = {v2f(0.0, 0.0), v2f(1.0, 0.0), - v2f(1.0, vertical_tiling), v2f(0.0, vertical_tiling)}; + const v2f tcoords[4] = {v2f(0.0, 0.0), v2f(1.0, 0.0), v2f(1.0, vertical_tiling), + v2f(0.0, vertical_tiling)}; video::S3DVertex vertices[4]; bool shade_face = !f->light_source && (normal != v3s16(0, 0, 0)); v3f normal2(normal.X, normal.Y, normal.Z); @@ -152,8 +153,8 @@ void MapblockMeshGenerator::drawQuad(v3f *coords, const v3s16 &normal, // should be (2+2)*6=24 values in the list. The order of // the faces in the list is up-down-right-left-back-front // (compatible with ContentFeatures). -void MapblockMeshGenerator::drawCuboid(const aabb3f &box, - TileSpec *tiles, int tilecount, const LightInfo *lights, const f32 *txc) +void MapblockMeshGenerator::drawCuboid(const aabb3f &box, TileSpec *tiles, int tilecount, + const LightInfo *lights, const f32 *txc) { assert(tilecount >= 1 && tilecount <= 6); // pre-condition @@ -176,46 +177,64 @@ void MapblockMeshGenerator::drawCuboid(const aabb3f &box, } video::S3DVertex vertices[24] = { - // top - video::S3DVertex(min.X, max.Y, max.Z, 0, 1, 0, colors[0], txc[0], txc[1]), - video::S3DVertex(max.X, max.Y, max.Z, 0, 1, 0, colors[0], txc[2], txc[1]), - video::S3DVertex(max.X, max.Y, min.Z, 0, 1, 0, colors[0], txc[2], txc[3]), - video::S3DVertex(min.X, max.Y, min.Z, 0, 1, 0, colors[0], txc[0], txc[3]), - // bottom - video::S3DVertex(min.X, min.Y, min.Z, 0, -1, 0, colors[1], txc[4], txc[5]), - video::S3DVertex(max.X, min.Y, min.Z, 0, -1, 0, colors[1], txc[6], txc[5]), - video::S3DVertex(max.X, min.Y, max.Z, 0, -1, 0, colors[1], txc[6], txc[7]), - video::S3DVertex(min.X, min.Y, max.Z, 0, -1, 0, colors[1], txc[4], txc[7]), - // right - video::S3DVertex(max.X, max.Y, min.Z, 1, 0, 0, colors[2], txc[ 8], txc[9]), - video::S3DVertex(max.X, max.Y, max.Z, 1, 0, 0, colors[2], txc[10], txc[9]), - video::S3DVertex(max.X, min.Y, max.Z, 1, 0, 0, colors[2], txc[10], txc[11]), - video::S3DVertex(max.X, min.Y, min.Z, 1, 0, 0, colors[2], txc[ 8], txc[11]), - // left - video::S3DVertex(min.X, max.Y, max.Z, -1, 0, 0, colors[3], txc[12], txc[13]), - video::S3DVertex(min.X, max.Y, min.Z, -1, 0, 0, colors[3], txc[14], txc[13]), - video::S3DVertex(min.X, min.Y, min.Z, -1, 0, 0, colors[3], txc[14], txc[15]), - video::S3DVertex(min.X, min.Y, max.Z, -1, 0, 0, colors[3], txc[12], txc[15]), - // back - video::S3DVertex(max.X, max.Y, max.Z, 0, 0, 1, colors[4], txc[16], txc[17]), - video::S3DVertex(min.X, max.Y, max.Z, 0, 0, 1, colors[4], txc[18], txc[17]), - video::S3DVertex(min.X, min.Y, max.Z, 0, 0, 1, colors[4], txc[18], txc[19]), - video::S3DVertex(max.X, min.Y, max.Z, 0, 0, 1, colors[4], txc[16], txc[19]), - // front - video::S3DVertex(min.X, max.Y, min.Z, 0, 0, -1, colors[5], txc[20], txc[21]), - video::S3DVertex(max.X, max.Y, min.Z, 0, 0, -1, colors[5], txc[22], txc[21]), - video::S3DVertex(max.X, min.Y, min.Z, 0, 0, -1, colors[5], txc[22], txc[23]), - video::S3DVertex(min.X, min.Y, min.Z, 0, 0, -1, colors[5], txc[20], txc[23]), + // top + video::S3DVertex(min.X, max.Y, max.Z, 0, 1, 0, colors[0], txc[0], + txc[1]), + video::S3DVertex(max.X, max.Y, max.Z, 0, 1, 0, colors[0], txc[2], + txc[1]), + video::S3DVertex(max.X, max.Y, min.Z, 0, 1, 0, colors[0], txc[2], + txc[3]), + video::S3DVertex(min.X, max.Y, min.Z, 0, 1, 0, colors[0], txc[0], + txc[3]), + // bottom + video::S3DVertex(min.X, min.Y, min.Z, 0, -1, 0, colors[1], txc[4], + txc[5]), + video::S3DVertex(max.X, min.Y, min.Z, 0, -1, 0, colors[1], txc[6], + txc[5]), + video::S3DVertex(max.X, min.Y, max.Z, 0, -1, 0, colors[1], txc[6], + txc[7]), + video::S3DVertex(min.X, min.Y, max.Z, 0, -1, 0, colors[1], txc[4], + txc[7]), + // right + video::S3DVertex(max.X, max.Y, min.Z, 1, 0, 0, colors[2], txc[8], + txc[9]), + video::S3DVertex(max.X, max.Y, max.Z, 1, 0, 0, colors[2], txc[10], + txc[9]), + video::S3DVertex(max.X, min.Y, max.Z, 1, 0, 0, colors[2], txc[10], + txc[11]), + video::S3DVertex(max.X, min.Y, min.Z, 1, 0, 0, colors[2], txc[8], + txc[11]), + // left + video::S3DVertex(min.X, max.Y, max.Z, -1, 0, 0, colors[3], + txc[12], txc[13]), + video::S3DVertex(min.X, max.Y, min.Z, -1, 0, 0, colors[3], + txc[14], txc[13]), + video::S3DVertex(min.X, min.Y, min.Z, -1, 0, 0, colors[3], + txc[14], txc[15]), + video::S3DVertex(min.X, min.Y, max.Z, -1, 0, 0, colors[3], + txc[12], txc[15]), + // back + video::S3DVertex(max.X, max.Y, max.Z, 0, 0, 1, colors[4], txc[16], + txc[17]), + video::S3DVertex(min.X, max.Y, max.Z, 0, 0, 1, colors[4], txc[18], + txc[17]), + video::S3DVertex(min.X, min.Y, max.Z, 0, 0, 1, colors[4], txc[18], + txc[19]), + video::S3DVertex(max.X, min.Y, max.Z, 0, 0, 1, colors[4], txc[16], + txc[19]), + // front + video::S3DVertex(min.X, max.Y, min.Z, 0, 0, -1, colors[5], + txc[20], txc[21]), + video::S3DVertex(max.X, max.Y, min.Z, 0, 0, -1, colors[5], + txc[22], txc[21]), + video::S3DVertex(max.X, min.Y, min.Z, 0, 0, -1, colors[5], + txc[22], txc[23]), + video::S3DVertex(min.X, min.Y, min.Z, 0, 0, -1, colors[5], + txc[20], txc[23]), }; - static const u8 light_indices[24] = { - 3, 7, 6, 2, - 0, 4, 5, 1, - 6, 7, 5, 4, - 3, 2, 0, 1, - 7, 3, 1, 5, - 2, 6, 4, 0 - }; + static const u8 light_indices[24] = {3, 7, 6, 2, 0, 4, 5, 1, 6, 7, 5, 4, 3, 2, 0, + 1, 7, 3, 1, 5, 2, 6, 4, 0}; for (int face = 0; face < 6; face++) { int tileindex = MYMIN(face, tilecount - 1); @@ -267,8 +286,9 @@ void MapblockMeshGenerator::drawCuboid(const aabb3f &box, for (int j = 0; j < 24; ++j) { video::S3DVertex &vertex = vertices[j]; vertex.Color = encode_light( - lights[light_indices[j]].getPair(MYMAX(0.0f, vertex.Normal.Y)), - f->light_source); + lights[light_indices[j]].getPair( + MYMAX(0.0f, vertex.Normal.Y)), + f->light_source); if (!f->light_source) applyFacesShading(vertex.Color, vertex.Normal); } @@ -287,7 +307,8 @@ void MapblockMeshGenerator::getSmoothLightFrame() for (int k = 0; k < 8; ++k) frame.sunlight[k] = false; for (int k = 0; k < 8; ++k) { - LightPair light(getSmoothLightTransparent(blockpos_nodes + p, light_dirs[k], data)); + LightPair light(getSmoothLightTransparent( + blockpos_nodes + p, light_dirs[k], data)); frame.lightsDay[k] = light.lightDay; frame.lightsNight[k] = light.lightNight; // If there is direct sunlight and no ambient occlusion at some corner, @@ -306,9 +327,12 @@ LightInfo MapblockMeshGenerator::blendLight(const v3f &vertex_pos) // Light levels at (logical) node corners are known. Here, // trilinear interpolation is used to calculate light level // at a given point in the node. - f32 x = core::clamp(vertex_pos.X / BS + 0.5, 0.0 - SMOOTH_LIGHTING_OVERSIZE, 1.0 + SMOOTH_LIGHTING_OVERSIZE); - f32 y = core::clamp(vertex_pos.Y / BS + 0.5, 0.0 - SMOOTH_LIGHTING_OVERSIZE, 1.0 + SMOOTH_LIGHTING_OVERSIZE); - f32 z = core::clamp(vertex_pos.Z / BS + 0.5, 0.0 - SMOOTH_LIGHTING_OVERSIZE, 1.0 + SMOOTH_LIGHTING_OVERSIZE); + f32 x = core::clamp(vertex_pos.X / BS + 0.5, 0.0 - SMOOTH_LIGHTING_OVERSIZE, + 1.0 + SMOOTH_LIGHTING_OVERSIZE); + f32 y = core::clamp(vertex_pos.Y / BS + 0.5, 0.0 - SMOOTH_LIGHTING_OVERSIZE, + 1.0 + SMOOTH_LIGHTING_OVERSIZE); + f32 z = core::clamp(vertex_pos.Z / BS + 0.5, 0.0 - SMOOTH_LIGHTING_OVERSIZE, + 1.0 + SMOOTH_LIGHTING_OVERSIZE); f32 lightDay = 0.0; // daylight f32 lightNight = 0.0; f32 lightBoosted = 0.0; // daylight + direct sunlight, if any @@ -334,11 +358,12 @@ video::SColor MapblockMeshGenerator::blendLightColor(const v3f &vertex_pos) return encode_light(light.getPair(), f->light_source); } -video::SColor MapblockMeshGenerator::blendLightColor(const v3f &vertex_pos, - const v3f &vertex_normal) +video::SColor MapblockMeshGenerator::blendLightColor( + const v3f &vertex_pos, const v3f &vertex_normal) { LightInfo light = blendLight(vertex_pos); - video::SColor color = encode_light(light.getPair(MYMAX(0.0f, vertex_normal.Y)), f->light_source); + video::SColor color = encode_light( + light.getPair(MYMAX(0.0f, vertex_normal.Y)), f->light_source); if (!f->light_source) applyFacesShading(color, vertex_normal); return color; @@ -353,19 +378,19 @@ void MapblockMeshGenerator::generateCuboidTextureCoords(const aabb3f &box, f32 * f32 ty2 = (box.MaxEdge.Y / BS) + 0.5; f32 tz2 = (box.MaxEdge.Z / BS) + 0.5; f32 txc[24] = { - tx1, 1 - tz2, tx2, 1 - tz1, // up - tx1, tz1, tx2, tz2, // down - tz1, 1 - ty2, tz2, 1 - ty1, // right - 1 - tz2, 1 - ty2, 1 - tz1, 1 - ty1, // left - 1 - tx2, 1 - ty2, 1 - tx1, 1 - ty1, // back - tx1, 1 - ty2, tx2, 1 - ty1, // front + tx1, 1 - tz2, tx2, 1 - tz1, // up + tx1, tz1, tx2, tz2, // down + tz1, 1 - ty2, tz2, 1 - ty1, // right + 1 - tz2, 1 - ty2, 1 - tz1, 1 - ty1, // left + 1 - tx2, 1 - ty2, 1 - tx1, 1 - ty1, // back + tx1, 1 - ty2, tx2, 1 - ty1, // front }; for (int i = 0; i != 24; ++i) coords[i] = txc[i]; } -void MapblockMeshGenerator::drawAutoLightedCuboid(aabb3f box, const f32 *txc, - TileSpec *tiles, int tile_count) +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]; @@ -413,12 +438,16 @@ void MapblockMeshGenerator::prepareLiquidNodeDrawing() getSpecialTile(0, &tile_liquid_top); getSpecialTile(1, &tile_liquid); - 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)); + 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 = 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); + 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) { const ContentFeatures &f2 = nodedef->get(nbottom.getContent()); if (f2.solidness > 1) @@ -432,7 +461,8 @@ void MapblockMeshGenerator::prepareLiquidNodeDrawing() // If this liquid emits light and doesn't contain light, draw // it at what it emits, for an increased effect u8 e = decode_light(f->light_source); - light = LightPair(std::max(e, light.lightDay), std::max(e, light.lightNight)); + light = LightPair(std::max(e, light.lightDay), + std::max(e, light.lightNight)); } else if (nodedef->get(ntop).param_type == CPT_LIGHT) { // Otherwise, use the light of the node on top if possible light = LightPair(getInteriorLight(ntop, 0, nodedef)); @@ -447,46 +477,47 @@ void MapblockMeshGenerator::getLiquidNeighborhood() u8 range = rangelim(nodedef->get(c_flowing).liquid_range, 1, 8); for (int w = -1; w <= 1; w++) - for (int u = -1; u <= 1; u++) { - NeighborData &neighbor = liquid_neighbors[w + 1][u + 1]; - v3s16 p2 = p + v3s16(u, 0, w); - MapNode n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2); - neighbor.content = n2.getContent(); - neighbor.level = -0.5 * BS; - neighbor.is_same_liquid = false; - neighbor.top_is_same_liquid = false; + for (int u = -1; u <= 1; u++) { + NeighborData &neighbor = liquid_neighbors[w + 1][u + 1]; + v3s16 p2 = p + v3s16(u, 0, w); + MapNode n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2); + neighbor.content = n2.getContent(); + neighbor.level = -0.5 * BS; + neighbor.is_same_liquid = false; + neighbor.top_is_same_liquid = false; - if (neighbor.content == CONTENT_IGNORE) - continue; + if (neighbor.content == CONTENT_IGNORE) + continue; - if (neighbor.content == c_source) { - neighbor.is_same_liquid = true; - neighbor.level = 0.5 * BS; - } else if (neighbor.content == c_flowing) { - neighbor.is_same_liquid = true; - u8 liquid_level = (n2.param2 & LIQUID_LEVEL_MASK); - if (liquid_level <= LIQUID_LEVEL_MAX + 1 - range) - liquid_level = 0; - else - liquid_level -= (LIQUID_LEVEL_MAX + 1 - range); - neighbor.level = (-0.5 + (liquid_level + 0.5) / range) * BS; + if (neighbor.content == c_source) { + neighbor.is_same_liquid = true; + neighbor.level = 0.5 * BS; + } else if (neighbor.content == c_flowing) { + neighbor.is_same_liquid = true; + u8 liquid_level = (n2.param2 & LIQUID_LEVEL_MASK); + if (liquid_level <= LIQUID_LEVEL_MAX + 1 - range) + liquid_level = 0; + else + liquid_level -= (LIQUID_LEVEL_MAX + 1 - range); + neighbor.level = (-0.5 + (liquid_level + 0.5) / range) * + BS; + } + + // Check node above neighbor. + // NOTE: This doesn't get executed if neighbor + // doesn't exist + p2.Y++; + n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2); + if (n2.getContent() == c_source || n2.getContent() == c_flowing) + neighbor.top_is_same_liquid = true; } - - // Check node above neighbor. - // NOTE: This doesn't get executed if neighbor - // doesn't exist - p2.Y++; - n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2); - if (n2.getContent() == c_source || n2.getContent() == c_flowing) - neighbor.top_is_same_liquid = true; - } } void MapblockMeshGenerator::calculateCornerLevels() { for (int k = 0; k < 2; k++) - for (int i = 0; i < 2; i++) - corner_levels[k][i] = getCornerLevel(i, k); + for (int i = 0; i < 2; i++) + corner_levels[k][i] = getCornerLevel(i, k); } f32 MapblockMeshGenerator::getCornerLevel(int i, int k) @@ -495,59 +526,58 @@ f32 MapblockMeshGenerator::getCornerLevel(int i, int k) int count = 0; int air_count = 0; for (int dk = 0; dk < 2; dk++) - for (int di = 0; di < 2; di++) { - NeighborData &neighbor_data = liquid_neighbors[k + dk][i + di]; - content_t content = neighbor_data.content; + for (int di = 0; di < 2; di++) { + NeighborData &neighbor_data = liquid_neighbors[k + dk][i + di]; + content_t content = neighbor_data.content; - // If top is liquid, draw starting from top of node - if (neighbor_data.top_is_same_liquid) - return 0.5 * BS; + // If top is liquid, draw starting from top of node + if (neighbor_data.top_is_same_liquid) + return 0.5 * BS; - // Source always has the full height - if (content == c_source) - return 0.5 * BS; + // Source always has the full height + if (content == c_source) + return 0.5 * BS; - // Flowing liquid has level information - if (content == c_flowing) { - sum += neighbor_data.level; - count++; - } else if (content == CONTENT_AIR) { - air_count++; - if (air_count >= 2) - return -0.5 * BS + 0.2; + // Flowing liquid has level information + if (content == c_flowing) { + sum += neighbor_data.level; + count++; + } else if (content == CONTENT_AIR) { + air_count++; + if (air_count >= 2) + return -0.5 * BS + 0.2; + } } - } if (count > 0) return sum / count; return 0; } -namespace { - struct LiquidFaceDesc { - v3s16 dir; // XZ - v3s16 p[2]; // XZ only; 1 means +, 0 means - - }; - struct UV { - int u, v; - }; - 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 liquid_base_vertices[4] = { - {0, 1}, - {1, 1}, - {1, 0}, - {0, 0} - }; +namespace +{ +struct LiquidFaceDesc +{ + v3s16 dir; // XZ + v3s16 p[2]; // XZ only; 1 means +, 0 means - +}; +struct UV +{ + int u, v; +}; +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 liquid_base_vertices[4] = {{0, 1}, {1, 1}, {1, 0}, {0, 0}}; } void MapblockMeshGenerator::drawLiquidSides() { for (const auto &face : liquid_base_faces) { - const NeighborData &neighbor = liquid_neighbors[face.dir.Z + 1][face.dir.X + 1]; + 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 // at the top to which it should be connected. Again, unless the face @@ -574,7 +604,9 @@ void MapblockMeshGenerator::drawLiquidSides() pos.X = (base.X - 0.5f) * BS; pos.Z = (base.Z - 0.5f) * BS; if (vertex.v) { - pos.Y = neighbor.is_same_liquid ? corner_levels[base.Z][base.X] : -0.5f * BS; + pos.Y = neighbor.is_same_liquid + ? corner_levels[base.Z][base.X] + : -0.5f * BS; } else if (top_is_same_liquid) { pos.Y = 0.5f * BS; } else { @@ -585,7 +617,8 @@ void MapblockMeshGenerator::drawLiquidSides() if (data->m_smooth_lighting) color = blendLightColor(pos); pos += origin; - vertices[j] = video::S3DVertex(pos.X, pos.Y, pos.Z, 0, 0, 0, color, vertex.u, v); + vertices[j] = video::S3DVertex( + pos.X, pos.Y, pos.Z, 0, 0, 0, color, vertex.u, v); }; collector->append(tile_liquid, vertices, 4, quad_indices, 6); } @@ -599,10 +632,14 @@ void MapblockMeshGenerator::drawLiquidTop() static const int corner_resolve[4][2] = {{0, 1}, {1, 1}, {1, 0}, {0, 0}}; video::S3DVertex vertices[4] = { - video::S3DVertex(-BS / 2, 0, BS / 2, 0, 0, 0, color_liquid_top, 0, 1), - video::S3DVertex( BS / 2, 0, BS / 2, 0, 0, 0, color_liquid_top, 1, 1), - video::S3DVertex( BS / 2, 0, -BS / 2, 0, 0, 0, color_liquid_top, 1, 0), - video::S3DVertex(-BS / 2, 0, -BS / 2, 0, 0, 0, color_liquid_top, 0, 0), + video::S3DVertex(-BS / 2, 0, BS / 2, 0, 0, 0, color_liquid_top, 0, + 1), + video::S3DVertex(BS / 2, 0, BS / 2, 0, 0, 0, color_liquid_top, 1, + 1), + video::S3DVertex(BS / 2, 0, -BS / 2, 0, 0, 0, color_liquid_top, 1, + 0), + video::S3DVertex(-BS / 2, 0, -BS / 2, 0, 0, 0, color_liquid_top, + 0, 0), }; for (int i = 0; i < 4; i++) { @@ -619,10 +656,10 @@ void MapblockMeshGenerator::drawLiquidTop() // Rotate texture to make animation go in flow direction // Positive if liquid moves towards +Z f32 dz = (corner_levels[0][0] + corner_levels[0][1]) - - (corner_levels[1][0] + corner_levels[1][1]); + (corner_levels[1][0] + corner_levels[1][1]); // Positive if liquid moves towards +X f32 dx = (corner_levels[0][0] + corner_levels[1][0]) - - (corner_levels[0][1] + corner_levels[1][1]); + (corner_levels[0][1] + corner_levels[1][1]); f32 tcoord_angle = atan2(dz, dx) * core::RADTODEG; v2f tcoord_center(0.5, 0.5); v2f tcoord_translate(blockpos_nodes.Z + p.Z, blockpos_nodes.X + p.X); @@ -643,10 +680,14 @@ void MapblockMeshGenerator::drawLiquidTop() void MapblockMeshGenerator::drawLiquidBottom() { video::S3DVertex vertices[4] = { - video::S3DVertex(-BS / 2, -BS / 2, -BS / 2, 0, 0, 0, color_liquid_top, 0, 0), - video::S3DVertex( BS / 2, -BS / 2, -BS / 2, 0, 0, 0, color_liquid_top, 1, 0), - video::S3DVertex( BS / 2, -BS / 2, BS / 2, 0, 0, 0, color_liquid_top, 1, 1), - video::S3DVertex(-BS / 2, -BS / 2, BS / 2, 0, 0, 0, color_liquid_top, 0, 1), + video::S3DVertex(-BS / 2, -BS / 2, -BS / 2, 0, 0, 0, + color_liquid_top, 0, 0), + video::S3DVertex(BS / 2, -BS / 2, -BS / 2, 0, 0, 0, + color_liquid_top, 1, 0), + video::S3DVertex(BS / 2, -BS / 2, BS / 2, 0, 0, 0, + color_liquid_top, 1, 1), + video::S3DVertex(-BS / 2, -BS / 2, BS / 2, 0, 0, 0, + color_liquid_top, 0, 1), }; for (int i = 0; i < 4; i++) { @@ -684,26 +725,32 @@ void MapblockMeshGenerator::drawGlasslikeNode() continue; // Face at Z- v3f vertices[4] = { - v3f(-BS / 2, BS / 2, -BS / 2), - v3f( BS / 2, BS / 2, -BS / 2), - v3f( BS / 2, -BS / 2, -BS / 2), - v3f(-BS / 2, -BS / 2, -BS / 2), + v3f(-BS / 2, BS / 2, -BS / 2), + v3f(BS / 2, BS / 2, -BS / 2), + v3f(BS / 2, -BS / 2, -BS / 2), + v3f(-BS / 2, -BS / 2, -BS / 2), }; for (v3f &vertex : vertices) { switch (face) { - case D6D_ZP: - vertex.rotateXZBy(180); break; - case D6D_YP: - vertex.rotateYZBy( 90); break; - case D6D_XP: - vertex.rotateXZBy( 90); break; - case D6D_ZN: - vertex.rotateXZBy( 0); break; - case D6D_YN: - vertex.rotateYZBy(-90); break; - case D6D_XN: - vertex.rotateXZBy(-90); break; + case D6D_ZP: + vertex.rotateXZBy(180); + break; + case D6D_YP: + vertex.rotateYZBy(90); + break; + case D6D_XP: + vertex.rotateXZBy(90); + break; + case D6D_ZN: + vertex.rotateXZBy(0); + break; + case D6D_YN: + vertex.rotateYZBy(-90); + break; + case D6D_XN: + vertex.rotateXZBy(-90); + break; } } drawQuad(vertices, dir); @@ -733,33 +780,34 @@ void MapblockMeshGenerator::drawGlasslikeFramedNode() static const float b = 0.876f * (BS / 2.0f); static const aabb3f frame_edges[FRAMED_EDGE_COUNT] = { - aabb3f( b, b, -a, a, a, a), // y+ - aabb3f(-a, b, -a, -b, a, a), // y+ - aabb3f( b, -a, -a, a, -b, a), // y- - aabb3f(-a, -a, -a, -b, -b, a), // y- - aabb3f( b, -a, b, a, a, a), // x+ - aabb3f( b, -a, -a, a, a, -b), // x+ - aabb3f(-a, -a, b, -b, a, a), // x- - aabb3f(-a, -a, -a, -b, a, -b), // x- - aabb3f(-a, b, b, a, a, a), // z+ - aabb3f(-a, -a, b, a, -b, a), // z+ - aabb3f(-a, -a, -a, a, -b, -b), // z- - aabb3f(-a, b, -a, a, a, -b), // z- + aabb3f(b, b, -a, a, a, a), // y+ + aabb3f(-a, b, -a, -b, a, a), // y+ + aabb3f(b, -a, -a, a, -b, a), // y- + aabb3f(-a, -a, -a, -b, -b, a), // y- + aabb3f(b, -a, b, a, a, a), // x+ + aabb3f(b, -a, -a, a, a, -b), // x+ + aabb3f(-a, -a, b, -b, a, a), // x- + aabb3f(-a, -a, -a, -b, a, -b), // x- + aabb3f(-a, b, b, a, a, a), // z+ + aabb3f(-a, -a, b, a, -b, a), // z+ + aabb3f(-a, -a, -a, a, -b, -b), // z- + aabb3f(-a, b, -a, a, a, -b), // z- }; // tables of neighbour (connect if same type and merge allowed), // checked with g_26dirs // 1 = connect, 0 = face visible - bool nb[FRAMED_NEIGHBOR_COUNT] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; + bool nb[FRAMED_NEIGHBOR_COUNT] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // 1 = check - static const bool check_nb_vertical [FRAMED_NEIGHBOR_COUNT] = - {0,1,0,0,1,0, 0,0,0,0, 0,0,0,0, 0,0,0,0}; - static const bool check_nb_horizontal [FRAMED_NEIGHBOR_COUNT] = - {1,0,1,1,0,1, 0,0,0,0, 1,1,1,1, 0,0,0,0}; - static const bool check_nb_all [FRAMED_NEIGHBOR_COUNT] = - {1,1,1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1}; + static const bool check_nb_vertical[FRAMED_NEIGHBOR_COUNT] = { + 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + static const bool check_nb_horizontal[FRAMED_NEIGHBOR_COUNT] = { + 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0}; + static const bool check_nb_all[FRAMED_NEIGHBOR_COUNT] = { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; const bool *check_nb = check_nb_all; // neighbours checks for frames visibility @@ -783,18 +831,29 @@ void MapblockMeshGenerator::drawGlasslikeFramedNode() // edge visibility static const u8 nb_triplet[FRAMED_EDGE_COUNT][3] = { - {1, 2, 7}, {1, 5, 6}, {4, 2, 15}, {4, 5, 14}, - {2, 0, 11}, {2, 3, 13}, {5, 0, 10}, {5, 3, 12}, - {0, 1, 8}, {0, 4, 16}, {3, 4, 17}, {3, 1, 9}, + {1, 2, 7}, + {1, 5, 6}, + {4, 2, 15}, + {4, 5, 14}, + {2, 0, 11}, + {2, 3, 13}, + {5, 0, 10}, + {5, 3, 12}, + {0, 1, 8}, + {0, 4, 16}, + {3, 4, 17}, + {3, 1, 9}, }; tile = tiles[1]; for (int edge = 0; edge < FRAMED_EDGE_COUNT; edge++) { bool edge_invisible; if (nb[nb_triplet[edge][2]]) - edge_invisible = nb[nb_triplet[edge][0]] & nb[nb_triplet[edge][1]]; + edge_invisible = + nb[nb_triplet[edge][0]] & nb[nb_triplet[edge][1]]; else - edge_invisible = nb[nb_triplet[edge][0]] ^ nb[nb_triplet[edge][1]]; + edge_invisible = + nb[nb_triplet[edge][0]] ^ nb[nb_triplet[edge][1]]; if (edge_invisible) continue; drawAutoLightedCuboid(frame_edges[edge]); @@ -807,26 +866,32 @@ void MapblockMeshGenerator::drawGlasslikeFramedNode() tile = glass_tiles[face]; // Face at Z- v3f vertices[4] = { - v3f(-a, a, -g), - v3f( a, a, -g), - v3f( a, -a, -g), - v3f(-a, -a, -g), + v3f(-a, a, -g), + v3f(a, a, -g), + v3f(a, -a, -g), + v3f(-a, -a, -g), }; for (v3f &vertex : vertices) { switch (face) { - case D6D_ZP: - vertex.rotateXZBy(180); break; - case D6D_YP: - vertex.rotateYZBy( 90); break; - case D6D_XP: - vertex.rotateXZBy( 90); break; - case D6D_ZN: - vertex.rotateXZBy( 0); break; - case D6D_YN: - vertex.rotateYZBy(-90); break; - case D6D_XN: - vertex.rotateXZBy(-90); break; + case D6D_ZP: + vertex.rotateXZBy(180); + break; + case D6D_YP: + vertex.rotateYZBy(90); + break; + case D6D_XP: + vertex.rotateXZBy(90); + break; + case D6D_ZN: + vertex.rotateXZBy(0); + break; + case D6D_YN: + vertex.rotateYZBy(-90); + break; + case D6D_XN: + vertex.rotateXZBy(-90); + break; } } v3s16 dir = g_6dirs[face]; @@ -841,12 +906,9 @@ void MapblockMeshGenerator::drawGlasslikeFramedNode() // convert it to -0.5 .. 0.5 float vlev = (param2 / 63.0f) * 2.0f - 1.0f; getSpecialTile(0, &tile); - drawAutoLightedCuboid(aabb3f(-(nb[5] ? g : b), - -(nb[4] ? g : b), - -(nb[3] ? g : b), - (nb[2] ? g : b), - (nb[1] ? g : b) * vlev, - (nb[0] ? g : b))); + drawAutoLightedCuboid(aabb3f(-(nb[5] ? g : b), -(nb[4] ? g : b), + -(nb[3] ? g : b), (nb[2] ? g : b), (nb[1] ? g : b) * vlev, + (nb[0] ? g : b))); } } @@ -862,44 +924,49 @@ void MapblockMeshGenerator::drawTorchlikeNode() u8 wall = n.getWallMounted(nodedef); u8 tileindex = 0; switch (wall) { - case DWM_YP: tileindex = 1; break; // ceiling - case DWM_YN: tileindex = 0; break; // floor - default: tileindex = 2; // side (or invalid—should we care?) + case DWM_YP: + tileindex = 1; + break; // ceiling + case DWM_YN: + tileindex = 0; + break; // floor + default: + tileindex = 2; // side (or invalid—should we care?) } useTile(tileindex, MATERIAL_FLAG_CRACK_OVERLAY, MATERIAL_FLAG_BACKFACE_CULLING); float size = BS / 2 * f->visual_scale; v3f vertices[4] = { - v3f(-size, size, 0), - v3f( size, size, 0), - v3f( size, -size, 0), - v3f(-size, -size, 0), + v3f(-size, size, 0), + v3f(size, size, 0), + v3f(size, -size, 0), + v3f(-size, -size, 0), }; for (v3f &vertex : vertices) { switch (wall) { - case DWM_YP: - vertex.Y += -size + BS/2; - vertex.rotateXZBy(-45); - break; - case DWM_YN: - vertex.Y += size - BS/2; - vertex.rotateXZBy(45); - break; - case DWM_XP: - vertex.X += -size + BS/2; - break; - case DWM_XN: - vertex.X += -size + BS/2; - vertex.rotateXZBy(180); - break; - case DWM_ZP: - vertex.X += -size + BS/2; - vertex.rotateXZBy(90); - break; - case DWM_ZN: - vertex.X += -size + BS/2; - vertex.rotateXZBy(-90); + case DWM_YP: + vertex.Y += -size + BS / 2; + vertex.rotateXZBy(-45); + break; + case DWM_YN: + vertex.Y += size - BS / 2; + vertex.rotateXZBy(45); + break; + case DWM_XP: + vertex.X += -size + BS / 2; + break; + case DWM_XN: + vertex.X += -size + BS / 2; + vertex.rotateXZBy(180); + break; + case DWM_ZP: + vertex.X += -size + BS / 2; + vertex.rotateXZBy(90); + break; + case DWM_ZN: + vertex.X += -size + BS / 2; + vertex.rotateXZBy(-90); } } drawQuad(vertices); @@ -913,39 +980,45 @@ void MapblockMeshGenerator::drawSignlikeNode() float size = BS / 2 * f->visual_scale; // Wall at X+ of node v3f vertices[4] = { - v3f(BS / 2 - offset, size, size), - v3f(BS / 2 - offset, size, -size), - v3f(BS / 2 - offset, -size, -size), - v3f(BS / 2 - offset, -size, size), + v3f(BS / 2 - offset, size, size), + v3f(BS / 2 - offset, size, -size), + v3f(BS / 2 - offset, -size, -size), + v3f(BS / 2 - offset, -size, size), }; for (v3f &vertex : vertices) { switch (wall) { - case DWM_YP: - vertex.rotateXYBy( 90); break; - case DWM_YN: - vertex.rotateXYBy(-90); break; - case DWM_XP: - vertex.rotateXZBy( 0); break; - case DWM_XN: - vertex.rotateXZBy(180); break; - case DWM_ZP: - vertex.rotateXZBy( 90); break; - case DWM_ZN: - vertex.rotateXZBy(-90); break; + case DWM_YP: + vertex.rotateXYBy(90); + break; + case DWM_YN: + vertex.rotateXYBy(-90); + break; + case DWM_XP: + vertex.rotateXZBy(0); + break; + case DWM_XN: + vertex.rotateXZBy(180); + break; + case DWM_ZP: + vertex.rotateXZBy(90); + break; + case DWM_ZN: + vertex.rotateXZBy(-90); + break; } } drawQuad(vertices); } -void MapblockMeshGenerator::drawPlantlikeQuad(float rotation, float quad_offset, - bool offset_top_only) +void MapblockMeshGenerator::drawPlantlikeQuad( + float rotation, float quad_offset, bool offset_top_only) { v3f vertices[4] = { - v3f(-scale, -BS / 2 + 2.0 * scale * plant_height, 0), - v3f( scale, -BS / 2 + 2.0 * scale * plant_height, 0), - v3f( scale, -BS / 2, 0), - v3f(-scale, -BS / 2, 0), + v3f(-scale, -BS / 2 + 2.0 * scale * plant_height, 0), + v3f(scale, -BS / 2 + 2.0 * scale * plant_height, 0), + v3f(scale, -BS / 2, 0), + v3f(-scale, -BS / 2, 0), }; if (random_offset_Y) { PseudoRandom yrng(face_num++ | p.X << 16 | p.Z << 8 | p.Y << 24); @@ -1016,15 +1089,15 @@ void MapblockMeshGenerator::drawPlantlike() break; case PLANT_STYLE_HASH: - drawPlantlikeQuad( 1, BS / 4); - drawPlantlikeQuad( 91, BS / 4); + drawPlantlikeQuad(1, BS / 4); + drawPlantlikeQuad(91, BS / 4); drawPlantlikeQuad(181, BS / 4); drawPlantlikeQuad(271, BS / 4); break; case PLANT_STYLE_HASH2: - drawPlantlikeQuad( 1, -BS / 2, true); - drawPlantlikeQuad( 91, -BS / 2, true); + drawPlantlikeQuad(1, -BS / 2, true); + drawPlantlikeQuad(91, -BS / 2, true); drawPlantlikeQuad(181, -BS / 2, true); drawPlantlikeQuad(271, -BS / 2, true); break; @@ -1052,14 +1125,14 @@ void MapblockMeshGenerator::drawPlantlikeRootedNode() p.Y--; } -void MapblockMeshGenerator::drawFirelikeQuad(float rotation, float opening_angle, - float offset_h, float offset_v) +void MapblockMeshGenerator::drawFirelikeQuad( + float rotation, float opening_angle, float offset_h, float offset_v) { v3f vertices[4] = { - v3f(-scale, -BS / 2 + scale * 2, 0), - v3f( scale, -BS / 2 + scale * 2, 0), - v3f( scale, -BS / 2, 0), - v3f(-scale, -BS / 2, 0), + v3f(-scale, -BS / 2 + scale * 2, 0), + v3f(scale, -BS / 2 + scale * 2, 0), + v3f(scale, -BS / 2, 0), + v3f(-scale, -BS / 2, 0), }; for (v3f &vertex : vertices) { @@ -1131,19 +1204,37 @@ void MapblockMeshGenerator::drawFencelikeNode() tile_rot.rotation = 1; static const f32 post_rad = BS / 8; - static const f32 bar_rad = BS / 16; - static const f32 bar_len = BS / 2 - post_rad; + static const f32 bar_rad = BS / 16; + static const f32 bar_len = BS / 2 - post_rad; // The post - always present - static const aabb3f post(-post_rad, -BS / 2, -post_rad, - post_rad, BS / 2, post_rad); + static const aabb3f post( + -post_rad, -BS / 2, -post_rad, post_rad, BS / 2, post_rad); static const f32 postuv[24] = { - 0.375, 0.375, 0.625, 0.625, - 0.375, 0.375, 0.625, 0.625, - 0.000, 0.000, 0.250, 1.000, - 0.250, 0.000, 0.500, 1.000, - 0.500, 0.000, 0.750, 1.000, - 0.750, 0.000, 1.000, 1.000, + 0.375, + 0.375, + 0.625, + 0.625, + 0.375, + 0.375, + 0.625, + 0.625, + 0.000, + 0.000, + 0.250, + 1.000, + 0.250, + 0.000, + 0.500, + 1.000, + 0.500, + 0.000, + 0.750, + 1.000, + 0.750, + 0.000, + 1.000, + 1.000, }; tile = tile_rot; drawAutoLightedCuboid(post, postuv); @@ -1156,17 +1247,35 @@ void MapblockMeshGenerator::drawFencelikeNode() MapNode n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2); const ContentFeatures *f2 = &nodedef->get(n2); if (f2->drawtype == NDT_FENCELIKE) { - static const aabb3f bar_x1(BS / 2 - bar_len, BS / 4 - bar_rad, -bar_rad, - BS / 2 + bar_len, BS / 4 + bar_rad, bar_rad); + static const aabb3f bar_x1(BS / 2 - bar_len, BS / 4 - bar_rad, -bar_rad, + BS / 2 + bar_len, BS / 4 + bar_rad, bar_rad); static const aabb3f bar_x2(BS / 2 - bar_len, -BS / 4 - bar_rad, -bar_rad, - BS / 2 + bar_len, -BS / 4 + bar_rad, bar_rad); + BS / 2 + bar_len, -BS / 4 + bar_rad, bar_rad); static const f32 xrailuv[24] = { - 0.000, 0.125, 1.000, 0.250, - 0.000, 0.250, 1.000, 0.375, - 0.375, 0.375, 0.500, 0.500, - 0.625, 0.625, 0.750, 0.750, - 0.000, 0.500, 1.000, 0.625, - 0.000, 0.875, 1.000, 1.000, + 0.000, + 0.125, + 1.000, + 0.250, + 0.000, + 0.250, + 1.000, + 0.375, + 0.375, + 0.375, + 0.500, + 0.500, + 0.625, + 0.625, + 0.750, + 0.750, + 0.000, + 0.500, + 1.000, + 0.625, + 0.000, + 0.875, + 1.000, + 1.000, }; drawAutoLightedCuboid(bar_x1, xrailuv); drawAutoLightedCuboid(bar_x2, xrailuv); @@ -1178,17 +1287,35 @@ void MapblockMeshGenerator::drawFencelikeNode() n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2); f2 = &nodedef->get(n2); if (f2->drawtype == NDT_FENCELIKE) { - static const aabb3f bar_z1(-bar_rad, BS / 4 - bar_rad, BS / 2 - bar_len, - bar_rad, BS / 4 + bar_rad, BS / 2 + bar_len); + static const aabb3f bar_z1(-bar_rad, BS / 4 - bar_rad, BS / 2 - bar_len, + bar_rad, BS / 4 + bar_rad, BS / 2 + bar_len); static const aabb3f bar_z2(-bar_rad, -BS / 4 - bar_rad, BS / 2 - bar_len, - bar_rad, -BS / 4 + bar_rad, BS / 2 + bar_len); + bar_rad, -BS / 4 + bar_rad, BS / 2 + bar_len); static const f32 zrailuv[24] = { - 0.1875, 0.0625, 0.3125, 0.3125, // cannot rotate; stretch - 0.2500, 0.0625, 0.3750, 0.3125, // for wood texture instead - 0.0000, 0.5625, 1.0000, 0.6875, - 0.0000, 0.3750, 1.0000, 0.5000, - 0.3750, 0.3750, 0.5000, 0.5000, - 0.6250, 0.6250, 0.7500, 0.7500, + 0.1875, + 0.0625, + 0.3125, + 0.3125, // cannot rotate; stretch + 0.2500, + 0.0625, + 0.3750, + 0.3125, // for wood texture instead + 0.0000, + 0.5625, + 1.0000, + 0.6875, + 0.0000, + 0.3750, + 1.0000, + 0.5000, + 0.3750, + 0.3750, + 0.5000, + 0.5000, + 0.6250, + 0.6250, + 0.7500, + 0.7500, }; drawAutoLightedCuboid(bar_z1, zrailuv); drawAutoLightedCuboid(bar_z2, zrailuv); @@ -1202,48 +1329,51 @@ bool MapblockMeshGenerator::isSameRail(v3s16 dir) return true; const ContentFeatures &def2 = nodedef->get(node2); return ((def2.drawtype == NDT_RAILLIKE) && - (def2.getGroup(raillike_groupname) == raillike_group)); + (def2.getGroup(raillike_groupname) == raillike_group)); } -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 rail_slope_angle[4] = {0, 180, 90, -90}; +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 rail_slope_angle[4] = {0, 180, 90, -90}; - enum RailTile { - straight, - curved, - junction, - cross, - }; - struct RailDesc { - int tile_index; - int angle; - }; - static const RailDesc rail_kinds[16] = { - // +x -x -z +z - //------------- - {straight, 0}, // . . . . - {straight, 0}, // . . . +Z - {straight, 0}, // . . -Z . - {straight, 0}, // . . -Z +Z - {straight, 90}, // . -X . . - { curved, 180}, // . -X . +Z - { curved, 270}, // . -X -Z . +enum RailTile +{ + straight, + curved, + junction, + cross, +}; +struct RailDesc +{ + int tile_index; + int angle; +}; +static const RailDesc rail_kinds[16] = { + // +x -x -z +z + //------------- + {straight, 0}, // . . . . + {straight, 0}, // . . . +Z + {straight, 0}, // . . -Z . + {straight, 0}, // . . -Z +Z + {straight, 90}, // . -X . . + {curved, 180}, // . -X . +Z + {curved, 270}, // . -X -Z . {junction, 180}, // . -X -Z +Z - {straight, 90}, // +X . . . - { curved, 90}, // +X . . +Z - { curved, 0}, // +X . -Z . - {junction, 0}, // +X . -Z +Z - {straight, 90}, // +X -X . . - {junction, 90}, // +X -X . +Z + {straight, 90}, // +X . . . + {curved, 90}, // +X . . +Z + {curved, 0}, // +X . -Z . + {junction, 0}, // +X . -Z +Z + {straight, 90}, // +X -X . . + {junction, 90}, // +X -X . +Z {junction, 270}, // +X -X -Z . - { cross, 0}, // +X -X -Z +Z - }; + {cross, 0}, // +X -X -Z +Z +}; } void MapblockMeshGenerator::drawRaillikeNode() @@ -1260,8 +1390,7 @@ void MapblockMeshGenerator::drawRaillikeNode() sloped = true; angle = rail_slope_angle[dir]; } - if (rail_above || - isSameRail(rail_direction[dir]) || + if (rail_above || isSameRail(rail_direction[dir]) || isSameRail(rail_direction[dir] + v3s16(0, -1, 0))) code |= 1 << dir; } @@ -1276,13 +1405,13 @@ void MapblockMeshGenerator::drawRaillikeNode() useTile(tile_index, MATERIAL_FLAG_CRACK_OVERLAY, MATERIAL_FLAG_BACKFACE_CULLING); static const float offset = BS / 64; - static const float size = BS / 2; + static const float size = BS / 2; float y2 = sloped ? size : -size; v3f vertices[4] = { - v3f(-size, y2 + offset, size), - v3f( size, y2 + offset, size), - v3f( size, -size + offset, -size), - v3f(-size, -size + offset, -size), + v3f(-size, y2 + offset, size), + v3f(size, y2 + offset, size), + v3f(size, -size + offset, -size), + v3f(-size, -size + offset, -size), }; if (angle) for (v3f &vertex : vertices) @@ -1290,25 +1419,20 @@ void MapblockMeshGenerator::drawRaillikeNode() drawQuad(vertices); } -namespace { - static const v3s16 nodebox_tile_dirs[6] = { - v3s16(0, 1, 0), - v3s16(0, -1, 0), - v3s16(1, 0, 0), - v3s16(-1, 0, 0), - v3s16(0, 0, 1), - v3s16(0, 0, -1) - }; +namespace +{ +static const v3s16 nodebox_tile_dirs[6] = {v3s16(0, 1, 0), v3s16(0, -1, 0), + v3s16(1, 0, 0), v3s16(-1, 0, 0), v3s16(0, 0, 1), v3s16(0, 0, -1)}; - // we have this order for some reason... - static const v3s16 nodebox_connection_dirs[6] = { - v3s16( 0, 1, 0), // top - v3s16( 0, -1, 0), // bottom - v3s16( 0, 0, -1), // front - v3s16(-1, 0, 0), // left - v3s16( 0, 0, 1), // back - v3s16( 1, 0, 0), // right - }; +// we have this order for some reason... +static const v3s16 nodebox_connection_dirs[6] = { + v3s16(0, 1, 0), // top + v3s16(0, -1, 0), // bottom + v3s16(0, 0, -1), // front + v3s16(-1, 0, 0), // left + v3s16(0, 0, 1), // back + v3s16(1, 0, 0), // right +}; } void MapblockMeshGenerator::drawNodeboxNode() @@ -1340,11 +1464,10 @@ void MapblockMeshGenerator::drawNodeboxNode() void MapblockMeshGenerator::drawMeshNode() { u8 facedir = 0; - scene::IMesh* mesh; + scene::IMesh *mesh; bool private_mesh; // as a grab/drop pair is not thread-safe - if (f->param_type_2 == CPT2_FACEDIR || - f->param_type_2 == CPT2_COLORED_FACEDIR) { + if (f->param_type_2 == CPT2_FACEDIR || f->param_type_2 == CPT2_COLORED_FACEDIR) { facedir = n.getFaceDir(nodedef); } else if (f->param_type_2 == CPT2_WALLMOUNTED || f->param_type_2 == CPT2_COLORED_WALLMOUNTED) { @@ -1384,14 +1507,14 @@ void MapblockMeshGenerator::drawMeshNode() vertex.Color = blendLightColor(vertex.Pos, vertex.Normal); vertex.Pos += origin; } - collector->append(tile, vertices, vertex_count, - buf->getIndices(), buf->getIndexCount()); + collector->append(tile, vertices, vertex_count, buf->getIndices(), + buf->getIndexCount()); } else { // Don't modify the mesh, it may not be private here. // Instead, let the collector process colors, etc. - collector->append(tile, vertices, vertex_count, - buf->getIndices(), buf->getIndexCount(), origin, - color, f->light_source); + collector->append(tile, vertices, vertex_count, buf->getIndices(), + buf->getIndexCount(), origin, color, + f->light_source); } } if (private_mesh) @@ -1409,12 +1532,12 @@ void MapblockMeshGenerator::drawNode() { // skip some drawtypes early switch (f->drawtype) { - case NDT_NORMAL: // Drawn by MapBlockMesh - case NDT_AIRLIKE: // Not drawn at all - case NDT_LIQUID: // Drawn by MapBlockMesh - return; - default: - break; + case NDT_NORMAL: // Drawn by MapBlockMesh + case NDT_AIRLIKE: // Not drawn at all + case NDT_LIQUID: // Drawn by MapBlockMesh + return; + default: + break; } origin = intToFloat(p, BS); if (data->m_smooth_lighting) @@ -1422,20 +1545,48 @@ void MapblockMeshGenerator::drawNode() else light = LightPair(getInteriorLight(n, 1, nodedef)); switch (f->drawtype) { - case NDT_FLOWINGLIQUID: drawLiquidNode(); break; - case NDT_GLASSLIKE: drawGlasslikeNode(); break; - case NDT_GLASSLIKE_FRAMED: drawGlasslikeFramedNode(); break; - case NDT_ALLFACES: drawAllfacesNode(); break; - case NDT_TORCHLIKE: drawTorchlikeNode(); break; - case NDT_SIGNLIKE: drawSignlikeNode(); break; - case NDT_PLANTLIKE: drawPlantlikeNode(); break; - case NDT_PLANTLIKE_ROOTED: drawPlantlikeRootedNode(); break; - case NDT_FIRELIKE: drawFirelikeNode(); break; - case NDT_FENCELIKE: drawFencelikeNode(); break; - case NDT_RAILLIKE: drawRaillikeNode(); break; - case NDT_NODEBOX: drawNodeboxNode(); break; - case NDT_MESH: drawMeshNode(); break; - default: errorUnknownDrawtype(); break; + case NDT_FLOWINGLIQUID: + drawLiquidNode(); + break; + case NDT_GLASSLIKE: + drawGlasslikeNode(); + break; + case NDT_GLASSLIKE_FRAMED: + drawGlasslikeFramedNode(); + break; + case NDT_ALLFACES: + drawAllfacesNode(); + break; + case NDT_TORCHLIKE: + drawTorchlikeNode(); + break; + case NDT_SIGNLIKE: + drawSignlikeNode(); + break; + case NDT_PLANTLIKE: + drawPlantlikeNode(); + break; + case NDT_PLANTLIKE_ROOTED: + drawPlantlikeRootedNode(); + break; + case NDT_FIRELIKE: + drawFirelikeNode(); + break; + case NDT_FENCELIKE: + drawFencelikeNode(); + break; + case NDT_RAILLIKE: + drawRaillikeNode(); + break; + case NDT_NODEBOX: + drawNodeboxNode(); + break; + case NDT_MESH: + drawMeshNode(); + break; + default: + errorUnknownDrawtype(); + break; } } @@ -1446,12 +1597,12 @@ void MapblockMeshGenerator::drawNode() void MapblockMeshGenerator::generate() { for (p.Z = 0; p.Z < MAP_BLOCKSIZE; p.Z++) - for (p.Y = 0; p.Y < MAP_BLOCKSIZE; p.Y++) - for (p.X = 0; p.X < MAP_BLOCKSIZE; p.X++) { - n = data->m_vmanip.getNodeNoEx(blockpos_nodes + p); - f = &nodedef->get(n); - drawNode(); - } + for (p.Y = 0; p.Y < MAP_BLOCKSIZE; p.Y++) + for (p.X = 0; p.X < MAP_BLOCKSIZE; p.X++) { + n = data->m_vmanip.getNodeNoEx(blockpos_nodes + p); + f = &nodedef->get(n); + drawNode(); + } } void MapblockMeshGenerator::renderSingle(content_t node) diff --git a/src/client/content_mapblock.h b/src/client/content_mapblock.h index 97947cdbe..73ff11333 100644 --- a/src/client/content_mapblock.h +++ b/src/client/content_mapblock.h @@ -25,7 +25,8 @@ with this program; if not, write to the Free Software Foundation, Inc., struct MeshMakeData; struct MeshCollector; -struct LightPair { +struct LightPair +{ u8 lightDay; u8 lightNight; @@ -33,26 +34,29 @@ struct LightPair { explicit LightPair(u16 value) : lightDay(value & 0xff), lightNight(value >> 8) {} LightPair(u8 valueA, u8 valueB) : lightDay(valueA), lightNight(valueB) {} LightPair(float valueA, float valueB) : - lightDay(core::clamp(core::round32(valueA), 0, 255)), - lightNight(core::clamp(core::round32(valueB), 0, 255)) {} + lightDay(core::clamp(core::round32(valueA), 0, 255)), + lightNight(core::clamp(core::round32(valueB), 0, 255)) + { + } operator u16() const { return lightDay | lightNight << 8; } }; -struct LightInfo { +struct LightInfo +{ float light_day; float light_night; float light_boosted; LightPair getPair(float sunlight_boost = 0.0) const { - return LightPair( - (1 - sunlight_boost) * light_day - + sunlight_boost * light_boosted, - light_night); + return LightPair((1 - sunlight_boost) * light_day + + sunlight_boost * light_boosted, + light_night); } }; -struct LightFrame { +struct LightFrame +{ f32 lightsDay[8]; f32 lightsNight[8]; bool sunlight[8]; @@ -67,10 +71,10 @@ public: const NodeDefManager *nodedef; scene::IMeshManipulator *meshmanip; -// options + // options bool enable_mesh_cache; -// current node + // current node v3s16 blockpos_nodes; v3s16 p; v3f origin; @@ -82,30 +86,30 @@ public: TileSpec tile; float scale; -// lighting + // lighting void getSmoothLightFrame(); LightInfo blendLight(const v3f &vertex_pos); video::SColor blendLightColor(const v3f &vertex_pos); video::SColor blendLightColor(const v3f &vertex_pos, const v3f &vertex_normal); void useTile(int index = 0, u8 set_flags = MATERIAL_FLAG_CRACK_OVERLAY, - u8 reset_flags = 0, bool special = false); + u8 reset_flags = 0, bool special = false); void getTile(int index, TileSpec *tile); void getTile(v3s16 direction, TileSpec *tile); void getSpecialTile(int index, TileSpec *tile, bool apply_crack = false); -// face drawing + // face drawing void drawQuad(v3f *vertices, const v3s16 &normal = v3s16(0, 0, 0), - float vertical_tiling = 1.0); + float vertical_tiling = 1.0); -// cuboid drawing! + // cuboid drawing! void drawCuboid(const aabb3f &box, TileSpec *tiles, int tilecount, - const LightInfo *lights , const f32 *txc); + const LightInfo *lights, const f32 *txc); void generateCuboidTextureCoords(aabb3f const &box, f32 *coords); void drawAutoLightedCuboid(aabb3f box, const f32 *txc = NULL, - TileSpec *tiles = NULL, int tile_count = 0); + TileSpec *tiles = NULL, int tile_count = 0); -// liquid-specific + // liquid-specific bool top_is_same_liquid; bool draw_liquid_bottom; TileSpec tile_liquid; @@ -113,7 +117,8 @@ public: content_t c_flowing; content_t c_source; video::SColor color_liquid_top; - struct NeighborData { + struct NeighborData + { f32 level; content_t content; bool is_same_liquid; @@ -130,13 +135,13 @@ public: void drawLiquidTop(); void drawLiquidBottom(); -// raillike-specific + // raillike-specific // name of the group that enables connecting to raillike nodes of different kind static const std::string raillike_groupname; int raillike_group; bool isSameRail(v3s16 dir); -// plantlike-specific + // plantlike-specific PlantlikeStyle draw_style; v3f offset; int rotate_degree; @@ -145,14 +150,14 @@ public: float plant_height; void drawPlantlikeQuad(float rotation, float quad_offset = 0, - bool offset_top_only = false); + bool offset_top_only = false); void drawPlantlike(); -// firelike-specific - void drawFirelikeQuad(float rotation, float opening_angle, - float offset_h, float offset_v = 0.0); + // firelike-specific + void drawFirelikeQuad(float rotation, float opening_angle, float offset_h, + float offset_v = 0.0); -// drawtypes + // drawtypes void drawLiquidNode(); void drawGlasslikeNode(); void drawGlasslikeFramedNode(); @@ -167,7 +172,7 @@ public: void drawNodeboxNode(); void drawMeshNode(); -// common + // common void errorUnknownDrawtype(); void drawNode(); diff --git a/src/client/filecache.cpp b/src/client/filecache.cpp index 46bbe4059..0c8f06198 100644 --- a/src/client/filecache.cpp +++ b/src/client/filecache.cpp @@ -32,28 +32,28 @@ bool FileCache::loadByPath(const std::string &path, std::ostream &os) { std::ifstream fis(path.c_str(), std::ios_base::binary); - if(!fis.good()){ - verbosestream<<"FileCache: File not found in cache: " - <getSkin() != NULL); // pre-condition readSettings(); if (m_currentMode == FM_Standard) { - m_settings->registerChangedCallback("font_size", font_setting_changed, NULL); - m_settings->registerChangedCallback("font_bold", font_setting_changed, NULL); - m_settings->registerChangedCallback("font_italic", font_setting_changed, NULL); - m_settings->registerChangedCallback("font_path", font_setting_changed, NULL); - m_settings->registerChangedCallback("font_path_bold", font_setting_changed, NULL); - m_settings->registerChangedCallback("font_path_italic", font_setting_changed, NULL); - m_settings->registerChangedCallback("font_path_bolditalic", font_setting_changed, NULL); - m_settings->registerChangedCallback("font_shadow", font_setting_changed, NULL); - m_settings->registerChangedCallback("font_shadow_alpha", font_setting_changed, NULL); - } - else if (m_currentMode == FM_Fallback) { - m_settings->registerChangedCallback("fallback_font_size", font_setting_changed, NULL); - m_settings->registerChangedCallback("fallback_font_path", font_setting_changed, NULL); - m_settings->registerChangedCallback("fallback_font_shadow", font_setting_changed, NULL); - m_settings->registerChangedCallback("fallback_font_shadow_alpha", font_setting_changed, NULL); + m_settings->registerChangedCallback( + "font_size", font_setting_changed, NULL); + m_settings->registerChangedCallback( + "font_bold", font_setting_changed, NULL); + m_settings->registerChangedCallback( + "font_italic", font_setting_changed, NULL); + m_settings->registerChangedCallback( + "font_path", font_setting_changed, NULL); + m_settings->registerChangedCallback( + "font_path_bold", font_setting_changed, NULL); + m_settings->registerChangedCallback( + "font_path_italic", font_setting_changed, NULL); + m_settings->registerChangedCallback( + "font_path_bolditalic", font_setting_changed, NULL); + m_settings->registerChangedCallback( + "font_shadow", font_setting_changed, NULL); + m_settings->registerChangedCallback( + "font_shadow_alpha", font_setting_changed, NULL); + } else if (m_currentMode == FM_Fallback) { + m_settings->registerChangedCallback( + "fallback_font_size", font_setting_changed, NULL); + m_settings->registerChangedCallback( + "fallback_font_path", font_setting_changed, NULL); + m_settings->registerChangedCallback( + "fallback_font_shadow", font_setting_changed, NULL); + m_settings->registerChangedCallback( + "fallback_font_shadow_alpha", font_setting_changed, NULL); } m_settings->registerChangedCallback("mono_font_path", font_setting_changed, NULL); @@ -107,9 +118,9 @@ irr::gui::IGUIFont *FontEngine::getFont(FontSpec spec) spec.mode = m_currentMode; } else if (m_currentMode == FM_Simple) { // Freetype disabled -> Force simple mode - spec.mode = (spec.mode == FM_Mono || - spec.mode == FM_SimpleMono) ? - FM_SimpleMono : FM_Simple; + spec.mode = (spec.mode == FM_Mono || spec.mode == FM_SimpleMono) + ? FM_SimpleMono + : FM_Simple; // Support for those could be added, but who cares? spec.bold = false; spec.italic = false; @@ -164,7 +175,6 @@ unsigned int FontEngine::getTextWidth(const std::wstring &text, const FontSpec & return font->getDimension(text.c_str()).Width; } - /** get line height for a specific font (including empty room between lines) */ unsigned int FontEngine::getLineHeight(const FontSpec &spec) { @@ -176,8 +186,8 @@ unsigned int FontEngine::getLineHeight(const FontSpec &spec) } FATAL_ERROR_IF(font == NULL, "Could not get font"); - return font->getDimension(L"Some unimportant example String").Height - + font->getKerningHeight(); + return font->getDimension(L"Some unimportant example String").Height + + font->getKerningHeight(); } /******************************************************************************/ @@ -207,7 +217,7 @@ void FontEngine::readSettings() if (USE_FREETYPE && g_settings->getBool("freetype")) { m_default_size[FM_Standard] = m_settings->getU16("font_size"); m_default_size[FM_Fallback] = m_settings->getU16("fallback_font_size"); - m_default_size[FM_Mono] = m_settings->getU16("mono_font_size"); + m_default_size[FM_Mono] = m_settings->getU16("mono_font_size"); /*~ DO NOT TRANSLATE THIS LITERALLY! This is a special string. Put either "no" or "yes" @@ -217,8 +227,8 @@ void FontEngine::readSettings() The fallback font is (normally) required for languages with non-Latin script, like Chinese. When in doubt, test your translation. */ - m_currentMode = is_yes(gettext("needs_fallback_font")) ? - FM_Fallback : FM_Standard; + m_currentMode = is_yes(gettext("needs_fallback_font")) ? FM_Fallback + : FM_Standard; m_default_bold = m_settings->getBool("font_bold"); m_default_italic = m_settings->getBool("font_italic"); @@ -227,8 +237,8 @@ void FontEngine::readSettings() m_currentMode = FM_Simple; } - m_default_size[FM_Simple] = m_settings->getU16("font_size"); - m_default_size[FM_SimpleMono] = m_settings->getU16("mono_font_size"); + m_default_size[FM_Simple] = m_settings->getU16("font_size"); + m_default_size[FM_SimpleMono] = m_settings->getU16("mono_font_size"); cleanCache(); updateFontCache(); @@ -243,11 +253,12 @@ void FontEngine::updateSkin() if (font) m_env->getSkin()->setFont(font); else - errorstream << "FontEngine: Default font file: " << - "\n\t\"" << m_settings->get("font_path") << "\"" << - "\n\trequired for current screen configuration was not found" << - " or was invalid file format." << - "\n\tUsing irrlicht default font." << std::endl; + errorstream << "FontEngine: Default font file: " + << "\n\t\"" << m_settings->get("font_path") << "\"" + << "\n\trequired for current screen configuration was not " + "found" + << " or was invalid file format." + << "\n\tUsing irrlicht default font." << std::endl; // If we did fail to create a font our own make irrlicht find a default one font = m_env->getSkin()->getFont(); @@ -274,15 +285,15 @@ gui::IGUIFont *FontEngine::initFont(const FontSpec &spec) std::string setting_prefix = ""; switch (spec.mode) { - case FM_Fallback: - setting_prefix = "fallback_"; - break; - case FM_Mono: - case FM_SimpleMono: - setting_prefix = "mono_"; - break; - default: - break; + case FM_Fallback: + setting_prefix = "fallback_"; + break; + case FM_Mono: + case FM_SimpleMono: + setting_prefix = "mono_"; + break; + default: + break; } std::string setting_suffix = ""; @@ -292,28 +303,26 @@ gui::IGUIFont *FontEngine::initFont(const FontSpec &spec) setting_suffix.append("_italic"); u32 size = std::floor(RenderingEngine::getDisplayDensity() * - m_settings->getFloat("gui_scaling") * spec.size); + m_settings->getFloat("gui_scaling") * spec.size); if (size == 0) { errorstream << "FontEngine: attempt to use font size 0" << std::endl; - errorstream << " display density: " << RenderingEngine::getDisplayDensity() << std::endl; + errorstream << " display density: " + << RenderingEngine::getDisplayDensity() << std::endl; abort(); } - u16 font_shadow = 0; + u16 font_shadow = 0; u16 font_shadow_alpha = 0; g_settings->getU16NoEx(setting_prefix + "font_shadow", font_shadow); - g_settings->getU16NoEx(setting_prefix + "font_shadow_alpha", - font_shadow_alpha); + g_settings->getU16NoEx(setting_prefix + "font_shadow_alpha", font_shadow_alpha); std::string wanted_font_path; wanted_font_path = g_settings->get(setting_prefix + "font_path" + setting_suffix); - std::string fallback_settings[] = { - wanted_font_path, - m_settings->get("fallback_font_path"), - m_settings->getDefault(setting_prefix + "font_path") - }; + std::string fallback_settings[] = {wanted_font_path, + m_settings->get("fallback_font_path"), + m_settings->getDefault(setting_prefix + "font_path")}; #if USE_FREETYPE for (const std::string &font_path : fallback_settings) { @@ -324,18 +333,19 @@ gui::IGUIFont *FontEngine::initFont(const FontSpec &spec) if (font) return font; - errorstream << "FontEngine: Cannot load '" << font_path << - "'. Trying to fall back to another path." << std::endl; + errorstream << "FontEngine: Cannot load '" << font_path + << "'. Trying to fall back to another path." << std::endl; } - // give up errorstream << "minetest can not continue without a valid font. " - "Please correct the 'font_path' setting or install the font " - "file in the proper location" << std::endl; + "Please correct the 'font_path' setting or install the font " + "file in the proper location" + << std::endl; #else errorstream << "FontEngine: Tried to load freetype fonts but Minetest was" - " not compiled with that library." << std::endl; + " not compiled with that library." + << std::endl; #endif abort(); } @@ -355,20 +365,18 @@ gui::IGUIFont *FontEngine::initSimpleFont(const FontSpec &spec) if (ending == ".ttf") { errorstream << "FontEngine: Found font \"" << font_path - << "\" but freetype is not available." << std::endl; + << "\" but freetype is not available." << std::endl; return nullptr; } if (ending == ".xml" || ending == ".png") basename = font_path.substr(0, pos_dot); - u32 size = std::floor( - RenderingEngine::getDisplayDensity() * - m_settings->getFloat("gui_scaling") * - spec.size); + u32 size = std::floor(RenderingEngine::getDisplayDensity() * + m_settings->getFloat("gui_scaling") * spec.size); irr::gui::IGUIFont *font = nullptr; - std::string font_extensions[] = { ".png", ".xml" }; + std::string font_extensions[] = {".png", ".xml"}; // Find nearest matching font scale // Does a "zig-zag motion" (positibe/negative), from 0 to MAX_FONT_SIZE_OFFSET @@ -389,7 +397,8 @@ gui::IGUIFont *FontEngine::initSimpleFont(const FontSpec &spec) font = m_env->getFont(path.str().c_str()); if (font) { - verbosestream << "FontEngine: found font: " << path.str() << std::endl; + verbosestream << "FontEngine: found font: " << path.str() + << std::endl; break; } } @@ -403,7 +412,8 @@ gui::IGUIFont *FontEngine::initSimpleFont(const FontSpec &spec) if (fs::PathExists(font_path)) { font = m_env->getFont(font_path.c_str()); if (font) - verbosestream << "FontEngine: found font: " << font_path << std::endl; + verbosestream << "FontEngine: found font: " << font_path + << std::endl; } } diff --git a/src/client/fontengine.h b/src/client/fontengine.h index 865b2d3ff..a9d7a13c2 100644 --- a/src/client/fontengine.h +++ b/src/client/fontengine.h @@ -29,7 +29,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #define FONT_SIZE_UNSPECIFIED 0xFFFFFFFF -enum FontMode : u8 { +enum FontMode : u8 +{ FM_Standard = 0, FM_Mono, FM_Fallback, @@ -39,18 +40,15 @@ enum FontMode : u8 { FM_Unspecified }; -struct FontSpec { +struct FontSpec +{ FontSpec(unsigned int font_size, FontMode mode, bool bold, bool italic) : - size(font_size), - mode(mode), - bold(bold), - italic(italic) {} - - u16 getHash() + size(font_size), mode(mode), bold(bold), italic(italic) { - return (mode << 2) | (bold << 1) | italic; } + u16 getHash() { return (mode << 2) | (bold << 1) | italic; } + unsigned int size; FontMode mode; bool bold; @@ -60,16 +58,15 @@ struct FontSpec { class FontEngine { public: - - FontEngine(Settings* main_settings, gui::IGUIEnvironment* env); + FontEngine(Settings *main_settings, gui::IGUIEnvironment *env); ~FontEngine(); // Get best possible font specified by FontSpec irr::gui::IGUIFont *getFont(FontSpec spec); - irr::gui::IGUIFont *getFont(unsigned int font_size=FONT_SIZE_UNSPECIFIED, - FontMode mode=FM_Unspecified) + irr::gui::IGUIFont *getFont(unsigned int font_size = FONT_SIZE_UNSPECIFIED, + FontMode mode = FM_Unspecified) { FontSpec spec(font_size, mode, m_default_bold, m_default_italic); return getFont(spec); @@ -79,9 +76,8 @@ public: unsigned int getTextHeight(const FontSpec &spec); /** get text width if a text for a specific font */ - unsigned int getTextHeight( - unsigned int font_size=FONT_SIZE_UNSPECIFIED, - FontMode mode=FM_Unspecified) + unsigned int getTextHeight(unsigned int font_size = FONT_SIZE_UNSPECIFIED, + FontMode mode = FM_Unspecified) { FontSpec spec(font_size, mode, m_default_bold, m_default_italic); return getTextHeight(spec); @@ -90,9 +86,9 @@ public: unsigned int getTextWidth(const std::wstring &text, const FontSpec &spec); /** get text width if a text for a specific font */ - unsigned int getTextWidth(const std::wstring& text, - unsigned int font_size=FONT_SIZE_UNSPECIFIED, - FontMode mode=FM_Unspecified) + unsigned int getTextWidth(const std::wstring &text, + unsigned int font_size = FONT_SIZE_UNSPECIFIED, + FontMode mode = FM_Unspecified) { FontSpec spec(font_size, mode, m_default_bold, m_default_italic); return getTextWidth(text, spec); @@ -103,9 +99,9 @@ public: return getTextWidth(utf8_to_wide(text), spec); } - unsigned int getTextWidth(const std::string& text, - unsigned int font_size=FONT_SIZE_UNSPECIFIED, - FontMode mode=FM_Unspecified) + unsigned int getTextWidth(const std::string &text, + unsigned int font_size = FONT_SIZE_UNSPECIFIED, + FontMode mode = FM_Unspecified) { FontSpec spec(font_size, mode, m_default_bold, m_default_italic); return getTextWidth(utf8_to_wide(text), spec); @@ -114,8 +110,8 @@ public: /** get line height for a specific font (including empty room between lines) */ unsigned int getLineHeight(const FontSpec &spec); - unsigned int getLineHeight(unsigned int font_size=FONT_SIZE_UNSPECIFIED, - FontMode mode=FM_Unspecified) + unsigned int getLineHeight(unsigned int font_size = FONT_SIZE_UNSPECIFIED, + FontMode mode = FM_Unspecified) { FontSpec spec(font_size, mode, m_default_bold, m_default_italic); return getLineHeight(spec); @@ -128,7 +124,7 @@ public: unsigned int getFontSize(FontMode mode); /** initialize font engine */ - void initialize(Settings* main_settings, gui::IGUIEnvironment* env); + void initialize(Settings *main_settings, gui::IGUIEnvironment *env); /** update internal parameters from settings */ void readSettings(); @@ -150,13 +146,13 @@ private: void cleanCache(); /** pointer to settings for registering callbacks or reading config */ - Settings* m_settings = nullptr; + Settings *m_settings = nullptr; /** pointer to irrlicht gui environment */ - gui::IGUIEnvironment* m_env = nullptr; + gui::IGUIEnvironment *m_env = nullptr; /** internal storage for caching fonts of different size */ - std::map m_font_cache[FM_MaxMode << 2]; + std::map m_font_cache[FM_MaxMode << 2]; /** default font size to use */ unsigned int m_default_size[FM_MaxMode]; @@ -172,4 +168,4 @@ private: }; /** interface to access main font engine*/ -extern FontEngine* g_fontengine; +extern FontEngine *g_fontengine; diff --git a/src/client/game.cpp b/src/client/game.cpp index 479484ae9..e56101436 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -27,7 +27,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "client/clientevent.h" #include "client/gameui.h" #include "client/inputhandler.h" -#include "client/tile.h" // For TextureSource +#include "client/tile.h" // For TextureSource #include "client/keys.h" #include "client/joystick_controller.h" #include "clientmap.h" @@ -52,7 +52,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "gui/profilergraph.h" #include "mapblock.h" #include "minimap.h" -#include "nodedef.h" // Needed for determining pointing to nodes +#include "nodedef.h" // Needed for determining pointing to nodes #include "nodemetadata.h" #include "particles.h" #include "porting.h" @@ -73,60 +73,49 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "hud.h" #if USE_SOUND - #include "client/sound_openal.h" +#include "client/sound_openal.h" #else - #include "client/sound.h" +#include "client/sound.h" #endif -Game::Game() : - m_chat_log_buf(g_logger), - m_game_ui(new GameUI()) +Game::Game() : m_chat_log_buf(g_logger), m_game_ui(new GameUI()) { - g_settings->registerChangedCallback("doubletap_jump", - &settingChangedCallback, this); - g_settings->registerChangedCallback("enable_clouds", - &settingChangedCallback, this); - g_settings->registerChangedCallback("doubletap_joysticks", - &settingChangedCallback, this); - g_settings->registerChangedCallback("enable_particles", - &settingChangedCallback, this); - g_settings->registerChangedCallback("enable_fog", - &settingChangedCallback, this); - g_settings->registerChangedCallback("mouse_sensitivity", - &settingChangedCallback, this); - g_settings->registerChangedCallback("joystick_frustum_sensitivity", - &settingChangedCallback, this); - g_settings->registerChangedCallback("repeat_rightclick_time", - &settingChangedCallback, this); - g_settings->registerChangedCallback("noclip", - &settingChangedCallback, this); - g_settings->registerChangedCallback("free_move", - &settingChangedCallback, this); - g_settings->registerChangedCallback("cinematic", - &settingChangedCallback, this); - g_settings->registerChangedCallback("cinematic_camera_smoothing", - &settingChangedCallback, this); - g_settings->registerChangedCallback("camera_smoothing", - &settingChangedCallback, this); - g_settings->registerChangedCallback("freecam", - &freecamChangedCallback, this); - g_settings->registerChangedCallback("xray", - &updateAllMapBlocksCallback, this); - g_settings->registerChangedCallback("xray_nodes", - &updateAllMapBlocksCallback, this); - g_settings->registerChangedCallback("fullbright", - &updateAllMapBlocksCallback, this); - + g_settings->registerChangedCallback( + "doubletap_jump", &settingChangedCallback, this); + g_settings->registerChangedCallback( + "enable_clouds", &settingChangedCallback, this); + g_settings->registerChangedCallback( + "doubletap_joysticks", &settingChangedCallback, this); + g_settings->registerChangedCallback( + "enable_particles", &settingChangedCallback, this); + g_settings->registerChangedCallback("enable_fog", &settingChangedCallback, this); + g_settings->registerChangedCallback( + "mouse_sensitivity", &settingChangedCallback, this); + g_settings->registerChangedCallback( + "joystick_frustum_sensitivity", &settingChangedCallback, this); + g_settings->registerChangedCallback( + "repeat_rightclick_time", &settingChangedCallback, this); + g_settings->registerChangedCallback("noclip", &settingChangedCallback, this); + g_settings->registerChangedCallback("free_move", &settingChangedCallback, this); + g_settings->registerChangedCallback("cinematic", &settingChangedCallback, this); + g_settings->registerChangedCallback( + "cinematic_camera_smoothing", &settingChangedCallback, this); + g_settings->registerChangedCallback( + "camera_smoothing", &settingChangedCallback, this); + g_settings->registerChangedCallback("freecam", &freecamChangedCallback, this); + g_settings->registerChangedCallback("xray", &updateAllMapBlocksCallback, this); + g_settings->registerChangedCallback( + "xray_nodes", &updateAllMapBlocksCallback, this); + g_settings->registerChangedCallback( + "fullbright", &updateAllMapBlocksCallback, this); + readSettings(); #ifdef __ANDROID__ - m_cache_hold_aux1 = false; // This is initialised properly later + m_cache_hold_aux1 = false; // This is initialised properly later #endif - } - - /**************************************************************************** MinetestApp Public ****************************************************************************/ @@ -152,53 +141,44 @@ Game::~Game() extendedResourceCleanup(); - g_settings->deregisterChangedCallback("doubletap_jump", - &settingChangedCallback, this); - g_settings->deregisterChangedCallback("enable_clouds", - &settingChangedCallback, this); - g_settings->deregisterChangedCallback("enable_particles", - &settingChangedCallback, this); - g_settings->deregisterChangedCallback("enable_fog", - &settingChangedCallback, this); - g_settings->deregisterChangedCallback("mouse_sensitivity", - &settingChangedCallback, this); - g_settings->deregisterChangedCallback("repeat_rightclick_time", - &settingChangedCallback, this); - g_settings->deregisterChangedCallback("noclip", - &settingChangedCallback, this); - g_settings->deregisterChangedCallback("free_move", - &settingChangedCallback, this); - g_settings->deregisterChangedCallback("cinematic", - &settingChangedCallback, this); - g_settings->deregisterChangedCallback("cinematic_camera_smoothing", - &settingChangedCallback, this); - g_settings->deregisterChangedCallback("camera_smoothing", - &settingChangedCallback, this); - g_settings->deregisterChangedCallback("freecam", - &freecamChangedCallback, this); - g_settings->deregisterChangedCallback("xray", - &updateAllMapBlocksCallback, this); - g_settings->deregisterChangedCallback("xray_nodes", - &updateAllMapBlocksCallback, this); - g_settings->deregisterChangedCallback("fullbright", - &updateAllMapBlocksCallback, this); + g_settings->deregisterChangedCallback( + "doubletap_jump", &settingChangedCallback, this); + g_settings->deregisterChangedCallback( + "enable_clouds", &settingChangedCallback, this); + g_settings->deregisterChangedCallback( + "enable_particles", &settingChangedCallback, this); + g_settings->deregisterChangedCallback( + "enable_fog", &settingChangedCallback, this); + g_settings->deregisterChangedCallback( + "mouse_sensitivity", &settingChangedCallback, this); + g_settings->deregisterChangedCallback( + "repeat_rightclick_time", &settingChangedCallback, this); + g_settings->deregisterChangedCallback("noclip", &settingChangedCallback, this); + g_settings->deregisterChangedCallback("free_move", &settingChangedCallback, this); + g_settings->deregisterChangedCallback("cinematic", &settingChangedCallback, this); + g_settings->deregisterChangedCallback( + "cinematic_camera_smoothing", &settingChangedCallback, this); + g_settings->deregisterChangedCallback( + "camera_smoothing", &settingChangedCallback, this); + g_settings->deregisterChangedCallback("freecam", &freecamChangedCallback, this); + g_settings->deregisterChangedCallback("xray", &updateAllMapBlocksCallback, this); + g_settings->deregisterChangedCallback( + "xray_nodes", &updateAllMapBlocksCallback, this); + g_settings->deregisterChangedCallback( + "fullbright", &updateAllMapBlocksCallback, this); } -bool Game::startup(bool *kill, - InputHandler *input, - const GameStartData &start_data, - std::string &error_message, - bool *reconnect, - ChatBackend *chat_backend) +bool Game::startup(bool *kill, InputHandler *input, const GameStartData &start_data, + std::string &error_message, bool *reconnect, ChatBackend *chat_backend) { // "cache" - this->device = RenderingEngine::get_raw_device(); - this->kill = kill; - this->error_message = &error_message; + this->device = RenderingEngine::get_raw_device(); + this->kill = kill; + this->error_message = &error_message; this->reconnect_requested = reconnect; - this->input = input; - this->chat_backend = chat_backend; + this->input = input; + this->chat_backend = chat_backend; this->simple_singleplayer_mode = start_data.isSinglePlayer(); input->keycache.populate(); @@ -206,8 +186,8 @@ bool Game::startup(bool *kill, driver = device->getVideoDriver(); smgr = RenderingEngine::get_scene_manager(); - RenderingEngine::get_scene_manager()->getParameters()-> - setAttribute(scene::OBJ_LOADER_IGNORE_MATERIAL_FILES, true); + RenderingEngine::get_scene_manager()->getParameters()->setAttribute( + scene::OBJ_LOADER_IGNORE_MATERIAL_FILES, true); // Reinit runData runData = GameRunData(); @@ -221,8 +201,8 @@ bool Game::startup(bool *kill, g_client_translations->clear(); // address can change if simple_singleplayer_mode - if (!init(start_data.world_spec.path, start_data.address, - start_data.socket_port, start_data.game_spec)) + if (!init(start_data.world_spec.path, start_data.address, start_data.socket_port, + start_data.game_spec)) return false; if (!createClient(start_data)) @@ -233,14 +213,13 @@ bool Game::startup(bool *kill, return true; } - void Game::run() { ProfilerGraph graph; - RunStats stats = { 0 }; - FpsControl draw_times = { 0 }; + RunStats stats = {0}; + FpsControl draw_times = {0}; f32 dtime; // in seconds - + /* Clear the profiler */ Profiler::GraphValues dummyvalues; g_profiler->graphGet(dummyvalues); @@ -250,24 +229,25 @@ void Game::run() set_light_table(g_settings->getFloat("display_gamma")); #ifdef __ANDROID__ - m_cache_hold_aux1 = g_settings->getBool("fast_move") - && client->checkPrivilege("fast"); + m_cache_hold_aux1 = g_settings->getBool("fast_move") && + client->checkPrivilege("fast"); #endif - irr::core::dimension2d previous_screen_size(g_settings->getU16("screen_w"), - g_settings->getU16("screen_h")); + irr::core::dimension2d previous_screen_size( + g_settings->getU16("screen_w"), g_settings->getU16("screen_h")); - while (RenderingEngine::run() - && !(*kill || g_gamecallback->shutdown_requested - || (server && server->isShutdownRequested()))) { + while (RenderingEngine::run() && + !(*kill || g_gamecallback->shutdown_requested || + (server && server->isShutdownRequested()))) { const irr::core::dimension2d ¤t_screen_size = - RenderingEngine::get_video_driver()->getScreenSize(); + RenderingEngine::get_video_driver()->getScreenSize(); // Verify if window size has changed and save it if it's the case // Ensure evaluating settings->getBool after verifying screensize // First condition is cheaper if (previous_screen_size != current_screen_size && - current_screen_size != irr::core::dimension2d(0,0) && + current_screen_size != + irr::core::dimension2d(0, 0) && g_settings->getBool("autosave_screensize")) { g_settings->setU16("screen_w", current_screen_size.Width); g_settings->setU16("screen_h", current_screen_size.Height); @@ -298,30 +278,32 @@ void Game::run() processUserInput(dtime); // Update camera before player movement to avoid camera lag of one frame updateCameraDirection(&cam_view_target, dtime); - cam_view.camera_yaw += (cam_view_target.camera_yaw - - cam_view.camera_yaw) * m_cache_cam_smoothing; - cam_view.camera_pitch += (cam_view_target.camera_pitch - - cam_view.camera_pitch) * m_cache_cam_smoothing; + cam_view.camera_yaw += + (cam_view_target.camera_yaw - cam_view.camera_yaw) * + m_cache_cam_smoothing; + cam_view.camera_pitch += + (cam_view_target.camera_pitch - cam_view.camera_pitch) * + m_cache_cam_smoothing; updatePlayerControl(cam_view); step(&dtime); processClientEvents(&cam_view_target); updateCamera(draw_times.busy_time, dtime); updateSound(dtime); processPlayerInteraction(dtime, m_game_ui->m_flags.show_hud, - m_game_ui->m_flags.show_debug); + m_game_ui->m_flags.show_debug); updateFrame(&graph, &stats, dtime, cam_view); updateProfilerGraphs(&graph); // Update if minimap has been disabled by the server m_game_ui->m_flags.show_minimap &= client->shouldShowMinimap(); - if (m_does_lost_focus_pause_game && !device->isWindowFocused() && !isMenuActive()) { + if (m_does_lost_focus_pause_game && !device->isWindowFocused() && + !isMenuActive()) { showPauseMenu(); } } } - void Game::shutdown() { RenderingEngine::finalize(); @@ -376,17 +358,13 @@ void Game::shutdown() } } - /****************************************************************************/ /**************************************************************************** Startup ****************************************************************************/ /****************************************************************************/ -bool Game::init( - const std::string &map_dir, - const std::string &address, - u16 port, +bool Game::init(const std::string &map_dir, const std::string &address, u16 port, const SubgameSpec &gamespec) { texture_src = createTextureSource(); @@ -401,8 +379,8 @@ bool Game::init( eventmgr = new EventManager(); quicktune = new QuicktuneShortcutter(); - if (!(texture_src && shader_src && itemdef_manager && nodedef_manager - && eventmgr && quicktune)) + if (!(texture_src && shader_src && itemdef_manager && nodedef_manager && + eventmgr && quicktune)) return false; if (!initSound()) @@ -422,7 +400,8 @@ bool Game::initSound() #if USE_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); + sound = createOpenALSoundManager( + g_sound_manager_singleton.get(), &soundfetcher); if (!sound) infostream << "Failed to initialize OpenAL audio" << std::endl; } else @@ -444,8 +423,8 @@ bool Game::initSound() return true; } -bool Game::createSingleplayerServer(const std::string &map_dir, - const SubgameSpec &gamespec, u16 port) +bool Game::createSingleplayerServer( + const std::string &map_dir, const SubgameSpec &gamespec, u16 port) { showOverlayMessage(N_("Creating server..."), 0, 5); @@ -453,7 +432,7 @@ bool Game::createSingleplayerServer(const std::string &map_dir, Address bind_addr(0, 0, 0, 0, port); if (g_settings->getBool("ipv6_server")) { - bind_addr.setAddress((IPv6AddressBytes *) NULL); + bind_addr.setAddress((IPv6AddressBytes *)NULL); } try { @@ -465,14 +444,14 @@ bool Game::createSingleplayerServer(const std::string &map_dir, } if (bind_addr.isIPv6() && !g_settings->getBool("enable_ipv6")) { - *error_message = "Unable to listen on " + - bind_addr.serializeString() + - " because IPv6 is disabled"; + *error_message = "Unable to listen on " + bind_addr.serializeString() + + " because IPv6 is disabled"; errorstream << *error_message << std::endl; return false; } - server = new Server(map_dir, gamespec, simple_singleplayer_mode, bind_addr, false); + server = new Server( + map_dir, gamespec, simple_singleplayer_mode, bind_addr, false); server->start(); return true; @@ -514,8 +493,9 @@ bool Game::createClient(const GameStartData &start_data) return false; } - GameGlobalShaderConstantSetterFactory *scsf = new GameGlobalShaderConstantSetterFactory( - &m_flags.force_fog_off, &runData.fog_range, client); + GameGlobalShaderConstantSetterFactory *scsf = + new GameGlobalShaderConstantSetterFactory(&m_flags.force_fog_off, + &runData.fog_range, client); shader_src->addShaderConstantSetterFactory(scsf); // Update cached textures, meshes and materials @@ -543,7 +523,7 @@ bool Game::createClient(const GameStartData &start_data) */ sky = new Sky(-1, texture_src); scsf->setSky(sky); - skybox = NULL; // This is used/set later on in the main run loop + skybox = NULL; // This is used/set later on in the main run loop if (!sky) { *error_message = "Memory allocation error sky"; @@ -607,15 +587,15 @@ bool Game::initGui() chat_backend->applySettings(); // Chat backend and console - gui_chat_console = new GUIChatConsole(guienv, guienv->getRootGUIElement(), - -1, chat_backend, client, &g_menumgr); + gui_chat_console = new GUIChatConsole(guienv, guienv->getRootGUIElement(), -1, + chat_backend, client, &g_menumgr); if (!gui_chat_console) { *error_message = "Could not allocate memory for chat console"; errorstream << *error_message << std::endl; return false; } - + m_cheat_menu = new CheatMenu(client); if (!m_cheat_menu) { @@ -634,10 +614,10 @@ bool Game::initGui() return true; } -bool Game::connectToServer(const GameStartData &start_data, - bool *connect_ok, bool *connection_aborted) +bool Game::connectToServer(const GameStartData &start_data, bool *connect_ok, + bool *connection_aborted) { - *connect_ok = false; // Let's not be overly optimistic + *connect_ok = false; // Let's not be overly optimistic *connection_aborted = false; bool local_server_mode = false; @@ -649,7 +629,7 @@ bool Game::connectToServer(const GameStartData &start_data, connect_address.Resolve(start_data.address.c_str()); if (connect_address.isZero()) { // i.e. INADDR_ANY, IN6ADDR_ANY - //connect_address.Resolve("localhost"); + // connect_address.Resolve("localhost"); if (connect_address.isIPv6()) { IPv6AddressBytes addr_bytes; addr_bytes.bytes[15] = 1; @@ -667,15 +647,14 @@ bool Game::connectToServer(const GameStartData &start_data, if (connect_address.isIPv6() && !g_settings->getBool("enable_ipv6")) { *error_message = "Unable to connect to " + - connect_address.serializeString() + - " because IPv6 is disabled"; + connect_address.serializeString() + + " because IPv6 is disabled"; errorstream << *error_message << std::endl; return false; } - client = new Client(start_data.name.c_str(), - start_data.password, start_data.address, - *draw_control, texture_src, shader_src, + client = new Client(start_data.name.c_str(), start_data.password, + start_data.address, *draw_control, texture_src, shader_src, itemdef_manager, nodedef_manager, sound, eventmgr, connect_address.isIPv6(), m_game_ui.get()); @@ -688,8 +667,7 @@ bool Game::connectToServer(const GameStartData &start_data, connect_address.print(&infostream); infostream << std::endl; - client->connect(connect_address, - simple_singleplayer_mode || local_server_mode); + client->connect(connect_address, simple_singleplayer_mode || local_server_mode); /* Wait for server to accept connection @@ -698,7 +676,7 @@ bool Game::connectToServer(const GameStartData &start_data, try { input->clear(); - FpsControl fps_control = { 0 }; + FpsControl fps_control = {0}; f32 dtime; f32 wait_time = 0; // in seconds @@ -725,8 +703,8 @@ bool Game::connectToServer(const GameStartData &start_data, break; if (client->accessDenied()) { - *error_message = "Access denied. Reason: " - + client->accessDeniedReason(); + *error_message = "Access denied. Reason: " + + client->accessDeniedReason(); *reconnect_requested = client->reconnectRequested(); errorstream << *error_message << std::endl; break; @@ -741,16 +719,22 @@ bool Game::connectToServer(const GameStartData &start_data, if (client->m_is_registration_confirmation_state) { if (registration_confirmation_shown) { // Keep drawing the GUI - RenderingEngine::draw_menu_scene(guienv, dtime, true); + RenderingEngine::draw_menu_scene( + guienv, dtime, true); } else { registration_confirmation_shown = true; - (new GUIConfirmRegistration(guienv, guienv->getRootGUIElement(), -1, - &g_menumgr, client, start_data.name, start_data.password, - connection_aborted, texture_src))->drop(); + (new GUIConfirmRegistration(guienv, + guienv->getRootGUIElement(), -1, + &g_menumgr, client, + start_data.name, + start_data.password, + connection_aborted, texture_src)) + ->drop(); } } else { wait_time += dtime; - // Only time out if we aren't waiting for the server we started + // Only time out if we aren't waiting for the server we + // started if (!start_data.isSinglePlayer() && wait_time > 10) { *error_message = "Connection timed out."; errorstream << *error_message << std::endl; @@ -758,7 +742,8 @@ bool Game::connectToServer(const GameStartData &start_data, } // Update status - showOverlayMessage(N_("Connecting to server..."), dtime, 20); + showOverlayMessage( + N_("Connecting to server..."), dtime, 20); } } } catch (con::PeerNotFoundException &e) { @@ -774,7 +759,7 @@ bool Game::getServerContent(bool *aborted) { input->clear(); - FpsControl fps_control = { 0 }; + FpsControl fps_control = {0}; f32 dtime; // in seconds fps_control.last_time = RenderingEngine::get_timer_time(); @@ -817,24 +802,26 @@ bool Game::getServerContent(bool *aborted) if (!client->itemdefReceived()) { const wchar_t *text = wgettext("Item definitions..."); progress = 25; - RenderingEngine::draw_load_screen(text, guienv, texture_src, - dtime, progress); + RenderingEngine::draw_load_screen( + text, guienv, texture_src, dtime, progress); delete[] text; } else if (!client->nodedefReceived()) { const wchar_t *text = wgettext("Node definitions..."); progress = 30; - RenderingEngine::draw_load_screen(text, guienv, texture_src, - dtime, progress); + RenderingEngine::draw_load_screen( + text, guienv, texture_src, dtime, progress); delete[] text; } else { std::stringstream message; std::fixed(message); message.precision(0); - message << gettext("Media...") << " " << (client->mediaReceiveProgress()*100) << "%"; + message << gettext("Media...") << " " + << (client->mediaReceiveProgress() * 100) << "%"; message.precision(2); if ((USE_CURL == 0) || - (!g_settings->getBool("enable_remote_media_server"))) { + (!g_settings->getBool( + "enable_remote_media_server"))) { float cur = client->getCurRate(); std::string cur_unit = gettext("KiB/s"); @@ -847,15 +834,14 @@ bool Game::getServerContent(bool *aborted) } progress = 30 + client->mediaReceiveProgress() * 35 + 0.5; - RenderingEngine::draw_load_screen(utf8_to_wide(message.str()), guienv, - texture_src, dtime, progress); + RenderingEngine::draw_load_screen(utf8_to_wide(message.str()), + guienv, texture_src, dtime, progress); } } return true; } - /****************************************************************************/ /**************************************************************************** Run @@ -873,14 +859,12 @@ inline void Game::updateInteractTimers(f32 dtime) runData.time_from_last_punch += dtime; } - /* returns false if game should exit, otherwise true */ inline bool Game::checkConnection() { if (client->accessDenied()) { - *error_message = "Access denied. Reason: " - + client->accessDeniedReason(); + *error_message = "Access denied. Reason: " + client->accessDeniedReason(); *reconnect_requested = client->reconnectRequested(); errorstream << *error_message << std::endl; return false; @@ -889,7 +873,6 @@ inline bool Game::checkConnection() return true; } - /* returns false if game should exit, otherwise true */ inline bool Game::handleCallbacks() @@ -900,20 +883,21 @@ inline bool Game::handleCallbacks() } if (g_gamecallback->changepassword_requested) { - (new GUIPasswordChange(guienv, guiroot, -1, - &g_menumgr, client, texture_src))->drop(); + (new GUIPasswordChange( + guienv, guiroot, -1, &g_menumgr, client, texture_src)) + ->drop(); g_gamecallback->changepassword_requested = false; } if (g_gamecallback->changevolume_requested) { - (new GUIVolumeChange(guienv, guiroot, -1, - &g_menumgr, texture_src))->drop(); + (new GUIVolumeChange(guienv, guiroot, -1, &g_menumgr, texture_src)) + ->drop(); g_gamecallback->changevolume_requested = false; } if (g_gamecallback->keyconfig_requested) { - (new GUIKeyChangeMenu(guienv, guiroot, -1, - &g_menumgr, texture_src))->drop(); + (new GUIKeyChangeMenu(guienv, guiroot, -1, &g_menumgr, texture_src)) + ->drop(); g_gamecallback->keyconfig_requested = false; } @@ -925,7 +909,6 @@ inline bool Game::handleCallbacks() return true; } - void Game::processQueues() { texture_src->processQueue(); @@ -933,12 +916,9 @@ void Game::processQueues() shader_src->processQueue(); } - -void Game::updateProfilers(const RunStats &stats, const FpsControl &draw_times, - f32 dtime) +void Game::updateProfilers(const RunStats &stats, const FpsControl &draw_times, f32 dtime) { - float profiler_print_interval = - g_settings->getFloat("profiler_print_interval"); + float profiler_print_interval = g_settings->getFloat("profiler_print_interval"); bool print_to_log = true; if (profiler_print_interval == 0) { @@ -957,15 +937,14 @@ void Game::updateProfilers(const RunStats &stats, const FpsControl &draw_times, } // Update update graphs - g_profiler->graphAdd("Time non-rendering [ms]", - draw_times.busy_time - stats.drawtime); + g_profiler->graphAdd( + "Time non-rendering [ms]", draw_times.busy_time - stats.drawtime); g_profiler->graphAdd("Sleep [ms]", draw_times.sleep_time); g_profiler->graphAdd("FPS", 1.0f / dtime); } -void Game::updateStats(RunStats *stats, const FpsControl &draw_times, - f32 dtime) +void Game::updateStats(RunStats *stats, const FpsControl &draw_times, f32 dtime) { f32 jitter; @@ -1013,8 +992,6 @@ void Game::updateStats(RunStats *stats, const FpsControl &draw_times, } } - - /**************************************************************************** Input handling ****************************************************************************/ @@ -1022,7 +999,8 @@ void Game::updateStats(RunStats *stats, const FpsControl &draw_times, void Game::processUserInput(f32 dtime) { // Reset input if window not active or some menu is active - if (!device->isWindowActive() || isMenuActive() || guienv->hasFocus(gui_chat_console)) { + if (!device->isWindowActive() || isMenuActive() || + guienv->hasFocus(gui_chat_console)) { input->clear(); #ifdef HAVE_TOUCHSCREENGUI g_touchscreengui->hide(); @@ -1059,7 +1037,6 @@ void Game::processUserInput(f32 dtime) processItemSelection(&runData.new_playeritem); } - void Game::processKeyInput() { if (wasKeyDown(KeyType::DROP)) { @@ -1088,9 +1065,11 @@ void Game::processKeyInput() if (client->modsLoaded()) openConsole(0.2, L"."); else - m_game_ui->showStatusText(wgettext("Client side scripting is disabled")); + m_game_ui->showStatusText( + wgettext("Client side scripting is disabled")); } else if (wasKeyDown(KeyType::CONSOLE)) { - openConsole(core::clamp(g_settings->getFloat("console_height"), 0.1f, 1.0f)); + openConsole(core::clamp( + g_settings->getFloat("console_height"), 0.1f, 1.0f)); } else if (wasKeyDown(KeyType::FREEMOVE)) { toggleFreeMove(); } else if (wasKeyDown(KeyType::JUMP)) { @@ -1133,11 +1112,14 @@ void Game::processKeyInput() } } else if (wasKeyDown(KeyType::INC_VOLUME)) { if (g_settings->getBool("enable_sound")) { - float new_volume = rangelim(g_settings->getFloat("sound_volume") + 0.1f, 0.0f, 1.0f); + 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)); + swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, + myround(new_volume * 100)); delete[] str; m_game_ui->showStatusText(buf); } else { @@ -1145,20 +1127,24 @@ void Game::processKeyInput() } } else if (wasKeyDown(KeyType::DEC_VOLUME)) { if (g_settings->getBool("enable_sound")) { - float new_volume = rangelim(g_settings->getFloat("sound_volume") - 0.1f, 0.0f, 1.0f); + 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)); + 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"); + } 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(); @@ -1217,8 +1203,7 @@ void Game::processItemSelection(u16 *new_playeritem) *new_playeritem = player->getWieldIndex(); s32 wheel = input->getMouseWheel(); - u16 max_item = MYMIN(PLAYER_INVENTORY_SIZE - 1, - player->hud_hotbar_itemcount - 1); + u16 max_item = MYMIN(PLAYER_INVENTORY_SIZE - 1, player->hud_hotbar_itemcount - 1); s32 dir = wheel; @@ -1241,14 +1226,13 @@ void Game::processItemSelection(u16 *new_playeritem) /* Item selection using hotbar slot keys */ for (u16 i = 0; i <= max_item; i++) { - if (wasKeyDown((GameKeyType) (KeyType::SLOT_1 + i))) { + if (wasKeyDown((GameKeyType)(KeyType::SLOT_1 + i))) { *new_playeritem = i; break; } } } - void Game::dropSelectedItem(bool single_item) { IDropAction *a = new IDropAction(); @@ -1259,7 +1243,6 @@ void Game::dropSelectedItem(bool single_item) client->inventoryAction(a); } - void Game::openInventory() { /* @@ -1278,12 +1261,13 @@ void Game::openInventory() InventoryLocation inventoryloc; inventoryloc.setCurrentPlayer(); - if (!client->modsLoaded() - || !client->getScript()->on_inventory_open(fs_src->m_client->getInventory(inventoryloc))) { + if (!client->modsLoaded() || + !client->getScript()->on_inventory_open( + fs_src->m_client->getInventory(inventoryloc))) { TextDest *txt_dst = new TextDestPlayerInventory(client); auto *&formspec = m_game_ui->updateFormspec(""); GUIFormSpecMenu::create(formspec, client, &input->joystick, fs_src, - txt_dst, client->getFormspecPrepend()); + txt_dst, client->getFormspecPrepend()); formspec->setFormSpec(fs_src->getForm(), inventoryloc); } @@ -1301,7 +1285,6 @@ void Game::openEnderchest() client->getScript()->open_enderchest(); } - void Game::openConsole(float scale, const wchar_t *line) { assert(scale > 0.0f && scale <= 1.0f); @@ -1331,7 +1314,6 @@ void Game::handleAndroidChatInput() } #endif - void Game::toggleFreeMove() { bool free_move = !g_settings->getBool("free_move"); @@ -1341,7 +1323,8 @@ void Game::toggleFreeMove() if (client->checkPrivilege("fly")) { m_game_ui->showTranslatedStatusText("Fly mode enabled"); } else { - m_game_ui->showTranslatedStatusText("Fly mode enabled (note: no 'fly' privilege)"); + m_game_ui->showTranslatedStatusText( + "Fly mode enabled (note: no 'fly' privilege)"); } } else { m_game_ui->showTranslatedStatusText("Fly mode disabled"); @@ -1356,7 +1339,6 @@ void Game::toggleFreeMoveAlt() runData.reset_jump_timer = true; } - void Game::togglePitchMove() { bool pitch_move = !g_settings->getBool("pitch_move"); @@ -1369,7 +1351,6 @@ void Game::togglePitchMove() } } - void Game::toggleFast() { bool fast_move = !g_settings->getBool("fast_move"); @@ -1380,7 +1361,8 @@ void Game::toggleFast() if (has_fast_privs) { m_game_ui->showTranslatedStatusText("Fast mode enabled"); } else { - m_game_ui->showTranslatedStatusText("Fast mode enabled (note: no 'fast' privilege)"); + m_game_ui->showTranslatedStatusText( + "Fast mode enabled (note: no 'fast' privilege)"); } } else { m_game_ui->showTranslatedStatusText("Fast mode disabled"); @@ -1391,7 +1373,6 @@ void Game::toggleFast() #endif } - void Game::toggleNoClip() { bool noclip = !g_settings->getBool("noclip"); @@ -1401,7 +1382,8 @@ void Game::toggleNoClip() if (client->checkPrivilege("noclip")) { m_game_ui->showTranslatedStatusText("Noclip mode enabled"); } else { - m_game_ui->showTranslatedStatusText("Noclip mode enabled (note: no 'noclip' privilege)"); + m_game_ui->showTranslatedStatusText("Noclip mode enabled (note: " + "no 'noclip' privilege)"); } } else { m_game_ui->showTranslatedStatusText("Noclip mode disabled"); @@ -1410,7 +1392,7 @@ void Game::toggleNoClip() void Game::toggleKillaura() { - bool killaura = ! g_settings->getBool("killaura"); + bool killaura = !g_settings->getBool("killaura"); g_settings->set("killaura", bool_to_cstr(killaura)); if (killaura) { @@ -1422,7 +1404,7 @@ void Game::toggleKillaura() void Game::toggleFreecam() { - bool freecam = ! g_settings->getBool("freecam"); + bool freecam = !g_settings->getBool("freecam"); g_settings->set("freecam", bool_to_cstr(freecam)); if (freecam) { @@ -1434,7 +1416,7 @@ void Game::toggleFreecam() void Game::toggleScaffold() { - bool scaffold = ! g_settings->getBool("scaffold"); + bool scaffold = !g_settings->getBool("scaffold"); g_settings->set("scaffold", bool_to_cstr(scaffold)); if (scaffold) { @@ -1446,7 +1428,7 @@ void Game::toggleScaffold() void Game::toggleNextItem() { - bool next_item = ! g_settings->getBool("next_item"); + bool next_item = !g_settings->getBool("next_item"); g_settings->set("next_item", bool_to_cstr(next_item)); if (next_item) { @@ -1481,7 +1463,8 @@ void Game::toggleAutoforward() void Game::toggleMinimap(bool shift_pressed) { - if (!mapper || !m_game_ui->m_flags.show_hud || !g_settings->getBool("enable_minimap")) + if (!mapper || !m_game_ui->m_flags.show_hud || + !g_settings->getBool("enable_minimap")) return; if (shift_pressed) { @@ -1502,31 +1485,32 @@ void Game::toggleMinimap(bool shift_pressed) m_game_ui->m_flags.show_minimap = true; switch (mode) { - case MINIMAP_MODE_SURFACEx1: - m_game_ui->showTranslatedStatusText("Minimap in surface mode, Zoom x1"); - break; - case MINIMAP_MODE_SURFACEx2: - m_game_ui->showTranslatedStatusText("Minimap in surface mode, Zoom x2"); - break; - case MINIMAP_MODE_SURFACEx4: - m_game_ui->showTranslatedStatusText("Minimap in surface mode, Zoom x4"); - break; - case MINIMAP_MODE_RADARx1: - m_game_ui->showTranslatedStatusText("Minimap in radar mode, Zoom x1"); - break; - case MINIMAP_MODE_RADARx2: - m_game_ui->showTranslatedStatusText("Minimap in radar mode, Zoom x2"); - break; - case MINIMAP_MODE_RADARx4: - m_game_ui->showTranslatedStatusText("Minimap in radar mode, Zoom x4"); - break; - default: - mode = MINIMAP_MODE_OFF; - m_game_ui->m_flags.show_minimap = false; - if (hud_flags & HUD_FLAG_MINIMAP_VISIBLE) - m_game_ui->showTranslatedStatusText("Minimap hidden"); - else - m_game_ui->showTranslatedStatusText("Minimap currently disabled by game or mod"); + case MINIMAP_MODE_SURFACEx1: + m_game_ui->showTranslatedStatusText("Minimap in surface mode, Zoom x1"); + break; + case MINIMAP_MODE_SURFACEx2: + m_game_ui->showTranslatedStatusText("Minimap in surface mode, Zoom x2"); + break; + case MINIMAP_MODE_SURFACEx4: + m_game_ui->showTranslatedStatusText("Minimap in surface mode, Zoom x4"); + break; + case MINIMAP_MODE_RADARx1: + m_game_ui->showTranslatedStatusText("Minimap in radar mode, Zoom x1"); + break; + case MINIMAP_MODE_RADARx2: + m_game_ui->showTranslatedStatusText("Minimap in radar mode, Zoom x2"); + break; + case MINIMAP_MODE_RADARx4: + m_game_ui->showTranslatedStatusText("Minimap in radar mode, Zoom x4"); + break; + default: + mode = MINIMAP_MODE_OFF; + m_game_ui->m_flags.show_minimap = false; + if (hud_flags & HUD_FLAG_MINIMAP_VISIBLE) + m_game_ui->showTranslatedStatusText("Minimap hidden"); + else + m_game_ui->showTranslatedStatusText( + "Minimap currently disabled by game or mod"); } mapper->setMinimapMode(mode); @@ -1542,7 +1526,6 @@ void Game::toggleFog() m_game_ui->showTranslatedStatusText("Fog enabled"); } - void Game::toggleDebug() { // Initial / 4x toggle: Chat only @@ -1554,7 +1537,8 @@ void Game::toggleDebug() m_game_ui->m_flags.show_profiler_graph = false; draw_control->show_wireframe = false; m_game_ui->showTranslatedStatusText("Debug info shown"); - } else if (!m_game_ui->m_flags.show_profiler_graph && !draw_control->show_wireframe) { + } else if (!m_game_ui->m_flags.show_profiler_graph && + !draw_control->show_wireframe) { m_game_ui->m_flags.show_profiler_graph = true; m_game_ui->showTranslatedStatusText("Profiler graph shown"); } else if (!draw_control->show_wireframe && client->checkPrivilege("debug")) { @@ -1566,14 +1550,15 @@ void Game::toggleDebug() m_game_ui->m_flags.show_profiler_graph = false; draw_control->show_wireframe = false; if (client->checkPrivilege("debug")) { - m_game_ui->showTranslatedStatusText("Debug info, profiler graph, and wireframe hidden"); + m_game_ui->showTranslatedStatusText("Debug info, profiler graph, " + "and wireframe hidden"); } else { - m_game_ui->showTranslatedStatusText("Debug info and profiler graph hidden"); + m_game_ui->showTranslatedStatusText( + "Debug info and profiler graph hidden"); } } } - void Game::toggleUpdateCamera() { if (g_settings->getBool("freecam")) @@ -1585,7 +1570,6 @@ void Game::toggleUpdateCamera() m_game_ui->showTranslatedStatusText("Camera update enabled"); } - void Game::increaseViewRange() { s16 range = g_settings->getS16("viewing_range"); @@ -1609,7 +1593,6 @@ void Game::increaseViewRange() g_settings->set("viewing_range", itos(range_new)); } - void Game::decreaseViewRange() { s16 range = g_settings->getS16("viewing_range"); @@ -1632,7 +1615,6 @@ void Game::decreaseViewRange() g_settings->set("viewing_range", itos(range_new)); } - void Game::toggleFullViewRange() { draw_control->range_all = !draw_control->range_all; @@ -1642,19 +1624,18 @@ void Game::toggleFullViewRange() m_game_ui->showTranslatedStatusText("Disabled unlimited viewing range"); } - void Game::checkZoomEnabled() { LocalPlayer *player = client->getEnv().getLocalPlayer(); if (player->getZoomFOV() < 0.001f || player->getFov().fov > 0.0f) - m_game_ui->showTranslatedStatusText("Zoom currently disabled by game or mod"); + m_game_ui->showTranslatedStatusText( + "Zoom currently disabled by game or mod"); } - void Game::updateCameraDirection(CameraOrientation *cam, float dtime) { - if ((device->isWindowActive() && device->isWindowFocused() - && !isMenuActive()) || input->isRandom()) { + if ((device->isWindowActive() && device->isWindowFocused() && !isMenuActive()) || + input->isRandom()) { #ifndef __ANDROID__ if (!input->isRandom()) { @@ -1668,7 +1649,7 @@ void Game::updateCameraDirection(CameraOrientation *cam, float dtime) m_first_loop_after_window_activation = false; input->setMousePos(driver->getScreenSize().Width / 2, - driver->getScreenSize().Height / 2); + driver->getScreenSize().Height / 2); } else { updateCameraOrientation(cam, dtime); } @@ -1682,7 +1663,6 @@ void Game::updateCameraDirection(CameraOrientation *cam, float dtime) #endif m_first_loop_after_window_activation = true; - } } @@ -1690,18 +1670,20 @@ void Game::updateCameraOrientation(CameraOrientation *cam, float dtime) { #ifdef HAVE_TOUCHSCREENGUI if (g_touchscreengui) { - cam->camera_yaw += g_touchscreengui->getYawChange(); - cam->camera_pitch = g_touchscreengui->getPitch(); + cam->camera_yaw += g_touchscreengui->getYawChange(); + cam->camera_pitch = g_touchscreengui->getPitch(); } else { #endif - v2s32 center(driver->getScreenSize().Width / 2, driver->getScreenSize().Height / 2); + v2s32 center(driver->getScreenSize().Width / 2, + driver->getScreenSize().Height / 2); v2s32 dist = input->getMousePos() - center; - if (m_invert_mouse || camera->getCameraMode() == CAMERA_MODE_THIRD_FRONT) { + if (m_invert_mouse || + camera->getCameraMode() == CAMERA_MODE_THIRD_FRONT) { dist.Y = -dist.Y; } - cam->camera_yaw -= dist.X * m_cache_mouse_sensitivity; + cam->camera_yaw -= dist.X * m_cache_mouse_sensitivity; cam->camera_pitch += dist.Y * m_cache_mouse_sensitivity; if (dist.X != 0 || dist.Y != 0) @@ -1712,51 +1694,45 @@ void Game::updateCameraOrientation(CameraOrientation *cam, float dtime) if (m_cache_enable_joysticks) { f32 c = m_cache_joystick_frustum_sensitivity * (1.f / 32767.f) * dtime; - cam->camera_yaw -= input->joystick.getAxisWithoutDead(JA_FRUSTUM_HORIZONTAL) * c; - cam->camera_pitch += input->joystick.getAxisWithoutDead(JA_FRUSTUM_VERTICAL) * c; + cam->camera_yaw -= input->joystick.getAxisWithoutDead( + JA_FRUSTUM_HORIZONTAL) * + c; + cam->camera_pitch += + input->joystick.getAxisWithoutDead(JA_FRUSTUM_VERTICAL) * + c; } cam->camera_pitch = rangelim(cam->camera_pitch, -89.5, 89.5); } - void Game::updatePlayerControl(const CameraOrientation &cam) { - //TimeTaker tt("update player control", NULL, PRECISION_NANO); + // TimeTaker tt("update player control", NULL, PRECISION_NANO); // DO NOT use the isKeyDown method for the forward, backward, left, right // buttons, as the code that uses the controls needs to be able to // distinguish between the two in order to know when to use joysticks. - PlayerControl control( - input->isKeyDown(KeyType::FORWARD), - input->isKeyDown(KeyType::BACKWARD), - input->isKeyDown(KeyType::LEFT), - input->isKeyDown(KeyType::RIGHT), - isKeyDown(KeyType::JUMP), - isKeyDown(KeyType::SPECIAL1), - isKeyDown(KeyType::SNEAK), - isKeyDown(KeyType::ZOOM), - input->getLeftState(), - input->getRightState(), - cam.camera_pitch, - cam.camera_yaw, - input->joystick.getAxisWithoutDead(JA_SIDEWARD_MOVE), - input->joystick.getAxisWithoutDead(JA_FORWARD_MOVE) - ); + PlayerControl control(input->isKeyDown(KeyType::FORWARD), + input->isKeyDown(KeyType::BACKWARD), + input->isKeyDown(KeyType::LEFT), input->isKeyDown(KeyType::RIGHT), + isKeyDown(KeyType::JUMP), isKeyDown(KeyType::SPECIAL1), + isKeyDown(KeyType::SNEAK), isKeyDown(KeyType::ZOOM), + input->getLeftState(), input->getRightState(), cam.camera_pitch, + cam.camera_yaw, + input->joystick.getAxisWithoutDead(JA_SIDEWARD_MOVE), + input->joystick.getAxisWithoutDead(JA_FORWARD_MOVE)); - u32 keypress_bits = ( - ( (u32)(isKeyDown(KeyType::FORWARD) & 0x1) << 0) | - ( (u32)(isKeyDown(KeyType::BACKWARD) & 0x1) << 1) | - ( (u32)(isKeyDown(KeyType::LEFT) & 0x1) << 2) | - ( (u32)(isKeyDown(KeyType::RIGHT) & 0x1) << 3) | - ( (u32)(isKeyDown(KeyType::JUMP) & 0x1) << 4) | - ( (u32)(isKeyDown(KeyType::SPECIAL1) & 0x1) << 5) | - ( (u32)(isKeyDown(KeyType::SNEAK) & 0x1) << 6) | - ( (u32)(input->getLeftState() & 0x1) << 7) | - ( (u32)(input->getRightState() & 0x1) << 8) | - ( (u32)(isKeyDown(KeyType::ZOOM) & 0x1) << 9) - ); + u32 keypress_bits = (((u32)(isKeyDown(KeyType::FORWARD) & 0x1) << 0) | + ((u32)(isKeyDown(KeyType::BACKWARD) & 0x1) << 1) | + ((u32)(isKeyDown(KeyType::LEFT) & 0x1) << 2) | + ((u32)(isKeyDown(KeyType::RIGHT) & 0x1) << 3) | + ((u32)(isKeyDown(KeyType::JUMP) & 0x1) << 4) | + ((u32)(isKeyDown(KeyType::SPECIAL1) & 0x1) << 5) | + ((u32)(isKeyDown(KeyType::SNEAK) & 0x1) << 6) | + ((u32)(input->getLeftState() & 0x1) << 7) | + ((u32)(input->getRightState() & 0x1) << 8) | + ((u32)(isKeyDown(KeyType::ZOOM) & 0x1) << 9)); #ifdef ANDROID /* For Android, simulate holding down AUX1 (fast move) if the user has @@ -1788,17 +1764,15 @@ void Game::updatePlayerControl(const CameraOrientation &cam) client->setPlayerControl(control); player->keyPressed = keypress_bits; - //tt.stop(); + // tt.stop(); } - inline void Game::step(f32 *dtime) { - bool can_be_and_is_paused = - (simple_singleplayer_mode && g_menumgr.pausesGame()); + bool can_be_and_is_paused = (simple_singleplayer_mode && g_menumgr.pausesGame()); if (can_be_and_is_paused) { // This is for a singleplayer server - *dtime = 0; // No time passes + *dtime = 0; // No time passes } else { if (server) server->step(*dtime); @@ -1808,24 +1782,24 @@ inline void Game::step(f32 *dtime) } const ClientEventHandler Game::clientEventHandler[CLIENTEVENT_MAX] = { - {&Game::handleClientEvent_None}, - {&Game::handleClientEvent_PlayerDamage}, - {&Game::handleClientEvent_PlayerForceMove}, - {&Game::handleClientEvent_Deathscreen}, - {&Game::handleClientEvent_ShowFormSpec}, - {&Game::handleClientEvent_ShowLocalFormSpec}, - {&Game::handleClientEvent_HandleParticleEvent}, - {&Game::handleClientEvent_HandleParticleEvent}, - {&Game::handleClientEvent_HandleParticleEvent}, - {&Game::handleClientEvent_HudAdd}, - {&Game::handleClientEvent_HudRemove}, - {&Game::handleClientEvent_HudChange}, - {&Game::handleClientEvent_SetSky}, - {&Game::handleClientEvent_SetSun}, - {&Game::handleClientEvent_SetMoon}, - {&Game::handleClientEvent_SetStars}, - {&Game::handleClientEvent_OverrideDayNigthRatio}, - {&Game::handleClientEvent_CloudParams}, + {&Game::handleClientEvent_None}, + {&Game::handleClientEvent_PlayerDamage}, + {&Game::handleClientEvent_PlayerForceMove}, + {&Game::handleClientEvent_Deathscreen}, + {&Game::handleClientEvent_ShowFormSpec}, + {&Game::handleClientEvent_ShowLocalFormSpec}, + {&Game::handleClientEvent_HandleParticleEvent}, + {&Game::handleClientEvent_HandleParticleEvent}, + {&Game::handleClientEvent_HandleParticleEvent}, + {&Game::handleClientEvent_HudAdd}, + {&Game::handleClientEvent_HudRemove}, + {&Game::handleClientEvent_HudChange}, + {&Game::handleClientEvent_SetSky}, + {&Game::handleClientEvent_SetSun}, + {&Game::handleClientEvent_SetMoon}, + {&Game::handleClientEvent_SetStars}, + {&Game::handleClientEvent_OverrideDayNigthRatio}, + {&Game::handleClientEvent_CloudParams}, }; void Game::handleClientEvent_None(ClientEvent *event, CameraOrientation *cam) @@ -1847,7 +1821,7 @@ void Game::handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation player->hurt_tilt_timer = 1.5f; player->hurt_tilt_strength = - rangelim(event->player_damage.amount / 4.0f, 1.0f, 4.0f); + rangelim(event->player_damage.amount / 4.0f, 1.0f, 4.0f); } // Play damage sound @@ -1880,19 +1854,21 @@ void Game::handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation { if (event->show_formspec.formspec->empty()) { auto formspec = m_game_ui->getFormspecGUI(); - if (formspec && (event->show_formspec.formname->empty() - || *(event->show_formspec.formname) == m_game_ui->getFormspecName())) { + if (formspec && (event->show_formspec.formname->empty() || + *(event->show_formspec.formname) == + m_game_ui->getFormspecName())) { formspec->quitMenu(); } } else { FormspecFormSource *fs_src = - new FormspecFormSource(*(event->show_formspec.formspec)); - TextDestPlayerInventory *txt_dst = - new TextDestPlayerInventory(client, *(event->show_formspec.formname)); + new FormspecFormSource(*(event->show_formspec.formspec)); + TextDestPlayerInventory *txt_dst = new TextDestPlayerInventory( + client, *(event->show_formspec.formname)); - auto *&formspec = m_game_ui->updateFormspec(*(event->show_formspec.formname)); - GUIFormSpecMenu::create(formspec, client, &input->joystick, - fs_src, txt_dst, client->getFormspecPrepend()); + auto *&formspec = m_game_ui->updateFormspec( + *(event->show_formspec.formname)); + GUIFormSpecMenu::create(formspec, client, &input->joystick, fs_src, + txt_dst, client->getFormspecPrepend()); } delete event->show_formspec.formspec; @@ -1901,9 +1877,10 @@ void Game::handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation void Game::handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam) { - FormspecFormSource *fs_src = new FormspecFormSource(*event->show_formspec.formspec); + FormspecFormSource *fs_src = + new FormspecFormSource(*event->show_formspec.formspec); LocalFormspecHandler *txt_dst = - new LocalFormspecHandler(*event->show_formspec.formname, client); + new LocalFormspecHandler(*event->show_formspec.formname, client); GUIFormSpecMenu::create(m_game_ui->getFormspecGUI(), client, &input->joystick, fs_src, txt_dst, client->getFormspecPrepend()); @@ -1911,8 +1888,8 @@ void Game::handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrienta delete event->show_formspec.formname; } -void Game::handleClientEvent_HandleParticleEvent(ClientEvent *event, - CameraOrientation *cam) +void Game::handleClientEvent_HandleParticleEvent( + ClientEvent *event, CameraOrientation *cam) { LocalPlayer *player = client->getEnv().getLocalPlayer(); client->getParticleManager()->handleParticleEvent(event, client, player); @@ -1940,20 +1917,20 @@ void Game::handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam) } HudElement *e = new HudElement; - e->type = (HudElementType)event->hudadd.type; - e->pos = *event->hudadd.pos; - e->name = *event->hudadd.name; - e->scale = *event->hudadd.scale; - e->text = *event->hudadd.text; + e->type = (HudElementType)event->hudadd.type; + e->pos = *event->hudadd.pos; + e->name = *event->hudadd.name; + e->scale = *event->hudadd.scale; + e->text = *event->hudadd.text; e->number = event->hudadd.number; - e->item = event->hudadd.item; - e->dir = event->hudadd.dir; - e->align = *event->hudadd.align; + e->item = event->hudadd.item; + e->dir = event->hudadd.dir; + e->align = *event->hudadd.align; e->offset = *event->hudadd.offset; e->world_pos = *event->hudadd.world_pos; e->size = *event->hudadd.size; e->z_index = event->hudadd.z_index; - e->text2 = *event->hudadd.text2; + e->text2 = *event->hudadd.text2; hud_server_to_client[server_id] = player->addHud(e); delete event->hudadd.pos; @@ -1990,57 +1967,57 @@ void Game::handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *ca } switch (event->hudchange.stat) { - case HUD_STAT_POS: - e->pos = *event->hudchange.v2fdata; - break; + case HUD_STAT_POS: + e->pos = *event->hudchange.v2fdata; + break; - case HUD_STAT_NAME: - e->name = *event->hudchange.sdata; - break; + case HUD_STAT_NAME: + e->name = *event->hudchange.sdata; + break; - case HUD_STAT_SCALE: - e->scale = *event->hudchange.v2fdata; - break; + case HUD_STAT_SCALE: + e->scale = *event->hudchange.v2fdata; + break; - case HUD_STAT_TEXT: - e->text = *event->hudchange.sdata; - break; + case HUD_STAT_TEXT: + e->text = *event->hudchange.sdata; + break; - case HUD_STAT_NUMBER: - e->number = event->hudchange.data; - break; + case HUD_STAT_NUMBER: + e->number = event->hudchange.data; + break; - case HUD_STAT_ITEM: - e->item = event->hudchange.data; - break; + case HUD_STAT_ITEM: + e->item = event->hudchange.data; + break; - case HUD_STAT_DIR: - e->dir = event->hudchange.data; - break; + case HUD_STAT_DIR: + e->dir = event->hudchange.data; + break; - case HUD_STAT_ALIGN: - e->align = *event->hudchange.v2fdata; - break; + case HUD_STAT_ALIGN: + e->align = *event->hudchange.v2fdata; + break; - case HUD_STAT_OFFSET: - e->offset = *event->hudchange.v2fdata; - break; + case HUD_STAT_OFFSET: + e->offset = *event->hudchange.v2fdata; + break; - case HUD_STAT_WORLD_POS: - e->world_pos = *event->hudchange.v3fdata; - break; + case HUD_STAT_WORLD_POS: + e->world_pos = *event->hudchange.v3fdata; + break; - case HUD_STAT_SIZE: - e->size = *event->hudchange.v2s32data; - break; + case HUD_STAT_SIZE: + e->size = *event->hudchange.v2s32data; + break; - case HUD_STAT_Z_INDEX: - e->z_index = event->hudchange.data; - break; + case HUD_STAT_Z_INDEX: + e->z_index = event->hudchange.data; + break; - case HUD_STAT_TEXT2: - e->text2 = *event->hudchange.sdata; - break; + case HUD_STAT_TEXT2: + e->text2 = *event->hudchange.sdata; + break; } delete event->hudchange.v3fdata; @@ -2067,11 +2044,9 @@ void Game::handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam) sky->setVisible(true); // Update mesh based skybox colours if applicable. sky->setSkyColors(event->set_sky->sky_color); - sky->setHorizonTint( - event->set_sky->fog_sun_tint, - event->set_sky->fog_moon_tint, - event->set_sky->fog_tint_type - ); + sky->setHorizonTint(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) { // Disable the dyanmic mesh skybox: @@ -2079,27 +2054,23 @@ void Game::handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam) // Set fog colors: sky->setFallbackBgColor(event->set_sky->bgcolor); // Set sunrise and sunset fog tinting: - sky->setHorizonTint( - event->set_sky->fog_sun_tint, - event->set_sky->fog_moon_tint, - event->set_sky->fog_tint_type - ); + sky->setHorizonTint(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++) - sky->addTextureToSkybox(event->set_sky->textures[i], i, texture_src); + sky->addTextureToSkybox( + event->set_sky->textures[i], i, texture_src); } else { // Handle everything else as plain color. if (event->set_sky->type != "plain") - infostream << "Unknown sky type: " - << (event->set_sky->type) << std::endl; + infostream << "Unknown sky type: " << (event->set_sky->type) + << std::endl; sky->setVisible(false); sky->setFallbackBgColor(event->set_sky->bgcolor); // Disable directional sun/moon tinting on plain or invalid skyboxes. - sky->setHorizonTint( - event->set_sky->bgcolor, - event->set_sky->bgcolor, - "custom" - ); + sky->setHorizonTint(event->set_sky->bgcolor, event->set_sky->bgcolor, + "custom"); } delete event->set_sky; } @@ -2107,8 +2078,8 @@ void Game::handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam) void Game::handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam) { sky->setSunVisible(event->sun_params->visible); - sky->setSunTexture(event->sun_params->texture, - event->sun_params->tonemap, texture_src); + sky->setSunTexture(event->sun_params->texture, event->sun_params->tonemap, + texture_src); sky->setSunScale(event->sun_params->scale); sky->setSunriseVisible(event->sun_params->sunrise_visible); sky->setSunriseTexture(event->sun_params->sunrise, texture_src); @@ -2118,8 +2089,8 @@ void Game::handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam) void Game::handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam) { sky->setMoonVisible(event->moon_params->visible); - sky->setMoonTexture(event->moon_params->texture, - event->moon_params->tonemap, texture_src); + sky->setMoonTexture(event->moon_params->texture, event->moon_params->tonemap, + texture_src); sky->setMoonScale(event->moon_params->scale); delete event->moon_params; } @@ -2133,12 +2104,12 @@ void Game::handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam delete event->star_params; } -void Game::handleClientEvent_OverrideDayNigthRatio(ClientEvent *event, - CameraOrientation *cam) +void Game::handleClientEvent_OverrideDayNigthRatio( + ClientEvent *event, CameraOrientation *cam) { client->getEnv().setDayNightRatioOverride( - event->override_day_night_ratio.do_override, - event->override_day_night_ratio.ratio_f * 1000.0f); + event->override_day_night_ratio.do_override, + event->override_day_night_ratio.ratio_f * 1000.0f); } void Game::handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam) @@ -2158,8 +2129,9 @@ void Game::processClientEvents(CameraOrientation *cam) { while (client->hasClientEvents()) { std::unique_ptr event(client->getClientEvent()); - FATAL_ERROR_IF(event->type >= CLIENTEVENT_MAX, "Invalid clientevent type"); - const ClientEventHandler& evHandler = clientEventHandler[event->type]; + FATAL_ERROR_IF(event->type >= CLIENTEVENT_MAX, + "Invalid clientevent type"); + const ClientEventHandler &evHandler = clientEventHandler[event->type]; (this->*evHandler.handler)(event.get(), cam); } } @@ -2181,7 +2153,7 @@ void Game::updateChat(f32 dtime, const v2u32 &screensize) // Display all messages in a static text element m_game_ui->setChatText(chat_backend->getRecentChat(), - chat_backend->getRecentBuffer().getLineCount()); + chat_backend->getRecentBuffer().getLineCount()); } void Game::updateCamera(u32 busy_time, f32 dtime) @@ -2201,11 +2173,11 @@ void Game::updateCamera(u32 busy_time, f32 dtime) } ToolCapabilities playeritem_toolcap = - playeritem.getToolCapabilities(itemdef_manager); + playeritem.getToolCapabilities(itemdef_manager); v3s16 old_camera_offset = camera->getOffset(); - if (wasKeyDown(KeyType::CAMERA_MODE) && ! g_settings->getBool("freecam")) { + if (wasKeyDown(KeyType::CAMERA_MODE) && !g_settings->getBool("freecam")) { camera->toggleCameraMode(); updatePlayerCAOVisibility(); } @@ -2244,18 +2216,20 @@ void Game::updatePlayerCAOVisibility() GenericCAO *playercao = player->getCAO(); if (!playercao) return; - playercao->setVisible(camera->getCameraMode() > CAMERA_MODE_FIRST || g_settings->getBool("freecam")); - playercao->setChildrenVisible(camera->getCameraMode() > CAMERA_MODE_FIRST || g_settings->getBool("freecam")); + playercao->setVisible(camera->getCameraMode() > CAMERA_MODE_FIRST || + g_settings->getBool("freecam")); + playercao->setChildrenVisible(camera->getCameraMode() > CAMERA_MODE_FIRST || + g_settings->getBool("freecam")); } void Game::updateSound(f32 dtime) { // Update sound listener v3s16 camera_offset = camera->getOffset(); - sound->updateListener(camera->getCameraNode()->getPosition() + intToFloat(camera_offset, BS), - v3f(0, 0, 0), // velocity - camera->getDirection(), - camera->getCameraNode()->getUpVector()); + sound->updateListener(camera->getCameraNode()->getPosition() + + intToFloat(camera_offset, BS), + v3f(0, 0, 0), // velocity + camera->getDirection(), camera->getCameraNode()->getUpVector()); bool mute_sound = g_settings->getBool("mute_sound"); if (mute_sound) { @@ -2285,13 +2259,12 @@ void Game::updateSound(f32 dtime) soundmaker->m_player_step_sound = nodedef_manager->get(n).sound_footstep; } - void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug) { LocalPlayer *player = client->getEnv().getLocalPlayer(); const v3f camera_direction = camera->getDirection(); - const v3s16 camera_offset = camera->getOffset(); + const v3s16 camera_offset = camera->getOffset(); /* Calculate what block is the crosshair pointing to @@ -2302,7 +2275,7 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug) const ItemDefinition &selected_def = selected_item.getDefinition(itemdef_manager); f32 d = getToolRange(selected_def, hand_item.getDefinition(itemdef_manager)); - + if (g_settings->getBool("increase_tool_range")) d += 2; if (g_settings->getBool("increase_tool_range_plus")) @@ -2332,18 +2305,17 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug) if ((g_settings->getBool("touchtarget")) && (g_touchscreengui)) { shootline = g_touchscreengui->getShootline(); // Scale shootline to the acual distance the player can reach - shootline.end = shootline.start - + shootline.getVector().normalize() * BS * d; + shootline.end = shootline.start + + shootline.getVector().normalize() * BS * d; shootline.start += intToFloat(camera_offset, BS); shootline.end += intToFloat(camera_offset, BS); } #endif - PointedThing pointed = updatePointedThing(shootline, - selected_def.liquids_pointable, - !runData.ldown_for_dig, - camera_offset); + PointedThing pointed = + updatePointedThing(shootline, selected_def.liquids_pointable, + !runData.ldown_for_dig, camera_offset); if (pointed != runData.pointed_old) { infostream << "Pointing at " << pointed.dump() << std::endl; @@ -2363,18 +2335,19 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug) if (runData.digging) { if (input->getLeftReleased()) { infostream << "Left button released" - << " (stopped digging)" << std::endl; + << " (stopped digging)" << std::endl; runData.digging = false; } else if (pointed != runData.pointed_old) { - if (pointed.type == POINTEDTHING_NODE - && runData.pointed_old.type == POINTEDTHING_NODE - && pointed.node_undersurface - == runData.pointed_old.node_undersurface) { + if (pointed.type == POINTEDTHING_NODE && + runData.pointed_old.type == POINTEDTHING_NODE && + pointed.node_undersurface == + runData.pointed_old + .node_undersurface) { // Still pointing to the same node, but a different face. // Don't reset. } else { infostream << "Pointing away from node" - << " (stopped digging)" << std::endl; + << " (stopped digging)" << std::endl; runData.digging = false; hud->updateSelectionMesh(camera_offset); } @@ -2400,19 +2373,22 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug) soundmaker->m_player_leftpunch_sound.name = ""; // Prepare for repeating, unless we're not supposed to - if ((input->getRightState() || g_settings->getBool("autoplace")) && !g_settings->getBool("safe_dig_and_place")) + if ((input->getRightState() || g_settings->getBool("autoplace")) && + !g_settings->getBool("safe_dig_and_place")) runData.repeat_rightclick_timer += dtime; else runData.repeat_rightclick_timer = 0; if (selected_def.usable && input->getLeftState()) { - if (input->getLeftClicked() && (!client->modsLoaded() - || !client->getScript()->on_item_use(selected_item, pointed))) + if (input->getLeftClicked() && + (!client->modsLoaded() || + !client->getScript()->on_item_use( + selected_item, pointed))) client->interact(INTERACT_USE, pointed); } else if (pointed.type == POINTEDTHING_NODE) { handlePointingAtNode(pointed, selected_item, hand_item, dtime); } else if (pointed.type == POINTEDTHING_OBJECT) { - v3f player_position = player->getPosition(); + 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 @@ -2436,18 +2412,14 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug) input->resetRightReleased(); } - -PointedThing Game::updatePointedThing( - const core::line3d &shootline, - bool liquids_pointable, - bool look_for_object, - const v3s16 &camera_offset) +PointedThing Game::updatePointedThing(const core::line3d &shootline, + bool liquids_pointable, bool look_for_object, const v3s16 &camera_offset) { std::vector *selectionboxes = hud->getSelectionBoxes(); selectionboxes->clear(); hud->setSelectedFaceNormal(v3f(0.0, 0.0, 0.0)); - static thread_local const bool show_entity_selectionbox = g_settings->getBool( - "show_entity_selectionbox"); + static thread_local const bool show_entity_selectionbox = + g_settings->getBool("show_entity_selectionbox"); ClientEnvironment &env = client->getEnv(); ClientMap &map = env.getClientMap(); @@ -2455,16 +2427,20 @@ PointedThing Game::updatePointedThing( runData.selected_object = NULL; hud->pointing_at_object = false; - RaycastState s(shootline, look_for_object, liquids_pointable, ! g_settings->getBool("dont_point_nodes")); + RaycastState s(shootline, look_for_object, liquids_pointable, + !g_settings->getBool("dont_point_nodes")); PointedThing result; env.continueRaycast(&s, &result); if (result.type == POINTEDTHING_OBJECT) { hud->pointing_at_object = true; - runData.selected_object = client->getEnv().getActiveObject(result.object_id); + runData.selected_object = + client->getEnv().getActiveObject(result.object_id); aabb3f selection_box; - if (show_entity_selectionbox && runData.selected_object->doShowSelectionBox() && - runData.selected_object->getSelectionBox(&selection_box)) { + if (show_entity_selectionbox && + runData.selected_object->doShowSelectionBox() && + runData.selected_object->getSelectionBox( + &selection_box)) { v3f pos = runData.selected_object->getPosition(); selectionboxes->push_back(aabb3f(selection_box)); hud->setSelectionPos(pos, camera_offset); @@ -2474,22 +2450,21 @@ PointedThing Game::updatePointedThing( MapNode n = map.getNode(result.node_undersurface); std::vector boxes; n.getSelectionBoxes(nodedef, &boxes, - n.getNeighbors(result.node_undersurface, &map)); + n.getNeighbors(result.node_undersurface, &map)); f32 d = 0.002 * BS; for (std::vector::const_iterator i = boxes.begin(); - i != boxes.end(); ++i) { + i != boxes.end(); ++i) { aabb3f box = *i; box.MinEdge -= v3f(d, d, d); box.MaxEdge += v3f(d, d, d); selectionboxes->push_back(box); } - hud->setSelectionPos(intToFloat(result.node_undersurface, BS), - camera_offset); - hud->setSelectedFaceNormal(v3f( - result.intersection_normal.X, - result.intersection_normal.Y, - result.intersection_normal.Z)); + hud->setSelectionPos( + intToFloat(result.node_undersurface, BS), camera_offset); + hud->setSelectedFaceNormal(v3f(result.intersection_normal.X, + result.intersection_normal.Y, + result.intersection_normal.Z)); } // Update selection mesh light level and vertex colors @@ -2515,13 +2490,15 @@ PointedThing Game::updatePointedThing( // Modify final color a bit with time u32 timer = porting::getTimeMs() % 5000; - float timerf = (float) (irr::core::PI * ((timer / 2500.0) - 0.5)); + float timerf = (float)(irr::core::PI * ((timer / 2500.0) - 0.5)); float sin_r = 0.08f * std::sin(timerf); float sin_g = 0.08f * std::sin(timerf + irr::core::PI * 0.5f); float sin_b = 0.08f * std::sin(timerf + irr::core::PI); c.setRed(core::clamp(core::round32(c.getRed() * (0.8 + sin_r)), 0, 255)); - c.setGreen(core::clamp(core::round32(c.getGreen() * (0.8 + sin_g)), 0, 255)); - c.setBlue(core::clamp(core::round32(c.getBlue() * (0.8 + sin_b)), 0, 255)); + c.setGreen(core::clamp( + core::round32(c.getGreen() * (0.8 + sin_g)), 0, 255)); + c.setBlue(core::clamp( + core::round32(c.getBlue() * (0.8 + sin_b)), 0, 255)); // Set mesh final color hud->setSelectionMeshColor(c); @@ -2537,9 +2514,8 @@ void Game::handlePointingAtNothing(const ItemStack &playerItem) client->interact(INTERACT_ACTIVATE, fauxPointed); } - void Game::handlePointingAtNode(const PointedThing &pointed, - const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime) + const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime) { v3s16 nodepos = pointed.node_undersurface; v3s16 neighbourpos = pointed.node_abovesurface; @@ -2550,10 +2526,10 @@ void Game::handlePointingAtNode(const PointedThing &pointed, ClientMap &map = client->getEnv().getClientMap(); - if (((runData.nodig_delay_timer <= 0.0 || g_settings->getBool("fastdig")) && (input->getLeftState() || g_settings->getBool("autodig")) - && !runData.digging_blocked - && client->checkPrivilege("interact")) - ) { + if (((runData.nodig_delay_timer <= 0.0 || g_settings->getBool("fastdig")) && + (input->getLeftState() || g_settings->getBool("autodig")) && + !runData.digging_blocked && + client->checkPrivilege("interact"))) { handleDigging(pointed, nodepos, selected_item, hand_item, dtime); } @@ -2561,25 +2537,30 @@ void Game::handlePointingAtNode(const PointedThing &pointed, NodeMetadata *meta = map.getNodeMetadata(nodepos); if (meta) { - m_game_ui->setInfoText(unescape_translate(utf8_to_wide( - meta->getString("infotext")))); + m_game_ui->setInfoText(unescape_translate( + utf8_to_wide(meta->getString("infotext")))); } else { MapNode n = map.getNode(nodepos); if (nodedef_manager->get(n).tiledef[0].name == "unknown_node.png") { - m_game_ui->setInfoText(L"Unknown node: " + - utf8_to_wide(nodedef_manager->get(n).name)); + m_game_ui->setInfoText( + L"Unknown node: " + + utf8_to_wide(nodedef_manager->get(n).name)); } } - if ((input->getRightState() || g_settings->getBool("autoplace")) && + if ((input->getRightState() || g_settings->getBool("autoplace")) && (input->getRightClicked() || - (runData.repeat_rightclick_timer >= (g_settings->getBool("fastplace") ? 0 : m_repeat_right_click_time))) && + (runData.repeat_rightclick_timer >= + (g_settings->getBool("fastplace") + ? 0 + : m_repeat_right_click_time))) && client->checkPrivilege("interact")) { runData.repeat_rightclick_timer = 0; infostream << "Ground right-clicked" << std::endl; - camera->setDigging(1); // right click animation (always shown for feedback) + camera->setDigging( + 1); // right click animation (always shown for feedback) soundmaker->m_player_rightpunch_sound = SimpleSoundSpec(); @@ -2588,8 +2569,8 @@ void Game::handlePointingAtNode(const PointedThing &pointed, // And also set the sound and send the interact // But first check for meta formspec and rightclickable auto &def = selected_item.getDefinition(itemdef_manager); - bool placed = nodePlacement(def, selected_item, nodepos, neighbourpos, - pointed, meta); + bool placed = nodePlacement( + def, selected_item, nodepos, neighbourpos, pointed, meta); if (placed && client->modsLoaded()) client->getScript()->on_placenode(pointed, def); @@ -2597,8 +2578,9 @@ void Game::handlePointingAtNode(const PointedThing &pointed, } bool Game::nodePlacement(const ItemDefinition &selected_def, - const ItemStack &selected_item, const v3s16 &nodepos, const v3s16 &neighbourpos, - const PointedThing &pointed, const NodeMetadata *meta) + const ItemStack &selected_item, const v3s16 &nodepos, + const v3s16 &neighbourpos, const PointedThing &pointed, + const NodeMetadata *meta) { std::string prediction = selected_def.node_placement_prediction; const NodeDefManager *nodedef = client->ndef(); @@ -2613,8 +2595,8 @@ bool Game::nodePlacement(const ItemDefinition &selected_def, } // formspec in meta - if (meta && !meta->getString("formspec").empty() && !input->isRandom() - && !isKeyDown(KeyType::SNEAK)) { + if (meta && !meta->getString("formspec").empty() && !input->isRandom() && + !isKeyDown(KeyType::SNEAK)) { // on_rightclick callbacks are called anyway if (nodedef_manager->get(map.getNode(nodepos)).rightclickable) client->interact(INTERACT_PLACE, pointed); @@ -2625,12 +2607,12 @@ bool Game::nodePlacement(const ItemDefinition &selected_def, inventoryloc.setNodeMeta(nodepos); NodeMetadataFormSource *fs_src = new NodeMetadataFormSource( - &client->getEnv().getClientMap(), nodepos); + &client->getEnv().getClientMap(), nodepos); TextDest *txt_dst = new TextDestNodeMetadata(nodepos, client); auto *&formspec = m_game_ui->updateFormspec(""); GUIFormSpecMenu::create(formspec, client, &input->joystick, fs_src, - txt_dst, client->getFormspecPrepend()); + txt_dst, client->getFormspecPrepend()); formspec->setFormSpec(meta->getString("formspec"), inventoryloc); return false; @@ -2638,15 +2620,14 @@ bool Game::nodePlacement(const ItemDefinition &selected_def, // on_rightclick callback if (prediction.empty() || (nodedef->get(node).rightclickable && - !isKeyDown(KeyType::SNEAK))) { + !isKeyDown(KeyType::SNEAK))) { // Report to server client->interact(INTERACT_PLACE, pointed); return false; } - verbosestream << "Node placement prediction for " - << selected_def.name << " is " - << prediction << std::endl; + verbosestream << "Node placement prediction for " << selected_def.name << " is " + << prediction << std::endl; v3s16 p = neighbourpos; // Place inside node itself if buildable_to @@ -2670,9 +2651,8 @@ bool Game::nodePlacement(const ItemDefinition &selected_def, if (!found) { errorstream << "Node placement prediction failed for " - << selected_def.name << " (places " - << prediction - << ") - Name not known" << std::endl; + << selected_def.name << " (places " << prediction + << ") - Name not known" << std::endl; // Handle this as if prediction was empty // Report to server client->interact(INTERACT_PLACE, pointed); @@ -2699,7 +2679,9 @@ bool Game::nodePlacement(const ItemDefinition &selected_def, if (predicted_f.param_type_2 == CPT2_FACEDIR || predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) { - v3s16 dir = nodepos - floatToInt(client->getEnv().getLocalPlayer()->getPosition(), BS); + v3s16 dir = nodepos - + floatToInt(client->getEnv().getLocalPlayer()->getPosition(), + BS); if (abs(dir.X) > abs(dir.Z)) { param2 = dir.X < 0 ? 3 : 1; @@ -2710,15 +2692,15 @@ bool Game::nodePlacement(const ItemDefinition &selected_def, assert(param2 <= 5); - //Check attachment if node is in group attached_node - if (((ItemGroupList) predicted_f.groups)["attached_node"] != 0) { + // Check attachment if node is in group attached_node + if (((ItemGroupList)predicted_f.groups)["attached_node"] != 0) { static v3s16 wallmounted_dirs[8] = { - v3s16(0, 1, 0), - v3s16(0, -1, 0), - v3s16(1, 0, 0), - v3s16(-1, 0, 0), - v3s16(0, 0, 1), - v3s16(0, 0, -1), + v3s16(0, 1, 0), + v3s16(0, -1, 0), + v3s16(1, 0, 0), + v3s16(-1, 0, 0), + v3s16(0, 0, 1), + v3s16(0, 0, -1), }; v3s16 pp; @@ -2736,11 +2718,11 @@ bool Game::nodePlacement(const ItemDefinition &selected_def, } // Apply color - if ((predicted_f.param_type_2 == CPT2_COLOR - || predicted_f.param_type_2 == CPT2_COLORED_FACEDIR - || predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)) { - const std::string &indexstr = selected_item.metadata.getString( - "palette_index", 0); + if ((predicted_f.param_type_2 == CPT2_COLOR || + predicted_f.param_type_2 == CPT2_COLORED_FACEDIR || + predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)) { + const std::string &indexstr = + selected_item.metadata.getString("palette_index", 0); if (!indexstr.empty()) { s32 index = mystoi(indexstr); if (predicted_f.param_type_2 == CPT2_COLOR) { @@ -2765,10 +2747,13 @@ bool Game::nodePlacement(const ItemDefinition &selected_def, // NOTE: This is to be eventually implemented by a mod as client-side Lua if (!nodedef->get(n).walkable || g_settings->getBool("enable_build_where_you_stand") || - (client->checkPrivilege("noclip") && g_settings->getBool("noclip")) || + (client->checkPrivilege("noclip") && + g_settings->getBool("noclip")) || (nodedef->get(n).walkable && - neighbourpos != player->getStandingNodePos() + v3s16(0, 1, 0) && - neighbourpos != player->getStandingNodePos() + v3s16(0, 2, 0))) { + neighbourpos != player->getStandingNodePos() + + v3s16(0, 1, 0) && + neighbourpos != player->getStandingNodePos() + + v3s16(0, 2, 0))) { // This triggers the required mesh update too client->addNode(p, n); // Report to server @@ -2777,24 +2762,24 @@ bool Game::nodePlacement(const ItemDefinition &selected_def, soundmaker->m_player_rightpunch_sound = selected_def.sound_place; return true; } else { - soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed; + soundmaker->m_player_rightpunch_sound = + selected_def.sound_place_failed; return false; } } catch (InvalidPositionException &e) { errorstream << "Node placement prediction failed for " - << selected_def.name << " (places " - << prediction - << ") - Position not loaded" << std::endl; + << selected_def.name << " (places " << prediction + << ") - Position not loaded" << std::endl; soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed; return false; } } -void Game::handlePointingAtObject(const PointedThing &pointed, - const ItemStack &tool_item, const v3f &player_position, bool show_debug) +void Game::handlePointingAtObject(const PointedThing &pointed, const ItemStack &tool_item, + const v3f &player_position, bool show_debug) { std::wstring infotext = unescape_translate( - utf8_to_wide(runData.selected_object->infoText())); + utf8_to_wide(runData.selected_object->infoText())); if (show_debug) { if (!infotext.empty()) { @@ -2809,7 +2794,8 @@ void Game::handlePointingAtObject(const PointedThing &pointed, bool do_punch = false; bool do_punch_damage = false; - if (runData.object_hit_delay_timer <= 0.0 || g_settings->getBool("spamclick")) { + if (runData.object_hit_delay_timer <= 0.0 || + g_settings->getBool("spamclick")) { do_punch = true; do_punch_damage = true; runData.object_hit_delay_timer = object_hit_delay; @@ -2838,11 +2824,10 @@ void Game::handlePointingAtObject(const PointedThing &pointed, } } else if (input->getRightClicked()) { infostream << "Right-clicked object" << std::endl; - client->interact(INTERACT_PLACE, pointed); // place + client->interact(INTERACT_PLACE, pointed); // place } } - void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos, const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime) { @@ -2870,13 +2855,14 @@ void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos, runData.dig_time_complete = params.time; if (m_cache_enable_particles) { - const ContentFeatures &features = client->getNodeDefManager()->get(n); - client->getParticleManager()->addNodeParticle(client, - player, nodepos, n, features); + const ContentFeatures &features = + client->getNodeDefManager()->get(n); + client->getParticleManager()->addNodeParticle( + client, player, nodepos, n, features); } } - - if(g_settings->getBool("instant_break")) { + + if (g_settings->getBool("instant_break")) { runData.dig_time_complete = 0; runData.dig_instantly = true; } @@ -2891,9 +2877,8 @@ void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos, } if (!runData.dig_instantly) { - runData.dig_index = (float)crack_animation_length - * runData.dig_time - / runData.dig_time_complete; + runData.dig_index = (float)crack_animation_length * runData.dig_time / + runData.dig_time_complete; } else { // This is for e.g. torches runData.dig_index = crack_animation_length; @@ -2917,8 +2902,8 @@ void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos, // Don't show cracks if not diggable if (runData.dig_time_complete >= 100000.0) { } else if (runData.dig_index < crack_animation_length) { - //TimeTaker timer("client.setTempMod"); - //infostream<<"dig_index="<modsLoaded() && - client->getScript()->on_dignode(nodepos, wasnode)) { + if (client->modsLoaded() && client->getScript()->on_dignode( + nodepos, wasnode)) { return; } @@ -2953,7 +2938,8 @@ void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos, client->removeNode(nodepos); } else if (!f.node_dig_prediction.empty()) { content_t id; - bool found = client->ndef()->getId(f.node_dig_prediction, id); + bool found = client->ndef()->getId( + f.node_dig_prediction, id); if (found) client->addNode(nodepos, id, true); } @@ -2964,12 +2950,11 @@ void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos, if (m_cache_enable_particles) { const ContentFeatures &features = - client->getNodeDefManager()->get(wasnode); - client->getParticleManager()->addDiggingParticles(client, - player, nodepos, wasnode, features); + client->getNodeDefManager()->get(wasnode); + client->getParticleManager()->addDiggingParticles( + client, player, nodepos, wasnode, features); } - // Send event to trigger sound client->getEventManager()->put(new NodeDugEvent(nodepos, wasnode)); } @@ -2981,10 +2966,9 @@ void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos, client->setCrack(-1, nodepos); } - camera->setDigging(0); // left click animation + camera->setDigging(0); // left click animation } - void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, const CameraOrientation &cam) { @@ -3014,10 +2998,13 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, sunlight_seen = true; } else { float old_brightness = sky->getBrightness(); - direct_brightness = client->getEnv().getClientMap() - .getBackgroundBrightness(MYMIN(runData.fog_range * 1.2, 60 * BS), - daynight_ratio, (int)(old_brightness * 255.5), &sunlight_seen) - / 255.0; + direct_brightness = + client->getEnv().getClientMap().getBackgroundBrightness( + MYMIN(runData.fog_range * 1.2, 60 * BS), + daynight_ratio, + (int)(old_brightness * 255.5), + &sunlight_seen) / + 255.0; } float time_of_day_smooth = runData.time_of_day_smooth; @@ -3032,17 +3019,16 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, time_of_day_smooth = time_of_day; if (time_of_day_smooth > 0.8 && time_of_day < 0.2) - time_of_day_smooth = time_of_day_smooth * (1.0 - todsm) - + (time_of_day + 1.0) * todsm; + time_of_day_smooth = time_of_day_smooth * (1.0 - todsm) + + (time_of_day + 1.0) * todsm; else - time_of_day_smooth = time_of_day_smooth * (1.0 - todsm) - + time_of_day * todsm; + time_of_day_smooth = + time_of_day_smooth * (1.0 - todsm) + time_of_day * todsm; runData.time_of_day_smooth = time_of_day_smooth; - sky->update(time_of_day_smooth, time_brightness, direct_brightness, - sunlight_seen, camera->getCameraMode(), player->getYaw(), - player->getPitch()); + sky->update(time_of_day_smooth, time_brightness, direct_brightness, sunlight_seen, + camera->getCameraMode(), player->getYaw(), player->getPitch()); /* Update clouds @@ -3053,20 +3039,26 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, clouds->step(dtime); // camera->getPosition is not enough for 3rd person views v3f camera_node_position = camera->getCameraNode()->getPosition(); - v3s16 camera_offset = camera->getOffset(); - camera_node_position.X = camera_node_position.X + camera_offset.X * BS; - camera_node_position.Y = camera_node_position.Y + camera_offset.Y * BS; - camera_node_position.Z = camera_node_position.Z + camera_offset.Z * BS; - clouds->update(camera_node_position, - sky->getCloudColor()); + v3s16 camera_offset = camera->getOffset(); + camera_node_position.X = + camera_node_position.X + camera_offset.X * BS; + camera_node_position.Y = + camera_node_position.Y + camera_offset.Y * BS; + camera_node_position.Z = + camera_node_position.Z + camera_offset.Z * BS; + clouds->update(camera_node_position, sky->getCloudColor()); if (clouds->isCameraInsideCloud() && m_cache_enable_fog) { // if inside clouds, and fog enabled, use that as sky // color(s) - video::SColor clouds_dark = clouds->getColor() - .getInterpolated(video::SColor(255, 0, 0, 0), 0.9); + video::SColor clouds_dark = + clouds->getColor().getInterpolated( + video::SColor(255, 0, 0, + 0), + 0.9); sky->overrideColors(clouds_dark, clouds->getColor()); sky->setInClouds(true); - runData.fog_range = std::fmin(runData.fog_range * 0.5f, 32.0f * BS); + runData.fog_range = std::fmin( + runData.fog_range * 0.5f, 32.0f * BS); // do not draw clouds after all clouds->setVisible(false); } @@ -3085,24 +3077,17 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, */ if (m_cache_enable_fog) { - driver->setFog( - sky->getBgColor(), - video::EFT_FOG_LINEAR, + driver->setFog(sky->getBgColor(), video::EFT_FOG_LINEAR, runData.fog_range * m_cache_fog_start, - runData.fog_range * 1.0, - 0.01, + runData.fog_range * 1.0, 0.01, false, // pixel fog - true // range fog + true // range fog ); } else { - driver->setFog( - sky->getBgColor(), - video::EFT_FOG_LINEAR, - 100000 * BS, - 110000 * BS, - 0.01f, + driver->setFog(sky->getBgColor(), video::EFT_FOG_LINEAR, 100000 * BS, + 110000 * BS, 0.01f, false, // pixel fog - false // range fog + false // range fog ); } @@ -3135,15 +3120,17 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, runData.update_draw_list_timer += dtime; v3f camera_direction = camera->getDirection(); - if (runData.update_draw_list_timer >= 0.2 - || runData.update_draw_list_last_cam_dir.getDistanceFrom(camera_direction) > 0.2 - || m_camera_offset_changed) { + if (runData.update_draw_list_timer >= 0.2 || + runData.update_draw_list_last_cam_dir.getDistanceFrom( + camera_direction) > 0.2 || + m_camera_offset_changed) { runData.update_draw_list_timer = 0; client->getEnv().getClientMap().updateDrawList(); runData.update_draw_list_last_cam_dir = camera_direction; } - m_game_ui->update(*stats, client, draw_control, cam, runData.pointed_old, gui_chat_console, dtime); + m_game_ui->update(*stats, client, draw_control, cam, runData.pointed_old, + gui_chat_console, dtime); /* make sure menu is on top @@ -3162,7 +3149,9 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, auto &loc = formspec->getFormspecLocation(); if (loc.type == InventoryLocation::NODEMETA) { - NodeMetadata *meta = client->getEnv().getClientMap().getNodeMetadata(loc.p); + NodeMetadata *meta = + client->getEnv().getClientMap().getNodeMetadata( + loc.p); if (!meta || meta->getString("formspec").empty()) { formspec->quitMenu(); break; @@ -3182,11 +3171,10 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, driver->beginScene(true, true, skycolor); bool draw_wield_tool = (m_game_ui->m_flags.show_hud && - (player->hud_flags & HUD_FLAG_WIELDITEM_VISIBLE) && - (camera->getCameraMode() == CAMERA_MODE_FIRST)); - bool draw_crosshair = ( - (player->hud_flags & HUD_FLAG_CROSSHAIR_VISIBLE) && - (camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT)); + (player->hud_flags & HUD_FLAG_WIELDITEM_VISIBLE) && + (camera->getCameraMode() == CAMERA_MODE_FIRST)); + bool draw_crosshair = ((player->hud_flags & HUD_FLAG_CROSSHAIR_VISIBLE) && + (camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT)); #ifdef HAVE_TOUCHSCREENGUI try { draw_crosshair = !g_settings->getBool("touchtarget"); @@ -3194,7 +3182,9 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, } #endif RenderingEngine::draw_scene(skycolor, m_game_ui->m_flags.show_hud, - m_game_ui->m_flags.show_minimap, draw_wield_tool, draw_crosshair, g_settings->getBool("enable_tracers"), g_settings->getBool("enable_esp")); + m_game_ui->m_flags.show_minimap, draw_wield_tool, draw_crosshair, + g_settings->getBool("enable_tracers"), + g_settings->getBool("enable_esp")); /* Profiler graph @@ -3206,7 +3196,7 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, Cheat menu */ - if (! gui_chat_console->isOpen()) { + if (!gui_chat_console->isOpen()) { if (m_game_ui->m_flags.show_cheat_menu) m_cheat_menu->draw(driver, m_game_ui->m_flags.show_debug); if (g_settings->getBool("cheat_hud")) @@ -3217,8 +3207,10 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, */ if (runData.damage_flash > 0.0f) { video::SColor color(runData.damage_flash, 180, 0, 0); - if (! g_settings->getBool("no_hurt_cam")) - driver->draw2DRectangle(color, core::rect(0, 0, screensize.X, screensize.Y), NULL); + if (!g_settings->getBool("no_hurt_cam")) + driver->draw2DRectangle(color, + core::rect(0, 0, screensize.X, screensize.Y), + NULL); runData.damage_flash -= 384.0f * dtime; } @@ -3244,7 +3236,7 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, /* End scene */ - + driver->endScene(); stats->drawtime = tt_draw.stop(true); @@ -3260,8 +3252,6 @@ inline void Game::updateProfilerGraphs(ProfilerGraph *graph) graph->put(values); } - - /**************************************************************************** Misc ****************************************************************************/ @@ -3275,14 +3265,15 @@ inline void Game::limitFps(FpsControl *fps_timings, f32 *dtime) u32 time = device->getTimer()->getTime(); u32 last_time = fps_timings->last_time; - if (time > last_time) // Make sure time hasn't overflowed + if (time > last_time) // Make sure time hasn't overflowed fps_timings->busy_time = time - last_time; else fps_timings->busy_time = 0; - u32 frametime_min = 1000 / (g_menumgr.pausesGame() - ? g_settings->getFloat("pause_fps_max") - : g_settings->getFloat("fps_max")); + u32 frametime_min = + 1000 / + (g_menumgr.pausesGame() ? g_settings->getFloat("pause_fps_max") + : g_settings->getFloat("fps_max")); if (fps_timings->busy_time < frametime_min) { fps_timings->sleep_time = frametime_min - fps_timings->busy_time; @@ -3300,7 +3291,7 @@ inline void Game::limitFps(FpsControl *fps_timings, f32 *dtime) device->getTimer()->tick(); // Update device timer time = device->getTimer()->getTime(); - if (time > last_time) // Make sure last_time hasn't overflowed + if (time > last_time) // Make sure last_time hasn't overflowed *dtime = (time - last_time) / 1000.0; else *dtime = 0; @@ -3311,8 +3302,8 @@ inline void Game::limitFps(FpsControl *fps_timings, f32 *dtime) void Game::showOverlayMessage(const char *msg, float dtime, int percent, bool draw_clouds) { const wchar_t *wmsg = wgettext(msg); - RenderingEngine::draw_load_screen(wmsg, guienv, texture_src, dtime, percent, - draw_clouds); + RenderingEngine::draw_load_screen( + wmsg, guienv, texture_src, dtime, percent, draw_clouds); delete[] wmsg; } @@ -3323,12 +3314,12 @@ void Game::settingChangedCallback(const std::string &setting_name, void *data) void Game::updateAllMapBlocksCallback(const std::string &setting_name, void *data) { - ((Game *) data)->client->updateAllMapBlocks(); + ((Game *)data)->client->updateAllMapBlocks(); } void Game::freecamChangedCallback(const std::string &setting_name, void *data) { - Game *game = (Game *) data; + Game *game = (Game *)data; LocalPlayer *player = game->client->getEnv().getLocalPlayer(); if (g_settings->getBool("freecam")) { game->camera->setCameraMode(CAMERA_MODE_FIRST); @@ -3341,23 +3332,25 @@ void Game::freecamChangedCallback(const std::string &setting_name, void *data) void Game::readSettings() { - m_cache_doubletap_jump = g_settings->getBool("doubletap_jump"); - m_cache_enable_clouds = g_settings->getBool("enable_clouds"); - m_cache_enable_joysticks = g_settings->getBool("enable_joysticks"); - m_cache_enable_particles = g_settings->getBool("enable_particles"); - m_cache_enable_fog = g_settings->getBool("enable_fog"); - m_cache_mouse_sensitivity = g_settings->getFloat("mouse_sensitivity"); - m_cache_joystick_frustum_sensitivity = g_settings->getFloat("joystick_frustum_sensitivity"); - m_repeat_right_click_time = g_settings->getFloat("repeat_rightclick_time"); + m_cache_doubletap_jump = g_settings->getBool("doubletap_jump"); + m_cache_enable_clouds = g_settings->getBool("enable_clouds"); + m_cache_enable_joysticks = g_settings->getBool("enable_joysticks"); + m_cache_enable_particles = g_settings->getBool("enable_particles"); + m_cache_enable_fog = g_settings->getBool("enable_fog"); + m_cache_mouse_sensitivity = g_settings->getFloat("mouse_sensitivity"); + m_cache_joystick_frustum_sensitivity = + g_settings->getFloat("joystick_frustum_sensitivity"); + m_repeat_right_click_time = g_settings->getFloat("repeat_rightclick_time"); - m_cache_enable_noclip = g_settings->getBool("noclip"); - m_cache_enable_free_move = g_settings->getBool("free_move"); + m_cache_enable_noclip = g_settings->getBool("noclip"); + m_cache_enable_free_move = g_settings->getBool("free_move"); - m_cache_fog_start = g_settings->getFloat("fog_start"); + m_cache_fog_start = g_settings->getFloat("fog_start"); m_cache_cam_smoothing = 0; if (g_settings->getBool("cinematic")) - m_cache_cam_smoothing = 1 - g_settings->getFloat("cinematic_camera_smoothing"); + m_cache_cam_smoothing = + 1 - g_settings->getFloat("cinematic_camera_smoothing"); else m_cache_cam_smoothing = 1 - g_settings->getFloat("camera_smoothing"); @@ -3379,41 +3372,40 @@ void Game::extendedResourceCleanup() // Extended resource accounting infostream << "Irrlicht resources after cleanup:" << std::endl; infostream << "\tRemaining meshes : " - << RenderingEngine::get_mesh_cache()->getMeshCount() << std::endl; - infostream << "\tRemaining textures : " - << driver->getTextureCount() << std::endl; + << RenderingEngine::get_mesh_cache()->getMeshCount() << std::endl; + infostream << "\tRemaining textures : " << driver->getTextureCount() << std::endl; for (unsigned int i = 0; i < driver->getTextureCount(); i++) { irr::video::ITexture *texture = driver->getTextureByIndex(i); infostream << "\t\t" << i << ":" << texture->getName().getPath().c_str() - << std::endl; + << std::endl; } clearTextureNameCache(); - infostream << "\tRemaining materials: " - << driver-> getMaterialRendererCount() - << " (note: irrlicht doesn't support removing renderers)" << std::endl; + infostream << "\tRemaining materials: " << driver->getMaterialRendererCount() + << " (note: irrlicht doesn't support removing renderers)" << std::endl; } void Game::showDeathFormspec() { - static std::string formspec_str = - std::string("formspec_version[1]") + - SIZE_TAG - "bgcolor[#320000b4;true]" - "label[4.85,1.35;" + gettext("You died") + "]" - "button_exit[4,3;3,0.5;btn_respawn;" + gettext("Respawn") + "]" - ; + static std::string formspec_str = std::string("formspec_version[1]") + + SIZE_TAG "bgcolor[#320000b4;true]" + "label[4.85,1.35;" + + gettext("You died") + + "]" + "button_exit[4,3;3,0.5;btn_respawn;" + + gettext("Respawn") + "]"; /* Create menu */ /* Note: FormspecFormSource and LocalFormspecHandler * * are deleted by guiFormSpecMenu */ FormspecFormSource *fs_src = new FormspecFormSource(formspec_str); - LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_DEATH_SCREEN", client); + LocalFormspecHandler *txt_dst = + new LocalFormspecHandler("MT_DEATH_SCREEN", client); auto *&formspec = m_game_ui->getFormspecGUI(); - GUIFormSpecMenu::create(formspec, client, &input->joystick, - fs_src, txt_dst, client->getFormspecPrepend()); + GUIFormSpecMenu::create(formspec, client, &input->joystick, fs_src, txt_dst, + client->getFormspecPrepend()); formspec->setFocus("btn_respawn"); } @@ -3421,59 +3413,53 @@ void Game::showDeathFormspec() void Game::showPauseMenu() { #ifdef __ANDROID__ - static const std::string control_text = strgettext("Default Controls:\n" - "No menu visible:\n" - "- single tap: button activate\n" - "- double tap: place/use\n" - "- slide finger: look around\n" - "Menu/Inventory visible:\n" - "- double tap (outside):\n" - " -->close\n" - "- touch stack, touch slot:\n" - " --> move stack\n" - "- touch&drag, tap 2nd finger\n" - " --> place single item to slot\n" - ); + static const std::string control_text = + strgettext("Default Controls:\n" + "No menu visible:\n" + "- single tap: button activate\n" + "- double tap: place/use\n" + "- slide finger: look around\n" + "Menu/Inventory visible:\n" + "- double tap (outside):\n" + " -->close\n" + "- touch stack, touch slot:\n" + " --> move stack\n" + "- touch&drag, tap 2nd finger\n" + " --> place single item to slot\n"); #else - static const std::string control_text_template = strgettext("Controls:\n" - "- %s: move forwards\n" - "- %s: move backwards\n" - "- %s: move left\n" - "- %s: move right\n" - "- %s: jump/climb\n" - "- %s: sneak/go down\n" - "- %s: drop item\n" - "- %s: inventory\n" - "- %s: enderchest\n" - "- Mouse: turn/look\n" - "- Mouse left: dig/punch\n" - "- Mouse right: place/use\n" - "- Mouse wheel: select item\n" - "- %s: chat\n" - "- %s: Killaura\n" - "- %s: Freecam\n" - "- %s: Scaffold\n" - "- %s: NextItem\n" - ); + static const std::string control_text_template = + strgettext("Controls:\n" + "- %s: move forwards\n" + "- %s: move backwards\n" + "- %s: move left\n" + "- %s: move right\n" + "- %s: jump/climb\n" + "- %s: sneak/go down\n" + "- %s: drop item\n" + "- %s: inventory\n" + "- %s: enderchest\n" + "- Mouse: turn/look\n" + "- Mouse left: dig/punch\n" + "- Mouse right: place/use\n" + "- Mouse wheel: select item\n" + "- %s: chat\n" + "- %s: Killaura\n" + "- %s: Freecam\n" + "- %s: Scaffold\n" + "- %s: NextItem\n"); - char control_text_buf[600]; + char control_text_buf[600]; - porting::mt_snprintf(control_text_buf, sizeof(control_text_buf), control_text_template.c_str(), - GET_KEY_NAME(keymap_forward), - GET_KEY_NAME(keymap_backward), - GET_KEY_NAME(keymap_left), - GET_KEY_NAME(keymap_right), - GET_KEY_NAME(keymap_jump), - GET_KEY_NAME(keymap_sneak), - GET_KEY_NAME(keymap_drop), - GET_KEY_NAME(keymap_inventory), - GET_KEY_NAME(keymap_enderchest), - GET_KEY_NAME(keymap_chat), - GET_KEY_NAME(keymap_toggle_killaura), + porting::mt_snprintf(control_text_buf, sizeof(control_text_buf), + control_text_template.c_str(), GET_KEY_NAME(keymap_forward), + GET_KEY_NAME(keymap_backward), GET_KEY_NAME(keymap_left), + GET_KEY_NAME(keymap_right), GET_KEY_NAME(keymap_jump), + GET_KEY_NAME(keymap_sneak), GET_KEY_NAME(keymap_drop), + GET_KEY_NAME(keymap_inventory), GET_KEY_NAME(keymap_enderchest), + GET_KEY_NAME(keymap_chat), GET_KEY_NAME(keymap_toggle_killaura), GET_KEY_NAME(keymap_toggle_freecam), GET_KEY_NAME(keymap_toggle_scaffold), - GET_KEY_NAME(keymap_toggle_next_item) - ); + GET_KEY_NAME(keymap_toggle_next_item)); std::string control_text = std::string(control_text_buf); str_formspec_escape(control_text); @@ -3482,13 +3468,12 @@ void Game::showPauseMenu() float ypos = simple_singleplayer_mode ? 0.7f : 0.1f; std::ostringstream os; - os << "formspec_version[1]" << SIZE_TAG - << "button_exit[4," << (ypos++) << ";3,0.5;btn_continue;" - << strgettext("Continue") << "]"; + os << "formspec_version[1]" << SIZE_TAG << "button_exit[4," << (ypos++) + << ";3,0.5;btn_continue;" << strgettext("Continue") << "]"; if (!simple_singleplayer_mode) { os << "button_exit[4," << (ypos++) << ";3,0.5;btn_change_password;" - << strgettext("Change Password") << "]"; + << strgettext("Change Password") << "]"; } else { os << "field[4.95,0;5,1.5;;" << strgettext("Game paused") << ";]"; } @@ -3497,27 +3482,27 @@ void Game::showPauseMenu() #if USE_SOUND if (g_settings->getBool("enable_sound")) { os << "button_exit[4," << (ypos++) << ";3,0.5;btn_sound;" - << strgettext("Sound Volume") << "]"; + << strgettext("Sound Volume") << "]"; } #endif - os << "button_exit[4," << (ypos++) << ";3,0.5;btn_key_config;" - << strgettext("Change Keys") << "]"; + os << "button_exit[4," << (ypos++) << ";3,0.5;btn_key_config;" + << strgettext("Change Keys") << "]"; #endif - os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_menu;" - << strgettext("Exit to Menu") << "]"; - os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;" - << strgettext("Exit to OS") << "]" - << "textarea[7.5,0.25;3.9,6.25;;" << control_text << ";]" - << "textarea[0.4,0.25;3.9,6.25;;" << PROJECT_NAME_C " " VERSION_STRING "\n" - << "\n" - << strgettext("Game info:") << "\n"; + os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_menu;" + << strgettext("Exit to Menu") << "]"; + os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;" + << strgettext("Exit to OS") << "]" + << "textarea[7.5,0.25;3.9,6.25;;" << control_text << ";]" + << "textarea[0.4,0.25;3.9,6.25;;" << PROJECT_NAME_C " " VERSION_STRING "\n" + << "\n" + << strgettext("Game info:") << "\n"; const std::string &address = client->getAddressName(); static const std::string mode = strgettext("- Mode: "); if (!simple_singleplayer_mode) { Address serverAddress = client->getServerAddress(); if (!address.empty()) { os << mode << strgettext("Remote server") << "\n" - << strgettext("- Address: ") << address; + << strgettext("- Address: ") << address; } else { os << mode << strgettext("Hosting server"); } @@ -3528,21 +3513,24 @@ void Game::showPauseMenu() if (simple_singleplayer_mode || address.empty()) { static const std::string on = strgettext("On"); static const std::string off = strgettext("Off"); - const std::string &damage = g_settings->getBool("enable_damage") ? on : off; - const std::string &creative = g_settings->getBool("creative_mode") ? on : off; - const std::string &announced = g_settings->getBool("server_announce") ? on : off; + const std::string &damage = + g_settings->getBool("enable_damage") ? on : off; + const std::string &creative = + g_settings->getBool("creative_mode") ? on : off; + const std::string &announced = + g_settings->getBool("server_announce") ? on : off; os << strgettext("- Damage: ") << damage << "\n" - << strgettext("- Creative Mode: ") << creative << "\n"; + << strgettext("- Creative Mode: ") << creative << "\n"; if (!simple_singleplayer_mode) { - const std::string &pvp = g_settings->getBool("enable_pvp") ? on : off; + const std::string &pvp = + g_settings->getBool("enable_pvp") ? on : off; //~ PvP = Player versus Player os << strgettext("- PvP: ") << pvp << "\n" - << strgettext("- Public: ") << announced << "\n"; + << strgettext("- Public: ") << announced << "\n"; std::string server_name = g_settings->get("server_name"); str_formspec_escape(server_name); if (announced == on && !server_name.empty()) os << strgettext("- Server Name: ") << server_name; - } } os << ";]"; @@ -3554,8 +3542,8 @@ void Game::showPauseMenu() LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_PAUSE_MENU"); auto *&formspec = m_game_ui->getFormspecGUI(); - GUIFormSpecMenu::create(formspec, client, &input->joystick, - fs_src, txt_dst, client->getFormspecPrepend()); + GUIFormSpecMenu::create(formspec, client, &input->joystick, fs_src, txt_dst, + client->getFormspecPrepend()); formspec->setFocus("btn_continue"); formspec->doPause = true; } @@ -3568,15 +3556,12 @@ void Game::showPauseMenu() Game *g_game; -void the_game(bool *kill, - InputHandler *input, - const GameStartData &start_data, - std::string &error_message, - ChatBackend &chat_backend, +void the_game(bool *kill, InputHandler *input, const GameStartData &start_data, + std::string &error_message, ChatBackend &chat_backend, bool *reconnect_requested) // Used for local game { Game game; - + g_game = &game; /* Make a copy of the server address because if a local singleplayer server @@ -3587,13 +3572,14 @@ void the_game(bool *kill, try { if (game.startup(kill, input, start_data, error_message, - reconnect_requested, &chat_backend)) { + reconnect_requested, &chat_backend)) { game.run(); } } catch (SerializationError &e) { - error_message = std::string("A serialization error occurred:\n") - + e.what() + "\n\nThe server is probably " + error_message = std::string("A serialization error occurred:\n") + + e.what() + + "\n\nThe server is probably " " running a different version of " PROJECT_NAME_C "."; errorstream << error_message << std::endl; } catch (ServerError &e) { diff --git a/src/client/game.h b/src/client/game.h index 51accc679..70286ba85 100644 --- a/src/client/game.h +++ b/src/client/game.h @@ -28,7 +28,7 @@ with this program; if not, write to the Free Software Foundation, Inc., //#include "client/gameui.h" #include "client/inputhandler.h" #include "client/sound.h" -#include "client/tile.h" // For TextureSource +#include "client/tile.h" // For TextureSource #include "client/keys.h" #include "client/joystick_controller.h" #include "clientmap.h" @@ -52,7 +52,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "gui/profilergraph.h" #include "mapblock.h" #include "minimap.h" -#include "nodedef.h" // Needed for determining pointing to nodes +#include "nodedef.h" // Needed for determining pointing to nodes #include "nodemetadata.h" #include "particles.h" #include "porting.h" @@ -75,23 +75,26 @@ with this program; if not, write to the Free Software Foundation, Inc., #include class InputHandler; -class ChatBackend; /* to avoid having to include chat.h */ +class ChatBackend; /* to avoid having to include chat.h */ struct SubgameSpec; struct GameStartData; -struct Jitter { +struct Jitter +{ f32 max, min, avg, counter, max_sample, min_sample, max_fraction; }; -struct RunStats { +struct RunStats +{ u32 drawtime; Jitter dtime_jitter, busy_time_jitter; }; -struct CameraOrientation { - f32 camera_yaw; // "right/left" - f32 camera_pitch; // "up/down" +struct CameraOrientation +{ + f32 camera_yaw; // "right/left" + f32 camera_pitch; // "up/down" }; /* @@ -146,13 +149,10 @@ struct TextDestPlayerInventory : public TextDest struct LocalFormspecHandler : public TextDest { - LocalFormspecHandler(const std::string &formname) - { - m_formname = formname; - } + LocalFormspecHandler(const std::string &formname) { m_formname = formname; } - LocalFormspecHandler(const std::string &formname, Client *client): - m_client(client) + LocalFormspecHandler(const std::string &formname, Client *client) : + m_client(client) { m_formname = formname; } @@ -212,14 +212,10 @@ struct LocalFormspecHandler : public TextDest /* Form update callback */ -class NodeMetadataFormSource: public IFormSource +class NodeMetadataFormSource : public IFormSource { public: - NodeMetadataFormSource(ClientMap *map, v3s16 p): - m_map(map), - m_p(p) - { - } + NodeMetadataFormSource(ClientMap *map, v3s16 p) : m_map(map), m_p(p) {} const std::string &getForm() const { static const std::string empty_string = ""; @@ -245,13 +241,10 @@ public: v3s16 m_p; }; -class PlayerInventoryFormSource: public IFormSource +class PlayerInventoryFormSource : public IFormSource { public: - PlayerInventoryFormSource(Client *client): - m_client(client) - { - } + PlayerInventoryFormSource(Client *client) : m_client(client) {} const std::string &getForm() const { @@ -262,26 +255,21 @@ public: Client *m_client; }; -class NodeDugEvent: public MtEvent +class NodeDugEvent : public MtEvent { public: v3s16 p; MapNode n; - NodeDugEvent(v3s16 p, MapNode n): - p(p), - n(n) - {} - MtEvent::Type getType() const - { - return MtEvent::NODE_DUG; - } + NodeDugEvent(v3s16 p, MapNode n) : p(p), n(n) {} + MtEvent::Type getType() const { return MtEvent::NODE_DUG; } }; class SoundMaker { ISoundManager *m_sound; const NodeDefManager *m_ndef; + public: bool makes_footstep_sound; float m_player_step_timer; @@ -291,12 +279,9 @@ public: SimpleSoundSpec m_player_leftpunch_sound; SimpleSoundSpec m_player_rightpunch_sound; - SoundMaker(ISoundManager *sound, const NodeDefManager *ndef): - m_sound(sound), - m_ndef(ndef), - makes_footstep_sound(true), - m_player_step_timer(0), - m_player_jump_timer(0.0f) + SoundMaker(ISoundManager *sound, const NodeDefManager *ndef) : + m_sound(sound), m_ndef(ndef), makes_footstep_sound(true), + m_player_step_timer(0), m_player_jump_timer(0.0f) { } @@ -363,19 +348,22 @@ public: static void playerFallingDamage(MtEvent *e, void *data) { SoundMaker *sm = (SoundMaker *)data; - sm->m_sound->playSound(SimpleSoundSpec("player_falling_damage", 0.5), false); + sm->m_sound->playSound( + SimpleSoundSpec("player_falling_damage", 0.5), false); } void registerReceiver(MtEventManager *mgr) { mgr->reg(MtEvent::VIEW_BOBBING_STEP, SoundMaker::viewBobbingStep, this); - mgr->reg(MtEvent::PLAYER_REGAIN_GROUND, SoundMaker::playerRegainGround, this); + mgr->reg(MtEvent::PLAYER_REGAIN_GROUND, SoundMaker::playerRegainGround, + this); mgr->reg(MtEvent::PLAYER_JUMP, SoundMaker::playerJump, this); mgr->reg(MtEvent::CAMERA_PUNCH_LEFT, SoundMaker::cameraPunchLeft, this); mgr->reg(MtEvent::CAMERA_PUNCH_RIGHT, SoundMaker::cameraPunchRight, this); mgr->reg(MtEvent::NODE_DUG, SoundMaker::nodeDug, this); mgr->reg(MtEvent::PLAYER_DAMAGE, SoundMaker::playerDamage, this); - mgr->reg(MtEvent::PLAYER_FALLING_DAMAGE, SoundMaker::playerFallingDamage, this); + mgr->reg(MtEvent::PLAYER_FALLING_DAMAGE, SoundMaker::playerFallingDamage, + this); } void step(float dtime) @@ -386,30 +374,40 @@ public: }; // Locally stored sounds don't need to be preloaded because of this -class GameOnDemandSoundFetcher: public OnDemandSoundFetcher +class GameOnDemandSoundFetcher : public OnDemandSoundFetcher { std::set m_fetched; + private: - void paths_insert(std::set &dst_paths, - const std::string &base, - const std::string &name) + void paths_insert(std::set &dst_paths, const std::string &base, + const std::string &name) { dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".ogg"); - dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".0.ogg"); - dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".1.ogg"); - dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".2.ogg"); - dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".3.ogg"); - dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".4.ogg"); - dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".5.ogg"); - dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".6.ogg"); - dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".7.ogg"); - dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".8.ogg"); - dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".9.ogg"); + dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + + ".0.ogg"); + dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + + ".1.ogg"); + dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + + ".2.ogg"); + dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + + ".3.ogg"); + dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + + ".4.ogg"); + dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + + ".5.ogg"); + dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + + ".6.ogg"); + dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + + ".7.ogg"); + dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + + ".8.ogg"); + dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + + ".9.ogg"); } + public: - void fetchSounds(const std::string &name, - std::set &dst_paths, - std::set &dst_datas) + void fetchSounds(const std::string &name, std::set &dst_paths, + std::set &dst_datas) { if (m_fetched.count(name)) return; @@ -417,11 +415,10 @@ public: m_fetched.insert(name); paths_insert(dst_paths, porting::path_share, name); - paths_insert(dst_paths, porting::path_user, name); + paths_insert(dst_paths, porting::path_user, name); } }; - // before 1.8 there isn't a "integer interface", only float #if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8) typedef f32 SamplerLayer_t; @@ -429,7 +426,6 @@ typedef f32 SamplerLayer_t; typedef s32 SamplerLayer_t; #endif - class GameGlobalShaderConstantSetter : public IShaderConstantSetter { Sky *m_sky; @@ -460,30 +456,25 @@ public: static void settingsCallback(const std::string &name, void *userdata) { - reinterpret_cast(userdata)->onSettingsChange(name); + reinterpret_cast(userdata) + ->onSettingsChange(name); } void setSky(Sky *sky) { m_sky = sky; } - GameGlobalShaderConstantSetter(Sky *sky, bool *force_fog_off, - f32 *fog_range, Client *client) : - m_sky(sky), - m_force_fog_off(force_fog_off), - m_fog_range(fog_range), - m_sky_bg_color("skyBgColor"), - m_fog_distance("fogDistance"), - m_animation_timer_vertex("animationTimer"), - m_animation_timer_pixel("animationTimer"), - m_day_light("dayLight"), - m_eye_position_pixel("eyePosition"), - m_eye_position_vertex("eyePosition"), - m_minimap_yaw("yawVec"), - m_camera_offset_pixel("cameraOffset"), - m_camera_offset_vertex("cameraOffset"), - m_base_texture("baseTexture"), - m_normal_texture("normalTexture"), - m_texture_flags("textureFlags"), - m_client(client) + GameGlobalShaderConstantSetter( + Sky *sky, bool *force_fog_off, f32 *fog_range, Client *client) : + m_sky(sky), + m_force_fog_off(force_fog_off), m_fog_range(fog_range), + m_sky_bg_color("skyBgColor"), m_fog_distance("fogDistance"), + m_animation_timer_vertex("animationTimer"), + m_animation_timer_pixel("animationTimer"), + m_day_light("dayLight"), m_eye_position_pixel("eyePosition"), + m_eye_position_vertex("eyePosition"), m_minimap_yaw("yawVec"), + m_camera_offset_pixel("cameraOffset"), + m_camera_offset_vertex("cameraOffset"), + m_base_texture("baseTexture"), m_normal_texture("normalTexture"), + m_texture_flags("textureFlags"), m_client(client) { g_settings->registerChangedCallback("enable_fog", settingsCallback, this); m_fog_enabled = g_settings->getBool("enable_fog"); @@ -491,11 +482,12 @@ public: ~GameGlobalShaderConstantSetter() { - g_settings->deregisterChangedCallback("enable_fog", settingsCallback, this); + g_settings->deregisterChangedCallback( + "enable_fog", settingsCallback, this); } - virtual void onSetConstants(video::IMaterialRendererServices *services, - bool is_highlevel) + virtual void onSetConstants( + video::IMaterialRendererServices *services, bool is_highlevel) { if (!is_highlevel) return; @@ -504,10 +496,10 @@ public: video::SColor bgcolor = m_sky->getBgColor(); video::SColorf bgcolorf(bgcolor); float bgcolorfa[4] = { - bgcolorf.r, - bgcolorf.g, - bgcolorf.b, - bgcolorf.a, + bgcolorf.r, + bgcolorf.g, + bgcolorf.b, + bgcolorf.a, }; m_sky_bg_color.set(bgcolorfa, services); @@ -522,10 +514,7 @@ public: u32 daynight_ratio = (float)m_client->getEnv().getDayNightRatio(); video::SColorf sunlight; get_sunlight_color(&sunlight, daynight_ratio); - float dnc[3] = { - sunlight.r, - sunlight.g, - sunlight.b }; + float dnc[3] = {sunlight.r, sunlight.g, sunlight.b}; m_day_light.set(dnc, services); u32 animation_timer = porting::getTimeMs() % 1000000; @@ -570,16 +559,13 @@ public: m_camera_offset_pixel.set(camera_offset_array, services); m_camera_offset_vertex.set(camera_offset_array, services); - SamplerLayer_t base_tex = 0, - normal_tex = 1, - flags_tex = 2; + SamplerLayer_t base_tex = 0, normal_tex = 1, flags_tex = 2; m_base_texture.set(&base_tex, services); m_normal_texture.set(&normal_tex, services); m_texture_flags.set(&flags_tex, services); } }; - class GameGlobalShaderConstantSetterFactory : public IShaderConstantSetterFactory { Sky *m_sky; @@ -587,16 +573,18 @@ class GameGlobalShaderConstantSetterFactory : public IShaderConstantSetterFactor f32 *m_fog_range; Client *m_client; std::vector created_nosky; -public: - GameGlobalShaderConstantSetterFactory(bool *force_fog_off, - f32 *fog_range, Client *client) : - m_sky(NULL), - m_force_fog_off(force_fog_off), - m_fog_range(fog_range), - m_client(client) - {} - void setSky(Sky *sky) { +public: + GameGlobalShaderConstantSetterFactory( + bool *force_fog_off, f32 *fog_range, Client *client) : + m_sky(NULL), + m_force_fog_off(force_fog_off), m_fog_range(fog_range), + m_client(client) + { + } + + void setSky(Sky *sky) + { m_sky = sky; for (GameGlobalShaderConstantSetter *ggscs : created_nosky) { ggscs->setSky(m_sky); @@ -604,7 +592,7 @@ public: created_nosky.clear(); } - virtual IShaderConstantSetter* create() + virtual IShaderConstantSetter *create() { GameGlobalShaderConstantSetter *scs = new GameGlobalShaderConstantSetter( m_sky, m_force_fog_off, m_fog_range, m_client); @@ -626,18 +614,19 @@ public: const float object_hit_delay = 0.2; -struct FpsControl { +struct FpsControl +{ u32 last_time, busy_time, sleep_time; }; - /* The reason the following structs are not anonymous structs within the * class is that they are not used by the majority of member functions and * many functions that do require objects of thse types do not modify them * (so they can be passed as a const qualified parameter) */ -struct GameRunData { +struct GameRunData +{ u16 dig_index; u16 new_playeritem; PointedThing pointed_old; @@ -673,27 +662,24 @@ struct ClientEventHandler void (Game::*handler)(ClientEvent *, CameraOrientation *); }; -class Game { +class Game +{ public: Game(); ~Game(); - bool startup(bool *kill, - InputHandler *input, - const GameStartData &game_params, - std::string &error_message, - bool *reconnect, + bool startup(bool *kill, InputHandler *input, const GameStartData &game_params, + std::string &error_message, bool *reconnect, ChatBackend *chat_backend); - void run(); void shutdown(); void extendedResourceCleanup(); // Basic initialisation - bool init(const std::string &map_dir, const std::string &address, - u16 port, const SubgameSpec &gamespec); + bool init(const std::string &map_dir, const std::string &address, u16 port, + const SubgameSpec &gamespec); bool initSound(); bool createSingleplayerServer(const std::string &map_dir, const SubgameSpec &gamespec, u16 port); @@ -703,8 +689,8 @@ public: bool initGui(); // Client connection - bool connectToServer(const GameStartData &start_data, - bool *connect_ok, bool *aborted); + bool connectToServer( + const GameStartData &start_data, bool *connect_ok, bool *aborted); bool getServerContent(bool *aborted); // Main loop @@ -713,7 +699,8 @@ public: bool checkConnection(); bool handleCallbacks(); void processQueues(); - void updateProfilers(const RunStats &stats, const FpsControl &draw_times, f32 dtime); + void updateProfilers( + const RunStats &stats, const FpsControl &draw_times, f32 dtime); void updateStats(RunStats *stats, const FpsControl &draw_times, f32 dtime); void updateProfilerGraphs(ProfilerGraph *graph); @@ -725,7 +712,7 @@ public: void dropSelectedItem(bool single_item = false); void openInventory(); void openEnderchest(); - void openConsole(float scale, const wchar_t *line=NULL); + void openConsole(float scale, const wchar_t *line = NULL); void toggleFreeMove(); void toggleFreeMoveAlt(); void togglePitchMove(); @@ -770,16 +757,19 @@ public: * @param[out] selected_object the selected object or * NULL if not found */ - PointedThing updatePointedThing( - const core::line3d &shootline, bool liquids_pointable, - bool look_for_object, const v3s16 &camera_offset); + PointedThing updatePointedThing(const core::line3d &shootline, + bool liquids_pointable, bool look_for_object, + const v3s16 &camera_offset); void handlePointingAtNothing(const ItemStack &playerItem); void handlePointingAtNode(const PointedThing &pointed, - const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime); - void handlePointingAtObject(const PointedThing &pointed, const ItemStack &playeritem, - const v3f &player_position, bool show_debug); + const ItemStack &selected_item, const ItemStack &hand_item, + f32 dtime); + void handlePointingAtObject(const PointedThing &pointed, + const ItemStack &playeritem, const v3f &player_position, + bool show_debug); void handleDigging(const PointedThing &pointed, const v3s16 &nodepos, - const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime); + const ItemStack &selected_item, const ItemStack &hand_item, + f32 dtime); void updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, const CameraOrientation &cam); @@ -791,23 +781,19 @@ public: static void freecamChangedCallback(const std::string &setting_name, void *data); static void settingChangedCallback(const std::string &setting_name, void *data); - static void updateAllMapBlocksCallback(const std::string &setting_name, void *data); + static void updateAllMapBlocksCallback( + const std::string &setting_name, void *data); void readSettings(); - inline bool isKeyDown(GameKeyType k) - { - return input->isKeyDown(k); - } - inline bool wasKeyDown(GameKeyType k) - { - return input->wasKeyDown(k); - } + inline bool isKeyDown(GameKeyType k) { return input->isKeyDown(k); } + inline bool wasKeyDown(GameKeyType k) { return input->wasKeyDown(k); } #ifdef __ANDROID__ void handleAndroidChatInput(); #endif - struct Flags { + struct Flags + { bool force_fog_off = false; bool disable_camera_update = false; }; @@ -818,12 +804,14 @@ public: // ClientEvent handlers void handleClientEvent_None(ClientEvent *event, CameraOrientation *cam); void handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam); - void handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam); + void handleClientEvent_PlayerForceMove( + ClientEvent *event, CameraOrientation *cam); void handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam); void handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam); - void handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam); - void handleClientEvent_HandleParticleEvent(ClientEvent *event, - CameraOrientation *cam); + void handleClientEvent_ShowLocalFormSpec( + ClientEvent *event, CameraOrientation *cam); + void handleClientEvent_HandleParticleEvent( + ClientEvent *event, CameraOrientation *cam); void handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam); void handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam); void handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam); @@ -831,15 +819,16 @@ public: void handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam); void handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam); void handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam); - void handleClientEvent_OverrideDayNigthRatio(ClientEvent *event, - CameraOrientation *cam); + void handleClientEvent_OverrideDayNigthRatio( + ClientEvent *event, CameraOrientation *cam); void handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam); void updateChat(f32 dtime, const v2u32 &screensize); - bool nodePlacement(const ItemDefinition &selected_def, const ItemStack &selected_item, - const v3s16 &nodepos, const v3s16 &neighbourpos, const PointedThing &pointed, - const NodeMetadata *meta); + bool nodePlacement(const ItemDefinition &selected_def, + const ItemStack &selected_item, const v3s16 &nodepos, + const v3s16 &neighbourpos, const PointedThing &pointed, + const NodeMetadata *meta); static const ClientEventHandler clientEventHandler[CLIENTEVENT_MAX]; InputHandler *input = nullptr; @@ -858,10 +847,10 @@ public: ISoundManager *sound = nullptr; bool sound_is_dummy = false; SoundMaker *soundmaker = nullptr; - + ChatBackend *chat_backend = nullptr; LogOutputBuffer m_chat_log_buf; - + EventManager *eventmgr = nullptr; QuicktuneShortcutter *quicktune = nullptr; bool registration_confirmation_shown = false; @@ -871,8 +860,8 @@ public: CheatMenu *m_cheat_menu = nullptr; MapDrawControl *draw_control = nullptr; Camera *camera = nullptr; - Clouds *clouds = nullptr; // Free using ->Drop() - Sky *sky = nullptr; // Free using ->Drop() + Clouds *clouds = nullptr; // Free using ->Drop() + Sky *sky = nullptr; // Free using ->Drop() Hud *hud = nullptr; Minimap *mapper = nullptr; @@ -914,11 +903,11 @@ public: bool m_cache_enable_fog; bool m_cache_enable_noclip; bool m_cache_enable_free_move; - f32 m_cache_mouse_sensitivity; - f32 m_cache_joystick_frustum_sensitivity; - f32 m_repeat_right_click_time; - f32 m_cache_cam_smoothing; - f32 m_cache_fog_start; + f32 m_cache_mouse_sensitivity; + f32 m_cache_joystick_frustum_sensitivity; + f32 m_repeat_right_click_time; + f32 m_cache_cam_smoothing; + f32 m_cache_fog_start; bool m_invert_mouse = false; bool m_first_loop_after_window_activation = false; @@ -926,8 +915,8 @@ public: bool m_does_lost_focus_pause_game = false; - CameraOrientation cam_view_target = { 0 }; - CameraOrientation cam_view = { 0 }; + CameraOrientation cam_view_target = {0}; + CameraOrientation cam_view = {0}; #ifdef __ANDROID__ bool m_cache_hold_aux1; @@ -936,9 +925,6 @@ public: }; extern Game *g_game; -void the_game(bool *kill, - InputHandler *input, - const GameStartData &start_data, - std::string &error_message, - ChatBackend &chat_backend, +void the_game(bool *kill, InputHandler *input, const GameStartData &start_data, + std::string &error_message, ChatBackend &chat_backend, bool *reconnect_requested); diff --git a/src/client/gameui.cpp b/src/client/gameui.cpp index a9057052e..b72fcd999 100644 --- a/src/client/gameui.cpp +++ b/src/client/gameui.cpp @@ -34,8 +34,7 @@ with this program; if not, write to the Free Software Foundation, Inc., inline static const char *yawToDirectionString(int yaw) { - static const char *direction[4] = - {"North +Z", "West -X", "South -Z", "East +X"}; + static const char *direction[4] = {"North +Z", "West -X", "South -Z", "East +X"}; yaw = wrapDegrees_0_360(yaw); yaw = (yaw + 45) % 360 / 90; @@ -46,56 +45,58 @@ inline static const char *yawToDirectionString(int yaw) GameUI::GameUI() { if (guienv && guienv->getSkin()) - m_statustext_initial_color = guienv->getSkin()->getColor(gui::EGDC_BUTTON_TEXT); + m_statustext_initial_color = + guienv->getSkin()->getColor(gui::EGDC_BUTTON_TEXT); else m_statustext_initial_color = video::SColor(255, 0, 0, 0); - } void GameUI::init() { - m_guitext_coords = gui::StaticText::add(guienv, L"", core::rect(0, 0, 0, 0), false, - false, guiroot); - + m_guitext_coords = gui::StaticText::add( + guienv, L"", core::rect(0, 0, 0, 0), false, false, guiroot); + // First line of debug text m_guitext = gui::StaticText::add(guienv, utf8_to_wide(PROJECT_NAME_C).c_str(), - core::rect(0, 0, 0, 0), false, false, guiroot); + core::rect(0, 0, 0, 0), false, false, guiroot); // Second line of debug text - m_guitext2 = gui::StaticText::add(guienv, L"", core::rect(0, 0, 0, 0), false, - false, guiroot); + m_guitext2 = gui::StaticText::add( + guienv, L"", core::rect(0, 0, 0, 0), false, false, guiroot); // At the middle of the screen // Object infos are shown in this m_guitext_info = gui::StaticText::add(guienv, L"", - core::rect(0, 0, 400, g_fontengine->getTextHeight() * 5 + 5) - + v2s32(100, 200), false, true, guiroot); + core::rect(0, 0, 400, + g_fontengine->getTextHeight() * 5 + 5) + + v2s32(100, 200), + false, true, guiroot); // Status text (displays info when showing and hiding GUI stuff, etc.) m_guitext_status = gui::StaticText::add(guienv, L"", - core::rect(0, 0, 0, 0), false, false, guiroot); + core::rect(0, 0, 0, 0), false, false, guiroot); m_guitext_status->setVisible(false); // Chat text m_guitext_chat = gui::StaticText::add(guienv, L"", core::rect(0, 0, 0, 0), - //false, false); // Disable word wrap as of now - false, true, guiroot); + // 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)); + 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"", - core::rect(0, 0, 0, 0), false, false, guiroot); + core::rect(0, 0, 0, 0), false, false, guiroot); m_guitext_profiler->setOverrideFont(g_fontengine->getFont( - g_fontengine->getDefaultFontSize() * 0.9f, FM_Mono)); + g_fontengine->getDefaultFontSize() * 0.9f, FM_Mono)); m_guitext_profiler->setVisible(false); } void GameUI::update(const RunStats &stats, Client *client, MapDrawControl *draw_control, - const CameraOrientation &cam, const PointedThing &pointed_old, - const GUIChatConsole *chat_console, float dtime) + const CameraOrientation &cam, const PointedThing &pointed_old, + const GUIChatConsole *chat_console, float dtime) { LocalPlayer *player = client->getEnv().getLocalPlayer(); v3f player_position = player->getPosition(); @@ -105,14 +106,15 @@ void GameUI::update(const RunStats &stats, Client *client, MapDrawControl *draw_ if (show_coords) { std::ostringstream os(std::ios_base::binary); - os << std::setprecision(1) << std::fixed - << (player_position.X / BS) - << ", " << (player_position.Y / BS) - << ", " << (player_position.Z / BS); + os << std::setprecision(1) << std::fixed << (player_position.X / BS) + << ", " << (player_position.Y / BS) << ", " + << (player_position.Z / BS); setStaticText(m_guitext_coords, utf8_to_wide(os.str()).c_str()); - m_guitext_coords->setRelativePosition(core::rect(5, screensize.Y - 5 - g_fontengine->getTextHeight(), screensize.X, screensize.Y)); + m_guitext_coords->setRelativePosition(core::rect(5, + screensize.Y - 5 - g_fontengine->getTextHeight(), + screensize.X, screensize.Y)); } - + m_guitext_coords->setVisible(show_coords); if (m_flags.show_debug) { @@ -121,23 +123,17 @@ void GameUI::update(const RunStats &stats, Client *client, MapDrawControl *draw_ u16 fps = 1.0 / stats.dtime_jitter.avg; std::ostringstream os(std::ios_base::binary); - os << std::fixed - << PROJECT_NAME_C " " << g_version_hash - << " | FPS: " << fps - << std::setprecision(0) - << " | drawtime: " << drawtime_avg << "ms" - << std::setprecision(1) - << " | dtime jitter: " - << (stats.dtime_jitter.max_fraction * 100.0) << "%" - << std::setprecision(1) - << " | view range: " - << (draw_control->range_all ? "All" : itos(draw_control->wanted_range)) - << std::setprecision(3) - << " | RTT: " << client->getRTT() << "s"; + os << std::fixed << PROJECT_NAME_C " " << g_version_hash + << " | FPS: " << fps << std::setprecision(0) + << " | drawtime: " << drawtime_avg << "ms" << std::setprecision(1) + << " | dtime jitter: " << (stats.dtime_jitter.max_fraction * 100.0) + << "%" << std::setprecision(1) << " | view range: " + << (draw_control->range_all ? "All" : itos(draw_control->wanted_range)) + << std::setprecision(3) << " | RTT: " << client->getRTT() << "s"; setStaticText(m_guitext, utf8_to_wide(os.str()).c_str()); - m_guitext->setRelativePosition(core::rect(5, 5, screensize.X, - 5 + g_fontengine->getTextHeight())); + m_guitext->setRelativePosition(core::rect( + 5, 5, screensize.X, 5 + g_fontengine->getTextHeight())); } // Finally set the guitext visible depending on the flag @@ -145,32 +141,31 @@ void GameUI::update(const RunStats &stats, Client *client, MapDrawControl *draw_ if (m_flags.show_debug) { std::ostringstream os(std::ios_base::binary); - os << std::setprecision(1) << std::fixed - << "pos: (" << (player_position.X / BS) - << ", " << (player_position.Y / BS) - << ", " << (player_position.Z / BS) - << ") | yaw: " << (wrapDegrees_0_360(cam.camera_yaw)) << "\xC2\xB0 " - << yawToDirectionString(cam.camera_yaw) - << " | pitch: " << (-wrapDegrees_180(cam.camera_pitch)) << "\xC2\xB0" - << " | seed: " << ((u64)client->getMapSeed()); + os << std::setprecision(1) << std::fixed << "pos: (" + << (player_position.X / BS) << ", " << (player_position.Y / BS) << ", " + << (player_position.Z / BS) + << ") | yaw: " << (wrapDegrees_0_360(cam.camera_yaw)) << "\xC2\xB0 " + << yawToDirectionString(cam.camera_yaw) + << " | pitch: " << (-wrapDegrees_180(cam.camera_pitch)) << "\xC2\xB0" + << " | seed: " << ((u64)client->getMapSeed()); if (pointed_old.type == POINTEDTHING_NODE) { ClientMap &map = client->getEnv().getClientMap(); const NodeDefManager *nodedef = client->getNodeDefManager(); MapNode n = map.getNode(pointed_old.node_undersurface); - if (n.getContent() != CONTENT_IGNORE && nodedef->get(n).name != "unknown") { + if (n.getContent() != CONTENT_IGNORE && + nodedef->get(n).name != "unknown") { os << ", pointed: " << nodedef->get(n).name - << ", param2: " << (u64) n.getParam2(); + << ", param2: " << (u64)n.getParam2(); } } setStaticText(m_guitext2, utf8_to_wide(os.str()).c_str()); m_guitext2->setRelativePosition(core::rect(5, - 5 + g_fontengine->getTextHeight(), screensize.X, - 5 + g_fontengine->getTextHeight() * 2 - )); + 5 + g_fontengine->getTextHeight(), screensize.X, + 5 + g_fontengine->getTextHeight() * 2)); } m_guitext2->setVisible(m_flags.show_debug); @@ -193,19 +188,22 @@ void GameUI::update(const RunStats &stats, Client *client, MapDrawControl *draw_ m_guitext_status->setVisible(!m_statustext.empty()); if (!m_statustext.empty()) { - s32 status_width = m_guitext_status->getTextWidth(); + s32 status_width = m_guitext_status->getTextWidth(); s32 status_height = m_guitext_status->getTextHeight(); s32 status_y = screensize.Y - 150; s32 status_x = (screensize.X - status_width) / 2; - m_guitext_status->setRelativePosition(core::rect(status_x , - status_y - status_height, status_x + status_width, status_y)); + m_guitext_status->setRelativePosition( + core::rect(status_x, status_y - status_height, + status_x + status_width, status_y)); // Fade out video::SColor final_color = m_statustext_initial_color; final_color.setAlpha(0); - video::SColor fade_color = m_statustext_initial_color.getInterpolated_quadratic( - m_statustext_initial_color, final_color, m_statustext_time / statustext_time_max); + video::SColor fade_color = + m_statustext_initial_color.getInterpolated_quadratic( + m_statustext_initial_color, final_color, + m_statustext_time / statustext_time_max); m_guitext_status->setOverrideColor(fade_color); m_guitext_status->enableOverrideColor(true); } @@ -235,18 +233,17 @@ void GameUI::setChatText(const EnrichedString &chat_text, u32 recent_chat_count) { // Update gui element size and position - + const v2u32 &window_size = RenderingEngine::get_instance()->getWindowSize(); - + s32 chat_y = window_size.Y - 150 - m_guitext_chat->getTextHeight(); if (m_flags.show_debug) chat_y += 2 * g_fontengine->getLineHeight(); - core::rect 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); + core::rect 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); @@ -258,24 +255,27 @@ void GameUI::updateProfiler() { if (m_profiler_current_page != 0) { std::ostringstream os(std::ios_base::binary); - os << " Profiler page " << (int)m_profiler_current_page << - ", elapsed: " << g_profiler->getElapsedMs() << " ms)" << std::endl; + os << " Profiler page " << (int)m_profiler_current_page + << ", elapsed: " << g_profiler->getElapsedMs() << " ms)" << std::endl; - int lines = g_profiler->print(os, m_profiler_current_page, m_profiler_max_page); + int lines = g_profiler->print( + os, m_profiler_current_page, m_profiler_max_page); ++lines; EnrichedString str(utf8_to_wide(os.str())); str.setBackground(video::SColor(120, 0, 0, 0)); setStaticText(m_guitext_profiler, str); - core::dimension2d size = m_guitext_profiler->getOverrideFont()-> - getDimension(str.c_str()); + core::dimension2d size = + m_guitext_profiler->getOverrideFont()->getDimension( + str.c_str()); core::position2di upper_left(6, 50); core::position2di lower_right = upper_left; lower_right.X += size.Width + 10; - lower_right.Y += size.Height; + lower_right.Y += size.Height; - m_guitext_profiler->setRelativePosition(core::rect(upper_left, lower_right)); + m_guitext_profiler->setRelativePosition( + core::rect(upper_left, lower_right)); } m_guitext_profiler->setVisible(m_profiler_current_page != 0); @@ -310,16 +310,17 @@ void GameUI::toggleHud() void GameUI::toggleProfiler() { - m_profiler_current_page = (m_profiler_current_page + 1) % (m_profiler_max_page + 1); + m_profiler_current_page = + (m_profiler_current_page + 1) % (m_profiler_max_page + 1); // FIXME: This updates the profiler with incomplete values updateProfiler(); if (m_profiler_current_page != 0) { wchar_t buf[255]; - const wchar_t* str = wgettext("Profiler shown (page %d of %d)"); - swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, - m_profiler_current_page, m_profiler_max_page); + const wchar_t *str = wgettext("Profiler shown (page %d of %d)"); + swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, m_profiler_current_page, + m_profiler_max_page); delete[] str; showStatusText(buf); } else { @@ -327,7 +328,6 @@ void GameUI::toggleProfiler() } } - void GameUI::deleteFormspec() { if (m_formspec) { diff --git a/src/client/gameui.h b/src/client/gameui.h index 8a1b5650d..2278ad1d6 100644 --- a/src/client/gameui.h +++ b/src/client/gameui.h @@ -85,7 +85,8 @@ public: const bool isChatVisible() { - return m_flags.show_chat && m_recent_chat_count != 0 && m_profiler_current_page == 0; + return m_flags.show_chat && m_recent_chat_count != 0 && + m_profiler_current_page == 0; } void setChatText(const EnrichedString &chat_text, u32 recent_chat_count); @@ -112,7 +113,7 @@ private: gui::IGUIStaticText *m_guitext = nullptr; // First line of debug text gui::IGUIStaticText *m_guitext2 = nullptr; // Second line of debug text gui::IGUIStaticText *m_guitext_coords = nullptr; - + gui::IGUIStaticText *m_guitext_info = nullptr; // At the middle of the screen std::wstring m_infotext; diff --git a/src/client/guiscalingfilter.cpp b/src/client/guiscalingfilter.cpp index 4262331bd..6b426fdf4 100644 --- a/src/client/guiscalingfilter.cpp +++ b/src/client/guiscalingfilter.cpp @@ -40,12 +40,13 @@ std::map g_txrCache; /* Manually insert an image into the cache, useful to avoid texture-to-image * conversion whenever we can intercept it. */ -void guiScalingCache(const io::path &key, video::IVideoDriver *driver, video::IImage *value) +void guiScalingCache( + const io::path &key, video::IVideoDriver *driver, video::IImage *value) { if (!g_settings->getBool("gui_scaling_filter")) return; - video::IImage *copied = driver->createImage(value->getColorFormat(), - value->getDimension()); + video::IImage *copied = driver->createImage( + value->getColorFormat(), value->getDimension()); value->copyTo(copied); g_imgCache[key] = copied; } @@ -69,9 +70,8 @@ void guiScalingCacheClear() * texture is not already cached, attempt to create it. Returns a pre-scaled texture, * or the original texture if unable to pre-scale it. */ -video::ITexture *guiScalingResizeCached(video::IVideoDriver *driver, - video::ITexture *src, const core::rect &srcrect, - const core::rect &destrect) +video::ITexture *guiScalingResizeCached(video::IVideoDriver *driver, video::ITexture *src, + const core::rect &srcrect, const core::rect &destrect) { if (src == NULL) return src; @@ -81,12 +81,9 @@ video::ITexture *guiScalingResizeCached(video::IVideoDriver *driver, // Calculate scaled texture name. char rectstr[200]; porting::mt_snprintf(rectstr, sizeof(rectstr), "%d:%d:%d:%d:%d:%d", - srcrect.UpperLeftCorner.X, - srcrect.UpperLeftCorner.Y, - srcrect.getWidth(), - srcrect.getHeight(), - destrect.getWidth(), - destrect.getHeight()); + srcrect.UpperLeftCorner.X, srcrect.UpperLeftCorner.Y, + srcrect.getWidth(), srcrect.getHeight(), destrect.getWidth(), + destrect.getHeight()); io::path origname = src->getName().getPath(); io::path scalename = origname + "@guiScalingFilter:" + rectstr; @@ -97,12 +94,12 @@ video::ITexture *guiScalingResizeCached(video::IVideoDriver *driver, // Try to find the texture converted to an image in the cache. // If the image was not found, try to extract it from the texture. - video::IImage* srcimg = g_imgCache[origname]; + video::IImage *srcimg = g_imgCache[origname]; if (srcimg == NULL) { if (!g_settings->getBool("gui_scaling_filter_txr2img")) return src; srcimg = driver->createImageFromData(src->getColorFormat(), - src->getSize(), src->lock(), false); + src->getSize(), src->lock(), false); src->unlock(); g_imgCache[origname] = srcimg; } @@ -111,7 +108,7 @@ video::ITexture *guiScalingResizeCached(video::IVideoDriver *driver, imageCleanTransparent(srcimg, 0); video::IImage *destimg = driver->createImage(src->getColorFormat(), core::dimension2d((u32)destrect.getWidth(), - (u32)destrect.getHeight())); + (u32)destrect.getHeight())); imageScaleNNAA(srcimg, srcrect, destimg); #if ENABLE_GLES @@ -120,7 +117,7 @@ video::ITexture *guiScalingResizeCached(video::IVideoDriver *driver, if (!hasNPotSupport()) { video::IImage *po2img = driver->createImage(src->getColorFormat(), core::dimension2d(npot2((u32)destrect.getWidth()), - npot2((u32)destrect.getHeight()))); + npot2((u32)destrect.getHeight()))); po2img->fill(video::SColor(0, 0, 0, 0)); destimg->copyTo(po2img); destimg->drop(); @@ -139,14 +136,15 @@ video::ITexture *guiScalingResizeCached(video::IVideoDriver *driver, /* Convenience wrapper for guiScalingResizeCached that accepts parameters that * are available at GUI imagebutton creation time. */ -video::ITexture *guiScalingImageButton(video::IVideoDriver *driver, - video::ITexture *src, s32 width, s32 height) +video::ITexture *guiScalingImageButton( + video::IVideoDriver *driver, video::ITexture *src, s32 width, s32 height) { if (src == NULL) return src; return guiScalingResizeCached(driver, src, - core::rect(0, 0, src->getSize().Width, src->getSize().Height), - core::rect(0, 0, width, height)); + core::rect(0, 0, src->getSize().Width, + src->getSize().Height), + core::rect(0, 0, width, height)); } /* Replacement for driver->draw2DImage() that uses the high-quality pre-scaled @@ -163,9 +161,10 @@ void draw2DImageFilterScaled(video::IVideoDriver *driver, video::ITexture *txr, return; // Correct source rect based on scaled image. - const core::rect mysrcrect = (scaled != txr) - ? core::rect(0, 0, destrect.getWidth(), destrect.getHeight()) - : srcrect; + const core::rect mysrcrect = + (scaled != txr) ? core::rect(0, 0, destrect.getWidth(), + destrect.getHeight()) + : srcrect; driver->draw2DImage(scaled, destrect, mysrcrect, cliprect, colors, usealpha); } @@ -174,11 +173,13 @@ void draw2DImage9Slice(video::IVideoDriver *driver, video::ITexture *texture, const core::rect &rect, const core::rect &middle, const core::rect *cliprect) { - const video::SColor color(255,255,255,255); - const video::SColor colors[] = {color,color,color,color}; + const video::SColor color(255, 255, 255, 255); + const video::SColor colors[] = {color, color, color, color}; auto originalSize = texture->getOriginalSize(); - core::vector2di lowerRightOffset = core::vector2di(originalSize.Width, originalSize.Height) - middle.LowerRightCorner; + core::vector2di lowerRightOffset = + core::vector2di(originalSize.Width, originalSize.Height) - + middle.LowerRightCorner; for (int y = 0; y < 3; ++y) { for (int x = 0; x < 3; ++x) { @@ -187,7 +188,8 @@ void draw2DImage9Slice(video::IVideoDriver *driver, video::ITexture *texture, switch (x) { case 0: - dest.LowerRightCorner.X = rect.UpperLeftCorner.X + middle.UpperLeftCorner.X; + dest.LowerRightCorner.X = rect.UpperLeftCorner.X + + middle.UpperLeftCorner.X; src.LowerRightCorner.X = middle.UpperLeftCorner.X; break; @@ -199,14 +201,16 @@ void draw2DImage9Slice(video::IVideoDriver *driver, video::ITexture *texture, break; case 2: - dest.UpperLeftCorner.X = rect.LowerRightCorner.X - lowerRightOffset.X; + dest.UpperLeftCorner.X = rect.LowerRightCorner.X - + lowerRightOffset.X; src.UpperLeftCorner.X = middle.LowerRightCorner.X; break; } switch (y) { case 0: - dest.LowerRightCorner.Y = rect.UpperLeftCorner.Y + middle.UpperLeftCorner.Y; + dest.LowerRightCorner.Y = rect.UpperLeftCorner.Y + + middle.UpperLeftCorner.Y; src.LowerRightCorner.Y = middle.UpperLeftCorner.Y; break; @@ -218,12 +222,14 @@ void draw2DImage9Slice(video::IVideoDriver *driver, video::ITexture *texture, break; case 2: - dest.UpperLeftCorner.Y = rect.LowerRightCorner.Y - lowerRightOffset.Y; + dest.UpperLeftCorner.Y = rect.LowerRightCorner.Y - + lowerRightOffset.Y; src.UpperLeftCorner.Y = middle.LowerRightCorner.Y; break; } - draw2DImageFilterScaled(driver, texture, dest, src, cliprect, colors, true); + draw2DImageFilterScaled(driver, texture, dest, src, cliprect, + colors, true); } } } diff --git a/src/client/guiscalingfilter.h b/src/client/guiscalingfilter.h index b703d91f0..703cbfb93 100644 --- a/src/client/guiscalingfilter.h +++ b/src/client/guiscalingfilter.h @@ -23,7 +23,8 @@ with this program; if not, write to the Free Software Foundation, Inc., /* Manually insert an image into the cache, useful to avoid texture-to-image * conversion whenever we can intercept it. */ -void guiScalingCache(const io::path &key, video::IVideoDriver *driver, video::IImage *value); +void guiScalingCache( + const io::path &key, video::IVideoDriver *driver, video::IImage *value); // Manually clear the cache, e.g. when switching to different worlds. void guiScalingCacheClear(); @@ -38,16 +39,16 @@ video::ITexture *guiScalingResizeCached(video::IVideoDriver *driver, video::ITex /* Convenience wrapper for guiScalingResizeCached that accepts parameters that * are available at GUI imagebutton creation time. */ -video::ITexture *guiScalingImageButton(video::IVideoDriver *driver, video::ITexture *src, - s32 width, s32 height); +video::ITexture *guiScalingImageButton( + video::IVideoDriver *driver, video::ITexture *src, s32 width, s32 height); /* Replacement for driver->draw2DImage() that uses the high-quality pre-scaled * texture, if configured. */ void draw2DImageFilterScaled(video::IVideoDriver *driver, video::ITexture *txr, const core::rect &destrect, const core::rect &srcrect, - const core::rect *cliprect = 0, const video::SColor *const colors = 0, - bool usealpha = false); + const core::rect *cliprect = 0, + const video::SColor *const colors = 0, bool usealpha = false); /* * 9-slice / segment drawing diff --git a/src/client/hud.cpp b/src/client/hud.cpp index 2b347c1e0..db5c5ef9f 100644 --- a/src/client/hud.cpp +++ b/src/client/hud.cpp @@ -47,16 +47,16 @@ with this program; if not, write to the Free Software Foundation, Inc., Hud::Hud(gui::IGUIEnvironment *guienv, Client *client, LocalPlayer *player, Inventory *inventory) { - driver = RenderingEngine::get_video_driver(); - this->guienv = guienv; - this->client = client; - this->player = player; - this->inventory = inventory; + driver = RenderingEngine::get_video_driver(); + this->guienv = guienv; + this->client = client; + this->player = 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_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; m_padding = m_hotbar_imagesize / 12; @@ -98,16 +98,19 @@ Hud::Hud(gui::IGUIEnvironment *guienv, Client *client, LocalPlayer *player, if (g_settings->getBool("enable_shaders")) { IShaderSource *shdrsrc = client->getShaderSource(); - u16 shader_id = shdrsrc->getShader( - m_mode == HIGHLIGHT_HALO ? "selection_shader" : "default_shader", 1, 1); - m_selection_material.MaterialType = shdrsrc->getShaderInfo(shader_id).material; + u16 shader_id = shdrsrc->getShader(m_mode == HIGHLIGHT_HALO + ? "selection_shader" + : "default_shader", + 1, 1); + m_selection_material.MaterialType = + shdrsrc->getShaderInfo(shader_id).material; } else { m_selection_material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; } if (m_mode == HIGHLIGHT_BOX) { m_selection_material.Thickness = - rangelim(g_settings->getS16("selectionbox_width"), 1, 5); + rangelim(g_settings->getS16("selectionbox_width"), 1, 5); } else if (m_mode == HIGHLIGHT_HALO) { m_selection_material.setTexture(0, tsrc->getTextureForMesh("halo.png")); m_selection_material.setFlag(video::EMF_BACK_FACE_CULLING, true); @@ -122,51 +125,50 @@ Hud::~Hud() m_selection_mesh->drop(); } -void Hud::drawItem(const ItemStack &item, const core::rect& rect, - bool selected) +void Hud::drawItem(const ItemStack &item, const core::rect &rect, bool selected) { if (selected) { /* draw hihlighting around selected item */ if (use_hotbar_selected_image) { core::rect imgrect2 = rect; - imgrect2.UpperLeftCorner.X -= (m_padding*2); - imgrect2.UpperLeftCorner.Y -= (m_padding*2); - imgrect2.LowerRightCorner.X += (m_padding*2); - imgrect2.LowerRightCorner.Y += (m_padding*2); - video::ITexture *texture = tsrc->getTexture(hotbar_selected_image); - core::dimension2di imgsize(texture->getOriginalSize()); + imgrect2.UpperLeftCorner.X -= (m_padding * 2); + imgrect2.UpperLeftCorner.Y -= (m_padding * 2); + imgrect2.LowerRightCorner.X += (m_padding * 2); + imgrect2.LowerRightCorner.Y += (m_padding * 2); + video::ITexture *texture = + tsrc->getTexture(hotbar_selected_image); + core::dimension2di imgsize(texture->getOriginalSize()); draw2DImageFilterScaled(driver, texture, imgrect2, - core::rect(core::position2d(0,0), imgsize), + core::rect(core::position2d(0, 0), + imgsize), NULL, hbar_colors, true); } else { - video::SColor c_outside(255,255,0,0); - //video::SColor c_outside(255,0,0,0); - //video::SColor c_inside(255,192,192,192); + video::SColor c_outside(255, 255, 0, 0); + // video::SColor c_outside(255,0,0,0); + // video::SColor c_inside(255,192,192,192); s32 x1 = rect.UpperLeftCorner.X; s32 y1 = rect.UpperLeftCorner.Y; s32 x2 = rect.LowerRightCorner.X; s32 y2 = rect.LowerRightCorner.Y; // Black base borders driver->draw2DRectangle(c_outside, - core::rect( - v2s32(x1 - m_padding, y1 - m_padding), - v2s32(x2 + m_padding, y1) - ), NULL); + core::rect(v2s32(x1 - m_padding, + y1 - m_padding), + v2s32(x2 + m_padding, y1)), + NULL); driver->draw2DRectangle(c_outside, - core::rect( - v2s32(x1 - m_padding, y2), - v2s32(x2 + m_padding, y2 + m_padding) - ), NULL); + core::rect(v2s32(x1 - m_padding, y2), + v2s32(x2 + m_padding, + y2 + m_padding)), + NULL); driver->draw2DRectangle(c_outside, - core::rect( - v2s32(x1 - m_padding, y1), - v2s32(x1, y2) - ), NULL); + core::rect(v2s32(x1 - m_padding, y1), + v2s32(x1, y2)), + NULL); driver->draw2DRectangle(c_outside, - core::rect( - v2s32(x2, y1), - v2s32(x2 + m_padding, y2) - ), NULL); + core::rect(v2s32(x2, y1), + v2s32(x2 + m_padding, y2)), + NULL); /*// Light inside borders driver->draw2DRectangle(c_inside, core::rect( @@ -195,11 +197,11 @@ void Hud::drawItem(const ItemStack &item, const core::rect& rect, video::SColor bgcolor2(128, 0, 0, 0); if (!use_hotbar_image) driver->draw2DRectangle(bgcolor2, rect, NULL); - drawItemStack(driver, g_fontengine->getFont(), item, rect, NULL, - client, selected ? IT_ROT_SELECTED : IT_ROT_NONE); + drawItemStack(driver, g_fontengine->getFont(), item, rect, NULL, client, + selected ? IT_ROT_SELECTED : IT_ROT_NONE); } -//NOTE: selectitem = 0 -> no selected; selectitem 1-based +// NOTE: selectitem = 0 -> no selected; selectitem 1-based void Hud::drawItems(v2s32 upperleftpos, v2s32 screen_offset, s32 itemcount, s32 inv_offset, InventoryList *mainlist, u16 selectitem, u16 direction) { @@ -208,8 +210,8 @@ void Hud::drawItems(v2s32 upperleftpos, v2s32 screen_offset, s32 itemcount, g_touchscreengui->resetHud(); #endif - s32 height = m_hotbar_imagesize + m_padding * 2; - s32 width = (itemcount - inv_offset) * (m_hotbar_imagesize + m_padding * 2); + s32 height = m_hotbar_imagesize + m_padding * 2; + s32 width = (itemcount - inv_offset) * (m_hotbar_imagesize + m_padding * 2); if (direction == HUD_DIR_TOP_BOTTOM || direction == HUD_DIR_BOTTOM_TOP) { s32 tmp = height; @@ -235,14 +237,14 @@ void Hud::drawItems(v2s32 upperleftpos, v2s32 screen_offset, s32 itemcount, // draw customized item background if (use_hotbar_image) { - core::rect imgrect2(-m_padding/2, -m_padding/2, - width+m_padding/2, height+m_padding/2); + core::rect imgrect2(-m_padding / 2, -m_padding / 2, + width + m_padding / 2, height + m_padding / 2); core::rect rect2 = imgrect2 + pos; video::ITexture *texture = tsrc->getTexture(hotbar_image); core::dimension2di imgsize(texture->getOriginalSize()); draw2DImageFilterScaled(driver, texture, rect2, - core::rect(core::position2d(0,0), imgsize), - NULL, hbar_colors, true); + core::rect(core::position2d(0, 0), imgsize), + NULL, hbar_colors, true); } // Draw items @@ -253,20 +255,25 @@ void Hud::drawItems(v2s32 upperleftpos, v2s32 screen_offset, s32 itemcount, v2s32 steppos; switch (direction) { case HUD_DIR_RIGHT_LEFT: - steppos = v2s32(-(m_padding + (i - inv_offset) * fullimglen), m_padding); + steppos = v2s32(-(m_padding + (i - inv_offset) * fullimglen), + m_padding); break; case HUD_DIR_TOP_BOTTOM: - steppos = v2s32(m_padding, m_padding + (i - inv_offset) * fullimglen); + steppos = v2s32(m_padding, + m_padding + (i - inv_offset) * fullimglen); break; case HUD_DIR_BOTTOM_TOP: - steppos = v2s32(m_padding, -(m_padding + (i - inv_offset) * fullimglen)); + steppos = v2s32(m_padding, + -(m_padding + (i - inv_offset) * fullimglen)); break; default: - steppos = v2s32(m_padding + (i - inv_offset) * fullimglen, m_padding); + steppos = v2s32(m_padding + (i - inv_offset) * fullimglen, + m_padding); break; } - drawItem(mainlist->getItem(i), (imgrect + pos + steppos), (i + 1) == selectitem); + drawItem(mainlist->getItem(i), (imgrect + pos + steppos), + (i + 1) == selectitem); #ifdef HAVE_TOUCHSCREENGUI if (g_touchscreengui) @@ -275,21 +282,22 @@ 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. +// 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(); + 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 }; + 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]); + 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; @@ -298,7 +306,7 @@ bool Hud::calculateScreenPos(const v3s16 &camera_offset, HudElement *e, v2s32 *p void Hud::drawLuaElements(const v3s16 &camera_offset) { u32 text_height = g_fontengine->getTextHeight(); - irr::gui::IGUIFont* font = g_fontengine->getFont(); + irr::gui::IGUIFont *font = g_fontengine->getFont(); // Reorder elements by z_index std::vector ids; @@ -318,122 +326,150 @@ void Hud::drawLuaElements(const v3s16 &camera_offset) for (size_t i : ids) { HudElement *e = player->getHud(i); - v2s32 pos(floor(e->pos.X * (float) m_screensize.X + 0.5), - floor(e->pos.Y * (float) m_screensize.Y + 0.5)); + 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_TEXT: { - irr::gui::IGUIFont *textfont = font; - unsigned int font_size = g_fontengine->getDefaultFontSize(); + 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 (e->size.X > 0) + font_size *= e->size.X; - if (font_size != g_fontengine->getDefaultFontSize()) - textfont = g_fontengine->getFont(font_size); + 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); - std::wstring text = unescape_translate(utf8_to_wide(e->text)); - core::dimension2d textsize = textfont->getDimension(text.c_str()); + video::SColor color(255, (e->number >> 16) & 0xFF, + (e->number >> 8) & 0xFF, (e->number >> 0) & 0xFF); + std::wstring text = unescape_translate(utf8_to_wide(e->text)); + core::dimension2d 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()); + // 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)); - core::rect 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); + v2s32 offset((e->align.X - 1.0) * (textsize.Width / 2), + (e->align.Y - 1.0) * (textsize.Height / 2)); + core::rect 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 + 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->text2, - e->number, e->item, offs, e->size); - break; } - case HUD_ELEM_INVENTORY: { - InventoryList *inv = inventory->getList(e->text); - drawItems(pos, v2s32(e->offset.X, e->offset.Y), e->number, 0, - inv, e->item, e->dir); - break; } - case HUD_ELEM_WAYPOINT: { - if (!calculateScreenPos(camera_offset, e, &pos)) - break; - 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); - std::wstring text = unescape_translate(utf8_to_wide(e->name)); - 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 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; + { + textfont->draw(text.c_str(), size + pos + offset + offs, + color); } - case HUD_ELEM_IMAGE: { - video::ITexture *texture = tsrc->getTexture(e->text); - if (!texture) - continue; + break; + } + case HUD_ELEM_STATBAR: { + v2s32 offs(e->offset.X, e->offset.Y); + 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); + drawItems(pos, v2s32(e->offset.X, e->offset.Y), e->number, 0, inv, + e->item, e->dir); + break; + } + case HUD_ELEM_WAYPOINT: { + if (!calculateScreenPos(camera_offset, e, &pos)) + break; + 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); + std::wstring text = unescape_translate(utf8_to_wide(e->name)); + 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; - 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 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(core::position2d(0,0), imgsize), + core::rect 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 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(core::position2d(0, 0), + imgsize), NULL, colors, true); - break; } - default: - infostream << "Hud::drawLuaElements: ignoring drawform " << e->type << - " of hud element ID " << i << " due to unrecognized type" << std::endl; + break; + } + default: + infostream << "Hud::drawLuaElements: ignoring drawform " + << e->type << " of hud element ID " << i + << " due to unrecognized type" << std::endl; } } } - -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) +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}; @@ -452,12 +488,12 @@ void Hud::drawStatbar(v2s32 pos, u16 corner, u16 drawdir, if (size == v2s32()) { dstd = srcd; dstd.Height *= m_scale_factor; - dstd.Width *= m_scale_factor; + dstd.Width *= m_scale_factor; offset.X *= m_scale_factor; offset.Y *= m_scale_factor; } else { dstd.Height = size.Y * m_scale_factor; - dstd.Width = size.X * m_scale_factor; + dstd.Width = size.X * m_scale_factor; offset.X *= m_scale_factor; offset.Y *= m_scale_factor; } @@ -470,29 +506,27 @@ void Hud::drawStatbar(v2s32 pos, u16 corner, u16 drawdir, v2s32 steppos; switch (drawdir) { - case HUD_DIR_RIGHT_LEFT: - steppos = v2s32(-1, 0); - break; - case HUD_DIR_TOP_BOTTOM: - steppos = v2s32(0, 1); - break; - case HUD_DIR_BOTTOM_TOP: - steppos = v2s32(0, -1); - break; - default: - // From left to right - steppos = v2s32(1, 0); - break; + case HUD_DIR_RIGHT_LEFT: + steppos = v2s32(-1, 0); + break; + case HUD_DIR_TOP_BOTTOM: + steppos = v2s32(0, 1); + break; + case HUD_DIR_BOTTOM_TOP: + steppos = v2s32(0, -1); + break; + default: + // From left to right + steppos = v2s32(1, 0); + break; } - auto calculate_clipping_rect = [] (core::dimension2di src, - v2s32 steppos) -> core::rect { - + auto calculate_clipping_rect = [](core::dimension2di src, + v2s32 steppos) -> core::rect { // Create basic rectangle core::rect rect(0, 0, - src.Width - std::abs(steppos.X) * src.Width / 2, - src.Height - std::abs(steppos.Y) * src.Height / 2 - ); + 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); @@ -507,8 +541,8 @@ void Hud::drawStatbar(v2s32 pos, u16 corner, u16 drawdir, if (count % 2 == 1) { // Need to draw halves: Calculate rectangles - srchalfrect = calculate_clipping_rect(srcd, steppos); - dsthalfrect = calculate_clipping_rect(dstd, steppos); + 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); } @@ -522,20 +556,19 @@ void Hud::drawStatbar(v2s32 pos, u16 corner, u16 drawdir, core::rect 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) { // Draw half a texture - draw2DImageFilterScaled(driver, stat_texture, - dsthalfrect + p, srchalfrect, NULL, colors, true); + 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); + draw2DImageFilterScaled(driver, stat_texture_bg, dsthalfrect2 + p, + srchalfrect2, NULL, colors, true); p += steppos; } } @@ -552,28 +585,26 @@ void Hud::drawStatbar(v2s32 pos, u16 corner, u16 drawdir, core::rect dstrect(0, 0, dstd.Width, dstd.Height); dstrect += p; - draw2DImageFilterScaled(driver, stat_texture_bg, - dstrect, srcrect, + 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); + draw2DImageFilterScaled(driver, stat_texture_bg, dsthalfrect + p, + srchalfrect, NULL, colors, true); } } } - -void Hud::drawHotbar(u16 playeritem) { +void Hud::drawHotbar(u16 playeritem) +{ v2s32 centerlowerpos(m_displaycenter.X, m_screensize.Y); InventoryList *mainlist = inventory->getList("main"); if (mainlist == NULL) { - //silently ignore this we may not be initialized completely + // silently ignore this we may not be initialized completely return; } @@ -582,49 +613,57 @@ 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); + drawItems(pos, v2s32(0, 0), hotbar_itemcount, 0, mainlist, + playeritem + 1, 0); } } else { - pos.X += width/4; + pos.X += width / 4; v2s32 secondpos = pos; pos = pos - v2s32(0, m_hotbar_imagesize + m_padding); if (player->hud_flags & HUD_FLAG_HOTBAR_VISIBLE) { - drawItems(pos, v2s32(0, 0), hotbar_itemcount / 2, 0, - mainlist, playeritem + 1, 0); + drawItems(pos, v2s32(0, 0), hotbar_itemcount / 2, 0, mainlist, + playeritem + 1, 0); drawItems(secondpos, v2s32(0, 0), hotbar_itemcount, - hotbar_itemcount / 2, mainlist, playeritem + 1, 0); + hotbar_itemcount / 2, mainlist, playeritem + 1, + 0); } } } - void Hud::drawCrosshair() { if (pointing_at_object) { if (use_object_crosshair_image) { - video::ITexture *object_crosshair = tsrc->getTexture("object_crosshair.png"); - v2u32 size = object_crosshair->getOriginalSize(); + video::ITexture *object_crosshair = + tsrc->getTexture("object_crosshair.png"); + v2u32 size = object_crosshair->getOriginalSize(); v2s32 lsize = v2s32(m_displaycenter.X - (size.X / 2), m_displaycenter.Y - (size.Y / 2)); driver->draw2DImage(object_crosshair, lsize, - core::rect(0, 0, size.X, size.Y), - nullptr, crosshair_argb, true); + core::rect(0, 0, size.X, size.Y), nullptr, + crosshair_argb, true); } else { driver->draw2DLine( - m_displaycenter - v2s32(OBJECT_CROSSHAIR_LINE_SIZE, - OBJECT_CROSSHAIR_LINE_SIZE), - m_displaycenter + v2s32(OBJECT_CROSSHAIR_LINE_SIZE, - OBJECT_CROSSHAIR_LINE_SIZE), crosshair_argb); + m_displaycenter - + v2s32(OBJECT_CROSSHAIR_LINE_SIZE, + OBJECT_CROSSHAIR_LINE_SIZE), + m_displaycenter + + v2s32(OBJECT_CROSSHAIR_LINE_SIZE, + OBJECT_CROSSHAIR_LINE_SIZE), + crosshair_argb); driver->draw2DLine( - m_displaycenter + v2s32(OBJECT_CROSSHAIR_LINE_SIZE, - -OBJECT_CROSSHAIR_LINE_SIZE), - m_displaycenter + v2s32(-OBJECT_CROSSHAIR_LINE_SIZE, - OBJECT_CROSSHAIR_LINE_SIZE), crosshair_argb); + m_displaycenter + + v2s32(OBJECT_CROSSHAIR_LINE_SIZE, + -OBJECT_CROSSHAIR_LINE_SIZE), + m_displaycenter + + v2s32(-OBJECT_CROSSHAIR_LINE_SIZE, + OBJECT_CROSSHAIR_LINE_SIZE), + crosshair_argb); } return; @@ -632,17 +671,19 @@ void Hud::drawCrosshair() if (use_crosshair_image) { video::ITexture *crosshair = tsrc->getTexture("crosshair.png"); - v2u32 size = crosshair->getOriginalSize(); + v2u32 size = crosshair->getOriginalSize(); v2s32 lsize = v2s32(m_displaycenter.X - (size.X / 2), m_displaycenter.Y - (size.Y / 2)); driver->draw2DImage(crosshair, lsize, - core::rect(0, 0, size.X, size.Y), - nullptr, crosshair_argb, true); + core::rect(0, 0, size.X, size.Y), nullptr, + crosshair_argb, true); } else { driver->draw2DLine(m_displaycenter - v2s32(CROSSHAIR_LINE_SIZE, 0), - m_displaycenter + v2s32(CROSSHAIR_LINE_SIZE, 0), crosshair_argb); + m_displaycenter + v2s32(CROSSHAIR_LINE_SIZE, 0), + crosshair_argb); driver->draw2DLine(m_displaycenter - v2s32(0, CROSSHAIR_LINE_SIZE), - m_displaycenter + v2s32(0, CROSSHAIR_LINE_SIZE), crosshair_argb); + m_displaycenter + v2s32(0, CROSSHAIR_LINE_SIZE), + crosshair_argb); } } @@ -659,10 +700,11 @@ void Hud::drawSelectionMesh() // Draw 3D selection boxes video::SMaterial oldmaterial = driver->getMaterial2D(); driver->setMaterial(m_selection_material); - for (auto & selection_box : m_selection_boxes) { - aabb3f box = aabb3f( - selection_box.MinEdge + m_selection_pos_with_offset, - selection_box.MaxEdge + m_selection_pos_with_offset); + for (auto &selection_box : m_selection_boxes) { + aabb3f box = aabb3f(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); @@ -679,12 +721,12 @@ void Hud::drawSelectionMesh() driver->setMaterial(m_selection_material); setMeshColor(m_selection_mesh, m_selection_mesh_color); video::SColor face_color(0, - MYMIN(255, m_selection_mesh_color.getRed() * 1.5), - MYMIN(255, m_selection_mesh_color.getGreen() * 1.5), - MYMIN(255, m_selection_mesh_color.getBlue() * 1.5)); - setMeshColorByNormal(m_selection_mesh, m_selected_face_normal, - face_color); - scene::IMesh* mesh = cloneMesh(m_selection_mesh); + MYMIN(255, m_selection_mesh_color.getRed() * 1.5), + MYMIN(255, m_selection_mesh_color.getGreen() * 1.5), + MYMIN(255, m_selection_mesh_color.getBlue() * 1.5)); + setMeshColorByNormal( + m_selection_mesh, m_selected_face_normal, face_color); + scene::IMesh *mesh = cloneMesh(m_selection_mesh); translateMesh(mesh, m_selection_pos_with_offset); u32 mc = m_selection_mesh->getMeshBufferCount(); for (u32 i = 0; i < mc; i++) { @@ -715,14 +757,8 @@ void Hud::updateSelectionMesh(const v3s16 &camera_offset) // New pointed object, create new mesh. // Texture UV coordinates for selection boxes - static f32 texture_uv[24] = { - 0,0,1,1, - 0,0,1,1, - 0,0,1,1, - 0,0,1,1, - 0,0,1,1, - 0,0,1,1 - }; + static f32 texture_uv[24] = {0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, + 1, 1, 0, 0, 1, 1}; // Use single halo box instead of multiple overlapping boxes. // Temporary solution - problem can be solved with multiple @@ -737,37 +773,34 @@ void Hud::updateSelectionMesh(const v3s16 &camera_offset) } m_halo_boxes.push_back(halo_box); - m_selection_mesh = convertNodeboxesToMesh( - m_halo_boxes, texture_uv, 0.5); + m_selection_mesh = convertNodeboxesToMesh(m_halo_boxes, texture_uv, 0.5); } -void Hud::resizeHotbar() { +void Hud::resizeHotbar() +{ const v2u32 &window_size = RenderingEngine::get_instance()->getWindowSize(); if (m_screensize != window_size) { - m_hotbar_imagesize = floor(HOTBAR_IMAGE_SIZE * - RenderingEngine::getDisplayDensity() + 0.5); + m_hotbar_imagesize = floor( + HOTBAR_IMAGE_SIZE * RenderingEngine::getDisplayDensity() + + 0.5); m_hotbar_imagesize *= m_hud_scaling; m_padding = m_hotbar_imagesize / 12; m_screensize = window_size; - m_displaycenter = v2s32(m_screensize.X/2,m_screensize.Y/2); + m_displaycenter = v2s32(m_screensize.X / 2, m_screensize.Y / 2); } } -struct MeshTimeInfo { +struct MeshTimeInfo +{ u64 time; scene::IMesh *mesh = nullptr; }; -void drawItemStack( - video::IVideoDriver *driver, - gui::IGUIFont *font, - const ItemStack &item, - const core::rect &rect, - const core::rect *clip, - Client *client, - ItemRotationKind rotation_kind, - const v3s16 &angle, +void drawItemStack(video::IVideoDriver *driver, gui::IGUIFont *font, + const ItemStack &item, const core::rect &rect, + const core::rect *clip, Client *client, + ItemRotationKind rotation_kind, const v3s16 &angle, const v3s16 &rotation_speed) { static MeshTimeInfo rotation_time_infos[IT_ROT_NONE]; @@ -792,7 +825,9 @@ void drawItemStack( ti.mesh = mesh; ti.time = porting::getTimeMs(); } else { - delta = porting::getDeltaMs(ti.time, porting::getTimeMs()) % 100000; + delta = porting::getDeltaMs( + ti.time, porting::getTimeMs()) % + 100000; } } core::rect oldViewPort = driver->getViewPort(); @@ -807,18 +842,26 @@ void drawItemStack( core::matrix4 ViewMatrix; ViewMatrix.buildProjectionMatrixOrthoLH( - 2.0f * viewrect.getWidth() / rect.getWidth(), - 2.0f * viewrect.getHeight() / rect.getHeight(), - -1.0f, - 100.0f); + 2.0f * viewrect.getWidth() / rect.getWidth(), + 2.0f * viewrect.getHeight() / rect.getHeight(), -1.0f, + 100.0f); ViewMatrix.setTranslation(core::vector3df( - 1.0f * (rect.LowerRightCorner.X + rect.UpperLeftCorner.X - - viewrect.LowerRightCorner.X - viewrect.UpperLeftCorner.X) / - viewrect.getWidth(), - 1.0f * (viewrect.LowerRightCorner.Y + viewrect.UpperLeftCorner.Y - - rect.LowerRightCorner.Y - rect.UpperLeftCorner.Y) / - viewrect.getHeight(), - 0.0f)); + 1.0f * + (rect.LowerRightCorner.X + + rect.UpperLeftCorner.X - + viewrect.LowerRightCorner + .X - + viewrect.UpperLeftCorner + .X) / + viewrect.getWidth(), + 1.0f * + (viewrect.LowerRightCorner.Y + + viewrect.UpperLeftCorner + .Y - + rect.LowerRightCorner.Y - + rect.UpperLeftCorner.Y) / + viewrect.getHeight(), + 0.0f)); driver->setTransform(video::ETS_PROJECTION, ProjMatrix); driver->setTransform(video::ETS_VIEW, ViewMatrix); @@ -827,22 +870,20 @@ void drawItemStack( matrix.makeIdentity(); static thread_local bool enable_animations = - g_settings->getBool("inventory_items_animations"); + g_settings->getBool("inventory_items_animations"); if (enable_animations) { - float timer_f = (float) delta / 5000.f; + float timer_f = (float)delta / 5000.f; matrix.setRotationDegrees(v3f( - angle.X + rotation_speed.X * 3.60f * timer_f, - angle.Y + rotation_speed.Y * 3.60f * timer_f, - angle.Z + rotation_speed.Z * 3.60f * timer_f) - ); + angle.X + rotation_speed.X * 3.60f * timer_f, + angle.Y + rotation_speed.Y * 3.60f * timer_f, + angle.Z + rotation_speed.Z * 3.60f * timer_f)); } driver->setTransform(video::ETS_WORLD, matrix); driver->setViewPort(viewrect); - video::SColor basecolor = - client->idef()->getItemstackColor(item, client); + video::SColor basecolor = client->idef()->getItemstackColor(item, client); u32 mc = mesh->getMeshBufferCount(); for (u32 j = 0; j < mc; ++j) { @@ -878,10 +919,13 @@ void drawItemStack( if (def.type == ITEM_NODE && def.inventory_image.empty() && !def.inventory_overlay.empty()) { ITextureSource *tsrc = client->getTextureSource(); - video::ITexture *overlay_texture = tsrc->getTexture(def.inventory_overlay); - core::dimension2d dimens = overlay_texture->getOriginalSize(); + video::ITexture *overlay_texture = + tsrc->getTexture(def.inventory_overlay); + core::dimension2d dimens = + overlay_texture->getOriginalSize(); core::rect srcrect(0, 0, dimens.Width, dimens.Height); - draw2DImageFilterScaled(driver, overlay_texture, rect, srcrect, clip, 0, true); + draw2DImageFilterScaled(driver, overlay_texture, rect, srcrect, + clip, 0, true); } } @@ -891,17 +935,15 @@ void drawItemStack( float barpad_x = rect.getWidth() / 16; float barpad_y = rect.getHeight() / 16; - core::rect progressrect( - rect.UpperLeftCorner.X + barpad_x, - rect.LowerRightCorner.Y - barpad_y - barheight, - rect.LowerRightCorner.X - barpad_x, - rect.LowerRightCorner.Y - barpad_y); + core::rect progressrect(rect.UpperLeftCorner.X + barpad_x, + rect.LowerRightCorner.Y - barpad_y - barheight, + rect.LowerRightCorner.X - barpad_x, + rect.LowerRightCorner.Y - barpad_y); // Shrink progressrect by amount of tool damage float wear = item.wear / 65535.0f; - int progressmid = - wear * progressrect.UpperLeftCorner.X + - (1 - wear) * progressrect.LowerRightCorner.X; + int progressmid = wear * progressrect.UpperLeftCorner.X + + (1 - wear) * progressrect.LowerRightCorner.X; // Compute progressbar color // wear = 0.0: green @@ -933,11 +975,9 @@ void drawItemStack( v2s32 sdim(dim.X, dim.Y); core::rect rect2( - /*rect.UpperLeftCorner, - core::dimension2d(rect.getWidth(), 15)*/ - rect.LowerRightCorner - sdim, - sdim - ); + /*rect.UpperLeftCorner, + core::dimension2d(rect.getWidth(), 15)*/ + rect.LowerRightCorner - sdim, sdim); video::SColor bgcolor(128, 0, 0, 0); driver->draw2DRectangle(bgcolor, rect2, clip); @@ -947,15 +987,11 @@ void drawItemStack( } } -void drawItemStack( - video::IVideoDriver *driver, - gui::IGUIFont *font, - const ItemStack &item, - const core::rect &rect, - const core::rect *clip, - Client *client, +void drawItemStack(video::IVideoDriver *driver, gui::IGUIFont *font, + const ItemStack &item, const core::rect &rect, + const core::rect *clip, Client *client, ItemRotationKind rotation_kind) { drawItemStack(driver, font, item, rect, clip, client, rotation_kind, - v3s16(0, 0, 0), v3s16(0, 100, 0)); + v3s16(0, 0, 0), v3s16(0, 100, 0)); } diff --git a/src/client/hud.h b/src/client/hud.h index ba34d479d..818e3dea3 100644 --- a/src/client/hud.h +++ b/src/client/hud.h @@ -85,9 +85,9 @@ public: private: 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 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, @@ -100,7 +100,8 @@ private: v3s16 m_camera_offset; v2u32 m_screensize; v2s32 m_displaycenter; - s32 m_hotbar_imagesize; // Takes hud_scaling into account, updated by resizeHotbar() + s32 m_hotbar_imagesize; // Takes hud_scaling into account, updated by + // resizeHotbar() s32 m_padding; // Takes hud_scaling into account, updated by resizeHotbar() video::SColor hbar_colors[4]; @@ -132,22 +133,13 @@ enum ItemRotationKind IT_ROT_NONE, // Must be last, also serves as number }; -void drawItemStack(video::IVideoDriver *driver, - gui::IGUIFont *font, - const ItemStack &item, - const core::rect &rect, - const core::rect *clip, - Client *client, +void drawItemStack(video::IVideoDriver *driver, gui::IGUIFont *font, + const ItemStack &item, const core::rect &rect, + const core::rect *clip, Client *client, ItemRotationKind rotation_kind); -void drawItemStack( - video::IVideoDriver *driver, - gui::IGUIFont *font, - const ItemStack &item, - const core::rect &rect, - const core::rect *clip, - Client *client, - ItemRotationKind rotation_kind, - const v3s16 &angle, +void drawItemStack(video::IVideoDriver *driver, gui::IGUIFont *font, + const ItemStack &item, const core::rect &rect, + const core::rect *clip, Client *client, + ItemRotationKind rotation_kind, const v3s16 &angle, const v3s16 &rotation_speed); - diff --git a/src/client/imagefilters.cpp b/src/client/imagefilters.cpp index 0fa501410..a75e7f8d6 100644 --- a/src/client/imagefilters.cpp +++ b/src/client/imagefilters.cpp @@ -38,44 +38,45 @@ void imageCleanTransparent(video::IImage *src, u32 threshold) // Walk each pixel looking for fully transparent ones. // Note: loop y around x for better cache locality. for (u32 ctry = 0; ctry < dim.Height; ctry++) - for (u32 ctrx = 0; ctrx < dim.Width; ctrx++) { + for (u32 ctrx = 0; ctrx < dim.Width; ctrx++) { - // Ignore opaque pixels. - irr::video::SColor c = src->getPixel(ctrx, ctry); - if (c.getAlpha() > threshold) - continue; - - // Sample size and total weighted r, g, b values. - u32 ss = 0, sr = 0, sg = 0, sb = 0; - - // Walk each neighbor pixel (clipped to image bounds). - for (u32 sy = (ctry < 1) ? 0 : (ctry - 1); - sy <= (ctry + 1) && sy < dim.Height; sy++) - for (u32 sx = (ctrx < 1) ? 0 : (ctrx - 1); - sx <= (ctrx + 1) && sx < dim.Width; sx++) { - - // Ignore transparent pixels. - irr::video::SColor d = src->getPixel(sx, sy); - if (d.getAlpha() <= threshold) + // Ignore opaque pixels. + irr::video::SColor c = src->getPixel(ctrx, ctry); + if (c.getAlpha() > threshold) continue; - // Add RGB values weighted by alpha. - u32 a = d.getAlpha(); - ss += a; - sr += a * d.getRed(); - sg += a * d.getGreen(); - sb += a * d.getBlue(); - } + // Sample size and total weighted r, g, b values. + u32 ss = 0, sr = 0, sg = 0, sb = 0; - // If we found any neighbor RGB data, set pixel to average - // weighted by alpha. - if (ss > 0) { - c.setRed(sr / ss); - c.setGreen(sg / ss); - c.setBlue(sb / ss); - src->setPixel(ctrx, ctry, c); + // Walk each neighbor pixel (clipped to image bounds). + for (u32 sy = (ctry < 1) ? 0 : (ctry - 1); + sy <= (ctry + 1) && sy < dim.Height; sy++) + for (u32 sx = (ctrx < 1) ? 0 : (ctrx - 1); + sx <= (ctrx + 1) && sx < dim.Width; + sx++) { + + // Ignore transparent pixels. + irr::video::SColor d = src->getPixel(sx, sy); + if (d.getAlpha() <= threshold) + continue; + + // Add RGB values weighted by alpha. + u32 a = d.getAlpha(); + ss += a; + sr += a * d.getRed(); + sg += a * d.getGreen(); + sb += a * d.getBlue(); + } + + // If we found any neighbor RGB data, set pixel to average + // weighted by alpha. + if (ss > 0) { + c.setRed(sr / ss); + c.setGreen(sg / ss); + c.setBlue(sb / ss); + src->setPixel(ctrx, ctry, c); + } } - } } /* Scale a region of an image into another image, using nearest-neighbor with @@ -85,7 +86,8 @@ void imageCleanTransparent(video::IImage *src, u32 threshold) * filter is designed to produce the most accurate results for both upscaling * and downscaling. */ -void imageScaleNNAA(video::IImage *src, const core::rect &srcrect, video::IImage *dest) +void imageScaleNNAA( + video::IImage *src, const core::rect &srcrect, video::IImage *dest) { double sx, sy, minsx, maxsx, minsy, maxsy, area, ra, ga, ba, aa, pw, ph, pa; u32 dy, dx; @@ -101,72 +103,73 @@ void imageScaleNNAA(video::IImage *src, const core::rect &srcrect, video::I // Note: loop y around x for better cache locality. core::dimension2d dim = dest->getDimension(); for (dy = 0; dy < dim.Height; dy++) - for (dx = 0; dx < dim.Width; dx++) { + for (dx = 0; dx < dim.Width; dx++) { - // Calculate floating-point source rectangle bounds. - // 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, sox + sw); - maxsx = minsx + sw / dim.Width; - maxsx = rangelim(maxsx, 0, sox + sw); - if (minsx > maxsx) - SWAP(double, minsx, maxsx); - minsy = soy + (dy * sh / dim.Height); - minsy = rangelim(minsy, 0, soy + sh); - maxsy = minsy + sh / dim.Height; - maxsy = rangelim(maxsy, 0, soy + sh); - if (minsy > maxsy) - SWAP(double, minsy, maxsy); + // Calculate floating-point source rectangle bounds. + // 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, sox + sw); + maxsx = minsx + sw / dim.Width; + maxsx = rangelim(maxsx, 0, sox + sw); + if (minsx > maxsx) + SWAP(double, minsx, maxsx); + minsy = soy + (dy * sh / dim.Height); + minsy = rangelim(minsy, 0, soy + sh); + maxsy = minsy + sh / dim.Height; + maxsy = rangelim(maxsy, 0, soy + sh); + if (minsy > maxsy) + SWAP(double, minsy, maxsy); - // Total area, and integral of r, g, b values over that area, - // initialized to zero, to be summed up in next loops. - area = 0; - ra = 0; - ga = 0; - ba = 0; - aa = 0; + // Total area, and integral of r, g, b values over that area, + // initialized to zero, to be summed up in next loops. + area = 0; + ra = 0; + ga = 0; + ba = 0; + aa = 0; - // Loop over the integral pixel positions described by those bounds. - for (sy = floor(minsy); sy < maxsy; sy++) - for (sx = floor(minsx); sx < maxsx; sx++) { + // Loop over the integral pixel positions described by those + // bounds. + for (sy = floor(minsy); sy < maxsy; sy++) + for (sx = floor(minsx); sx < maxsx; sx++) { - // Calculate width, height, then area of dest pixel - // that's covered by this source pixel. - pw = 1; - if (minsx > sx) - pw += sx - minsx; - if (maxsx < (sx + 1)) - pw += maxsx - sx - 1; - ph = 1; - if (minsy > sy) - ph += sy - minsy; - if (maxsy < (sy + 1)) - ph += maxsy - sy - 1; - pa = pw * ph; + // Calculate width, height, then area of dest + // pixel that's covered by this source pixel. + pw = 1; + if (minsx > sx) + pw += sx - minsx; + if (maxsx < (sx + 1)) + pw += maxsx - sx - 1; + ph = 1; + if (minsy > sy) + ph += sy - minsy; + if (maxsy < (sy + 1)) + ph += maxsy - sy - 1; + pa = pw * ph; - // Get source pixel and add it to totals, weighted - // by covered area and alpha. - pxl = src->getPixel((u32)sx, (u32)sy); - area += pa; - ra += pa * pxl.getRed(); - ga += pa * pxl.getGreen(); - ba += pa * pxl.getBlue(); - aa += pa * pxl.getAlpha(); + // Get source pixel and add it to totals, weighted + // by covered area and alpha. + pxl = src->getPixel((u32)sx, (u32)sy); + area += pa; + ra += pa * pxl.getRed(); + ga += pa * pxl.getGreen(); + ba += pa * pxl.getBlue(); + aa += pa * pxl.getAlpha(); + } + + // Set the destination image pixel to the average color. + if (area > 0) { + pxl.setRed(ra / area + 0.5); + pxl.setGreen(ga / area + 0.5); + pxl.setBlue(ba / area + 0.5); + pxl.setAlpha(aa / area + 0.5); + } else { + pxl.setRed(0); + pxl.setGreen(0); + pxl.setBlue(0); + pxl.setAlpha(0); + } + dest->setPixel(dx, dy, pxl); } - - // Set the destination image pixel to the average color. - if (area > 0) { - pxl.setRed(ra / area + 0.5); - pxl.setGreen(ga / area + 0.5); - pxl.setBlue(ba / area + 0.5); - pxl.setAlpha(aa / area + 0.5); - } else { - pxl.setRed(0); - pxl.setGreen(0); - pxl.setBlue(0); - pxl.setAlpha(0); - } - dest->setPixel(dx, dy, pxl); - } } diff --git a/src/client/imagefilters.h b/src/client/imagefilters.h index 5676faf85..67f031890 100644 --- a/src/client/imagefilters.h +++ b/src/client/imagefilters.h @@ -40,4 +40,5 @@ void imageCleanTransparent(video::IImage *src, u32 threshold); * filter is designed to produce the most accurate results for both upscaling * and downscaling. */ -void imageScaleNNAA(video::IImage *src, const core::rect &srcrect, video::IImage *dest); +void imageScaleNNAA( + video::IImage *src, const core::rect &srcrect, video::IImage *dest); diff --git a/src/client/inputhandler.h b/src/client/inputhandler.h index e006affb2..ef5f6fb32 100644 --- a/src/client/inputhandler.h +++ b/src/client/inputhandler.h @@ -218,10 +218,7 @@ public: virtual ~InputHandler() = default; - virtual bool isRandom() const - { - return false; - } + virtual bool isRandom() const { return false; } virtual bool isKeyDown(GameKeyType k) = 0; virtual void setKeypress(const KeyPress &keyCode) = 0; @@ -377,7 +374,7 @@ public: m_receiver->clearInput(); } - private: +private: MyEventReceiver *m_receiver = nullptr; v2s32 m_mousepos; }; @@ -387,20 +384,11 @@ class RandomInputHandler : public InputHandler public: RandomInputHandler() = default; - bool isRandom() const - { - return true; - } + bool isRandom() const { return true; } virtual bool isKeyDown(GameKeyType k) { return keydown[keycache.key[k]]; } - virtual void setKeypress(const KeyPress &keyCode) - { - keydown.set(keyCode); - } - virtual void unsetKeypress(const KeyPress &keyCode) - { - keydown.unset(keyCode); - } + virtual void setKeypress(const KeyPress &keyCode) { keydown.set(keyCode); } + virtual void unsetKeypress(const KeyPress &keyCode) { keydown.unset(keyCode); } virtual bool wasKeyDown(GameKeyType k) { return false; } virtual bool cancelPressed() { return false; } virtual v2s32 getMousePos() { return mousepos; } diff --git a/src/client/joystick_controller.cpp b/src/client/joystick_controller.cpp index c29e8b639..5bff6fbba 100644 --- a/src/client/joystick_controller.cpp +++ b/src/client/joystick_controller.cpp @@ -41,7 +41,7 @@ bool JoystickAxisCmb::isTriggered(const irr::SEvent::SJoystickEvent &ev) const } // spares many characters -#define JLO_B_PB(A, B, C) jlo.button_keys.emplace_back(A, B, C) +#define JLO_B_PB(A, B, C) jlo.button_keys.emplace_back(A, B, C) #define JLO_A_PB(A, B, C, D) jlo.axis_keys.emplace_back(A, B, C, D) JoystickLayout create_default_layout() @@ -51,41 +51,41 @@ JoystickLayout create_default_layout() jlo.axes_dead_border = 1024; const JoystickAxisLayout axes[JA_COUNT] = { - {0, 1}, // JA_SIDEWARD_MOVE - {1, 1}, // JA_FORWARD_MOVE - {3, 1}, // JA_FRUSTUM_HORIZONTAL - {4, 1}, // JA_FRUSTUM_VERTICAL + {0, 1}, // JA_SIDEWARD_MOVE + {1, 1}, // JA_FORWARD_MOVE + {3, 1}, // JA_FRUSTUM_HORIZONTAL + {4, 1}, // JA_FRUSTUM_VERTICAL }; memcpy(jlo.axes, axes, sizeof(jlo.axes)); - u32 sb = 1 << 7; // START button mask - u32 fb = 1 << 3; // FOUR button mask + u32 sb = 1 << 7; // START button mask + u32 fb = 1 << 3; // FOUR button mask u32 bm = sb | fb; // Mask for Both Modifiers // The back button means "ESC". - JLO_B_PB(KeyType::ESC, 1 << 6, 1 << 6); + JLO_B_PB(KeyType::ESC, 1 << 6, 1 << 6); // The start button counts as modifier as well as use key. // JLO_B_PB(KeyType::USE, sb, sb)); // Accessible without start modifier button pressed // regardless whether four is pressed or not - JLO_B_PB(KeyType::SNEAK, sb | 1 << 2, 1 << 2); + JLO_B_PB(KeyType::SNEAK, sb | 1 << 2, 1 << 2); // Accessible without four modifier button pressed // regardless whether start is pressed or not - JLO_B_PB(KeyType::MOUSE_L, fb | 1 << 4, 1 << 4); - JLO_B_PB(KeyType::MOUSE_R, fb | 1 << 5, 1 << 5); + JLO_B_PB(KeyType::MOUSE_L, fb | 1 << 4, 1 << 4); + JLO_B_PB(KeyType::MOUSE_R, fb | 1 << 5, 1 << 5); // Accessible without any modifier pressed - JLO_B_PB(KeyType::JUMP, bm | 1 << 0, 1 << 0); - JLO_B_PB(KeyType::SPECIAL1, bm | 1 << 1, 1 << 1); + JLO_B_PB(KeyType::JUMP, bm | 1 << 0, 1 << 0); + JLO_B_PB(KeyType::SPECIAL1, bm | 1 << 1, 1 << 1); // Accessible with start button not pressed, but four pressed // TODO find usage for button 0 - JLO_B_PB(KeyType::DROP, bm | 1 << 1, fb | 1 << 1); - JLO_B_PB(KeyType::SCROLL_UP, bm | 1 << 4, fb | 1 << 4); - JLO_B_PB(KeyType::SCROLL_DOWN,bm | 1 << 5, fb | 1 << 5); + JLO_B_PB(KeyType::DROP, bm | 1 << 1, fb | 1 << 1); + JLO_B_PB(KeyType::SCROLL_UP, bm | 1 << 4, fb | 1 << 4); + JLO_B_PB(KeyType::SCROLL_DOWN, bm | 1 << 5, fb | 1 << 5); // Accessible with start button and four pressed // TODO find usage for buttons 0, 1 and 4, 5 @@ -93,13 +93,13 @@ JoystickLayout create_default_layout() // Now about the buttons simulated by the axes // Movement buttons, important for vessels - JLO_A_PB(KeyType::FORWARD, 1, 1, 1024); + JLO_A_PB(KeyType::FORWARD, 1, 1, 1024); JLO_A_PB(KeyType::BACKWARD, 1, -1, 1024); - JLO_A_PB(KeyType::LEFT, 0, 1, 1024); - JLO_A_PB(KeyType::RIGHT, 0, -1, 1024); + JLO_A_PB(KeyType::LEFT, 0, 1, 1024); + JLO_A_PB(KeyType::RIGHT, 0, -1, 1024); // Scroll buttons - JLO_A_PB(KeyType::SCROLL_UP, 2, -1, 1024); + JLO_A_PB(KeyType::SCROLL_UP, 2, -1, 1024); JLO_A_PB(KeyType::SCROLL_DOWN, 5, -1, 1024); return jlo; @@ -112,44 +112,44 @@ JoystickLayout create_xbox_layout() jlo.axes_dead_border = 7000; const JoystickAxisLayout axes[JA_COUNT] = { - {0, 1}, // JA_SIDEWARD_MOVE - {1, 1}, // JA_FORWARD_MOVE - {2, 1}, // JA_FRUSTUM_HORIZONTAL - {3, 1}, // JA_FRUSTUM_VERTICAL + {0, 1}, // JA_SIDEWARD_MOVE + {1, 1}, // JA_FORWARD_MOVE + {2, 1}, // JA_FRUSTUM_HORIZONTAL + {3, 1}, // JA_FRUSTUM_VERTICAL }; memcpy(jlo.axes, axes, sizeof(jlo.axes)); // The back button means "ESC". - JLO_B_PB(KeyType::ESC, 1 << 8, 1 << 8); // back - JLO_B_PB(KeyType::ESC, 1 << 9, 1 << 9); // start + JLO_B_PB(KeyType::ESC, 1 << 8, 1 << 8); // back + JLO_B_PB(KeyType::ESC, 1 << 9, 1 << 9); // start // 4 Buttons - JLO_B_PB(KeyType::JUMP, 1 << 0, 1 << 0); // A/green - JLO_B_PB(KeyType::ESC, 1 << 1, 1 << 1); // B/red - JLO_B_PB(KeyType::SPECIAL1, 1 << 2, 1 << 2); // X/blue - JLO_B_PB(KeyType::INVENTORY, 1 << 3, 1 << 3); // Y/yellow + JLO_B_PB(KeyType::JUMP, 1 << 0, 1 << 0); // A/green + JLO_B_PB(KeyType::ESC, 1 << 1, 1 << 1); // B/red + JLO_B_PB(KeyType::SPECIAL1, 1 << 2, 1 << 2); // X/blue + JLO_B_PB(KeyType::INVENTORY, 1 << 3, 1 << 3); // Y/yellow // Analog Sticks - JLO_B_PB(KeyType::SPECIAL1, 1 << 11, 1 << 11); // left - JLO_B_PB(KeyType::SNEAK, 1 << 12, 1 << 12); // right + JLO_B_PB(KeyType::SPECIAL1, 1 << 11, 1 << 11); // left + JLO_B_PB(KeyType::SNEAK, 1 << 12, 1 << 12); // right // Triggers - JLO_B_PB(KeyType::MOUSE_L, 1 << 6, 1 << 6); // lt - JLO_B_PB(KeyType::MOUSE_R, 1 << 7, 1 << 7); // rt - JLO_B_PB(KeyType::SCROLL_UP, 1 << 4, 1 << 4); // lb - JLO_B_PB(KeyType::SCROLL_DOWN, 1 << 5, 1 << 5); // rb + JLO_B_PB(KeyType::MOUSE_L, 1 << 6, 1 << 6); // lt + JLO_B_PB(KeyType::MOUSE_R, 1 << 7, 1 << 7); // rt + JLO_B_PB(KeyType::SCROLL_UP, 1 << 4, 1 << 4); // lb + JLO_B_PB(KeyType::SCROLL_DOWN, 1 << 5, 1 << 5); // rb // D-PAD - JLO_B_PB(KeyType::ZOOM, 1 << 15, 1 << 15); // up - JLO_B_PB(KeyType::DROP, 1 << 13, 1 << 13); // left - JLO_B_PB(KeyType::SCREENSHOT, 1 << 14, 1 << 14); // right - JLO_B_PB(KeyType::FREEMOVE, 1 << 16, 1 << 16); // down + JLO_B_PB(KeyType::ZOOM, 1 << 15, 1 << 15); // up + JLO_B_PB(KeyType::DROP, 1 << 13, 1 << 13); // left + JLO_B_PB(KeyType::SCREENSHOT, 1 << 14, 1 << 14); // right + JLO_B_PB(KeyType::FREEMOVE, 1 << 16, 1 << 16); // down // Movement buttons, important for vessels - JLO_A_PB(KeyType::FORWARD, 1, 1, 1024); + JLO_A_PB(KeyType::FORWARD, 1, 1, 1024); JLO_A_PB(KeyType::BACKWARD, 1, -1, 1024); - JLO_A_PB(KeyType::LEFT, 0, 1, 1024); - JLO_A_PB(KeyType::RIGHT, 0, -1, 1024); + JLO_A_PB(KeyType::LEFT, 0, 1, 1024); + JLO_A_PB(KeyType::RIGHT, 0, -1, 1024); return jlo; } @@ -163,9 +163,10 @@ JoystickController::JoystickController() : clear(); } -void JoystickController::onJoystickConnect(const std::vector &joystick_infos) +void JoystickController::onJoystickConnect( + const std::vector &joystick_infos) { - s32 id = g_settings->getS32("joystick_id"); + s32 id = g_settings->getS32("joystick_id"); std::string layout = g_settings->get("joystick_type"); if (id < 0 || (u16)id >= joystick_infos.size()) { @@ -220,7 +221,9 @@ bool JoystickController::handleEvent(const irr::SEvent::SJoystickEvent &ev) for (size_t i = 0; i < KeyType::INTERNAL_ENUM_COUNT; i++) { if (keys_pressed[i]) { if (!m_past_pressed_keys[i] && - m_past_pressed_time[i] < m_internal_time - doubling_dtime) { + m_past_pressed_time[i] < + m_internal_time - + doubling_dtime) { m_past_pressed_keys[i] = true; m_past_pressed_time[i] = m_internal_time; } @@ -236,7 +239,6 @@ bool JoystickController::handleEvent(const irr::SEvent::SJoystickEvent &ev) m_axes_vals[i] = ax_la.invert * ev.Axis[ax_la.axis_id]; } - return true; } diff --git a/src/client/joystick_controller.h b/src/client/joystick_controller.h index 7baacd81b..ea0833561 100644 --- a/src/client/joystick_controller.h +++ b/src/client/joystick_controller.h @@ -24,7 +24,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include -enum JoystickAxis { +enum JoystickAxis +{ JA_SIDEWARD_MOVE, JA_FORWARD_MOVE, @@ -35,27 +36,28 @@ enum JoystickAxis { JA_COUNT, }; -struct JoystickAxisLayout { +struct JoystickAxisLayout +{ u16 axis_id; // -1 if to invert, 1 if to keep it. int invert; }; +struct JoystickCombination +{ -struct JoystickCombination { - - virtual bool isTriggered(const irr::SEvent::SJoystickEvent &ev) const=0; + virtual bool isTriggered(const irr::SEvent::SJoystickEvent &ev) const = 0; GameKeyType key; }; -struct JoystickButtonCmb : public JoystickCombination { +struct JoystickButtonCmb : public JoystickCombination +{ JoystickButtonCmb() = default; JoystickButtonCmb(GameKeyType key, u32 filter_mask, u32 compare_mask) : - filter_mask(filter_mask), - compare_mask(compare_mask) + filter_mask(filter_mask), compare_mask(compare_mask) { this->key = key; } @@ -68,14 +70,14 @@ struct JoystickButtonCmb : public JoystickCombination { u32 compare_mask; }; -struct JoystickAxisCmb : public JoystickCombination { +struct JoystickAxisCmb : public JoystickCombination +{ JoystickAxisCmb() = default; JoystickAxisCmb(GameKeyType key, u16 axis_to_compare, int direction, s16 thresh) : - axis_to_compare(axis_to_compare), - direction(direction), - thresh(thresh) + axis_to_compare(axis_to_compare), direction(direction), + thresh(thresh) { this->key = key; } @@ -92,14 +94,16 @@ struct JoystickAxisCmb : public JoystickCombination { s16 thresh; }; -struct JoystickLayout { +struct JoystickLayout +{ std::vector button_keys; std::vector axis_keys; JoystickAxisLayout axes[JA_COUNT]; s16 axes_dead_border; }; -class JoystickController { +class JoystickController +{ public: JoystickController(); @@ -115,14 +119,8 @@ public: m_past_pressed_keys[b] = false; return r; } - bool getWasKeyDown(GameKeyType b) - { - return m_past_pressed_keys[b]; - } - void clearWasKeyDown(GameKeyType b) - { - m_past_pressed_keys[b] = false; - } + bool getWasKeyDown(GameKeyType b) { return m_past_pressed_keys[b]; } + void clearWasKeyDown(GameKeyType b) { m_past_pressed_keys[b] = false; } bool wasKeyReleased(GameKeyType b) { @@ -130,24 +128,12 @@ public: m_past_released_keys[b] = false; return r; } - bool getWasKeyReleased(GameKeyType b) - { - return m_past_pressed_keys[b]; - } - void clearWasKeyReleased(GameKeyType b) - { - m_past_pressed_keys[b] = false; - } + bool getWasKeyReleased(GameKeyType b) { return m_past_pressed_keys[b]; } + void clearWasKeyReleased(GameKeyType b) { m_past_pressed_keys[b] = false; } - bool isKeyDown(GameKeyType b) - { - return m_pressed_keys[b]; - } + bool isKeyDown(GameKeyType b) { return m_pressed_keys[b]; } - s16 getAxis(JoystickAxis axis) - { - return m_axes_vals[axis]; - } + s16 getAxis(JoystickAxis axis) { return m_axes_vals[axis]; } s16 getAxisWithoutDead(JoystickAxis axis); diff --git a/src/client/keycode.cpp b/src/client/keycode.cpp index 6a0e9f569..bbd269e81 100644 --- a/src/client/keycode.cpp +++ b/src/client/keycode.cpp @@ -29,224 +29,182 @@ with this program; if not, write to the Free Software Foundation, Inc., class UnknownKeycode : public BaseException { public: - UnknownKeycode(const char *s) : - BaseException(s) {}; + UnknownKeycode(const char *s) : BaseException(s){}; }; -struct table_key { +struct table_key +{ const char *Name; irr::EKEY_CODE Key; - wchar_t Char; // L'\0' means no character assigned + wchar_t Char; // L'\0' means no character assigned const char *LangName; // NULL means it doesn't have a human description }; -#define DEFINEKEY1(x, lang) /* Irrlicht key without character */ \ - { #x, irr::x, L'\0', lang }, -#define DEFINEKEY2(x, ch, lang) /* Irrlicht key with character */ \ - { #x, irr::x, ch, lang }, -#define DEFINEKEY3(ch) /* single Irrlicht key (e.g. KEY_KEY_X) */ \ - { "KEY_KEY_" TOSTRING(ch), irr::KEY_KEY_ ## ch, (wchar_t) *TOSTRING(ch), TOSTRING(ch) }, -#define DEFINEKEY4(ch) /* single Irrlicht function key (e.g. KEY_F3) */ \ - { "KEY_F" TOSTRING(ch), irr::KEY_F ## ch, L'\0', "F" TOSTRING(ch) }, -#define DEFINEKEY5(ch) /* key without Irrlicht keycode */ \ - { ch, irr::KEY_KEY_CODES_COUNT, (wchar_t) *ch, ch }, +#define DEFINEKEY1(x, lang) /* Irrlicht key without character */ \ + {#x, irr::x, L'\0', lang}, +#define DEFINEKEY2(x, ch, lang) /* Irrlicht key with character */ {#x, irr::x, ch, lang}, +#define DEFINEKEY3(ch) /* single Irrlicht key (e.g. KEY_KEY_X) */ \ + {"KEY_KEY_" TOSTRING(ch), irr::KEY_KEY_##ch, (wchar_t)*TOSTRING(ch), \ + TOSTRING(ch)}, +#define DEFINEKEY4(ch) /* single Irrlicht function key (e.g. KEY_F3) */ \ + {"KEY_F" TOSTRING(ch), irr::KEY_F##ch, L'\0', "F" TOSTRING(ch)}, +#define DEFINEKEY5(ch) /* key without Irrlicht keycode */ \ + {ch, irr::KEY_KEY_CODES_COUNT, (wchar_t)*ch, ch}, #define N_(text) text static const struct table_key table[] = { - // Keys that can be reliably mapped between Char and Key - DEFINEKEY3(0) - DEFINEKEY3(1) - DEFINEKEY3(2) - DEFINEKEY3(3) - DEFINEKEY3(4) - DEFINEKEY3(5) - DEFINEKEY3(6) - DEFINEKEY3(7) - DEFINEKEY3(8) - DEFINEKEY3(9) - DEFINEKEY3(A) - DEFINEKEY3(B) - DEFINEKEY3(C) - DEFINEKEY3(D) - DEFINEKEY3(E) - DEFINEKEY3(F) - DEFINEKEY3(G) - DEFINEKEY3(H) - DEFINEKEY3(I) - DEFINEKEY3(J) - DEFINEKEY3(K) - DEFINEKEY3(L) - DEFINEKEY3(M) - DEFINEKEY3(N) - DEFINEKEY3(O) - DEFINEKEY3(P) - DEFINEKEY3(Q) - DEFINEKEY3(R) - DEFINEKEY3(S) - DEFINEKEY3(T) - DEFINEKEY3(U) - DEFINEKEY3(V) - DEFINEKEY3(W) - DEFINEKEY3(X) - DEFINEKEY3(Y) - DEFINEKEY3(Z) - DEFINEKEY2(KEY_PLUS, L'+', "+") - DEFINEKEY2(KEY_COMMA, L',', ",") - DEFINEKEY2(KEY_MINUS, L'-', "-") - DEFINEKEY2(KEY_PERIOD, L'.', ".") + // Keys that can be reliably mapped between Char and Key + DEFINEKEY3(0) DEFINEKEY3(1) DEFINEKEY3(2) DEFINEKEY3(3) DEFINEKEY3(4) DEFINEKEY3(5) DEFINEKEY3( + 6) DEFINEKEY3(7) DEFINEKEY3(8) DEFINEKEY3(9) DEFINEKEY3(A) DEFINEKEY3(B) DEFINEKEY3(C) + DEFINEKEY3(D) DEFINEKEY3(E) DEFINEKEY3(F) DEFINEKEY3(G) DEFINEKEY3(H) DEFINEKEY3( + I) DEFINEKEY3(J) DEFINEKEY3(K) DEFINEKEY3(L) DEFINEKEY3(M) + DEFINEKEY3(N) DEFINEKEY3(O) DEFINEKEY3(P) DEFINEKEY3(Q) DEFINEKEY3( + R) DEFINEKEY3(S) DEFINEKEY3(T) DEFINEKEY3(U) + DEFINEKEY3(V) DEFINEKEY3(W) DEFINEKEY3(X) DEFINEKEY3( + Y) DEFINEKEY3(Z) DEFINEKEY2(KEY_PLUS, + L'+', + "+") DEFINEKEY2(KEY_COMMA, + L',', + ",") DEFINEKEY2(KEY_MINUS, + L'-', + "-") DEFINEKEY2(KEY_PERIOD, + L'.', ".") - // Keys without a Char - DEFINEKEY1(KEY_LBUTTON, N_("Left Button")) - DEFINEKEY1(KEY_RBUTTON, N_("Right Button")) - DEFINEKEY1(KEY_CANCEL, N_("Cancel")) - DEFINEKEY1(KEY_MBUTTON, N_("Middle Button")) - DEFINEKEY1(KEY_XBUTTON1, N_("X Button 1")) - DEFINEKEY1(KEY_XBUTTON2, N_("X Button 2")) - DEFINEKEY1(KEY_BACK, N_("Backspace")) - DEFINEKEY1(KEY_TAB, N_("Tab")) - DEFINEKEY1(KEY_CLEAR, N_("Clear")) - DEFINEKEY1(KEY_RETURN, N_("Return")) - DEFINEKEY1(KEY_SHIFT, N_("Shift")) - DEFINEKEY1(KEY_CONTROL, N_("Control")) - //~ Key name, common on Windows keyboards - DEFINEKEY1(KEY_MENU, N_("Menu")) - DEFINEKEY1(KEY_PAUSE, N_("Pause")) - DEFINEKEY1(KEY_CAPITAL, N_("Caps Lock")) - DEFINEKEY1(KEY_SPACE, N_("Space")) - DEFINEKEY1(KEY_PRIOR, N_("Page up")) - DEFINEKEY1(KEY_NEXT, N_("Page down")) - DEFINEKEY1(KEY_END, N_("End")) - DEFINEKEY1(KEY_HOME, N_("Home")) - DEFINEKEY1(KEY_LEFT, N_("Left")) - DEFINEKEY1(KEY_UP, N_("Up")) - DEFINEKEY1(KEY_RIGHT, N_("Right")) - DEFINEKEY1(KEY_DOWN, N_("Down")) - //~ Key name - DEFINEKEY1(KEY_SELECT, N_("Select")) - //~ "Print screen" key - DEFINEKEY1(KEY_PRINT, N_("Print")) - DEFINEKEY1(KEY_EXECUT, N_("Execute")) - DEFINEKEY1(KEY_SNAPSHOT, N_("Snapshot")) - DEFINEKEY1(KEY_INSERT, N_("Insert")) - DEFINEKEY1(KEY_DELETE, N_("Delete")) - DEFINEKEY1(KEY_HELP, N_("Help")) - DEFINEKEY1(KEY_LWIN, N_("Left Windows")) - DEFINEKEY1(KEY_RWIN, N_("Right Windows")) - DEFINEKEY1(KEY_NUMPAD0, N_("Numpad 0")) // These are not assigned to a char - DEFINEKEY1(KEY_NUMPAD1, N_("Numpad 1")) // to prevent interference with KEY_KEY_[0-9]. - DEFINEKEY1(KEY_NUMPAD2, N_("Numpad 2")) - DEFINEKEY1(KEY_NUMPAD3, N_("Numpad 3")) - DEFINEKEY1(KEY_NUMPAD4, N_("Numpad 4")) - DEFINEKEY1(KEY_NUMPAD5, N_("Numpad 5")) - DEFINEKEY1(KEY_NUMPAD6, N_("Numpad 6")) - DEFINEKEY1(KEY_NUMPAD7, N_("Numpad 7")) - DEFINEKEY1(KEY_NUMPAD8, N_("Numpad 8")) - DEFINEKEY1(KEY_NUMPAD9, N_("Numpad 9")) - DEFINEKEY1(KEY_MULTIPLY, N_("Numpad *")) - DEFINEKEY1(KEY_ADD, N_("Numpad +")) - DEFINEKEY1(KEY_SEPARATOR, N_("Numpad .")) - DEFINEKEY1(KEY_SUBTRACT, N_("Numpad -")) - DEFINEKEY1(KEY_DECIMAL, NULL) - DEFINEKEY1(KEY_DIVIDE, N_("Numpad /")) - DEFINEKEY4(1) - DEFINEKEY4(2) - DEFINEKEY4(3) - DEFINEKEY4(4) - DEFINEKEY4(5) - DEFINEKEY4(6) - DEFINEKEY4(7) - DEFINEKEY4(8) - DEFINEKEY4(9) - DEFINEKEY4(10) - DEFINEKEY4(11) - DEFINEKEY4(12) - DEFINEKEY4(13) - DEFINEKEY4(14) - DEFINEKEY4(15) - DEFINEKEY4(16) - DEFINEKEY4(17) - DEFINEKEY4(18) - DEFINEKEY4(19) - DEFINEKEY4(20) - DEFINEKEY4(21) - DEFINEKEY4(22) - DEFINEKEY4(23) - DEFINEKEY4(24) - DEFINEKEY1(KEY_NUMLOCK, N_("Num Lock")) - DEFINEKEY1(KEY_SCROLL, N_("Scroll Lock")) - DEFINEKEY1(KEY_LSHIFT, N_("Left Shift")) - DEFINEKEY1(KEY_RSHIFT, N_("Right Shift")) - DEFINEKEY1(KEY_LCONTROL, N_("Left Control")) - DEFINEKEY1(KEY_RCONTROL, N_("Right Control")) - DEFINEKEY1(KEY_LMENU, N_("Left Menu")) - DEFINEKEY1(KEY_RMENU, N_("Right Menu")) + // Keys without a Char + DEFINEKEY1(KEY_LBUTTON, N_("Left Button")) DEFINEKEY1( + KEY_RBUTTON, N_("Right Button")) DEFINEKEY1(KEY_CANCEL, + N_("Cancel")) DEFINEKEY1(KEY_MBUTTON, + N_("Middle Button")) DEFINEKEY1(KEY_XBUTTON1, + N_("X Button 1")) DEFINEKEY1(KEY_XBUTTON2, + N_("X Button 2")) DEFINEKEY1(KEY_BACK, + N_("Backspace")) DEFINEKEY1(KEY_TAB, + N_("Tab")) DEFINEKEY1(KEY_CLEAR, N_("Clear")) + DEFINEKEY1(KEY_RETURN, N_("Return")) DEFINEKEY1( + KEY_SHIFT, N_("Shift")) + DEFINEKEY1(KEY_CONTROL, N_("Control")) + //~ Key name, common on Windows keyboards + DEFINEKEY1(KEY_MENU, N_("Menu")) DEFINEKEY1( + KEY_PAUSE, N_("Pause")) DEFINEKEY1(KEY_CAPITAL, + N_("Caps Lock")) DEFINEKEY1(KEY_SPACE, + N_("Space")) DEFINEKEY1(KEY_PRIOR, N_("Page up")) + DEFINEKEY1(KEY_NEXT, N_("Page down")) DEFINEKEY1( + KEY_END, N_("End")) DEFINEKEY1(KEY_HOME, + N_("Home")) DEFINEKEY1(KEY_LEFT, + N_("Left")) DEFINEKEY1(KEY_UP, + N_("Up")) DEFINEKEY1(KEY_RIGHT, + N_("Right")) + DEFINEKEY1(KEY_DOWN, N_("Down")) + //~ Key name + DEFINEKEY1(KEY_SELECT, N_("Select")) + //~ "Print screen" key + DEFINEKEY1(KEY_PRINT, N_("Print")) DEFINEKEY1( + KEY_EXECUT, N_("Execute")) DEFINEKEY1(KEY_SNAPSHOT, + N_("Snapshot")) DEFINEKEY1(KEY_INSERT, + N_("Insert")) DEFINEKEY1(KEY_DELETE, + N_("Delete")) DEFINEKEY1(KEY_HELP, + N_("Help")) DEFINEKEY1(KEY_LWIN, + N_("Left Windows")) DEFINEKEY1(KEY_RWIN, + N_("Right Windows")) DEFINEKEY1(KEY_NUMPAD0, + N_("Numpad 0")) // These are not assigned to a char + DEFINEKEY1(KEY_NUMPAD1, N_("Numpad 1")) // to prevent interference with + // KEY_KEY_[0-9]. + DEFINEKEY1(KEY_NUMPAD2, N_("Numpad 2")) DEFINEKEY1( + KEY_NUMPAD3, N_("Numpad 3")) DEFINEKEY1(KEY_NUMPAD4, + N_("Numpad 4")) DEFINEKEY1(KEY_NUMPAD5, + N_("Numpad 5")) DEFINEKEY1(KEY_NUMPAD6, + N_("Numpad 6")) DEFINEKEY1(KEY_NUMPAD7, + N_("Numpad 7")) DEFINEKEY1(KEY_NUMPAD8, + N_("Numpad 8")) DEFINEKEY1(KEY_NUMPAD9, + N_("Numpad 9")) DEFINEKEY1(KEY_MULTIPLY, + N_("Numpad *")) DEFINEKEY1(KEY_ADD, + N_("Numpad +")) DEFINEKEY1(KEY_SEPARATOR, + N_("Numpad .")) DEFINEKEY1(KEY_SUBTRACT, + N_("Numpad -")) DEFINEKEY1(KEY_DECIMAL, + NULL) DEFINEKEY1(KEY_DIVIDE, + N_("Numpad /")) DEFINEKEY4(1) DEFINEKEY4(2) DEFINEKEY4(3) DEFINEKEY4(4) + DEFINEKEY4(5) DEFINEKEY4(6) DEFINEKEY4(7) DEFINEKEY4(8) DEFINEKEY4(9) DEFINEKEY4( + 10) DEFINEKEY4(11) DEFINEKEY4(12) DEFINEKEY4(13) DEFINEKEY4(14) + DEFINEKEY4(15) DEFINEKEY4(16) DEFINEKEY4(17) DEFINEKEY4( + 18) DEFINEKEY4(19) DEFINEKEY4(20) DEFINEKEY4(21) + DEFINEKEY4(22) DEFINEKEY4(23) DEFINEKEY4( + 24) DEFINEKEY1(KEY_NUMLOCK, + N_("Num " + "Loc" + "k")) DEFINEKEY1(KEY_SCROLL, + N_("Scrol" + "l " + "Loc" + "k")) DEFINEKEY1(KEY_LSHIFT, + N_("Left " + "Shif" + "t")) DEFINEKEY1(KEY_RSHIFT, + N_("Right" + " Shif" + "t")) DEFINEKEY1(KEY_LCONTROL, + N_("Left " + "Contr" + "ol")) DEFINEKEY1(KEY_RCONTROL, + N_("Right" + " Cont" + "rol")) DEFINEKEY1(KEY_LMENU, + N_("Left " + "Men" + "u")) DEFINEKEY1(KEY_RMENU, + N_("Right" + " Men" + "u")) - // Rare/weird keys - DEFINEKEY1(KEY_KANA, "Kana") - DEFINEKEY1(KEY_HANGUEL, "Hangul") - DEFINEKEY1(KEY_HANGUL, "Hangul") - DEFINEKEY1(KEY_JUNJA, "Junja") - DEFINEKEY1(KEY_FINAL, "Final") - DEFINEKEY1(KEY_KANJI, "Kanji") - DEFINEKEY1(KEY_HANJA, "Hanja") - DEFINEKEY1(KEY_ESCAPE, N_("IME Escape")) - DEFINEKEY1(KEY_CONVERT, N_("IME Convert")) - DEFINEKEY1(KEY_NONCONVERT, N_("IME Nonconvert")) - DEFINEKEY1(KEY_ACCEPT, N_("IME Accept")) - DEFINEKEY1(KEY_MODECHANGE, N_("IME Mode Change")) - DEFINEKEY1(KEY_APPS, N_("Apps")) - DEFINEKEY1(KEY_SLEEP, N_("Sleep")) -#if !(IRRLICHT_VERSION_MAJOR <= 1 && IRRLICHT_VERSION_MINOR <= 7 && IRRLICHT_VERSION_REVISION < 3) - DEFINEKEY1(KEY_OEM_1, "OEM 1") // KEY_OEM_[0-9] and KEY_OEM_102 are assigned to multiple - DEFINEKEY1(KEY_OEM_2, "OEM 2") // different chars (on different platforms too) and thus w/o char - DEFINEKEY1(KEY_OEM_3, "OEM 3") - DEFINEKEY1(KEY_OEM_4, "OEM 4") - DEFINEKEY1(KEY_OEM_5, "OEM 5") - DEFINEKEY1(KEY_OEM_6, "OEM 6") - DEFINEKEY1(KEY_OEM_7, "OEM 7") - DEFINEKEY1(KEY_OEM_8, "OEM 8") - DEFINEKEY1(KEY_OEM_AX, "OEM AX") - DEFINEKEY1(KEY_OEM_102, "OEM 102") + // Rare/weird keys + DEFINEKEY1(KEY_KANA, "Kana") DEFINEKEY1(KEY_HANGUEL, "Hangul") DEFINEKEY1( + KEY_HANGUL, "Hangul") DEFINEKEY1(KEY_JUNJA, + "Junja") DEFINEKEY1(KEY_FINAL, + "Final") DEFINEKEY1(KEY_KANJI, + "Kanji") DEFINEKEY1(KEY_HANJA, + "Hanja") DEFINEKEY1(KEY_ESCAPE, + N_("IME Escape")) DEFINEKEY1(KEY_CONVERT, + N_("IME Convert")) DEFINEKEY1(KEY_NONCONVERT, + N_("IME Nonconvert")) DEFINEKEY1(KEY_ACCEPT, + N_("IME Accept")) DEFINEKEY1(KEY_MODECHANGE, + N_("IME Mode Change")) DEFINEKEY1(KEY_APPS, + N_("Apps")) DEFINEKEY1(KEY_SLEEP, N_("Sleep")) +#if !(IRRLICHT_VERSION_MAJOR <= 1 && IRRLICHT_VERSION_MINOR <= 7 && \ + IRRLICHT_VERSION_REVISION < 3) + DEFINEKEY1(KEY_OEM_1, + "OEM 1") // KEY_OEM_[0-9] and KEY_OEM_102 + // are assigned to multiple + DEFINEKEY1(KEY_OEM_2, "OEM 2") // different chars (on different platforms + // too) and thus w/o char + DEFINEKEY1(KEY_OEM_3, "OEM 3") DEFINEKEY1(KEY_OEM_4, "OEM 4") DEFINEKEY1( + KEY_OEM_5, "OEM 5") DEFINEKEY1(KEY_OEM_6, + "OEM 6") DEFINEKEY1(KEY_OEM_7, + "OEM 7") DEFINEKEY1(KEY_OEM_8, + "OEM 8") DEFINEKEY1(KEY_OEM_AX, + "OEM AX") DEFINEKEY1(KEY_OEM_102, "OEM 102") #endif - DEFINEKEY1(KEY_ATTN, "Attn") - DEFINEKEY1(KEY_CRSEL, "CrSel") - DEFINEKEY1(KEY_EXSEL, "ExSel") - DEFINEKEY1(KEY_EREOF, N_("Erase EOF")) - DEFINEKEY1(KEY_PLAY, N_("Play")) - DEFINEKEY1(KEY_ZOOM, N_("Zoom")) - DEFINEKEY1(KEY_PA1, "PA1") - DEFINEKEY1(KEY_OEM_CLEAR, N_("OEM Clear")) + DEFINEKEY1(KEY_ATTN, "Attn") DEFINEKEY1( + KEY_CRSEL, "CrSel") DEFINEKEY1(KEY_EXSEL, + "ExSel") DEFINEKEY1(KEY_EREOF, + N_("Erase EOF")) DEFINEKEY1(KEY_PLAY, + N_("Play")) DEFINEKEY1(KEY_ZOOM, + N_("Zoom")) DEFINEKEY1(KEY_PA1, "PA1") + DEFINEKEY1(KEY_OEM_CLEAR, N_("OEM Clear")) - // Keys without Irrlicht keycode - DEFINEKEY5("!") - DEFINEKEY5("\"") - DEFINEKEY5("#") - DEFINEKEY5("$") - DEFINEKEY5("%") - DEFINEKEY5("&") - DEFINEKEY5("'") - DEFINEKEY5("(") - DEFINEKEY5(")") - DEFINEKEY5("*") - DEFINEKEY5("/") - DEFINEKEY5(":") - DEFINEKEY5(";") - DEFINEKEY5("<") - DEFINEKEY5("=") - DEFINEKEY5(">") - DEFINEKEY5("?") - DEFINEKEY5("@") - DEFINEKEY5("[") - DEFINEKEY5("\\") - DEFINEKEY5("]") - DEFINEKEY5("^") - DEFINEKEY5("_") -}; + // Keys without Irrlicht keycode + DEFINEKEY5("!") DEFINEKEY5("\"") DEFINEKEY5("#") DEFINEKEY5( + "$") DEFINEKEY5("%") DEFINEKEY5("&") DEFINEKEY5("'") + DEFINEKEY5("(") DEFINEKEY5(")") DEFINEKEY5( + "*") DEFINEKEY5("/") DEFINEKEY5(":") + DEFINEKEY5(";") DEFINEKEY5("<") DEFINEKEY5( + "=") DEFINEKEY5(">") + DEFINEKEY5("?") DEFINEKEY5("@") DEFINEKEY5( + "[") DEFINEKEY5("\\") + DEFINEKEY5("]") DEFINEKEY5( + "^") + DEFINEKEY5("_")}; #undef N_ - struct table_key lookup_keyname(const char *name) { for (const auto &table_key : table) { @@ -265,7 +223,7 @@ struct table_key lookup_keykey(irr::EKEY_CODE key) } std::ostringstream os; - os << ""; + os << ""; throw UnknownKeycode(os.str().c_str()); } @@ -277,7 +235,7 @@ struct table_key lookup_keychar(wchar_t Char) } std::ostringstream os; - os << ""; + os << ""; throw UnknownKeycode(os.str().c_str()); } @@ -299,7 +257,8 @@ KeyPress::KeyPress(const char *name) m_name = k.Name; Key = k.Key; return; - } catch (UnknownKeycode &e) {}; + } catch (UnknownKeycode &e) { + }; } else { // Lookup by name m_name = name; @@ -308,7 +267,8 @@ KeyPress::KeyPress(const char *name) Key = k.Key; Char = k.Char; return; - } catch (UnknownKeycode &e) {}; + } catch (UnknownKeycode &e) { + }; } // It's not a known key, complain and try to do something @@ -316,7 +276,8 @@ KeyPress::KeyPress(const char *name) int chars_read = mbtowc(&Char, name, 1); FATAL_ERROR_IF(chars_read != 1, "Unexpected multibyte character"); m_name = ""; - warningstream << "KeyPress: Unknown key '" << name << "', falling back to first char."; + warningstream << "KeyPress: Unknown key '" << name + << "', falling back to first char."; } KeyPress::KeyPress(const irr::SEvent::SKeyInput &in, bool prefer_character) diff --git a/src/client/keys.h b/src/client/keys.h index 43a032a7b..e288ecda0 100644 --- a/src/client/keys.h +++ b/src/client/keys.h @@ -79,7 +79,7 @@ public: SELECT_LEFT, SELECT_RIGHT, SELECT_CONFIRM, - + QUICKTUNE_NEXT, QUICKTUNE_PREV, QUICKTUNE_INC, diff --git a/src/client/localplayer.cpp b/src/client/localplayer.cpp index 00195cd02..324055796 100644 --- a/src/client/localplayer.cpp +++ b/src/client/localplayer.cpp @@ -34,9 +34,8 @@ with this program; if not, write to the Free Software Foundation, Inc., LocalPlayer */ -LocalPlayer::LocalPlayer(Client *client, const char *name): - Player(name, client->idef()), - m_client(client) +LocalPlayer::LocalPlayer(Client *client, const char *name) : + Player(name, client->idef()), m_client(client) { } @@ -57,20 +56,11 @@ static aabb3f getNodeBoundingBox(const std::vector &nodeboxes) return b_max; } -bool LocalPlayer::updateSneakNode(Map *map, const v3f &position, - const v3f &sneak_max) +bool LocalPlayer::updateSneakNode(Map *map, const v3f &position, const v3f &sneak_max) { - static const v3s16 dir9_center[9] = { - v3s16( 0, 0, 0), - v3s16( 1, 0, 0), - v3s16(-1, 0, 0), - v3s16( 0, 0, 1), - v3s16( 0, 0, -1), - v3s16( 1, 0, 1), - v3s16(-1, 0, 1), - v3s16( 1, 0, -1), - v3s16(-1, 0, -1) - }; + static const v3s16 dir9_center[9] = {v3s16(0, 0, 0), v3s16(1, 0, 0), + v3s16(-1, 0, 0), v3s16(0, 0, 1), v3s16(0, 0, -1), v3s16(1, 0, 1), + v3s16(-1, 0, 1), v3s16(1, 0, -1), v3s16(-1, 0, -1)}; const NodeDefManager *nodemgr = m_client->ndef(); MapNode node; @@ -83,7 +73,8 @@ bool LocalPlayer::updateSneakNode(Map *map, const v3f &position, position_y_mod = m_sneak_node_bb_top.MaxEdge.Y - position_y_mod; // Get position of current standing node - const v3s16 current_node = floatToInt(position - v3f(0.0f, position_y_mod, 0.0f), BS); + const v3s16 current_node = + floatToInt(position - v3f(0.0f, position_y_mod, 0.0f), BS); if (current_node != m_sneak_node) { new_sneak_node_exists = false; @@ -112,18 +103,19 @@ bool LocalPlayer::updateSneakNode(Map *map, const v3f &position, fabs(diff.Y) > (0.5f + 0.1f) * BS + sneak_max.Z) continue; - // The node to be sneaked on has to be walkable node = map->getNode(p, &is_valid_position); - if (!is_valid_position || ! nodemgr->get(node).walkable) + if (!is_valid_position || !nodemgr->get(node).walkable) continue; // And the node(s) above have to be nonwalkable bool ok = true; if (!physics_override_sneak_glitch) { - u16 height = - ceilf((m_collisionbox.MaxEdge.Y - m_collisionbox.MinEdge.Y) / BS); + u16 height = ceilf((m_collisionbox.MaxEdge.Y - + m_collisionbox.MinEdge.Y) / + BS); for (u16 y = 1; y <= height; y++) { - node = map->getNode(p + v3s16(0, y, 0), &is_valid_position); + node = map->getNode( + p + v3s16(0, y, 0), &is_valid_position); if (!is_valid_position || nodemgr->get(node).walkable) { ok = false; break; @@ -132,7 +124,7 @@ bool LocalPlayer::updateSneakNode(Map *map, const v3f &position, } else { // legacy behaviour: check just one node node = map->getNode(p + v3s16(0, 1, 0), &is_valid_position); - ok = is_valid_position && ! nodemgr->get(node).walkable; + ok = is_valid_position && !nodemgr->get(node).walkable; } if (!ok) continue; @@ -154,14 +146,13 @@ bool LocalPlayer::updateSneakNode(Map *map, const v3f &position, if (physics_override_sneak_glitch) { // Detect sneak ladder: // Node two meters above sneak node must be solid - node = map->getNode(m_sneak_node + v3s16(0, 2, 0), - &is_valid_position); + node = map->getNode(m_sneak_node + v3s16(0, 2, 0), &is_valid_position); if (is_valid_position && nodemgr->get(node).walkable) { // Node three meters above: must be non-solid node = map->getNode(m_sneak_node + v3s16(0, 3, 0), - &is_valid_position); - m_sneak_ladder_detected = is_valid_position && - ! nodemgr->get(node).walkable; + &is_valid_position); + m_sneak_ladder_detected = + is_valid_position && !nodemgr->get(node).walkable; } } return true; @@ -172,7 +163,7 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d, { if (m_cao && m_cao->m_waiting_for_reattach > 0) m_cao->m_waiting_for_reattach -= dtime; - + // Node at feet position, update each ClientEnvironment::step() if (!collision_info || collision_info->empty()) m_standing_node = floatToInt(m_position, BS); @@ -227,13 +218,13 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d, */ // If in liquid, the threshold of coming out is at higher y - if (in_liquid) - { + if (in_liquid) { pp = floatToInt(position + v3f(0.0f, BS * 0.1f, 0.0f), BS); node = map->getNode(pp, &is_valid_position); if (is_valid_position) { in_liquid = nodemgr->get(node.getContent()).isLiquid(); - liquid_viscosity = nodemgr->get(node.getContent()).liquid_viscosity; + liquid_viscosity = + nodemgr->get(node.getContent()).liquid_viscosity; } else { in_liquid = false; } @@ -244,13 +235,13 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d, node = map->getNode(pp, &is_valid_position); if (is_valid_position) { in_liquid = nodemgr->get(node.getContent()).isLiquid(); - liquid_viscosity = nodemgr->get(node.getContent()).liquid_viscosity; + liquid_viscosity = + nodemgr->get(node.getContent()).liquid_viscosity; } else { in_liquid = false; } } - /* Check if player is in liquid (the stable value) */ @@ -276,14 +267,16 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d, is_climbing = false; } else { is_climbing = (nodemgr->get(node.getContent()).climbable || - nodemgr->get(node2.getContent()).climbable) && !free_move; + nodemgr->get(node2.getContent()) + .climbable) && + !free_move; } /* Collision uncertainty radius Make it a bit larger than the maximum distance of movement */ - //f32 d = pos_max_d * 1.1; + // f32 d = pos_max_d * 1.1; // A fairly large value in here makes moving smoother f32 d = 0.15f * BS; @@ -292,19 +285,21 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d, // Player object property step height is multiplied by BS in // /src/script/common/c_content.cpp and /src/content_sao.cpp - float player_stepheight = (m_cao == nullptr) ? 0.0f : - (touching_ground ? m_cao->getStepHeight() : (0.2f * BS)); + float player_stepheight = + (m_cao == nullptr) ? 0.0f + : (touching_ground ? m_cao->getStepHeight() + : (0.2f * BS)); v3f accel_f; const v3f initial_position = position; const v3f initial_speed = m_speed; - collisionMoveResult result = collisionMoveSimple(env, m_client, - pos_max_d, m_collisionbox, player_stepheight, dtime, - &position, &m_speed, accel_f, NULL, true, true); + collisionMoveResult result = collisionMoveSimple(env, m_client, pos_max_d, + m_collisionbox, player_stepheight, dtime, &position, &m_speed, + accel_f, NULL, true, true); - bool could_sneak = control.sneak && !free_move && !in_liquid && - !is_climbing && physics_override_sneak; + bool could_sneak = control.sneak && !free_move && !in_liquid && !is_climbing && + physics_override_sneak; // Add new collisions to the vector if (collision_info && !free_move) { @@ -367,10 +362,10 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d, // (BS * 0.6f) is the basic stepheight while standing on ground if (y_diff < BS * 0.6f) { // Only center player when they're on the node - position.X = rangelim(position.X, - bmin.X - sneak_max.X, bmax.X + sneak_max.X); - position.Z = rangelim(position.Z, - bmin.Z - sneak_max.Z, bmax.Z + sneak_max.Z); + position.X = rangelim(position.X, bmin.X - sneak_max.X, + bmax.X + sneak_max.X); + position.Z = rangelim(position.Z, bmin.Z - sneak_max.Z, + bmax.Z + sneak_max.Z); if (position.X != old_pos.X) m_speed.X = 0.0f; @@ -393,8 +388,7 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d, if (m_speed.Y == 0.0f || m_sneak_ladder_detected) sneak_can_jump = true; - if (collision_info && - m_speed.Y - old_speed.Y > BS) { + if (collision_info && m_speed.Y - old_speed.Y > BS) { // Collide with sneak node, report fall damage CollisionInfo sn_info; sn_info.node_p = m_sneak_node; @@ -423,7 +417,8 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d, */ if (!result.standing_on_object && !touching_ground_was && touching_ground) { - m_client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::PLAYER_REGAIN_GROUND)); + m_client->getEventManager()->put( + new SimpleTriggerEvent(MtEvent::PLAYER_REGAIN_GROUND)); // Set camera impact value to be used for view bobbing camera_impact = getSpeed().Y * -1; @@ -443,16 +438,18 @@ 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))); + 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") || - itemgroup_get(f1.groups, "disable_jump"); - m_can_jump = ((touching_ground && !is_climbing) || sneak_can_jump) && !m_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 if (m_can_jump && control.jump && itemgroup_get(f.groups, "bouncy") && - m_speed.Y >= -0.5f * BS) { + m_speed.Y >= -0.5f * BS) { float jumpspeed = movement_speed_jump * physics_override_jump; if (m_speed.Y > 1.0f) { // Reduce boost when speed already is high @@ -461,7 +458,7 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d, m_speed.Y += jumpspeed; } setSpeed(m_speed); - if (! m_freecam) + if (!m_freecam) m_legit_speed = m_speed; m_can_jump = false; } @@ -503,7 +500,8 @@ void LocalPlayer::applyControl(float dtime, Environment *env) bool free_move = fly_allowed && player_settings.free_move; bool fast_move = fast_allowed && player_settings.fast_move; bool pitch_move = (free_move || in_liquid) && player_settings.pitch_move; - // When aux1_descends is enabled the fast key is used to go down, so fast isn't possible + // When aux1_descends is enabled the fast key is used to go down, so fast isn't + // possible bool fast_climb = fast_move && control.aux1 && !player_settings.aux1_descends; bool always_fly_fast = player_settings.always_fly_fast; @@ -580,7 +578,8 @@ void LocalPlayer::applyControl(float dtime, Environment *env) speedH -= v3f(0.0f, 0.0f, 1.0f); if (!control.up && !control.down) - speedH -= v3f(0.0f, 0.0f, 1.0f) * (control.forw_move_joystick_axis / 32767.f); + speedH -= v3f(0.0f, 0.0f, 1.0f) * + (control.forw_move_joystick_axis / 32767.f); if (control.left) speedH += v3f(-1.0f, 0.0f, 0.0f); @@ -589,7 +588,8 @@ void LocalPlayer::applyControl(float dtime, Environment *env) speedH += v3f(1.0f, 0.0f, 0.0f); if (!control.left && !control.right) - speedH += v3f(1.0f, 0.0f, 0.0f) * (control.sidew_move_joystick_axis / 32767.f); + speedH += v3f(1.0f, 0.0f, 0.0f) * + (control.sidew_move_joystick_axis / 32767.f); if (m_autojump) { // release autojump after a given time @@ -621,7 +621,8 @@ void LocalPlayer::applyControl(float dtime, Environment *env) if (speedJ.Y >= -0.5f * BS) { speedJ.Y = movement_speed_jump * physics_override_jump; setSpeed(speedJ); - m_client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::PLAYER_JUMP)); + m_client->getEventManager()->put(new SimpleTriggerEvent( + MtEvent::PLAYER_JUMP)); } } else if (in_liquid && !m_disable_jump) { if (fast_climb) @@ -641,7 +642,8 @@ void LocalPlayer::applyControl(float dtime, Environment *env) if (superspeed || (is_climbing && fast_climb) || ((in_liquid || in_liquid_stable) && fast_climb)) speedH = speedH.normalize() * movement_speed_fast; - else if (control.sneak && !free_move && !in_liquid && !in_liquid_stable && !g_settings->getBool("no_slow")) + else if (control.sneak && !free_move && !in_liquid && !in_liquid_stable && + !g_settings->getBool("no_slow")) speedH = speedH.normalize() * movement_speed_crouch; else speedH = speedH.normalize() * movement_speed_walk; @@ -677,8 +679,8 @@ void LocalPlayer::applyControl(float dtime, Environment *env) // Accelerate to target speed with maximum increment accelerate((speedH + speedV) * physics_override_speed, - incH * physics_override_speed * slip_factor, incV * physics_override_speed, - pitch_move); + incH * physics_override_speed * slip_factor, + incV * physics_override_speed, pitch_move); } v3s16 LocalPlayer::getStandingNodePos() @@ -713,13 +715,15 @@ v3s16 LocalPlayer::getLightPosition() const v3f LocalPlayer::getEyeOffset() const { - float eye_height = camera_barely_in_ceiling ? m_eye_height - 0.125f : m_eye_height; + float eye_height = + camera_barely_in_ceiling ? m_eye_height - 0.125f : m_eye_height; return v3f(0.0f, BS * eye_height, 0.0f); } ClientActiveObject *LocalPlayer::getParent() const { - return (m_cao && ! g_settings->getBool("entity_speed")) ? m_cao->getParent() : nullptr; + return (m_cao && !g_settings->getBool("entity_speed")) ? m_cao->getParent() + : nullptr; } bool LocalPlayer::isDead() const @@ -737,17 +741,19 @@ void LocalPlayer::tryReattach(int id) bool LocalPlayer::isWaitingForReattach() const { - return g_settings->getBool("entity_speed") && m_cao && ! m_cao->getParent() && m_cao->m_waiting_for_reattach > 0; + return g_settings->getBool("entity_speed") && m_cao && !m_cao->getParent() && + m_cao->m_waiting_for_reattach > 0; } // 3D acceleration void LocalPlayer::accelerate(const v3f &target_speed, const f32 max_increase_H, - const f32 max_increase_V, const bool use_pitch) + const f32 max_increase_V, const bool use_pitch) { const f32 yaw = getYaw(); const f32 pitch = getPitch(); v3f flat_speed = m_speed; - // Rotate speed vector by -yaw and -pitch to make it relative to the player's yaw and pitch + // Rotate speed vector by -yaw and -pitch to make it relative to the player's yaw + // and pitch flat_speed.rotateXZBy(-yaw); if (use_pitch) flat_speed.rotateYZBy(-pitch); @@ -784,7 +790,7 @@ void LocalPlayer::accelerate(const v3f &target_speed, const f32 max_increase_H, // Temporary option for old move code void LocalPlayer::old_move(f32 dtime, Environment *env, f32 pos_max_d, - std::vector *collision_info) + std::vector *collision_info) { Map *map = &env->getMap(); const NodeDefManager *nodemgr = m_client->ndef(); @@ -834,7 +840,8 @@ void LocalPlayer::old_move(f32 dtime, Environment *env, f32 pos_max_d, node = map->getNode(pp, &is_valid_position); if (is_valid_position) { in_liquid = nodemgr->get(node.getContent()).isLiquid(); - liquid_viscosity = nodemgr->get(node.getContent()).liquid_viscosity; + liquid_viscosity = + nodemgr->get(node.getContent()).liquid_viscosity; } else { in_liquid = false; } @@ -844,7 +851,8 @@ void LocalPlayer::old_move(f32 dtime, Environment *env, f32 pos_max_d, node = map->getNode(pp, &is_valid_position); if (is_valid_position) { in_liquid = nodemgr->get(node.getContent()).isLiquid(); - liquid_viscosity = nodemgr->get(node.getContent()).liquid_viscosity; + liquid_viscosity = + nodemgr->get(node.getContent()).liquid_viscosity; } else { in_liquid = false; } @@ -873,13 +881,15 @@ void LocalPlayer::old_move(f32 dtime, Environment *env, f32 pos_max_d, is_climbing = false; else is_climbing = (nodemgr->get(node.getContent()).climbable || - nodemgr->get(node2.getContent()).climbable) && !free_move; + nodemgr->get(node2.getContent()) + .climbable) && + !free_move; /* Collision uncertainty radius Make it a bit larger than the maximum distance of movement */ - //f32 d = pos_max_d * 1.1; + // f32 d = pos_max_d * 1.1; // A fairly large value in here makes moving smoother f32 d = 0.15f * BS; // This should always apply, otherwise there are glitches @@ -907,7 +917,8 @@ void LocalPlayer::old_move(f32 dtime, Environment *env, f32 pos_max_d, /* Collision seems broken, since player is sinking when sneaking over the edges of current sneaking_node. - TODO (when fixed): Set Y-speed only to 0 when position.Y < new_y. + TODO (when fixed): Set Y-speed only to 0 when position.Y < + new_y. */ if (m_speed.Y < 0.0f) m_speed.Y = 0.0f; @@ -921,9 +932,9 @@ void LocalPlayer::old_move(f32 dtime, Environment *env, f32 pos_max_d, const v3f initial_position = position; const v3f initial_speed = m_speed; - collisionMoveResult result = collisionMoveSimple(env, m_client, - pos_max_d, m_collisionbox, player_stepheight, dtime, - &position, &m_speed, accel_f, NULL, true, true); + collisionMoveResult result = collisionMoveSimple(env, m_client, pos_max_d, + m_collisionbox, player_stepheight, dtime, &position, &m_speed, + accel_f, NULL, true, true); // Positition was slightly changed; update standing node pos if (touching_ground) @@ -940,7 +951,7 @@ void LocalPlayer::old_move(f32 dtime, Environment *env, f32 pos_max_d, bool touching_ground_was = touching_ground; touching_ground = result.touching_ground; - //bool standing_on_unloaded = result.standing_on_unloaded; + // bool standing_on_unloaded = result.standing_on_unloaded; /* Check the nodes under the player to see from which node the @@ -966,43 +977,50 @@ void LocalPlayer::old_move(f32 dtime, Environment *env, f32 pos_max_d, if (m_need_to_get_new_sneak_node && physics_override_sneak) { m_sneak_node_bb_ymax = 0.0f; - v3s16 pos_i_bottom = floatToInt(position - v3f(0.0f, position_y_mod, 0.0f), BS); + v3s16 pos_i_bottom = floatToInt( + position - v3f(0.0f, position_y_mod, 0.0f), BS); v2f player_p2df(position.X, position.Z); f32 min_distance_f = 100000.0f * BS; // If already seeking from some node, compare to it. v3s16 new_sneak_node = m_sneak_node; - for (s16 x= -1; x <= 1; x++) - for (s16 z= -1; z <= 1; z++) { - v3s16 p = pos_i_bottom + v3s16(x, 0, z); - v3f pf = intToFloat(p, BS); - v2f node_p2df(pf.X, pf.Z); - f32 distance_f = player_p2df.getDistanceFrom(node_p2df); - f32 max_axis_distance_f = MYMAX( - std::fabs(player_p2df.X - node_p2df.X), - std::fabs(player_p2df.Y - node_p2df.Y)); + for (s16 x = -1; x <= 1; x++) + for (s16 z = -1; z <= 1; z++) { + v3s16 p = pos_i_bottom + v3s16(x, 0, z); + v3f pf = intToFloat(p, BS); + v2f node_p2df(pf.X, pf.Z); + f32 distance_f = player_p2df.getDistanceFrom(node_p2df); + f32 max_axis_distance_f = MYMAX( + std::fabs(player_p2df.X - node_p2df.X), + std::fabs(player_p2df.Y - node_p2df.Y)); - if (distance_f > min_distance_f || - max_axis_distance_f > 0.5f * BS + sneak_max + 0.1f * BS) - continue; + if (distance_f > min_distance_f || + max_axis_distance_f > + 0.5f * BS + sneak_max + + 0.1f * BS) + continue; - // The node to be sneaked on has to be walkable - node = map->getNode(p, &is_valid_position); - if (!is_valid_position || !nodemgr->get(node).walkable) - continue; - // And the node above it has to be nonwalkable - node = map->getNode(p + v3s16(0, 1, 0), &is_valid_position); - if (!is_valid_position || nodemgr->get(node).walkable) - continue; - // If not 'sneak_glitch' the node 2 nodes above it has to be nonwalkable - if (!physics_override_sneak_glitch) { - node = map->getNode(p + v3s16(0, 2, 0), &is_valid_position); + // The node to be sneaked on has to be walkable + node = map->getNode(p, &is_valid_position); + if (!is_valid_position || !nodemgr->get(node).walkable) + continue; + // And the node above it has to be nonwalkable + node = map->getNode( + p + v3s16(0, 1, 0), &is_valid_position); if (!is_valid_position || nodemgr->get(node).walkable) continue; - } + // If not 'sneak_glitch' the node 2 nodes above it has to + // be nonwalkable + if (!physics_override_sneak_glitch) { + node = map->getNode(p + v3s16(0, 2, 0), + &is_valid_position); + if (!is_valid_position || + nodemgr->get(node).walkable) + continue; + } - min_distance_f = distance_f; - new_sneak_node = p; - } + min_distance_f = distance_f; + new_sneak_node = p; + } bool sneak_node_found = (min_distance_f < 100000.0f * BS * 0.9f); @@ -1047,7 +1065,8 @@ void LocalPlayer::old_move(f32 dtime, Environment *env, f32 pos_max_d, } if (!result.standing_on_object && !touching_ground_was && touching_ground) { - m_client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::PLAYER_REGAIN_GROUND)); + m_client->getEventManager()->put( + new SimpleTriggerEvent(MtEvent::PLAYER_REGAIN_GROUND)); // Set camera impact value to be used for view bobbing camera_impact = getSpeed().Y * -1.0f; } @@ -1115,8 +1134,8 @@ float LocalPlayer::getSlipFactor(Environment *env, const v3f &speedH) } void LocalPlayer::handleAutojump(f32 dtime, Environment *env, - const collisionMoveResult &result, const v3f &initial_position, - const v3f &initial_speed, f32 pos_max_d) + const collisionMoveResult &result, const v3f &initial_position, + const v3f &initial_speed, f32 pos_max_d) { PlayerSettings &player_settings = getPlayerSettings(); if (!player_settings.autojump) @@ -1126,11 +1145,11 @@ void LocalPlayer::handleAutojump(f32 dtime, Environment *env, return; bool control_forward = control.up || - (!control.up && !control.down && - control.forw_move_joystick_axis < -0.05f); + (!control.up && !control.down && + control.forw_move_joystick_axis < -0.05f); bool could_autojump = - m_can_jump && !control.jump && !control.sneak && control_forward; + m_can_jump && !control.jump && !control.sneak && control_forward; if (!could_autojump) return; @@ -1157,12 +1176,14 @@ void LocalPlayer::handleAutojump(f32 dtime, Environment *env, bool is_position_valid; for (s16 z = ceilpos_min.Z; z <= ceilpos_max.Z; ++z) { for (s16 x = ceilpos_min.X; x <= ceilpos_max.X; ++x) { - MapNode n = env->getMap().getNode(v3s16(x, ceilpos_max.Y, z), &is_position_valid); + MapNode n = env->getMap().getNode( + v3s16(x, ceilpos_max.Y, z), &is_position_valid); if (!is_position_valid) - break; // won't collide with the void outside + break; // won't collide with the void outside if (n.getContent() == CONTENT_IGNORE) - return; // players collide with ignore blocks -> same as walkable + return; // players collide with ignore blocks -> same as + // walkable const ContentFeatures &f = ndef->get(n); if (f.walkable) return; // would bump head, don't jump @@ -1175,7 +1196,8 @@ void LocalPlayer::handleAutojump(f32 dtime, Environment *env, // try at peak of jump, zero step height collisionMoveResult jump_result = collisionMoveSimple(env, m_client, pos_max_d, - m_collisionbox, 0.0f, dtime, &jump_pos, &jump_speed, v3f(0.0f), NULL, true, true); + m_collisionbox, 0.0f, dtime, &jump_pos, &jump_speed, v3f(0.0f), + NULL, true, true); // see if we can get a little bit farther horizontally if we had // jumped @@ -1188,4 +1210,3 @@ void LocalPlayer::handleAutojump(f32 dtime, Environment *env, m_autojump_time = 0.1f; } } - diff --git a/src/client/localplayer.h b/src/client/localplayer.h index 0e071d2b4..8b2d932ab 100644 --- a/src/client/localplayer.h +++ b/src/client/localplayer.h @@ -132,7 +132,7 @@ public: inline void setPosition(const v3f &position) { m_position = position; - if (! m_freecam) + if (!m_freecam) m_legit_position = position; m_sneak_node_exists = false; } @@ -140,7 +140,7 @@ public: v3f getPosition() const { return m_position; } v3f getLegitPosition() const { return m_legit_position; } - + v3f getLegitSpeed() const { return m_legit_speed; } inline void setLegitPosition(const v3f &position) @@ -151,18 +151,15 @@ public: setPosition(position); } - inline void freecamEnable() - { - m_freecam = true; - } - - inline void freecamDisable() + inline void freecamEnable() { m_freecam = true; } + + inline void freecamDisable() { m_freecam = false; setPosition(m_legit_position); setSpeed(m_legit_speed); } - + // Non-transformed eye offset getters // For accurate positions, use the Camera functions v3f getEyePosition() const { return m_position + getEyeOffset(); } @@ -171,7 +168,7 @@ public: void setCollisionbox(const aabb3f &box) { m_collisionbox = box; } - const aabb3f& getCollisionbox() const { return m_collisionbox; } + const aabb3f &getCollisionbox() const { return m_collisionbox; } float getZoomFOV() const { return m_zoom_fov; } void setZoomFOV(float zoom_fov) { m_zoom_fov = zoom_fov; } @@ -180,26 +177,23 @@ public: bool isDead() const; - inline void addVelocity(const v3f &vel) - { - added_velocity += vel; - } - + inline void addVelocity(const v3f &vel) { added_velocity += vel; } + void tryReattach(int id); - + bool isWaitingForReattach() const; - + bool canWalkOn(const ContentFeatures &f); - + private: void accelerate(const v3f &target_speed, const f32 max_increase_H, - const f32 max_increase_V, const bool use_pitch); + const f32 max_increase_V, const bool use_pitch); bool updateSneakNode(Map *map, const v3f &position, const v3f &sneak_max); float getSlipFactor(Environment *env, const v3f &speedH); void handleAutojump(f32 dtime, Environment *env, - const collisionMoveResult &result, - const v3f &position_before_move, const v3f &speed_before_move, - f32 pos_max_d); + const collisionMoveResult &result, + const v3f &position_before_move, const v3f &speed_before_move, + f32 pos_max_d); bool m_freecam = false; v3f m_position; @@ -234,7 +228,7 @@ private: f32 m_pitch = 0.0f; bool camera_barely_in_ceiling = false; aabb3f m_collisionbox = aabb3f(-BS * 0.30f, 0.0f, -BS * 0.30f, BS * 0.30f, - BS * 1.75f, BS * 0.30f); + BS * 1.75f, BS * 0.30f); float m_eye_height = 1.625f; float m_zoom_fov = 0.0f; bool m_autojump = false; diff --git a/src/client/mapblock_mesh.cpp b/src/client/mapblock_mesh.cpp index fbd7e2ab7..9487eae22 100644 --- a/src/client/mapblock_mesh.cpp +++ b/src/client/mapblock_mesh.cpp @@ -35,40 +35,40 @@ with this program; if not, write to the Free Software Foundation, Inc., MeshMakeData */ -MeshMakeData::MeshMakeData(Client *client, bool use_shaders, - bool use_tangent_vertices): - m_client(client), - m_use_shaders(use_shaders), - m_use_tangent_vertices(use_tangent_vertices) -{} +MeshMakeData::MeshMakeData(Client *client, bool use_shaders, bool use_tangent_vertices) : + m_client(client), m_use_shaders(use_shaders), + m_use_tangent_vertices(use_tangent_vertices) +{ +} void MeshMakeData::fillBlockDataBegin(const v3s16 &blockpos) { m_blockpos = blockpos; - v3s16 blockpos_nodes = m_blockpos*MAP_BLOCKSIZE; + v3s16 blockpos_nodes = m_blockpos * MAP_BLOCKSIZE; m_vmanip.clear(); - VoxelArea voxel_area(blockpos_nodes - v3s16(1,1,1) * MAP_BLOCKSIZE, - blockpos_nodes + v3s16(1,1,1) * MAP_BLOCKSIZE*2-v3s16(1,1,1)); + VoxelArea voxel_area(blockpos_nodes - v3s16(1, 1, 1) * MAP_BLOCKSIZE, + blockpos_nodes + v3s16(1, 1, 1) * MAP_BLOCKSIZE * 2 - + v3s16(1, 1, 1)); m_vmanip.addArea(voxel_area); } void MeshMakeData::fillBlockData(const v3s16 &block_offset, MapNode *data) { v3s16 data_size(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE); - VoxelArea data_area(v3s16(0,0,0), data_size - v3s16(1,1,1)); + VoxelArea data_area(v3s16(0, 0, 0), data_size - v3s16(1, 1, 1)); v3s16 bp = m_blockpos + block_offset; v3s16 blockpos_nodes = bp * MAP_BLOCKSIZE; - m_vmanip.copyFrom(data, data_area, v3s16(0,0,0), blockpos_nodes, data_size); + m_vmanip.copyFrom(data, data_area, v3s16(0, 0, 0), blockpos_nodes, data_size); } void MeshMakeData::fill(MapBlock *block) { fillBlockDataBegin(block->getPos()); - fillBlockData(v3s16(0,0,0), block->getData()); + fillBlockData(v3s16(0, 0, 0), block->getData()); // Get map for reading neighbor blocks Map *map = block->getParent(); @@ -76,20 +76,21 @@ void MeshMakeData::fill(MapBlock *block) for (const v3s16 &dir : g_26dirs) { v3s16 bp = m_blockpos + dir; MapBlock *b = map->getBlockNoCreateNoEx(bp); - if(b) + if (b) fillBlockData(dir, b->getData()); } } void MeshMakeData::fillSingleNode(MapNode *node) { - m_blockpos = v3s16(0,0,0); + m_blockpos = v3s16(0, 0, 0); - v3s16 blockpos_nodes = v3s16(0,0,0); - VoxelArea area(blockpos_nodes-v3s16(1,1,1)*MAP_BLOCKSIZE, - blockpos_nodes+v3s16(1,1,1)*MAP_BLOCKSIZE*2-v3s16(1,1,1)); + v3s16 blockpos_nodes = v3s16(0, 0, 0); + VoxelArea area(blockpos_nodes - v3s16(1, 1, 1) * MAP_BLOCKSIZE, + blockpos_nodes + v3s16(1, 1, 1) * MAP_BLOCKSIZE * 2 - + v3s16(1, 1, 1)); s32 volume = area.getVolume(); - s32 our_node_index = area.index(1,1,1); + s32 our_node_index = area.index(1, 1, 1); // Allocate this block + neighbors m_vmanip.clear(); @@ -97,8 +98,7 @@ void MeshMakeData::fillSingleNode(MapNode *node) // Fill in data MapNode *data = new MapNode[volume]; - for(s32 i = 0; i < volume; i++) - { + for (s32 i = 0; i < volume; i++) { if (i == our_node_index) data[i] = *node; else @@ -111,12 +111,12 @@ void MeshMakeData::fillSingleNode(MapNode *node) void MeshMakeData::setCrack(int crack_level, v3s16 crack_pos) { if (crack_level >= 0) - m_crack_pos_relative = crack_pos - m_blockpos*MAP_BLOCKSIZE; + m_crack_pos_relative = crack_pos - m_blockpos * MAP_BLOCKSIZE; } void MeshMakeData::setSmoothLighting(bool smooth_lighting) { - m_smooth_lighting = smooth_lighting && ! g_settings->getBool("fullbright"); + m_smooth_lighting = smooth_lighting && !g_settings->getBool("fullbright"); } /* @@ -127,13 +127,13 @@ void MeshMakeData::setSmoothLighting(bool smooth_lighting) Calculate non-smooth lighting at interior of node. Single light bank. */ -static u8 getInteriorLight(enum LightBank bank, MapNode n, s32 increment, - const NodeDefManager *ndef) +static u8 getInteriorLight( + enum LightBank bank, MapNode n, s32 increment, const NodeDefManager *ndef) { u8 light = n.getLight(bank, ndef); if (light > 0) light = rangelim(light + increment, 0, LIGHT_SUN); - if(g_settings->getBool("fullbright")) + if (g_settings->getBool("fullbright")) return 255; return decode_light(light); } @@ -153,23 +153,22 @@ u16 getInteriorLight(MapNode n, s32 increment, const NodeDefManager *ndef) Calculate non-smooth lighting at face of node. Single light bank. */ -static u8 getFaceLight(enum LightBank bank, MapNode n, MapNode n2, - v3s16 face_dir, const NodeDefManager *ndef) +static u8 getFaceLight(enum LightBank bank, MapNode n, MapNode n2, v3s16 face_dir, + const NodeDefManager *ndef) { u8 light; u8 l1 = n.getLight(bank, ndef); u8 l2 = n2.getLight(bank, ndef); - if(l1 > l2) + if (l1 > l2) light = l1; else light = l2; // Boost light level for light sources - u8 light_source = MYMAX(ndef->get(n).light_source, - ndef->get(n2).light_source); - if(light_source > light) + u8 light_source = MYMAX(ndef->get(n).light_source, ndef->get(n2).light_source); + if (light_source > light) light = light_source; - if(g_settings->getBool("fullbright")) + if (g_settings->getBool("fullbright")) return 255; return decode_light(light); } @@ -178,8 +177,7 @@ static u8 getFaceLight(enum LightBank bank, MapNode n, MapNode n2, Calculate non-smooth lighting at face of node. Both light banks. */ -u16 getFaceLight(MapNode n, MapNode n2, const v3s16 &face_dir, - const NodeDefManager *ndef) +u16 getFaceLight(MapNode n, MapNode n2, const v3s16 &face_dir, const NodeDefManager *ndef) { u16 day = getFaceLight(LIGHTBANK_DAY, n, n2, face_dir, ndef); u16 night = getFaceLight(LIGHTBANK_NIGHT, n, n2, face_dir, ndef); @@ -190,8 +188,8 @@ u16 getFaceLight(MapNode n, MapNode n2, const v3s16 &face_dir, Calculate smooth lighting at the XYZ- corner of p. Both light banks */ -static u16 getSmoothLightCombined(const v3s16 &p, - const std::array &dirs, MeshMakeData *data) +static u16 getSmoothLightCombined( + const v3s16 &p, const std::array &dirs, MeshMakeData *data) { const NodeDefManager *ndef = data->m_client->ndef(); @@ -202,7 +200,7 @@ static u16 getSmoothLightCombined(const v3s16 &p, u16 light_night = 0; bool direct_sunlight = false; - auto add_node = [&] (u8 i, bool obstructed = false) -> bool { + auto add_node = [&](u8 i, bool obstructed = false) -> bool { if (obstructed) { ambient_occlusion++; return false; @@ -228,7 +226,7 @@ static u16 getSmoothLightCombined(const v3s16 &p, return f.light_propagates; }; - bool obstructed[4] = { true, true, true, true }; + bool obstructed[4] = {true, true, true, true}; add_node(0); bool opaque1 = !add_node(1); bool opaque2 = !add_node(2); @@ -264,31 +262,34 @@ static u16 getSmoothLightCombined(const v3s16 &p, } bool skip_ambient_occlusion_night = false; - if(decode_light(light_source_max) >= light_night) { + if (decode_light(light_source_max) >= light_night) { light_night = decode_light(light_source_max); skip_ambient_occlusion_night = true; } if (ambient_occlusion > 4) { - static thread_local const float ao_gamma = rangelim( - g_settings->getFloat("ambient_occlusion_gamma"), 0.25, 4.0); + static thread_local const float ao_gamma = + rangelim(g_settings->getFloat("ambient_occlusion_gamma"), + 0.25, 4.0); // Table of gamma space multiply factors. static thread_local const float light_amount[3] = { - powf(0.75, 1.0 / ao_gamma), - powf(0.5, 1.0 / ao_gamma), - powf(0.25, 1.0 / ao_gamma) - }; + powf(0.75, 1.0 / ao_gamma), powf(0.5, 1.0 / ao_gamma), + powf(0.25, 1.0 / ao_gamma)}; - //calculate table index for gamma space multiplier + // calculate table index for gamma space multiplier ambient_occlusion -= 5; if (!skip_ambient_occlusion_day) - light_day = rangelim(core::round32( - light_day * light_amount[ambient_occlusion]), 0, 255); + light_day = rangelim( + core::round32(light_day * + light_amount[ambient_occlusion]), + 0, 255); if (!skip_ambient_occlusion_night) - light_night = rangelim(core::round32( - light_night * light_amount[ambient_occlusion]), 0, 255); + light_night = rangelim( + core::round32(light_night * + light_amount[ambient_occlusion]), + 0, 255); } return light_day | (light_night << 8); @@ -299,7 +300,8 @@ static u16 getSmoothLightCombined(const v3s16 &p, Both light banks. Node at p is solid, and thus the lighting is face-dependent. */ -u16 getSmoothLightSolid(const v3s16 &p, const v3s16 &face_dir, const v3s16 &corner, MeshMakeData *data) +u16 getSmoothLightSolid(const v3s16 &p, const v3s16 &face_dir, const v3s16 &corner, + MeshMakeData *data) { return getSmoothLightTransparent(p + face_dir, corner - 2 * face_dir, data); } @@ -311,23 +313,19 @@ u16 getSmoothLightSolid(const v3s16 &p, const v3s16 &face_dir, const v3s16 &corn */ u16 getSmoothLightTransparent(const v3s16 &p, const v3s16 &corner, MeshMakeData *data) { - const std::array dirs = {{ - // Always shine light - v3s16(0,0,0), - v3s16(corner.X,0,0), - v3s16(0,corner.Y,0), - v3s16(0,0,corner.Z), + const std::array dirs = {{// Always shine light + v3s16(0, 0, 0), v3s16(corner.X, 0, 0), v3s16(0, corner.Y, 0), + v3s16(0, 0, corner.Z), - // Can be obstructed - v3s16(corner.X,corner.Y,0), - v3s16(corner.X,0,corner.Z), - v3s16(0,corner.Y,corner.Z), - v3s16(corner.X,corner.Y,corner.Z) - }}; + // Can be obstructed + v3s16(corner.X, corner.Y, 0), v3s16(corner.X, 0, corner.Z), + v3s16(0, corner.Y, corner.Z), + v3s16(corner.X, corner.Y, corner.Z)}}; return getSmoothLightCombined(p, dirs, data); } -void get_sunlight_color(video::SColorf *sunlight, u32 daynight_ratio){ +void get_sunlight_color(video::SColorf *sunlight, u32 daynight_ratio) +{ f32 rg = daynight_ratio / 1000.0f - 0.04f; f32 b = (0.98f * daynight_ratio) / 1000.0f + 0.078f; sunlight->r = rg; @@ -335,17 +333,15 @@ void get_sunlight_color(video::SColorf *sunlight, u32 daynight_ratio){ sunlight->b = b; } -void final_color_blend(video::SColor *result, - u16 light, u32 daynight_ratio) +void final_color_blend(video::SColor *result, u16 light, u32 daynight_ratio) { video::SColorf dayLight; get_sunlight_color(&dayLight, daynight_ratio); - final_color_blend(result, - encode_light(light, 0), dayLight); + final_color_blend(result, encode_light(light, 0), dayLight); } -void final_color_blend(video::SColor *result, - const video::SColor &data, const video::SColorf &dayLight) +void final_color_blend(video::SColor *result, const video::SColor &data, + const video::SColorf &dayLight) { static const video::SColorf artificialColor(1.04f, 1.04f, 1.04f); @@ -359,16 +355,48 @@ void final_color_blend(video::SColor *result, // Emphase blue a bit in darker places // Each entry of this array represents a range of 8 blue levels static const u8 emphase_blue_when_dark[32] = { - 1, 4, 6, 6, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, + 4, + 6, + 6, + 6, + 5, + 4, + 3, + 2, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, }; - b += emphase_blue_when_dark[irr::core::clamp((s32) ((r + g + b) / 3 * 255), - 0, 255) / 8] / 255.0f; + b += emphase_blue_when_dark[irr::core::clamp((s32)((r + g + b) / 3 * 255), 0, + 255) / + 8] / + 255.0f; - result->setRed(core::clamp((s32) (r * 255.0f), 0, 255)); - result->setGreen(core::clamp((s32) (g * 255.0f), 0, 255)); - result->setBlue(core::clamp((s32) (b * 255.0f), 0, 255)); + result->setRed(core::clamp((s32)(r * 255.0f), 0, 255)); + result->setGreen(core::clamp((s32)(g * 255.0f), 0, 255)); + result->setBlue(core::clamp((s32)(b * 255.0f), 0, 255)); } /* @@ -379,27 +407,20 @@ void final_color_blend(video::SColor *result, // 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) -}; + // ( 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] @@ -425,26 +446,27 @@ static void getNodeVertexDirs(const v3s16 &dir, v3s16 *vertex_dirs) 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) +static void getNodeTextureCoords( + v3f base, const v3f &scale, const v3s16 &dir, float *u, float *v) { if (dir.X > 0 || dir.Y > 0 || dir.Z < 0) base -= scale; - if (dir == v3s16(0,0,1)) { + if (dir == v3s16(0, 0, 1)) { *u = -base.X - 1; *v = -base.Y - 1; - } else if (dir == v3s16(0,0,-1)) { + } else if (dir == v3s16(0, 0, -1)) { *u = base.X + 1; *v = -base.Y - 2; - } else if (dir == v3s16(1,0,0)) { + } else if (dir == v3s16(1, 0, 0)) { *u = base.Z + 1; *v = -base.Y - 2; - } else if (dir == v3s16(-1,0,0)) { + } else if (dir == v3s16(-1, 0, 0)) { *u = -base.Z - 1; *v = -base.Y - 1; - } else if (dir == v3s16(0,1,0)) { + } else if (dir == v3s16(0, 1, 0)) { *u = base.X + 1; *v = -base.Z - 2; - } else if (dir == v3s16(0,-1,0)) { + } else if (dir == v3s16(0, -1, 0)) { *u = base.X; *v = base.Z; } @@ -463,7 +485,8 @@ struct FastFace }; static void makeFastFace(const TileSpec &tile, u16 li0, u16 li1, u16 li2, u16 li3, - const v3f &tp, const v3f &p, const v3s16 &dir, const v3f &scale, std::vector &dest) + const v3f &tp, const v3f &p, const v3s16 &dir, const v3f &scale, + std::vector &dest) { // Position is at the center of the cube. v3f pos = p * BS; @@ -484,51 +507,51 @@ static void makeFastFace(const TileSpec &tile, u16 li0, u16 li1, u16 li2, u16 li switch (tile.rotation) { case 0: break; - case 1: //R90 + case 1: // R90 t = vertex_dirs[0]; vertex_dirs[0] = vertex_dirs[3]; vertex_dirs[3] = vertex_dirs[2]; vertex_dirs[2] = vertex_dirs[1]; vertex_dirs[1] = t; - t1 = li0; + t1 = li0; li0 = li3; li3 = li2; li2 = li1; li1 = t1; break; - case 2: //R180 + case 2: // R180 t = vertex_dirs[0]; vertex_dirs[0] = vertex_dirs[2]; vertex_dirs[2] = t; t = vertex_dirs[1]; vertex_dirs[1] = vertex_dirs[3]; vertex_dirs[3] = t; - t1 = li0; + t1 = li0; li0 = li2; li2 = t1; - t1 = li1; + t1 = li1; li1 = li3; li3 = t1; break; - case 3: //R270 + case 3: // R270 t = vertex_dirs[0]; vertex_dirs[0] = vertex_dirs[1]; vertex_dirs[1] = vertex_dirs[2]; vertex_dirs[2] = vertex_dirs[3]; vertex_dirs[3] = t; - t1 = li0; + t1 = li0; li0 = li1; li1 = li2; li2 = li3; li3 = t1; break; - case 4: //FXR90 + case 4: // FXR90 t = vertex_dirs[0]; vertex_dirs[0] = vertex_dirs[3]; vertex_dirs[3] = vertex_dirs[2]; vertex_dirs[2] = vertex_dirs[1]; vertex_dirs[1] = t; - t1 = li0; + t1 = li0; li0 = li3; li3 = li2; li2 = li1; @@ -536,13 +559,13 @@ static void makeFastFace(const TileSpec &tile, u16 li0, u16 li1, u16 li2, u16 li y0 += h; h *= -1; break; - case 5: //FXR270 + case 5: // FXR270 t = vertex_dirs[0]; vertex_dirs[0] = vertex_dirs[1]; vertex_dirs[1] = vertex_dirs[2]; vertex_dirs[2] = vertex_dirs[3]; vertex_dirs[3] = t; - t1 = li0; + t1 = li0; li0 = li1; li1 = li2; li2 = li3; @@ -550,13 +573,13 @@ static void makeFastFace(const TileSpec &tile, u16 li0, u16 li1, u16 li2, u16 li y0 += h; h *= -1; break; - case 6: //FYR90 + case 6: // FYR90 t = vertex_dirs[0]; vertex_dirs[0] = vertex_dirs[3]; vertex_dirs[3] = vertex_dirs[2]; vertex_dirs[2] = vertex_dirs[1]; vertex_dirs[1] = t; - t1 = li0; + t1 = li0; li0 = li3; li3 = li2; li2 = li1; @@ -564,13 +587,13 @@ static void makeFastFace(const TileSpec &tile, u16 li0, u16 li1, u16 li2, u16 li x0 += w; w *= -1; break; - case 7: //FYR270 + case 7: // FYR270 t = vertex_dirs[0]; vertex_dirs[0] = vertex_dirs[1]; vertex_dirs[1] = vertex_dirs[2]; vertex_dirs[2] = vertex_dirs[3]; vertex_dirs[3] = t; - t1 = li0; + t1 = li0; li0 = li1; li1 = li2; li2 = li3; @@ -578,11 +601,11 @@ static void makeFastFace(const TileSpec &tile, u16 li0, u16 li1, u16 li2, u16 li x0 += w; w *= -1; break; - case 8: //FX + case 8: // FX y0 += h; h *= -1; break; - case 9: //FY + case 9: // FY x0 += w; w *= -1; break; @@ -591,11 +614,8 @@ static void makeFastFace(const TileSpec &tile, u16 li0, u16 li1, u16 li2, u16 li } for (u16 i = 0; i < 4; i++) { - vertex_pos[i] = v3f( - BS / 2 * vertex_dirs[i].X, - BS / 2 * vertex_dirs[i].Y, - BS / 2 * vertex_dirs[i].Z - ); + vertex_pos[i] = v3f(BS / 2 * vertex_dirs[i].X, BS / 2 * vertex_dirs[i].Y, + BS / 2 * vertex_dirs[i].Z); } for (v3f &vpos : vertex_pos) { @@ -606,13 +626,16 @@ static void makeFastFace(const TileSpec &tile, u16 li0, u16 li1, u16 li2, u16 li } f32 abs_scale = 1.0f; - if (scale.X < 0.999f || scale.X > 1.001f) abs_scale = scale.X; - else if (scale.Y < 0.999f || scale.Y > 1.001f) abs_scale = scale.Y; - else if (scale.Z < 0.999f || scale.Z > 1.001f) abs_scale = scale.Z; + if (scale.X < 0.999f || scale.X > 1.001f) + abs_scale = scale.X; + else if (scale.Y < 0.999f || scale.Y > 1.001f) + abs_scale = scale.Y; + else if (scale.Z < 0.999f || scale.Z > 1.001f) + abs_scale = scale.Z; v3f normal(dir.X, dir.Y, dir.Z); - u16 li[4] = { li0, li1, li2, li3 }; + u16 li[4] = {li0, li1, li2, li3}; u16 day[4]; u16 night[4]; @@ -621,18 +644,16 @@ static void makeFastFace(const TileSpec &tile, u16 li0, u16 li1, u16 li2, u16 li night[i] = li[i] & 0xFF; } - bool vertex_0_2_connected = abs(day[0] - day[2]) + abs(night[0] - night[2]) - < abs(day[1] - day[3]) + abs(night[1] - night[3]); + bool vertex_0_2_connected = abs(day[0] - day[2]) + abs(night[0] - night[2]) < + abs(day[1] - day[3]) + abs(night[1] - night[3]); - v2f32 f[4] = { - core::vector2d(x0 + w * abs_scale, y0 + h), - core::vector2d(x0, y0 + h), - core::vector2d(x0, y0), - core::vector2d(x0 + w * abs_scale, y0) }; + v2f32 f[4] = {core::vector2d(x0 + w * abs_scale, y0 + h), + core::vector2d(x0, y0 + h), core::vector2d(x0, y0), + core::vector2d(x0 + w * abs_scale, y0)}; // equivalent to dest.push_back(FastFace()) but faster dest.emplace_back(); - FastFace& face = *dest.rbegin(); + FastFace &face = *dest.rbegin(); for (u8 i = 0; i < 4; i++) { video::SColor c = encode_light(li[i], tile.emissive_light); @@ -661,8 +682,8 @@ static void makeFastFace(const TileSpec &tile, u16 li0, u16 li1, u16 li2, u16 li TODO: Add 3: Both faces drawn with backface culling, remove equivalent */ -static u8 face_contents(content_t m1, content_t m2, bool *equivalent, - const NodeDefManager *ndef) +static u8 face_contents( + content_t m1, content_t m2, bool *equivalent, const NodeDefManager *ndef) { *equivalent = false; @@ -679,7 +700,6 @@ static u8 face_contents(content_t m1, content_t m2, bool *equivalent, u8 c1 = f1.solidness; u8 c2 = f2.solidness; - if (c1 == c2) return 0; @@ -688,7 +708,6 @@ static u8 face_contents(content_t m1, content_t m2, bool *equivalent, else if (c2 == 0) c2 = f2.visual_solidness; - if (c1 == c2) { *equivalent = true; // If same solidness, liquid takes precense @@ -707,7 +726,8 @@ static u8 face_contents(content_t m1, content_t m2, bool *equivalent, /* Gets nth node tile (0 <= n <= 5). */ -void getNodeTileN(MapNode mn, const v3s16 &p, u8 tileindex, MeshMakeData *data, TileSpec &tile) +void getNodeTileN(MapNode mn, const v3s16 &p, u8 tileindex, MeshMakeData *data, + TileSpec &tile) { const NodeDefManager *ndef = data->m_client->ndef(); const ContentFeatures &f = ndef->get(mn); @@ -727,7 +747,8 @@ void getNodeTileN(MapNode mn, const v3s16 &p, u8 tileindex, MeshMakeData *data, /* Gets node tile given a face direction. */ -void getNodeTile(MapNode mn, const v3s16 &p, const v3s16 &dir, MeshMakeData *data, TileSpec &tile) +void getNodeTile(MapNode mn, const v3s16 &p, const v3s16 &dir, MeshMakeData *data, + TileSpec &tile) { const NodeDefManager *ndef = data->m_client->ndef(); @@ -749,38 +770,43 @@ void getNodeTile(MapNode mn, const v3s16 &p, const v3s16 &dir, MeshMakeData *dat // Get rotation for things like chests u8 facedir = mn.getFaceDir(ndef, true); - static const u16 dir_to_tile[24 * 16] = - { - // 0 +X +Y +Z -Z -Y -X -> value=tile,rotation - 0,0, 2,0 , 0,0 , 4,0 , 0,0, 5,0 , 1,0 , 3,0 , // rotate around y+ 0 - 3 - 0,0, 4,0 , 0,3 , 3,0 , 0,0, 2,0 , 1,1 , 5,0 , - 0,0, 3,0 , 0,2 , 5,0 , 0,0, 4,0 , 1,2 , 2,0 , - 0,0, 5,0 , 0,1 , 2,0 , 0,0, 3,0 , 1,3 , 4,0 , + static const u16 dir_to_tile[24 * 16] = {// 0 +X +Y +Z -Z -Y + // -X -> value=tile,rotation + 0, 0, 2, 0, 0, 0, 4, 0, 0, 0, 5, 0, 1, 0, 3, + 0, // rotate around y+ 0 - 3 + 0, 0, 4, 0, 0, 3, 3, 0, 0, 0, 2, 0, 1, 1, 5, 0, 0, 0, 3, 0, 0, 2, + 5, 0, 0, 0, 4, 0, 1, 2, 2, 0, 0, 0, 5, 0, 0, 1, 2, 0, 0, 0, 3, 0, + 1, 3, 4, 0, - 0,0, 2,3 , 5,0 , 0,2 , 0,0, 1,0 , 4,2 , 3,1 , // rotate around z+ 4 - 7 - 0,0, 4,3 , 2,0 , 0,1 , 0,0, 1,1 , 3,2 , 5,1 , - 0,0, 3,3 , 4,0 , 0,0 , 0,0, 1,2 , 5,2 , 2,1 , - 0,0, 5,3 , 3,0 , 0,3 , 0,0, 1,3 , 2,2 , 4,1 , + 0, 0, 2, 3, 5, 0, 0, 2, 0, 0, 1, 0, 4, 2, 3, + 1, // rotate around z+ 4 - 7 + 0, 0, 4, 3, 2, 0, 0, 1, 0, 0, 1, 1, 3, 2, 5, 1, 0, 0, 3, 3, 4, 0, + 0, 0, 0, 0, 1, 2, 5, 2, 2, 1, 0, 0, 5, 3, 3, 0, 0, 3, 0, 0, 1, 3, + 2, 2, 4, 1, - 0,0, 2,1 , 4,2 , 1,2 , 0,0, 0,0 , 5,0 , 3,3 , // rotate around z- 8 - 11 - 0,0, 4,1 , 3,2 , 1,3 , 0,0, 0,3 , 2,0 , 5,3 , - 0,0, 3,1 , 5,2 , 1,0 , 0,0, 0,2 , 4,0 , 2,3 , - 0,0, 5,1 , 2,2 , 1,1 , 0,0, 0,1 , 3,0 , 4,3 , + 0, 0, 2, 1, 4, 2, 1, 2, 0, 0, 0, 0, 5, 0, 3, + 3, // rotate around z- 8 - 11 + 0, 0, 4, 1, 3, 2, 1, 3, 0, 0, 0, 3, 2, 0, 5, 3, 0, 0, 3, 1, 5, 2, + 1, 0, 0, 0, 0, 2, 4, 0, 2, 3, 0, 0, 5, 1, 2, 2, 1, 1, 0, 0, 0, 1, + 3, 0, 4, 3, - 0,0, 0,3 , 3,3 , 4,1 , 0,0, 5,3 , 2,3 , 1,3 , // rotate around x+ 12 - 15 - 0,0, 0,2 , 5,3 , 3,1 , 0,0, 2,3 , 4,3 , 1,0 , - 0,0, 0,1 , 2,3 , 5,1 , 0,0, 4,3 , 3,3 , 1,1 , - 0,0, 0,0 , 4,3 , 2,1 , 0,0, 3,3 , 5,3 , 1,2 , + 0, 0, 0, 3, 3, 3, 4, 1, 0, 0, 5, 3, 2, 3, 1, + 3, // rotate around x+ 12 - 15 + 0, 0, 0, 2, 5, 3, 3, 1, 0, 0, 2, 3, 4, 3, 1, 0, 0, 0, 0, 1, 2, 3, + 5, 1, 0, 0, 4, 3, 3, 3, 1, 1, 0, 0, 0, 0, 4, 3, 2, 1, 0, 0, 3, 3, + 5, 3, 1, 2, - 0,0, 1,1 , 2,1 , 4,3 , 0,0, 5,1 , 3,1 , 0,1 , // rotate around x- 16 - 19 - 0,0, 1,2 , 4,1 , 3,3 , 0,0, 2,1 , 5,1 , 0,0 , - 0,0, 1,3 , 3,1 , 5,3 , 0,0, 4,1 , 2,1 , 0,3 , - 0,0, 1,0 , 5,1 , 2,3 , 0,0, 3,1 , 4,1 , 0,2 , + 0, 0, 1, 1, 2, 1, 4, 3, 0, 0, 5, 1, 3, 1, 0, + 1, // rotate around x- 16 - 19 + 0, 0, 1, 2, 4, 1, 3, 3, 0, 0, 2, 1, 5, 1, 0, 0, 0, 0, 1, 3, 3, 1, + 5, 3, 0, 0, 4, 1, 2, 1, 0, 3, 0, 0, 1, 0, 5, 1, 2, 3, 0, 0, 3, 1, + 4, 1, 0, 2, - 0,0, 3,2 , 1,2 , 4,2 , 0,0, 5,2 , 0,2 , 2,2 , // rotate around y- 20 - 23 - 0,0, 5,2 , 1,3 , 3,2 , 0,0, 2,2 , 0,1 , 4,2 , - 0,0, 2,2 , 1,0 , 5,2 , 0,0, 4,2 , 0,0 , 3,2 , - 0,0, 4,2 , 1,1 , 2,2 , 0,0, 3,2 , 0,3 , 5,2 + 0, 0, 3, 2, 1, 2, 4, 2, 0, 0, 5, 2, 0, 2, 2, + 2, // rotate around y- 20 - 23 + 0, 0, 5, 2, 1, 3, 3, 2, 0, 0, 2, 2, 0, 1, 4, 2, 0, 0, 2, 2, 1, 0, + 5, 2, 0, 0, 4, 2, 0, 0, 3, 2, 0, 0, 4, 2, 1, 1, 2, 2, 0, 0, 3, 2, + 0, 3, 5, 2 }; u16 tile_index = facedir * 16 + dir_i; @@ -795,7 +821,7 @@ std::set splitToContentT(std::string str, const NodeDefManager *ndef) std::string buf; for (char c : str) { if (c == ',' || c == '\n') { - if (! buf.empty()) { + if (!buf.empty()) { dat.insert(ndef->getId(buf)); } buf.clear(); @@ -808,24 +834,16 @@ std::set splitToContentT(std::string str, const NodeDefManager *ndef) static void getTileInfo( // Input: - MeshMakeData *data, - const v3s16 &p, - const v3s16 &face_dir, + MeshMakeData *data, const v3s16 &p, const v3s16 &face_dir, // Output: - bool &makes_face, - v3s16 &p_corrected, - v3s16 &face_dir_corrected, - u16 *lights, - u8 &waving, - TileSpec &tile, - bool xray, - std::set xraySet - ) + bool &makes_face, v3s16 &p_corrected, v3s16 &face_dir_corrected, + u16 *lights, u8 &waving, TileSpec &tile, bool xray, + std::set xraySet) { VoxelManipulator &vmanip = data->m_vmanip; const NodeDefManager *ndef = data->m_client->ndef(); v3s16 blockpos_nodes = data->m_blockpos * MAP_BLOCKSIZE; - + const MapNode &n0 = vmanip.getNodeRefUnsafe(blockpos_nodes + p); content_t c0 = n0.getContent(); @@ -838,7 +856,8 @@ static void getTileInfo( return; } - const MapNode &n1 = vmanip.getNodeRefUnsafeCheckFlags(blockpos_nodes + p + face_dir); + const MapNode &n1 = + vmanip.getNodeRefUnsafeCheckFlags(blockpos_nodes + p + face_dir); content_t c1 = n1.getContent(); if (xray && xraySet.find(c1) != xraySet.end()) @@ -851,8 +870,7 @@ static void getTileInfo( // This is hackish bool equivalent = false; - u8 mf = face_contents(c0, c1, - &equivalent, ndef); + u8 mf = face_contents(c0, c1, &equivalent, ndef); if (mf == 0) { makes_face = false; @@ -892,7 +910,8 @@ static void getTileInfo( v3s16 light_p = blockpos_nodes + p_corrected; for (u16 i = 0; i < 4; i++) - lights[i] = getSmoothLightSolid(light_p, face_dir_corrected, vertex_dirs[i], data); + lights[i] = getSmoothLightSolid(light_p, face_dir_corrected, + vertex_dirs[i], data); } } @@ -901,19 +920,13 @@ static void getTileInfo( translate_dir: unit vector with only one of x, y or z face_dir: unit vector with only one of x, y or z */ -static void updateFastFaceRow( - MeshMakeData *data, - const v3s16 &&startpos, - v3s16 translate_dir, - const v3f &&translate_dir_f, - const v3s16 &&face_dir, - std::vector &dest, - bool xray, - std::set xraySet) +static void updateFastFaceRow(MeshMakeData *data, const v3s16 &&startpos, + v3s16 translate_dir, const v3f &&translate_dir_f, const v3s16 &&face_dir, + std::vector &dest, bool xray, std::set xraySet) { static thread_local const bool waving_liquids = - g_settings->getBool("enable_shaders") && - g_settings->getBool("enable_waving_water"); + g_settings->getBool("enable_shaders") && + g_settings->getBool("enable_waving_water"); v3s16 p = startpos; @@ -927,8 +940,7 @@ static void updateFastFaceRow( TileSpec tile; // Get info of first tile - getTileInfo(data, p, face_dir, - makes_face, p_corrected, face_dir_corrected, + getTileInfo(data, p, face_dir, makes_face, p_corrected, face_dir_corrected, lights, waving, tile, xray, xraySet); // Unroll this variable which has a significant build cost @@ -947,21 +959,17 @@ static void updateFastFaceRow( if (j != MAP_BLOCKSIZE - 1) { p += translate_dir; - getTileInfo(data, p, face_dir, - next_makes_face, next_p_corrected, - next_face_dir_corrected, next_lights, - waving, - next_tile, - xray, - xraySet); + getTileInfo(data, p, face_dir, next_makes_face, next_p_corrected, + next_face_dir_corrected, next_lights, waving, + next_tile, xray, xraySet); - if (next_makes_face == makes_face - && next_p_corrected == p_corrected + translate_dir - && next_face_dir_corrected == face_dir_corrected - && memcmp(next_lights, lights, sizeof(lights)) == 0 + if (next_makes_face == makes_face && + next_p_corrected == p_corrected + translate_dir && + next_face_dir_corrected == face_dir_corrected && + memcmp(next_lights, lights, sizeof(lights)) == 0 // Don't apply fast faces to waving water. - && (waving != 3 || !waving_liquids) - && next_tile.isTileable(tile)) { + && (waving != 3 || !waving_liquids) && + next_tile.isTileable(tile)) { next_is_different = false; continuous_tiles_count++; } @@ -974,8 +982,9 @@ static void updateFastFaceRow( // Floating point conversion of the position vector v3f pf(p_corrected.X, p_corrected.Y, p_corrected.Z); // Center point of face (kind of) - v3f sp = pf - ((f32)continuous_tiles_count * 0.5f - 0.5f) - * translate_dir_f; + v3f sp = pf - + ((f32)continuous_tiles_count * 0.5f - 0.5f) * + translate_dir_f; v3f scale(1, 1, 1); if (translate_dir.X != 0) @@ -985,9 +994,11 @@ static void updateFastFaceRow( if (translate_dir.Z != 0) scale.Z = continuous_tiles_count; - makeFastFace(tile, lights[0], lights[1], lights[2], lights[3], - pf, sp, face_dir_corrected, scale, dest); - g_profiler->avg("Meshgen: Tiles per face [#]", continuous_tiles_count); + makeFastFace(tile, lights[0], lights[1], lights[2], + lights[3], pf, sp, face_dir_corrected, + scale, dest); + g_profiler->avg("Meshgen: Tiles per face [#]", + continuous_tiles_count); } continuous_tiles_count = 1; @@ -1002,50 +1013,35 @@ static void updateFastFaceRow( } } -static void updateAllFastFaceRows(MeshMakeData *data, - std::vector &dest, bool xray, std::set xraySet) +static void updateAllFastFaceRows(MeshMakeData *data, std::vector &dest, + bool xray, std::set xraySet) { /* Go through every y,z and get top(y+) faces in rows of x+ */ for (s16 y = 0; y < MAP_BLOCKSIZE; y++) - for (s16 z = 0; z < MAP_BLOCKSIZE; z++) - updateFastFaceRow(data, - v3s16(0, y, z), - v3s16(1, 0, 0), //dir - v3f (1, 0, 0), - v3s16(0, 1, 0), //face dir - dest, - xray, - xraySet); + for (s16 z = 0; z < MAP_BLOCKSIZE; z++) + updateFastFaceRow(data, v3s16(0, y, z), v3s16(1, 0, 0), // dir + v3f(1, 0, 0), v3s16(0, 1, 0), // face dir + dest, xray, xraySet); /* Go through every x,y and get right(x+) faces in rows of z+ */ for (s16 x = 0; x < MAP_BLOCKSIZE; x++) - for (s16 y = 0; y < MAP_BLOCKSIZE; y++) - updateFastFaceRow(data, - v3s16(x, y, 0), - v3s16(0, 0, 1), //dir - v3f (0, 0, 1), - v3s16(1, 0, 0), //face dir - dest, - xray, - xraySet); + for (s16 y = 0; y < MAP_BLOCKSIZE; y++) + updateFastFaceRow(data, v3s16(x, y, 0), v3s16(0, 0, 1), // dir + v3f(0, 0, 1), v3s16(1, 0, 0), // face dir + dest, xray, xraySet); /* Go through every y,z and get back(z+) faces in rows of x+ */ for (s16 z = 0; z < MAP_BLOCKSIZE; z++) - for (s16 y = 0; y < MAP_BLOCKSIZE; y++) - updateFastFaceRow(data, - v3s16(0, y, z), - v3s16(1, 0, 0), //dir - v3f (1, 0, 0), - v3s16(0, 0, 1), //face dir - dest, - xray, - xraySet); + for (s16 y = 0; y < MAP_BLOCKSIZE; y++) + updateFastFaceRow(data, v3s16(0, y, z), v3s16(1, 0, 0), // dir + v3f(1, 0, 0), v3s16(0, 0, 1), // face dir + dest, xray, xraySet); } static void applyTileColor(PreMeshBuffer &pmb) @@ -1055,10 +1051,9 @@ static void applyTileColor(PreMeshBuffer &pmb) return; for (video::S3DVertex &vertex : pmb.vertices) { video::SColor *c = &vertex.Color; - c->set(c->getAlpha(), - c->getRed() * tc.getRed() / 255, - c->getGreen() * tc.getGreen() / 255, - c->getBlue() * tc.getBlue() / 255); + c->set(c->getAlpha(), c->getRed() * tc.getRed() / 255, + c->getGreen() * tc.getGreen() / 255, + c->getBlue() * tc.getBlue() / 255); } } @@ -1066,13 +1061,11 @@ static void applyTileColor(PreMeshBuffer &pmb) MapBlockMesh */ -MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset): - m_minimap_mapblock(NULL), - m_tsrc(data->m_client->getTextureSource()), - m_shdrsrc(data->m_client->getShaderSource()), - m_animation_force_timer(0), // force initial animation - m_last_crack(-1), - m_last_daynight_ratio((u32) -1) +MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset) : + m_minimap_mapblock(NULL), m_tsrc(data->m_client->getTextureSource()), + m_shdrsrc(data->m_client->getShaderSource()), + m_animation_force_timer(0), // force initial animation + m_last_crack(-1), m_last_daynight_ratio((u32)-1) { for (auto &m : m_mesh) m = new scene::SMesh(); @@ -1083,12 +1076,12 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset): if (g_settings->getBool("enable_minimap")) { m_minimap_mapblock = new MinimapMapblock; m_minimap_mapblock->getMinimapNodes( - &data->m_vmanip, data->m_blockpos * MAP_BLOCKSIZE); + &data->m_vmanip, data->m_blockpos * MAP_BLOCKSIZE); } // 4-21ms for MAP_BLOCKSIZE=16 (NOTE: probably outdated) // 24-155ms for MAP_BLOCKSIZE=32 (NOTE: probably outdated) - //TimeTaker timer1("MapBlockMesh()"); + // TimeTaker timer1("MapBlockMesh()"); std::vector fastfaces_new; fastfaces_new.reserve(512); @@ -1098,18 +1091,19 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset): bool xray = g_settings->getBool("xray"); std::set xraySet; if (xray) - xraySet = splitToContentT(g_settings->get("xray_nodes"), data->m_client->ndef()); - + xraySet = splitToContentT( + g_settings->get("xray_nodes"), data->m_client->ndef()); + /* We are including the faces of the trailing edges of the block. This means that when something changes, the caller must also update the meshes of the blocks at the leading edges. NOTE: This is the slowest part of this method. - */ + */ { // 4-23ms for MAP_BLOCKSIZE=16 (NOTE: probably outdated) - //TimeTaker timer2("updateAllFastFaceRows()"); + // TimeTaker timer2("updateAllFastFaceRows()"); updateAllFastFaceRows(data, fastfaces_new, xray, xraySet); } // End of slow part @@ -1123,13 +1117,13 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset): { // avg 0ms (100ms spikes when loading textures the first time) // (NOTE: probably outdated) - //TimeTaker timer2("MeshCollector building"); + // TimeTaker timer2("MeshCollector building"); for (const FastFace &f : fastfaces_new) { static const u16 indices[] = {0, 1, 2, 2, 3, 0}; static const u16 indices_alternate[] = {0, 1, 3, 2, 3, 1}; - const u16 *indices_p = - f.vertex_0_2_connected ? indices : indices_alternate; + const u16 *indices_p = f.vertex_0_2_connected ? indices + : indices_alternate; collector.append(f.tile, f.vertices, 4, indices_p, 6); } } @@ -1152,8 +1146,7 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset): */ for (int layer = 0; layer < MAX_TILE_LAYERS; layer++) { - for(u32 i = 0; i < collector.prebuffers[layer].size(); i++) - { + for (u32 i = 0; i < collector.prebuffers[layer].size(); i++) { PreMeshBuffer &p = collector.prebuffers[layer][i]; applyTileColor(p); @@ -1163,9 +1156,10 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset): if (p.layer.material_flags & MATERIAL_FLAG_CRACK) { // Find the texture name plus ^[crack:N: std::ostringstream os(std::ios::binary); - os << m_tsrc->getTextureName(p.layer.texture_id) << "^[crack"; + os << m_tsrc->getTextureName(p.layer.texture_id) + << "^[crack"; if (p.layer.material_flags & MATERIAL_FLAG_CRACK_OVERLAY) - os << "o"; // use ^[cracko + os << "o"; // use ^[cracko u8 tiles = p.layer.scale; if (tiles > 1) os << ":" << (u32)tiles; @@ -1174,24 +1168,27 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset): std::pair(layer, i), os.str())); // Replace tile texture with the cracked one p.layer.texture = m_tsrc->getTextureForMesh( - os.str() + "0", - &p.layer.texture_id); + os.str() + "0", &p.layer.texture_id); } // - Texture animation if (p.layer.material_flags & MATERIAL_FLAG_ANIMATION) { // Add to MapBlockMesh in order to animate these tiles m_animation_tiles[std::pair(layer, i)] = p.layer; m_animation_frames[std::pair(layer, i)] = 0; - if (g_settings->getBool( - "desynchronize_mapblock_texture_animation")) { + if (g_settings->getBool("desynchronize_mapblock_texture_" + "animation")) { // Get starting position from noise - m_animation_frame_offsets[std::pair(layer, i)] = - 100000 * (2.0 + noise3d( - data->m_blockpos.X, data->m_blockpos.Y, - data->m_blockpos.Z, 0)); + m_animation_frame_offsets[std::pair( + layer, i)] = + 100000 * + (2.0 + noise3d(data->m_blockpos.X, + data->m_blockpos.Y, + data->m_blockpos.Z, + 0)); } else { // Play all synchronized - m_animation_frame_offsets[std::pair(layer, i)] = 0; + m_animation_frame_offsets[std::pair( + layer, i)] = 0; } // Replace tile texture with the first animation frame p.layer.texture = (*p.layer.frames)[0].texture; @@ -1206,10 +1203,14 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset): for (u32 j = 0; j < vertex_count; j++) { video::SColor *vc = &p.vertices[j].Color; video::SColor copy = *vc; - if (vc->getAlpha() == 0) // No sunlight - no need to animate - final_color_blend(vc, copy, sunlight); // Finalize color + if (vc->getAlpha() == 0) // No sunlight - no need + // to animate + final_color_blend(vc, copy, + sunlight); // Finalize + // color else // Record color to animate - m_daynight_diffs[std::pair(layer, i)][j] = copy; + m_daynight_diffs[std::pair( + layer, i)][j] = copy; // The sunlight ratio has been stored, // delete alpha (for the final rendering). @@ -1226,8 +1227,9 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset): material.setTexture(0, p.layer.texture); if (m_enable_shaders) { - material.MaterialType = m_shdrsrc->getShaderInfo( - p.layer.shader_id).material; + material.MaterialType = + m_shdrsrc->getShaderInfo(p.layer.shader_id) + .material; p.layer.applyMaterialOptionsWithShaders(material); if (p.layer.normal_texture) material.setTexture(1, p.layer.normal_texture); @@ -1245,9 +1247,10 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset): buf->Material = material; buf->Vertices.reallocate(p.vertices.size()); buf->Indices.reallocate(p.indices.size()); - for (const video::S3DVertex &v: p.vertices) - buf->Vertices.push_back(video::S3DVertexTangents(v.Pos, v.Color, v.TCoords)); - for (u16 i: p.indices) + for (const video::S3DVertex &v : p.vertices) + buf->Vertices.push_back(video::S3DVertexTangents( + v.Pos, v.Color, v.TCoords)); + for (u16 i : p.indices) buf->Indices.push_back(i); buf->recalculateBoundingBox(); mesh->addMeshBuffer(buf); @@ -1256,7 +1259,7 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset): scene::SMeshBuffer *buf = new scene::SMeshBuffer(); buf->Material = material; buf->append(&p.vertices[0], p.vertices.size(), - &p.indices[0], p.indices.size()); + &p.indices[0], p.indices.size()); mesh->addMeshBuffer(buf); buf->drop(); } @@ -1267,11 +1270,14 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset): */ m_camera_offset = camera_offset; translateMesh(m_mesh[layer], - intToFloat(data->m_blockpos * MAP_BLOCKSIZE - camera_offset, BS)); + intToFloat(data->m_blockpos * MAP_BLOCKSIZE - + camera_offset, + BS)); if (m_use_tangent_vertices) { - scene::IMeshManipulator* meshmanip = - RenderingEngine::get_scene_manager()->getMeshManipulator(); + scene::IMeshManipulator *meshmanip = + RenderingEngine::get_scene_manager() + ->getMeshManipulator(); meshmanip->recalculateTangents(m_mesh[layer], true, false, false); } @@ -1289,13 +1295,11 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset): } } - //std::cout<<"added "<getMeshBufferCount(); i++) { scene::IMeshBuffer *buf = m->getMeshBuffer(i); - RenderingEngine::get_video_driver()->removeHardwareBuffer(buf); + RenderingEngine::get_video_driver()->removeHardwareBuffer( + buf); } m->drop(); m = NULL; @@ -1312,8 +1317,7 @@ MapBlockMesh::~MapBlockMesh() delete m_minimap_mapblock; } -bool MapBlockMesh::animate(bool faraway, float time, int crack, - u32 daynight_ratio) +bool MapBlockMesh::animate(bool faraway, float time, int crack, u32 daynight_ratio) { if (!m_has_animation) { m_animation_force_timer = 100000; @@ -1325,16 +1329,17 @@ bool MapBlockMesh::animate(bool faraway, float time, int crack, // Cracks if (crack != m_last_crack) { for (auto &crack_material : m_crack_materials) { - scene::IMeshBuffer *buf = m_mesh[crack_material.first.first]-> - getMeshBuffer(crack_material.first.second); + scene::IMeshBuffer *buf = + m_mesh[crack_material.first.first]->getMeshBuffer( + crack_material.first.second); std::string basename = crack_material.second; // Create new texture name from original std::ostringstream os; os << basename << crack; u32 new_texture_id = 0; - video::ITexture *new_texture = - m_tsrc->getTextureForMesh(os.str(), &new_texture_id); + video::ITexture *new_texture = m_tsrc->getTextureForMesh( + os.str(), &new_texture_id); buf->getMaterial().setTexture(0, new_texture); // If the current material is also animated, @@ -1357,23 +1362,25 @@ bool MapBlockMesh::animate(bool faraway, float time, int crack, const TileLayer &tile = animation_tile.second; // Figure out current frame int frameoffset = m_animation_frame_offsets[animation_tile.first]; - int frame = (int)(time * 1000 / tile.animation_frame_length_ms - + frameoffset) % tile.animation_frame_count; + int frame = (int)(time * 1000 / tile.animation_frame_length_ms + + frameoffset) % + tile.animation_frame_count; // If frame doesn't change, skip if (frame == m_animation_frames[animation_tile.first]) continue; m_animation_frames[animation_tile.first] = frame; - scene::IMeshBuffer *buf = m_mesh[animation_tile.first.first]-> - getMeshBuffer(animation_tile.first.second); + scene::IMeshBuffer *buf = + m_mesh[animation_tile.first.first]->getMeshBuffer( + animation_tile.first.second); const FrameSpec &animation_frame = (*tile.frames)[frame]; buf->getMaterial().setTexture(0, animation_frame.texture); if (m_enable_shaders) { if (animation_frame.normal_texture) - buf->getMaterial().setTexture(1, - animation_frame.normal_texture); + buf->getMaterial().setTexture( + 1, animation_frame.normal_texture); buf->getMaterial().setTexture(2, animation_frame.flags_texture); } } @@ -1388,9 +1395,11 @@ bool MapBlockMesh::animate(bool faraway, float time, int crack, get_sunlight_color(&day_color, daynight_ratio); for (auto &daynight_diff : m_daynight_diffs) { - scene::IMeshBuffer *buf = m_mesh[daynight_diff.first.first]-> - getMeshBuffer(daynight_diff.first.second); - video::S3DVertex *vertices = (video::S3DVertex *)buf->getVertices(); + scene::IMeshBuffer *buf = + m_mesh[daynight_diff.first.first]->getMeshBuffer( + daynight_diff.first.second); + video::S3DVertex *vertices = + (video::S3DVertex *)buf->getVertices(); for (const auto &j : daynight_diff.second) final_color_blend(&(vertices[j.first].Color), j.second, day_color); @@ -1406,7 +1415,7 @@ void MapBlockMesh::updateCameraOffset(v3s16 camera_offset) if (camera_offset != m_camera_offset) { for (scene::IMesh *layer : m_mesh) { translateMesh(layer, - intToFloat(m_camera_offset - camera_offset, BS)); + intToFloat(m_camera_offset - camera_offset, BS)); if (m_enable_vbo) layer->setDirty(); } diff --git a/src/client/mapblock_mesh.h b/src/client/mapblock_mesh.h index 6af23a656..dd08433a1 100644 --- a/src/client/mapblock_mesh.h +++ b/src/client/mapblock_mesh.h @@ -32,23 +32,21 @@ class IShaderSource; Mesh making stuff */ - class MapBlock; struct MinimapMapblock; struct MeshMakeData { VoxelManipulator m_vmanip; - v3s16 m_blockpos = v3s16(-1337,-1337,-1337); - v3s16 m_crack_pos_relative = v3s16(-1337,-1337,-1337); + v3s16 m_blockpos = v3s16(-1337, -1337, -1337); + v3s16 m_crack_pos_relative = v3s16(-1337, -1337, -1337); bool m_smooth_lighting = false; Client *m_client; bool m_use_shaders; bool m_use_tangent_vertices; - MeshMakeData(Client *client, bool use_shaders, - bool use_tangent_vertices = false); + MeshMakeData(Client *client, bool use_shaders, bool use_tangent_vertices = false); /* Copy block data manually (to allow optimizations by the caller) @@ -104,15 +102,9 @@ public: // Returns true if anything has been changed. bool animate(bool faraway, float time, int crack, u32 daynight_ratio); - scene::IMesh *getMesh() - { - return m_mesh[0]; - } + scene::IMesh *getMesh() { return m_mesh[0]; } - scene::IMesh *getMesh(u8 layer) - { - return m_mesh[layer]; - } + scene::IMesh *getMesh(u8 layer) { return m_mesh[layer]; } MinimapMapblock *moveMinimapMapblock() { @@ -121,14 +113,11 @@ public: return p; } - bool isAnimationForced() const - { - return m_animation_force_timer == 0; - } + bool isAnimationForced() const { return m_animation_force_timer == 0; } void decreaseAnimationForceTimer() { - if(m_animation_force_timer > 0) + if (m_animation_force_timer > 0) m_animation_force_timer--; } @@ -167,7 +156,7 @@ private: // For each mesh and mesh buffer, stores pre-baked colors // of sunlit vertices // Keys are pairs of (mesh index, buffer index in the mesh) - std::map, std::map > m_daynight_diffs; + std::map, std::map> m_daynight_diffs; // Camera offset info -> do we have to translate the mesh? v3s16 m_camera_offset; @@ -189,9 +178,10 @@ video::SColor encode_light(u16 light, u8 emissive_light); // Compute light at node u16 getInteriorLight(MapNode n, s32 increment, const NodeDefManager *ndef); -u16 getFaceLight(MapNode n, MapNode n2, const v3s16 &face_dir, - const NodeDefManager *ndef); -u16 getSmoothLightSolid(const v3s16 &p, const v3s16 &face_dir, const v3s16 &corner, MeshMakeData *data); +u16 getFaceLight( + MapNode n, MapNode n2, const v3s16 &face_dir, const NodeDefManager *ndef); +u16 getSmoothLightSolid(const v3s16 &p, const v3s16 &face_dir, const v3s16 &corner, + MeshMakeData *data); u16 getSmoothLightTransparent(const v3s16 &p, const v3s16 &corner, MeshMakeData *data); /*! @@ -207,8 +197,7 @@ void get_sunlight_color(video::SColorf *sunlight, u32 daynight_ratio); * \param light first 8 bits are day light, second 8 bits are * night light */ -void final_color_blend(video::SColor *result, - u16 light, u32 daynight_ratio); +void final_color_blend(video::SColor *result, u16 light, u32 daynight_ratio); /*! * Gives the final SColor shown on screen. @@ -217,12 +206,14 @@ void final_color_blend(video::SColor *result, * \param data the half-baked vertex color * \param dayLight color of the sunlight */ -void final_color_blend(video::SColor *result, - const video::SColor &data, const video::SColorf &dayLight); +void final_color_blend(video::SColor *result, const video::SColor &data, + const video::SColorf &dayLight); // Retrieves the TileSpec of a face of a node // Adds MATERIAL_FLAG_CRACK if the node is cracked // TileSpec should be passed as reference due to the underlying TileFrame and its vector // TileFrame vector copy cost very much to client -void getNodeTileN(MapNode mn, const v3s16 &p, u8 tileindex, MeshMakeData *data, TileSpec &tile); -void getNodeTile(MapNode mn, const v3s16 &p, const v3s16 &dir, MeshMakeData *data, TileSpec &tile); +void getNodeTileN(MapNode mn, const v3s16 &p, u8 tileindex, MeshMakeData *data, + TileSpec &tile); +void getNodeTile(MapNode mn, const v3s16 &p, const v3s16 &dir, MeshMakeData *data, + TileSpec &tile); diff --git a/src/client/mesh.cpp b/src/client/mesh.cpp index e1ec22068..312d4e7f6 100644 --- a/src/client/mesh.cpp +++ b/src/client/mesh.cpp @@ -35,11 +35,11 @@ with this program; if not, write to the Free Software Foundation, Inc., #define MY_ETLM_READ_ONLY video::ETLM_READ_ONLY #endif -inline static void applyShadeFactor(video::SColor& color, float factor) +inline static void applyShadeFactor(video::SColor &color, float factor) { - color.setRed(core::clamp(core::round32(color.getRed()*factor), 0, 255)); - color.setGreen(core::clamp(core::round32(color.getGreen()*factor), 0, 255)); - color.setBlue(core::clamp(core::round32(color.getBlue()*factor), 0, 255)); + color.setRed(core::clamp(core::round32(color.getRed() * factor), 0, 255)); + color.setGreen(core::clamp(core::round32(color.getGreen() * factor), 0, 255)); + color.setBlue(core::clamp(core::round32(color.getBlue() * factor), 0, 255)); } void applyFacesShading(video::SColor &color, const v3f &normal) @@ -62,54 +62,53 @@ void applyFacesShading(video::SColor &color, const v3f &normal) applyShadeFactor(color, 0.670820f * x2 + 1.000000f * y2 + 0.836660f * z2); } -scene::IAnimatedMesh* createCubeMesh(v3f scale) +scene::IAnimatedMesh *createCubeMesh(v3f scale) { - video::SColor c(255,255,255,255); - video::S3DVertex vertices[24] = - { - // Up - video::S3DVertex(-0.5,+0.5,-0.5, 0,1,0, c, 0,1), - video::S3DVertex(-0.5,+0.5,+0.5, 0,1,0, c, 0,0), - video::S3DVertex(+0.5,+0.5,+0.5, 0,1,0, c, 1,0), - video::S3DVertex(+0.5,+0.5,-0.5, 0,1,0, c, 1,1), - // Down - video::S3DVertex(-0.5,-0.5,-0.5, 0,-1,0, c, 0,0), - video::S3DVertex(+0.5,-0.5,-0.5, 0,-1,0, c, 1,0), - video::S3DVertex(+0.5,-0.5,+0.5, 0,-1,0, c, 1,1), - video::S3DVertex(-0.5,-0.5,+0.5, 0,-1,0, c, 0,1), - // Right - video::S3DVertex(+0.5,-0.5,-0.5, 1,0,0, c, 0,1), - video::S3DVertex(+0.5,+0.5,-0.5, 1,0,0, c, 0,0), - video::S3DVertex(+0.5,+0.5,+0.5, 1,0,0, c, 1,0), - video::S3DVertex(+0.5,-0.5,+0.5, 1,0,0, c, 1,1), - // Left - video::S3DVertex(-0.5,-0.5,-0.5, -1,0,0, c, 1,1), - video::S3DVertex(-0.5,-0.5,+0.5, -1,0,0, c, 0,1), - video::S3DVertex(-0.5,+0.5,+0.5, -1,0,0, c, 0,0), - video::S3DVertex(-0.5,+0.5,-0.5, -1,0,0, c, 1,0), - // Back - video::S3DVertex(-0.5,-0.5,+0.5, 0,0,1, c, 1,1), - video::S3DVertex(+0.5,-0.5,+0.5, 0,0,1, c, 0,1), - video::S3DVertex(+0.5,+0.5,+0.5, 0,0,1, c, 0,0), - video::S3DVertex(-0.5,+0.5,+0.5, 0,0,1, c, 1,0), - // Front - video::S3DVertex(-0.5,-0.5,-0.5, 0,0,-1, c, 0,1), - video::S3DVertex(-0.5,+0.5,-0.5, 0,0,-1, c, 0,0), - video::S3DVertex(+0.5,+0.5,-0.5, 0,0,-1, c, 1,0), - video::S3DVertex(+0.5,-0.5,-0.5, 0,0,-1, c, 1,1), + video::SColor c(255, 255, 255, 255); + video::S3DVertex vertices[24] = { + // Up + video::S3DVertex(-0.5, +0.5, -0.5, 0, 1, 0, c, 0, 1), + video::S3DVertex(-0.5, +0.5, +0.5, 0, 1, 0, c, 0, 0), + video::S3DVertex(+0.5, +0.5, +0.5, 0, 1, 0, c, 1, 0), + video::S3DVertex(+0.5, +0.5, -0.5, 0, 1, 0, c, 1, 1), + // Down + video::S3DVertex(-0.5, -0.5, -0.5, 0, -1, 0, c, 0, 0), + video::S3DVertex(+0.5, -0.5, -0.5, 0, -1, 0, c, 1, 0), + video::S3DVertex(+0.5, -0.5, +0.5, 0, -1, 0, c, 1, 1), + video::S3DVertex(-0.5, -0.5, +0.5, 0, -1, 0, c, 0, 1), + // Right + video::S3DVertex(+0.5, -0.5, -0.5, 1, 0, 0, c, 0, 1), + video::S3DVertex(+0.5, +0.5, -0.5, 1, 0, 0, c, 0, 0), + video::S3DVertex(+0.5, +0.5, +0.5, 1, 0, 0, c, 1, 0), + video::S3DVertex(+0.5, -0.5, +0.5, 1, 0, 0, c, 1, 1), + // Left + video::S3DVertex(-0.5, -0.5, -0.5, -1, 0, 0, c, 1, 1), + video::S3DVertex(-0.5, -0.5, +0.5, -1, 0, 0, c, 0, 1), + video::S3DVertex(-0.5, +0.5, +0.5, -1, 0, 0, c, 0, 0), + video::S3DVertex(-0.5, +0.5, -0.5, -1, 0, 0, c, 1, 0), + // Back + video::S3DVertex(-0.5, -0.5, +0.5, 0, 0, 1, c, 1, 1), + video::S3DVertex(+0.5, -0.5, +0.5, 0, 0, 1, c, 0, 1), + video::S3DVertex(+0.5, +0.5, +0.5, 0, 0, 1, c, 0, 0), + video::S3DVertex(-0.5, +0.5, +0.5, 0, 0, 1, c, 1, 0), + // Front + video::S3DVertex(-0.5, -0.5, -0.5, 0, 0, -1, c, 0, 1), + video::S3DVertex(-0.5, +0.5, -0.5, 0, 0, -1, c, 0, 0), + video::S3DVertex(+0.5, +0.5, -0.5, 0, 0, -1, c, 1, 0), + video::S3DVertex(+0.5, -0.5, -0.5, 0, 0, -1, c, 1, 1), }; - u16 indices[6] = {0,1,2,2,3,0}; + u16 indices[6] = {0, 1, 2, 2, 3, 0}; scene::SMesh *mesh = new scene::SMesh(); - for (u32 i=0; i<6; ++i) - { + for (u32 i = 0; i < 6; ++i) { scene::IMeshBuffer *buf = new scene::SMeshBuffer(); buf->append(vertices + 4 * i, 4, indices, 6); // Set default material buf->getMaterial().setFlag(video::EMF_LIGHTING, false); buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false); - buf->getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; + buf->getMaterial().MaterialType = + video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; // Add mesh buffer to mesh mesh->addMeshBuffer(buf); buf->drop(); @@ -117,7 +116,7 @@ scene::IAnimatedMesh* createCubeMesh(v3f scale) scene::SAnimatedMesh *anim_mesh = new scene::SAnimatedMesh(mesh); mesh->drop(); - scaleMesh(anim_mesh, scale); // also recalculates bounding box + scaleMesh(anim_mesh, scale); // also recalculates bounding box return anim_mesh; } @@ -181,9 +180,9 @@ void setMeshBufferColor(scene::IMeshBuffer *buf, const video::SColor &color) { const u32 stride = getVertexPitchFromType(buf->getVertexType()); u32 vertex_count = buf->getVertexCount(); - u8 *vertices = (u8 *) buf->getVertices(); + u8 *vertices = (u8 *)buf->getVertices(); for (u32 i = 0; i < vertex_count; i++) - ((video::S3DVertex *) (vertices + i * stride))->Color = color; + ((video::S3DVertex *)(vertices + i * stride))->Color = color; } void setAnimatedMeshColor(scene::IAnimatedMeshSceneNode *node, const video::SColor &color) @@ -203,8 +202,7 @@ void setMeshColor(scene::IMesh *mesh, const video::SColor &color) setMeshBufferColor(mesh->getMeshBuffer(j), color); } -template -static void applyToMesh(scene::IMesh *mesh, const F &fn) +template static void applyToMesh(scene::IMesh *mesh, const F &fn) { u16 mc = mesh->getMeshBufferCount(); for (u16 j = 0; j < mc; j++) { @@ -221,9 +219,9 @@ void colorizeMeshBuffer(scene::IMeshBuffer *buf, const video::SColor *buffercolo { const u32 stride = getVertexPitchFromType(buf->getVertexType()); u32 vertex_count = buf->getVertexCount(); - u8 *vertices = (u8 *) buf->getVertices(); + u8 *vertices = (u8 *)buf->getVertices(); for (u32 i = 0; i < vertex_count; i++) { - video::S3DVertex *vertex = (video::S3DVertex *) (vertices + i * stride); + video::S3DVertex *vertex = (video::S3DVertex *)(vertices + i * stride); video::SColor *vc = &(vertex->Color); // Reset color *vc = *buffercolor; @@ -232,14 +230,12 @@ void colorizeMeshBuffer(scene::IMeshBuffer *buf, const video::SColor *buffercolo } } -void setMeshColorByNormalXYZ(scene::IMesh *mesh, - const video::SColor &colorX, - const video::SColor &colorY, - const video::SColor &colorZ) +void setMeshColorByNormalXYZ(scene::IMesh *mesh, const video::SColor &colorX, + const video::SColor &colorY, const video::SColor &colorZ) { if (!mesh) return; - auto colorizator = [=] (video::S3DVertex *vertex) { + auto colorizator = [=](video::S3DVertex *vertex) { f32 x = fabs(vertex->Normal.X); f32 y = fabs(vertex->Normal.Y); f32 z = fabs(vertex->Normal.Z); @@ -253,12 +249,12 @@ void setMeshColorByNormalXYZ(scene::IMesh *mesh, applyToMesh(mesh, colorizator); } -void setMeshColorByNormal(scene::IMesh *mesh, const v3f &normal, - const video::SColor &color) +void setMeshColorByNormal( + scene::IMesh *mesh, const v3f &normal, const video::SColor &color) { if (!mesh) return; - auto colorizator = [normal, color] (video::S3DVertex *vertex) { + auto colorizator = [normal, color](video::S3DVertex *vertex) { if (vertex->Normal == normal) vertex->Color = color; }; @@ -271,7 +267,7 @@ static void rotateMesh(scene::IMesh *mesh, float degrees) degrees *= M_PI / 180.0f; float c = std::cos(degrees); float s = std::sin(degrees); - auto rotator = [c, s] (video::S3DVertex *vertex) { + auto rotator = [c, s](video::S3DVertex *vertex) { float u = vertex->Pos.*U; float v = vertex->Pos.*V; vertex->Pos.*U = c * u - s * v; @@ -300,23 +296,39 @@ void rotateMeshBy6dFacedir(scene::IMesh *mesh, int facedir) int axisdir = facedir >> 2; facedir &= 0x03; switch (facedir) { - case 1: rotateMeshXZby(mesh, -90); break; - case 2: rotateMeshXZby(mesh, 180); break; - case 3: rotateMeshXZby(mesh, 90); break; + case 1: + rotateMeshXZby(mesh, -90); + break; + case 2: + rotateMeshXZby(mesh, 180); + break; + case 3: + rotateMeshXZby(mesh, 90); + break; } switch (axisdir) { - case 1: rotateMeshYZby(mesh, 90); break; // z+ - case 2: rotateMeshYZby(mesh, -90); break; // z- - case 3: rotateMeshXYby(mesh, -90); break; // x+ - case 4: rotateMeshXYby(mesh, 90); break; // x- - case 5: rotateMeshXYby(mesh, -180); break; + case 1: + rotateMeshYZby(mesh, 90); + break; // z+ + case 2: + rotateMeshYZby(mesh, -90); + break; // z- + case 3: + rotateMeshXYby(mesh, -90); + break; // x+ + case 4: + rotateMeshXYby(mesh, 90); + break; // x- + case 5: + rotateMeshXYby(mesh, -180); + break; } } void recalculateBoundingBox(scene::IMesh *src_mesh) { aabb3f bbox; - bbox.reset(0,0,0); + bbox.reset(0, 0, 0); for (u16 j = 0; j < src_mesh->getMeshBufferCount(); j++) { scene::IMeshBuffer *buf = src_mesh->getMeshBuffer(j); buf->recalculateBoundingBox(); @@ -348,35 +360,35 @@ bool checkMeshNormals(scene::IMesh *mesh) return true; } -scene::IMeshBuffer* cloneMeshBuffer(scene::IMeshBuffer *mesh_buffer) +scene::IMeshBuffer *cloneMeshBuffer(scene::IMeshBuffer *mesh_buffer) { switch (mesh_buffer->getVertexType()) { case video::EVT_STANDARD: { - video::S3DVertex *v = (video::S3DVertex *) mesh_buffer->getVertices(); + video::S3DVertex *v = (video::S3DVertex *)mesh_buffer->getVertices(); u16 *indices = mesh_buffer->getIndices(); scene::SMeshBuffer *cloned_buffer = new scene::SMeshBuffer(); cloned_buffer->append(v, mesh_buffer->getVertexCount(), indices, - mesh_buffer->getIndexCount()); + mesh_buffer->getIndexCount()); return cloned_buffer; } case video::EVT_2TCOORDS: { video::S3DVertex2TCoords *v = - (video::S3DVertex2TCoords *) mesh_buffer->getVertices(); + (video::S3DVertex2TCoords *)mesh_buffer->getVertices(); u16 *indices = mesh_buffer->getIndices(); scene::SMeshBufferLightMap *cloned_buffer = - new scene::SMeshBufferLightMap(); + new scene::SMeshBufferLightMap(); cloned_buffer->append(v, mesh_buffer->getVertexCount(), indices, - mesh_buffer->getIndexCount()); + mesh_buffer->getIndexCount()); return cloned_buffer; } case video::EVT_TANGENTS: { video::S3DVertexTangents *v = - (video::S3DVertexTangents *) mesh_buffer->getVertices(); + (video::S3DVertexTangents *)mesh_buffer->getVertices(); u16 *indices = mesh_buffer->getIndices(); scene::SMeshBufferTangents *cloned_buffer = - new scene::SMeshBufferTangents(); + new scene::SMeshBufferTangents(); cloned_buffer->append(v, mesh_buffer->getVertexCount(), indices, - mesh_buffer->getIndexCount()); + mesh_buffer->getIndexCount()); return cloned_buffer; } } @@ -385,26 +397,24 @@ scene::IMeshBuffer* cloneMeshBuffer(scene::IMeshBuffer *mesh_buffer) return NULL; } -scene::SMesh* cloneMesh(scene::IMesh *src_mesh) +scene::SMesh *cloneMesh(scene::IMesh *src_mesh) { - scene::SMesh* dst_mesh = new scene::SMesh(); + scene::SMesh *dst_mesh = new scene::SMesh(); for (u16 j = 0; j < src_mesh->getMeshBufferCount(); j++) { - scene::IMeshBuffer *temp_buf = cloneMeshBuffer( - src_mesh->getMeshBuffer(j)); + scene::IMeshBuffer *temp_buf = + cloneMeshBuffer(src_mesh->getMeshBuffer(j)); dst_mesh->addMeshBuffer(temp_buf); temp_buf->drop(); - } return dst_mesh; } -scene::IMesh* convertNodeboxesToMesh(const std::vector &boxes, - const f32 *uv_coords, float expand) +scene::IMesh *convertNodeboxesToMesh( + const std::vector &boxes, const f32 *uv_coords, float expand) { - scene::SMesh* dst_mesh = new scene::SMesh(); + scene::SMesh *dst_mesh = new scene::SMesh(); - for (u16 j = 0; j < 6; j++) - { + for (u16 j = 0; j < 6; j++) { scene::IMeshBuffer *buf = new scene::SMeshBuffer(); buf->getMaterial().setFlag(video::EMF_LIGHTING, false); buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false); @@ -412,7 +422,7 @@ scene::IMesh* convertNodeboxesToMesh(const std::vector &boxes, buf->drop(); } - video::SColor c(255,255,255,255); + video::SColor c(255, 255, 255, 255); for (aabb3f box : boxes) { box.repair(); @@ -433,18 +443,36 @@ scene::IMesh* convertNodeboxesToMesh(const std::vector &boxes, f32 tz2 = (box.MaxEdge.Z / BS) + 0.5; f32 txc_default[24] = { - // up - tx1, 1 - tz2, tx2, 1 - tz1, - // down - tx1, tz1, tx2, tz2, - // right - tz1, 1 - ty2, tz2, 1 - ty1, - // left - 1 - tz2, 1 - ty2, 1 - tz1, 1 - ty1, - // back - 1 - tx2, 1 - ty2, 1 - tx1, 1 - ty1, - // front - tx1, 1 - ty2, tx2, 1 - ty1, + // up + tx1, + 1 - tz2, + tx2, + 1 - tz1, + // down + tx1, + tz1, + tx2, + tz2, + // right + tz1, + 1 - ty2, + tz2, + 1 - ty1, + // left + 1 - tz2, + 1 - ty2, + 1 - tz1, + 1 - ty1, + // back + 1 - tx2, + 1 - ty2, + 1 - tx1, + 1 - ty1, + // front + tx1, + 1 - ty2, + tx2, + 1 - ty1, }; // use default texture UV mapping if not provided @@ -453,44 +481,66 @@ scene::IMesh* convertNodeboxesToMesh(const std::vector &boxes, v3f min = box.MinEdge; v3f max = box.MaxEdge; - video::S3DVertex vertices[24] = - { - // up - video::S3DVertex(min.X,max.Y,max.Z, 0,1,0, c, txc[0],txc[1]), - video::S3DVertex(max.X,max.Y,max.Z, 0,1,0, c, txc[2],txc[1]), - video::S3DVertex(max.X,max.Y,min.Z, 0,1,0, c, txc[2],txc[3]), - video::S3DVertex(min.X,max.Y,min.Z, 0,1,0, c, txc[0],txc[3]), - // down - video::S3DVertex(min.X,min.Y,min.Z, 0,-1,0, c, txc[4],txc[5]), - video::S3DVertex(max.X,min.Y,min.Z, 0,-1,0, c, txc[6],txc[5]), - video::S3DVertex(max.X,min.Y,max.Z, 0,-1,0, c, txc[6],txc[7]), - video::S3DVertex(min.X,min.Y,max.Z, 0,-1,0, c, txc[4],txc[7]), - // right - video::S3DVertex(max.X,max.Y,min.Z, 1,0,0, c, txc[ 8],txc[9]), - video::S3DVertex(max.X,max.Y,max.Z, 1,0,0, c, txc[10],txc[9]), - video::S3DVertex(max.X,min.Y,max.Z, 1,0,0, c, txc[10],txc[11]), - video::S3DVertex(max.X,min.Y,min.Z, 1,0,0, c, txc[ 8],txc[11]), - // left - video::S3DVertex(min.X,max.Y,max.Z, -1,0,0, c, txc[12],txc[13]), - video::S3DVertex(min.X,max.Y,min.Z, -1,0,0, c, txc[14],txc[13]), - video::S3DVertex(min.X,min.Y,min.Z, -1,0,0, c, txc[14],txc[15]), - video::S3DVertex(min.X,min.Y,max.Z, -1,0,0, c, txc[12],txc[15]), - // back - video::S3DVertex(max.X,max.Y,max.Z, 0,0,1, c, txc[16],txc[17]), - video::S3DVertex(min.X,max.Y,max.Z, 0,0,1, c, txc[18],txc[17]), - video::S3DVertex(min.X,min.Y,max.Z, 0,0,1, c, txc[18],txc[19]), - video::S3DVertex(max.X,min.Y,max.Z, 0,0,1, c, txc[16],txc[19]), - // front - video::S3DVertex(min.X,max.Y,min.Z, 0,0,-1, c, txc[20],txc[21]), - video::S3DVertex(max.X,max.Y,min.Z, 0,0,-1, c, txc[22],txc[21]), - video::S3DVertex(max.X,min.Y,min.Z, 0,0,-1, c, txc[22],txc[23]), - video::S3DVertex(min.X,min.Y,min.Z, 0,0,-1, c, txc[20],txc[23]), + video::S3DVertex vertices[24] = { + // up + video::S3DVertex(min.X, max.Y, max.Z, 0, 1, 0, c, txc[0], + txc[1]), + video::S3DVertex(max.X, max.Y, max.Z, 0, 1, 0, c, txc[2], + txc[1]), + video::S3DVertex(max.X, max.Y, min.Z, 0, 1, 0, c, txc[2], + txc[3]), + video::S3DVertex(min.X, max.Y, min.Z, 0, 1, 0, c, txc[0], + txc[3]), + // down + video::S3DVertex(min.X, min.Y, min.Z, 0, -1, 0, c, txc[4], + txc[5]), + video::S3DVertex(max.X, min.Y, min.Z, 0, -1, 0, c, txc[6], + txc[5]), + video::S3DVertex(max.X, min.Y, max.Z, 0, -1, 0, c, txc[6], + txc[7]), + video::S3DVertex(min.X, min.Y, max.Z, 0, -1, 0, c, txc[4], + txc[7]), + // right + video::S3DVertex(max.X, max.Y, min.Z, 1, 0, 0, c, txc[8], + txc[9]), + video::S3DVertex(max.X, max.Y, max.Z, 1, 0, 0, c, txc[10], + txc[9]), + video::S3DVertex(max.X, min.Y, max.Z, 1, 0, 0, c, txc[10], + txc[11]), + video::S3DVertex(max.X, min.Y, min.Z, 1, 0, 0, c, txc[8], + txc[11]), + // left + video::S3DVertex(min.X, max.Y, max.Z, -1, 0, 0, c, + txc[12], txc[13]), + video::S3DVertex(min.X, max.Y, min.Z, -1, 0, 0, c, + txc[14], txc[13]), + video::S3DVertex(min.X, min.Y, min.Z, -1, 0, 0, c, + txc[14], txc[15]), + video::S3DVertex(min.X, min.Y, max.Z, -1, 0, 0, c, + txc[12], txc[15]), + // back + video::S3DVertex(max.X, max.Y, max.Z, 0, 0, 1, c, txc[16], + txc[17]), + video::S3DVertex(min.X, max.Y, max.Z, 0, 0, 1, c, txc[18], + txc[17]), + video::S3DVertex(min.X, min.Y, max.Z, 0, 0, 1, c, txc[18], + txc[19]), + video::S3DVertex(max.X, min.Y, max.Z, 0, 0, 1, c, txc[16], + txc[19]), + // front + video::S3DVertex(min.X, max.Y, min.Z, 0, 0, -1, c, + txc[20], txc[21]), + video::S3DVertex(max.X, max.Y, min.Z, 0, 0, -1, c, + txc[22], txc[21]), + video::S3DVertex(max.X, min.Y, min.Z, 0, 0, -1, c, + txc[22], txc[23]), + video::S3DVertex(min.X, min.Y, min.Z, 0, 0, -1, c, + txc[20], txc[23]), }; - u16 indices[] = {0,1,2,2,3,0}; + u16 indices[] = {0, 1, 2, 2, 3, 0}; - for(u16 j = 0; j < 24; j += 4) - { + for (u16 j = 0; j < 24; j += 4) { scene::IMeshBuffer *buf = dst_mesh->getMeshBuffer(j / 4); buf->append(vertices + j, 4, indices, 6); } @@ -523,28 +573,21 @@ float FindVertexScore(vcache *v) const float ValenceBoostPower = 0.5f; const float MaxSizeVertexCache = 32.0f; - if (v->NumActiveTris == 0) - { + if (v->NumActiveTris == 0) { // No tri needs this vertex! return -1.0f; } float Score = 0.0f; int CachePosition = v->cachepos; - if (CachePosition < 0) - { + if (CachePosition < 0) { // Vertex is not in FIFO cache - no score. - } - else - { - if (CachePosition < 3) - { + } else { + if (CachePosition < 3) { // This vertex was used in the last triangle, // so it has a fixed score. Score = LastTriScore; - } - else - { + } else { // Points for being high in the cache. const float Scaler = 1.0f / (MaxSizeVertexCache - 3); Score = 1.0f - (CachePosition - 3) * Scaler; @@ -554,8 +597,7 @@ float FindVertexScore(vcache *v) // Bonus points for having a low number of tris still to // use the vert, so we get rid of lone verts quickly. - float ValenceBoost = powf(v->NumActiveTris, - -ValenceBoostPower); + float ValenceBoost = powf(v->NumActiveTris, -ValenceBoostPower); Score += ValenceBoostScale * ValenceBoost; return Score; @@ -569,7 +611,7 @@ class f_lru { public: - f_lru(vcache *v, tcache *t): vc(v), tc(t) + f_lru(vcache *v, tcache *t) : vc(v), tc(t) { for (int &i : cache) { i = -1; @@ -582,13 +624,10 @@ public: bool found = false; // Mark existing pos as empty - for (u16 i = 0; i < cachesize; i++) - { - if (cache[i] == vert) - { + for (u16 i = 0; i < cachesize; i++) { + if (cache[i] == vert) { // Move everything down - for (u16 j = i; j; j--) - { + for (u16 j = i; j; j--) { cache[j] = cache[j - 1]; } @@ -597,14 +636,12 @@ public: } } - if (!found) - { - if (cache[cachesize-1] != -1) - vc[cache[cachesize-1]].cachepos = -1; + if (!found) { + if (cache[cachesize - 1] != -1) + vc[cache[cachesize - 1]].cachepos = -1; // Move everything down - for (u16 i = cachesize - 1; i; i--) - { + for (u16 i = cachesize - 1; i; i--) { cache[i] = cache[i - 1]; } } @@ -614,11 +651,9 @@ public: u32 highest = 0; float hiscore = 0; - if (updatetris) - { + if (updatetris) { // Update cache positions - for (u16 i = 0; i < cachesize; i++) - { + for (u16 i = 0; i < cachesize; i++) { if (cache[i] == -1) break; @@ -632,17 +667,14 @@ public: break; const u16 trisize = vc[i].tris.size(); - for (u16 t = 0; t < trisize; t++) - { + for (u16 t = 0; t < trisize; t++) { tcache *tri = &tc[vc[i].tris[t]]; - tri->score = - vc[tri->ind[0]].score + - vc[tri->ind[1]].score + - vc[tri->ind[2]].score; + tri->score = vc[tri->ind[0]].score + + vc[tri->ind[1]].score + + vc[tri->ind[2]].score; - if (tri->score > hiscore) - { + if (tri->score > hiscore) { hiscore = tri->score; highest = vc[i].tris[t]; } @@ -666,7 +698,7 @@ http://home.comcast.net/~tom_forsyth/papers/fast_vert_cache_opt.html The function is thread-safe (read: you can optimize several meshes in different threads) \param mesh Source mesh for the operation. */ -scene::IMesh* createForsythOptimizedMesh(const scene::IMesh *mesh) +scene::IMesh *createForsythOptimizedMesh(const scene::IMesh *mesh) { if (!mesh) return 0; @@ -676,13 +708,12 @@ scene::IMesh* createForsythOptimizedMesh(const scene::IMesh *mesh) const u32 mbcount = mesh->getMeshBufferCount(); - for (u32 b = 0; b < mbcount; ++b) - { + for (u32 b = 0; b < mbcount; ++b) { const scene::IMeshBuffer *mb = mesh->getMeshBuffer(b); - if (mb->getIndexType() != video::EIT_16BIT) - { - //os::Printer::log("Cannot optimize a mesh with 32bit indices", ELL_ERROR); + if (mb->getIndexType() != video::EIT_16BIT) { + // os::Printer::log("Cannot optimize a mesh with 32bit indices", + // ELL_ERROR); newmesh->drop(); return 0; } @@ -698,29 +729,26 @@ scene::IMesh* createForsythOptimizedMesh(const scene::IMesh *mesh) f_lru lru(vc, tc); // init - for (u16 i = 0; i < vcount; i++) - { + for (u16 i = 0; i < vcount; i++) { vc[i].score = 0; vc[i].cachepos = -1; vc[i].NumActiveTris = 0; } // First pass: count how many times a vert is used - for (u32 i = 0; i < icount; i += 3) - { + for (u32 i = 0; i < icount; i += 3) { vc[ind[i]].NumActiveTris++; vc[ind[i + 1]].NumActiveTris++; vc[ind[i + 2]].NumActiveTris++; - const u32 tri_ind = i/3; + const u32 tri_ind = i / 3; tc[tri_ind].ind[0] = ind[i]; tc[tri_ind].ind[1] = ind[i + 1]; tc[tri_ind].ind[2] = ind[i + 2]; } // Second pass: list of each triangle - for (u32 i = 0; i < tcount; i++) - { + for (u32 i = 0; i < tcount; i++) { vc[tc[i].ind[0]].tris.push_back(i); vc[tc[i].ind[1]].tris.push_back(i); vc[tc[i].ind[2]].tris.push_back(i); @@ -729,358 +757,308 @@ scene::IMesh* createForsythOptimizedMesh(const scene::IMesh *mesh) } // Give initial scores - for (u16 i = 0; i < vcount; i++) - { + for (u16 i = 0; i < vcount; i++) { vc[i].score = FindVertexScore(&vc[i]); } - for (u32 i = 0; i < tcount; i++) - { - tc[i].score = - vc[tc[i].ind[0]].score + - vc[tc[i].ind[1]].score + - vc[tc[i].ind[2]].score; + for (u32 i = 0; i < tcount; i++) { + tc[i].score = vc[tc[i].ind[0]].score + vc[tc[i].ind[1]].score + + vc[tc[i].ind[2]].score; } - switch(mb->getVertexType()) - { - case video::EVT_STANDARD: - { - video::S3DVertex *v = (video::S3DVertex *) mb->getVertices(); + switch (mb->getVertexType()) { + case video::EVT_STANDARD: { + video::S3DVertex *v = (video::S3DVertex *)mb->getVertices(); - scene::SMeshBuffer *buf = new scene::SMeshBuffer(); - buf->Material = mb->getMaterial(); + scene::SMeshBuffer *buf = new scene::SMeshBuffer(); + buf->Material = mb->getMaterial(); - buf->Vertices.reallocate(vcount); - buf->Indices.reallocate(icount); + buf->Vertices.reallocate(vcount); + buf->Indices.reallocate(icount); - core::map sind; // search index for fast operation - typedef core::map::Node snode; + core::map + sind; // search index for fast operation + typedef core::map::Node snode; - // Main algorithm - u32 highest = 0; - u32 drawcalls = 0; - for (;;) - { - if (tc[highest].drawn) - { - bool found = false; - float hiscore = 0; - for (u32 t = 0; t < tcount; t++) - { - if (!tc[t].drawn) - { - if (tc[t].score > hiscore) - { - highest = t; - hiscore = tc[t].score; - found = true; - } - } - } - if (!found) - break; - } - - // Output the best triangle - u16 newind = buf->Vertices.size(); - - snode *s = sind.find(v[tc[highest].ind[0]]); - - if (!s) - { - buf->Vertices.push_back(v[tc[highest].ind[0]]); - buf->Indices.push_back(newind); - sind.insert(v[tc[highest].ind[0]], newind); - newind++; - } - else - { - buf->Indices.push_back(s->getValue()); - } - - s = sind.find(v[tc[highest].ind[1]]); - - if (!s) - { - buf->Vertices.push_back(v[tc[highest].ind[1]]); - buf->Indices.push_back(newind); - sind.insert(v[tc[highest].ind[1]], newind); - newind++; - } - else - { - buf->Indices.push_back(s->getValue()); - } - - s = sind.find(v[tc[highest].ind[2]]); - - if (!s) - { - buf->Vertices.push_back(v[tc[highest].ind[2]]); - buf->Indices.push_back(newind); - sind.insert(v[tc[highest].ind[2]], newind); - } - else - { - buf->Indices.push_back(s->getValue()); - } - - vc[tc[highest].ind[0]].NumActiveTris--; - vc[tc[highest].ind[1]].NumActiveTris--; - vc[tc[highest].ind[2]].NumActiveTris--; - - tc[highest].drawn = true; - - for (u16 j : tc[highest].ind) { - vcache *vert = &vc[j]; - for (u16 t = 0; t < vert->tris.size(); t++) - { - if (highest == vert->tris[t]) - { - vert->tris.erase(t); - break; + // Main algorithm + u32 highest = 0; + u32 drawcalls = 0; + for (;;) { + if (tc[highest].drawn) { + bool found = false; + float hiscore = 0; + for (u32 t = 0; t < tcount; t++) { + if (!tc[t].drawn) { + if (tc[t].score > hiscore) { + highest = t; + hiscore = tc[t].score; + found = true; } } } - - lru.add(tc[highest].ind[0]); - lru.add(tc[highest].ind[1]); - highest = lru.add(tc[highest].ind[2], true); - drawcalls++; + if (!found) + break; } - buf->setBoundingBox(mb->getBoundingBox()); - newmesh->addMeshBuffer(buf); - buf->drop(); - } - break; - case video::EVT_2TCOORDS: - { - video::S3DVertex2TCoords *v = (video::S3DVertex2TCoords *) mb->getVertices(); + // Output the best triangle + u16 newind = buf->Vertices.size(); - scene::SMeshBufferLightMap *buf = new scene::SMeshBufferLightMap(); - buf->Material = mb->getMaterial(); + snode *s = sind.find(v[tc[highest].ind[0]]); - buf->Vertices.reallocate(vcount); - buf->Indices.reallocate(icount); - - core::map sind; // search index for fast operation - typedef core::map::Node snode; - - // Main algorithm - u32 highest = 0; - u32 drawcalls = 0; - for (;;) - { - if (tc[highest].drawn) - { - bool found = false; - float hiscore = 0; - for (u32 t = 0; t < tcount; t++) - { - if (!tc[t].drawn) - { - if (tc[t].score > hiscore) - { - highest = t; - hiscore = tc[t].score; - found = true; - } - } - } - if (!found) - break; - } - - // Output the best triangle - u16 newind = buf->Vertices.size(); - - snode *s = sind.find(v[tc[highest].ind[0]]); - - if (!s) - { - buf->Vertices.push_back(v[tc[highest].ind[0]]); - buf->Indices.push_back(newind); - sind.insert(v[tc[highest].ind[0]], newind); - newind++; - } - else - { - buf->Indices.push_back(s->getValue()); - } - - s = sind.find(v[tc[highest].ind[1]]); - - if (!s) - { - buf->Vertices.push_back(v[tc[highest].ind[1]]); - buf->Indices.push_back(newind); - sind.insert(v[tc[highest].ind[1]], newind); - newind++; - } - else - { - buf->Indices.push_back(s->getValue()); - } - - s = sind.find(v[tc[highest].ind[2]]); - - if (!s) - { - buf->Vertices.push_back(v[tc[highest].ind[2]]); - buf->Indices.push_back(newind); - sind.insert(v[tc[highest].ind[2]], newind); - } - else - { - buf->Indices.push_back(s->getValue()); - } - - vc[tc[highest].ind[0]].NumActiveTris--; - vc[tc[highest].ind[1]].NumActiveTris--; - vc[tc[highest].ind[2]].NumActiveTris--; - - tc[highest].drawn = true; - - for (u16 j : tc[highest].ind) { - vcache *vert = &vc[j]; - for (u16 t = 0; t < vert->tris.size(); t++) - { - if (highest == vert->tris[t]) - { - vert->tris.erase(t); - break; - } - } - } - - lru.add(tc[highest].ind[0]); - lru.add(tc[highest].ind[1]); - highest = lru.add(tc[highest].ind[2]); - drawcalls++; + if (!s) { + buf->Vertices.push_back(v[tc[highest].ind[0]]); + buf->Indices.push_back(newind); + sind.insert(v[tc[highest].ind[0]], newind); + newind++; + } else { + buf->Indices.push_back(s->getValue()); } - buf->setBoundingBox(mb->getBoundingBox()); - newmesh->addMeshBuffer(buf); - buf->drop(); + s = sind.find(v[tc[highest].ind[1]]); - } - break; - case video::EVT_TANGENTS: - { - video::S3DVertexTangents *v = (video::S3DVertexTangents *) mb->getVertices(); - - scene::SMeshBufferTangents *buf = new scene::SMeshBufferTangents(); - buf->Material = mb->getMaterial(); - - buf->Vertices.reallocate(vcount); - buf->Indices.reallocate(icount); - - core::map sind; // search index for fast operation - typedef core::map::Node snode; - - // Main algorithm - u32 highest = 0; - u32 drawcalls = 0; - for (;;) - { - if (tc[highest].drawn) - { - bool found = false; - float hiscore = 0; - for (u32 t = 0; t < tcount; t++) - { - if (!tc[t].drawn) - { - if (tc[t].score > hiscore) - { - highest = t; - hiscore = tc[t].score; - found = true; - } - } - } - if (!found) - break; - } - - // Output the best triangle - u16 newind = buf->Vertices.size(); - - snode *s = sind.find(v[tc[highest].ind[0]]); - - if (!s) - { - buf->Vertices.push_back(v[tc[highest].ind[0]]); - buf->Indices.push_back(newind); - sind.insert(v[tc[highest].ind[0]], newind); - newind++; - } - else - { - buf->Indices.push_back(s->getValue()); - } - - s = sind.find(v[tc[highest].ind[1]]); - - if (!s) - { - buf->Vertices.push_back(v[tc[highest].ind[1]]); - buf->Indices.push_back(newind); - sind.insert(v[tc[highest].ind[1]], newind); - newind++; - } - else - { - buf->Indices.push_back(s->getValue()); - } - - s = sind.find(v[tc[highest].ind[2]]); - - if (!s) - { - buf->Vertices.push_back(v[tc[highest].ind[2]]); - buf->Indices.push_back(newind); - sind.insert(v[tc[highest].ind[2]], newind); - } - else - { - buf->Indices.push_back(s->getValue()); - } - - vc[tc[highest].ind[0]].NumActiveTris--; - vc[tc[highest].ind[1]].NumActiveTris--; - vc[tc[highest].ind[2]].NumActiveTris--; - - tc[highest].drawn = true; - - for (u16 j : tc[highest].ind) { - vcache *vert = &vc[j]; - for (u16 t = 0; t < vert->tris.size(); t++) - { - if (highest == vert->tris[t]) - { - vert->tris.erase(t); - break; - } - } - } - - lru.add(tc[highest].ind[0]); - lru.add(tc[highest].ind[1]); - highest = lru.add(tc[highest].ind[2]); - drawcalls++; + if (!s) { + buf->Vertices.push_back(v[tc[highest].ind[1]]); + buf->Indices.push_back(newind); + sind.insert(v[tc[highest].ind[1]], newind); + newind++; + } else { + buf->Indices.push_back(s->getValue()); } - buf->setBoundingBox(mb->getBoundingBox()); - newmesh->addMeshBuffer(buf); - buf->drop(); + s = sind.find(v[tc[highest].ind[2]]); + + if (!s) { + buf->Vertices.push_back(v[tc[highest].ind[2]]); + buf->Indices.push_back(newind); + sind.insert(v[tc[highest].ind[2]], newind); + } else { + buf->Indices.push_back(s->getValue()); + } + + vc[tc[highest].ind[0]].NumActiveTris--; + vc[tc[highest].ind[1]].NumActiveTris--; + vc[tc[highest].ind[2]].NumActiveTris--; + + tc[highest].drawn = true; + + for (u16 j : tc[highest].ind) { + vcache *vert = &vc[j]; + for (u16 t = 0; t < vert->tris.size(); t++) { + if (highest == vert->tris[t]) { + vert->tris.erase(t); + break; + } + } + } + + lru.add(tc[highest].ind[0]); + lru.add(tc[highest].ind[1]); + highest = lru.add(tc[highest].ind[2], true); + drawcalls++; } - break; + + buf->setBoundingBox(mb->getBoundingBox()); + newmesh->addMeshBuffer(buf); + buf->drop(); + } break; + case video::EVT_2TCOORDS: { + video::S3DVertex2TCoords *v = + (video::S3DVertex2TCoords *)mb->getVertices(); + + scene::SMeshBufferLightMap *buf = + new scene::SMeshBufferLightMap(); + buf->Material = mb->getMaterial(); + + buf->Vertices.reallocate(vcount); + buf->Indices.reallocate(icount); + + core::map + sind; // search index for fast operation + typedef core::map::Node + snode; + + // Main algorithm + u32 highest = 0; + u32 drawcalls = 0; + for (;;) { + if (tc[highest].drawn) { + bool found = false; + float hiscore = 0; + for (u32 t = 0; t < tcount; t++) { + if (!tc[t].drawn) { + if (tc[t].score > hiscore) { + highest = t; + hiscore = tc[t].score; + found = true; + } + } + } + if (!found) + break; + } + + // Output the best triangle + u16 newind = buf->Vertices.size(); + + snode *s = sind.find(v[tc[highest].ind[0]]); + + if (!s) { + buf->Vertices.push_back(v[tc[highest].ind[0]]); + buf->Indices.push_back(newind); + sind.insert(v[tc[highest].ind[0]], newind); + newind++; + } else { + buf->Indices.push_back(s->getValue()); + } + + s = sind.find(v[tc[highest].ind[1]]); + + if (!s) { + buf->Vertices.push_back(v[tc[highest].ind[1]]); + buf->Indices.push_back(newind); + sind.insert(v[tc[highest].ind[1]], newind); + newind++; + } else { + buf->Indices.push_back(s->getValue()); + } + + s = sind.find(v[tc[highest].ind[2]]); + + if (!s) { + buf->Vertices.push_back(v[tc[highest].ind[2]]); + buf->Indices.push_back(newind); + sind.insert(v[tc[highest].ind[2]], newind); + } else { + buf->Indices.push_back(s->getValue()); + } + + vc[tc[highest].ind[0]].NumActiveTris--; + vc[tc[highest].ind[1]].NumActiveTris--; + vc[tc[highest].ind[2]].NumActiveTris--; + + tc[highest].drawn = true; + + for (u16 j : tc[highest].ind) { + vcache *vert = &vc[j]; + for (u16 t = 0; t < vert->tris.size(); t++) { + if (highest == vert->tris[t]) { + vert->tris.erase(t); + break; + } + } + } + + lru.add(tc[highest].ind[0]); + lru.add(tc[highest].ind[1]); + highest = lru.add(tc[highest].ind[2]); + drawcalls++; + } + + buf->setBoundingBox(mb->getBoundingBox()); + newmesh->addMeshBuffer(buf); + buf->drop(); + + } break; + case video::EVT_TANGENTS: { + video::S3DVertexTangents *v = + (video::S3DVertexTangents *)mb->getVertices(); + + scene::SMeshBufferTangents *buf = + new scene::SMeshBufferTangents(); + buf->Material = mb->getMaterial(); + + buf->Vertices.reallocate(vcount); + buf->Indices.reallocate(icount); + + core::map + sind; // search index for fast operation + typedef core::map::Node + snode; + + // Main algorithm + u32 highest = 0; + u32 drawcalls = 0; + for (;;) { + if (tc[highest].drawn) { + bool found = false; + float hiscore = 0; + for (u32 t = 0; t < tcount; t++) { + if (!tc[t].drawn) { + if (tc[t].score > hiscore) { + highest = t; + hiscore = tc[t].score; + found = true; + } + } + } + if (!found) + break; + } + + // Output the best triangle + u16 newind = buf->Vertices.size(); + + snode *s = sind.find(v[tc[highest].ind[0]]); + + if (!s) { + buf->Vertices.push_back(v[tc[highest].ind[0]]); + buf->Indices.push_back(newind); + sind.insert(v[tc[highest].ind[0]], newind); + newind++; + } else { + buf->Indices.push_back(s->getValue()); + } + + s = sind.find(v[tc[highest].ind[1]]); + + if (!s) { + buf->Vertices.push_back(v[tc[highest].ind[1]]); + buf->Indices.push_back(newind); + sind.insert(v[tc[highest].ind[1]], newind); + newind++; + } else { + buf->Indices.push_back(s->getValue()); + } + + s = sind.find(v[tc[highest].ind[2]]); + + if (!s) { + buf->Vertices.push_back(v[tc[highest].ind[2]]); + buf->Indices.push_back(newind); + sind.insert(v[tc[highest].ind[2]], newind); + } else { + buf->Indices.push_back(s->getValue()); + } + + vc[tc[highest].ind[0]].NumActiveTris--; + vc[tc[highest].ind[1]].NumActiveTris--; + vc[tc[highest].ind[2]].NumActiveTris--; + + tc[highest].drawn = true; + + for (u16 j : tc[highest].ind) { + vcache *vert = &vc[j]; + for (u16 t = 0; t < vert->tris.size(); t++) { + if (highest == vert->tris[t]) { + vert->tris.erase(t); + break; + } + } + } + + lru.add(tc[highest].ind[0]); + lru.add(tc[highest].ind[1]); + highest = lru.add(tc[highest].ind[2]); + drawcalls++; + } + + buf->setBoundingBox(mb->getBoundingBox()); + newmesh->addMeshBuffer(buf); + buf->drop(); + } break; } - delete [] vc; - delete [] tc; + delete[] vc; + delete[] tc; } // for each meshbuffer diff --git a/src/client/mesh.h b/src/client/mesh.h index 103c61e45..1698026e8 100644 --- a/src/client/mesh.h +++ b/src/client/mesh.h @@ -35,7 +35,7 @@ void applyFacesShading(video::SColor &color, const v3f &normal); The resulting mesh has 6 materials (up, down, right, left, back, front) which must be defined by the caller. */ -scene::IAnimatedMesh* createCubeMesh(v3f scale); +scene::IAnimatedMesh *createCubeMesh(v3f scale); /* Multiplies each vertex coordinate by the specified scaling factors @@ -61,7 +61,8 @@ void setMeshColor(scene::IMesh *mesh, const video::SColor &color); /* Set a constant color for an animated mesh */ -void setAnimatedMeshColor(scene::IAnimatedMeshSceneNode *node, const video::SColor &color); +void setAnimatedMeshColor( + scene::IAnimatedMeshSceneNode *node, const video::SColor &color); /*! * Overwrites the color of a mesh buffer. @@ -75,13 +76,11 @@ void colorizeMeshBuffer(scene::IMeshBuffer *buf, const video::SColor *buffercolo the normal vector, and choose one of colorX, colorY or colorZ accordingly. */ -void setMeshColorByNormalXYZ(scene::IMesh *mesh, - const video::SColor &colorX, - const video::SColor &colorY, - const video::SColor &colorZ); +void setMeshColorByNormalXYZ(scene::IMesh *mesh, const video::SColor &colorX, + const video::SColor &colorY, const video::SColor &colorZ); -void setMeshColorByNormal(scene::IMesh *mesh, const v3f &normal, - const video::SColor &color); +void setMeshColorByNormal( + scene::IMesh *mesh, const v3f &normal, const video::SColor &color); /* Rotate the mesh by 6d facedir value. @@ -92,20 +91,20 @@ void rotateMeshBy6dFacedir(scene::IMesh *mesh, int facedir); /* Rotate the mesh around the axis and given angle in degrees. */ -void rotateMeshXYby (scene::IMesh *mesh, f64 degrees); -void rotateMeshXZby (scene::IMesh *mesh, f64 degrees); -void rotateMeshYZby (scene::IMesh *mesh, f64 degrees); +void rotateMeshXYby(scene::IMesh *mesh, f64 degrees); +void rotateMeshXZby(scene::IMesh *mesh, f64 degrees); +void rotateMeshYZby(scene::IMesh *mesh, f64 degrees); /* * Clone the mesh buffer. * The returned pointer should be dropped. */ -scene::IMeshBuffer* cloneMeshBuffer(scene::IMeshBuffer *mesh_buffer); +scene::IMeshBuffer *cloneMeshBuffer(scene::IMeshBuffer *mesh_buffer); /* Clone the mesh. */ -scene::SMesh* cloneMesh(scene::IMesh *src_mesh); +scene::SMesh *cloneMesh(scene::IMesh *src_mesh); /* Convert nodeboxes to mesh. Each tile goes into a different buffer. @@ -113,7 +112,7 @@ scene::SMesh* cloneMesh(scene::IMesh *src_mesh); uv_coords[24] - table of texture uv coords for each cuboid face expand - factor by which cuboids will be resized */ -scene::IMesh* convertNodeboxesToMesh(const std::vector &boxes, +scene::IMesh *convertNodeboxesToMesh(const std::vector &boxes, const f32 *uv_coords = NULL, float expand = 0); /* @@ -132,4 +131,4 @@ bool checkMeshNormals(scene::IMesh *mesh); http://home.comcast.net/~tom_forsyth/papers/fast_vert_cache_opt.html Ported from irrlicht 1.8 */ -scene::IMesh* createForsythOptimizedMesh(const scene::IMesh *mesh); +scene::IMesh *createForsythOptimizedMesh(const scene::IMesh *mesh); diff --git a/src/client/mesh_generator_thread.cpp b/src/client/mesh_generator_thread.cpp index 53b980eeb..69d61d5e6 100644 --- a/src/client/mesh_generator_thread.cpp +++ b/src/client/mesh_generator_thread.cpp @@ -48,13 +48,13 @@ QueuedMeshUpdate::~QueuedMeshUpdate() MeshUpdateQueue */ -MeshUpdateQueue::MeshUpdateQueue(Client *client): - m_client(client) +MeshUpdateQueue::MeshUpdateQueue(Client *client) : m_client(client) { m_cache_enable_shaders = g_settings->getBool("enable_shaders"); - m_cache_use_tangent_vertices = m_cache_enable_shaders && ( - g_settings->getBool("enable_bumpmapping") || - g_settings->getBool("enable_parallax_occlusion")); + m_cache_use_tangent_vertices = + m_cache_enable_shaders && + (g_settings->getBool("enable_bumpmapping") || + g_settings->getBool("enable_parallax_occlusion")); m_cache_smooth_lighting = g_settings->getBool("smooth_lighting"); m_meshgen_block_cache_size = g_settings->getS32("meshgen_block_cache_size"); } @@ -82,22 +82,23 @@ void MeshUpdateQueue::addBlock(Map *map, v3s16 p, bool ack_block_to_server, bool Cache the block data (force-update the center block, don't update the neighbors but get them if they aren't already cached) */ - std::vector cached_blocks; + std::vector cached_blocks; size_t cache_hit_counter = 0; - cached_blocks.reserve(3*3*3); + cached_blocks.reserve(3 * 3 * 3); v3s16 dp; for (dp.X = -1; dp.X <= 1; dp.X++) - for (dp.Y = -1; dp.Y <= 1; dp.Y++) - for (dp.Z = -1; dp.Z <= 1; dp.Z++) { - v3s16 p1 = p + dp; - CachedMapBlockData *cached_block; - if (dp == v3s16(0, 0, 0)) - cached_block = cacheBlock(map, p1, FORCE_UPDATE); - else - cached_block = cacheBlock(map, p1, SKIP_UPDATE_IF_ALREADY_CACHED, - &cache_hit_counter); - cached_blocks.push_back(cached_block); - } + for (dp.Y = -1; dp.Y <= 1; dp.Y++) + for (dp.Z = -1; dp.Z <= 1; dp.Z++) { + v3s16 p1 = p + dp; + CachedMapBlockData *cached_block; + if (dp == v3s16(0, 0, 0)) + cached_block = cacheBlock(map, p1, FORCE_UPDATE); + else + cached_block = cacheBlock(map, p1, + SKIP_UPDATE_IF_ALREADY_CACHED, + &cache_hit_counter); + cached_blocks.push_back(cached_block); + } g_profiler->avg("MeshUpdateQueue: MapBlocks from cache [%]", 100.0f * cache_hit_counter / cached_blocks.size()); @@ -115,7 +116,7 @@ void MeshUpdateQueue::addBlock(Map *map, v3s16 p, bool ack_block_to_server, bool if (q->p == p) { // NOTE: We are not adding a new position to the queue, thus // refcount_from_queue stays the same. - if(ack_block_to_server) + if (ack_block_to_server) q->ack_block_to_server = true; q->crack_level = m_client->getCrackLevel(); q->crack_pos = m_client->getCrackPos(); @@ -146,10 +147,10 @@ QueuedMeshUpdate *MeshUpdateQueue::pop() MutexAutoLock lock(m_mutex); bool must_be_urgent = !m_urgents.empty(); - for (std::vector::iterator i = m_queue.begin(); + for (std::vector::iterator i = m_queue.begin(); i != m_queue.end(); ++i) { QueuedMeshUpdate *q = *i; - if(must_be_urgent && m_urgents.count(q->p) == 0) + if (must_be_urgent && m_urgents.count(q->p) == 0) continue; m_queue.erase(i); m_urgents.erase(q->p); @@ -159,12 +160,11 @@ QueuedMeshUpdate *MeshUpdateQueue::pop() return NULL; } -CachedMapBlockData* MeshUpdateQueue::cacheBlock(Map *map, v3s16 p, UpdateMode mode, - size_t *cache_hit_counter) +CachedMapBlockData *MeshUpdateQueue::cacheBlock( + Map *map, v3s16 p, UpdateMode mode, size_t *cache_hit_counter) { CachedMapBlockData *cached_block = nullptr; - std::map::iterator it = - m_cache.find(p); + std::map::iterator it = m_cache.find(p); if (it != m_cache.end()) { cached_block = it->second; @@ -185,10 +185,11 @@ CachedMapBlockData* MeshUpdateQueue::cacheBlock(Map *map, v3s16 p, UpdateMode mo MapBlock *b = map->getBlockNoCreateNoEx(p); if (b) { if (!cached_block->data) - cached_block->data = - new MapNode[MAP_BLOCKSIZE * MAP_BLOCKSIZE * MAP_BLOCKSIZE]; + cached_block->data = new MapNode[MAP_BLOCKSIZE * MAP_BLOCKSIZE * + MAP_BLOCKSIZE]; memcpy(cached_block->data, b->getData(), - MAP_BLOCKSIZE * MAP_BLOCKSIZE * MAP_BLOCKSIZE * sizeof(MapNode)); + MAP_BLOCKSIZE * MAP_BLOCKSIZE * MAP_BLOCKSIZE * + sizeof(MapNode)); } else { delete[] cached_block->data; cached_block->data = nullptr; @@ -196,9 +197,9 @@ CachedMapBlockData* MeshUpdateQueue::cacheBlock(Map *map, v3s16 p, UpdateMode mo return cached_block; } -CachedMapBlockData* MeshUpdateQueue::getCachedBlock(const v3s16 &p) +CachedMapBlockData *MeshUpdateQueue::getCachedBlock(const v3s16 &p) { - std::map::iterator it = m_cache.find(p); + std::map::iterator it = m_cache.find(p); if (it != m_cache.end()) { return it->second; } @@ -207,8 +208,8 @@ CachedMapBlockData* MeshUpdateQueue::getCachedBlock(const v3s16 &p) void MeshUpdateQueue::fillDataFromMapBlockCache(QueuedMeshUpdate *q) { - MeshMakeData *data = new MeshMakeData(m_client, m_cache_enable_shaders, - m_cache_use_tangent_vertices); + MeshMakeData *data = new MeshMakeData( + m_client, m_cache_enable_shaders, m_cache_use_tangent_vertices); q->data = data; data->fillBlockDataBegin(q->p); @@ -218,17 +219,18 @@ void MeshUpdateQueue::fillDataFromMapBlockCache(QueuedMeshUpdate *q) // Collect data for 3*3*3 blocks from cache v3s16 dp; for (dp.X = -1; dp.X <= 1; dp.X++) - for (dp.Y = -1; dp.Y <= 1; dp.Y++) - for (dp.Z = -1; dp.Z <= 1; dp.Z++) { - v3s16 p = q->p + dp; - CachedMapBlockData *cached_block = getCachedBlock(p); - if (cached_block) { - cached_block->refcount_from_queue--; - cached_block->last_used_timestamp = t_now; - if (cached_block->data) - data->fillBlockData(dp, cached_block->data); - } - } + for (dp.Y = -1; dp.Y <= 1; dp.Y++) + for (dp.Z = -1; dp.Z <= 1; dp.Z++) { + v3s16 p = q->p + dp; + CachedMapBlockData *cached_block = getCachedBlock(p); + if (cached_block) { + cached_block->refcount_from_queue--; + cached_block->last_used_timestamp = t_now; + if (cached_block->data) + data->fillBlockData( + dp, cached_block->data); + } + } data->setCrack(q->crack_level, q->crack_pos); data->setSmoothLighting(m_cache_smooth_lighting); @@ -237,7 +239,7 @@ void MeshUpdateQueue::fillDataFromMapBlockCache(QueuedMeshUpdate *q) void MeshUpdateQueue::cleanupCache() { const int mapblock_kB = MAP_BLOCKSIZE * MAP_BLOCKSIZE * MAP_BLOCKSIZE * - sizeof(MapNode) / 1000; + sizeof(MapNode) / 1000; g_profiler->avg("MeshUpdateQueue MapBlock cache size kB", mapblock_kB * m_cache.size()); @@ -245,16 +247,20 @@ void MeshUpdateQueue::cleanupCache() // anything get older than cache_seconds_max or deleted before 2 seconds. const int cache_seconds_max = 10; const int cache_soft_max_size = m_meshgen_block_cache_size * 1000 / mapblock_kB; - int cache_seconds = MYMAX(2, cache_seconds_max - - m_cache.size() / (cache_soft_max_size / cache_seconds_max)); + int cache_seconds = MYMAX(2, + cache_seconds_max - + m_cache.size() / + (cache_soft_max_size / + cache_seconds_max)); int t_now = time(0); - for (std::map::iterator it = m_cache.begin(); - it != m_cache.end(); ) { + for (std::map::iterator it = m_cache.begin(); + it != m_cache.end();) { CachedMapBlockData *cached_block = it->second; if (cached_block->refcount_from_queue == 0 && - cached_block->last_used_timestamp < t_now - cache_seconds) { + cached_block->last_used_timestamp < + t_now - cache_seconds) { m_cache.erase(it++); delete cached_block; } else { @@ -267,16 +273,15 @@ void MeshUpdateQueue::cleanupCache() MeshUpdateThread */ -MeshUpdateThread::MeshUpdateThread(Client *client): - UpdateThread("Mesh"), - m_queue_in(client) +MeshUpdateThread::MeshUpdateThread(Client *client) : + UpdateThread("Mesh"), m_queue_in(client) { m_generation_interval = g_settings->getU16("mesh_generation_interval"); m_generation_interval = rangelim(m_generation_interval, 0, 50); } -void MeshUpdateThread::updateBlock(Map *map, v3s16 p, bool ack_block_to_server, - bool urgent) +void MeshUpdateThread::updateBlock( + Map *map, v3s16 p, bool ack_block_to_server, bool urgent) { // Allow the MeshUpdateQueue to do whatever it wants m_queue_in.addBlock(map, p, ack_block_to_server, urgent); diff --git a/src/client/minimap.cpp b/src/client/minimap.cpp index 68770ec19..e321aaae5 100644 --- a/src/client/minimap.cpp +++ b/src/client/minimap.cpp @@ -26,7 +26,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "mapblock.h" #include "client/renderingengine.h" - //// //// MinimapUpdateThread //// @@ -58,7 +57,7 @@ bool MinimapUpdateThread::pushBlockUpdate(v3s16 pos, MinimapMapblock *data) // Add the block QueuedMinimapUpdate q; - q.pos = pos; + q.pos = pos; q.data = data; m_update_queue.push_back(q); @@ -84,7 +83,6 @@ void MinimapUpdateThread::enqueueBlock(v3s16 pos, MinimapMapblock *data) deferUpdate(); } - void MinimapUpdateThread::doUpdate() { QueuedMinimapUpdate update; @@ -92,8 +90,9 @@ void MinimapUpdateThread::doUpdate() while (popBlockUpdate(&update)) { if (update.data) { // Swap two values in the map using single lookup - std::pair::iterator, bool> - result = m_blocks_cache.insert(std::make_pair(update.pos, update.data)); + std::pair::iterator, bool> + result = m_blocks_cache.insert(std::make_pair( + update.pos, update.data)); if (!result.second) { delete result.first->second; result.first->second = update.data; @@ -121,51 +120,60 @@ void MinimapUpdateThread::getMap(v3s16 pos, s16 size, s16 height) v3s16 blockpos_min = getNodeBlockPos(pos_min); v3s16 blockpos_max = getNodeBlockPos(pos_max); -// clear the map + // clear the map for (int z = 0; z < size; z++) - for (int x = 0; x < size; x++) { - MinimapPixel &mmpixel = data->minimap_scan[x + z * size]; - mmpixel.air_count = 0; - mmpixel.height = 0; - mmpixel.n = MapNode(CONTENT_AIR); - } + for (int x = 0; x < size; x++) { + MinimapPixel &mmpixel = data->minimap_scan[x + z * size]; + mmpixel.air_count = 0; + mmpixel.height = 0; + mmpixel.n = MapNode(CONTENT_AIR); + } -// draw the map + // draw the map v3s16 blockpos; for (blockpos.Z = blockpos_min.Z; blockpos.Z <= blockpos_max.Z; ++blockpos.Z) - for (blockpos.Y = blockpos_min.Y; blockpos.Y <= blockpos_max.Y; ++blockpos.Y) - for (blockpos.X = blockpos_min.X; blockpos.X <= blockpos_max.X; ++blockpos.X) { - std::map::const_iterator pblock = - m_blocks_cache.find(blockpos); - if (pblock == m_blocks_cache.end()) - continue; - const MinimapMapblock &block = *pblock->second; + for (blockpos.Y = blockpos_min.Y; blockpos.Y <= blockpos_max.Y; + ++blockpos.Y) + for (blockpos.X = blockpos_min.X; blockpos.X <= blockpos_max.X; + ++blockpos.X) { + std::map::const_iterator + pblock = m_blocks_cache.find(blockpos); + if (pblock == m_blocks_cache.end()) + continue; + const MinimapMapblock &block = *pblock->second; - v3s16 block_node_min(blockpos * MAP_BLOCKSIZE); - v3s16 block_node_max(block_node_min + MAP_BLOCKSIZE - 1); - // clip - v3s16 range_min = componentwise_max(block_node_min, pos_min); - v3s16 range_max = componentwise_min(block_node_max, pos_max); + v3s16 block_node_min(blockpos * MAP_BLOCKSIZE); + v3s16 block_node_max(block_node_min + MAP_BLOCKSIZE - 1); + // clip + v3s16 range_min = componentwise_max( + block_node_min, pos_min); + v3s16 range_max = componentwise_min( + block_node_max, pos_max); - v3s16 pos; - pos.Y = range_min.Y; - for (pos.Z = range_min.Z; pos.Z <= range_max.Z; ++pos.Z) - for (pos.X = range_min.X; pos.X <= range_max.X; ++pos.X) { - v3s16 inblock_pos = pos - block_node_min; - const MinimapPixel &in_pixel = - block.data[inblock_pos.Z * MAP_BLOCKSIZE + inblock_pos.X]; + v3s16 pos; + pos.Y = range_min.Y; + for (pos.Z = range_min.Z; pos.Z <= range_max.Z; ++pos.Z) + for (pos.X = range_min.X; pos.X <= range_max.X; + ++pos.X) { + v3s16 inblock_pos = pos - block_node_min; + const MinimapPixel &in_pixel = + block.data[inblock_pos.Z * MAP_BLOCKSIZE + + inblock_pos.X]; - v3s16 inmap_pos = pos - pos_min; - MinimapPixel &out_pixel = - data->minimap_scan[inmap_pos.X + inmap_pos.Z * size]; + v3s16 inmap_pos = pos - pos_min; + MinimapPixel &out_pixel = + data->minimap_scan[inmap_pos.X + + inmap_pos.Z * size]; - out_pixel.air_count += in_pixel.air_count; - if (in_pixel.n.param0 != CONTENT_AIR) { - out_pixel.n = in_pixel.n; - out_pixel.height = inmap_pos.Y + in_pixel.height; + out_pixel.air_count += in_pixel.air_count; + if (in_pixel.n.param0 != CONTENT_AIR) { + out_pixel.n = in_pixel.n; + out_pixel.height = + inmap_pos.Y + + in_pixel.height; + } + } } - } - } } //// @@ -174,40 +182,40 @@ void MinimapUpdateThread::getMap(v3s16 pos, s16 size, s16 height) Minimap::Minimap(Client *client) { - this->client = client; - this->driver = RenderingEngine::get_video_driver(); - this->m_tsrc = client->getTextureSource(); + this->client = client; + this->driver = RenderingEngine::get_video_driver(); + this->m_tsrc = client->getTextureSource(); this->m_shdrsrc = client->getShaderSource(); - this->m_ndef = client->getNodeDefManager(); + this->m_ndef = client->getNodeDefManager(); m_angle = 0.f; // Initialize static settings m_enable_shaders = g_settings->getBool("enable_shaders"); m_surface_mode_scan_height = - g_settings->getBool("minimap_double_scan_height") ? 256 : 128; + g_settings->getBool("minimap_double_scan_height") ? 256 : 128; // Initialize minimap data data = new MinimapData; - data->mode = MINIMAP_MODE_OFF; - data->is_radar = false; - data->map_invalidated = true; - data->texture = NULL; + data->mode = MINIMAP_MODE_OFF; + data->is_radar = false; + data->map_invalidated = true; + data->texture = NULL; data->heightmap_texture = NULL; data->minimap_shape_round = g_settings->getBool("minimap_shape_round"); // Get round minimap textures data->minimap_mask_round = driver->createImage( - m_tsrc->getTexture("minimap_mask_round.png"), - core::position2d(0, 0), - core::dimension2d(MINIMAP_MAX_SX, MINIMAP_MAX_SY)); + m_tsrc->getTexture("minimap_mask_round.png"), + core::position2d(0, 0), + core::dimension2d(MINIMAP_MAX_SX, MINIMAP_MAX_SY)); data->minimap_overlay_round = m_tsrc->getTexture("minimap_overlay_round.png"); // Get square minimap textures data->minimap_mask_square = driver->createImage( - m_tsrc->getTexture("minimap_mask_square.png"), - core::position2d(0, 0), - core::dimension2d(MINIMAP_MAX_SX, MINIMAP_MAX_SY)); + m_tsrc->getTexture("minimap_mask_square.png"), + core::position2d(0, 0), + core::dimension2d(MINIMAP_MAX_SX, MINIMAP_MAX_SY)); data->minimap_overlay_square = m_tsrc->getTexture("minimap_overlay_square.png"); // Create player marker texture @@ -282,25 +290,21 @@ MinimapShape Minimap::getMinimapShape() void Minimap::setMinimapMode(MinimapMode mode) { - static const MinimapModeDef modedefs[MINIMAP_MODE_COUNT] = { - {false, 0, 0}, - {false, m_surface_mode_scan_height, 256}, - {false, m_surface_mode_scan_height, 128}, - {false, m_surface_mode_scan_height, 64}, - {true, 32, 128}, - {true, 32, 64}, - {true, 32, 32} - }; + static const MinimapModeDef modedefs[MINIMAP_MODE_COUNT] = {{false, 0, 0}, + {false, m_surface_mode_scan_height, 256}, + {false, m_surface_mode_scan_height, 128}, + {false, m_surface_mode_scan_height, 64}, {true, 32, 128}, + {true, 32, 64}, {true, 32, 32}}; if (mode >= MINIMAP_MODE_COUNT) return; MutexAutoLock lock(m_mutex); - data->is_radar = modedefs[mode].is_radar; + data->is_radar = modedefs[mode].is_radar; data->scan_height = modedefs[mode].scan_height; - data->map_size = modedefs[mode].map_size; - data->mode = mode; + data->map_size = modedefs[mode].map_size; + data->mode = mode; m_minimap_update_thread->deferUpdate(); } @@ -332,47 +336,55 @@ void Minimap::blitMinimapPixelsToImageRadar(video::IImage *map_image) { video::SColor c(240, 0, 0, 0); for (s16 x = 0; x < data->map_size; x++) - for (s16 z = 0; z < data->map_size; z++) { - MinimapPixel *mmpixel = &data->minimap_scan[x + z * data->map_size]; + for (s16 z = 0; z < data->map_size; z++) { + MinimapPixel *mmpixel = + &data->minimap_scan[x + z * data->map_size]; - if (mmpixel->air_count > 0) - c.setGreen(core::clamp(core::round32(32 + mmpixel->air_count * 8), 0, 255)); - else - c.setGreen(0); + if (mmpixel->air_count > 0) + c.setGreen(core::clamp( + core::round32(32 + + mmpixel->air_count * 8), + 0, 255)); + else + c.setGreen(0); - map_image->setPixel(x, data->map_size - z - 1, c); - } + map_image->setPixel(x, data->map_size - z - 1, c); + } } void Minimap::blitMinimapPixelsToImageSurface( - video::IImage *map_image, video::IImage *heightmap_image) + video::IImage *map_image, video::IImage *heightmap_image) { // This variable creation/destruction has a 1% cost on rendering minimap video::SColor tilecolor; for (s16 x = 0; x < data->map_size; x++) - for (s16 z = 0; z < data->map_size; z++) { - MinimapPixel *mmpixel = &data->minimap_scan[x + z * data->map_size]; + for (s16 z = 0; z < data->map_size; z++) { + MinimapPixel *mmpixel = + &data->minimap_scan[x + z * data->map_size]; - const ContentFeatures &f = m_ndef->get(mmpixel->n); - const TileDef *tile = &f.tiledef[0]; + const ContentFeatures &f = m_ndef->get(mmpixel->n); + const TileDef *tile = &f.tiledef[0]; - // Color of the 0th tile (mostly this is the topmost) - if(tile->has_color) - tilecolor = tile->color; - else - mmpixel->n.getColor(f, &tilecolor); + // Color of the 0th tile (mostly this is the topmost) + if (tile->has_color) + tilecolor = tile->color; + else + mmpixel->n.getColor(f, &tilecolor); - tilecolor.setRed(tilecolor.getRed() * f.minimap_color.getRed() / 255); - tilecolor.setGreen(tilecolor.getGreen() * f.minimap_color.getGreen() / 255); - tilecolor.setBlue(tilecolor.getBlue() * f.minimap_color.getBlue() / 255); - tilecolor.setAlpha(240); + tilecolor.setRed(tilecolor.getRed() * f.minimap_color.getRed() / + 255); + tilecolor.setGreen(tilecolor.getGreen() * + f.minimap_color.getGreen() / 255); + tilecolor.setBlue(tilecolor.getBlue() * + f.minimap_color.getBlue() / 255); + tilecolor.setAlpha(240); - map_image->setPixel(x, data->map_size - z - 1, tilecolor); + map_image->setPixel(x, data->map_size - z - 1, tilecolor); - u32 h = mmpixel->height; - heightmap_image->setPixel(x,data->map_size - z - 1, - video::SColor(255, h, h, h)); - } + u32 h = mmpixel->height; + heightmap_image->setPixel(x, data->map_size - z - 1, + video::SColor(255, h, h, h)); + } } video::ITexture *Minimap::getMinimapTexture() @@ -383,10 +395,10 @@ video::ITexture *Minimap::getMinimapTexture() // create minimap and heightmap images in memory core::dimension2d dim(data->map_size, data->map_size); - video::IImage *map_image = driver->createImage(video::ECF_A8R8G8B8, dim); + video::IImage *map_image = driver->createImage(video::ECF_A8R8G8B8, dim); video::IImage *heightmap_image = driver->createImage(video::ECF_A8R8G8B8, dim); - video::IImage *minimap_image = driver->createImage(video::ECF_A8R8G8B8, - core::dimension2d(MINIMAP_MAX_SX, MINIMAP_MAX_SY)); + video::IImage *minimap_image = driver->createImage(video::ECF_A8R8G8B8, + core::dimension2d(MINIMAP_MAX_SX, MINIMAP_MAX_SY)); // Blit MinimapPixels to images if (data->is_radar) @@ -397,16 +409,19 @@ video::ITexture *Minimap::getMinimapTexture() map_image->copyToScaling(minimap_image); map_image->drop(); - video::IImage *minimap_mask = data->minimap_shape_round ? - data->minimap_mask_round : data->minimap_mask_square; + video::IImage *minimap_mask = data->minimap_shape_round + ? data->minimap_mask_round + : data->minimap_mask_square; if (minimap_mask) { for (s16 y = 0; y < MINIMAP_MAX_SY; y++) - for (s16 x = 0; x < MINIMAP_MAX_SX; x++) { - const video::SColor &mask_col = minimap_mask->getPixel(x, y); - if (!mask_col.getAlpha()) - minimap_image->setPixel(x, y, video::SColor(0,0,0,0)); - } + for (s16 x = 0; x < MINIMAP_MAX_SX; x++) { + const video::SColor &mask_col = + minimap_mask->getPixel(x, y); + if (!mask_col.getAlpha()) + minimap_image->setPixel( + x, y, video::SColor(0, 0, 0, 0)); + } } if (data->texture) @@ -416,7 +431,7 @@ video::ITexture *Minimap::getMinimapTexture() data->texture = driver->addTexture("minimap__", minimap_image); data->heightmap_texture = - driver->addTexture("minimap_heightmap__", heightmap_image); + driver->addTexture("minimap_heightmap__", heightmap_image); minimap_image->drop(); heightmap_image->drop(); @@ -428,10 +443,8 @@ video::ITexture *Minimap::getMinimapTexture() v3f Minimap::getYawVec() { if (data->minimap_shape_round) { - return v3f( - std::cos(m_angle * core::DEGTORAD), - std::sin(m_angle * core::DEGTORAD), - 1.0); + return v3f(std::cos(m_angle * core::DEGTORAD), + std::sin(m_angle * core::DEGTORAD), 1.0); } return v3f(1.0, 0.0, 1.0); @@ -445,9 +458,9 @@ scene::SMeshBuffer *Minimap::getMinimapMeshBuffer() static const video::SColor c(255, 255, 255, 255); buf->Vertices[0] = video::S3DVertex(-1, -1, 0, 0, 0, 1, c, 0, 1); - buf->Vertices[1] = video::S3DVertex(-1, 1, 0, 0, 0, 1, c, 0, 0); - buf->Vertices[2] = video::S3DVertex( 1, 1, 0, 0, 0, 1, c, 1, 0); - buf->Vertices[3] = video::S3DVertex( 1, -1, 0, 0, 0, 1, c, 1, 1); + buf->Vertices[1] = video::S3DVertex(-1, 1, 0, 0, 0, 1, c, 0, 0); + buf->Vertices[2] = video::S3DVertex(1, 1, 0, 0, 0, 1, c, 1, 0); + buf->Vertices[3] = video::S3DVertex(1, -1, 0, 0, 0, 1, c, 1, 1); buf->Indices[0] = 0; buf->Indices[1] = 1; @@ -474,8 +487,7 @@ void Minimap::drawMinimap() core::matrix4 oldViewMat = driver->getTransform(video::ETS_VIEW); driver->setViewPort(core::rect( - screensize.X - size - 10, 10, - screensize.X - 10, size + 10)); + screensize.X - size - 10, 10, screensize.X - 10, size + 10)); driver->setTransform(video::ETS_PROJECTION, core::matrix4()); driver->setTransform(video::ETS_VIEW, core::matrix4()); @@ -504,8 +516,9 @@ void Minimap::drawMinimap() driver->drawMeshBuffer(m_meshbuffer); // Draw overlay - video::ITexture *minimap_overlay = data->minimap_shape_round ? - data->minimap_overlay_round : data->minimap_overlay_square; + video::ITexture *minimap_overlay = data->minimap_shape_round + ? data->minimap_overlay_round + : data->minimap_overlay_square; material.TextureLayer[0].Texture = minimap_overlay; material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; driver->setMaterial(material); @@ -536,9 +549,8 @@ void Minimap::drawMinimap() static const video::SColor c[4] = {col, col, col, col}; f32 sin_angle = std::sin(m_angle * core::DEGTORAD); f32 cos_angle = std::cos(m_angle * core::DEGTORAD); - s32 marker_size2 = 0.025 * (float)size; - for (std::list::const_iterator - i = m_active_markers.begin(); + s32 marker_size2 = 0.025 * (float)size; + for (std::list::const_iterator i = m_active_markers.begin(); i != m_active_markers.end(); ++i) { v2f posf = *i; if (data->minimap_shape_round) { @@ -549,34 +561,36 @@ void Minimap::drawMinimap() } posf.X = (posf.X + 0.5) * (float)size; posf.Y = (posf.Y + 0.5) * (float)size; - core::rect dest_rect( - s_pos.X + posf.X - marker_size2, - s_pos.Y + posf.Y - marker_size2, - s_pos.X + posf.X + marker_size2, - s_pos.Y + posf.Y + marker_size2); - driver->draw2DImage(data->object_marker_red, dest_rect, - img_rect, &dest_rect, &c[0], true); + core::rect dest_rect(s_pos.X + posf.X - marker_size2, + s_pos.Y + posf.Y - marker_size2, + s_pos.X + posf.X + marker_size2, + s_pos.Y + posf.Y + marker_size2); + driver->draw2DImage(data->object_marker_red, dest_rect, img_rect, + &dest_rect, &c[0], true); } } void Minimap::updateActiveMarkers() { - video::IImage *minimap_mask = data->minimap_shape_round ? - data->minimap_mask_round : data->minimap_mask_square; + video::IImage *minimap_mask = data->minimap_shape_round + ? data->minimap_mask_round + : data->minimap_mask_square; const std::list &nametags = client->getCamera()->getNametags(); m_active_markers.clear(); for (Nametag *nametag : nametags) { - v3s16 pos = floatToInt(nametag->parent_node->getAbsolutePosition() + - intToFloat(client->getCamera()->getOffset(), BS), BS); - pos -= data->pos - v3s16(data->map_size / 2, - data->scan_height / 2, - data->map_size / 2); - if (pos.X < 0 || pos.X > data->map_size || - pos.Y < 0 || pos.Y > data->scan_height || - pos.Z < 0 || pos.Z > data->map_size) { + v3s16 pos = floatToInt( + nametag->parent_node->getAbsolutePosition() + + intToFloat(client->getCamera()->getOffset(), + BS), + BS); + pos -= data->pos - v3s16(data->map_size / 2, data->scan_height / 2, + data->map_size / 2); + if (pos.X < 0 || pos.X > data->map_size || pos.Y < 0 || + pos.Y > data->scan_height || pos.Z < 0 || + pos.Z > data->map_size) { continue; } pos.X = ((float)pos.X / data->map_size) * MINIMAP_MAX_SX; @@ -586,8 +600,9 @@ void Minimap::updateActiveMarkers() continue; } - m_active_markers.emplace_back(((float)pos.X / (float)MINIMAP_MAX_SX) - 0.5, - (1.0 - (float)pos.Z / (float)MINIMAP_MAX_SY) - 0.5); + m_active_markers.emplace_back( + ((float)pos.X / (float)MINIMAP_MAX_SX) - 0.5, + (1.0 - (float)pos.Z / (float)MINIMAP_MAX_SY) - 0.5); } } @@ -599,26 +614,26 @@ void MinimapMapblock::getMinimapNodes(VoxelManipulator *vmanip, const v3s16 &pos { for (s16 x = 0; x < MAP_BLOCKSIZE; x++) - for (s16 z = 0; z < MAP_BLOCKSIZE; z++) { - s16 air_count = 0; - bool surface_found = false; - MinimapPixel *mmpixel = &data[z * MAP_BLOCKSIZE + x]; + for (s16 z = 0; z < MAP_BLOCKSIZE; z++) { + s16 air_count = 0; + bool surface_found = false; + MinimapPixel *mmpixel = &data[z * MAP_BLOCKSIZE + x]; - for (s16 y = MAP_BLOCKSIZE -1; y >= 0; y--) { - v3s16 p(x, y, z); - MapNode n = vmanip->getNodeNoEx(pos + p); - if (!surface_found && n.getContent() != CONTENT_AIR) { - mmpixel->height = y; - mmpixel->n = n; - surface_found = true; - } else if (n.getContent() == CONTENT_AIR) { - air_count++; + for (s16 y = MAP_BLOCKSIZE - 1; y >= 0; y--) { + v3s16 p(x, y, z); + MapNode n = vmanip->getNodeNoEx(pos + p); + if (!surface_found && n.getContent() != CONTENT_AIR) { + mmpixel->height = y; + mmpixel->n = n; + surface_found = true; + } else if (n.getContent() == CONTENT_AIR) { + air_count++; + } } + + if (!surface_found) + mmpixel->n = MapNode(CONTENT_AIR); + + mmpixel->air_count = air_count; } - - if (!surface_found) - mmpixel->n = MapNode(CONTENT_AIR); - - mmpixel->air_count = air_count; - } } diff --git a/src/client/minimap.h b/src/client/minimap.h index 258d5330d..dc4db514a 100644 --- a/src/client/minimap.h +++ b/src/client/minimap.h @@ -33,7 +33,8 @@ class IShaderSource; #define MINIMAP_MAX_SX 512 #define MINIMAP_MAX_SY 512 -enum MinimapMode { +enum MinimapMode +{ MINIMAP_MODE_OFF, MINIMAP_MODE_SURFACEx1, MINIMAP_MODE_SURFACEx2, @@ -44,31 +45,36 @@ enum MinimapMode { MINIMAP_MODE_COUNT, }; -enum MinimapShape { +enum MinimapShape +{ MINIMAP_SHAPE_SQUARE, MINIMAP_SHAPE_ROUND, }; -struct MinimapModeDef { +struct MinimapModeDef +{ bool is_radar; u16 scan_height; u16 map_size; }; -struct MinimapPixel { +struct MinimapPixel +{ //! The topmost node that the minimap displays. MapNode n; u16 height; u16 air_count; }; -struct MinimapMapblock { +struct MinimapMapblock +{ void getMinimapNodes(VoxelManipulator *vmanip, const v3s16 &pos); MinimapPixel data[MAP_BLOCKSIZE * MAP_BLOCKSIZE]; }; -struct MinimapData { +struct MinimapData +{ bool is_radar; MinimapMode mode; v3s16 pos; @@ -88,12 +94,14 @@ struct MinimapData { video::ITexture *object_marker_red = nullptr; }; -struct QueuedMinimapUpdate { +struct QueuedMinimapUpdate +{ v3s16 pos; MinimapMapblock *data = nullptr; }; -class MinimapUpdateThread : public UpdateThread { +class MinimapUpdateThread : public UpdateThread +{ public: MinimapUpdateThread() : UpdateThread("Minimap") {} virtual ~MinimapUpdateThread(); @@ -114,7 +122,8 @@ private: std::map m_blocks_cache; }; -class Minimap { +class Minimap +{ public: Minimap(Client *client); ~Minimap(); @@ -133,12 +142,11 @@ public: void setMinimapShape(MinimapShape shape); MinimapShape getMinimapShape(); - video::ITexture *getMinimapTexture(); void blitMinimapPixelsToImageRadar(video::IImage *map_image); - void blitMinimapPixelsToImageSurface(video::IImage *map_image, - video::IImage *heightmap_image); + void blitMinimapPixelsToImageSurface( + video::IImage *map_image, video::IImage *heightmap_image); scene::SMeshBuffer *getMinimapMeshBuffer(); @@ -146,7 +154,7 @@ public: void drawMinimap(); video::IVideoDriver *driver; - Client* client; + Client *client; MinimapData *data; private: diff --git a/src/client/particles.cpp b/src/client/particles.cpp index 7acd996dc..5885987fc 100644 --- a/src/client/particles.cpp +++ b/src/client/particles.cpp @@ -44,28 +44,20 @@ static f32 random_f32(f32 min, f32 max) static v3f random_v3f(v3f min, v3f max) { - return v3f( - random_f32(min.X, max.X), - random_f32(min.Y, max.Y), - random_f32(min.Z, max.Z)); + return v3f(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, - const ParticleParameters &p, - video::ITexture *texture, - v2f texpos, - v2f texsize, - video::SColor color -): - scene::ISceneNode(RenderingEngine::get_scene_manager()->getRootSceneNode(), - RenderingEngine::get_scene_manager()) +Particle::Particle(IGameDef *gamedef, LocalPlayer *player, ClientEnvironment *env, + const ParticleParameters &p, video::ITexture *texture, v2f texpos, + v2f texsize, video::SColor color) : + scene::ISceneNode( + RenderingEngine::get_scene_manager()->getRootSceneNode(), + RenderingEngine::get_scene_manager()) { // Misc m_gamedef = gamedef; @@ -114,7 +106,8 @@ Particle::Particle( void Particle::OnRegisterSceneNode() { if (IsVisible) - SceneManager->registerNodeForRendering(this, scene::ESNRP_TRANSPARENT_EFFECT); + SceneManager->registerNodeForRendering( + this, scene::ESNRP_TRANSPARENT_EFFECT); ISceneNode::OnRegisterSceneNode(); } @@ -125,9 +118,8 @@ void Particle::render() driver->setMaterial(m_material); driver->setTransform(video::ETS_WORLD, AbsoluteTransformation); - u16 indices[] = {0,1,2, 2,3,0}; - driver->drawVertexPrimitiveList(m_vertices, 4, - indices, 2, video::EVT_STANDARD, + u16 indices[] = {0, 1, 2, 2, 3, 0}; + driver->drawVertexPrimitiveList(m_vertices, 4, indices, 2, video::EVT_STANDARD, scene::EPT_TRIANGLES, video::EIT_16BIT); } @@ -139,8 +131,8 @@ void Particle::step(float dtime) v3f p_pos = m_pos * BS; v3f p_velocity = m_velocity * BS; collisionMoveResult r = collisionMoveSimple(m_env, m_gamedef, BS * 0.5f, - box, 0.0f, dtime, &p_pos, &p_velocity, m_acceleration * BS, nullptr, - m_object_collision); + box, 0.0f, dtime, &p_pos, &p_velocity, + m_acceleration * BS, nullptr, m_object_collision); if (m_collision_removal && r.collides) { // force expiration of the particle m_expiration = -1.0; @@ -155,8 +147,7 @@ void Particle::step(float dtime) if (m_animation.type != TAT_NONE) { m_animation_time += dtime; int frame_length_i, frame_count; - m_animation.determineParams( - m_material.getTexture(0)->getSize(), + m_animation.determineParams(m_material.getTexture(0)->getSize(), &frame_count, &frame_length_i, NULL); float frame_length = frame_length_i / 1000.0; while (m_animation_time > frame_length) { @@ -177,11 +168,7 @@ void Particle::updateLight() u8 light = 0; bool pos_ok; - v3s16 p = v3s16( - floor(m_pos.X+0.5), - floor(m_pos.Y+0.5), - floor(m_pos.Z+0.5) - ); + v3s16 p = v3s16(floor(m_pos.X + 0.5), floor(m_pos.Y + 0.5), floor(m_pos.Z + 0.5)); MapNode n = m_env->getClientMap().getNode(p, &pos_ok); if (pos_ok) light = n.getLightBlend(m_env->getDayNightRatio(), m_gamedef->ndef()); @@ -189,10 +176,9 @@ void Particle::updateLight() light = blend_light(m_env->getDayNightRatio(), LIGHT_SUN, 0); u8 m_light = decode_light(light + m_glow); - m_color.set(255, - m_light * m_base_color.getRed() / 255, - m_light * m_base_color.getGreen() / 255, - m_light * m_base_color.getBlue() / 255); + m_color.set(255, m_light * m_base_color.getRed() / 255, + m_light * m_base_color.getGreen() / 255, + m_light * m_base_color.getBlue() / 255); } void Particle::updateVertices() @@ -205,7 +191,8 @@ void Particle::updateVertices() v2u32 framesize; texcoord = m_animation.getTextureCoords(texsize, m_animation_frame); m_animation.determineParams(texsize, NULL, NULL, &framesize); - framesize_f = v2f(framesize.X / (float) texsize.X, framesize.Y / (float) texsize.Y); + framesize_f = v2f(framesize.X / (float)texsize.X, + framesize.Y / (float)texsize.Y); tx0 = m_texpos.X + texcoord.X; tx1 = m_texpos.X + texcoord.X + framesize_f.X * m_texsize.X; @@ -218,27 +205,29 @@ void Particle::updateVertices() ty1 = m_texpos.Y + m_texsize.Y; } - m_vertices[0] = video::S3DVertex(-m_size / 2, -m_size / 2, - 0, 0, 0, 0, m_color, tx0, ty1); - m_vertices[1] = video::S3DVertex(m_size / 2, -m_size / 2, - 0, 0, 0, 0, m_color, tx1, ty1); - m_vertices[2] = video::S3DVertex(m_size / 2, m_size / 2, - 0, 0, 0, 0, m_color, tx1, ty0); - m_vertices[3] = video::S3DVertex(-m_size / 2, m_size / 2, - 0, 0, 0, 0, m_color, tx0, ty0); + m_vertices[0] = video::S3DVertex( + -m_size / 2, -m_size / 2, 0, 0, 0, 0, m_color, tx0, ty1); + m_vertices[1] = video::S3DVertex( + m_size / 2, -m_size / 2, 0, 0, 0, 0, m_color, tx1, ty1); + m_vertices[2] = video::S3DVertex( + m_size / 2, m_size / 2, 0, 0, 0, 0, m_color, tx1, ty0); + m_vertices[3] = video::S3DVertex( + -m_size / 2, m_size / 2, 0, 0, 0, 0, m_color, tx0, ty0); v3s16 camera_offset = m_env->getCameraOffset(); for (video::S3DVertex &vertex : m_vertices) { if (m_vertical) { - v3f ppos = m_player->getPosition()/BS; - vertex.Pos.rotateXZBy(std::atan2(ppos.Z - m_pos.Z, ppos.X - m_pos.X) / - core::DEGTORAD + 90); + v3f ppos = m_player->getPosition() / BS; + vertex.Pos.rotateXZBy( + std::atan2(ppos.Z - m_pos.Z, ppos.X - m_pos.X) / + core::DEGTORAD + + 90); } else { vertex.Pos.rotateYZBy(m_player->getPitch()); vertex.Pos.rotateXZBy(m_player->getYaw()); } m_box.addInternalPoint(vertex.Pos); - vertex.Pos += m_pos*BS - intToFloat(camera_offset, BS); + vertex.Pos += m_pos * BS - intToFloat(camera_offset, BS); } } @@ -246,15 +235,11 @@ void Particle::updateVertices() ParticleSpawner */ -ParticleSpawner::ParticleSpawner( - IGameDef *gamedef, - LocalPlayer *player, - const ParticleSpawnerParameters &p, - u16 attached_id, - video::ITexture *texture, - ParticleManager *p_manager -): - m_particlemanager(p_manager), p(p) +ParticleSpawner::ParticleSpawner(IGameDef *gamedef, LocalPlayer *player, + const ParticleSpawnerParameters &p, u16 attached_id, + video::ITexture *texture, ParticleManager *p_manager) : + m_particlemanager(p_manager), + p(p) { m_gamedef = gamedef; m_player = player; @@ -270,7 +255,7 @@ ParticleSpawner::ParticleSpawner( } void ParticleSpawner::spawnParticle(ClientEnvironment *env, float radius, - const core::matrix4 *attached_absolute_pos_rot_matrix) + const core::matrix4 *attached_absolute_pos_rot_matrix) { v3f ppos = m_player->getPosition() / BS; v3f pos = random_v3f(p.minpos, p.maxpos); @@ -312,9 +297,10 @@ void ParticleSpawner::spawnParticle(ClientEnvironment *env, float radius, if (p.node.getContent() != CONTENT_IGNORE) { const ContentFeatures &f = - m_particlemanager->m_env->getGameDef()->ndef()->get(p.node); + m_particlemanager->m_env->getGameDef()->ndef()->get( + p.node); if (!ParticleManager::getNodeParticleParams(p.node, f, pp, &texture, - texpos, texsize, &color, p.node_tile)) + texpos, texsize, &color, p.node_tile)) return; } else { texture = m_texture; @@ -327,15 +313,7 @@ void ParticleSpawner::spawnParticle(ClientEnvironment *env, float radius, pp.size = random_f32(p.minsize, p.maxsize); m_particlemanager->addParticle(new Particle( - m_gamedef, - m_player, - env, - pp, - texture, - texpos, - texsize, - color - )); + m_gamedef, m_player, env, pp, texture, texpos, texsize, color)); } void ParticleSpawner::step(float dtime, ClientEnvironment *env) @@ -348,8 +326,10 @@ void ParticleSpawner::step(float dtime, ClientEnvironment *env) bool unloaded = false; const core::matrix4 *attached_absolute_pos_rot_matrix = nullptr; if (m_attached_id) { - if (GenericCAO *attached = dynamic_cast(env->getActiveObject(m_attached_id))) { - attached_absolute_pos_rot_matrix = attached->getAbsolutePosRotMatrix(); + if (GenericCAO *attached = dynamic_cast( + env->getActiveObject(m_attached_id))) { + attached_absolute_pos_rot_matrix = + attached->getAbsolutePosRotMatrix(); } else { unloaded = true; } @@ -357,14 +337,16 @@ void ParticleSpawner::step(float dtime, ClientEnvironment *env) if (p.time != 0) { // Spawner exists for a predefined timespan - for (auto i = m_spawntimes.begin(); i != m_spawntimes.end(); ) { + 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. + // Pretend to, but don't actually spawn a particle if it + // is attached to an unloaded object or distant from + // player. if (!unloaded) - spawnParticle(env, radius, attached_absolute_pos_rot_matrix); + spawnParticle(env, radius, + attached_absolute_pos_rot_matrix); i = m_spawntimes.erase(i); } else { @@ -380,7 +362,8 @@ void ParticleSpawner::step(float dtime, ClientEnvironment *env) for (int i = 0; i <= p.amount; i++) { if (rand() / (float)RAND_MAX < dtime) - spawnParticle(env, radius, attached_absolute_pos_rot_matrix); + spawnParticle(env, radius, + attached_absolute_pos_rot_matrix); } } } @@ -389,9 +372,9 @@ void ParticleSpawner::step(float dtime, ClientEnvironment *env) ParticleManager */ -ParticleManager::ParticleManager(ClientEnvironment *env) : - m_env(env) -{} +ParticleManager::ParticleManager(ClientEnvironment *env) : m_env(env) +{ +} ParticleManager::~ParticleManager() { @@ -400,8 +383,8 @@ ParticleManager::~ParticleManager() void ParticleManager::step(float dtime) { - stepParticles (dtime); - stepSpawners (dtime); + stepParticles(dtime); + stepSpawners(dtime); } void ParticleManager::stepSpawners(float dtime) @@ -442,83 +425,80 @@ void ParticleManager::clearAll() m_particle_spawners.erase(i++); } - for(auto i = m_particles.begin(); i != m_particles.end();) - { + for (auto i = m_particles.begin(); i != m_particles.end();) { (*i)->remove(); delete *i; i = m_particles.erase(i); } } -void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client, - LocalPlayer *player) +void ParticleManager::handleParticleEvent( + ClientEvent *event, Client *client, LocalPlayer *player) { switch (event->type) { - case CE_DELETE_PARTICLESPAWNER: { - deleteParticleSpawner(event->delete_particlespawner.id); - // no allocated memory in delete event - break; + case CE_DELETE_PARTICLESPAWNER: { + deleteParticleSpawner(event->delete_particlespawner.id); + // no allocated memory in delete event + break; + } + case CE_ADD_PARTICLESPAWNER: { + deleteParticleSpawner(event->add_particlespawner.id); + + const ParticleSpawnerParameters &p = *event->add_particlespawner.p; + + video::ITexture *texture = client->tsrc()->getTextureForMesh(p.texture); + + auto toadd = new ParticleSpawner(client, player, p, + event->add_particlespawner.attached_id, texture, this); + + addParticleSpawner(event->add_particlespawner.id, toadd); + + delete event->add_particlespawner.p; + break; + } + case CE_SPAWN_PARTICLE: { + ParticleParameters &p = *event->spawn_particle; + + video::ITexture *texture; + v2f texpos, texsize; + video::SColor color(0xFFFFFFFF); + + 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); } - case CE_ADD_PARTICLESPAWNER: { - deleteParticleSpawner(event->add_particlespawner.id); - const ParticleSpawnerParameters &p = *event->add_particlespawner.p; + // Allow keeping default random size + if (oldsize > 0.0f) + p.size = oldsize; - video::ITexture *texture = - client->tsrc()->getTextureForMesh(p.texture); + if (texture) { + Particle *toadd = new Particle(client, player, m_env, p, texture, + texpos, texsize, color); - auto toadd = new ParticleSpawner(client, player, - p, - event->add_particlespawner.attached_id, - texture, - this); - - addParticleSpawner(event->add_particlespawner.id, toadd); - - delete event->add_particlespawner.p; - break; + addParticle(toadd); } - case CE_SPAWN_PARTICLE: { - ParticleParameters &p = *event->spawn_particle; - video::ITexture *texture; - v2f texpos, texsize; - video::SColor color(0xFFFFFFFF); - - 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; + 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) +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) @@ -558,8 +538,8 @@ bool ParticleManager::getNodeParticleParams(const MapNode &n, // The final burst of particles when a node is finally dug, *not* particles // spawned during the digging of a node. -void ParticleManager::addDiggingParticles(IGameDef *gamedef, - LocalPlayer *player, v3s16 pos, const MapNode &n, const ContentFeatures &f) +void ParticleManager::addDiggingParticles(IGameDef *gamedef, LocalPlayer *player, + v3s16 pos, const MapNode &n, const ContentFeatures &f) { // No particles for "airlike" nodes if (f.drawtype == NDT_AIRLIKE) @@ -573,8 +553,8 @@ void ParticleManager::addDiggingParticles(IGameDef *gamedef, // During the digging of a node particles are spawned individually by this // function, called from Game::handleDigging() in game.cpp. -void ParticleManager::addNodeParticle(IGameDef *gamedef, - LocalPlayer *player, v3s16 pos, const MapNode &n, const ContentFeatures &f) +void ParticleManager::addNodeParticle(IGameDef *gamedef, LocalPlayer *player, v3s16 pos, + const MapNode &n, const ContentFeatures &f) { ParticleParameters p; video::ITexture *texture; @@ -587,31 +567,17 @@ void ParticleManager::addNodeParticle(IGameDef *gamedef, p.expirationtime = (rand() % 100) / 100.0f; // Physics - p.vel = v3f( - (rand() % 150) / 50.0f - 1.5f, - (rand() % 150) / 50.0f, - (rand() % 150) / 50.0f - 1.5f - ); - p.acc = v3f( - 0.0f, - -player->movement_gravity * player->physics_override_gravity / BS, - 0.0f - ); - 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 - ); + p.vel = v3f((rand() % 150) / 50.0f - 1.5f, (rand() % 150) / 50.0f, + (rand() % 150) / 50.0f - 1.5f); + p.acc = v3f(0.0f, + -player->movement_gravity * player->physics_override_gravity / BS, + 0.0f); + 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); Particle *toadd = new Particle( - gamedef, - player, - m_env, - p, - texture, - texpos, - texsize, - color); + gamedef, player, m_env, p, texture, texpos, texsize, color); addParticle(toadd); } @@ -622,7 +588,6 @@ void ParticleManager::addParticle(Particle *toadd) m_particles.push_back(toadd); } - void ParticleManager::addParticleSpawner(u64 id, ParticleSpawner *toadd) { MutexAutoLock lock(m_spawner_list_lock); diff --git a/src/client/particles.h b/src/client/particles.h index 2011f0262..b3a1f1156 100644 --- a/src/client/particles.h +++ b/src/client/particles.h @@ -33,41 +33,24 @@ struct ContentFeatures; class Particle : public scene::ISceneNode { - public: - Particle( - IGameDef* gamedef, - LocalPlayer *player, - ClientEnvironment *env, - const ParticleParameters &p, - video::ITexture *texture, - v2f texpos, - v2f texsize, - video::SColor color - ); +public: + Particle(IGameDef *gamedef, LocalPlayer *player, ClientEnvironment *env, + const ParticleParameters &p, video::ITexture *texture, v2f texpos, + v2f texsize, video::SColor color); ~Particle() = default; - virtual const aabb3f &getBoundingBox() const - { - return m_box; - } + virtual const aabb3f &getBoundingBox() const { return m_box; } - virtual u32 getMaterialCount() const - { - return 1; - } + virtual u32 getMaterialCount() const { return 1; } - virtual video::SMaterial& getMaterial(u32 i) - { - return m_material; - } + virtual video::SMaterial &getMaterial(u32 i) { return m_material; } virtual void OnRegisterSceneNode(); virtual void render(); void step(float dtime); - bool get_expired () - { return m_expiration < m_time; } + bool get_expired() { return m_expiration < m_time; } private: void updateLight(); @@ -107,23 +90,19 @@ private: class ParticleSpawner { public: - ParticleSpawner(IGameDef* gamedef, - LocalPlayer *player, - const ParticleSpawnerParameters &p, - u16 attached_id, - video::ITexture *texture, - ParticleManager* p_manager); + ParticleSpawner(IGameDef *gamedef, LocalPlayer *player, + const ParticleSpawnerParameters &p, u16 attached_id, + video::ITexture *texture, ParticleManager *p_manager); ~ParticleSpawner() = default; void step(float dtime, ClientEnvironment *env); - bool get_expired () - { return p.amount <= 0 && p.time != 0; } + bool get_expired() { return p.amount <= 0 && p.time != 0; } private: void spawnParticle(ClientEnvironment *env, float radius, - const core::matrix4 *attached_absolute_pos_rot_matrix); + const core::matrix4 *attached_absolute_pos_rot_matrix); ParticleManager *m_particlemanager; float m_time; @@ -140,21 +119,21 @@ private: */ class ParticleManager { -friend class ParticleSpawner; + friend class ParticleSpawner; + public: - ParticleManager(ClientEnvironment* env); + ParticleManager(ClientEnvironment *env); ~ParticleManager(); - void step (float dtime); + void step(float dtime); - void handleParticleEvent(ClientEvent *event, Client *client, - LocalPlayer *player); + void handleParticleEvent(ClientEvent *event, Client *client, LocalPlayer *player); void addDiggingParticles(IGameDef *gamedef, LocalPlayer *player, v3s16 pos, - const MapNode &n, const ContentFeatures &f); + const MapNode &n, const ContentFeatures &f); void addNodeParticle(IGameDef *gamedef, LocalPlayer *player, v3s16 pos, - const MapNode &n, const ContentFeatures &f); + const MapNode &n, const ContentFeatures &f); /** * This function is only used by client particle spawners @@ -163,17 +142,14 @@ public: * never overlap (u64) * @return new id */ - u64 generateSpawnerId() - { - return m_next_particle_spawner_id++; - } + u64 generateSpawnerId() { return m_next_particle_spawner_id++; } 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); + ParticleParameters &p, video::ITexture **texture, v2f &texpos, + v2f &texsize, video::SColor *color, u8 tilenum = 0); - void addParticle(Particle* toadd); + void addParticle(Particle *toadd); private: void addParticleSpawner(u64 id, ParticleSpawner *toadd); @@ -184,13 +160,13 @@ private: void clearAll(); - std::vector m_particles; - std::unordered_map m_particle_spawners; - // Start the particle spawner ids generated from here after u32_max. lower values are - // for server sent spawners. + std::vector m_particles; + std::unordered_map m_particle_spawners; + // Start the particle spawner ids generated from here after u32_max. lower values + // are for server sent spawners. u64 m_next_particle_spawner_id = U32_MAX + 1; - ClientEnvironment* m_env; + ClientEnvironment *m_env; std::mutex m_particle_list_lock; std::mutex m_spawner_list_lock; }; diff --git a/src/client/render/core.cpp b/src/client/render/core.cpp index 223af5142..a7644681e 100644 --- a/src/client/render/core.cpp +++ b/src/client/render/core.cpp @@ -27,10 +27,11 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "client/minimap.h" #include "client/content_cao.h" -RenderingCore::RenderingCore(IrrlichtDevice *_device, Client *_client, Hud *_hud) - : device(_device), driver(device->getVideoDriver()), smgr(device->getSceneManager()), - guienv(device->getGUIEnvironment()), client(_client), camera(client->getCamera()), - mapper(client->getMinimap()), hud(_hud) +RenderingCore::RenderingCore(IrrlichtDevice *_device, Client *_client, Hud *_hud) : + device(_device), driver(device->getVideoDriver()), + smgr(device->getSceneManager()), guienv(device->getGUIEnvironment()), + client(_client), camera(client->getCamera()), + mapper(client->getMinimap()), hud(_hud) { screensize = driver->getScreenSize(); virtual_size = screensize; @@ -55,7 +56,8 @@ void RenderingCore::updateScreenSize() } void RenderingCore::draw(video::SColor _skycolor, bool _show_hud, bool _show_minimap, - bool _draw_wield_tool, bool _draw_crosshair, bool _draw_tracers, bool _draw_esp) + bool _draw_wield_tool, bool _draw_crosshair, bool _draw_tracers, + bool _draw_esp) { v2u32 ss = driver->getScreenSize(); if (screensize != ss) { @@ -69,7 +71,7 @@ void RenderingCore::draw(video::SColor _skycolor, bool _show_hud, bool _show_min draw_crosshair = _draw_crosshair; draw_tracers = _draw_tracers; draw_esp = _draw_esp; - + beforeDraw(); drawAll(); } @@ -78,29 +80,29 @@ void RenderingCore::drawTracersAndESP() { ClientEnvironment &env = client->getEnv(); Camera *camera = client->getCamera(); - + v3f camera_offset = intToFloat(camera->getOffset(), BS); - + v3f eye_pos = (camera->getPosition() + camera->getDirection() - camera_offset); - - video::SMaterial material, oldmaterial; - oldmaterial = driver->getMaterial2D(); + + video::SMaterial material, oldmaterial; + oldmaterial = driver->getMaterial2D(); material.setFlag(video::EMF_LIGHTING, false); material.setFlag(video::EMF_BILINEAR_FILTER, false); material.setFlag(video::EMF_ZBUFFER, false); material.setFlag(video::EMF_ZWRITE_ENABLE, false); driver->setMaterial(material); - + auto allObjects = env.getAllActiveObjects(); for (auto &it : allObjects) { ClientActiveObject *cao = it.second; if (cao->isLocalPlayer() || cao->getParent()) continue; GenericCAO *obj = dynamic_cast(cao); - if (! obj) + if (!obj) continue; aabb3f box; - if (! obj->getSelectionBox(&box)) + if (!obj->getSelectionBox(&box)) continue; v3f pos = obj->getPosition(); pos -= camera_offset; @@ -110,9 +112,10 @@ void RenderingCore::drawTracersAndESP() if (draw_esp) driver->draw3DBox(box, video::SColor(255, 255, 255, 255)); if (draw_tracers) - driver->draw3DLine(eye_pos, pos, video::SColor(255, 255, 255, 255)); + driver->draw3DLine( + eye_pos, pos, video::SColor(255, 255, 255, 255)); } - + driver->setMaterial(oldmaterial); } @@ -134,7 +137,7 @@ void RenderingCore::drawHUD() if (show_hud) { if (draw_crosshair) hud->drawCrosshair(); - + hud->drawHotbar(client->getEnv().getLocalPlayer()->getWieldIndex()); hud->drawLuaElements(camera->getOffset()); camera->drawNametags(); diff --git a/src/client/render/interlaced.cpp b/src/client/render/interlaced.cpp index 2aadadc17..c5d9c9c24 100644 --- a/src/client/render/interlaced.cpp +++ b/src/client/render/interlaced.cpp @@ -24,8 +24,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "client/tile.h" RenderingCoreInterlaced::RenderingCoreInterlaced( - IrrlichtDevice *_device, Client *_client, Hud *_hud) - : RenderingCoreStereo(_device, _client, _hud) + IrrlichtDevice *_device, Client *_client, Hud *_hud) : + RenderingCoreStereo(_device, _client, _hud) { initMaterial(); } diff --git a/src/client/render/plain.cpp b/src/client/render/plain.cpp index a130a14eb..8ac5e106b 100644 --- a/src/client/render/plain.cpp +++ b/src/client/render/plain.cpp @@ -27,8 +27,8 @@ inline u32 scaledown(u32 coef, u32 size) } RenderingCorePlain::RenderingCorePlain( - IrrlichtDevice *_device, Client *_client, Hud *_hud) - : RenderingCore(_device, _client, _hud) + IrrlichtDevice *_device, Client *_client, Hud *_hud) : + RenderingCore(_device, _client, _hud) { scale = g_settings->getU16("undersampling"); } diff --git a/src/client/render/sidebyside.cpp b/src/client/render/sidebyside.cpp index ed08810db..89e4b5e80 100644 --- a/src/client/render/sidebyside.cpp +++ b/src/client/render/sidebyside.cpp @@ -22,9 +22,10 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include "client/hud.h" -RenderingCoreSideBySide::RenderingCoreSideBySide( - IrrlichtDevice *_device, Client *_client, Hud *_hud, bool _horizontal, bool _flipped) - : RenderingCoreStereo(_device, _client, _hud), horizontal(_horizontal), flipped(_flipped) +RenderingCoreSideBySide::RenderingCoreSideBySide(IrrlichtDevice *_device, Client *_client, + Hud *_hud, bool _horizontal, bool _flipped) : + RenderingCoreStereo(_device, _client, _hud), + horizontal(_horizontal), flipped(_flipped) { } diff --git a/src/client/render/stereo.cpp b/src/client/render/stereo.cpp index 967b5a78f..99997c967 100644 --- a/src/client/render/stereo.cpp +++ b/src/client/render/stereo.cpp @@ -24,8 +24,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "settings.h" RenderingCoreStereo::RenderingCoreStereo( - IrrlichtDevice *_device, Client *_client, Hud *_hud) - : RenderingCore(_device, _client, _hud) + IrrlichtDevice *_device, Client *_client, Hud *_hud) : + RenderingCore(_device, _client, _hud) { eye_offset = BS * g_settings->getFloat("3d_paralax_strength"); } diff --git a/src/client/renderingengine.cpp b/src/client/renderingengine.cpp index 1534289d4..0b7a242fa 100644 --- a/src/client/renderingengine.cpp +++ b/src/client/renderingengine.cpp @@ -37,7 +37,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "gettext.h" #include "../gui/guiSkin.h" -#if !defined(_WIN32) && !defined(__APPLE__) && !defined(__ANDROID__) && \ +#if !defined(_WIN32) && !defined(__APPLE__) && !defined(__ANDROID__) && \ !defined(SERVER) && !defined(__HAIKU__) #define XORG_USED #endif @@ -58,7 +58,6 @@ with this program; if not, write to the Free Software Foundation, Inc., RenderingEngine *RenderingEngine::s_singleton = nullptr; - static gui::GUISkin *createSkin(gui::IGUIEnvironment *environment, gui::EGUI_SKIN_TYPE type, video::IVideoDriver *driver) { @@ -67,7 +66,7 @@ static gui::GUISkin *createSkin(gui::IGUIEnvironment *environment, gui::IGUIFont *builtinfont = environment->getBuiltInFont(); gui::IGUIFontBitmap *bitfont = nullptr; if (builtinfont && builtinfont->getType() == gui::EGFT_BITMAP) - bitfont = (gui::IGUIFontBitmap*)builtinfont; + bitfont = (gui::IGUIFontBitmap *)builtinfont; gui::IGUISpriteBank *bank = 0; skin->setFont(builtinfont); @@ -80,7 +79,6 @@ static gui::GUISkin *createSkin(gui::IGUIEnvironment *environment, return skin; } - RenderingEngine::RenderingEngine(IEventReceiver *receiver) { sanity_check(!s_singleton); @@ -106,7 +104,7 @@ RenderingEngine::RenderingEngine(IEventReceiver *receiver) u32 i; for (i = 0; i != drivers.size(); i++) { if (!strcasecmp(driverstring.c_str(), - RenderingEngine::getVideoDriverName(drivers[i]))) { + RenderingEngine::getVideoDriverName(drivers[i]))) { driverType = drivers[i]; break; } @@ -134,9 +132,10 @@ RenderingEngine::RenderingEngine(IEventReceiver *receiver) #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"; - params.OGLES2ShaderPath = (porting::path_share + DIR_DELIM + rel_path + DIR_DELIM).c_str(); + std::string rel_path = std::string("client") + DIR_DELIM + "shaders" + DIR_DELIM + + "Irrlicht"; + params.OGLES2ShaderPath = + (porting::path_share + DIR_DELIM + rel_path + DIR_DELIM).c_str(); #endif m_device = createDeviceEx(params); @@ -144,8 +143,8 @@ RenderingEngine::RenderingEngine(IEventReceiver *receiver) s_singleton = this; - auto skin = createSkin(m_device->getGUIEnvironment(), - gui::EGST_WINDOWS_METALLIC, driver); + auto skin = createSkin(m_device->getGUIEnvironment(), gui::EGST_WINDOWS_METALLIC, + driver); m_device->getGUIEnvironment()->setSkin(skin); skin->drop(); } @@ -234,8 +233,7 @@ bool RenderingEngine::setupTopLevelWindow(const std::string &name) /* Setting general properties for the top level window */ verbosestream << "Client: Configuring general top level" - << " window properties" - << std::endl; + << " window properties" << std::endl; bool result = setWindowIcon(); return result; @@ -246,17 +244,16 @@ void RenderingEngine::setupTopLevelXorgWindow(const std::string &name) #ifdef XORG_USED const video::SExposedVideoData exposedData = driver->getExposedVideoData(); - Display *x11_dpl = reinterpret_cast(exposedData.OpenGLLinux.X11Display); + Display *x11_dpl = + reinterpret_cast(exposedData.OpenGLLinux.X11Display); if (x11_dpl == NULL) { warningstream << "Client: Could not find X11 Display in ExposedVideoData" - << std::endl; + << std::endl; return; } verbosestream << "Client: Configuring X11-specific top level" - << " window properties" - << std::endl; - + << " window properties" << std::endl; Window x11_win = reinterpret_cast(exposedData.OpenGLLinux.X11Window); @@ -284,38 +281,35 @@ void RenderingEngine::setupTopLevelXorgWindow(const std::string &name) // more, using gtk/gdk as the model it would seem that not using a FQDN is // not an issue for modern Xorg window managers. - verbosestream << "Client: Setting Xorg window manager Properties" - << std::endl; + verbosestream << "Client: Setting Xorg window manager Properties" << std::endl; - XSetWMProperties (x11_dpl, x11_win, NULL, NULL, NULL, 0, NULL, NULL, NULL); + XSetWMProperties(x11_dpl, x11_win, NULL, NULL, NULL, 0, NULL, NULL, NULL); // Set the _NET_WM_PID window property according to the EWMH spec. _NET_WM_PID // (in conjunction with WM_CLIENT_MACHINE) can be used by window managers to // force a shutdown of an application if it doesn't respond to the destroy // window message. - verbosestream << "Client: Setting Xorg _NET_WM_PID extened window manager property" - << std::endl; + verbosestream << "Client: Setting Xorg _NET_WM_PID extened window manager " + "property" + << std::endl; Atom NET_WM_PID = XInternAtom(x11_dpl, "_NET_WM_PID", false); pid_t pid = getpid(); - XChangeProperty(x11_dpl, x11_win, NET_WM_PID, - XA_CARDINAL, 32, PropModeReplace, - reinterpret_cast(&pid),1); + XChangeProperty(x11_dpl, x11_win, NET_WM_PID, XA_CARDINAL, 32, PropModeReplace, + reinterpret_cast(&pid), 1); // Set the WM_CLIENT_LEADER window property here. Minetest has only one // window and that window will always be the leader. - verbosestream << "Client: Setting Xorg WM_CLIENT_LEADER property" - << std::endl; + verbosestream << "Client: Setting Xorg WM_CLIENT_LEADER property" << std::endl; Atom WM_CLIENT_LEADER = XInternAtom(x11_dpl, "WM_CLIENT_LEADER", false); - XChangeProperty (x11_dpl, x11_win, WM_CLIENT_LEADER, - XA_WINDOW, 32, PropModeReplace, - reinterpret_cast(&x11_win), 1); + XChangeProperty(x11_dpl, x11_win, WM_CLIENT_LEADER, XA_WINDOW, 32, + PropModeReplace, reinterpret_cast(&x11_win), 1); #endif } @@ -543,8 +537,8 @@ void RenderingEngine::_draw_load_screen(const std::wstring &text, /* Draws the menu scene including (optional) cloud background. */ -void RenderingEngine::_draw_menu_scene(gui::IGUIEnvironment *guienv, - float dtime, bool clouds) +void RenderingEngine::_draw_menu_scene( + gui::IGUIEnvironment *guienv, float dtime, bool clouds) { bool cloud_menu_background = clouds && g_settings->getBool("menu_clouds"); if (cloud_menu_background) { @@ -604,9 +598,11 @@ void RenderingEngine::_finalize() } void RenderingEngine::_draw_scene(video::SColor skycolor, bool show_hud, - bool show_minimap, bool draw_wield_tool, bool draw_crosshair, bool draw_tracers, bool draw_esp) + bool show_minimap, bool draw_wield_tool, bool draw_crosshair, + bool draw_tracers, bool draw_esp) { - core->draw(skycolor, show_hud, show_minimap, draw_wield_tool, draw_crosshair, draw_tracers, draw_esp); + core->draw(skycolor, show_hud, show_minimap, draw_wield_tool, draw_crosshair, + draw_tracers, draw_esp); } const char *RenderingEngine::getVideoDriverName(irr::video::E_DRIVER_TYPE type) @@ -679,7 +675,6 @@ float RenderingEngine::getDisplayDensity() #elif defined(_WIN32) - static float calcDisplayDensity(irr::video::IVideoDriver *driver) { HWND hWnd; @@ -725,7 +720,7 @@ v2u32 RenderingEngine::getDisplaySize() return deskres; } -#else // __ANDROID__ +#else // __ANDROID__ float RenderingEngine::getDisplayDensity() { return porting::getDisplayDensity(); diff --git a/src/client/renderingengine.h b/src/client/renderingengine.h index c5fa8918b..c9f7d9223 100644 --- a/src/client/renderingengine.h +++ b/src/client/renderingengine.h @@ -118,7 +118,8 @@ public: } inline static void draw_scene(video::SColor skycolor, bool show_hud, - bool show_minimap, bool draw_wield_tool, bool draw_crosshair, bool draw_tracers, bool draw_esp) + bool show_minimap, bool draw_wield_tool, bool draw_crosshair, + bool draw_tracers, bool draw_esp) { s_singleton->_draw_scene(skycolor, show_hud, show_minimap, draw_wield_tool, draw_crosshair, draw_tracers, draw_esp); @@ -149,7 +150,8 @@ private: bool clouds = true); void _draw_scene(video::SColor skycolor, bool show_hud, bool show_minimap, - bool draw_wield_tool, bool draw_crosshair, bool draw_tracers, bool draw_esp); + bool draw_wield_tool, bool draw_crosshair, bool draw_tracers, + bool draw_esp); void _initialize(Client *client, Hud *hud); diff --git a/src/client/shader.cpp b/src/client/shader.cpp index ee6079f7a..9e0b73696 100644 --- a/src/client/shader.cpp +++ b/src/client/shader.cpp @@ -52,8 +52,7 @@ MutexedMap g_shadername_to_path_cache; Utilizes a thread-safe cache. */ -std::string getShaderPath(const std::string &name_of_shader, - const std::string &filename) +std::string getShaderPath(const std::string &name_of_shader, const std::string &filename) { std::string combined = name_of_shader + DIR_DELIM + filename; std::string fullpath; @@ -61,7 +60,7 @@ std::string getShaderPath(const std::string &name_of_shader, Check from cache */ bool incache = g_shadername_to_path_cache.get(combined, &fullpath); - if(incache) + if (incache) return fullpath; /* @@ -70,7 +69,7 @@ std::string getShaderPath(const std::string &name_of_shader, std::string shader_path = g_settings->get("shader_path"); if (!shader_path.empty()) { std::string testpath = shader_path + DIR_DELIM + combined; - if(fs::PathExists(testpath)) + if (fs::PathExists(testpath)) fullpath = testpath; } @@ -78,12 +77,10 @@ std::string getShaderPath(const std::string &name_of_shader, Check from default data directory */ if (fullpath.empty()) { - std::string rel_path = std::string("client") + DIR_DELIM - + "shaders" + DIR_DELIM - + name_of_shader + DIR_DELIM - + filename; + std::string rel_path = std::string("client") + DIR_DELIM + "shaders" + + DIR_DELIM + name_of_shader + DIR_DELIM + filename; std::string testpath = porting::path_share + DIR_DELIM + rel_path; - if(fs::PathExists(testpath)) + if (fs::PathExists(testpath)) fullpath = testpath; } @@ -102,13 +99,13 @@ class SourceShaderCache { public: void insert(const std::string &name_of_shader, const std::string &filename, - const std::string &program, bool prefer_local) + const std::string &program, bool prefer_local) { std::string combined = name_of_shader + DIR_DELIM + filename; // Try to use local shader instead if asked to - if(prefer_local){ + if (prefer_local) { std::string path = getShaderPath(name_of_shader, filename); - if(!path.empty()){ + if (!path.empty()) { std::string p = readFile(path); if (!p.empty()) { m_programs[combined] = p; @@ -119,8 +116,7 @@ public: m_programs[combined] = program; } - std::string get(const std::string &name_of_shader, - const std::string &filename) + std::string get(const std::string &name_of_shader, const std::string &filename) { std::string combined = name_of_shader + DIR_DELIM + filename; StringMap::iterator n = m_programs.find(combined); @@ -130,8 +126,8 @@ public: } // Primarily fetches from cache, secondarily tries to read from filesystem - std::string getOrLoad(const std::string &name_of_shader, - const std::string &filename) + std::string getOrLoad( + const std::string &name_of_shader, const std::string &filename) { std::string combined = name_of_shader + DIR_DELIM + filename; StringMap::iterator n = m_programs.find(combined); @@ -139,12 +135,13 @@ public: return n->second; std::string path = getShaderPath(name_of_shader, filename); if (path.empty()) { - infostream << "SourceShaderCache::getOrLoad(): No path found for \"" - << combined << "\"" << std::endl; + infostream << "SourceShaderCache::getOrLoad(): No path found for " + "\"" + << combined << "\"" << std::endl; return ""; } - infostream << "SourceShaderCache::getOrLoad(): Loading path \"" - << path << "\"" << std::endl; + infostream << "SourceShaderCache::getOrLoad(): Loading path \"" << path + << "\"" << std::endl; std::string p = readFile(path); if (!p.empty()) { m_programs[combined] = p; @@ -152,13 +149,14 @@ public: } return ""; } + private: StringMap m_programs; std::string readFile(const std::string &path) { std::ifstream is(path.c_str(), std::ios::binary); - if(!is.is_open()) + if (!is.is_open()) return ""; std::ostringstream tmp_os; tmp_os << is.rdbuf(); @@ -166,14 +164,13 @@ private: } }; - /* ShaderCallback: Sets constants that can be used in shaders */ class ShaderCallback : public video::IShaderConstantSetCallBack { - std::vector m_setters; + std::vector m_setters; public: ShaderCallback(const std::vector &factories) @@ -188,7 +185,8 @@ public: delete setter; } - virtual void OnSetConstants(video::IMaterialRendererServices *services, s32 userData) override + virtual void OnSetConstants( + video::IMaterialRendererServices *services, s32 userData) override { video::IVideoDriver *driver = services->getVideoDriver(); sanity_check(driver != NULL); @@ -199,14 +197,13 @@ public: setter->onSetConstants(services, is_highlevel); } - virtual void OnSetMaterial(const video::SMaterial& material) override + virtual void OnSetMaterial(const video::SMaterial &material) override { for (IShaderConstantSetter *setter : m_setters) setter->onSetMaterial(material); } }; - /* MainShaderConstantSetter: Set basic constants required for almost everything */ @@ -218,13 +215,13 @@ class MainShaderConstantSetter : public IShaderConstantSetter public: MainShaderConstantSetter() : - m_world_view_proj("mWorldViewProj"), - m_world("mWorld") - {} + m_world_view_proj("mWorldViewProj"), m_world("mWorld") + { + } ~MainShaderConstantSetter() = default; - virtual void onSetConstants(video::IMaterialRendererServices *services, - bool is_highlevel) + virtual void onSetConstants( + video::IMaterialRendererServices *services, bool is_highlevel) { video::IVideoDriver *driver = services->getVideoDriver(); sanity_check(driver); @@ -235,29 +232,28 @@ public: worldViewProj *= driver->getTransform(video::ETS_VIEW); worldViewProj *= driver->getTransform(video::ETS_WORLD); if (is_highlevel) - m_world_view_proj.set(*reinterpret_cast(worldViewProj.pointer()), services); + m_world_view_proj.set(*reinterpret_cast( + worldViewProj.pointer()), + services); else services->setVertexShaderConstant(worldViewProj.pointer(), 0, 4); // Set world matrix core::matrix4 world = driver->getTransform(video::ETS_WORLD); if (is_highlevel) - m_world.set(*reinterpret_cast(world.pointer()), services); + m_world.set(*reinterpret_cast(world.pointer()), + services); else services->setVertexShaderConstant(world.pointer(), 4, 4); - } }; - class MainShaderConstantSetterFactory : public IShaderConstantSetterFactory { public: - virtual IShaderConstantSetter* create() - { return new MainShaderConstantSetter(); } + virtual IShaderConstantSetter *create() { return new MainShaderConstantSetter(); } }; - /* ShaderSource */ @@ -275,8 +271,8 @@ public: The id 0 points to a null shader. Its material is EMT_SOLID. */ - u32 getShaderIdDirect(const std::string &name, - const u8 material_type, const u8 drawtype); + u32 getShaderIdDirect(const std::string &name, const u8 material_type, + const u8 drawtype); /* If shader specified by the name pointed by the id doesn't @@ -287,8 +283,7 @@ public: for processing. */ - u32 getShader(const std::string &name, - const u8 material_type, const u8 drawtype); + u32 getShader(const std::string &name, const u8 material_type, const u8 drawtype); ShaderInfo getShaderInfo(u32 id); @@ -299,7 +294,7 @@ public: // Insert a shader program into the cache without touching the // filesystem. Shall be called from the main thread. void insertSourceShader(const std::string &name_of_shader, - const std::string &filename, const std::string &program); + const std::string &filename, const std::string &program); // Rebuild shaders from the current set of source shaders // Shall be called from the main thread. @@ -311,7 +306,6 @@ public: } private: - // The id of the thread that is allowed to use irrlicht directly std::thread::id m_main_thread; @@ -343,8 +337,8 @@ IWritableShaderSource *createShaderSource() /* Generate shader given the shader name. */ -ShaderInfo generate_shader(const std::string &name, - u8 material_type, u8 drawtype, std::vector &callbacks, +ShaderInfo generate_shader(const std::string &name, u8 material_type, u8 drawtype, + std::vector &callbacks, const std::vector &setter_factories, SourceShaderCache *sourcecache); @@ -377,8 +371,8 @@ ShaderSource::~ShaderSource() } } -u32 ShaderSource::getShader(const std::string &name, - const u8 material_type, const u8 drawtype) +u32 ShaderSource::getShader( + const std::string &name, const u8 material_type, const u8 drawtype) { /* Get shader @@ -400,15 +394,15 @@ u32 ShaderSource::getShader(const std::string &name, /* infostream<<"Waiting for shader from main thread, name=\"" < - result = result_queue.pop_frontNoEx(); + while (true) { + GetResult result = result_queue.pop_frontNoEx(); if (result.key == name) { return result.item; } - errorstream << "Got shader with invalid name: " << result.key << std::endl; + errorstream << "Got shader with invalid name: " << result.key + << std::endl; } infostream << "getShader(): Failed" << std::endl; @@ -419,22 +413,22 @@ u32 ShaderSource::getShader(const std::string &name, /* This method generates all the shaders */ -u32 ShaderSource::getShaderIdDirect(const std::string &name, - const u8 material_type, const u8 drawtype) +u32 ShaderSource::getShaderIdDirect( + const std::string &name, const u8 material_type, const u8 drawtype) { - //infostream<<"getShaderIdDirect(): name=\""<name == name && info->material_type == material_type && - info->drawtype == drawtype) + if (info->name == name && info->material_type == material_type && + info->drawtype == drawtype) return i; } @@ -442,13 +436,14 @@ u32 ShaderSource::getShaderIdDirect(const std::string &name, Calling only allowed from main thread */ if (std::this_thread::get_id() != m_main_thread) { - errorstream<<"ShaderSource::getShaderIdDirect() " - "called not from main thread"<= m_shaderinfo_cache.size()) + if (id >= m_shaderinfo_cache.size()) return ShaderInfo(); return m_shaderinfo_cache[id]; @@ -478,8 +473,6 @@ ShaderInfo ShaderSource::getShaderInfo(u32 id) void ShaderSource::processQueue() { - - } void ShaderSource::insertSourceShader(const std::string &name_of_shader, @@ -513,13 +506,12 @@ void ShaderSource::rebuildShaders() ShaderInfo *info = &i; if (!info->name.empty()) { *info = generate_shader(info->name, info->material_type, - info->drawtype, m_callbacks, - m_setter_factories, &m_sourcecache); + info->drawtype, m_callbacks, m_setter_factories, + &m_sourcecache); } } } - ShaderInfo generate_shader(const std::string &name, u8 material_type, u8 drawtype, std::vector &callbacks, const std::vector &setter_factories, @@ -558,11 +550,13 @@ ShaderInfo generate_shader(const std::string &name, u8 material_type, u8 drawtyp video::IVideoDriver *driver = RenderingEngine::get_video_driver(); video::IGPUProgrammingServices *gpu = driver->getGPUProgrammingServices(); - if(!gpu){ - errorstream<<"generate_shader(): " - "failed to generate \""<getDriverType(), - enable_shaders, vertex_program, pixel_program, - geometry_program, is_highlevel); + load_shaders(name, sourcecache, driver->getDriverType(), enable_shaders, + vertex_program, pixel_program, geometry_program, is_highlevel); // Check hardware/driver support if (!vertex_program.empty() && !driver->queryFeature(video::EVDF_VERTEX_SHADER_1_1) && - !driver->queryFeature(video::EVDF_ARB_VERTEX_PROGRAM_1)){ - infostream<<"generate_shader(): vertex shaders disabled " - "because of missing driver/hardware support." - <queryFeature(video::EVDF_ARB_VERTEX_PROGRAM_1)) { + infostream << "generate_shader(): vertex shaders disabled " + "because of missing driver/hardware support." + << std::endl; vertex_program = ""; } if (!pixel_program.empty() && !driver->queryFeature(video::EVDF_PIXEL_SHADER_1_1) && - !driver->queryFeature(video::EVDF_ARB_FRAGMENT_PROGRAM_1)){ - infostream<<"generate_shader(): pixel shaders disabled " - "because of missing driver/hardware support." - <queryFeature(video::EVDF_ARB_FRAGMENT_PROGRAM_1)) { + infostream << "generate_shader(): pixel shaders disabled " + "because of missing driver/hardware support." + << std::endl; pixel_program = ""; } if (!geometry_program.empty() && - !driver->queryFeature(video::EVDF_GEOMETRY_SHADER)){ - infostream<<"generate_shader(): geometry shaders disabled " - "because of missing driver/hardware support." - <queryFeature(video::EVDF_GEOMETRY_SHADER)) { + infostream << "generate_shader(): geometry shaders disabled " + "because of missing driver/hardware support." + << std::endl; geometry_program = ""; } @@ -607,27 +600,27 @@ ShaderInfo generate_shader(const std::string &name, u8 material_type, u8 drawtyp // Create shaders header std::string shaders_header = "#version 120\n"; - static const char* drawTypes[] = { - "NDT_NORMAL", - "NDT_AIRLIKE", - "NDT_LIQUID", - "NDT_FLOWINGLIQUID", - "NDT_GLASSLIKE", - "NDT_ALLFACES", - "NDT_ALLFACES_OPTIONAL", - "NDT_TORCHLIKE", - "NDT_SIGNLIKE", - "NDT_PLANTLIKE", - "NDT_FENCELIKE", - "NDT_RAILLIKE", - "NDT_NODEBOX", - "NDT_GLASSLIKE_FRAMED", - "NDT_FIRELIKE", - "NDT_GLASSLIKE_FRAMED_OPTIONAL", - "NDT_PLANTLIKE_ROOTED", + static const char *drawTypes[] = { + "NDT_NORMAL", + "NDT_AIRLIKE", + "NDT_LIQUID", + "NDT_FLOWINGLIQUID", + "NDT_GLASSLIKE", + "NDT_ALLFACES", + "NDT_ALLFACES_OPTIONAL", + "NDT_TORCHLIKE", + "NDT_SIGNLIKE", + "NDT_PLANTLIKE", + "NDT_FENCELIKE", + "NDT_RAILLIKE", + "NDT_NODEBOX", + "NDT_GLASSLIKE_FRAMED", + "NDT_FIRELIKE", + "NDT_GLASSLIKE_FRAMED_OPTIONAL", + "NDT_PLANTLIKE_ROOTED", }; - for (int i = 0; i < 14; i++){ + for (int i = 0; i < 14; i++) { shaders_header += "#define "; shaders_header += drawTypes[i]; shaders_header += " "; @@ -635,22 +628,22 @@ ShaderInfo generate_shader(const std::string &name, u8 material_type, u8 drawtyp shaders_header += "\n"; } - static const char* materialTypes[] = { - "TILE_MATERIAL_BASIC", - "TILE_MATERIAL_ALPHA", - "TILE_MATERIAL_LIQUID_TRANSPARENT", - "TILE_MATERIAL_LIQUID_OPAQUE", - "TILE_MATERIAL_WAVING_LEAVES", - "TILE_MATERIAL_WAVING_PLANTS", - "TILE_MATERIAL_OPAQUE", - "TILE_MATERIAL_WAVING_LIQUID_BASIC", - "TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT", - "TILE_MATERIAL_WAVING_LIQUID_OPAQUE", - "TILE_MATERIAL_PLAIN", - "TILE_MATERIAL_PLAIN_ALPHA", + static const char *materialTypes[] = { + "TILE_MATERIAL_BASIC", + "TILE_MATERIAL_ALPHA", + "TILE_MATERIAL_LIQUID_TRANSPARENT", + "TILE_MATERIAL_LIQUID_OPAQUE", + "TILE_MATERIAL_WAVING_LEAVES", + "TILE_MATERIAL_WAVING_PLANTS", + "TILE_MATERIAL_OPAQUE", + "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 < 12; i++){ + for (int i = 0; i < 12; i++) { shaders_header += "#define "; shaders_header += materialTypes[i]; shaders_header += " "; @@ -675,7 +668,7 @@ ShaderInfo generate_shader(const std::string &name, u8 material_type, u8 drawtyp shaders_header += "\n"; float sample_step; int smooth = (int)g_settings->getFloat("normalmaps_smooth"); - switch (smooth){ + switch (smooth) { case 0: sample_step = 0.0078125; // 1.0 / 128.0 break; @@ -696,7 +689,7 @@ ShaderInfo generate_shader(const std::string &name, u8 material_type, u8 drawtyp if (g_settings->getBool("enable_bumpmapping")) shaders_header += "#define ENABLE_BUMPMAPPING\n"; - if (g_settings->getBool("enable_parallax_occlusion")){ + if (g_settings->getBool("enable_parallax_occlusion")) { int mode = g_settings->getFloat("parallax_occlusion_mode"); float scale = g_settings->getFloat("parallax_occlusion_scale"); float bias = g_settings->getFloat("parallax_occlusion_bias"); @@ -717,12 +710,13 @@ ShaderInfo generate_shader(const std::string &name, u8 material_type, u8 drawtyp } shaders_header += "#define USE_NORMALMAPS "; - if (g_settings->getBool("enable_bumpmapping") || g_settings->getBool("enable_parallax_occlusion")) + if (g_settings->getBool("enable_bumpmapping") || + g_settings->getBool("enable_parallax_occlusion")) shaders_header += "1\n"; else shaders_header += "0\n"; - if (g_settings->getBool("enable_waving_water")){ + if (g_settings->getBool("enable_waving_water")) { shaders_header += "#define ENABLE_WAVING_WATER 1\n"; shaders_header += "#define WATER_WAVE_HEIGHT "; shaders_header += ftos(g_settings->getFloat("water_wave_height")); @@ -733,7 +727,7 @@ ShaderInfo generate_shader(const std::string &name, u8 material_type, u8 drawtyp shaders_header += "#define WATER_WAVE_SPEED "; shaders_header += ftos(g_settings->getFloat("water_wave_speed")); shaders_header += "\n"; - } else{ + } else { shaders_header += "#define ENABLE_WAVING_WATER 0\n"; } @@ -757,9 +751,9 @@ ShaderInfo generate_shader(const std::string &name, u8 material_type, u8 drawtyp shaders_header += "\n"; // Call addHighLevelShaderMaterial() or addShaderMaterial() - const c8* vertex_program_ptr = 0; - const c8* pixel_program_ptr = 0; - const c8* geometry_program_ptr = 0; + const c8 *vertex_program_ptr = 0; + const c8 *pixel_program_ptr = 0; + const c8 *geometry_program_ptr = 0; if (!vertex_program.empty()) { vertex_program = shaders_header + vertex_program; vertex_program_ptr = vertex_program.c_str(); @@ -774,54 +768,57 @@ ShaderInfo generate_shader(const std::string &name, u8 material_type, u8 drawtyp } ShaderCallback *cb = new ShaderCallback(setter_factories); s32 shadermat = -1; - if(is_highlevel){ - infostream<<"Compiling high level shaders for "<addHighLevelShaderMaterial( - vertex_program_ptr, // Vertex shader program - "vertexMain", // Vertex shader entry point - video::EVST_VS_1_1, // Vertex shader version - pixel_program_ptr, // Pixel shader program - "pixelMain", // Pixel shader entry point - video::EPST_PS_1_2, // Pixel shader version - geometry_program_ptr, // Geometry shader program - "geometryMain", // Geometry shader entry point - video::EGST_GS_4_0, // Geometry shader version - scene::EPT_TRIANGLES, // Geometry shader input - scene::EPT_TRIANGLE_STRIP, // Geometry shader output - 0, // Support maximum number of vertices - cb, // Set-constant callback - shaderinfo.base_material, // Base material - 1 // Userdata passed to callback - ); - if(shadermat == -1){ - errorstream<<"generate_shader(): " - "failed to generate \""<addShaderMaterial( - vertex_program_ptr, // Vertex shader program - pixel_program_ptr, // Pixel shader program - cb, // Set-constant callback - shaderinfo.base_material, // Base material - 0 // Userdata passed to callback - ); + vertex_program_ptr, // Vertex shader program + pixel_program_ptr, // Pixel shader program + cb, // Set-constant callback + shaderinfo.base_material, // Base material + 0 // Userdata passed to callback + ); - if(shadermat == -1){ - errorstream<<"generate_shader(): " - "failed to generate \""<getMaterialRenderer(shadermat)->grab(); // Apply the newly created material type - shaderinfo.material = (video::E_MATERIAL_TYPE) shadermat; + shaderinfo.material = (video::E_MATERIAL_TYPE)shadermat; return shaderinfo; } @@ -847,43 +844,45 @@ void load_shaders(const std::string &name, SourceShaderCache *sourcecache, geometry_program = ""; is_highlevel = false; - if(enable_shaders){ + if (enable_shaders) { // Look for high level shaders - if(drivertype == video::EDT_DIRECT3D9){ + if (drivertype == video::EDT_DIRECT3D9) { // Direct3D 9: HLSL // (All shaders in one file) vertex_program = sourcecache->getOrLoad(name, "d3d9.hlsl"); pixel_program = vertex_program; geometry_program = vertex_program; - } - else if(drivertype == video::EDT_OPENGL){ + } else if (drivertype == video::EDT_OPENGL) { // OpenGL: GLSL - vertex_program = sourcecache->getOrLoad(name, "opengl_vertex.glsl"); - pixel_program = sourcecache->getOrLoad(name, "opengl_fragment.glsl"); - geometry_program = sourcecache->getOrLoad(name, "opengl_geometry.glsl"); + vertex_program = sourcecache->getOrLoad( + name, "opengl_vertex.glsl"); + pixel_program = sourcecache->getOrLoad( + name, "opengl_fragment.glsl"); + geometry_program = sourcecache->getOrLoad( + name, "opengl_geometry.glsl"); } - if (!vertex_program.empty() || !pixel_program.empty() || !geometry_program.empty()){ + if (!vertex_program.empty() || !pixel_program.empty() || + !geometry_program.empty()) { is_highlevel = true; return; } } - } -void dumpShaderProgram(std::ostream &output_stream, - const std::string &program_type, const std::string &program) +void dumpShaderProgram(std::ostream &output_stream, const std::string &program_type, + const std::string &program) { - output_stream << program_type << " shader program:" << std::endl << - "----------------------------------" << std::endl; + output_stream << program_type << " shader program:" << std::endl + << "----------------------------------" << std::endl; size_t pos = 0; size_t prev = 0; s16 line = 1; while ((pos = program.find('\n', prev)) != std::string::npos) { - output_stream << line++ << ": "<< program.substr(prev, pos - prev) << - std::endl; + output_stream << line++ << ": " << program.substr(prev, pos - prev) + << std::endl; prev = pos + 1; } - output_stream << line << ": " << program.substr(prev) << std::endl << - "End of " << program_type << " shader program." << std::endl << - " " << std::endl; + output_stream << line << ": " << program.substr(prev) << std::endl + << "End of " << program_type << " shader program." << std::endl + << " " << std::endl; } diff --git a/src/client/shader.h b/src/client/shader.h index 109d39336..4d1578df0 100644 --- a/src/client/shader.h +++ b/src/client/shader.h @@ -39,10 +39,10 @@ class IGameDef; Utilizes a thread-safe cache. */ -std::string getShaderPath(const std::string &name_of_shader, - const std::string &filename); +std::string getShaderPath(const std::string &name_of_shader, const std::string &filename); -struct ShaderInfo { +struct ShaderInfo +{ std::string name = ""; video::E_MATERIAL_TYPE base_material = video::EMT_SOLID; video::E_MATERIAL_TYPE material = video::EMT_SOLID; @@ -57,38 +57,43 @@ struct ShaderInfo { Setter of constants for shaders */ -namespace irr { namespace video { - class IMaterialRendererServices; -} } +namespace irr +{ +namespace video +{ +class IMaterialRendererServices; +} +} - -class IShaderConstantSetter { +class IShaderConstantSetter +{ public: virtual ~IShaderConstantSetter() = default; virtual void onSetConstants(video::IMaterialRendererServices *services, bool is_highlevel) = 0; - virtual void onSetMaterial(const video::SMaterial& material) - { } + virtual void onSetMaterial(const video::SMaterial &material) {} }; - -class IShaderConstantSetterFactory { +class IShaderConstantSetterFactory +{ public: virtual ~IShaderConstantSetterFactory() = default; - virtual IShaderConstantSetter* create() = 0; + virtual IShaderConstantSetter *create() = 0; }; - -template -class CachedShaderSetting { +template class CachedShaderSetting +{ const char *m_name; T m_sent[count]; bool has_been_set = false; bool is_pixel; + protected: CachedShaderSetting(const char *name, bool is_pixel) : - m_name(name), is_pixel(is_pixel) - {} + m_name(name), is_pixel(is_pixel) + { + } + public: void set(const T value[count], video::IMaterialRendererServices *services) { @@ -104,55 +109,75 @@ public: }; template -class CachedPixelShaderSetting : public CachedShaderSetting { +class CachedPixelShaderSetting : public CachedShaderSetting +{ public: CachedPixelShaderSetting(const char *name) : - CachedShaderSetting(name, true){} + CachedShaderSetting(name, true) + { + } }; template -class CachedVertexShaderSetting : public CachedShaderSetting { +class CachedVertexShaderSetting : public CachedShaderSetting +{ public: CachedVertexShaderSetting(const char *name) : - CachedShaderSetting(name, false){} + CachedShaderSetting(name, false) + { + } }; - /* ShaderSource creates and caches shaders. */ -class IShaderSource { +class IShaderSource +{ public: IShaderSource() = default; virtual ~IShaderSource() = default; - virtual u32 getShaderIdDirect(const std::string &name, - const u8 material_type, const u8 drawtype){return 0;} - virtual ShaderInfo getShaderInfo(u32 id){return ShaderInfo();} - virtual u32 getShader(const std::string &name, - const u8 material_type, const u8 drawtype){return 0;} + virtual u32 getShaderIdDirect(const std::string &name, const u8 material_type, + const u8 drawtype) + { + return 0; + } + virtual ShaderInfo getShaderInfo(u32 id) { return ShaderInfo(); } + virtual u32 getShader(const std::string &name, const u8 material_type, + const u8 drawtype) + { + return 0; + } }; -class IWritableShaderSource : public IShaderSource { +class IWritableShaderSource : public IShaderSource +{ public: IWritableShaderSource() = default; virtual ~IWritableShaderSource() = default; - virtual u32 getShaderIdDirect(const std::string &name, - const u8 material_type, const u8 drawtype){return 0;} - virtual ShaderInfo getShaderInfo(u32 id){return ShaderInfo();} - virtual u32 getShader(const std::string &name, - const u8 material_type, const u8 drawtype){return 0;} + virtual u32 getShaderIdDirect(const std::string &name, const u8 material_type, + const u8 drawtype) + { + return 0; + } + virtual ShaderInfo getShaderInfo(u32 id) { return ShaderInfo(); } + virtual u32 getShader(const std::string &name, const u8 material_type, + const u8 drawtype) + { + return 0; + } - virtual void processQueue()=0; + virtual void processQueue() = 0; virtual void insertSourceShader(const std::string &name_of_shader, - const std::string &filename, const std::string &program)=0; - virtual void rebuildShaders()=0; - virtual void addShaderConstantSetterFactory(IShaderConstantSetterFactory *setter) = 0; + const std::string &filename, const std::string &program) = 0; + virtual void rebuildShaders() = 0; + virtual void addShaderConstantSetterFactory( + IShaderConstantSetterFactory *setter) = 0; }; IWritableShaderSource *createShaderSource(); -void dumpShaderProgram(std::ostream &output_stream, - const std::string &program_type, const std::string &program); +void dumpShaderProgram(std::ostream &output_stream, const std::string &program_type, + const std::string &program); diff --git a/src/client/sky.cpp b/src/client/sky.cpp index 2e0cbca86..fa36f623e 100644 --- a/src/client/sky.cpp +++ b/src/client/sky.cpp @@ -35,8 +35,9 @@ with this program; if not, write to the Free Software Foundation, Inc., using namespace irr::core; Sky::Sky(s32 id, ITextureSource *tsrc) : - scene::ISceneNode(RenderingEngine::get_scene_manager()->getRootSceneNode(), - RenderingEngine::get_scene_manager(), id) + scene::ISceneNode( + RenderingEngine::get_scene_manager()->getRootSceneNode(), + RenderingEngine::get_scene_manager(), id) { setAutomaticCulling(scene::EAC_OFF); m_box.MaxEdge.set(0, 0, 0); @@ -60,33 +61,40 @@ Sky::Sky(s32 id, ITextureSource *tsrc) : m_materials[0] = mat; m_materials[1] = mat; - //m_materials[1].MaterialType = video::EMT_TRANSPARENT_VERTEX_ALPHA; + // m_materials[1].MaterialType = video::EMT_TRANSPARENT_VERTEX_ALPHA; m_materials[1].MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; m_materials[2] = mat; m_materials[2].setTexture(0, tsrc->getTextureForMesh("sunrisebg.png")); m_materials[2].MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; - //m_materials[2].MaterialType = video::EMT_TRANSPARENT_ADD_COLOR; + // m_materials[2].MaterialType = video::EMT_TRANSPARENT_ADD_COLOR; // Ensures that sun and moon textures and tonemaps are correct. setSkyDefaults(); - m_sun_texture = tsrc->isKnownSourceImage(m_sun_params.texture) ? - tsrc->getTextureForMesh(m_sun_params.texture) : NULL; - m_moon_texture = tsrc->isKnownSourceImage(m_moon_params.texture) ? - tsrc->getTextureForMesh(m_moon_params.texture) : NULL; - m_sun_tonemap = tsrc->isKnownSourceImage(m_sun_params.tonemap) ? - tsrc->getTexture(m_sun_params.tonemap) : NULL; - m_moon_tonemap = tsrc->isKnownSourceImage(m_moon_params.tonemap) ? - tsrc->getTexture(m_moon_params.tonemap) : NULL; + m_sun_texture = tsrc->isKnownSourceImage(m_sun_params.texture) + ? tsrc->getTextureForMesh(m_sun_params.texture) + : NULL; + m_moon_texture = tsrc->isKnownSourceImage(m_moon_params.texture) + ? tsrc->getTextureForMesh(m_moon_params.texture) + : NULL; + m_sun_tonemap = tsrc->isKnownSourceImage(m_sun_params.tonemap) + ? tsrc->getTexture(m_sun_params.tonemap) + : NULL; + m_moon_tonemap = tsrc->isKnownSourceImage(m_moon_params.tonemap) + ? tsrc->getTexture(m_moon_params.tonemap) + : NULL; if (m_sun_texture) { m_materials[3] = mat; m_materials[3].setTexture(0, m_sun_texture); m_materials[3].MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; // Disables texture filtering - m_materials[3].setFlag(video::E_MATERIAL_FLAG::EMF_BILINEAR_FILTER, false); - m_materials[3].setFlag(video::E_MATERIAL_FLAG::EMF_TRILINEAR_FILTER, false); - m_materials[3].setFlag(video::E_MATERIAL_FLAG::EMF_ANISOTROPIC_FILTER, false); + m_materials[3].setFlag( + video::E_MATERIAL_FLAG::EMF_BILINEAR_FILTER, false); + m_materials[3].setFlag( + video::E_MATERIAL_FLAG::EMF_TRILINEAR_FILTER, false); + m_materials[3].setFlag( + video::E_MATERIAL_FLAG::EMF_ANISOTROPIC_FILTER, false); // Use tonemaps if available if (m_sun_tonemap) m_materials[3].Lighting = true; @@ -96,9 +104,12 @@ Sky::Sky(s32 id, ITextureSource *tsrc) : m_materials[4].setTexture(0, m_moon_texture); m_materials[4].MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; // Disables texture filtering - m_materials[4].setFlag(video::E_MATERIAL_FLAG::EMF_BILINEAR_FILTER, false); - m_materials[4].setFlag(video::E_MATERIAL_FLAG::EMF_TRILINEAR_FILTER, false); - m_materials[4].setFlag(video::E_MATERIAL_FLAG::EMF_ANISOTROPIC_FILTER, false); + m_materials[4].setFlag( + video::E_MATERIAL_FLAG::EMF_BILINEAR_FILTER, false); + m_materials[4].setFlag( + video::E_MATERIAL_FLAG::EMF_TRILINEAR_FILTER, false); + m_materials[4].setFlag( + video::E_MATERIAL_FLAG::EMF_ANISOTROPIC_FILTER, false); // Use tonemaps if available if (m_moon_tonemap) m_materials[4].Lighting = true; @@ -146,9 +157,9 @@ void Sky::render() if (m_sunlight_seen) { float sunsize = 0.07; video::SColorf suncolor_f(1, 1, 0, 1); - //suncolor_f.r = 1; - //suncolor_f.g = MYMAX(0.3, MYMIN(1.0, 0.7 + m_time_brightness * 0.5)); - //suncolor_f.b = MYMAX(0.0, m_brightness * 0.95); + // suncolor_f.r = 1; + // suncolor_f.g = MYMAX(0.3, MYMIN(1.0, 0.7 + m_time_brightness * 0.5)); + // suncolor_f.b = MYMAX(0.0, m_brightness * 0.95); video::SColorf suncolor2_f(1, 1, 1, 1); // The values below were probably meant to be suncolor2_f instead of a // reassignment of suncolor_f. However, the resulting colour was chosen @@ -166,7 +177,8 @@ void Sky::render() float wn = nightlength / 2; float wicked_time_of_day = 0; if (m_time_of_day > wn && m_time_of_day < 1.0 - wn) - wicked_time_of_day = (m_time_of_day - wn) / (1.0 - wn * 2) * 0.5 + 0.25; + wicked_time_of_day = (m_time_of_day - wn) / (1.0 - wn * 2) * 0.5 + + 0.25; else if (m_time_of_day < 0.5) wicked_time_of_day = m_time_of_day / wn * 0.25; else @@ -180,22 +192,25 @@ void Sky::render() video::SColor mooncolor2 = mooncolor2_f.toSColor(); // Calculate offset normalized to the X dimension of a 512x1 px tonemap - float offset = (1.0 - fabs(sin((m_time_of_day - 0.5) * irr::core::PI))) * 511; + float offset = (1.0 - fabs(sin((m_time_of_day - 0.5) * irr::core::PI))) * + 511; if (m_sun_tonemap) { - u8 * texels = (u8 *)m_sun_tonemap->lock(); - video::SColor* texel = (video::SColor *)(texels + (u32)offset * 4); - video::SColor texel_color (255, texel->getRed(), - texel->getGreen(), texel->getBlue()); + u8 *texels = (u8 *)m_sun_tonemap->lock(); + video::SColor *texel = + (video::SColor *)(texels + (u32)offset * 4); + video::SColor texel_color(255, texel->getRed(), texel->getGreen(), + texel->getBlue()); m_sun_tonemap->unlock(); m_materials[3].EmissiveColor = texel_color; } if (m_moon_tonemap) { - u8 * texels = (u8 *)m_moon_tonemap->lock(); - video::SColor* texel = (video::SColor *)(texels + (u32)offset * 4); - video::SColor texel_color (255, texel->getRed(), - texel->getGreen(), texel->getBlue()); + u8 *texels = (u8 *)m_moon_tonemap->lock(); + video::SColor *texel = + (video::SColor *)(texels + (u32)offset * 4); + video::SColor texel_color(255, texel->getRed(), texel->getGreen(), + texel->getBlue()); m_moon_tonemap->unlock(); m_materials[4].EmissiveColor = texel_color; } @@ -221,10 +236,14 @@ void Sky::render() driver->setMaterial(m_materials[j]); // Use 1.05 rather than 1.0 to avoid colliding with the // sun, moon and stars, as this is a background skybox. - vertices[0] = video::S3DVertex(-1.05, -1.05, -1.05, 0, 0, 1, c, t, t); - vertices[1] = video::S3DVertex( 1.05, -1.05, -1.05, 0, 0, 1, c, o, t); - vertices[2] = video::S3DVertex( 1.05, 1.05, -1.05, 0, 0, 1, c, o, o); - vertices[3] = video::S3DVertex(-1.05, 1.05, -1.05, 0, 0, 1, c, t, o); + vertices[0] = video::S3DVertex( + -1.05, -1.05, -1.05, 0, 0, 1, c, t, t); + vertices[1] = video::S3DVertex( + 1.05, -1.05, -1.05, 0, 0, 1, c, o, t); + vertices[2] = video::S3DVertex( + 1.05, 1.05, -1.05, 0, 0, 1, c, o, o); + vertices[3] = video::S3DVertex( + -1.05, 1.05, -1.05, 0, 0, 1, c, t, o); for (video::S3DVertex &vertex : vertices) { if (j == 5) { // Top texture vertex.Pos.rotateYZBy(90); @@ -237,14 +256,16 @@ void Sky::render() } else if (j == 8) { // Right texture vertex.Pos.rotateXZBy(-90); } else if (j == 9) { // Front texture, do nothing - // Irrlicht doesn't like it when vertexes are left - // alone and not rotated for some reason. + // Irrlicht doesn't like it when vertexes + // are left alone and not rotated for some + // reason. vertex.Pos.rotateXZBy(0); - } else {// Back texture + } else { // Back texture vertex.Pos.rotateXZBy(180); } } - driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2); + driver->drawIndexedTriangleFan( + &vertices[0], 4, indices, 2); } } @@ -252,15 +273,19 @@ void Sky::render() if (m_visible) { driver->setMaterial(m_materials[1]); for (u32 j = 0; j < 4; j++) { - 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); + 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 - {} - else if (j == 1) + // Don't switch + { + } else if (j == 1) // Switch from -Z (south) to +X (east) vertex.Pos.rotateXZBy(90); else if (j == 2) @@ -270,7 +295,8 @@ void Sky::render() // Switch from -Z (south) to +Z (north) vertex.Pos.rotateXZBy(-180); } - driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2); + driver->drawIndexedTriangleFan( + &vertices[0], 4, indices, 2); } } @@ -286,13 +312,15 @@ void Sky::render() float mid = wicked_time_of_day < 0.5 ? mid1 : (1.0 - mid1); float a_ = 1.0f - std::fabs(wicked_time_of_day - mid) * 35.0f; float a = easeCurve(MYMAX(0, MYMIN(1, a_))); - //std::cerr<<"a_="<getTextureForMesh(sunglow_texture.empty() + ? "sunrisebg.png" + : sunglow_texture)); } -void Sky::setMoonTexture(std::string moon_texture, - std::string moon_tonemap, ITextureSource *tsrc) +void Sky::setMoonTexture( + std::string moon_texture, std::string moon_tonemap, ITextureSource *tsrc) { // Ignore matching textures (with modifiers) entirely, // but lets at least update the tonemap before hand. m_moon_params.tonemap = moon_tonemap; - m_moon_tonemap = tsrc->isKnownSourceImage(m_moon_params.tonemap) ? - tsrc->getTexture(m_moon_params.tonemap) : NULL; + m_moon_tonemap = tsrc->isKnownSourceImage(m_moon_params.tonemap) + ? tsrc->getTexture(m_moon_params.tonemap) + : NULL; m_materials[4].Lighting = !!m_moon_tonemap; if (m_moon_params.texture == moon_texture) @@ -872,15 +945,18 @@ void Sky::setMoonTexture(std::string moon_texture, if (m_moon_texture) { m_materials[4] = m_materials[0]; m_materials[4].setTexture(0, m_moon_texture); - m_materials[4].MaterialType = video:: - EMT_TRANSPARENT_ALPHA_CHANNEL; + m_materials[4].MaterialType = + video::EMT_TRANSPARENT_ALPHA_CHANNEL; // Disables texture filtering m_materials[4].setFlag( - video::E_MATERIAL_FLAG::EMF_BILINEAR_FILTER, false); + video::E_MATERIAL_FLAG::EMF_BILINEAR_FILTER, + false); m_materials[4].setFlag( - video::E_MATERIAL_FLAG::EMF_TRILINEAR_FILTER, false); + video::E_MATERIAL_FLAG::EMF_TRILINEAR_FILTER, + false); m_materials[4].setFlag( - video::E_MATERIAL_FLAG::EMF_ANISOTROPIC_FILTER, false); + video::E_MATERIAL_FLAG::EMF_ANISOTROPIC_FILTER, + false); } } else { m_moon_texture = nullptr; @@ -895,11 +971,9 @@ void Sky::setStarCount(u16 star_count, bool force_update) m_stars.clear(); // Rebuild the stars surrounding the camera for (u16 i = 0; i < star_count; i++) { - v3f star = v3f( - myrand_range(-10000, 10000), - myrand_range(-10000, 10000), - myrand_range(-10000, 10000) - ); + v3f star = v3f(myrand_range(-10000, 10000), + myrand_range(-10000, 10000), + myrand_range(-10000, 10000)); star.normalize(); m_stars.emplace_back(star); @@ -912,8 +986,8 @@ void Sky::setSkyColors(const SkyColor &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) +void Sky::setHorizonTint( + video::SColor sun_tint, video::SColor moon_tint, std::string use_sun_tint) { // Change sun and moon tinting: m_sky_params.fog_sun_tint = sun_tint; @@ -927,8 +1001,7 @@ void Sky::setHorizonTint(video::SColor sun_tint, video::SColor moon_tint, m_default_tint = true; } -void Sky::addTextureToSkybox(std::string texture, int material_id, - ITextureSource *tsrc) +void Sky::addTextureToSkybox(std::string texture, int material_id, ITextureSource *tsrc) { // Sanity check for more than six textures. if (material_id + 5 >= SKY_MATERIAL_COUNT) @@ -936,9 +1009,9 @@ void Sky::addTextureToSkybox(std::string texture, int material_id, // Keep a list of texture names handy. m_sky_params.textures.emplace_back(texture); video::ITexture *result = tsrc->getTextureForMesh(texture); - m_materials[material_id+5] = m_materials[0]; - m_materials[material_id+5].setTexture(0, result); - m_materials[material_id+5].MaterialType = video::EMT_SOLID; + m_materials[material_id + 5] = m_materials[0]; + m_materials[material_id + 5].setTexture(0, result); + m_materials[material_id + 5].MaterialType = video::EMT_SOLID; } // To be called once at game init to setup default values. diff --git a/src/client/sky.h b/src/client/sky.h index 3227e8f59..8983c3775 100644 --- a/src/client/sky.h +++ b/src/client/sky.h @@ -63,20 +63,29 @@ public: } void setSunVisible(bool sun_visible) { m_sun_params.visible = sun_visible; } - void setSunTexture(std::string sun_texture, - std::string sun_tonemap, ITextureSource *tsrc); + void setSunTexture(std::string sun_texture, std::string sun_tonemap, + ITextureSource *tsrc); void setSunScale(f32 sun_scale) { m_sun_params.scale = sun_scale; } - void setSunriseVisible(bool glow_visible) { m_sun_params.sunrise_visible = glow_visible; } - void setSunriseTexture(std::string sunglow_texture, ITextureSource* tsrc); + void setSunriseVisible(bool glow_visible) + { + m_sun_params.sunrise_visible = glow_visible; + } + void setSunriseTexture(std::string sunglow_texture, ITextureSource *tsrc); void setMoonVisible(bool moon_visible) { m_moon_params.visible = moon_visible; } - void setMoonTexture(std::string moon_texture, - std::string moon_tonemap, ITextureSource *tsrc); + void setMoonTexture(std::string moon_texture, std::string moon_tonemap, + ITextureSource *tsrc); void setMoonScale(f32 moon_scale) { m_moon_params.scale = moon_scale; } - void setStarsVisible(bool stars_visible) { m_star_params.visible = stars_visible; } + void setStarsVisible(bool stars_visible) + { + m_star_params.visible = stars_visible; + } void setStarCount(u16 star_count, bool force_update); - void setStarColor(video::SColor star_color) { m_star_params.starcolor = star_color; } + void setStarColor(video::SColor star_color) + { + m_star_params.starcolor = star_color; + } void setStarScale(f32 star_scale) { m_star_params.scale = star_scale; } bool getCloudsVisible() const { return m_clouds_visible && m_clouds_enabled; } @@ -96,11 +105,12 @@ public: } void setSkyColors(const SkyColor &sky_color); void setHorizonTint(video::SColor sun_tint, video::SColor moon_tint, - std::string use_sun_tint); + std::string use_sun_tint); void setInClouds(bool clouds) { m_in_clouds = clouds; } void clearSkyboxTextures() { m_sky_params.textures.clear(); } - void addTextureToSkybox(std::string texture, int material_id, - ITextureSource *tsrc); + void addTextureToSkybox( + std::string texture, int material_id, ITextureSource *tsrc); + private: aabb3f m_box; video::SMaterial m_materials[SKY_MATERIAL_COUNT]; @@ -165,11 +175,8 @@ private: // pure white: becomes "diffuse light component" for clouds video::SColorf m_cloudcolor_day_f = video::SColorf(1, 1, 1, 1); // dawn-factoring version of pure white (note: R is above 1.0) - video::SColorf m_cloudcolor_dawn_f = video::SColorf( - 255.0f/240.0f, - 223.0f/240.0f, - 191.0f/255.0f - ); + video::SColorf m_cloudcolor_dawn_f = + video::SColorf(255.0f / 240.0f, 223.0f / 240.0f, 191.0f / 255.0f); SkyboxParams m_sky_params; SunParams m_sun_params; @@ -185,14 +192,16 @@ private: video::ITexture *m_sun_tonemap; video::ITexture *m_moon_tonemap; - void draw_sun(video::IVideoDriver *driver, float sunsize, const video::SColor &suncolor, - const video::SColor &suncolor2, float wicked_time_of_day); - void draw_moon(video::IVideoDriver *driver, float moonsize, const video::SColor &mooncolor, - const video::SColor &mooncolor2, float wicked_time_of_day); - void draw_sky_body(std::array &vertices, - float pos_1, float pos_2, const video::SColor &c); + void draw_sun(video::IVideoDriver *driver, float sunsize, + const video::SColor &suncolor, const video::SColor &suncolor2, + float wicked_time_of_day); + void draw_moon(video::IVideoDriver *driver, float moonsize, + const video::SColor &mooncolor, const video::SColor &mooncolor2, + float wicked_time_of_day); + void draw_sky_body(std::array &vertices, float pos_1, + float pos_2, const video::SColor &c); void draw_stars(video::IVideoDriver *driver, float wicked_time_of_day); void place_sky_body(std::array &vertices, - float horizon_position, float day_position); + float horizon_position, float day_position); void setSkyDefaults(); }; diff --git a/src/client/sound_openal.cpp b/src/client/sound_openal.cpp index 20a651c1d..7cc16d8d7 100644 --- a/src/client/sound_openal.cpp +++ b/src/client/sound_openal.cpp @@ -24,17 +24,17 @@ with this program; ifnot, write to the Free Software Foundation, Inc., #include "sound_openal.h" #if defined(_WIN32) - #include - #include - //#include +#include +#include +//#include #elif defined(__APPLE__) - #include - #include - //#include +#include +#include +//#include #else - #include - #include - #include +#include +#include +#include #endif #include #include @@ -52,7 +52,7 @@ with this program; ifnot, write to the Free Software Foundation, Inc., std::shared_ptr g_sound_manager_singleton; typedef std::unique_ptr unique_ptr_alcdevice; -typedef std::unique_ptr unique_ptr_alccontext; +typedef std::unique_ptr unique_ptr_alccontext; static void delete_alcdevice(ALCdevice *p) { @@ -90,9 +90,9 @@ static const char *alErrorString(ALenum err) static ALenum warn_if_error(ALenum err, const char *desc) { - if(err == AL_NO_ERROR) + if (err == AL_NO_ERROR) return err; - warningstream< buffer; }; -SoundBuffer *load_opened_ogg_file(OggVorbis_File *oggFile, - const std::string &filename_for_logging) +SoundBuffer *load_opened_ogg_file( + OggVorbis_File *oggFile, const std::string &filename_for_logging) { int endian = 0; // 0 for Little-Endian, 1 for Big-Endian int bitStream; @@ -126,7 +126,7 @@ SoundBuffer *load_opened_ogg_file(OggVorbis_File *oggFile, pInfo = ov_info(oggFile, -1); // Check the number of channels... always use 16-bit samples - if(pInfo->channels == 1) + if (pInfo->channels == 1) snd->format = AL_FORMAT_MONO16; else snd->format = AL_FORMAT_STEREO16; @@ -135,16 +135,14 @@ SoundBuffer *load_opened_ogg_file(OggVorbis_File *oggFile, snd->freq = pInfo->rate; // Keep reading until all is read - do - { + do { // Read up to a buffer's worth of decoded sound data bytes = ov_read(oggFile, array, BUFFER_SIZE, endian, 2, 1, &bitStream); - if(bytes < 0) - { + if (bytes < 0) { ov_clear(oggFile); - infostream << "Audio: Error decoding " - << filename_for_logging << std::endl; + infostream << "Audio: Error decoding " << filename_for_logging + << std::endl; delete snd; return nullptr; } @@ -154,18 +152,17 @@ SoundBuffer *load_opened_ogg_file(OggVorbis_File *oggFile, } while (bytes > 0); alGenBuffers(1, &snd->buffer_id); - alBufferData(snd->buffer_id, snd->format, - &(snd->buffer[0]), snd->buffer.size(), + alBufferData(snd->buffer_id, snd->format, &(snd->buffer[0]), snd->buffer.size(), snd->freq); ALenum error = alGetError(); - if(error != AL_NO_ERROR){ + if (error != AL_NO_ERROR) { infostream << "Audio: OpenAL error: " << alErrorString(error) - << "preparing sound buffer" << std::endl; + << "preparing sound buffer" << std::endl; } - //infostream << "Audio file " + // infostream << "Audio file " // << filename_for_logging << " loaded" << std::endl; // Clean up! @@ -182,15 +179,16 @@ SoundBuffer *load_ogg_from_file(const std::string &path) // This requires libvorbis >= 1.3.2, as // previous versions expect a non-const char * if (ov_fopen(path.c_str(), &oggFile) != 0) { - infostream << "Audio: Error opening " << path - << " for decoding" << std::endl; + infostream << "Audio: Error opening " << path << " for decoding" + << std::endl; return nullptr; } return load_opened_ogg_file(&oggFile, path); } -struct BufferSource { +struct BufferSource +{ const char *buf; size_t cur_offset; size_t len; @@ -216,8 +214,8 @@ int buffer_sound_seek_func(void *datasource, ogg_int64_t offset, int whence) s->cur_offset = offset; return 0; } else if (whence == SEEK_CUR) { - if ((size_t)MYMIN(-offset, 0) > s->cur_offset - || s->cur_offset + offset > s->len) { + if ((size_t)MYMIN(-offset, 0) > s->cur_offset || + s->cur_offset + offset > s->len) { // offset out of bounds return -1; } @@ -234,12 +232,8 @@ long BufferSourceell_func(void *datasource) return s->cur_offset; } -static ov_callbacks g_buffer_ov_callbacks = { - &buffer_sound_read_func, - &buffer_sound_seek_func, - nullptr, - &BufferSourceell_func -}; +static ov_callbacks g_buffer_ov_callbacks = {&buffer_sound_read_func, + &buffer_sound_seek_func, nullptr, &BufferSourceell_func}; SoundBuffer *load_ogg_from_buffer(const std::string &buf, const std::string &id_for_log) { @@ -251,8 +245,8 @@ SoundBuffer *load_ogg_from_buffer(const std::string &buf, const std::string &id_ s.len = buf.size(); if (ov_open_callbacks(&s, &oggFile, nullptr, 0, g_buffer_ov_callbacks) != 0) { - infostream << "Audio: Error opening " << id_for_log - << " for decoding" << std::endl; + infostream << "Audio: Error opening " << id_for_log << " for decoding" + << std::endl; return nullptr; } @@ -268,43 +262,54 @@ struct PlayingSound class SoundManagerSingleton { public: - unique_ptr_alcdevice m_device; + unique_ptr_alcdevice m_device; unique_ptr_alccontext m_context; + public: SoundManagerSingleton() : - m_device(nullptr, delete_alcdevice), - m_context(nullptr, delete_alccontext) + m_device(nullptr, delete_alcdevice), + m_context(nullptr, delete_alccontext) { } bool init() { - if (!(m_device = unique_ptr_alcdevice(alcOpenDevice(nullptr), delete_alcdevice))) { - errorstream << "Audio: Global Initialization: Failed to open device" << std::endl; + 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))) { - errorstream << "Audio: Global Initialization: Failed to create context" << std::endl; + alcCreateContext(m_device.get(), nullptr), + delete_alccontext))) { + errorstream << "Audio: Global Initialization: Failed to create " + "context" + << std::endl; return false; } if (!alcMakeContextCurrent(m_context.get())) { - errorstream << "Audio: Global Initialization: Failed to make current context" << std::endl; + errorstream << "Audio: Global Initialization: Failed to make " + "current context" + << std::endl; return false; } alDistanceModel(AL_INVERSE_DISTANCE_CLAMPED); if (alGetError() != AL_NO_ERROR) { - errorstream << "Audio: Global Initialization: OpenAL Error " << alGetError() << std::endl; + 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; + infostream << "Audio: Global Initialized: OpenAL " + << alGetString(AL_VERSION) << ", using " + << alcGetString(m_device.get(), ALC_DEVICE_SPECIFIER) + << std::endl; return true; } @@ -315,22 +320,24 @@ public: } }; -class OpenALSoundManager: public ISoundManager +class OpenALSoundManager : public ISoundManager { private: OnDemandSoundFetcher *m_fetcher; ALCdevice *m_device; ALCcontext *m_context; int m_next_id; - std::unordered_map> m_buffers; - std::unordered_map m_sounds_playing; - struct FadeState { + std::unordered_map> m_buffers; + std::unordered_map m_sounds_playing; + struct FadeState + { FadeState() = default; - FadeState(float step, float current_gain, float target_gain): - step(step), - current_gain(current_gain), - target_gain(target_gain) {} + FadeState(float step, float current_gain, float target_gain) : + step(step), current_gain(current_gain), + target_gain(target_gain) + { + } float step; float current_gain; float target_gain; @@ -338,13 +345,11 @@ private: std::unordered_map m_sounds_fading; float m_fade_delay; + public: - OpenALSoundManager(SoundManagerSingleton *smg, OnDemandSoundFetcher *fetcher): - m_fetcher(fetcher), - m_device(smg->m_device.get()), - m_context(smg->m_context.get()), - m_next_id(1), - m_fade_delay(0) + OpenALSoundManager(SoundManagerSingleton *smg, OnDemandSoundFetcher *fetcher) : + m_fetcher(fetcher), m_device(smg->m_device.get()), + m_context(smg->m_context.get()), m_next_id(1), m_fade_delay(0) { infostream << "Audio: Initialized: OpenAL " << std::endl; } @@ -372,37 +377,34 @@ public: infostream << "Audio: Deinitialized." << std::endl; } - void step(float dtime) - { - doFades(dtime); - } + void step(float dtime) { doFades(dtime); } void addBuffer(const std::string &name, SoundBuffer *buf) { - std::unordered_map>::iterator i = + std::unordered_map>::iterator i = m_buffers.find(name); - if(i != m_buffers.end()){ + if (i != m_buffers.end()) { i->second.push_back(buf); return; } - std::vector bufs; + std::vector bufs; bufs.push_back(buf); m_buffers[name] = bufs; } - SoundBuffer* getBuffer(const std::string &name) + SoundBuffer *getBuffer(const std::string &name) { - std::unordered_map>::iterator i = + std::unordered_map>::iterator i = m_buffers.find(name); - if(i == m_buffers.end()) + if (i == m_buffers.end()) return nullptr; - std::vector &bufs = i->second; + std::vector &bufs = i->second; int j = myrand() % bufs.size(); return bufs[j]; } - PlayingSound* createPlayingSound(SoundBuffer *buf, bool loop, - float volume, float pitch) + PlayingSound *createPlayingSound( + SoundBuffer *buf, bool loop, float volume, float pitch) { infostream << "OpenALSoundManager: Creating playing sound" << std::endl; assert(buf); @@ -423,11 +425,11 @@ public: return sound; } - PlayingSound* createPlayingSoundAt(SoundBuffer *buf, bool loop, - float volume, v3f pos, float pitch) + PlayingSound *createPlayingSoundAt( + SoundBuffer *buf, bool loop, float volume, v3f pos, float pitch) { infostream << "OpenALSoundManager: Creating positional playing sound" - << std::endl; + << std::endl; assert(buf); PlayingSound *sound = new PlayingSound; assert(sound); @@ -456,7 +458,7 @@ public: { assert(buf); PlayingSound *sound = createPlayingSound(buf, loop, volume, pitch); - if(!sound) + if (!sound) return -1; int id = m_next_id++; m_sounds_playing[id] = sound; @@ -468,7 +470,7 @@ public: { assert(buf); PlayingSound *sound = createPlayingSoundAt(buf, loop, volume, pos, pitch); - if(!sound) + if (!sound) return -1; int id = m_next_id++; m_sounds_playing[id] = sound; @@ -477,8 +479,9 @@ public: void deleteSound(int id) { - std::unordered_map::iterator i = m_sounds_playing.find(id); - if(i == m_sounds_playing.end()) + std::unordered_map::iterator i = + m_sounds_playing.find(id); + if (i == m_sounds_playing.end()) return; PlayingSound *sound = i->second; @@ -489,12 +492,12 @@ public: } /* If buffer does not exist, consult the fetcher */ - SoundBuffer* getFetchBuffer(const std::string &name) + SoundBuffer *getFetchBuffer(const std::string &name) { SoundBuffer *buf = getBuffer(name); - if(buf) + if (buf) return buf; - if(!m_fetcher) + if (!m_fetcher) return nullptr; std::set paths; std::set datas; @@ -513,8 +516,9 @@ public: { if (!m_sounds_playing.empty()) { verbosestream << "OpenALSoundManager::maintain(): " - << m_sounds_playing.size() <<" playing sounds, " - << m_buffers.size() <<" sound names loaded"< del_list; for (const auto &sp : m_sounds_playing) { @@ -524,14 +528,15 @@ public: { ALint state; alGetSourcei(sound->source_id, AL_SOURCE_STATE, &state); - if(state != AL_PLAYING){ + if (state != AL_PLAYING) { del_list.insert(id); } } } - if(!del_list.empty()) - verbosestream<<"OpenALSoundManager::maintain(): deleting " - <second.step < 0.f) chkGain = -(i->second.current_gain); else @@ -636,7 +637,8 @@ public: if (chkGain < i->second.target_gain) { i->second.current_gain += (i->second.step * m_fade_delay); - i->second.current_gain = rangelim(i->second.current_gain, 0, 1); + i->second.current_gain = + rangelim(i->second.current_gain, 0, 1); updateSoundGain(i->first, i->second.current_gain); ++i; @@ -702,7 +704,8 @@ std::shared_ptr createSoundManagerSingleton() return smg; } -ISoundManager *createOpenALSoundManager(SoundManagerSingleton *smg, OnDemandSoundFetcher *fetcher) +ISoundManager *createOpenALSoundManager( + SoundManagerSingleton *smg, OnDemandSoundFetcher *fetcher) { return new OpenALSoundManager(smg, fetcher); }; diff --git a/src/client/tile.cpp b/src/client/tile.cpp index d03588b2b..dd159e909 100644 --- a/src/client/tile.cpp +++ b/src/client/tile.cpp @@ -34,7 +34,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "guiscalingfilter.h" #include "renderingengine.h" - #if ENABLE_GLES #ifdef _IRR_COMPILE_WITH_OGLES1_ #include @@ -62,10 +61,8 @@ static bool replace_ext(std::string &path, const char *ext) return false; // Find place of last dot, fail if \ or / found. s32 last_dot_i = -1; - for (s32 i=path.size()-1; i>=0; i--) - { - if (path[i] == '.') - { + for (s32 i = path.size() - 1; i >= 0; i--) { + if (path[i] == '.') { last_dot_i = i; break; } @@ -77,7 +74,7 @@ static bool replace_ext(std::string &path, const char *ext) if (last_dot_i == -1) return false; // Else make the new path - path = path.substr(0, last_dot_i+1) + ext; + path = path.substr(0, last_dot_i + 1) + ext; return true; } @@ -90,24 +87,20 @@ static bool replace_ext(std::string &path, const char *ext) std::string getImagePath(std::string path) { // A NULL-ended list of possible image extensions - const char *extensions[] = { - "png", "jpg", "bmp", "tga", - "pcx", "ppm", "psd", "wal", "rgb", - NULL - }; + const char *extensions[] = {"png", "jpg", "bmp", "tga", "pcx", "ppm", "psd", + "wal", "rgb", NULL}; // If there is no extension, add one if (removeStringEnd(path, extensions).empty()) path = path + ".png"; // Check paths until something is found to exist const char **ext = extensions; - do{ + do { bool r = replace_ext(path, *ext); if (!r) return ""; if (fs::PathExists(path)) return path; - } - while((++ext) != NULL); + } while ((++ext) != NULL); return ""; } @@ -152,10 +145,9 @@ std::string getTexturePath(const std::string &filename, bool *is_base_pack) /* Check from default data directory */ - if (fullpath.empty()) - { - std::string base_path = porting::path_share + DIR_DELIM + "textures" - + DIR_DELIM + "base" + DIR_DELIM + "pack"; + if (fullpath.empty()) { + std::string base_path = porting::path_share + DIR_DELIM + "textures" + + DIR_DELIM + "base" + DIR_DELIM + "pack"; std::string testpath = base_path + DIR_DELIM + filename; // Check all filename extensions. Returns "" if not found. fullpath = getImagePath(testpath); @@ -184,12 +176,8 @@ struct TextureInfo std::string name; video::ITexture *texture; - TextureInfo( - const std::string &name_, - video::ITexture *texture_=NULL - ): - name(name_), - texture(texture_) + TextureInfo(const std::string &name_, video::ITexture *texture_ = NULL) : + name(name_), texture(texture_) { } }; @@ -201,7 +189,8 @@ struct TextureInfo class SourceImageCache { public: - ~SourceImageCache() { + ~SourceImageCache() + { for (auto &m_image : m_images) { m_image.second->drop(); } @@ -211,14 +200,14 @@ public: { assert(img); // Pre-condition // Remove old image - std::map::iterator n; + std::map::iterator n; n = m_images.find(name); - if (n != m_images.end()){ + if (n != m_images.end()) { if (n->second) n->second->drop(); } - video::IImage* toadd = img; + video::IImage *toadd = img; bool need_to_grab = true; // Try to use local texture instead if asked to @@ -227,9 +216,11 @@ public: std::string path = getTexturePath(name, &is_base_pack); // Ignore base pack if (!path.empty() && !is_base_pack) { - video::IImage *img2 = RenderingEngine::get_video_driver()-> - createImageFromFile(path.c_str()); - if (img2){ + video::IImage *img2 = + RenderingEngine::get_video_driver() + ->createImageFromFile( + path.c_str()); + if (img2) { toadd = img2; need_to_grab = false; } @@ -240,9 +231,9 @@ public: toadd->grab(); m_images[name] = toadd; } - video::IImage* get(const std::string &name) + video::IImage *get(const std::string &name) { - std::map::iterator n; + std::map::iterator n; n = m_images.find(name); if (n != m_images.end()) return n->second; @@ -251,31 +242,33 @@ public: // Primarily fetches from cache, secondarily tries to read from filesystem video::IImage *getOrLoad(const std::string &name) { - std::map::iterator n; + std::map::iterator n; n = m_images.find(name); - if (n != m_images.end()){ + if (n != m_images.end()) { n->second->grab(); // Grab for caller return n->second; } video::IVideoDriver *driver = RenderingEngine::get_video_driver(); std::string path = getTexturePath(name); if (path.empty()) { - infostream<<"SourceImageCache::getOrLoad(): No path found for \"" - <createImageFromFile(path.c_str()); - if (img){ + if (img) { m_images[name] = img; img->grab(); // Grab for caller } return img; } + private: - std::map m_images; + std::map m_images; }; /* @@ -340,9 +333,9 @@ public: and not found in cache, the call is queued to the main thread for processing. */ - video::ITexture* getTexture(u32 id); + video::ITexture *getTexture(u32 id); - video::ITexture* getTexture(const std::string &name, u32 *id = NULL); + video::ITexture *getTexture(const std::string &name, u32 *id = NULL); /* Get a texture specifically intended for mesh @@ -350,9 +343,9 @@ public: use. This texture may be a different size and may have had additional filters applied. */ - video::ITexture* getTextureForMesh(const std::string &name, u32 *id); + video::ITexture *getTextureForMesh(const std::string &name, u32 *id); - virtual Palette* getPalette(const std::string &name); + virtual Palette *getPalette(const std::string &name); bool isKnownSourceImage(const std::string &name) { @@ -378,12 +371,11 @@ public: // Shall be called from the main thread. void rebuildImagesAndTextures(); - video::ITexture* getNormalTexture(const std::string &name); + video::ITexture *getNormalTexture(const std::string &name); video::SColor getTextureAverageColor(const std::string &name); video::ITexture *getShaderFlagsTexture(bool normamap_present); private: - // The id of the thread that is allowed to use irrlicht directly std::thread::id m_main_thread; @@ -396,14 +388,14 @@ private: // Generate image based on a string like "stone.png" or "[crack:1:0". // if baseimg is NULL, it is created. Otherwise stuff is made on it. - bool generateImagePart(std::string part_of_name, video::IImage *& baseimg); + bool generateImagePart(std::string part_of_name, video::IImage *&baseimg); /*! Generates an image from a full string like * "stone.png^mineral_coal.png^[crack:1:0". * Shall be called from the main thread. * The returned Image should be dropped. */ - video::IImage* generateImage(const std::string &name); + video::IImage *generateImage(const std::string &name); // Thread-safe cache of what source images are known (true = known) MutexedMap m_source_image_existence; @@ -421,7 +413,7 @@ private: // Textures that have been overwritten with other ones // but can't be deleted because the ITexture* might still be used - std::vector m_texture_trash; + std::vector m_texture_trash; // Maps image file names to loaded palettes. std::unordered_map m_palettes; @@ -460,24 +452,24 @@ TextureSource::~TextureSource() unsigned int textures_before = driver->getTextureCount(); for (const auto &iter : m_textureinfo_cache) { - //cleanup texture + // cleanup texture if (iter.texture) driver->removeTexture(iter.texture); } m_textureinfo_cache.clear(); for (auto t : m_texture_trash) { - //cleanup trashed texture + // cleanup trashed texture driver->removeTexture(t); } - infostream << "~TextureSource() before cleanup: "<< textures_before - << " after: " << driver->getTextureCount() << std::endl; + infostream << "~TextureSource() before cleanup: " << textures_before + << " after: " << driver->getTextureCount() << std::endl; } u32 TextureSource::getTextureId(const std::string &name) { - //infostream<<"getTextureId(): \""<::iterator n; n = m_name_to_id.find(name); - if (n != m_name_to_id.end()) - { + if (n != m_name_to_id.end()) { return n->second; } } @@ -499,8 +490,7 @@ u32 TextureSource::getTextureId(const std::string &name) return generateTexture(name); } - - infostream<<"getTextureId(): Queued: name=\""< result_queue; @@ -509,17 +499,18 @@ u32 TextureSource::getTextureId(const std::string &name) m_get_texture_queue.add(name, 0, 0, &result_queue); try { - while(true) { + while (true) { // Wait result for a second - GetResult - result = result_queue.pop_front(1000); + GetResult result = + result_queue.pop_front(1000); if (result.key == name) { return result.item; } } - } catch(ItemNotFoundException &e) { - errorstream << "Waiting for texture " << name << " timed out." << std::endl; + } catch (ItemNotFoundException &e) { + errorstream << "Waiting for texture " << name << " timed out." + << std::endl; return 0; } @@ -530,13 +521,13 @@ u32 TextureSource::getTextureId(const std::string &name) // Draw an image on top of an another one, using the alpha channel of the // source image -static void blit_with_alpha(video::IImage *src, video::IImage *dst, - v2s32 src_pos, v2s32 dst_pos, v2u32 size); +static void blit_with_alpha(video::IImage *src, video::IImage *dst, v2s32 src_pos, + v2s32 dst_pos, v2u32 size); // Like blit_with_alpha, but only modifies destination pixels that // are fully opaque -static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst, - v2s32 src_pos, v2s32 dst_pos, v2u32 size); +static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst, v2s32 src_pos, + v2s32 dst_pos, v2u32 size); // Apply a color to an image. Uses an int (0-255) to calculate the ratio. // If the ratio is 255 or -1 and keep_alpha is true, then it multiples the @@ -550,18 +541,18 @@ static void apply_multiplication(video::IImage *dst, v2u32 dst_pos, v2u32 size, const video::SColor &color); // Apply a mask to an image -static void apply_mask(video::IImage *mask, video::IImage *dst, - v2s32 mask_pos, v2s32 dst_pos, v2u32 size); +static void apply_mask(video::IImage *mask, video::IImage *dst, v2s32 mask_pos, + v2s32 dst_pos, v2u32 size); // Draw or overlay a crack -static void draw_crack(video::IImage *crack, video::IImage *dst, - bool use_overlay, s32 frame_count, s32 progression, - video::IVideoDriver *driver, u8 tiles = 1); +static void draw_crack(video::IImage *crack, video::IImage *dst, bool use_overlay, + s32 frame_count, s32 progression, video::IVideoDriver *driver, + u8 tiles = 1); // Brighten image void brighten(video::IImage *image); // Parse a transform name -u32 parseImageTransform(const std::string& s); +u32 parseImageTransform(const std::string &s); // Apply transform to image dimension core::dimension2d imageTransformDimension(u32 transform, core::dimension2d dim); // Apply transform to image data @@ -572,11 +563,11 @@ void imageTransform(u32 transform, video::IImage *src, video::IImage *dst); */ u32 TextureSource::generateTexture(const std::string &name) { - //infostream << "generateTexture(): name=\"" << name << "\"" << std::endl; + // infostream << "generateTexture(): name=\"" << name << "\"" << std::endl; // Empty name means texture 0 if (name.empty()) { - infostream<<"generateTexture(): name is empty"<= m_textureinfo_cache.size()) - { - errorstream<<"TextureSource::getTextureName(): id="<= m_textureinfo_cache.size()=" - <= m_textureinfo_cache.size()) { + errorstream << "TextureSource::getTextureName(): id=" << id + << " >= m_textureinfo_cache.size()=" + << m_textureinfo_cache.size() << std::endl; return ""; } return m_textureinfo_cache[id].name; } -video::ITexture* TextureSource::getTexture(u32 id) +video::ITexture *TextureSource::getTexture(u32 id) { MutexAutoLock lock(m_textureinfo_cache_mutex); @@ -657,28 +648,28 @@ video::ITexture* TextureSource::getTexture(u32 id) return m_textureinfo_cache[id].texture; } -video::ITexture* TextureSource::getTexture(const std::string &name, u32 *id) +video::ITexture *TextureSource::getTexture(const std::string &name, u32 *id) { u32 actual_id = getTextureId(name); - if (id){ + if (id) { *id = actual_id; } return getTexture(actual_id); } -video::ITexture* TextureSource::getTextureForMesh(const std::string &name, u32 *id) +video::ITexture *TextureSource::getTextureForMesh(const std::string &name, u32 *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); + 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) +Palette *TextureSource::getPalette(const std::string &name) { // Only the main thread may load images sanity_check(std::this_thread::get_id() == m_main_thread); @@ -692,7 +683,7 @@ Palette* TextureSource::getPalette(const std::string &name) video::IImage *img = generateImage(name); if (!img) { warningstream << "TextureSource::getPalette(): palette \"" << name - << "\" could not be loaded." << std::endl; + << "\" could not be loaded." << std::endl; return NULL; } Palette new_palette; @@ -704,13 +695,15 @@ Palette* TextureSource::getPalette(const std::string &name) return NULL; if (area > 256) { warningstream << "TextureSource::getPalette(): the specified" - << " palette image \"" << name << "\" is larger than 256" - << " pixels, using the first 256." << std::endl; + << " palette image \"" << name + << "\" is larger than 256" + << " pixels, using the first 256." << std::endl; area = 256; } else if (256 % area != 0) warningstream << "TextureSource::getPalette(): the " - << "specified palette image \"" << name << "\" does not " - << "contain power of two pixels." << std::endl; + << "specified palette image \"" << name + << "\" does not " + << "contain power of two pixels." << std::endl; // We stretch the palette so it will fit 256 values // This many param2 values will have the same color u32 step = 256 / area; @@ -738,11 +731,9 @@ void TextureSource::processQueue() /* Fetch textures */ - //NOTE this is only thread safe for ONE consumer thread! - if (!m_get_texture_queue.empty()) - { - GetRequest - request = m_get_texture_queue.pop(); + // NOTE this is only thread safe for ONE consumer thread! + if (!m_get_texture_queue.empty()) { + GetRequest request = m_get_texture_queue.pop(); /*infostream<<"TextureSource::processQueue(): " <<"got texture request with " @@ -755,7 +746,7 @@ void TextureSource::processQueue() void TextureSource::insertSourceImage(const std::string &name, video::IImage *img) { - //infostream<<"TextureSource::insertSourceImage(): name="<getDimension(); core::dimension2du size_left = left->getDimension(); core::dimension2du size_right = right->getDimension(); u32 size = npot2(std::max({ - size_top.Width, size_top.Height, - size_left.Width, size_left.Height, - size_right.Width, size_right.Height, + size_top.Width, + size_top.Height, + size_left.Width, + size_left.Height, + size_right.Width, + size_right.Height, })); // It must be divisible by 4, to let everything work correctly. @@ -828,12 +822,14 @@ static video::IImage *createInventoryCubeImage( video::IVideoDriver *driver = RenderingEngine::get_video_driver(); - auto lock_image = [size, driver] (video::IImage *&image) -> const u32 * { + auto lock_image = [size, driver](video::IImage *&image) -> const u32 * { image->grab(); core::dimension2du dim = image->getDimension(); video::ECOLOR_FORMAT format = image->getColorFormat(); - if (dim.Width != size || dim.Height != size || format != video::ECF_A8R8G8B8) { - video::IImage *scaled = driver->createImage(video::ECF_A8R8G8B8, {size, size}); + if (dim.Width != size || dim.Height != size || + format != video::ECF_A8R8G8B8) { + video::IImage *scaled = driver->createImage( + video::ECF_A8R8G8B8, {size, size}); image->copyToScaling(scaled); image->drop(); image = scaled; @@ -841,12 +837,13 @@ static video::IImage *createInventoryCubeImage( sanity_check(image->getPitch() == 4 * size); return reinterpret_cast(image->lock()); }; - auto free_image = [] (video::IImage *image) -> void { + auto free_image = [](video::IImage *image) -> void { image->unlock(); image->drop(); }; - video::IImage *result = driver->createImage(video::ECF_A8R8G8B8, {cube_size, cube_size}); + video::IImage *result = + driver->createImage(video::ECF_A8R8G8B8, {cube_size, cube_size}); sanity_check(result->getPitch() == 4 * cube_size); result->fill(video::SColor(0x00000000u)); u32 *target = reinterpret_cast(result->lock()); @@ -855,10 +852,9 @@ static video::IImage *createInventoryCubeImage( // `shade_factor` is face brightness, in range [0.0, 1.0] // (xu, xv, x1; yu, yv, y1) form coordinate transformation matrix // `offsets` list pixels to be drawn for single source pixel - auto draw_image = [=] (video::IImage *image, float shade_factor, - s16 xu, s16 xv, s16 x1, - s16 yu, s16 yv, s16 y1, - std::initializer_list offsets) -> void { + auto draw_image = [=](video::IImage *image, float shade_factor, s16 xu, s16 xv, + s16 x1, s16 yu, s16 yv, s16 y1, + std::initializer_list offsets) -> void { u32 brightness = core::clamp(256 * shade_factor, 0, 256); const u32 *source = lock_image(image); for (u16 v = 0; v < size; v++) { @@ -868,51 +864,87 @@ static video::IImage *createInventoryCubeImage( s16 x = xu * u + xv * v + x1; s16 y = yu * u + yv * v + y1; for (const auto &off : offsets) - target[(y + off.Y) * cube_size + (x + off.X) + offset] = pixel.color; + target[(y + off.Y) * cube_size + (x + off.X) + + offset] = pixel.color; source++; } } free_image(image); }; - draw_image(top, 1.000000f, - 4, -4, 4 * (size - 1), - 2, 2, 0, + draw_image(top, 1.000000f, 4, -4, 4 * (size - 1), 2, 2, 0, { - {2, 0}, {3, 0}, {4, 0}, {5, 0}, - {0, 1}, {1, 1}, {2, 1}, {3, 1}, {4, 1}, {5, 1}, {6, 1}, {7, 1}, - {2, 2}, {3, 2}, {4, 2}, {5, 2}, + {2, 0}, + {3, 0}, + {4, 0}, + {5, 0}, + {0, 1}, + {1, 1}, + {2, 1}, + {3, 1}, + {4, 1}, + {5, 1}, + {6, 1}, + {7, 1}, + {2, 2}, + {3, 2}, + {4, 2}, + {5, 2}, }); - draw_image(left, 0.836660f, - 4, 0, 0, - 2, 5, 2 * size, + draw_image(left, 0.836660f, 4, 0, 0, 2, 5, 2 * size, { - {0, 0}, {1, 0}, - {0, 1}, {1, 1}, {2, 1}, {3, 1}, - {0, 2}, {1, 2}, {2, 2}, {3, 2}, - {0, 3}, {1, 3}, {2, 3}, {3, 3}, - {0, 4}, {1, 4}, {2, 4}, {3, 4}, - {2, 5}, {3, 5}, + {0, 0}, + {1, 0}, + {0, 1}, + {1, 1}, + {2, 1}, + {3, 1}, + {0, 2}, + {1, 2}, + {2, 2}, + {3, 2}, + {0, 3}, + {1, 3}, + {2, 3}, + {3, 3}, + {0, 4}, + {1, 4}, + {2, 4}, + {3, 4}, + {2, 5}, + {3, 5}, }); - draw_image(right, 0.670820f, - 4, 0, 4 * size, - -2, 5, 4 * size - 2, + draw_image(right, 0.670820f, 4, 0, 4 * size, -2, 5, 4 * size - 2, { - {2, 0}, {3, 0}, - {0, 1}, {1, 1}, {2, 1}, {3, 1}, - {0, 2}, {1, 2}, {2, 2}, {3, 2}, - {0, 3}, {1, 3}, {2, 3}, {3, 3}, - {0, 4}, {1, 4}, {2, 4}, {3, 4}, - {0, 5}, {1, 5}, + {2, 0}, + {3, 0}, + {0, 1}, + {1, 1}, + {2, 1}, + {3, 1}, + {0, 2}, + {1, 2}, + {2, 2}, + {3, 2}, + {0, 3}, + {1, 3}, + {2, 3}, + {3, 3}, + {0, 4}, + {1, 4}, + {2, 4}, + {3, 4}, + {0, 5}, + {1, 5}, }); result->unlock(); return result; } -video::IImage* TextureSource::generateImage(const std::string &name) +video::IImage *TextureSource::generateImage(const std::string &name) { // Get the base image @@ -925,7 +957,7 @@ video::IImage* TextureSource::generateImage(const std::string &name) s32 last_separator_pos = -1; u8 paren_bal = 0; for (s32 i = name.size() - 1; i >= 0; i--) { - if (i > 0 && name[i-1] == escape) + if (i > 0 && name[i - 1] == escape) continue; switch (name[i]) { case separator: @@ -937,8 +969,9 @@ video::IImage* TextureSource::generateImage(const std::string &name) case paren_open: if (paren_bal == 0) { errorstream << "generateImage(): unbalanced parentheses" - << "(extranous '(') while generating texture \"" - << name << "\"" << std::endl; + << "(extranous '(') while generating texture " + "\"" + << name << "\"" << std::endl; return NULL; } paren_bal--; @@ -952,12 +985,11 @@ video::IImage* TextureSource::generateImage(const std::string &name) } if (paren_bal > 0) { errorstream << "generateImage(): unbalanced parentheses" - << "(missing matching '(') while generating texture \"" - << name << "\"" << std::endl; + << "(missing matching '(') while generating texture \"" + << name << "\"" << std::endl; return NULL; } - video::IImage *baseimg = NULL; /* @@ -979,15 +1011,15 @@ video::IImage* TextureSource::generateImage(const std::string &name) If this name is enclosed in parentheses, generate it and blit it onto the base image */ - if (last_part_of_name[0] == paren_open - && last_part_of_name[last_part_of_name.size() - 1] == paren_close) { - std::string name2 = last_part_of_name.substr(1, - last_part_of_name.size() - 2); + if (last_part_of_name[0] == paren_open && + last_part_of_name[last_part_of_name.size() - 1] == paren_close) { + std::string name2 = + last_part_of_name.substr(1, last_part_of_name.size() - 2); video::IImage *tmp = generateImage(name2); if (!tmp) { errorstream << "generateImage(): " - "Failed to generate \"" << name2 << "\"" - << std::endl; + "Failed to generate \"" + << name2 << "\"" << std::endl; return NULL; } core::dimension2d dim = tmp->getDimension(); @@ -1000,14 +1032,15 @@ video::IImage* TextureSource::generateImage(const std::string &name) } else if (!generateImagePart(last_part_of_name, baseimg)) { // Generate image according to part of name errorstream << "generateImage(): " - "Failed to generate \"" << last_part_of_name << "\"" - << std::endl; + "Failed to generate \"" + << last_part_of_name << "\"" << std::endl; } // If no resulting image, print a warning if (baseimg == NULL) { errorstream << "generateImage(): baseimg is NULL (attempted to" - " create texture \"" << name << "\")" << std::endl; + " create texture \"" + << name << "\")" << std::endl; } return baseimg; @@ -1015,11 +1048,10 @@ video::IImage* TextureSource::generateImage(const std::string &name) #if ENABLE_GLES - static inline u16 get_GL_major_version() { const GLubyte *gl_version = glGetString(GL_VERSION); - return (u16) (gl_version[0] - '0'); + return (u16)(gl_version[0] - '0'); } /** @@ -1031,9 +1063,9 @@ bool hasNPotSupport() { // Only GLES2 is trusted to correctly report npot support // Note: we cache the boolean result, the GL context will never change. - static const bool supported = get_GL_major_version() > 1 && - glGetString(GL_EXTENSIONS) && - strstr((char *)glGetString(GL_EXTENSIONS), "GL_OES_texture_npot"); + static const bool supported = + get_GL_major_version() > 1 && glGetString(GL_EXTENSIONS) && + strstr((char *)glGetString(GL_EXTENSIONS), "GL_OES_texture_npot"); return supported; } @@ -1044,8 +1076,7 @@ bool hasNPotSupport() * @return image or copy of image aligned to npot2 */ -video::IImage * Align2Npot2(video::IImage * image, - video::IVideoDriver* driver) +video::IImage *Align2Npot2(video::IImage *image, video::IVideoDriver *driver) { if (image == NULL) return image; @@ -1055,7 +1086,7 @@ video::IImage * Align2Npot2(video::IImage * image, core::dimension2d dim = image->getDimension(); unsigned int height = npot2(dim.Height); - unsigned int width = npot2(dim.Width); + unsigned int width = npot2(dim.Width); if (dim.Height == height && dim.Width == width) return image; @@ -1065,9 +1096,8 @@ video::IImage * Align2Npot2(video::IImage * image, if (dim.Width > width) width *= 2; - video::IImage *targetimage = - driver->createImage(video::ECF_A8R8G8B8, - core::dimension2d(width, height)); + video::IImage *targetimage = driver->createImage( + video::ECF_A8R8G8B8, core::dimension2d(width, height)); if (targetimage != NULL) image->copyToScaling(targetimage); @@ -1094,8 +1124,7 @@ static std::string unescape_string(const std::string &str, const char esc = '\\' return out; } -bool TextureSource::generateImagePart(std::string part_of_name, - video::IImage *& baseimg) +bool TextureSource::generateImagePart(std::string part_of_name, video::IImage *&baseimg) { const char escape = '\\'; // same as in generateImage() video::IVideoDriver *driver = RenderingEngine::get_video_driver(); @@ -1111,28 +1140,34 @@ bool TextureSource::generateImagePart(std::string part_of_name, if (!part_of_name.empty()) { // Do not create normalmap dummies - if (part_of_name.find("_normal.png") != std::string::npos) { - warningstream << "generateImage(): Could not load normal map \"" - << part_of_name << "\"" << std::endl; + if (part_of_name.find("_normal.png") != + std::string::npos) { + warningstream << "generateImage(): Could not " + "load normal map \"" + << part_of_name << "\"" + << std::endl; return true; } errorstream << "generateImage(): Could not load image \"" - << part_of_name << "\" while building texture; " - "Creating a dummy image" << std::endl; + << part_of_name + << "\" while building texture; " + "Creating a dummy image" + << std::endl; } // Just create a dummy image - //core::dimension2d dim(2,2); - core::dimension2d dim(1,1); + // core::dimension2d dim(2,2); + core::dimension2d dim(1, 1); image = driver->createImage(video::ECF_A8R8G8B8, dim); sanity_check(image != NULL); /*image->setPixel(0,0, video::SColor(255,255,0,0)); image->setPixel(1,0, video::SColor(255,0,255,0)); image->setPixel(0,1, video::SColor(255,0,0,255)); image->setPixel(1,1, video::SColor(255,255,0,255));*/ - image->setPixel(0,0, video::SColor(255,myrand()%256, - myrand()%256,myrand()%256)); + image->setPixel(0, 0, + video::SColor(255, myrand() % 256, myrand() % 256, + myrand() % 256)); /*image->setPixel(1,0, video::SColor(255,myrand()%256, myrand()%256,myrand()%256)); image->setPixel(0,1, video::SColor(255,myrand()%256, @@ -1142,9 +1177,8 @@ bool TextureSource::generateImagePart(std::string part_of_name, } // If base image is NULL, load as base. - if (baseimg == NULL) - { - //infostream<<"Setting "<copyTo(baseimg); } // Else blit on base. - else - { - //infostream<<"Blitting "< dim = image->getDimension(); - //core::dimension2d dim(16,16); + // core::dimension2d dim(16,16); // Position to copy the blitted to in the base image - core::position2d pos_to(0,0); + core::position2d pos_to(0, 0); // Position to copy the blitted from in the blitted image - core::position2d pos_from(0,0); + core::position2d pos_from(0, 0); // Blit /*image->copyToWithAlpha(baseimg, pos_to, core::rect(pos_from, dim), @@ -1174,18 +1207,23 @@ bool TextureSource::generateImagePart(std::string part_of_name, core::dimension2d dim_dst = baseimg->getDimension(); if (dim == dim_dst) { blit_with_alpha(image, baseimg, pos_from, pos_to, dim); - } else if (dim.Width * dim.Height < dim_dst.Width * dim_dst.Height) { + } else if (dim.Width * dim.Height < + dim_dst.Width * dim_dst.Height) { // Upscale overlying image - video::IImage *scaled_image = RenderingEngine::get_video_driver()-> - createImage(video::ECF_A8R8G8B8, dim_dst); + video::IImage *scaled_image = + RenderingEngine::get_video_driver()->createImage( + video::ECF_A8R8G8B8, + dim_dst); image->copyToScaling(scaled_image); - blit_with_alpha(scaled_image, baseimg, pos_from, pos_to, dim_dst); + blit_with_alpha(scaled_image, baseimg, pos_from, pos_to, + dim_dst); scaled_image->drop(); } else { // Upscale base image - video::IImage *scaled_base = RenderingEngine::get_video_driver()-> - createImage(video::ECF_A8R8G8B8, dim); + video::IImage *scaled_base = + RenderingEngine::get_video_driver()->createImage( + video::ECF_A8R8G8B8, dim); baseimg->copyToScaling(scaled_base); baseimg->drop(); baseimg = scaled_base; @@ -1193,11 +1231,9 @@ bool TextureSource::generateImagePart(std::string part_of_name, blit_with_alpha(image, baseimg, pos_from, pos_to, dim); } } - //cleanup + // cleanup image->drop(); - } - else - { + } else { // A special texture modification /*infostream<<"generateImage(): generating special " @@ -1210,12 +1246,11 @@ bool TextureSource::generateImagePart(std::string part_of_name, Adds a cracking texture N = animation frame count, P = crack progression */ - if (str_starts_with(part_of_name, "[crack")) - { + if (str_starts_with(part_of_name, "[crack")) { if (baseimg == NULL) { - errorstream<<"generateImagePart(): baseimg == NULL " - <<"for part_of_name=\""<drop(); } } @@ -1259,53 +1294,55 @@ bool TextureSource::generateImagePart(std::string part_of_name, [combine:WxH:X,Y=filename:X,Y=filename2 Creates a bigger texture from any amount of smaller ones */ - else if (str_starts_with(part_of_name, "[combine")) - { + else if (str_starts_with(part_of_name, "[combine")) { Strfnd sf(part_of_name); sf.next(":"); u32 w0 = stoi(sf.next("x")); u32 h0 = stoi(sf.next(":")); - core::dimension2d dim(w0,h0); + core::dimension2d dim(w0, h0); if (baseimg == NULL) { baseimg = driver->createImage(video::ECF_A8R8G8B8, dim); - baseimg->fill(video::SColor(0,0,0,0)); + baseimg->fill(video::SColor(0, 0, 0, 0)); } while (!sf.at_end()) { u32 x = stoi(sf.next(",")); u32 y = stoi(sf.next("=")); - std::string filename = unescape_string(sf.next_esc(":", escape), escape); - infostream<<"Adding \""< dim = img->getDimension(); core::position2d pos_base(x, y); - video::IImage *img2 = - driver->createImage(video::ECF_A8R8G8B8, dim); + video::IImage *img2 = driver->createImage( + video::ECF_A8R8G8B8, dim); img->copyTo(img2); img->drop(); /*img2->copyToWithAlpha(baseimg, pos_base, core::rect(v2s32(0,0), dim), video::SColor(255,255,255,255), NULL);*/ - blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim); + blit_with_alpha(img2, baseimg, v2s32(0, 0), + pos_base, dim); img2->drop(); } else { - errorstream << "generateImagePart(): Failed to load image \"" - << filename << "\" for [combine" << std::endl; + errorstream << "generateImagePart(): Failed to " + "load image \"" + << filename << "\" for [combine" + << std::endl; } } } /* [brighten */ - else if (str_starts_with(part_of_name, "[brighten")) - { + else if (str_starts_with(part_of_name, "[brighten")) { if (baseimg == NULL) { - errorstream<<"generateImagePart(): baseimg==NULL " - <<"for part_of_name=\""< dim = baseimg->getDimension(); // Set alpha to full - for (u32 y=0; ygetPixel(x,y); - c.setAlpha(255); - baseimg->setPixel(x,y,c); - } + for (u32 y = 0; y < dim.Height; y++) + for (u32 x = 0; x < dim.Width; x++) { + video::SColor c = baseimg->getPixel(x, y); + c.setAlpha(255); + baseimg->setPixel(x, y, c); + } } /* [makealpha:R,G,B Convert one color to transparent. */ - else if (str_starts_with(part_of_name, "[makealpha:")) - { + else if (str_starts_with(part_of_name, "[makealpha:")) { if (baseimg == NULL) { - errorstream<<"generateImagePart(): baseimg == NULL " - <<"for part_of_name=\""<drop();*/ // Set alpha to full - for (u32 y=0; ygetPixel(x,y); - u32 r = c.getRed(); - u32 g = c.getGreen(); - u32 b = c.getBlue(); - if (!(r == r1 && g == g1 && b == b1)) - continue; - c.setAlpha(0); - baseimg->setPixel(x,y,c); - } + for (u32 y = 0; y < dim.Height; y++) + for (u32 x = 0; x < dim.Width; x++) { + video::SColor c = baseimg->getPixel(x, y); + u32 r = c.getRed(); + u32 g = c.getGreen(); + u32 b = c.getBlue(); + if (!(r == r1 && g == g1 && b == b1)) + continue; + c.setAlpha(0); + baseimg->setPixel(x, y, c); + } } /* [transformN @@ -1397,12 +1430,11 @@ bool TextureSource::generateImagePart(std::string part_of_name, The resulting transform will be equivalent to one of the eight existing ones, though (see: dihedral group). */ - else if (str_starts_with(part_of_name, "[transform")) - { + else if (str_starts_with(part_of_name, "[transform")) { if (baseimg == NULL) { - errorstream<<"generateImagePart(): baseimg == NULL " - <<"for part_of_name=\""<createImage(video::ECF_A8R8G8B8, v2u32(16,16)); + baseimg = driver->createImage( + video::ECF_A8R8G8B8, v2u32(16, 16)); video::IImage *img = generateImage(filename); - if (img) - { + if (img) { core::dimension2d dim = img->getDimension(); core::position2d pos_base(0, 0); - video::IImage *img2 = - driver->createImage(video::ECF_A8R8G8B8, dim); + video::IImage *img2 = driver->createImage( + video::ECF_A8R8G8B8, dim); img->copyTo(img2); img->drop(); core::position2d clippos(0, 0); - clippos.Y = dim.Height * (100-percent) / 100; + clippos.Y = dim.Height * (100 - percent) / 100; core::dimension2d clipdim = dim; clipdim.Height = clipdim.Height * percent / 100 + 1; core::rect cliprect(clippos, clipdim); img2->copyToWithAlpha(baseimg, pos_base, - core::rect(v2s32(0,0), dim), - video::SColor(255,255,255,255), + core::rect(v2s32(0, 0), dim), + video::SColor(255, 255, 255, 255), &cliprect); img2->drop(); } @@ -1501,42 +1533,41 @@ bool TextureSource::generateImagePart(std::string part_of_name, Crops a frame of a vertical animation. N = frame count, I = frame index */ - else if (str_starts_with(part_of_name, "[verticalframe:")) - { + else if (str_starts_with(part_of_name, "[verticalframe:")) { Strfnd sf(part_of_name); sf.next(":"); u32 frame_count = stoi(sf.next(":")); u32 frame_index = stoi(sf.next(":")); - if (baseimg == NULL){ - errorstream<<"generateImagePart(): baseimg != NULL " - <<"for part_of_name=\""<getDimension(); frame_size.Y /= frame_count; - video::IImage *img = driver->createImage(video::ECF_A8R8G8B8, - frame_size); - if (!img){ - errorstream<<"generateImagePart(): Could not create image " - <<"for part_of_name=\""<createImage( + video::ECF_A8R8G8B8, frame_size); + if (!img) { + errorstream << "generateImagePart(): Could not create " + "image " + << "for part_of_name=\"" << part_of_name + << "\", cancelling." << std::endl; return false; } // Fill target image with transparency - img->fill(video::SColor(0,0,0,0)); + img->fill(video::SColor(0, 0, 0, 0)); core::dimension2d dim = frame_size; core::position2d pos_dst(0, 0); core::position2d pos_src(0, frame_index * frame_size.Y); baseimg->copyToWithAlpha(img, pos_dst, core::rect(pos_src, dim), - video::SColor(255,255,255,255), - NULL); + video::SColor(255, 255, 255, 255), NULL); // Replace baseimg baseimg->drop(); baseimg = img; @@ -1545,17 +1576,17 @@ bool TextureSource::generateImagePart(std::string part_of_name, [mask:filename Applies a mask to an image */ - else if (str_starts_with(part_of_name, "[mask:")) - { + else if (str_starts_with(part_of_name, "[mask:")) { if (baseimg == NULL) { errorstream << "generateImage(): baseimg == NULL " - << "for part_of_name=\"" << part_of_name - << "\", cancelling." << std::endl; + << "for part_of_name=\"" << part_of_name + << "\", cancelling." << std::endl; return false; } Strfnd sf(part_of_name); sf.next(":"); - std::string filename = unescape_string(sf.next_esc(":", escape), escape); + std::string filename = + unescape_string(sf.next_esc(":", escape), escape); video::IImage *img = generateImage(filename); if (img) { @@ -1564,7 +1595,7 @@ bool TextureSource::generateImagePart(std::string part_of_name, img->drop(); } else { errorstream << "generateImage(): Failed to load \"" - << filename << "\"."; + << filename << "\"."; } } /* @@ -1579,8 +1610,8 @@ bool TextureSource::generateImagePart(std::string part_of_name, if (baseimg == NULL) { errorstream << "generateImagePart(): baseimg != NULL " - << "for part_of_name=\"" << part_of_name - << "\", cancelling." << std::endl; + << "for part_of_name=\"" << part_of_name + << "\", cancelling." << std::endl; return false; } @@ -1589,15 +1620,15 @@ bool TextureSource::generateImagePart(std::string part_of_name, if (!parseColorString(color_str, color, false)) return false; - apply_multiplication(baseimg, v2u32(0, 0), baseimg->getDimension(), color); + apply_multiplication(baseimg, v2u32(0, 0), + baseimg->getDimension(), color); } /* [colorize:color Overlays image with given color color = color as ColorString */ - else if (str_starts_with(part_of_name, "[colorize:")) - { + else if (str_starts_with(part_of_name, "[colorize:")) { Strfnd sf(part_of_name); sf.next(":"); std::string color_str = sf.next(":"); @@ -1605,8 +1636,8 @@ bool TextureSource::generateImagePart(std::string part_of_name, if (baseimg == NULL) { errorstream << "generateImagePart(): baseimg != NULL " - << "for part_of_name=\"" << part_of_name - << "\", cancelling." << std::endl; + << "for part_of_name=\"" << part_of_name + << "\", cancelling." << std::endl; return false; } @@ -1622,14 +1653,14 @@ bool TextureSource::generateImagePart(std::string part_of_name, else if (ratio_str == "alpha") keep_alpha = true; - apply_colorize(baseimg, v2u32(0, 0), baseimg->getDimension(), color, ratio, keep_alpha); + apply_colorize(baseimg, v2u32(0, 0), baseimg->getDimension(), + color, ratio, keep_alpha); } /* [applyfiltersformesh Internal modifier */ - else if (str_starts_with(part_of_name, "[applyfiltersformesh")) - { + else if (str_starts_with(part_of_name, "[applyfiltersformesh")) { /* IMPORTANT: When changing this, getTextureForMesh() needs to be * updated too. */ @@ -1637,25 +1668,33 @@ bool TextureSource::generateImagePart(std::string part_of_name, if (g_settings->getBool("texture_clean_transparent")) imageCleanTransparent(baseimg, 127); - /* Upscale textures to user's requested minimum size. This is a trick to make - * filters look as good on low-res textures as on high-res ones, by making - * low-res textures BECOME high-res ones. This is helpful for worlds that - * mix high- and low-res textures, or for mods with least-common-denominator - * textures that don't have the resources to offer high-res alternatives. + /* Upscale textures to user's requested minimum size. This is a + * trick to make filters look as good on low-res textures as on + * high-res ones, by making low-res textures BECOME high-res ones. + * This is helpful for worlds that mix high- and low-res textures, + * or for mods with least-common-denominator textures that don't + * have the resources to offer high-res alternatives. */ - const bool filter = m_setting_trilinear_filter || m_setting_bilinear_filter; - const s32 scaleto = filter ? g_settings->getS32("texture_min_size") : 1; + const bool filter = m_setting_trilinear_filter || + m_setting_bilinear_filter; + const s32 scaleto = + filter ? g_settings->getS32("texture_min_size") + : 1; if (scaleto > 1) { - const core::dimension2d dim = baseimg->getDimension(); + const core::dimension2d dim = + baseimg->getDimension(); - /* Calculate scaling needed to make the shortest texture dimension - * equal to the target minimum. If e.g. this is a vertical frames - * animation, the short dimension will be the real size. + /* Calculate scaling needed to make the shortest texture + * dimension equal to the target minimum. If e.g. this is + * a vertical frames animation, the short dimension will + * be the real size. */ if ((dim.Width == 0) || (dim.Height == 0)) { - errorstream << "generateImagePart(): Illegal 0 dimension " - << "for part_of_name=\""<< part_of_name - << "\", cancelling." << std::endl; + errorstream << "generateImagePart(): Illegal 0 " + "dimension " + << "for part_of_name=\"" + << part_of_name << "\", cancelling." + << std::endl; return false; } u32 xscale = scaleto / dim.Width; @@ -1666,9 +1705,11 @@ bool TextureSource::generateImagePart(std::string part_of_name, if (scale > 1) { u32 w = scale * dim.Width; u32 h = scale * dim.Height; - const core::dimension2d newdim = core::dimension2d(w, h); + const core::dimension2d newdim = + core::dimension2d(w, h); video::IImage *newimg = driver->createImage( - baseimg->getColorFormat(), newdim); + baseimg->getColorFormat(), + newdim); baseimg->copyToScaling(newimg); baseimg->drop(); baseimg = newimg; @@ -1679,12 +1720,11 @@ bool TextureSource::generateImagePart(std::string part_of_name, [resize:WxH Resizes the base image to the given dimensions */ - else if (str_starts_with(part_of_name, "[resize")) - { + else if (str_starts_with(part_of_name, "[resize")) { if (baseimg == NULL) { errorstream << "generateImagePart(): baseimg == NULL " - << "for part_of_name=\""<< part_of_name - << "\", cancelling." << std::endl; + << "for part_of_name=\"" << part_of_name + << "\", cancelling." << std::endl; return false; } @@ -1694,8 +1734,9 @@ bool TextureSource::generateImagePart(std::string part_of_name, u32 height = stoi(sf.next("")); core::dimension2d dim(width, height); - video::IImage *image = RenderingEngine::get_video_driver()-> - createImage(video::ECF_A8R8G8B8, dim); + video::IImage *image = + RenderingEngine::get_video_driver()->createImage( + video::ECF_A8R8G8B8, dim); baseimg->copyToScaling(image); baseimg->drop(); baseimg = image; @@ -1710,8 +1751,8 @@ bool TextureSource::generateImagePart(std::string part_of_name, else if (str_starts_with(part_of_name, "[opacity:")) { if (baseimg == NULL) { errorstream << "generateImagePart(): baseimg == NULL " - << "for part_of_name=\"" << part_of_name - << "\", cancelling." << std::endl; + << "for part_of_name=\"" << part_of_name + << "\", cancelling." << std::endl; return false; } @@ -1723,12 +1764,12 @@ bool TextureSource::generateImagePart(std::string part_of_name, core::dimension2d dim = baseimg->getDimension(); for (u32 y = 0; y < dim.Height; y++) - for (u32 x = 0; x < dim.Width; x++) - { - video::SColor c = baseimg->getPixel(x, y); - c.setAlpha(floor((c.getAlpha() * ratio) / 255 + 0.5)); - baseimg->setPixel(x, y, c); - } + for (u32 x = 0; x < dim.Width; x++) { + video::SColor c = baseimg->getPixel(x, y); + c.setAlpha(floor((c.getAlpha() * ratio) / 255 + + 0.5)); + baseimg->setPixel(x, y, c); + } } /* [invert:mode @@ -1740,8 +1781,8 @@ bool TextureSource::generateImagePart(std::string part_of_name, else if (str_starts_with(part_of_name, "[invert:")) { if (baseimg == NULL) { errorstream << "generateImagePart(): baseimg == NULL " - << "for part_of_name=\"" << part_of_name - << "\", cancelling." << std::endl; + << "for part_of_name=\"" << part_of_name + << "\", cancelling." << std::endl; return false; } @@ -1762,12 +1803,11 @@ bool TextureSource::generateImagePart(std::string part_of_name, core::dimension2d dim = baseimg->getDimension(); for (u32 y = 0; y < dim.Height; y++) - for (u32 x = 0; x < dim.Width; x++) - { - video::SColor c = baseimg->getPixel(x, y); - c.color ^= mask; - baseimg->setPixel(x, y, c); - } + for (u32 x = 0; x < dim.Width; x++) { + video::SColor c = baseimg->getPixel(x, y); + c.color ^= mask; + baseimg->setPixel(x, y, c); + } } /* [sheet:WxH:X,Y @@ -1775,11 +1815,11 @@ bool TextureSource::generateImagePart(std::string part_of_name, from the base image it assumes to be a tilesheet with dimensions W,H (in tiles). */ - else if (part_of_name.substr(0,7) == "[sheet:") { + else if (part_of_name.substr(0, 7) == "[sheet:") { if (baseimg == NULL) { errorstream << "generateImagePart(): baseimg != NULL " - << "for part_of_name=\"" << part_of_name - << "\", cancelling." << std::endl; + << "for part_of_name=\"" << part_of_name + << "\", cancelling." << std::endl; return false; } @@ -1796,26 +1836,26 @@ bool TextureSource::generateImagePart(std::string part_of_name, video::IImage *img = driver->createImage( video::ECF_A8R8G8B8, tile_dim); if (!img) { - errorstream << "generateImagePart(): Could not create image " - << "for part_of_name=\"" << part_of_name - << "\", cancelling." << std::endl; + errorstream << "generateImagePart(): Could not create " + "image " + << "for part_of_name=\"" << part_of_name + << "\", cancelling." << std::endl; return false; } - img->fill(video::SColor(0,0,0,0)); + img->fill(video::SColor(0, 0, 0, 0)); v2u32 vdim(tile_dim); core::rect rect(v2s32(x0 * vdim.X, y0 * vdim.Y), tile_dim); baseimg->copyToWithAlpha(img, v2s32(0), rect, - video::SColor(255,255,255,255), NULL); + video::SColor(255, 255, 255, 255), NULL); // Replace baseimg baseimg->drop(); baseimg = img; - } - else - { + } else { errorstream << "generateImagePart(): Invalid " - " modification: \"" << part_of_name << "\"" << std::endl; + " modification: \"" + << part_of_name << "\"" << std::endl; } } @@ -1830,13 +1870,14 @@ bool TextureSource::generateImagePart(std::string part_of_name, pixel with alpha=64 drawn atop a pixel with alpha=128 should yield a pixel with alpha=160, while getInterpolated would yield alpha=96. */ -static inline video::SColor blitPixel(const video::SColor &src_c, const video::SColor &dst_c, u32 ratio) +static inline video::SColor blitPixel( + const video::SColor &src_c, const video::SColor &dst_c, u32 ratio) { if (dst_c.getAlpha() == 0) return src_c; video::SColor out_c = src_c.getInterpolated(dst_c, (float)ratio / 255.0f); - out_c.setAlpha(dst_c.getAlpha() + (255 - dst_c.getAlpha()) * - src_c.getAlpha() * ratio / (255 * 255)); + out_c.setAlpha(dst_c.getAlpha() + (255 - dst_c.getAlpha()) * src_c.getAlpha() * + ratio / (255 * 255)); return out_c; } @@ -1847,45 +1888,42 @@ static inline video::SColor blitPixel(const video::SColor &src_c, const video::S This exists because IImage::copyToWithAlpha() doesn't seem to always work. */ -static void blit_with_alpha(video::IImage *src, video::IImage *dst, - v2s32 src_pos, v2s32 dst_pos, v2u32 size) +static void blit_with_alpha(video::IImage *src, video::IImage *dst, v2s32 src_pos, + v2s32 dst_pos, v2u32 size) { - for (u32 y0=0; y0getPixel(src_x, src_y); - video::SColor dst_c = dst->getPixel(dst_x, dst_y); - dst_c = blitPixel(src_c, dst_c, src_c.getAlpha()); - dst->setPixel(dst_x, dst_y, dst_c); - } + for (u32 y0 = 0; y0 < size.Y; y0++) + for (u32 x0 = 0; x0 < size.X; x0++) { + s32 src_x = src_pos.X + x0; + s32 src_y = src_pos.Y + y0; + s32 dst_x = dst_pos.X + x0; + s32 dst_y = dst_pos.Y + y0; + video::SColor src_c = src->getPixel(src_x, src_y); + video::SColor dst_c = dst->getPixel(dst_x, dst_y); + dst_c = blitPixel(src_c, dst_c, src_c.getAlpha()); + dst->setPixel(dst_x, dst_y, dst_c); + } } /* Draw an image on top of an another one, using the alpha channel of the source image; only modify fully opaque pixels in destinaion */ -static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst, - v2s32 src_pos, v2s32 dst_pos, v2u32 size) +static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst, v2s32 src_pos, + v2s32 dst_pos, v2u32 size) { - for (u32 y0=0; y0getPixel(src_x, src_y); - video::SColor dst_c = dst->getPixel(dst_x, dst_y); - if (dst_c.getAlpha() == 255 && src_c.getAlpha() != 0) - { - dst_c = blitPixel(src_c, dst_c, src_c.getAlpha()); - dst->setPixel(dst_x, dst_y, dst_c); + for (u32 y0 = 0; y0 < size.Y; y0++) + for (u32 x0 = 0; x0 < size.X; x0++) { + s32 src_x = src_pos.X + x0; + s32 src_y = src_pos.Y + y0; + s32 dst_x = dst_pos.X + x0; + s32 dst_y = dst_pos.Y + y0; + video::SColor src_c = src->getPixel(src_x, src_y); + video::SColor dst_c = dst->getPixel(dst_x, dst_y); + if (dst_c.getAlpha() == 255 && src_c.getAlpha() != 0) { + dst_c = blitPixel(src_c, dst_c, src_c.getAlpha()); + dst->setPixel(dst_x, dst_y, dst_c); + } } - } } // This function has been disabled because it is currently unused. @@ -1928,61 +1966,60 @@ static void apply_colorize(video::IImage *dst, v2u32 dst_pos, v2u32 size, u32 alpha = color.getAlpha(); video::SColor dst_c; if ((ratio == -1 && alpha == 255) || ratio == 255) { // full replacement of color - if (keep_alpha) { // replace the color with alpha = dest alpha * color alpha + if (keep_alpha) { // replace the color with alpha = dest alpha * color + // alpha dst_c = color; for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++) + for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) { + u32 dst_alpha = dst->getPixel(x, y).getAlpha(); + if (dst_alpha > 0) { + dst_c.setAlpha(dst_alpha * alpha / 255); + dst->setPixel(x, y, dst_c); + } + } + } else { // replace the color including the alpha + for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++) + for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) + if (dst->getPixel(x, y).getAlpha() > 0) + dst->setPixel(x, y, color); + } + } else { // interpolate between the color and destination + float interp = (ratio == -1 ? color.getAlpha() / 255.0f : ratio / 255.0f); + for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++) for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) { - u32 dst_alpha = dst->getPixel(x, y).getAlpha(); - if (dst_alpha > 0) { - dst_c.setAlpha(dst_alpha * alpha / 255); + dst_c = dst->getPixel(x, y); + if (dst_c.getAlpha() > 0) { + dst_c = color.getInterpolated(dst_c, interp); dst->setPixel(x, y, dst_c); } } - } else { // replace the color including the alpha - for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++) - for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) - if (dst->getPixel(x, y).getAlpha() > 0) - dst->setPixel(x, y, color); - } - } else { // interpolate between the color and destination - float interp = (ratio == -1 ? color.getAlpha() / 255.0f : ratio / 255.0f); - for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++) - for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) { - dst_c = dst->getPixel(x, y); - if (dst_c.getAlpha() > 0) { - dst_c = color.getInterpolated(dst_c, interp); - dst->setPixel(x, y, dst_c); - } - } } } /* Apply color to destination */ -static void apply_multiplication(video::IImage *dst, v2u32 dst_pos, v2u32 size, - const video::SColor &color) +static void apply_multiplication( + video::IImage *dst, v2u32 dst_pos, v2u32 size, const video::SColor &color) { video::SColor dst_c; for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++) - for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) { - dst_c = dst->getPixel(x, y); - dst_c.set( - dst_c.getAlpha(), - (dst_c.getRed() * color.getRed()) / 255, - (dst_c.getGreen() * color.getGreen()) / 255, - (dst_c.getBlue() * color.getBlue()) / 255 - ); - dst->setPixel(x, y, dst_c); - } + for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) { + dst_c = dst->getPixel(x, y); + dst_c.set(dst_c.getAlpha(), + (dst_c.getRed() * color.getRed()) / 255, + (dst_c.getGreen() * color.getGreen()) / 255, + (dst_c.getBlue() * color.getBlue()) / 255); + dst->setPixel(x, y, dst_c); + } } /* Apply mask to destination */ -static void apply_mask(video::IImage *mask, video::IImage *dst, - v2s32 mask_pos, v2s32 dst_pos, v2u32 size) +static void apply_mask(video::IImage *mask, video::IImage *dst, v2s32 mask_pos, + v2s32 dst_pos, v2u32 size) { for (u32 y0 = 0; y0 < size.Y; y0++) { for (u32 x0 = 0; x0 < size.X; x0++) { @@ -2010,14 +2047,15 @@ video::IImage *create_crack_image(video::IImage *crack, s32 frame_index, core::rect frame(v2s32(0, frame_index * frame_size.Height), frame_size); video::IImage *result = nullptr; -// extract crack frame + // extract crack frame video::IImage *crack_tile = driver->createImage(video::ECF_A8R8G8B8, tile_size); if (!crack_tile) return nullptr; if (tile_size == frame_size) { crack->copyTo(crack_tile, v2s32(0, 0), frame); } else { - video::IImage *crack_frame = driver->createImage(video::ECF_A8R8G8B8, frame_size); + video::IImage *crack_frame = + driver->createImage(video::ECF_A8R8G8B8, frame_size); if (!crack_frame) goto exit__has_tile; crack->copyTo(crack_frame, v2s32(0, 0), frame); @@ -2027,46 +2065,43 @@ video::IImage *create_crack_image(video::IImage *crack, s32 frame_index, if (tiles == 1) return crack_tile; -// tile it + // tile it result = driver->createImage(video::ECF_A8R8G8B8, size); if (!result) goto exit__has_tile; result->fill({}); for (u8 i = 0; i < tiles; i++) for (u8 j = 0; j < tiles; j++) - crack_tile->copyTo(result, v2s32(i * tile_size.Width, j * tile_size.Height)); + crack_tile->copyTo(result, + v2s32(i * tile_size.Width, j * tile_size.Height)); exit__has_tile: crack_tile->drop(); return result; } -static void draw_crack(video::IImage *crack, video::IImage *dst, - bool use_overlay, s32 frame_count, s32 progression, - video::IVideoDriver *driver, u8 tiles) +static void draw_crack(video::IImage *crack, video::IImage *dst, bool use_overlay, + s32 frame_count, s32 progression, video::IVideoDriver *driver, u8 tiles) { // Dimension of destination image core::dimension2d dim_dst = dst->getDimension(); // Limit frame_count - if (frame_count > (s32) dim_dst.Height) + if (frame_count > (s32)dim_dst.Height) frame_count = dim_dst.Height; if (frame_count < 1) frame_count = 1; // Dimension of the scaled crack stage, // which is the same as the dimension of a single destination frame - core::dimension2d frame_size( - dim_dst.Width, - dim_dst.Height / frame_count - ); - video::IImage *crack_scaled = create_crack_image(crack, progression, - frame_size, tiles, driver); + core::dimension2d frame_size(dim_dst.Width, dim_dst.Height / frame_count); + video::IImage *crack_scaled = + create_crack_image(crack, progression, frame_size, tiles, driver); if (!crack_scaled) return; auto blit = use_overlay ? blit_with_alpha_overlay : blit_with_alpha; for (s32 i = 0; i < frame_count; ++i) { v2s32 dst_pos(0, frame_size.Height * i); - blit(crack_scaled, dst, v2s32(0,0), dst_pos, frame_size); + blit(crack_scaled, dst, v2s32(0, 0), dst_pos, frame_size); } crack_scaled->drop(); @@ -2079,18 +2114,17 @@ void brighten(video::IImage *image) core::dimension2d dim = image->getDimension(); - for (u32 y=0; ygetPixel(x,y); - c.setRed(0.5 * 255 + 0.5 * (float)c.getRed()); - c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen()); - c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue()); - image->setPixel(x,y,c); - } + for (u32 y = 0; y < dim.Height; y++) + for (u32 x = 0; x < dim.Width; x++) { + video::SColor c = image->getPixel(x, y); + c.setRed(0.5 * 255 + 0.5 * (float)c.getRed()); + c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen()); + c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue()); + image->setPixel(x, y, c); + } } -u32 parseImageTransform(const std::string& s) +u32 parseImageTransform(const std::string &s) { int total_transform = 0; @@ -2103,21 +2137,20 @@ u32 parseImageTransform(const std::string& s) transform_names[6] = "fy"; std::size_t pos = 0; - while(pos < s.size()) - { + while (pos < s.size()) { int transform = -1; - for (int i = 0; i <= 7; ++i) - { + for (int i = 0; i <= 7; ++i) { const std::string &name_i = transform_names[i]; - if (s[pos] == ('0' + i)) - { + if (s[pos] == ('0' + i)) { transform = i; pos++; break; } - if (!(name_i.empty()) && lowercase(s.substr(pos, name_i.size())) == name_i) { + if (!(name_i.empty()) && + lowercase(s.substr(pos, name_i.size())) == + name_i) { transform = i; pos += name_i.size(); break; @@ -2165,35 +2198,35 @@ void imageTransform(u32 transform, video::IImage *src, video::IImage *dst) */ int sxn = 0; int syn = 2; - if (transform == 0) // identity - sxn = 0, syn = 2; // sx = dx, sy = dy - else if (transform == 1) // rotate by 90 degrees ccw - sxn = 3, syn = 0; // sx = (H-1) - dy, sy = dx - else if (transform == 2) // rotate by 180 degrees - sxn = 1, syn = 3; // sx = (W-1) - dx, sy = (H-1) - dy - else if (transform == 3) // rotate by 270 degrees ccw - sxn = 2, syn = 1; // sx = dy, sy = (W-1) - dx - else if (transform == 4) // flip x - sxn = 1, syn = 2; // sx = (W-1) - dx, sy = dy - else if (transform == 5) // flip x then rotate by 90 degrees ccw - sxn = 2, syn = 0; // sx = dy, sy = dx - else if (transform == 6) // flip y - sxn = 0, syn = 3; // sx = dx, sy = (H-1) - dy - else if (transform == 7) // flip y then rotate by 90 degrees ccw - sxn = 3, syn = 1; // sx = (H-1) - dy, sy = (W-1) - dx + if (transform == 0) // identity + sxn = 0, syn = 2; // sx = dx, sy = dy + else if (transform == 1) // rotate by 90 degrees ccw + sxn = 3, syn = 0; // sx = (H-1) - dy, sy = dx + else if (transform == 2) // rotate by 180 degrees + sxn = 1, syn = 3; // sx = (W-1) - dx, sy = (H-1) - dy + else if (transform == 3) // rotate by 270 degrees ccw + sxn = 2, syn = 1; // sx = dy, sy = (W-1) - dx + else if (transform == 4) // flip x + sxn = 1, syn = 2; // sx = (W-1) - dx, sy = dy + else if (transform == 5) // flip x then rotate by 90 degrees ccw + sxn = 2, syn = 0; // sx = dy, sy = dx + else if (transform == 6) // flip y + sxn = 0, syn = 3; // sx = dx, sy = (H-1) - dy + else if (transform == 7) // flip y then rotate by 90 degrees ccw + sxn = 3, syn = 1; // sx = (H-1) - dy, sy = (W-1) - dx - for (u32 dy=0; dygetPixel(sx,sy); - dst->setPixel(dx,dy,c); - } + for (u32 dy = 0; dy < dstdim.Height; dy++) + for (u32 dx = 0; dx < dstdim.Width; dx++) { + u32 entries[4] = {dx, dstdim.Width - 1 - dx, dy, + dstdim.Height - 1 - dy}; + u32 sx = entries[sxn]; + u32 sy = entries[syn]; + video::SColor c = src->getPixel(sx, sy); + dst->setPixel(dx, dy, c); + } } -video::ITexture* TextureSource::getNormalTexture(const std::string &name) +video::ITexture *TextureSource::getNormalTexture(const std::string &name) { if (isKnownSourceImage("override_normal.png")) return getTexture("override_normal.png"); @@ -2219,9 +2252,8 @@ video::SColor TextureSource::getTextureAverageColor(const std::string &name) video::IVideoDriver *driver = RenderingEngine::get_video_driver(); video::SColor c(0, 0, 0, 0); video::ITexture *texture = getTexture(name); - video::IImage *image = driver->createImage(texture, - core::position2d(0, 0), - texture->getOriginalSize()); + video::IImage *image = driver->createImage( + texture, core::position2d(0, 0), texture->getOriginalSize()); u32 total = 0; u32 tR = 0; u32 tG = 0; @@ -2232,7 +2264,7 @@ video::SColor TextureSource::getTextureAverageColor(const std::string &name) step = dim.Width / 16; for (u16 x = 0; x < dim.Width; x += step) { for (u16 y = 0; y < dim.Width; y += step) { - c = image->getPixel(x,y); + c = image->getPixel(x, y); if (c.getAlpha() > 0) { total++; tR += c.getRed(); @@ -2251,7 +2283,6 @@ video::SColor TextureSource::getTextureAverageColor(const std::string &name) return c; } - video::ITexture *TextureSource::getShaderFlagsTexture(bool normalmap_present) { std::string tname = "__shaderFlagsTexture"; @@ -2263,14 +2294,13 @@ video::ITexture *TextureSource::getShaderFlagsTexture(bool normalmap_present) video::IVideoDriver *driver = RenderingEngine::get_video_driver(); video::IImage *flags_image = driver->createImage( - video::ECF_A8R8G8B8, core::dimension2d(1, 1)); + video::ECF_A8R8G8B8, core::dimension2d(1, 1)); sanity_check(flags_image != NULL); video::SColor c(255, normalmap_present ? 255 : 0, 0, 0); flags_image->setPixel(0, 0, c); insertSourceImage(tname, flags_image); flags_image->drop(); return getTexture(tname); - } std::vector getTextureDirs() diff --git a/src/client/tile.h b/src/client/tile.h index 49c46f749..695bcdeac 100644 --- a/src/client/tile.h +++ b/src/client/tile.h @@ -78,7 +78,7 @@ public: virtual ~ISimpleTextureSource() = default; - virtual video::ITexture* getTexture( + virtual video::ITexture *getTexture( const std::string &name, u32 *id = nullptr) = 0; }; @@ -89,12 +89,12 @@ public: virtual ~ITextureSource() = default; - virtual u32 getTextureId(const std::string &name)=0; - virtual std::string getTextureName(u32 id)=0; - virtual video::ITexture* getTexture(u32 id)=0; - virtual video::ITexture* getTexture( - const std::string &name, u32 *id = nullptr)=0; - virtual video::ITexture* getTextureForMesh( + virtual u32 getTextureId(const std::string &name) = 0; + virtual std::string getTextureName(u32 id) = 0; + virtual video::ITexture *getTexture(u32 id) = 0; + virtual video::ITexture *getTexture( + const std::string &name, u32 *id = nullptr) = 0; + virtual video::ITexture *getTextureForMesh( const std::string &name, u32 *id = nullptr) = 0; /*! * Returns a palette from the given texture name. @@ -102,11 +102,11 @@ public: * destructed. * Should be called from the main thread. */ - virtual Palette* getPalette(const std::string &name) = 0; - virtual bool isKnownSourceImage(const std::string &name)=0; - virtual video::ITexture* getNormalTexture(const std::string &name)=0; - virtual video::SColor getTextureAverageColor(const std::string &name)=0; - virtual video::ITexture *getShaderFlagsTexture(bool normalmap_present)=0; + virtual Palette *getPalette(const std::string &name) = 0; + virtual bool isKnownSourceImage(const std::string &name) = 0; + virtual video::ITexture *getNormalTexture(const std::string &name) = 0; + virtual video::SColor getTextureAverageColor(const std::string &name) = 0; + virtual video::ITexture *getShaderFlagsTexture(bool normalmap_present) = 0; }; class IWritableTextureSource : public ITextureSource @@ -116,29 +116,30 @@ public: virtual ~IWritableTextureSource() = default; - virtual u32 getTextureId(const std::string &name)=0; - virtual std::string getTextureName(u32 id)=0; - virtual video::ITexture* getTexture(u32 id)=0; - virtual video::ITexture* getTexture( - const std::string &name, u32 *id = nullptr)=0; - virtual bool isKnownSourceImage(const std::string &name)=0; + virtual u32 getTextureId(const std::string &name) = 0; + virtual std::string getTextureName(u32 id) = 0; + virtual video::ITexture *getTexture(u32 id) = 0; + virtual video::ITexture *getTexture( + const std::string &name, u32 *id = nullptr) = 0; + virtual bool isKnownSourceImage(const std::string &name) = 0; - virtual void processQueue()=0; - virtual void insertSourceImage(const std::string &name, video::IImage *img)=0; - virtual void rebuildImagesAndTextures()=0; - virtual video::ITexture* getNormalTexture(const std::string &name)=0; - virtual video::SColor getTextureAverageColor(const std::string &name)=0; - virtual video::ITexture *getShaderFlagsTexture(bool normalmap_present)=0; + virtual void processQueue() = 0; + virtual void insertSourceImage(const std::string &name, video::IImage *img) = 0; + virtual void rebuildImagesAndTextures() = 0; + virtual video::ITexture *getNormalTexture(const std::string &name) = 0; + virtual video::SColor getTextureAverageColor(const std::string &name) = 0; + virtual video::ITexture *getShaderFlagsTexture(bool normalmap_present) = 0; }; IWritableTextureSource *createTextureSource(); #if ENABLE_GLES bool hasNPotSupport(); -video::IImage * Align2Npot2(video::IImage * image, irr::video::IVideoDriver* driver); +video::IImage *Align2Npot2(video::IImage *image, irr::video::IVideoDriver *driver); #endif -enum MaterialType{ +enum MaterialType +{ TILE_MATERIAL_BASIC, TILE_MATERIAL_ALPHA, TILE_MATERIAL_LIQUID_TRANSPARENT, @@ -192,21 +193,16 @@ struct TileLayer */ bool operator==(const TileLayer &other) const { - return - texture_id == other.texture_id && - material_type == other.material_type && - material_flags == other.material_flags && - color == other.color && - scale == other.scale; + return texture_id == other.texture_id && + material_type == other.material_type && + material_flags == other.material_flags && color == other.color && + scale == other.scale; } /*! * Two tiles are not equal if they must have different vertices. */ - bool operator!=(const TileLayer &other) const - { - return !(*this == other); - } + bool operator!=(const TileLayer &other) const { return !(*this == other); } // Sets everything else except the texture in the material void applyMaterialOptions(video::SMaterial &material) const @@ -232,7 +228,8 @@ struct TileLayer default: break; } - material.BackfaceCulling = (material_flags & MATERIAL_FLAG_BACKFACE_CULLING) != 0; + material.BackfaceCulling = + (material_flags & MATERIAL_FLAG_BACKFACE_CULLING) != 0; if (!(material_flags & MATERIAL_FLAG_TILEABLE_HORIZONTAL)) { material.TextureLayer[0].TextureWrapU = video::ETC_CLAMP_TO_EDGE; } @@ -243,7 +240,8 @@ struct TileLayer void applyMaterialOptionsWithShaders(video::SMaterial &material) const { - material.BackfaceCulling = (material_flags & MATERIAL_FLAG_BACKFACE_CULLING) != 0; + material.BackfaceCulling = + (material_flags & MATERIAL_FLAG_BACKFACE_CULLING) != 0; if (!(material_flags & MATERIAL_FLAG_TILEABLE_HORIZONTAL)) { material.TextureLayer[0].TextureWrapU = video::ETC_CLAMP_TO_EDGE; material.TextureLayer[1].TextureWrapU = video::ETC_CLAMP_TO_EDGE; @@ -256,8 +254,8 @@ struct TileLayer bool isTileable() const { - return (material_flags & MATERIAL_FLAG_TILEABLE_HORIZONTAL) - && (material_flags & MATERIAL_FLAG_TILEABLE_VERTICAL); + return (material_flags & MATERIAL_FLAG_TILEABLE_HORIZONTAL) && + (material_flags & MATERIAL_FLAG_TILEABLE_VERTICAL); } // Ordered for size, please do not reorder @@ -275,10 +273,10 @@ struct TileLayer u8 material_type = TILE_MATERIAL_BASIC; u8 material_flags = - //0 // <- DEBUG, Use the one below - MATERIAL_FLAG_BACKFACE_CULLING | - MATERIAL_FLAG_TILEABLE_HORIZONTAL| - MATERIAL_FLAG_TILEABLE_VERTICAL; + // 0 // <- DEBUG, Use the one below + MATERIAL_FLAG_BACKFACE_CULLING | + MATERIAL_FLAG_TILEABLE_HORIZONTAL | + MATERIAL_FLAG_TILEABLE_VERTICAL; //! If true, the tile has its own color. bool has_color = false; @@ -304,16 +302,16 @@ struct TileSpec /*! * Returns true if this tile can be merged with the other tile. */ - bool isTileable(const TileSpec &other) const { + bool isTileable(const TileSpec &other) const + { for (int layer = 0; layer < MAX_TILE_LAYERS; layer++) { if (layers[layer] != other.layers[layer]) return false; if (!layers[layer].isTileable()) return false; } - return rotation == 0 - && rotation == other.rotation - && emissive_light == other.emissive_light; + return rotation == 0 && rotation == other.rotation && + emissive_light == other.emissive_light; } //! If true, the tile rotation is ignored. diff --git a/src/client/wieldmesh.cpp b/src/client/wieldmesh.cpp index 8cd3e29a9..06053302f 100644 --- a/src/client/wieldmesh.cpp +++ b/src/client/wieldmesh.cpp @@ -45,29 +45,29 @@ static scene::IMesh *createExtrusionMesh(int resolution_x, int resolution_y) const f32 r = 0.5; scene::IMeshBuffer *buf = new scene::SMeshBuffer(); - video::SColor c(255,255,255,255); + video::SColor c(255, 255, 255, 255); v3f scale(1.0, 1.0, 0.1); // Front and back { video::S3DVertex vertices[8] = { - // z- - video::S3DVertex(-r,+r,-r, 0,0,-1, c, 0,0), - video::S3DVertex(+r,+r,-r, 0,0,-1, c, 1,0), - video::S3DVertex(+r,-r,-r, 0,0,-1, c, 1,1), - video::S3DVertex(-r,-r,-r, 0,0,-1, c, 0,1), - // z+ - video::S3DVertex(-r,+r,+r, 0,0,+1, c, 0,0), - video::S3DVertex(-r,-r,+r, 0,0,+1, c, 0,1), - video::S3DVertex(+r,-r,+r, 0,0,+1, c, 1,1), - video::S3DVertex(+r,+r,+r, 0,0,+1, c, 1,0), + // z- + video::S3DVertex(-r, +r, -r, 0, 0, -1, c, 0, 0), + video::S3DVertex(+r, +r, -r, 0, 0, -1, c, 1, 0), + video::S3DVertex(+r, -r, -r, 0, 0, -1, c, 1, 1), + video::S3DVertex(-r, -r, -r, 0, 0, -1, c, 0, 1), + // z+ + video::S3DVertex(-r, +r, +r, 0, 0, +1, c, 0, 0), + video::S3DVertex(-r, -r, +r, 0, 0, +1, c, 0, 1), + video::S3DVertex(+r, -r, +r, 0, 0, +1, c, 1, 1), + video::S3DVertex(+r, +r, +r, 0, 0, +1, c, 1, 0), }; - u16 indices[12] = {0,1,2,2,3,0,4,5,6,6,7,4}; + u16 indices[12] = {0, 1, 2, 2, 3, 0, 4, 5, 6, 6, 7, 4}; buf->append(vertices, 8, indices, 12); } - f32 pixelsize_x = 1 / (f32) resolution_x; - f32 pixelsize_y = 1 / (f32) resolution_y; + f32 pixelsize_x = 1 / (f32)resolution_x; + f32 pixelsize_y = 1 / (f32)resolution_y; for (int i = 0; i < resolution_x; ++i) { f32 pixelpos_x = i * pixelsize_x - 0.5; @@ -76,18 +76,18 @@ static scene::IMesh *createExtrusionMesh(int resolution_x, int resolution_y) f32 tex0 = (i + 0.1) * pixelsize_x; f32 tex1 = (i + 0.9) * pixelsize_x; video::S3DVertex vertices[8] = { - // x- - video::S3DVertex(x0,-r,-r, -1,0,0, c, tex0,1), - video::S3DVertex(x0,-r,+r, -1,0,0, c, tex1,1), - video::S3DVertex(x0,+r,+r, -1,0,0, c, tex1,0), - video::S3DVertex(x0,+r,-r, -1,0,0, c, tex0,0), - // x+ - video::S3DVertex(x1,-r,-r, +1,0,0, c, tex0,1), - video::S3DVertex(x1,+r,-r, +1,0,0, c, tex0,0), - video::S3DVertex(x1,+r,+r, +1,0,0, c, tex1,0), - video::S3DVertex(x1,-r,+r, +1,0,0, c, tex1,1), + // x- + video::S3DVertex(x0, -r, -r, -1, 0, 0, c, tex0, 1), + video::S3DVertex(x0, -r, +r, -1, 0, 0, c, tex1, 1), + video::S3DVertex(x0, +r, +r, -1, 0, 0, c, tex1, 0), + video::S3DVertex(x0, +r, -r, -1, 0, 0, c, tex0, 0), + // x+ + video::S3DVertex(x1, -r, -r, +1, 0, 0, c, tex0, 1), + video::S3DVertex(x1, +r, -r, +1, 0, 0, c, tex0, 0), + video::S3DVertex(x1, +r, +r, +1, 0, 0, c, tex1, 0), + video::S3DVertex(x1, -r, +r, +1, 0, 0, c, tex1, 1), }; - u16 indices[12] = {0,1,2,2,3,0,4,5,6,6,7,4}; + u16 indices[12] = {0, 1, 2, 2, 3, 0, 4, 5, 6, 6, 7, 4}; buf->append(vertices, 8, indices, 12); } for (int i = 0; i < resolution_y; ++i) { @@ -97,18 +97,18 @@ static scene::IMesh *createExtrusionMesh(int resolution_x, int resolution_y) f32 tex0 = (i + 0.1) * pixelsize_y; f32 tex1 = (i + 0.9) * pixelsize_y; video::S3DVertex vertices[8] = { - // y- - video::S3DVertex(-r,y0,-r, 0,-1,0, c, 0,tex0), - video::S3DVertex(+r,y0,-r, 0,-1,0, c, 1,tex0), - video::S3DVertex(+r,y0,+r, 0,-1,0, c, 1,tex1), - video::S3DVertex(-r,y0,+r, 0,-1,0, c, 0,tex1), - // y+ - video::S3DVertex(-r,y1,-r, 0,+1,0, c, 0,tex0), - video::S3DVertex(-r,y1,+r, 0,+1,0, c, 0,tex1), - video::S3DVertex(+r,y1,+r, 0,+1,0, c, 1,tex1), - video::S3DVertex(+r,y1,-r, 0,+1,0, c, 1,tex0), + // y- + video::S3DVertex(-r, y0, -r, 0, -1, 0, c, 0, tex0), + video::S3DVertex(+r, y0, -r, 0, -1, 0, c, 1, tex0), + video::S3DVertex(+r, y0, +r, 0, -1, 0, c, 1, tex1), + video::S3DVertex(-r, y0, +r, 0, -1, 0, c, 0, tex1), + // y+ + video::S3DVertex(-r, y1, -r, 0, +1, 0, c, 0, tex0), + video::S3DVertex(-r, y1, +r, 0, +1, 0, c, 0, tex1), + video::S3DVertex(+r, y1, +r, 0, +1, 0, c, 1, tex1), + video::S3DVertex(+r, y1, -r, 0, +1, 0, c, 1, tex0), }; - u16 indices[12] = {0,1,2,2,3,0,4,5,6,6,7,4}; + u16 indices[12] = {0, 1, 2, 2, 3, 0, 4, 5, 6, 6, 7, 4}; buf->append(vertices, 8, indices, 12); } @@ -116,7 +116,7 @@ static scene::IMesh *createExtrusionMesh(int resolution_x, int resolution_y) scene::SMesh *mesh = new scene::SMesh(); mesh->addMeshBuffer(buf); buf->drop(); - scaleMesh(mesh, scale); // also recalculates bounding box + scaleMesh(mesh, scale); // also recalculates bounding box return mesh; } @@ -131,7 +131,7 @@ static scene::IMesh *createExtrusionMesh(int resolution_x, int resolution_y) rendering related classes (such as WieldMeshSceneNode) will be used from the rendering thread only. */ -class ExtrusionMeshCache: public IReferenceCounted +class ExtrusionMeshCache : public IReferenceCounted { public: // Constructor @@ -141,7 +141,7 @@ public: resolution <= MAX_EXTRUSION_MESH_RESOLUTION; resolution *= 2) { m_extrusion_meshes[resolution] = - createExtrusionMesh(resolution, resolution); + createExtrusionMesh(resolution, resolution); } m_cube = createCubeMesh(v3f(1.0, 1.0, 1.0)); } @@ -155,7 +155,7 @@ public: } // Get closest extrusion mesh for given image dimensions // Caller must drop the returned pointer - scene::IMesh* create(core::dimension2d dim) + scene::IMesh *create(core::dimension2d dim) { // handle non-power of two textures inefficiently without cache if (!is_power_of_two(dim.Width) || !is_power_of_two(dim.Height)) { @@ -164,8 +164,8 @@ public: int maxdim = MYMAX(dim.Width, dim.Height); - std::map::iterator - it = m_extrusion_meshes.lower_bound(maxdim); + std::map::iterator it = + m_extrusion_meshes.lower_bound(maxdim); if (it == m_extrusion_meshes.end()) { // no viable resolution found; use largest one @@ -179,24 +179,23 @@ public: } // Returns a 1x1x1 cube mesh with one meshbuffer (material) per face // Caller must drop the returned pointer - scene::IMesh* createCube() + scene::IMesh *createCube() { m_cube->grab(); return m_cube; } private: - std::map m_extrusion_meshes; + std::map m_extrusion_meshes; scene::IMesh *m_cube; }; ExtrusionMeshCache *g_extrusion_mesh_cache = NULL; - -WieldMeshSceneNode::WieldMeshSceneNode(scene::ISceneManager *mgr, s32 id, bool lighting): - scene::ISceneNode(mgr->getRootSceneNode(), mgr, id), - m_material_type(video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF), - m_lighting(lighting) +WieldMeshSceneNode::WieldMeshSceneNode(scene::ISceneManager *mgr, s32 id, bool lighting) : + scene::ISceneNode(mgr->getRootSceneNode(), mgr, id), + m_material_type(video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF), + m_lighting(lighting) { m_enable_shaders = g_settings->getBool("enable_shaders"); m_anisotropic_filter = g_settings->getBool("anisotropic_filter"); @@ -229,8 +228,7 @@ WieldMeshSceneNode::~WieldMeshSceneNode() g_extrusion_mesh_cache = nullptr; } -void WieldMeshSceneNode::setCube(const ContentFeatures &f, - v3f wield_scale) +void WieldMeshSceneNode::setCube(const ContentFeatures &f, v3f wield_scale) { scene::IMesh *cubemesh = g_extrusion_mesh_cache->createCube(); scene::SMesh *copy = cloneMesh(cubemesh); @@ -242,8 +240,8 @@ void WieldMeshSceneNode::setCube(const ContentFeatures &f, } void WieldMeshSceneNode::setExtruded(const std::string &imagename, - const std::string &overlay_name, v3f wield_scale, ITextureSource *tsrc, - u8 num_frames) + const std::string &overlay_name, v3f wield_scale, ITextureSource *tsrc, + u8 num_frames) { video::ITexture *texture = tsrc->getTexture(imagename); if (!texture) { @@ -251,7 +249,7 @@ void WieldMeshSceneNode::setExtruded(const std::string &imagename, return; } video::ITexture *overlay_texture = - overlay_name.empty() ? NULL : tsrc->getTexture(overlay_name); + overlay_name.empty() ? NULL : tsrc->getTexture(overlay_name); core::dimension2d dim = texture->getSize(); // Detect animation texture and pull off top frame instead of using entire thing @@ -262,9 +260,8 @@ void WieldMeshSceneNode::setExtruded(const std::string &imagename, scene::IMesh *original = g_extrusion_mesh_cache->create(dim); scene::SMesh *mesh = cloneMesh(original); original->drop(); - //set texture - mesh->getMeshBuffer(0)->getMaterial().setTexture(0, - tsrc->getTexture(imagename)); + // set texture + mesh->getMeshBuffer(0)->getMaterial().setTexture(0, tsrc->getTexture(imagename)); if (overlay_texture) { scene::IMeshBuffer *copy = cloneMeshBuffer(mesh->getMeshBuffer(0)); copy->getMaterial().setTexture(0, overlay_texture); @@ -294,7 +291,8 @@ void WieldMeshSceneNode::setExtruded(const std::string &imagename, } material.setFlag(video::EMF_ANISOTROPIC_FILTER, m_anisotropic_filter); // mipmaps cause "thin black line" artifacts -#if (IRRLICHT_VERSION_MAJOR >= 1 && IRRLICHT_VERSION_MINOR >= 8) || IRRLICHT_VERSION_MAJOR >= 2 +#if (IRRLICHT_VERSION_MAJOR >= 1 && IRRLICHT_VERSION_MINOR >= 8) || \ + IRRLICHT_VERSION_MAJOR >= 2 material.setFlag(video::EMF_USE_MIP_MAPS, false); #endif if (m_enable_shaders) { @@ -303,7 +301,8 @@ void WieldMeshSceneNode::setExtruded(const std::string &imagename, } } -scene::SMesh *createSpecialNodeMesh(Client *client, content_t id, std::vector *colors) +scene::SMesh *createSpecialNodeMesh( + Client *client, content_t id, std::vector *colors) { MeshMakeData mesh_make_data(client, false, false); MeshCollector collector; @@ -325,16 +324,17 @@ scene::SMesh *createSpecialNodeMesh(Client *client, content_t id, std::vectorMaterial.setTexture(0, p.layer.texture); p.layer.applyMaterialOptions(buf->Material); mesh->addMeshBuffer(buf); - buf->append(&p.vertices[0], p.vertices.size(), - &p.indices[0], p.indices.size()); + buf->append(&p.vertices[0], p.vertices.size(), &p.indices[0], + p.indices.size()); buf->drop(); colors->push_back( - ItemPartColor(p.layer.has_color, p.layer.color)); + ItemPartColor(p.layer.has_color, p.layer.color)); } return mesh; } -void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool check_wield_image) +void WieldMeshSceneNode::setItem( + const ItemStack &item, Client *client, bool check_wield_image) { ITextureSource *tsrc = client->getTextureSource(); IItemDefManager *idef = client->getItemDefManager(); @@ -347,7 +347,8 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che scene::SMesh *mesh = nullptr; if (m_enable_shaders) { - u32 shader_id = shdrsrc->getShader("object_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; } @@ -357,8 +358,7 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che // If wield_image needs to be checked and is defined, it overrides everything else if (!def.wield_image.empty() && check_wield_image) { - setExtruded(def.wield_image, def.wield_overlay, def.wield_scale, tsrc, - 1); + setExtruded(def.wield_image, def.wield_overlay, def.wield_scale, tsrc, 1); m_colors.emplace_back(); // overlay is white, if present m_colors.emplace_back(true, video::SColor(0xFFFFFFFF)); @@ -372,55 +372,64 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che // e.g. mesh nodes and nodeboxes mesh = cloneMesh(f.mesh_ptr[0]); postProcessNodeMesh(mesh, f, m_enable_shaders, true, - &m_material_type, &m_colors); + &m_material_type, &m_colors); changeToMesh(mesh); mesh->drop(); // mesh is pre-scaled by BS * f->visual_scale - m_meshnode->setScale( - def.wield_scale * WIELD_SCALE_FACTOR - / (BS * f.visual_scale)); + m_meshnode->setScale(def.wield_scale * WIELD_SCALE_FACTOR / + (BS * f.visual_scale)); } else { switch (f.drawtype) { - case NDT_AIRLIKE: { - changeToMesh(nullptr); - break; - } - case NDT_PLANTLIKE: { - setExtruded(tsrc->getTextureName(f.tiles[0].layers[0].texture_id), - tsrc->getTextureName(f.tiles[0].layers[1].texture_id), + case NDT_AIRLIKE: { + changeToMesh(nullptr); + break; + } + case NDT_PLANTLIKE: { + setExtruded(tsrc->getTextureName( + f.tiles[0].layers[0] + .texture_id), + tsrc->getTextureName( + f.tiles[0].layers[1] + .texture_id), def.wield_scale, tsrc, - f.tiles[0].layers[0].animation_frame_count); - // Add color - const TileLayer &l0 = f.tiles[0].layers[0]; - m_colors.emplace_back(l0.has_color, l0.color); - const TileLayer &l1 = f.tiles[0].layers[1]; - m_colors.emplace_back(l1.has_color, l1.color); - break; - } - case NDT_PLANTLIKE_ROOTED: { - setExtruded(tsrc->getTextureName(f.special_tiles[0].layers[0].texture_id), + f.tiles[0].layers[0] + .animation_frame_count); + // Add color + const TileLayer &l0 = f.tiles[0].layers[0]; + m_colors.emplace_back(l0.has_color, l0.color); + const TileLayer &l1 = f.tiles[0].layers[1]; + m_colors.emplace_back(l1.has_color, l1.color); + break; + } + case NDT_PLANTLIKE_ROOTED: { + setExtruded(tsrc->getTextureName( + f.special_tiles[0] + .layers[0] + .texture_id), "", def.wield_scale, tsrc, - f.special_tiles[0].layers[0].animation_frame_count); - // Add color - const TileLayer &l0 = f.special_tiles[0].layers[0]; - m_colors.emplace_back(l0.has_color, l0.color); - break; - } - case NDT_NORMAL: - case NDT_ALLFACES: - case NDT_LIQUID: - case NDT_FLOWINGLIQUID: { - setCube(f, def.wield_scale); - break; - } - default: { - mesh = createSpecialNodeMesh(client, id, &m_colors); - changeToMesh(mesh); - mesh->drop(); - m_meshnode->setScale( - def.wield_scale * WIELD_SCALE_FACTOR - / (BS * f.visual_scale)); - } + f.special_tiles[0] + .layers[0] + .animation_frame_count); + // Add color + const TileLayer &l0 = f.special_tiles[0].layers[0]; + m_colors.emplace_back(l0.has_color, l0.color); + break; + } + case NDT_NORMAL: + case NDT_ALLFACES: + case NDT_LIQUID: + case NDT_FLOWINGLIQUID: { + setCube(f, def.wield_scale); + break; + } + default: { + mesh = createSpecialNodeMesh(client, id, &m_colors); + changeToMesh(mesh); + mesh->drop(); + m_meshnode->setScale(def.wield_scale * + WIELD_SCALE_FACTOR / + (BS * f.visual_scale)); + } } } u32 material_count = m_meshnode->getMaterialCount(); @@ -433,10 +442,9 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che material.setFlag(video::EMF_TRILINEAR_FILTER, m_trilinear_filter); } return; - } - else if (!def.inventory_image.empty()) { + } else if (!def.inventory_image.empty()) { setExtruded(def.inventory_image, def.inventory_overlay, def.wield_scale, - tsrc, 1); + tsrc, 1); m_colors.emplace_back(); // overlay is white, if present m_colors.emplace_back(true, video::SColor(0xFFFFFFFF)); @@ -462,10 +470,8 @@ void WieldMeshSceneNode::setColor(video::SColor c) video::SColor bc(m_base_color); if ((m_colors.size() > j) && (m_colors[j].override_base)) bc = m_colors[j].color; - video::SColor buffercolor(255, - bc.getRed() * red / 255, - bc.getGreen() * green / 255, - bc.getBlue() * blue / 255); + video::SColor buffercolor(255, bc.getRed() * red / 255, + bc.getGreen() * green / 255, bc.getBlue() * blue / 255); scene::IMeshBuffer *buf = mesh->getMeshBuffer(j); if (m_enable_shaders) @@ -502,7 +508,7 @@ void WieldMeshSceneNode::changeToMesh(scene::IMesh *mesh) scene::IMesh *dummymesh = g_extrusion_mesh_cache->createCube(); m_meshnode->setVisible(false); m_meshnode->setMesh(dummymesh); - dummymesh->drop(); // m_meshnode grabbed it + dummymesh->drop(); // m_meshnode grabbed it } else { m_meshnode->setMesh(mesh); } @@ -522,8 +528,9 @@ void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result) const ContentFeatures &f = ndef->get(def.name); content_t id = ndef->getId(def.name); - FATAL_ERROR_IF(!g_extrusion_mesh_cache, "Extrusion mesh cache is not yet initialized"); - + FATAL_ERROR_IF(!g_extrusion_mesh_cache, + "Extrusion mesh cache is not yet initialized"); + scene::SMesh *mesh = nullptr; // Shading is on by default @@ -531,8 +538,7 @@ void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result) // If inventory_image is defined, it overrides everything else if (!def.inventory_image.empty()) { - mesh = getExtrudedMesh(tsrc, def.inventory_image, - def.inventory_overlay); + mesh = getExtrudedMesh(tsrc, def.inventory_image, def.inventory_overlay); result->buffer_colors.emplace_back(); // overlay is white, if present result->buffer_colors.emplace_back(true, video::SColor(0xFFFFFFFF)); @@ -543,45 +549,57 @@ void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result) mesh = cloneMesh(f.mesh_ptr[0]); scaleMesh(mesh, v3f(0.12, 0.12, 0.12)); postProcessNodeMesh(mesh, f, false, false, nullptr, - &result->buffer_colors); + &result->buffer_colors); } else { switch (f.drawtype) { - case NDT_PLANTLIKE: { - mesh = getExtrudedMesh(tsrc, - tsrc->getTextureName(f.tiles[0].layers[0].texture_id), - tsrc->getTextureName(f.tiles[0].layers[1].texture_id)); - // Add color - const TileLayer &l0 = f.tiles[0].layers[0]; - result->buffer_colors.emplace_back(l0.has_color, l0.color); - const TileLayer &l1 = f.tiles[0].layers[1]; - result->buffer_colors.emplace_back(l1.has_color, l1.color); - break; - } - case NDT_PLANTLIKE_ROOTED: { - mesh = getExtrudedMesh(tsrc, - tsrc->getTextureName(f.special_tiles[0].layers[0].texture_id), ""); - // Add color - const TileLayer &l0 = f.special_tiles[0].layers[0]; - result->buffer_colors.emplace_back(l0.has_color, l0.color); - break; - } - case NDT_NORMAL: - case NDT_ALLFACES: - case NDT_LIQUID: - case NDT_FLOWINGLIQUID: { - scene::IMesh *cube = g_extrusion_mesh_cache->createCube(); - mesh = cloneMesh(cube); - cube->drop(); - scaleMesh(mesh, v3f(1.2, 1.2, 1.2)); - // add overlays - postProcessNodeMesh(mesh, f, false, false, nullptr, + case NDT_PLANTLIKE: { + mesh = getExtrudedMesh(tsrc, + tsrc->getTextureName( + f.tiles[0].layers[0] + .texture_id), + tsrc->getTextureName( + f.tiles[0].layers[1] + .texture_id)); + // Add color + const TileLayer &l0 = f.tiles[0].layers[0]; + result->buffer_colors.emplace_back( + l0.has_color, l0.color); + const TileLayer &l1 = f.tiles[0].layers[1]; + result->buffer_colors.emplace_back( + l1.has_color, l1.color); + break; + } + case NDT_PLANTLIKE_ROOTED: { + mesh = getExtrudedMesh(tsrc, + tsrc->getTextureName( + f.special_tiles[0] + .layers[0] + .texture_id), + ""); + // Add color + const TileLayer &l0 = f.special_tiles[0].layers[0]; + result->buffer_colors.emplace_back( + l0.has_color, l0.color); + break; + } + case NDT_NORMAL: + case NDT_ALLFACES: + case NDT_LIQUID: + case NDT_FLOWINGLIQUID: { + scene::IMesh *cube = g_extrusion_mesh_cache->createCube(); + mesh = cloneMesh(cube); + cube->drop(); + scaleMesh(mesh, v3f(1.2, 1.2, 1.2)); + // add overlays + postProcessNodeMesh(mesh, f, false, false, nullptr, &result->buffer_colors); - break; - } - default: { - mesh = createSpecialNodeMesh(client, id, &result->buffer_colors); - scaleMesh(mesh, v3f(0.12, 0.12, 0.12)); - } + break; + } + default: { + mesh = createSpecialNodeMesh( + client, id, &result->buffer_colors); + scaleMesh(mesh, v3f(0.12, 0.12, 0.12)); + } } } @@ -603,10 +621,8 @@ void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result) result->mesh = mesh; } - - -scene::SMesh *getExtrudedMesh(ITextureSource *tsrc, - const std::string &imagename, const std::string &overlay_name) +scene::SMesh *getExtrudedMesh(ITextureSource *tsrc, const std::string &imagename, + const std::string &overlay_name) { // check textures video::ITexture *texture = tsrc->getTextureForMesh(imagename); @@ -614,7 +630,7 @@ scene::SMesh *getExtrudedMesh(ITextureSource *tsrc, return NULL; } video::ITexture *overlay_texture = - (overlay_name.empty()) ? NULL : tsrc->getTexture(overlay_name); + (overlay_name.empty()) ? NULL : tsrc->getTexture(overlay_name); // get mesh core::dimension2d dim = texture->getSize(); @@ -622,9 +638,8 @@ scene::SMesh *getExtrudedMesh(ITextureSource *tsrc, scene::SMesh *mesh = cloneMesh(original); original->drop(); - //set texture - mesh->getMeshBuffer(0)->getMaterial().setTexture(0, - tsrc->getTexture(imagename)); + // set texture + mesh->getMeshBuffer(0)->getMaterial().setTexture(0, tsrc->getTexture(imagename)); if (overlay_texture) { scene::IMeshBuffer *copy = cloneMeshBuffer(mesh->getMeshBuffer(0)); copy->getMaterial().setTexture(0, overlay_texture); @@ -648,9 +663,9 @@ scene::SMesh *getExtrudedMesh(ITextureSource *tsrc, return mesh; } -void postProcessNodeMesh(scene::SMesh *mesh, const ContentFeatures &f, - bool use_shaders, bool set_material, const video::E_MATERIAL_TYPE *mattype, - std::vector *colors, bool apply_scale) +void postProcessNodeMesh(scene::SMesh *mesh, const ContentFeatures &f, bool use_shaders, + bool set_material, const video::E_MATERIAL_TYPE *mattype, + std::vector *colors, bool apply_scale) { u32 mc = mesh->getMeshBufferCount(); // Allocate colors for existing buffers @@ -671,10 +686,11 @@ void postProcessNodeMesh(scene::SMesh *mesh, const ContentFeatures &f, mesh->addMeshBuffer(copy); copy->drop(); buf = copy; - colors->push_back( - ItemPartColor(layer->has_color, layer->color)); + colors->push_back(ItemPartColor( + layer->has_color, layer->color)); } else { - (*colors)[i] = ItemPartColor(layer->has_color, layer->color); + (*colors)[i] = ItemPartColor( + layer->has_color, layer->color); } video::SMaterial &material = buf->getMaterial(); if (set_material) @@ -691,10 +707,13 @@ void postProcessNodeMesh(scene::SMesh *mesh, const ContentFeatures &f, if (use_shaders) { if (layer->normal_texture) { if (layer->animation_frame_count > 1) { - const FrameSpec &animation_frame = (*layer->frames)[0]; - material.setTexture(1, animation_frame.normal_texture); + const FrameSpec &animation_frame = + (*layer->frames)[0]; + material.setTexture(1, + animation_frame.normal_texture); } else - material.setTexture(1, layer->normal_texture); + material.setTexture( + 1, layer->normal_texture); } material.setTexture(2, layer->flags_texture); } diff --git a/src/clientiface.cpp b/src/clientiface.cpp index 602a44c90..9effa7eb7 100644 --- a/src/clientiface.cpp +++ b/src/clientiface.cpp @@ -34,33 +34,33 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "face_position_cache.h" const char *ClientInterface::statenames[] = { - "Invalid", - "Disconnecting", - "Denied", - "Created", - "AwaitingInit2", - "HelloSent", - "InitDone", - "DefinitionsSent", - "Active", - "SudoMode", + "Invalid", + "Disconnecting", + "Denied", + "Created", + "AwaitingInit2", + "HelloSent", + "InitDone", + "DefinitionsSent", + "Active", + "SudoMode", }; - - std::string ClientInterface::state2Name(ClientState state) { return statenames[state]; } RemoteClient::RemoteClient() : - m_max_simul_sends(g_settings->getU16("max_simultaneous_block_sends_per_client")), - m_min_time_from_building( - g_settings->getFloat("full_block_send_enable_min_time_from_building")), - m_max_send_distance(g_settings->getS16("max_block_send_distance")), - m_block_optimize_distance(g_settings->getS16("block_send_optimize_distance")), - m_max_gen_distance(g_settings->getS16("max_block_generate_distance")), - m_occ_cull(g_settings->getBool("server_side_occlusion_culling")) + m_max_simul_sends(g_settings->getU16( + "max_simultaneous_block_sends_per_client")), + m_min_time_from_building(g_settings->getFloat( + "full_block_send_enable_min_time_from_building")), + m_max_send_distance(g_settings->getS16("max_block_send_distance")), + m_block_optimize_distance( + g_settings->getS16("block_send_optimize_distance")), + m_max_gen_distance(g_settings->getS16("max_block_generate_distance")), + m_occ_cull(g_settings->getBool("server_side_occlusion_culling")) { } @@ -90,11 +90,8 @@ LuaEntitySAO *getAttachedObject(PlayerSAO *sao, ServerEnvironment *env) return dynamic_cast(ao); } -void RemoteClient::GetNextBlocks ( - ServerEnvironment *env, - EmergeManager * emerge, - float dtime, - std::vector &dest) +void RemoteClient::GetNextBlocks(ServerEnvironment *env, EmergeManager *emerge, + float dtime, std::vector &dest) { // Increment timers m_nothing_to_send_pause_timer -= dtime; @@ -114,15 +111,15 @@ void RemoteClient::GetNextBlocks ( // Won't send anything if already sending if (m_blocks_sending.size() >= m_max_simul_sends) { - //infostream<<"Not sending any blocks, Queue full."<getBasePosition(); // if the player is attached, get the velocity from the attached object LuaEntitySAO *lsao = getAttachedObject(sao, env); - const v3f &playerspeed = lsao? lsao->getVelocity() : player->getSpeed(); - v3f playerspeeddir(0,0,0); + const v3f &playerspeed = lsao ? lsao->getVelocity() : player->getSpeed(); + v3f playerspeeddir(0, 0, 0); if (playerspeed.getLength() > 1.0f * BS) playerspeeddir = playerspeed / playerspeed.getLength(); // Predict to next block @@ -134,7 +131,7 @@ void RemoteClient::GetNextBlocks ( // Camera position and direction v3f camera_pos = sao->getEyePosition(); - v3f camera_dir = v3f(0,0,1); + v3f camera_dir = v3f(0, 0, 1); camera_dir.rotateYZBy(sao->getLookPitch()); camera_dir.rotateXZBy(sao->getRotation().Y); @@ -157,14 +154,14 @@ void RemoteClient::GetNextBlocks ( if (m_nearest_unsent_reset_timer > 20.0f) { m_nearest_unsent_reset_timer = 0.0f; m_nearest_unsent_d = 0; - //infostream<<"Resetting m_nearest_unsent_d for " + // infostream<<"Resetting m_nearest_unsent_d for " // <getPlayerName(peer_id)< &blocks) +void RemoteClient::SetBlocksNotSent(std::map &blocks) { m_nearest_unsent_d = 0; m_nothing_to_send_pause_timer = 0; @@ -461,10 +468,9 @@ void RemoteClient::SetBlocksNotSent(std::map &blocks) void RemoteClient::notifyEvent(ClientStateEvent event) { std::ostringstream myerror; - switch (m_state) - { + switch (m_state) { case CS_Invalid: - //intentionally do nothing + // intentionally do nothing break; case CS_Created: switch (event) { @@ -490,13 +496,12 @@ void RemoteClient::notifyEvent(ClientStateEvent event) /* don't do anything if in denied state */ break; case CS_HelloSent: - switch(event) - { + switch (event) { case CSE_AuthAccept: m_state = CS_AwaitingInit2; if (chosen_mech == AUTH_MECHANISM_SRP || chosen_mech == AUTH_MECHANISM_LEGACY_PASSWORD) - srp_verifier_delete((SRPVerifier *) auth_data); + srp_verifier_delete((SRPVerifier *)auth_data); chosen_mech = AUTH_MECHANISM_NONE; break; case CSE_Disconnect: @@ -506,17 +511,17 @@ void RemoteClient::notifyEvent(ClientStateEvent event) m_state = CS_Denied; if (chosen_mech == AUTH_MECHANISM_SRP || chosen_mech == AUTH_MECHANISM_LEGACY_PASSWORD) - srp_verifier_delete((SRPVerifier *) auth_data); + srp_verifier_delete((SRPVerifier *)auth_data); chosen_mech = AUTH_MECHANISM_NONE; break; default: - myerror << "HelloSent: Invalid client state transition! " << event; + myerror << "HelloSent: Invalid client state transition! " + << event; throw ClientStateError(myerror.str()); } break; case CS_AwaitingInit2: - switch(event) - { + switch (event) { case CSE_GotInit2: confirmSerializationVersion(); m_state = CS_InitDone; @@ -536,8 +541,7 @@ void RemoteClient::notifyEvent(ClientStateEvent event) break; case CS_InitDone: - switch(event) - { + switch (event) { case CSE_SetDefinitionsSent: m_state = CS_DefinitionsSent; break; @@ -555,8 +559,7 @@ void RemoteClient::notifyEvent(ClientStateEvent event) } break; case CS_DefinitionsSent: - switch(event) - { + switch (event) { case CSE_SetClientReady: m_state = CS_Active; break; @@ -568,13 +571,13 @@ void RemoteClient::notifyEvent(ClientStateEvent event) break; /* Init GotInit2 SetDefinitionsSent */ default: - myerror << "DefinitionsSent: Invalid client state transition! " << event; + myerror << "DefinitionsSent: Invalid client state transition! " + << event; throw ClientStateError(myerror.str()); } break; case CS_Active: - switch(event) - { + switch (event) { case CSE_SetDenied: m_state = CS_Denied; break; @@ -584,7 +587,7 @@ void RemoteClient::notifyEvent(ClientStateEvent event) case CSE_SudoSuccess: m_state = CS_SudoMode; if (chosen_mech == AUTH_MECHANISM_SRP) - srp_verifier_delete((SRPVerifier *) auth_data); + srp_verifier_delete((SRPVerifier *)auth_data); chosen_mech = AUTH_MECHANISM_NONE; break; /* Init GotInit2 SetDefinitionsSent SetMediaSent SetDenied */ @@ -595,8 +598,7 @@ void RemoteClient::notifyEvent(ClientStateEvent event) } break; case CS_SudoMode: - switch(event) - { + switch (event) { case CSE_SetDenied: m_state = CS_Denied; break; @@ -623,13 +625,9 @@ u64 RemoteClient::uptime() const return porting::getTimeS() - m_connection_time; } -ClientInterface::ClientInterface(const std::shared_ptr & con) -: - m_con(con), - m_env(NULL), - m_print_info_timer(0.0f) +ClientInterface::ClientInterface(const std::shared_ptr &con) : + m_con(con), m_env(NULL), m_print_info_timer(0.0f) { - } ClientInterface::~ClientInterface() { @@ -693,9 +691,8 @@ void ClientInterface::UpdatePlayerList() std::vector clients = getClientIDs(); m_clients_names.clear(); - if (!clients.empty()) - infostream<<"Players:"<getPlayer(i); @@ -707,7 +704,7 @@ void ClientInterface::UpdatePlayerList() { RecursiveMutexAutoLock clientslock(m_clients_mutex); - RemoteClient* client = lockedGetClientNoEx(i); + RemoteClient *client = lockedGetClientNoEx(i); if (client) client->PrintInfo(infostream); } @@ -717,8 +714,8 @@ void ClientInterface::UpdatePlayerList() } } -void ClientInterface::send(session_t peer_id, u8 channelnum, - NetworkPacket *pkt, bool reliable) +void ClientInterface::send( + session_t peer_id, u8 channelnum, NetworkPacket *pkt, bool reliable) { m_con->Send(peer_id, channelnum, pkt, reliable); } @@ -731,14 +728,17 @@ void ClientInterface::sendToAll(NetworkPacket *pkt) if (client->net_proto_version != 0) { m_con->Send(client->peer_id, - clientCommandFactoryTable[pkt->getCommand()].channel, pkt, - clientCommandFactoryTable[pkt->getCommand()].reliable); + clientCommandFactoryTable[pkt->getCommand()] + .channel, + pkt, + clientCommandFactoryTable[pkt->getCommand()] + .reliable); } } } -void ClientInterface::sendToAllCompat(NetworkPacket *pkt, NetworkPacket *legacypkt, - u16 min_proto_ver) +void ClientInterface::sendToAllCompat( + NetworkPacket *pkt, NetworkPacket *legacypkt, u16 min_proto_ver) { RecursiveMutexAutoLock clientslock(m_clients_mutex); for (auto &client_it : m_clients) { @@ -751,18 +751,20 @@ void ClientInterface::sendToAllCompat(NetworkPacket *pkt, NetworkPacket *legacyp pkt_to_send = legacypkt; } else { warningstream << "Client with unhandled version to handle: '" - << client->net_proto_version << "'"; + << client->net_proto_version << "'"; continue; } m_con->Send(client->peer_id, - clientCommandFactoryTable[pkt_to_send->getCommand()].channel, - pkt_to_send, - clientCommandFactoryTable[pkt_to_send->getCommand()].reliable); + clientCommandFactoryTable[pkt_to_send->getCommand()] + .channel, + pkt_to_send, + clientCommandFactoryTable[pkt_to_send->getCommand()] + .reliable); } } -RemoteClient* ClientInterface::getClientNoEx(session_t peer_id, ClientState state_min) +RemoteClient *ClientInterface::getClientNoEx(session_t peer_id, ClientState state_min) { RecursiveMutexAutoLock clientslock(m_clients_mutex); RemoteClientMap::const_iterator n = m_clients.find(peer_id); @@ -777,7 +779,8 @@ RemoteClient* ClientInterface::getClientNoEx(session_t peer_id, ClientState stat return NULL; } -RemoteClient* ClientInterface::lockedGetClientNoEx(session_t peer_id, ClientState state_min) +RemoteClient *ClientInterface::lockedGetClientNoEx( + session_t peer_id, ClientState state_min) { RemoteClientMap::const_iterator n = m_clients.find(peer_id); // The client may not exist; clients are immediately removed if their @@ -827,14 +830,14 @@ void ClientInterface::DeleteClient(session_t peer_id) /* Mark objects to be not known by the client */ - //TODO this should be done by client destructor!!! + // TODO this should be done by client destructor!!! RemoteClient *client = n->second; // Handle objects for (u16 id : client->m_known_objects) { // Get object - ServerActiveObject* obj = m_env->getActiveObject(id); + ServerActiveObject *obj = m_env->getActiveObject(id); - if(obj && obj->m_known_by_count > 0) + if (obj && obj->m_known_by_count > 0) obj->m_known_by_count--; } @@ -850,7 +853,8 @@ void ClientInterface::CreateClient(session_t peer_id) // Error check RemoteClientMap::iterator n = m_clients.find(peer_id); // The client shouldn't already exist - if (n != m_clients.end()) return; + if (n != m_clients.end()) + return; // Create client RemoteClient *client = new RemoteClient(); @@ -872,10 +876,8 @@ void ClientInterface::event(session_t peer_id, ClientStateEvent event) n->second->notifyEvent(event); } - if ((event == CSE_SetClientReady) || - (event == CSE_Disconnect) || - (event == CSE_SetDenied)) - { + if ((event == CSE_SetClientReady) || (event == CSE_Disconnect) || + (event == CSE_SetDenied)) { UpdatePlayerList(); } } @@ -894,8 +896,8 @@ u16 ClientInterface::getProtocolVersion(session_t peer_id) return n->second->net_proto_version; } -void ClientInterface::setClientVersion(session_t peer_id, u8 major, u8 minor, u8 patch, - const std::string &full) +void ClientInterface::setClientVersion( + session_t peer_id, u8 major, u8 minor, u8 patch, const std::string &full) { RecursiveMutexAutoLock conlock(m_clients_mutex); diff --git a/src/clientiface.h b/src/clientiface.h index 83fa6fe99..e629066c4 100644 --- a/src/clientiface.h +++ b/src/clientiface.h @@ -19,10 +19,10 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once -#include "irr_v3d.h" // for irrlicht datatypes +#include "irr_v3d.h" // for irrlicht datatypes #include "constants.h" -#include "serialization.h" // for SER_FMT_VER_INVALID +#include "serialization.h" // for SER_FMT_VER_INVALID #include "network/networkpacket.h" #include "network/networkprotocol.h" #include "porting.h" @@ -41,49 +41,49 @@ class EmergeManager; Start (peer connect) - | - v + | + v /-----------------\ | | | Created | | | \-----------------/ - | depending of the incoming packet - ---------------------------------------- - v - +-----------------------------+ - |IN: | - | TOSERVER_INIT | - +-----------------------------+ - | invalid playername - | or denied by mod - v - +-----------------------------+ - |OUT: | - | TOCLIENT_HELLO | - +-----------------------------+ - | - | - v + | depending of the incoming packet + ---------------------------------------- + v + +-----------------------------+ + |IN: | + | TOSERVER_INIT | + +-----------------------------+ + | invalid playername + | or denied by mod + v + +-----------------------------+ + |OUT: | + | TOCLIENT_HELLO | + +-----------------------------+ + | + | + v /-----------------\ /-----------------\ | | | | | AwaitingInit2 |<--------- | HelloSent | | | | | | \-----------------/ | \-----------------/ - | | | + | | | +-----------------------------+ | *-----------------------------* Auth fails |IN: | | |Authentication, depending on |------------------ | TOSERVER_INIT2 | | | packet sent by client | | +-----------------------------+ | *-----------------------------* | - | | | | - | | | Authentication | - v | | successful | + | | | | + | | | Authentication | + v | | successful | /-----------------\ | v | | | | +-----------------------------+ | | InitDone | | |OUT: | | | | | | TOCLIENT_AUTH_ACCEPT | | \-----------------/ | +-----------------------------+ | - | | | | + | | | | +-----------------------------+ --------------------- | |OUT: | | | TOCLIENT_MOVEMENT | | @@ -93,25 +93,25 @@ class EmergeManager; | TOCLIENT_DETACHED_INVENTORY | | | TOCLIENT_TIME_OF_DAY | | +-----------------------------+ | - | | - | | - | ----------------------------- | - v | | | + | | + | | + | ----------------------------- | + v | | | /-----------------\ v | | | +-----------------------------+ | | DefinitionsSent | |IN: | | | | | TOSERVER_REQUEST_MEDIA | | \-----------------/ | | | - | +-----------------------------+ | - | ^ | | - | ----------------------------- | - v v + | +-----------------------------+ | + | ^ | | + | ----------------------------- | + v v +-----------------------------+ --------------------------------+ |IN: | | ^ | TOSERVER_CLIENT_READY | v | +-----------------------------+ +------------------------+ | - | |OUT: | | - v | TOCLIENT_ACCESS_DENIED | | + | |OUT: | | + v | TOCLIENT_ACCESS_DENIED | | +-----------------------------+ +------------------------+ | |OUT: | | | | TOCLIENT_MOVE_PLAYER | v | @@ -123,8 +123,8 @@ class EmergeManager; | TOCLIENT_BREATH | | | TOCLIENT_DEATHSCREEN | | +-----------------------------+ | - | | - v | + | | + v | /-----------------\ async mod action (ban, kick) | | |--------------------------------------------------------------- ---->| Active | @@ -159,14 +159,14 @@ class EmergeManager; | | +-----------------------------+ | | sets password accordingly |IN: | -------------------+-------------------------------| TOSERVER_FIRST_SRP | - +-----------------------------+ + +-----------------------------+ */ -namespace con { - class Connection; +namespace con +{ +class Connection; } - // Also make sure to update the ClientInterface::statenames // array when modifying these enums @@ -205,13 +205,14 @@ enum ClientStateEvent */ struct PrioritySortedBlockTransfer { - PrioritySortedBlockTransfer(float a_priority, const v3s16 &a_pos, session_t a_peer_id) + PrioritySortedBlockTransfer( + float a_priority, const v3s16 &a_pos, session_t a_peer_id) { priority = a_priority; pos = a_pos; peer_id = a_peer_id; } - bool operator < (const PrioritySortedBlockTransfer &other) const + bool operator<(const PrioritySortedBlockTransfer &other) const { return priority < other.priority; } @@ -236,15 +237,13 @@ public: /* Authentication information */ std::string enc_pwd = ""; bool create_player_on_auth_success = false; - AuthMechanism chosen_mech = AUTH_MECHANISM_NONE; + AuthMechanism chosen_mech = AUTH_MECHANISM_NONE; void *auth_data = nullptr; u32 allowed_auth_mechs = 0; u32 allowed_sudo_mechs = 0; - bool isSudoMechAllowed(AuthMechanism mech) - { return allowed_sudo_mechs & mech; } - bool isMechAllowed(AuthMechanism mech) - { return allowed_auth_mechs & mech; } + bool isSudoMechAllowed(AuthMechanism mech) { return allowed_sudo_mechs & mech; } + bool isMechAllowed(AuthMechanism mech) { return allowed_auth_mechs & mech; } RemoteClient(); ~RemoteClient() = default; @@ -254,15 +253,15 @@ public: Environment should be locked when this is called. dtime is used for resetting send radius at slow interval */ - void GetNextBlocks(ServerEnvironment *env, EmergeManager* emerge, - float dtime, std::vector &dest); + void GetNextBlocks(ServerEnvironment *env, EmergeManager *emerge, float dtime, + std::vector &dest); void GotBlock(v3s16 p); void SentBlock(v3s16 p); void SetBlockNotSent(v3s16 p); - void SetBlocksNotSent(std::map &blocks); + void SetBlocksNotSent(std::map &blocks); /** * tell client about this block being modified right now. @@ -282,16 +281,15 @@ public: // Increments timeouts and removes timed-out blocks from list // NOTE: This doesn't fix the server-not-sending-block bug // because it is related to emerging, not sending. - //void RunSendingTimeouts(float dtime, float timeout); + // void RunSendingTimeouts(float dtime, float timeout); void PrintInfo(std::ostream &o) { - o<<"RemoteClient "< 0 && staticbox.MinEdge.Y - movingbox.MaxEdge.Y > inner_margin) || - (speed.Y < 0 && movingbox.MinEdge.Y - staticbox.MaxEdge.Y > inner_margin)) { - if ( - (std::max(movingbox.MaxEdge.X + speed.X * time, staticbox.MaxEdge.X) - - std::min(movingbox.MinEdge.X + speed.X * time, staticbox.MinEdge.X) - - relbox.MinEdge.X < 0) && - (std::max(movingbox.MaxEdge.Z + speed.Z * time, staticbox.MaxEdge.Z) - - std::min(movingbox.MinEdge.Z + speed.Z * time, staticbox.MinEdge.Z) - - relbox.MinEdge.Z < 0) - ) + if ((speed.Y > 0 && staticbox.MinEdge.Y - movingbox.MaxEdge.Y > + inner_margin) || + (speed.Y < 0 && movingbox.MinEdge.Y - staticbox.MaxEdge.Y > + inner_margin)) { + if ((std::max(movingbox.MaxEdge.X + speed.X * time, + staticbox.MaxEdge.X) - + std::min(movingbox.MinEdge.X + speed.X * time, + staticbox.MinEdge + .X) - + relbox.MinEdge.X < + 0) && + (std::max(movingbox.MaxEdge.Z + speed.Z * time, + staticbox.MaxEdge.Z) - + std::min(movingbox.MinEdge.Z + speed.Z * time, + staticbox.MinEdge + .Z) - + relbox.MinEdge.Z < + 0)) return COLLISION_AXIS_Y; } - } - else { + } else { return COLLISION_AXIS_NONE; } } @@ -142,18 +155,29 @@ CollisionAxis axisAlignedCollision( time = std::max(*dtime, 0.0f); if (*dtime <= dtime_max) { - inner_margin = std::max(-0.5f * (staticbox.MaxEdge.X - staticbox.MinEdge.X), -2.0f); + inner_margin = std::max( + -0.5f * (staticbox.MaxEdge.X - + staticbox.MinEdge.X), + -2.0f); - if ((speed.X > 0 && staticbox.MinEdge.X - movingbox.MaxEdge.X > inner_margin) || - (speed.X < 0 && movingbox.MinEdge.X - staticbox.MaxEdge.X > inner_margin)) { - if ( - (std::max(movingbox.MaxEdge.Y + speed.Y * time, staticbox.MaxEdge.Y) - - std::min(movingbox.MinEdge.Y + speed.Y * time, staticbox.MinEdge.Y) - - relbox.MinEdge.Y < 0) && - (std::max(movingbox.MaxEdge.Z + speed.Z * time, staticbox.MaxEdge.Z) - - std::min(movingbox.MinEdge.Z + speed.Z * time, staticbox.MinEdge.Z) - - relbox.MinEdge.Z < 0) - ) + if ((speed.X > 0 && staticbox.MinEdge.X - movingbox.MaxEdge.X > + inner_margin) || + (speed.X < 0 && movingbox.MinEdge.X - staticbox.MaxEdge.X > + inner_margin)) { + if ((std::max(movingbox.MaxEdge.Y + speed.Y * time, + staticbox.MaxEdge.Y) - + std::min(movingbox.MinEdge.Y + speed.Y * time, + staticbox.MinEdge + .Y) - + relbox.MinEdge.Y < + 0) && + (std::max(movingbox.MaxEdge.Z + speed.Z * time, + staticbox.MaxEdge.Z) - + std::min(movingbox.MinEdge.Z + speed.Z * time, + staticbox.MinEdge + .Z) - + relbox.MinEdge.Z < + 0)) return COLLISION_AXIS_X; } } else { @@ -169,18 +193,29 @@ CollisionAxis axisAlignedCollision( time = std::max(*dtime, 0.0f); if (*dtime <= dtime_max) { - inner_margin = std::max(-0.5f * (staticbox.MaxEdge.Z - staticbox.MinEdge.Z), -2.0f); + inner_margin = std::max( + -0.5f * (staticbox.MaxEdge.Z - + staticbox.MinEdge.Z), + -2.0f); - if ((speed.Z > 0 && staticbox.MinEdge.Z - movingbox.MaxEdge.Z > inner_margin) || - (speed.Z < 0 && movingbox.MinEdge.Z - staticbox.MaxEdge.Z > inner_margin)) { - if ( - (std::max(movingbox.MaxEdge.X + speed.X * time, staticbox.MaxEdge.X) - - std::min(movingbox.MinEdge.X + speed.X * time, staticbox.MinEdge.X) - - relbox.MinEdge.X < 0) && - (std::max(movingbox.MaxEdge.Y + speed.Y * time, staticbox.MaxEdge.Y) - - std::min(movingbox.MinEdge.Y + speed.Y * time, staticbox.MinEdge.Y) - - relbox.MinEdge.Y < 0) - ) + if ((speed.Z > 0 && staticbox.MinEdge.Z - movingbox.MaxEdge.Z > + inner_margin) || + (speed.Z < 0 && movingbox.MinEdge.Z - staticbox.MaxEdge.Z > + inner_margin)) { + if ((std::max(movingbox.MaxEdge.X + speed.X * time, + staticbox.MaxEdge.X) - + std::min(movingbox.MinEdge.X + speed.X * time, + staticbox.MinEdge + .X) - + relbox.MinEdge.X < + 0) && + (std::max(movingbox.MaxEdge.Y + speed.Y * time, + staticbox.MaxEdge.Y) - + std::min(movingbox.MinEdge.Y + speed.Y * time, + staticbox.MinEdge + .Y) - + relbox.MinEdge.Y < + 0)) return COLLISION_AXIS_Z; } } @@ -191,19 +226,18 @@ CollisionAxis axisAlignedCollision( // Helper function: // Checks if moving the movingbox up by the given distance would hit a ceiling. -bool wouldCollideWithCeiling( - const std::vector &cinfo, - const aabb3f &movingbox, - f32 y_increase, f32 d) +bool wouldCollideWithCeiling(const std::vector &cinfo, + const aabb3f &movingbox, f32 y_increase, f32 d) { - //TimeTaker tt("wouldCollideWithCeiling"); + // TimeTaker tt("wouldCollideWithCeiling"); - assert(y_increase >= 0); // pre-condition + assert(y_increase >= 0); // pre-condition for (const auto &it : cinfo) { const aabb3f &staticbox = it.box; if ((movingbox.MaxEdge.Y - d <= staticbox.MinEdge.Y) && - (movingbox.MaxEdge.Y + y_increase > staticbox.MinEdge.Y) && + (movingbox.MaxEdge.Y + y_increase > + staticbox.MinEdge.Y) && (movingbox.MinEdge.X < staticbox.MaxEdge.X) && (movingbox.MaxEdge.X > staticbox.MinEdge.X) && (movingbox.MinEdge.Z < staticbox.MaxEdge.Z) && @@ -215,7 +249,7 @@ bool wouldCollideWithCeiling( } static inline void getNeighborConnectingFace(const v3s16 &p, - const NodeDefManager *nodedef, Map *map, MapNode n, int v, int *neighbors) + const NodeDefManager *nodedef, Map *map, MapNode n, int v, int *neighbors) { MapNode n2 = map->getNode(p); if (nodedef->nodeboxConnects(n, n2, v)) @@ -223,11 +257,9 @@ static inline void getNeighborConnectingFace(const v3s16 &p, } collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef, - f32 pos_max_d, const aabb3f &box_0, - f32 stepheight, f32 dtime, - v3f *pos_f, v3f *speed_f, - v3f accel_f, ActiveObject *self, - bool collideWithObjects, bool jesus) + f32 pos_max_d, const aabb3f &box_0, f32 stepheight, f32 dtime, v3f *pos_f, + v3f *speed_f, v3f accel_f, ActiveObject *self, bool collideWithObjects, + bool jesus) { static bool time_notification_done = false; Map *map = &env->getMap(); @@ -242,8 +274,10 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef, if (dtime > 0.5f) { if (!time_notification_done) { time_notification_done = true; - infostream << "collisionMoveSimple: maximum step interval exceeded," - " lost movement details!"< cinfo; { - //TimeTaker tt2("collisionMoveSimple collect boxes"); - ScopeProfiler sp2(g_profiler, "collisionMoveSimple(): collect boxes", SPT_AVG); + // TimeTaker tt2("collisionMoveSimple collect boxes"); + ScopeProfiler sp2(g_profiler, "collisionMoveSimple(): collect boxes", + SPT_AVG); - v3f newpos_f = *pos_f + *speed_f * dtime; - v3f minpos_f( - MYMIN(pos_f->X, newpos_f.X), - MYMIN(pos_f->Y, newpos_f.Y) + 0.01f * BS, // bias rounding, player often at +/-n.5 - MYMIN(pos_f->Z, newpos_f.Z) - ); - v3f maxpos_f( - MYMAX(pos_f->X, newpos_f.X), - MYMAX(pos_f->Y, newpos_f.Y), - MYMAX(pos_f->Z, newpos_f.Z) - ); - v3s16 min = floatToInt(minpos_f + box_0.MinEdge, BS) - v3s16(1, 1, 1); - v3s16 max = floatToInt(maxpos_f + box_0.MaxEdge, BS) + v3s16(1, 1, 1); + v3f newpos_f = *pos_f + *speed_f * dtime; + v3f minpos_f(MYMIN(pos_f->X, newpos_f.X), + MYMIN(pos_f->Y, newpos_f.Y) + + 0.01f * BS, // bias rounding, player often + // at +/-n.5 + MYMIN(pos_f->Z, newpos_f.Z)); + v3f maxpos_f(MYMAX(pos_f->X, newpos_f.X), MYMAX(pos_f->Y, newpos_f.Y), + MYMAX(pos_f->Z, newpos_f.Z)); + v3s16 min = floatToInt(minpos_f + box_0.MinEdge, BS) - v3s16(1, 1, 1); + v3s16 max = floatToInt(maxpos_f + box_0.MaxEdge, BS) + v3s16(1, 1, 1); - bool any_position_valid = false; - jesus = jesus && g_settings->getBool("jesus"); + bool any_position_valid = false; + jesus = jesus && g_settings->getBool("jesus"); - v3s16 p; - for (p.X = min.X; p.X <= max.X; p.X++) - for (p.Y = min.Y; p.Y <= max.Y; p.Y++) - for (p.Z = min.Z; p.Z <= max.Z; p.Z++) { - bool is_position_valid; - MapNode n = map->getNode(p, &is_position_valid); + v3s16 p; + for (p.X = min.X; p.X <= max.X; p.X++) + for (p.Y = min.Y; p.Y <= max.Y; p.Y++) + for (p.Z = min.Z; p.Z <= max.Z; p.Z++) { + bool is_position_valid; + MapNode n = map->getNode(p, &is_position_valid); - if (is_position_valid && n.getContent() != CONTENT_IGNORE) { - // Object collides into walkable nodes + if (is_position_valid && + n.getContent() != + CONTENT_IGNORE) { + // Object collides into walkable nodes - any_position_valid = true; - const NodeDefManager *nodedef = gamedef->getNodeDefManager(); - const ContentFeatures &f = nodedef->get(n); + any_position_valid = true; + const NodeDefManager *nodedef = + gamedef->getNodeDefManager(); + const ContentFeatures &f = + nodedef->get(n); - if (!(f.walkable || (jesus && f.isLiquid()))) - continue; + if (!(f.walkable || (jesus && f.isLiquid()))) + continue; - int n_bouncy_value = itemgroup_get(f.groups, "bouncy"); + int n_bouncy_value = itemgroup_get( + f.groups, "bouncy"); - int neighbors = 0; - if (f.drawtype == NDT_NODEBOX && - f.node_box.type == NODEBOX_CONNECTED) { - v3s16 p2 = p; + int neighbors = 0; + if (f.drawtype == NDT_NODEBOX && + f.node_box.type == + NODEBOX_CONNECTED) { + v3s16 p2 = p; - p2.Y++; - getNeighborConnectingFace(p2, nodedef, map, n, 1, &neighbors); + p2.Y++; + getNeighborConnectingFace(p2, + nodedef, map, n, + 1, &neighbors); - p2 = p; - p2.Y--; - getNeighborConnectingFace(p2, nodedef, map, n, 2, &neighbors); + p2 = p; + p2.Y--; + getNeighborConnectingFace(p2, + nodedef, map, n, + 2, &neighbors); - p2 = p; - p2.Z--; - getNeighborConnectingFace(p2, nodedef, map, n, 4, &neighbors); + p2 = p; + p2.Z--; + getNeighborConnectingFace(p2, + nodedef, map, n, + 4, &neighbors); - p2 = p; - p2.X--; - getNeighborConnectingFace(p2, nodedef, map, n, 8, &neighbors); + p2 = p; + p2.X--; + getNeighborConnectingFace(p2, + nodedef, map, n, + 8, &neighbors); - p2 = p; - p2.Z++; - getNeighborConnectingFace(p2, nodedef, map, n, 16, &neighbors); + p2 = p; + p2.Z++; + getNeighborConnectingFace(p2, + nodedef, map, n, + 16, &neighbors); - p2 = p; - p2.X++; - getNeighborConnectingFace(p2, nodedef, map, n, 32, &neighbors); - } - std::vector nodeboxes; - n.getCollisionBoxes(gamedef->ndef(), &nodeboxes, neighbors); + p2 = p; + p2.X++; + getNeighborConnectingFace(p2, + nodedef, map, n, + 32, &neighbors); + } + std::vector nodeboxes; + n.getCollisionBoxes(gamedef->ndef(), + &nodeboxes, neighbors); - // Calculate float position only once - v3f posf = intToFloat(p, BS); - for (auto box : nodeboxes) { - box.MinEdge += posf; - box.MaxEdge += posf; - cinfo.emplace_back(false, n_bouncy_value, p, box); - } - } else { - // Collide with unloaded nodes (position invalid) and loaded - // CONTENT_IGNORE nodes (position valid) - aabb3f box = getNodeBox(p, BS); - cinfo.emplace_back(true, 0, p, box); + // Calculate float position only once + v3f posf = intToFloat(p, BS); + for (auto box : nodeboxes) { + box.MinEdge += posf; + box.MaxEdge += posf; + cinfo.emplace_back(false, + n_bouncy_value, p, + box); + } + } else { + // Collide with unloaded nodes (position + // invalid) and loaded CONTENT_IGNORE + // nodes (position valid) + aabb3f box = getNodeBox(p, BS); + cinfo.emplace_back(true, 0, p, box); + } + } + + // Do not move if world has not loaded yet, since custom node boxes + // are not available for collision detection. + // This also intentionally occurs in the case of the object being + // positioned solely on loaded CONTENT_IGNORE nodes, no matter where they + // come from. + if (!any_position_valid) { + *speed_f = v3f(0, 0, 0); + return result; } - } - - // Do not move if world has not loaded yet, since custom node boxes - // are not available for collision detection. - // This also intentionally occurs in the case of the object being positioned - // solely on loaded CONTENT_IGNORE nodes, no matter where they come from. - if (!any_position_valid) { - *speed_f = v3f(0, 0, 0); - return result; - } } // tt2 - if(collideWithObjects) - { + if (collideWithObjects) { /* add object boxes to cinfo */ - std::vector objects; + std::vector objects; #ifndef SERVER - ClientEnvironment *c_env = dynamic_cast(env); + ClientEnvironment *c_env = dynamic_cast(env); if (c_env != 0) { - // Calculate distance by speed, add own extent and 1.5m of tolerance + // Calculate distance by speed, add own extent and 1.5m of + // tolerance f32 distance = speed_f->getLength() * dtime + - box_0.getExtent().getLength() + 1.5f * BS; + box_0.getExtent().getLength() + 1.5f * BS; std::vector clientobjects; c_env->getActiveObjects(*pos_f, distance, clientobjects); for (auto &clientobject : clientobjects) { - // Do collide with everything but itself and the parent CAO + // Do collide with everything but itself and the parent + // CAO if (!self || (self != clientobject.obj && - self != clientobject.obj->getParent())) { - objects.push_back((ActiveObject*) clientobject.obj); + self != clientobject.obj->getParent())) { + objects.push_back( + (ActiveObject *)clientobject.obj); } } - } - else + } else #endif { - ServerEnvironment *s_env = dynamic_cast(env); + ServerEnvironment *s_env = dynamic_cast(env); if (s_env != NULL) { - // Calculate distance by speed, add own extent and 1.5m of tolerance + // Calculate distance by speed, add own extent and 1.5m of + // tolerance f32 distance = speed_f->getLength() * dtime + - box_0.getExtent().getLength() + 1.5f * BS; + box_0.getExtent().getLength() + 1.5f * BS; - // search for objects which are not us, or we are not its parent - // we directly use the callback to populate the result to prevent - // a useless result loop here - auto include_obj_cb = [self, &objects] (ServerActiveObject *obj) { + // search for objects which are not us, or we are not its + // parent we directly use the callback to populate the + // result to prevent a useless result loop here + auto include_obj_cb = [self, &objects]( + ServerActiveObject * + obj) { if (!obj->isGone() && - (!self || (self != obj && self != obj->getParent()))) { + (!self || (self != obj && self != obj->getParent()))) { objects.push_back((ActiveObject *)obj); } return false; }; std::vector s_objects; - s_env->getObjectsInsideRadius(s_objects, *pos_f, distance, include_obj_cb); + s_env->getObjectsInsideRadius(s_objects, *pos_f, distance, + include_obj_cb); } } - for (std::vector::const_iterator iter = objects.begin(); + for (std::vector::const_iterator iter = objects.begin(); iter != objects.end(); ++iter) { ActiveObject *object = *iter; if (object && object->collideWithObjects()) { aabb3f object_collisionbox; if (object->getCollisionBox(&object_collisionbox)) - cinfo.emplace_back(object, 0, object_collisionbox); + cinfo.emplace_back( + object, 0, object_collisionbox); } } #ifndef SERVER @@ -428,12 +489,12 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef, v3f lplayer_pos = lplayer->getPosition(); lplayer_collisionbox.MinEdge += lplayer_pos; lplayer_collisionbox.MaxEdge += lplayer_pos; - ActiveObject *obj = (ActiveObject*) lplayer->getCAO(); + ActiveObject *obj = (ActiveObject *)lplayer->getCAO(); cinfo.emplace_back(obj, 0, lplayer_collisionbox); } } #endif - } //tt3 + } // tt3 /* Collision detection @@ -443,11 +504,13 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef, int loopcount = 0; - while(dtime > BS * 1e-10f) { + while (dtime > BS * 1e-10f) { // Avoid infinite loop loopcount++; if (loopcount >= 100) { - warningstream << "collisionMoveSimple: Loop count exceeded, aborting to avoid infiniite loop" << std::endl; + warningstream << "collisionMoveSimple: Loop count exceeded, " + "aborting to avoid infiniite loop" + << std::endl; break; } @@ -470,8 +533,8 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef, // Find nearest collision of the two boxes (raytracing-like) f32 dtime_tmp = nearest_dtime; - CollisionAxis collided = axisAlignedCollision(box_info.box, - movingbox, *speed_f, &dtime_tmp); + CollisionAxis collided = axisAlignedCollision( + box_info.box, movingbox, *speed_f, &dtime_tmp); if (collided == -1 || dtime_tmp >= nearest_dtime) continue; @@ -484,30 +547,38 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef, if (nearest_collided == COLLISION_AXIS_NONE) { // No collision with any collision box. *pos_f += truncate(*speed_f * dtime, 100.0f); - dtime = 0; // Set to 0 to avoid "infinite" loop due to small FP numbers + dtime = 0; // Set to 0 to avoid "infinite" loop due to small FP + // numbers } else { // Otherwise, a collision occurred. NearbyCollisionInfo &nearest_info = cinfo[nearest_boxindex]; - const aabb3f& cbox = nearest_info.box; + const aabb3f &cbox = nearest_info.box; - //movingbox except moved to the horizontal position it would be after step up + // movingbox except moved to the horizontal position it would be + // after step up aabb3f stepbox = movingbox; stepbox.MinEdge.X += speed_f->X * dtime; stepbox.MinEdge.Z += speed_f->Z * dtime; stepbox.MaxEdge.X += speed_f->X * dtime; stepbox.MaxEdge.Z += speed_f->Z * dtime; // Check for stairs. - bool step_up = (nearest_collided != COLLISION_AXIS_Y) && // must not be Y direction - (movingbox.MinEdge.Y < cbox.MaxEdge.Y) && - (movingbox.MinEdge.Y + stepheight > cbox.MaxEdge.Y) && - (!wouldCollideWithCeiling(cinfo, stepbox, - cbox.MaxEdge.Y - movingbox.MinEdge.Y, - d)); + bool step_up = (nearest_collided != + COLLISION_AXIS_Y) && // must not be + // Y direction + (movingbox.MinEdge.Y < cbox.MaxEdge.Y) && + (movingbox.MinEdge.Y + stepheight > + cbox.MaxEdge.Y) && + (!wouldCollideWithCeiling(cinfo, stepbox, + cbox.MaxEdge.Y - + movingbox.MinEdge + .Y, + d)); // Get bounce multiplier float bounce = -(float)nearest_info.bouncy / 100.0f; - // Move to the point of collision and reduce dtime by nearest_dtime + // Move to the point of collision and reduce dtime by + // nearest_dtime if (nearest_dtime < 0) { // Handle negative nearest_dtime if (!step_up) { @@ -550,7 +621,7 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef, speed_f->X = 0; result.collides = true; } else if (nearest_collided == COLLISION_AXIS_Y) { - if(fabs(speed_f->Y) > BS * 3) + if (fabs(speed_f->Y) > BS * 3) speed_f->Y *= bounce; else speed_f->Y = 0; @@ -591,7 +662,8 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef, X-Z-area. */ - if (cbox.MaxEdge.X - d > box.MinEdge.X && cbox.MinEdge.X + d < box.MaxEdge.X && + if (cbox.MaxEdge.X - d > box.MinEdge.X && + cbox.MinEdge.X + d < box.MaxEdge.X && cbox.MaxEdge.Z - d > box.MinEdge.Z && cbox.MinEdge.Z + d < box.MaxEdge.Z) { if (box_info.is_step_up) { diff --git a/src/collision.h b/src/collision.h index 998598f1e..5138f4d6a 100644 --- a/src/collision.h +++ b/src/collision.h @@ -47,8 +47,8 @@ struct CollisionInfo CollisionType type = COLLISION_NODE; CollisionAxis axis = COLLISION_AXIS_NONE; - v3s16 node_p = v3s16(-32768,-32768,-32768); // COLLISION_NODE - ActiveObject *object = nullptr; // COLLISION_OBJECT + v3s16 node_p = v3s16(-32768, -32768, -32768); // COLLISION_NODE + ActiveObject *object = nullptr; // COLLISION_OBJECT v3f old_speed; v3f new_speed; int plane = -1; @@ -65,24 +65,19 @@ struct collisionMoveResult }; // Moves using a single iteration; speed should not exceed pos_max_d/dtime -collisionMoveResult collisionMoveSimple(Environment *env,IGameDef *gamedef, - f32 pos_max_d, const aabb3f &box_0, - f32 stepheight, f32 dtime, - v3f *pos_f, v3f *speed_f, - v3f accel_f, ActiveObject *self=NULL, - bool collideWithObjects=true, bool jesus=false); +collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef, + f32 pos_max_d, const aabb3f &box_0, f32 stepheight, f32 dtime, v3f *pos_f, + v3f *speed_f, v3f accel_f, ActiveObject *self = NULL, + bool collideWithObjects = true, bool jesus = false); // Helper function: // Checks for collision of a moving aabbox with a static aabbox // Returns -1 if no collision, 0 if X collision, 1 if Y collision, 2 if Z collision // dtime receives time until first collision, invalid if -1 is returned -CollisionAxis axisAlignedCollision( - const aabb3f &staticbox, const aabb3f &movingbox, +CollisionAxis axisAlignedCollision(const aabb3f &staticbox, const aabb3f &movingbox, const v3f &speed, f32 *dtime); // Helper function: // Checks if moving the movingbox up by the given distance would hit a ceiling. -bool wouldCollideWithCeiling( - const std::vector &staticboxes, - const aabb3f &movingbox, - f32 y_increase, f32 d); +bool wouldCollideWithCeiling(const std::vector &staticboxes, + const aabb3f &movingbox, f32 y_increase, f32 d); diff --git a/src/config.h b/src/config.h index 5e1164642..d4bde7896 100644 --- a/src/config.h +++ b/src/config.h @@ -8,23 +8,24 @@ #define STRINGIFY(x) #x #define STR(x) STRINGIFY(x) - #if defined USE_CMAKE_CONFIG_H - #include "cmake_config.h" -#elif defined (__ANDROID__) - #define PROJECT_NAME "minetest" - #define PROJECT_NAME_C "Minetest" - #define STATIC_SHAREDIR "" - #define VERSION_STRING STR(VERSION_MAJOR) "." STR(VERSION_MINOR) "." STR(VERSION_PATCH) STR(VERSION_EXTRA) +#include "cmake_config.h" +#elif defined(__ANDROID__) +#define PROJECT_NAME "minetest" +#define PROJECT_NAME_C "Minetest" +#define STATIC_SHAREDIR "" +#define VERSION_STRING \ + STR(VERSION_MAJOR) \ + "." STR(VERSION_MINOR) "." STR(VERSION_PATCH) STR(VERSION_EXTRA) #ifdef NDEBUG - #define BUILD_TYPE "Release" - #else - #define BUILD_TYPE "Debug" - #endif +#define BUILD_TYPE "Release" #else - #ifdef NDEBUG - #define BUILD_TYPE "Release" - #else - #define BUILD_TYPE "Debug" - #endif +#define BUILD_TYPE "Debug" +#endif +#else +#ifdef NDEBUG +#define BUILD_TYPE "Release" +#else +#define BUILD_TYPE "Debug" +#endif #endif diff --git a/src/content_mapnode.cpp b/src/content_mapnode.cpp index 6da5f45ad..11a018fc7 100644 --- a/src/content_mapnode.cpp +++ b/src/content_mapnode.cpp @@ -45,27 +45,27 @@ with this program; if not, write to the Free Software Foundation, Inc., #define CONTENT_LADDER 31 #define CONTENT_LAVA 32 #define CONTENT_LAVASOURCE 33 -#define CONTENT_GRASS 0x800 //1 -#define CONTENT_TREE 0x801 //4 -#define CONTENT_LEAVES 0x802 //5 -#define CONTENT_GRASS_FOOTSTEPS 0x803 //6 -#define CONTENT_MESE 0x804 //7 -#define CONTENT_MUD 0x805 //8 -#define CONTENT_CLOUD 0x806 //10 -#define CONTENT_COALSTONE 0x807 //11 -#define CONTENT_WOOD 0x808 //12 -#define CONTENT_SAND 0x809 //13 -#define CONTENT_COBBLE 0x80a //18 -#define CONTENT_STEEL 0x80b //19 -#define CONTENT_GLASS 0x80c //20 -#define CONTENT_MOSSYCOBBLE 0x80d //22 -#define CONTENT_GRAVEL 0x80e //23 -#define CONTENT_SANDSTONE 0x80f //24 -#define CONTENT_CACTUS 0x810 //25 -#define CONTENT_BRICK 0x811 //26 -#define CONTENT_CLAY 0x812 //27 -#define CONTENT_PAPYRUS 0x813 //28 -#define CONTENT_BOOKSHELF 0x814 //29 +#define CONTENT_GRASS 0x800 // 1 +#define CONTENT_TREE 0x801 // 4 +#define CONTENT_LEAVES 0x802 // 5 +#define CONTENT_GRASS_FOOTSTEPS 0x803 // 6 +#define CONTENT_MESE 0x804 // 7 +#define CONTENT_MUD 0x805 // 8 +#define CONTENT_CLOUD 0x806 // 10 +#define CONTENT_COALSTONE 0x807 // 11 +#define CONTENT_WOOD 0x808 // 12 +#define CONTENT_SAND 0x809 // 13 +#define CONTENT_COBBLE 0x80a // 18 +#define CONTENT_STEEL 0x80b // 19 +#define CONTENT_GLASS 0x80c // 20 +#define CONTENT_MOSSYCOBBLE 0x80d // 22 +#define CONTENT_GRAVEL 0x80e // 23 +#define CONTENT_SANDSTONE 0x80f // 24 +#define CONTENT_CACTUS 0x810 // 25 +#define CONTENT_BRICK 0x811 // 26 +#define CONTENT_CLAY 0x812 // 27 +#define CONTENT_PAPYRUS 0x813 // 28 +#define CONTENT_BOOKSHELF 0x814 // 29 #define CONTENT_JUNGLETREE 0x815 #define CONTENT_JUNGLEGRASS 0x816 #define CONTENT_NC 0x817 @@ -79,34 +79,33 @@ with this program; if not, write to the Free Software Foundation, Inc., Should never be touched. */ content_t trans_table_19[21][2] = { - {CONTENT_GRASS, 1}, - {CONTENT_TREE, 4}, - {CONTENT_LEAVES, 5}, - {CONTENT_GRASS_FOOTSTEPS, 6}, - {CONTENT_MESE, 7}, - {CONTENT_MUD, 8}, - {CONTENT_CLOUD, 10}, - {CONTENT_COALSTONE, 11}, - {CONTENT_WOOD, 12}, - {CONTENT_SAND, 13}, - {CONTENT_COBBLE, 18}, - {CONTENT_STEEL, 19}, - {CONTENT_GLASS, 20}, - {CONTENT_MOSSYCOBBLE, 22}, - {CONTENT_GRAVEL, 23}, - {CONTENT_SANDSTONE, 24}, - {CONTENT_CACTUS, 25}, - {CONTENT_BRICK, 26}, - {CONTENT_CLAY, 27}, - {CONTENT_PAPYRUS, 28}, - {CONTENT_BOOKSHELF, 29}, + {CONTENT_GRASS, 1}, + {CONTENT_TREE, 4}, + {CONTENT_LEAVES, 5}, + {CONTENT_GRASS_FOOTSTEPS, 6}, + {CONTENT_MESE, 7}, + {CONTENT_MUD, 8}, + {CONTENT_CLOUD, 10}, + {CONTENT_COALSTONE, 11}, + {CONTENT_WOOD, 12}, + {CONTENT_SAND, 13}, + {CONTENT_COBBLE, 18}, + {CONTENT_STEEL, 19}, + {CONTENT_GLASS, 20}, + {CONTENT_MOSSYCOBBLE, 22}, + {CONTENT_GRAVEL, 23}, + {CONTENT_SANDSTONE, 24}, + {CONTENT_CACTUS, 25}, + {CONTENT_BRICK, 26}, + {CONTENT_CLAY, 27}, + {CONTENT_PAPYRUS, 28}, + {CONTENT_BOOKSHELF, 29}, }; MapNode mapnode_translate_to_internal(MapNode n_from, u8 version) { MapNode result = n_from; - if(version <= 19) - { + if (version <= 19) { content_t c_from = n_from.getContent(); for (const auto &tt_i : trans_table_19) { if (tt_i[1] == c_from) { @@ -164,4 +163,3 @@ void content_mapnode_get_name_id_mapping(NameIdMapping *nimap) nimap->set(CONTENT_IGNORE, "ignore"); nimap->set(CONTENT_AIR, "air"); } - diff --git a/src/content_nodemeta.cpp b/src/content_nodemeta.cpp index fc2859d27..7d38fb78b 100644 --- a/src/content_nodemeta.cpp +++ b/src/content_nodemeta.cpp @@ -40,99 +40,92 @@ static bool content_nodemeta_deserialize_legacy_body( { meta->clear(); - if(id == NODEMETA_GENERIC) // GenericNodeMetadata (0.4-dev) + if (id == NODEMETA_GENERIC) // GenericNodeMetadata (0.4-dev) { meta->getInventory()->deSerialize(is); - deSerializeLongString(is); // m_text - deSerializeString(is); // m_owner + deSerializeLongString(is); // m_text + deSerializeString(is); // m_owner - meta->setString("infotext",deSerializeString(is)); - meta->setString("formspec",deSerializeString(is)); - readU8(is); // m_allow_text_input - readU8(is); // m_allow_removal - readU8(is); // m_enforce_owner + meta->setString("infotext", deSerializeString(is)); + meta->setString("formspec", deSerializeString(is)); + readU8(is); // m_allow_text_input + readU8(is); // m_allow_removal + readU8(is); // m_enforce_owner int num_vars = readU32(is); - for(int i=0; isetString(name, var); } return false; - } - else if(id == NODEMETA_SIGN) // SignNodeMetadata + } else if (id == NODEMETA_SIGN) // SignNodeMetadata { meta->setString("text", deSerializeString(is)); - //meta->setString("infotext","\"${text}\""); + // meta->setString("infotext","\"${text}\""); meta->setString("infotext", std::string("\"") + meta->getString("text") + "\""); - meta->setString("formspec","field[text;;${text}]"); + meta->setString("formspec", "field[text;;${text}]"); return false; - } - else if(id == NODEMETA_CHEST) // ChestNodeMetadata + } else if (id == NODEMETA_CHEST) // ChestNodeMetadata { meta->getInventory()->deSerialize(is); // Rename inventory list "0" to "main" Inventory *inv = meta->getInventory(); - if(!inv->getList("main") && inv->getList("0")){ + if (!inv->getList("main") && inv->getList("0")) { inv->getList("0")->setName("main"); } assert(inv->getList("main") && !inv->getList("0")); - meta->setString("formspec","size[8,9]" - "list[current_name;main;0,0;8,4;]" - "list[current_player;main;0,5;8,4;]"); + meta->setString("formspec", "size[8,9]" + "list[current_name;main;0,0;8,4;]" + "list[current_player;main;0,5;8,4;]"); return false; - } - else if(id == NODEMETA_LOCKABLE_CHEST) // LockingChestNodeMetadata + } else if (id == NODEMETA_LOCKABLE_CHEST) // LockingChestNodeMetadata { meta->setString("owner", deSerializeString(is)); meta->getInventory()->deSerialize(is); // Rename inventory list "0" to "main" Inventory *inv = meta->getInventory(); - if(!inv->getList("main") && inv->getList("0")){ + if (!inv->getList("main") && inv->getList("0")) { inv->getList("0")->setName("main"); } assert(inv->getList("main") && !inv->getList("0")); - meta->setString("formspec","size[8,9]" - "list[current_name;main;0,0;8,4;]" - "list[current_player;main;0,5;8,4;]"); + meta->setString("formspec", "size[8,9]" + "list[current_name;main;0,0;8,4;]" + "list[current_player;main;0,5;8,4;]"); return false; - } - else if(id == NODEMETA_FURNACE) // FurnaceNodeMetadata + } else if (id == NODEMETA_FURNACE) // FurnaceNodeMetadata { meta->getInventory()->deSerialize(is); int temp = 0; - is>>temp; - meta->setString("fuel_totaltime", ftos((float)temp/10)); + is >> temp; + meta->setString("fuel_totaltime", ftos((float)temp / 10)); temp = 0; - is>>temp; - meta->setString("fuel_time", ftos((float)temp/10)); + is >> temp; + meta->setString("fuel_time", ftos((float)temp / 10)); temp = 0; - is>>temp; - //meta->setString("src_totaltime", ftos((float)temp/10)); + is >> temp; + // meta->setString("src_totaltime", ftos((float)temp/10)); temp = 0; - is>>temp; - meta->setString("src_time", ftos((float)temp/10)); + is >> temp; + meta->setString("src_time", ftos((float)temp / 10)); - meta->setString("formspec","size[8,9]" - "list[current_name;fuel;2,3;1,1;]" - "list[current_name;src;2,1;1,1;]" - "list[current_name;dst;5,1;2,2;]" - "list[current_player;main;0,5;8,4;]"); + meta->setString("formspec", "size[8,9]" + "list[current_name;fuel;2,3;1,1;]" + "list[current_name;src;2,1;1,1;]" + "list[current_name;dst;5,1;2,2;]" + "list[current_player;main;0,5;8,4;]"); return true; - } - else - { + } else { throw SerializationError("Unknown legacy node metadata"); } } -static bool content_nodemeta_deserialize_legacy_meta( - std::istream &is, NodeMetadata *meta) +static bool content_nodemeta_deserialize_legacy_meta(std::istream &is, NodeMetadata *meta) { // Read id s16 id = readS16(is); @@ -143,41 +136,37 @@ static bool content_nodemeta_deserialize_legacy_meta( return content_nodemeta_deserialize_legacy_body(tmp_is, id, meta); } -void content_nodemeta_deserialize_legacy(std::istream &is, - NodeMetadataList *meta, NodeTimerList *timers, - IItemDefManager *item_def_mgr) +void content_nodemeta_deserialize_legacy(std::istream &is, NodeMetadataList *meta, + NodeTimerList *timers, IItemDefManager *item_def_mgr) { meta->clear(); timers->clear(); u16 version = readU16(is); - if(version > 1) - { - infostream< 1) { + infostream << FUNCTION_NAME << ": version " << version << " not supported" + << std::endl; throw SerializationError(FUNCTION_NAME); } u16 count = readU16(is); - for(u16 i=0; iget(p) != NULL) - { - warningstream<get(p) != NULL) { + warningstream << FUNCTION_NAME << ": " + << "already set data at position" + << "(" << p.X << "," << p.Y << "," << p.Z + << "): Ignoring." << std::endl; continue; } @@ -185,7 +174,7 @@ void content_nodemeta_deserialize_legacy(std::istream &is, bool need_timer = content_nodemeta_deserialize_legacy_meta(is, data); meta->set(p, data); - if(need_timer) + if (need_timer) timers->set(NodeTimer(1., 0., p)); } } diff --git a/src/content_nodemeta.h b/src/content_nodemeta.h index b853274f5..92a9bd546 100644 --- a/src/content_nodemeta.h +++ b/src/content_nodemeta.h @@ -29,6 +29,5 @@ class IItemDefManager; Legacy nodemeta definitions */ -void content_nodemeta_deserialize_legacy(std::istream &is, - NodeMetadataList *meta, NodeTimerList *timers, - IItemDefManager *item_def_mgr); +void content_nodemeta_deserialize_legacy(std::istream &is, NodeMetadataList *meta, + NodeTimerList *timers, IItemDefManager *item_def_mgr); diff --git a/src/convert_json.cpp b/src/convert_json.cpp index c774aa002..0a8fa1e22 100644 --- a/src/convert_json.cpp +++ b/src/convert_json.cpp @@ -29,8 +29,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "httpfetch.h" #include "porting.h" -Json::Value fetchJsonValue(const std::string &url, - std::vector *extra_headers) +Json::Value fetchJsonValue( + const std::string &url, std::vector *extra_headers) { HTTPFetchRequest fetch_request; HTTPFetchResult fetch_result; @@ -57,10 +57,12 @@ Json::Value fetchJsonValue(const std::string &url, errorstream << "Failed to parse json data " << errs << std::endl; if (fetch_result.data.size() > 100) { errorstream << "Data (" << fetch_result.data.size() - << " bytes) printed to warningstream." << std::endl; - warningstream << "data: \"" << fetch_result.data << "\"" << std::endl; + << " bytes) printed to warningstream." << std::endl; + warningstream << "data: \"" << fetch_result.data << "\"" + << std::endl; } else { - errorstream << "data: \"" << fetch_result.data << "\"" << std::endl; + errorstream << "data: \"" << fetch_result.data << "\"" + << std::endl; } return Json::Value(); } diff --git a/src/convert_json.h b/src/convert_json.h index d8825acdc..bf2ce2cb9 100644 --- a/src/convert_json.h +++ b/src/convert_json.h @@ -21,7 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include -Json::Value fetchJsonValue(const std::string &url, - std::vector *extra_headers); +Json::Value fetchJsonValue( + const std::string &url, std::vector *extra_headers); std::string fastWriteJson(const Json::Value &value); diff --git a/src/craftdef.cpp b/src/craftdef.cpp index 210605198..e821a4620 100644 --- a/src/craftdef.cpp +++ b/src/craftdef.cpp @@ -55,24 +55,26 @@ inline u64 getHashForString(const std::string &recipe_str) static u64 getHashForGrid(CraftHashType type, const std::vector &grid_names) { switch (type) { - case CRAFT_HASH_TYPE_ITEM_NAMES: { - std::ostringstream os; - bool is_first = true; - for (const std::string &grid_name : grid_names) { - if (!grid_name.empty()) { - os << (is_first ? "" : "\n") << grid_name; - is_first = false; - } + case CRAFT_HASH_TYPE_ITEM_NAMES: { + std::ostringstream os; + bool is_first = true; + for (const std::string &grid_name : grid_names) { + if (!grid_name.empty()) { + os << (is_first ? "" : "\n") << grid_name; + is_first = false; } - return getHashForString(os.str()); - } case CRAFT_HASH_TYPE_COUNT: { - u64 cnt = 0; - for (const std::string &grid_name : grid_names) - if (!grid_name.empty()) - cnt++; - return cnt; - } case CRAFT_HASH_TYPE_UNHASHED: - return 0; + } + return getHashForString(os.str()); + } + case CRAFT_HASH_TYPE_COUNT: { + u64 cnt = 0; + for (const std::string &grid_name : grid_names) + if (!grid_name.empty()) + cnt++; + return cnt; + } + case CRAFT_HASH_TYPE_UNHASHED: + return 0; } // invalid CraftHashType assert(false); @@ -147,8 +149,8 @@ static std::vector craftGetItems( std::vector result; result.reserve(items.size()); for (const auto &item : items) { - result.emplace_back(std::string(item), (u16)1, - (u16)0, gamedef->getItemDefManager()); + result.emplace_back(std::string(item), (u16)1, (u16)0, + gamedef->getItemDefManager()); } return result; } @@ -156,8 +158,8 @@ static std::vector craftGetItems( // Compute bounding rectangle given a matrix of items // Returns false if every item is "" static bool craftGetBounds(const std::vector &items, unsigned int width, - unsigned int &min_x, unsigned int &max_x, - unsigned int &min_y, unsigned int &max_y) + unsigned int &min_x, unsigned int &max_x, unsigned int &min_y, + unsigned int &max_y) { bool success = false; unsigned int x = 0; @@ -171,10 +173,14 @@ static bool craftGetBounds(const std::vector &items, unsigned int w min_y = max_y = y; success = true; } else { - if (x < min_x) min_x = x; - if (x > max_x) max_x = x; - if (y < min_y) min_y = y; - if (y > max_y) max_y = y; + if (x < min_x) + min_x = x; + if (x > max_x) + max_x = x; + if (y < min_y) + min_y = y; + if (y > max_y) + max_y = y; } } @@ -198,12 +204,12 @@ static void craftDecrementInput(CraftInput &input, IGameDef *gamedef) } // Removes 1 from each item stack with replacement support -// Example: if replacements contains the pair ("bucket:bucket_water", "bucket:bucket_empty"), +// Example: if replacements contains the pair ("bucket:bucket_water", +// "bucket:bucket_empty"), // a water bucket will not be removed but replaced by an empty bucket. static void craftDecrementOrReplaceInput(CraftInput &input, std::vector &output_replacements, - const CraftReplacements &replacements, - IGameDef *gamedef) + const CraftReplacements &replacements, IGameDef *gamedef) { if (replacements.pairs.empty()) { craftDecrementInput(input, gamedef); @@ -211,13 +217,14 @@ static void craftDecrementOrReplaceInput(CraftInput &input, } // Make a copy of the replacements pair list - std::vector > pairs = replacements.pairs; + std::vector> pairs = replacements.pairs; for (auto &item : input.items) { // Find an appropriate replacement bool found_replacement = false; for (auto j = pairs.begin(); j != pairs.end(); ++j) { - if (inputItemMatchesRecipe(item.name, j->first, gamedef->idef())) { + if (inputItemMatchesRecipe( + item.name, j->first, gamedef->idef())) { if (item.count == 1) { item.deSerialize(j->second, gamedef->idef()); found_replacement = true; @@ -231,7 +238,6 @@ static void craftDecrementOrReplaceInput(CraftInput &input, found_replacement = true; output_replacements.push_back(rep); break; - } } // No replacement was found, simply decrement count by one @@ -241,14 +247,13 @@ static void craftDecrementOrReplaceInput(CraftInput &input, } // Dump an itemstring matrix -static std::string craftDumpMatrix(const std::vector &items, - unsigned int width) +static std::string craftDumpMatrix( + const std::vector &items, unsigned int width) { std::ostringstream os(std::ios::binary); os << "{ "; unsigned int x = 0; - for(std::vector::size_type i = 0; - i < items.size(); i++, x++) { + for (std::vector::size_type i = 0; i < items.size(); i++, x++) { if (x == width) { os << "; "; x = 0; @@ -262,14 +267,12 @@ static std::string craftDumpMatrix(const std::vector &items, } // Dump an item matrix -std::string craftDumpMatrix(const std::vector &items, - unsigned int width) +std::string craftDumpMatrix(const std::vector &items, unsigned int width) { std::ostringstream os(std::ios::binary); os << "{ "; unsigned int x = 0; - for (std::vector::size_type i = 0; - i < items.size(); i++, x++) { + for (std::vector::size_type i = 0; i < items.size(); i++, x++) { if (x == width) { os << "; "; x = 0; @@ -282,7 +285,6 @@ std::string craftDumpMatrix(const std::vector &items, return os.str(); } - /* CraftInput */ @@ -299,8 +301,8 @@ bool CraftInput::empty() const std::string CraftInput::dump() const { std::ostringstream os(std::ios::binary); - os << "(method=" << ((int)method) << ", items=" - << craftDumpMatrix(items, width) << ")"; + os << "(method=" << ((int)method) << ", items=" << craftDumpMatrix(items, width) + << ")"; return os.str(); } @@ -322,12 +324,10 @@ std::string CraftOutput::dump() const std::string CraftReplacements::dump() const { std::ostringstream os(std::ios::binary); - os<<"{"; + os << "{"; const char *sep = ""; for (const auto &repl_p : pairs) { - os << sep - << '"' << (repl_p.first) - << "\"=>\"" << (repl_p.second) << '"'; + os << sep << '"' << (repl_p.first) << "\"=>\"" << (repl_p.second) << '"'; sep = ","; } os << "}"; @@ -338,12 +338,11 @@ std::string CraftReplacements::dump() const CraftDefinitionShaped */ -CraftDefinitionShaped::CraftDefinitionShaped( - const std::string &output_, - unsigned int width_, - const std::vector &recipe_, - const CraftReplacements &replacements_): - output(output_), width(width_), recipe(recipe_), replacements(replacements_) +CraftDefinitionShaped::CraftDefinitionShaped(const std::string &output_, + unsigned int width_, const std::vector &recipe_, + const CraftReplacements &replacements_) : + output(output_), + width(width_), recipe(recipe_), replacements(replacements_) { if (hasGroupItem(recipe)) priority = PRIORITY_SHAPED_AND_GROUPS; @@ -371,9 +370,9 @@ bool CraftDefinitionShaped::check(const CraftInput &input, IGameDef *gamedef) co // Get input bounds unsigned int inp_min_x = 0, inp_max_x = 0, inp_min_y = 0, inp_max_y = 0; - if (!craftGetBounds(inp_names, inp_width, inp_min_x, inp_max_x, - inp_min_y, inp_max_y)) - return false; // it was empty + if (!craftGetBounds(inp_names, inp_width, inp_min_x, inp_max_x, inp_min_y, + inp_max_y)) + return false; // it was empty std::vector rec_names; if (hash_inited) @@ -389,10 +388,10 @@ bool CraftDefinitionShaped::check(const CraftInput &input, IGameDef *gamedef) co rec_names.emplace_back(""); // Get recipe bounds - unsigned int rec_min_x=0, rec_max_x=0, rec_min_y=0, rec_max_y=0; - if (!craftGetBounds(rec_names, rec_width, rec_min_x, rec_max_x, - rec_min_y, rec_max_y)) - return false; // it was empty + unsigned int rec_min_x = 0, rec_max_x = 0, rec_min_y = 0, rec_max_y = 0; + if (!craftGetBounds(rec_names, rec_width, rec_min_x, rec_max_x, rec_min_y, + rec_max_y)) + return false; // it was empty // Different sizes? if (inp_max_x - inp_min_x != rec_max_x - rec_min_x || @@ -403,17 +402,16 @@ bool CraftDefinitionShaped::check(const CraftInput &input, IGameDef *gamedef) co unsigned int w = inp_max_x - inp_min_x + 1; unsigned int h = inp_max_y - inp_min_y + 1; - for (unsigned int y=0; y < h; y++) { + for (unsigned int y = 0; y < h; y++) { unsigned int inp_y = (inp_min_y + y) * inp_width; unsigned int rec_y = (rec_min_y + y) * rec_width; - for (unsigned int x=0; x < w; x++) { + for (unsigned int x = 0; x < w; x++) { unsigned int inp_x = inp_min_x + x; unsigned int rec_x = rec_min_x + x; - if (!inputItemMatchesRecipe( - inp_names[inp_y + inp_x], - rec_names[rec_y + rec_x], gamedef->idef())) { + if (!inputItemMatchesRecipe(inp_names[inp_y + inp_x], + rec_names[rec_y + rec_x], gamedef->idef())) { return false; } } @@ -422,18 +420,20 @@ bool CraftDefinitionShaped::check(const CraftInput &input, IGameDef *gamedef) co return true; } -CraftOutput CraftDefinitionShaped::getOutput(const CraftInput &input, IGameDef *gamedef) const +CraftOutput CraftDefinitionShaped::getOutput( + const CraftInput &input, IGameDef *gamedef) const { return CraftOutput(output, 0); } -CraftInput CraftDefinitionShaped::getInput(const CraftOutput &output, IGameDef *gamedef) const +CraftInput CraftDefinitionShaped::getInput( + const CraftOutput &output, IGameDef *gamedef) const { - return CraftInput(CRAFT_METHOD_NORMAL,width,craftGetItems(recipe,gamedef)); + return CraftInput(CRAFT_METHOD_NORMAL, width, craftGetItems(recipe, gamedef)); } -void CraftDefinitionShaped::decrementInput(CraftInput &input, std::vector &output_replacements, - IGameDef *gamedef) const +void CraftDefinitionShaped::decrementInput(CraftInput &input, + std::vector &output_replacements, IGameDef *gamedef) const { craftDecrementOrReplaceInput(input, output_replacements, replacements, gamedef); } @@ -441,8 +441,8 @@ void CraftDefinitionShaped::decrementInput(CraftInput &input, std::vector rec_names = recipe_names; std::sort(rec_names.begin(), rec_names.end()); @@ -466,8 +466,8 @@ std::string CraftDefinitionShaped::dump() const { std::ostringstream os(std::ios::binary); os << "(shaped, output=\"" << output - << "\", recipe=" << craftDumpMatrix(recipe, width) - << ", replacements=" << replacements.dump() << ")"; + << "\", recipe=" << craftDumpMatrix(recipe, width) + << ", replacements=" << replacements.dump() << ")"; return os.str(); } @@ -475,11 +475,11 @@ std::string CraftDefinitionShaped::dump() const CraftDefinitionShapeless */ -CraftDefinitionShapeless::CraftDefinitionShapeless( - const std::string &output_, +CraftDefinitionShapeless::CraftDefinitionShapeless(const std::string &output_, const std::vector &recipe_, - const CraftReplacements &replacements_): - output(output_), recipe(recipe_), replacements(replacements_) + const CraftReplacements &replacements_) : + output(output_), + recipe(recipe_), replacements(replacements_) { if (hasGroupItem(recipe)) priority = PRIORITY_SHAPELESS_AND_GROUPS; @@ -526,16 +526,16 @@ bool CraftDefinitionShapeless::check(const CraftInput &input, IGameDef *gamedef) do { // If all items match, the recipe matches bool all_match = true; - //dstream<<"Testing recipe (output="<idef())) { + gamedef->idef())) { all_match = false; break; } } - //dstream<<" -> match="< match="< &output_replacements, - IGameDef *gamedef) const +void CraftDefinitionShapeless::decrementInput(CraftInput &input, + std::vector &output_replacements, IGameDef *gamedef) const { craftDecrementOrReplaceInput(input, output_replacements, replacements, gamedef); } @@ -562,8 +564,8 @@ void CraftDefinitionShapeless::decrementInput(CraftInput &input, std::vectoridef(); - if (item1.count != 1 || item2.count != 1 || item1.name != item2.name - || idef->get(item1.name).type != ITEM_TOOL - || itemgroup_get(idef->get(item1.name).groups, "disable_repair") == 1) { + if (item1.count != 1 || item2.count != 1 || item1.name != item2.name || + idef->get(item1.name).type != ITEM_TOOL || + itemgroup_get(idef->get(item1.name).groups, "disable_repair") == + 1) { // Failure return ItemStack(); } - s32 item1_uses = 65536 - (u32) item1.wear; - s32 item2_uses = 65536 - (u32) item2.wear; + s32 item1_uses = 65536 - (u32)item1.wear; + s32 item2_uses = 65536 - (u32)item2.wear; s32 new_uses = item1_uses + item2_uses; s32 new_wear = 65536 - new_uses + floor(additional_wear * 65536 + 0.5); if (new_wear >= 65536) @@ -654,7 +654,8 @@ bool CraftDefinitionToolRepair::check(const CraftInput &input, IGameDef *gamedef return !repaired.empty(); } -CraftOutput CraftDefinitionToolRepair::getOutput(const CraftInput &input, IGameDef *gamedef) const +CraftOutput CraftDefinitionToolRepair::getOutput( + const CraftInput &input, IGameDef *gamedef) const { ItemStack item1; ItemStack item2; @@ -670,15 +671,16 @@ CraftOutput CraftDefinitionToolRepair::getOutput(const CraftInput &input, IGameD return CraftOutput(repaired.getItemString(), 0); } -CraftInput CraftDefinitionToolRepair::getInput(const CraftOutput &output, IGameDef *gamedef) const +CraftInput CraftDefinitionToolRepair::getInput( + const CraftOutput &output, IGameDef *gamedef) const { std::vector stack; stack.emplace_back(); return CraftInput(CRAFT_METHOD_COOKING, additional_wear, stack); } -void CraftDefinitionToolRepair::decrementInput(CraftInput &input, std::vector &output_replacements, - IGameDef *gamedef) const +void CraftDefinitionToolRepair::decrementInput(CraftInput &input, + std::vector &output_replacements, IGameDef *gamedef) const { craftDecrementInput(input, gamedef); } @@ -694,12 +696,11 @@ std::string CraftDefinitionToolRepair::dump() const CraftDefinitionCooking */ -CraftDefinitionCooking::CraftDefinitionCooking( - const std::string &output_, - const std::string &recipe_, - float cooktime_, - const CraftReplacements &replacements_): - output(output_), recipe(recipe_), cooktime(cooktime_), replacements(replacements_) +CraftDefinitionCooking::CraftDefinitionCooking(const std::string &output_, + const std::string &recipe_, float cooktime_, + const CraftReplacements &replacements_) : + output(output_), + recipe(recipe_), cooktime(cooktime_), replacements(replacements_) { if (isGroupRecipeStr(recipe)) priority = PRIORITY_SHAPELESS_AND_GROUPS; @@ -737,20 +738,22 @@ bool CraftDefinitionCooking::check(const CraftInput &input, IGameDef *gamedef) c return inputItemMatchesRecipe(input_filtered[0], recipe, gamedef->idef()); } -CraftOutput CraftDefinitionCooking::getOutput(const CraftInput &input, IGameDef *gamedef) const +CraftOutput CraftDefinitionCooking::getOutput( + const CraftInput &input, IGameDef *gamedef) const { return CraftOutput(output, cooktime); } -CraftInput CraftDefinitionCooking::getInput(const CraftOutput &output, IGameDef *gamedef) const +CraftInput CraftDefinitionCooking::getInput( + const CraftOutput &output, IGameDef *gamedef) const { std::vector rec; rec.push_back(recipe); - return CraftInput(CRAFT_METHOD_COOKING,cooktime,craftGetItems(rec,gamedef)); + return CraftInput(CRAFT_METHOD_COOKING, cooktime, craftGetItems(rec, gamedef)); } -void CraftDefinitionCooking::decrementInput(CraftInput &input, std::vector &output_replacements, - IGameDef *gamedef) const +void CraftDefinitionCooking::decrementInput(CraftInput &input, + std::vector &output_replacements, IGameDef *gamedef) const { craftDecrementOrReplaceInput(input, output_replacements, replacements, gamedef); } @@ -786,10 +789,9 @@ void CraftDefinitionCooking::initHash(IGameDef *gamedef) std::string CraftDefinitionCooking::dump() const { std::ostringstream os(std::ios::binary); - os << "(cooking, output=\"" << output - << "\", recipe=\"" << recipe - << "\", cooktime=" << cooktime << ")" - << ", replacements=" << replacements.dump() << ")"; + os << "(cooking, output=\"" << output << "\", recipe=\"" << recipe + << "\", cooktime=" << cooktime << ")" + << ", replacements=" << replacements.dump() << ")"; return os.str(); } @@ -797,11 +799,10 @@ std::string CraftDefinitionCooking::dump() const CraftDefinitionFuel */ -CraftDefinitionFuel::CraftDefinitionFuel( - const std::string &recipe_, - float burntime_, - const CraftReplacements &replacements_): - recipe(recipe_), burntime(burntime_), replacements(replacements_) +CraftDefinitionFuel::CraftDefinitionFuel(const std::string &recipe_, float burntime_, + const CraftReplacements &replacements_) : + recipe(recipe_), + burntime(burntime_), replacements(replacements_) { if (isGroupRecipeStr(recipe_name)) priority = PRIORITY_SHAPELESS_AND_GROUPS; @@ -839,20 +840,23 @@ bool CraftDefinitionFuel::check(const CraftInput &input, IGameDef *gamedef) cons return inputItemMatchesRecipe(input_filtered[0], recipe, gamedef->idef()); } -CraftOutput CraftDefinitionFuel::getOutput(const CraftInput &input, IGameDef *gamedef) const +CraftOutput CraftDefinitionFuel::getOutput( + const CraftInput &input, IGameDef *gamedef) const { return CraftOutput("", burntime); } -CraftInput CraftDefinitionFuel::getInput(const CraftOutput &output, IGameDef *gamedef) const +CraftInput CraftDefinitionFuel::getInput( + const CraftOutput &output, IGameDef *gamedef) const { std::vector rec; rec.push_back(recipe); - return CraftInput(CRAFT_METHOD_COOKING,(int)burntime,craftGetItems(rec,gamedef)); + return CraftInput( + CRAFT_METHOD_COOKING, (int)burntime, craftGetItems(rec, gamedef)); } -void CraftDefinitionFuel::decrementInput(CraftInput &input, std::vector &output_replacements, - IGameDef *gamedef) const +void CraftDefinitionFuel::decrementInput(CraftInput &input, + std::vector &output_replacements, IGameDef *gamedef) const { craftDecrementOrReplaceInput(input, output_replacements, replacements, gamedef); } @@ -888,9 +892,8 @@ void CraftDefinitionFuel::initHash(IGameDef *gamedef) std::string CraftDefinitionFuel::dump() const { std::ostringstream os(std::ios::binary); - os << "(fuel, recipe=\"" << recipe - << "\", burntime=" << burntime << ")" - << ", replacements=" << replacements.dump() << ")"; + os << "(fuel, recipe=\"" << recipe << "\", burntime=" << burntime << ")" + << ", replacements=" << replacements.dump() << ")"; return os.str(); } @@ -898,18 +901,12 @@ std::string CraftDefinitionFuel::dump() const Craft definition manager */ -class CCraftDefManager: public IWritableCraftDefManager +class CCraftDefManager : public IWritableCraftDefManager { public: - CCraftDefManager() - { - m_craft_defs.resize(craft_hash_type_max + 1); - } + CCraftDefManager() { m_craft_defs.resize(craft_hash_type_max + 1); } - virtual ~CCraftDefManager() - { - clear(); - } + virtual ~CCraftDefManager() { clear(); } virtual bool getCraftResult(CraftInput &input, CraftOutput &output, std::vector &output_replacement, bool decrementInput, @@ -925,40 +922,48 @@ public: // Try hash types with increasing collision rate // while remembering the latest, highest priority recipe. CraftDefinition::RecipePriority priority_best = - CraftDefinition::PRIORITY_NO_RECIPE; + CraftDefinition::PRIORITY_NO_RECIPE; CraftDefinition *def_best = nullptr; for (int type = 0; type <= craft_hash_type_max; type++) { - u64 hash = getHashForGrid((CraftHashType) type, input_names); + u64 hash = getHashForGrid((CraftHashType)type, input_names); - /*errorstream << "Checking type " << type << " with hash " << hash << std::endl;*/ + /*errorstream << "Checking type " << type << " with hash " << hash + * << std::endl;*/ - // We'd like to do "const [...] hash_collisions = m_craft_defs[type][hash];" - // but that doesn't compile for some reason. This does. + // We'd like to do "const [...] hash_collisions = + // m_craft_defs[type][hash];" but that doesn't compile for some + // reason. This does. auto col_iter = (m_craft_defs[type]).find(hash); if (col_iter == (m_craft_defs[type]).end()) continue; - const std::vector &hash_collisions = col_iter->second; + const std::vector &hash_collisions = + col_iter->second; // Walk crafting definitions from back to front, so that later // definitions can override earlier ones. - for (std::vector::size_type - i = hash_collisions.size(); i > 0; i--) { + for (std::vector::size_type i = + hash_collisions.size(); + i > 0; i--) { CraftDefinition *def = hash_collisions[i - 1]; /*errorstream << "Checking " << input.dump() << std::endl << " against " << def->dump() << std::endl;*/ - CraftDefinition::RecipePriority priority = def->getPriority(); - if (priority > priority_best - && def->check(input, gamedef)) { + CraftDefinition::RecipePriority priority = + def->getPriority(); + if (priority > priority_best && + def->check(input, gamedef)) { // Check if the crafted node/item exists CraftOutput out = def->getOutput(input, gamedef); ItemStack is; is.deSerialize(out.item, gamedef->idef()); if (!is.isKnown(gamedef->idef())) { - infostream << "trying to craft non-existent " - << out.item << ", ignoring recipe" << std::endl; + infostream << "trying to craft " + "non-existent " + << out.item + << ", ignoring recipe" + << std::endl; continue; } @@ -975,22 +980,22 @@ public: return true; } - virtual std::vector getCraftRecipes(CraftOutput &output, - IGameDef *gamedef, unsigned limit=0) const + virtual std::vector getCraftRecipes( + CraftOutput &output, IGameDef *gamedef, unsigned limit = 0) const { - std::vector recipes; + std::vector recipes; auto vec_iter = m_output_craft_definitions.find(output.item); if (vec_iter == m_output_craft_definitions.end()) return recipes; - const std::vector &vec = vec_iter->second; + const std::vector &vec = vec_iter->second; recipes.reserve(limit ? MYMIN(limit, vec.size()) : vec.size()); - for (std::vector::size_type i = vec.size(); - i > 0; i--) { + for (std::vector::size_type i = vec.size(); i > 0; + i--) { CraftDefinition *def = vec[i - 1]; if (limit && recipes.size() >= limit) break; @@ -1009,8 +1014,10 @@ public: for (auto def : to_clear->second) { // Recipes are not yet hashed at this point - std::vector &defs = m_craft_defs[(int)CRAFT_HASH_TYPE_UNHASHED][0]; - defs.erase(std::remove(defs.begin(), defs.end(), def), defs.end()); + std::vector &defs = + m_craft_defs[(int)CRAFT_HASH_TYPE_UNHASHED][0]; + defs.erase(std::remove(defs.begin(), defs.end(), def), + defs.end()); delete def; } m_output_craft_definitions.erase(to_clear); @@ -1023,7 +1030,8 @@ public: return false; // Recipes are not yet hashed at this point - std::vector &defs = m_craft_defs[(int)CRAFT_HASH_TYPE_UNHASHED][0]; + std::vector &defs = + m_craft_defs[(int)CRAFT_HASH_TYPE_UNHASHED][0]; std::vector new_defs; bool got_hit = false; for (auto def : defs) { @@ -1034,11 +1042,13 @@ public: got_hit = true; std::string output = def->getOutput(input, gamedef).item; delete def; - auto it = m_output_craft_definitions.find(craftGetItemName(output, gamedef)); + auto it = m_output_craft_definitions.find( + craftGetItemName(output, gamedef)); if (it == m_output_craft_definitions.end()) continue; std::vector &outdefs = it->second; - outdefs.erase(std::remove(outdefs.begin(), outdefs.end(), def), outdefs.end()); + outdefs.erase(std::remove(outdefs.begin(), outdefs.end(), def), + outdefs.end()); } if (got_hit) defs.swap(new_defs); @@ -1053,12 +1063,10 @@ public: for (int type = 0; type <= craft_hash_type_max; ++type) { for (auto it = m_craft_defs[type].begin(); it != m_craft_defs[type].end(); ++it) { - for (std::vector::size_type i = 0; + for (std::vector::size_type i = 0; i < it->second.size(); i++) { - os << "type " << type - << " hash " << it->first - << " def " << it->second[i]->dump() - << "\n"; + os << "type " << type << " hash " << it->first + << " def " << it->second[i]->dump() << "\n"; } } } @@ -1067,8 +1075,8 @@ public: virtual void registerCraft(CraftDefinition *def, IGameDef *gamedef) { TRACESTREAM(<< "registerCraft: registering craft definition: " - << def->dump() << std::endl); - m_craft_defs[(int) CRAFT_HASH_TYPE_UNHASHED][0].push_back(def); + << def->dump() << std::endl); + m_craft_defs[(int)CRAFT_HASH_TYPE_UNHASHED][0].push_back(def); CraftInput input; std::string output_name = craftGetItemName( @@ -1092,7 +1100,7 @@ public: { // Move the CraftDefs from the unhashed layer into layers higher up. std::vector &unhashed = - m_craft_defs[(int) CRAFT_HASH_TYPE_UNHASHED][0]; + m_craft_defs[(int)CRAFT_HASH_TYPE_UNHASHED][0]; for (auto def : unhashed) { // Initialize and get the definition's hash def->initHash(gamedef); @@ -1104,14 +1112,14 @@ public: } unhashed.clear(); } + private: - std::vector > > - m_craft_defs; - std::unordered_map > - m_output_craft_definitions; + std::vector>> m_craft_defs; + std::unordered_map> + m_output_craft_definitions; }; -IWritableCraftDefManager* createCraftDefManager() +IWritableCraftDefManager *createCraftDefManager() { return new CCraftDefManager(); } diff --git a/src/craftdef.h b/src/craftdef.h index 7c14e702a..dcadabe78 100644 --- a/src/craftdef.h +++ b/src/craftdef.h @@ -57,12 +57,13 @@ enum CraftHashType // Counts the non-empty slots. CRAFT_HASH_TYPE_COUNT, - // This layer both spares an extra variable, and helps to retain (albeit rarely used) functionality. Maps to 0. - // Before hashes are "initialized", all hashes reside here, after initialisation, none are. + // This layer both spares an extra variable, and helps to retain (albeit rarely + // used) functionality. Maps to 0. Before hashes are "initialized", all hashes + // reside here, after initialisation, none are. CRAFT_HASH_TYPE_UNHASHED }; -const int craft_hash_type_max = (int) CRAFT_HASH_TYPE_UNHASHED; +const int craft_hash_type_max = (int)CRAFT_HASH_TYPE_UNHASHED; /* Input: The contents of the crafting slots, arranged in matrix form @@ -76,9 +77,11 @@ struct CraftInput CraftInput() = default; CraftInput(CraftMethod method_, unsigned int width_, - const std::vector &items_): - method(method_), width(width_), items(items_) - {} + const std::vector &items_) : + method(method_), + width(width_), items(items_) + { + } // Returns true if all items are empty. bool empty() const; @@ -98,9 +101,7 @@ struct CraftOutput CraftOutput() = default; - CraftOutput(const std::string &item_, float time_): - item(item_), time(time_) - {} + CraftOutput(const std::string &item_, float time_) : item(item_), time(time_) {} std::string dump() const; }; @@ -117,12 +118,14 @@ struct CraftOutput struct CraftReplacements { // List of replacements - std::vector > pairs; + std::vector> pairs; CraftReplacements() = default; - CraftReplacements(const std::vector > &pairs_): - pairs(pairs_) - {} + CraftReplacements( + const std::vector> &pairs_) : + pairs(pairs_) + { + } std::string dump() const; }; @@ -153,33 +156,30 @@ public: virtual ~CraftDefinition() = default; // Returns type of crafting definition - virtual std::string getName() const=0; + virtual std::string getName() const = 0; // Checks whether the recipe is applicable - virtual bool check(const CraftInput &input, IGameDef *gamedef) const=0; - RecipePriority getPriority() const - { - return priority; - } + virtual bool check(const CraftInput &input, IGameDef *gamedef) const = 0; + RecipePriority getPriority() const { return priority; } // Returns the output structure, meaning depends on crafting method // The implementation can assume that check(input) returns true - virtual CraftOutput getOutput(const CraftInput &input, IGameDef *gamedef) const=0; + virtual CraftOutput getOutput( + const CraftInput &input, IGameDef *gamedef) const = 0; // the inverse of the above - virtual CraftInput getInput(const CraftOutput &output, IGameDef *gamedef) const=0; + virtual CraftInput getInput( + const CraftOutput &output, IGameDef *gamedef) const = 0; // Decreases count of every input item virtual void decrementInput(CraftInput &input, - std::vector &output_replacements, IGameDef *gamedef) const=0; + std::vector &output_replacements, + IGameDef *gamedef) const = 0; - CraftHashType getHashType() const - { - return hash_type; - } + CraftHashType getHashType() const { return hash_type; } virtual u64 getHash(CraftHashType type) const = 0; // to be called after all mods are loaded, so that we catch all aliases virtual void initHash(IGameDef *gamedef) = 0; - virtual std::string dump() const=0; + virtual std::string dump() const = 0; protected: CraftHashType hash_type; @@ -192,15 +192,13 @@ protected: Supported crafting method: CRAFT_METHOD_NORMAL. Requires the input items to be arranged exactly like in the recipe. */ -class CraftDefinitionShaped: public CraftDefinition +class CraftDefinitionShaped : public CraftDefinition { public: CraftDefinitionShaped() = delete; - CraftDefinitionShaped( - const std::string &output_, - unsigned int width_, - const std::vector &recipe_, - const CraftReplacements &replacements_); + CraftDefinitionShaped(const std::string &output_, unsigned int width_, + const std::vector &recipe_, + const CraftReplacements &replacements_); virtual ~CraftDefinitionShaped() = default; @@ -209,7 +207,8 @@ public: virtual CraftOutput getOutput(const CraftInput &input, IGameDef *gamedef) const; virtual CraftInput getInput(const CraftOutput &output, IGameDef *gamedef) const; virtual void decrementInput(CraftInput &input, - std::vector &output_replacements, IGameDef *gamedef) const; + std::vector &output_replacements, + IGameDef *gamedef) const; virtual u64 getHash(CraftHashType type) const; @@ -237,14 +236,13 @@ private: Supported crafting method: CRAFT_METHOD_NORMAL. Input items can arranged in any way. */ -class CraftDefinitionShapeless: public CraftDefinition +class CraftDefinitionShapeless : public CraftDefinition { public: CraftDefinitionShapeless() = delete; - CraftDefinitionShapeless( - const std::string &output_, - const std::vector &recipe_, - const CraftReplacements &replacements_); + CraftDefinitionShapeless(const std::string &output_, + const std::vector &recipe_, + const CraftReplacements &replacements_); virtual ~CraftDefinitionShapeless() = default; @@ -253,7 +251,8 @@ public: virtual CraftOutput getOutput(const CraftInput &input, IGameDef *gamedef) const; virtual CraftInput getInput(const CraftOutput &output, IGameDef *gamedef) const; virtual void decrementInput(CraftInput &input, - std::vector &output_replacements, IGameDef *gamedef) const; + std::vector &output_replacements, + IGameDef *gamedef) const; virtual u64 getHash(CraftHashType type) const; @@ -280,7 +279,7 @@ private: Put two damaged tools into the crafting grid, get one tool back. There should only be one crafting definition of this type. */ -class CraftDefinitionToolRepair: public CraftDefinition +class CraftDefinitionToolRepair : public CraftDefinition { public: CraftDefinitionToolRepair() = delete; @@ -293,14 +292,12 @@ public: virtual CraftOutput getOutput(const CraftInput &input, IGameDef *gamedef) const; virtual CraftInput getInput(const CraftOutput &output, IGameDef *gamedef) const; virtual void decrementInput(CraftInput &input, - std::vector &output_replacements, IGameDef *gamedef) const; + std::vector &output_replacements, + IGameDef *gamedef) const; virtual u64 getHash(CraftHashType type) const { return 2; } - virtual void initHash(IGameDef *gamedef) - { - hash_type = CRAFT_HASH_TYPE_COUNT; - } + virtual void initHash(IGameDef *gamedef) { hash_type = CRAFT_HASH_TYPE_COUNT; } virtual std::string dump() const; @@ -317,15 +314,12 @@ private: A cooking (in furnace) definition Supported crafting method: CRAFT_METHOD_COOKING. */ -class CraftDefinitionCooking: public CraftDefinition +class CraftDefinitionCooking : public CraftDefinition { public: CraftDefinitionCooking() = delete; - CraftDefinitionCooking( - const std::string &output_, - const std::string &recipe_, - float cooktime_, - const CraftReplacements &replacements_); + CraftDefinitionCooking(const std::string &output_, const std::string &recipe_, + float cooktime_, const CraftReplacements &replacements_); virtual ~CraftDefinitionCooking() = default; @@ -334,7 +328,8 @@ public: virtual CraftOutput getOutput(const CraftInput &input, IGameDef *gamedef) const; virtual CraftInput getInput(const CraftOutput &output, IGameDef *gamedef) const; virtual void decrementInput(CraftInput &input, - std::vector &output_replacements, IGameDef *gamedef) const; + std::vector &output_replacements, + IGameDef *gamedef) const; virtual u64 getHash(CraftHashType type) const; @@ -361,14 +356,12 @@ private: A fuel (for furnace) definition Supported crafting method: CRAFT_METHOD_FUEL. */ -class CraftDefinitionFuel: public CraftDefinition +class CraftDefinitionFuel : public CraftDefinition { public: CraftDefinitionFuel() = delete; - CraftDefinitionFuel( - const std::string &recipe_, - float burntime_, - const CraftReplacements &replacements_); + CraftDefinitionFuel(const std::string &recipe_, float burntime_, + const CraftReplacements &replacements_); virtual ~CraftDefinitionFuel() = default; @@ -377,7 +370,8 @@ public: virtual CraftOutput getOutput(const CraftInput &input, IGameDef *gamedef) const; virtual CraftInput getInput(const CraftOutput &output, IGameDef *gamedef) const; virtual void decrementInput(CraftInput &input, - std::vector &output_replacements, IGameDef *gamedef) const; + std::vector &output_replacements, + IGameDef *gamedef) const; virtual u64 getHash(CraftHashType type) const; @@ -420,14 +414,14 @@ public: * @return true if a result was found, otherwise false. */ virtual bool getCraftResult(CraftInput &input, CraftOutput &output, - std::vector &output_replacements, - bool decrementInput, IGameDef *gamedef) const=0; + std::vector &output_replacements, bool decrementInput, + IGameDef *gamedef) const = 0; - virtual std::vector getCraftRecipes(CraftOutput &output, - IGameDef *gamedef, unsigned limit=0) const=0; + virtual std::vector getCraftRecipes(CraftOutput &output, + IGameDef *gamedef, unsigned limit = 0) const = 0; // Print crafting recipes for debugging - virtual std::string dump() const=0; + virtual std::string dump() const = 0; }; class IWritableCraftDefManager : public ICraftDefManager @@ -438,26 +432,27 @@ public: // The main crafting function virtual bool getCraftResult(CraftInput &input, CraftOutput &output, - std::vector &output_replacements, - bool decrementInput, IGameDef *gamedef) const=0; - virtual std::vector getCraftRecipes(CraftOutput &output, - IGameDef *gamedef, unsigned limit=0) const=0; + std::vector &output_replacements, bool decrementInput, + IGameDef *gamedef) const = 0; + virtual std::vector getCraftRecipes(CraftOutput &output, + IGameDef *gamedef, unsigned limit = 0) const = 0; - virtual bool clearCraftsByOutput(const CraftOutput &output, IGameDef *gamedef) = 0; + virtual bool clearCraftsByOutput( + const CraftOutput &output, IGameDef *gamedef) = 0; virtual bool clearCraftsByInput(const CraftInput &input, IGameDef *gamedef) = 0; // Print crafting recipes for debugging - virtual std::string dump() const=0; + virtual std::string dump() const = 0; // Add a crafting definition. // After calling this, the pointer belongs to the manager. virtual void registerCraft(CraftDefinition *def, IGameDef *gamedef) = 0; // Delete all crafting definitions - virtual void clear()=0; + virtual void clear() = 0; // To be called after all mods are loaded, so that we catch all aliases virtual void initHashes(IGameDef *gamedef) = 0; }; -IWritableCraftDefManager* createCraftDefManager(); +IWritableCraftDefManager *createCraftDefManager(); diff --git a/src/database/database-dummy.cpp b/src/database/database-dummy.cpp index a3d8cd579..3d30761be 100644 --- a/src/database/database-dummy.cpp +++ b/src/database/database-dummy.cpp @@ -23,7 +23,6 @@ Dummy database class #include "database-dummy.h" - bool Database_Dummy::saveBlock(const v3s16 &pos, const std::string &data) { m_database[getBlockAsInteger(pos)] = data; @@ -56,4 +55,3 @@ void Database_Dummy::listAllLoadableBlocks(std::vector &dst) dst.push_back(getIntegerAsBlock(x->first)); } } - diff --git a/src/database/database-files.cpp b/src/database/database-files.cpp index d2b0b1543..fe5293a68 100644 --- a/src/database/database-files.cpp +++ b/src/database/database-files.cpp @@ -95,7 +95,7 @@ void PlayerDatabaseFiles::savePlayer(RemotePlayer *player) if (!path_found) { errorstream << "Didn't find free file for player " << player->getName() - << std::endl; + << std::endl; return; } @@ -156,7 +156,8 @@ bool PlayerDatabaseFiles::loadPlayer(RemotePlayer *player, PlayerSAO *sao) path = players_path + player_to_load + itos(i); } - infostream << "Player file for player " << player_to_load << " not found" << std::endl; + infostream << "Player file for player " << player_to_load << " not found" + << std::endl; return false; } @@ -164,8 +165,8 @@ void PlayerDatabaseFiles::listPlayers(std::vector &res) { std::vector files = fs::GetDirListing(m_savedir); // list files into players directory - for (std::vector::const_iterator it = files.begin(); it != - files.end(); ++it) { + for (std::vector::const_iterator it = files.begin(); + it != files.end(); ++it) { // Ignore directories if (it->dir) continue; diff --git a/src/database/database-leveldb.cpp b/src/database/database-leveldb.cpp index 1976ae13d..51830ff35 100644 --- a/src/database/database-leveldb.cpp +++ b/src/database/database-leveldb.cpp @@ -33,20 +33,18 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "leveldb/db.h" - -#define ENSURE_STATUS_OK(s) \ - if (!(s).ok()) { \ - throw DatabaseException(std::string("LevelDB error: ") + \ - (s).ToString()); \ +#define ENSURE_STATUS_OK(s) \ + if (!(s).ok()) { \ + throw DatabaseException( \ + std::string("LevelDB error: ") + (s).ToString()); \ } - Database_LevelDB::Database_LevelDB(const std::string &savedir) { leveldb::Options options; options.create_if_missing = true; - leveldb::Status status = leveldb::DB::Open(options, - savedir + DIR_DELIM + "map.db", &m_database); + leveldb::Status status = leveldb::DB::Open( + options, savedir + DIR_DELIM + "map.db", &m_database); ENSURE_STATUS_OK(status); } @@ -57,11 +55,11 @@ Database_LevelDB::~Database_LevelDB() bool Database_LevelDB::saveBlock(const v3s16 &pos, const std::string &data) { - leveldb::Status status = m_database->Put(leveldb::WriteOptions(), - i64tos(getBlockAsInteger(pos)), data); + leveldb::Status status = m_database->Put( + leveldb::WriteOptions(), i64tos(getBlockAsInteger(pos)), data); if (!status.ok()) { - warningstream << "saveBlock: LevelDB error saving block " - << PP(pos) << ": " << status.ToString() << std::endl; + warningstream << "saveBlock: LevelDB error saving block " << PP(pos) + << ": " << status.ToString() << std::endl; return false; } @@ -71,19 +69,19 @@ bool Database_LevelDB::saveBlock(const v3s16 &pos, const std::string &data) void Database_LevelDB::loadBlock(const v3s16 &pos, std::string *block) { std::string datastr; - leveldb::Status status = m_database->Get(leveldb::ReadOptions(), - i64tos(getBlockAsInteger(pos)), &datastr); + leveldb::Status status = m_database->Get( + leveldb::ReadOptions(), i64tos(getBlockAsInteger(pos)), &datastr); *block = (status.ok()) ? datastr : ""; } bool Database_LevelDB::deleteBlock(const v3s16 &pos) { - leveldb::Status status = m_database->Delete(leveldb::WriteOptions(), - i64tos(getBlockAsInteger(pos))); + leveldb::Status status = m_database->Delete( + leveldb::WriteOptions(), i64tos(getBlockAsInteger(pos))); if (!status.ok()) { - warningstream << "deleteBlock: LevelDB error deleting block " - << PP(pos) << ": " << status.ToString() << std::endl; + warningstream << "deleteBlock: LevelDB error deleting block " << PP(pos) + << ": " << status.ToString() << std::endl; return false; } @@ -92,11 +90,11 @@ bool Database_LevelDB::deleteBlock(const v3s16 &pos) void Database_LevelDB::listAllLoadableBlocks(std::vector &dst) { - leveldb::Iterator* it = m_database->NewIterator(leveldb::ReadOptions()); + leveldb::Iterator *it = m_database->NewIterator(leveldb::ReadOptions()); for (it->SeekToFirst(); it->Valid(); it->Next()) { dst.push_back(getIntegerAsBlock(stoi64(it->key().ToString()))); } - ENSURE_STATUS_OK(it->status()); // Check for any errors found during the scan + ENSURE_STATUS_OK(it->status()); // Check for any errors found during the scan delete it; } @@ -104,8 +102,8 @@ PlayerDatabaseLevelDB::PlayerDatabaseLevelDB(const std::string &savedir) { leveldb::Options options; options.create_if_missing = true; - leveldb::Status status = leveldb::DB::Open(options, - savedir + DIR_DELIM + "players.db", &m_database); + leveldb::Status status = leveldb::DB::Open( + options, savedir + DIR_DELIM + "players.db", &m_database); ENSURE_STATUS_OK(status); } @@ -151,8 +149,8 @@ void PlayerDatabaseLevelDB::savePlayer(RemotePlayer *player) player->inventory.serialize(os); - leveldb::Status status = m_database->Put(leveldb::WriteOptions(), - player->getName(), os.str()); + leveldb::Status status = m_database->Put( + leveldb::WriteOptions(), player->getName(), os.str()); ENSURE_STATUS_OK(status); player->onSuccessfulSave(); } @@ -166,8 +164,8 @@ bool PlayerDatabaseLevelDB::removePlayer(const std::string &name) bool PlayerDatabaseLevelDB::loadPlayer(RemotePlayer *player, PlayerSAO *sao) { std::string raw; - leveldb::Status s = m_database->Get(leveldb::ReadOptions(), - player->getName(), &raw); + leveldb::Status s = + m_database->Get(leveldb::ReadOptions(), player->getName(), &raw); if (!s.ok()) return false; std::istringstream is(raw); @@ -194,7 +192,7 @@ bool PlayerDatabaseLevelDB::loadPlayer(RemotePlayer *player, PlayerSAO *sao) player->inventory.deSerialize(is); } catch (SerializationError &e) { errorstream << "Failed to deserialize player inventory. player_name=" - << player->getName() << " " << e.what() << std::endl; + << player->getName() << " " << e.what() << std::endl; } return true; @@ -202,7 +200,7 @@ bool PlayerDatabaseLevelDB::loadPlayer(RemotePlayer *player, PlayerSAO *sao) void PlayerDatabaseLevelDB::listPlayers(std::vector &res) { - leveldb::Iterator* it = m_database->NewIterator(leveldb::ReadOptions()); + leveldb::Iterator *it = m_database->NewIterator(leveldb::ReadOptions()); res.clear(); for (it->SeekToFirst(); it->Valid(); it->Next()) { res.push_back(it->key().ToString()); @@ -214,8 +212,8 @@ AuthDatabaseLevelDB::AuthDatabaseLevelDB(const std::string &savedir) { leveldb::Options options; options.create_if_missing = true; - leveldb::Status status = leveldb::DB::Open(options, - savedir + DIR_DELIM + "auth.db", &m_database); + leveldb::Status status = leveldb::DB::Open( + options, savedir + DIR_DELIM + "auth.db", &m_database); ENSURE_STATUS_OK(status); } @@ -267,16 +265,15 @@ bool AuthDatabaseLevelDB::saveAuth(const AuthEntry &authEntry) os << serializeString(authEntry.password); size_t privilege_count = authEntry.privileges.size(); - FATAL_ERROR_IF(privilege_count > U16_MAX, - "Unsupported number of privileges"); + FATAL_ERROR_IF(privilege_count > U16_MAX, "Unsupported number of privileges"); writeU16(os, privilege_count); for (const std::string &privilege : authEntry.privileges) { os << serializeString(privilege); } writeS64(os, authEntry.last_login); - leveldb::Status s = m_database->Put(leveldb::WriteOptions(), - authEntry.name, os.str()); + leveldb::Status s = m_database->Put( + leveldb::WriteOptions(), authEntry.name, os.str()); return s.ok(); } @@ -293,7 +290,7 @@ bool AuthDatabaseLevelDB::deleteAuth(const std::string &name) void AuthDatabaseLevelDB::listNames(std::vector &res) { - leveldb::Iterator* it = m_database->NewIterator(leveldb::ReadOptions()); + leveldb::Iterator *it = m_database->NewIterator(leveldb::ReadOptions()); res.clear(); for (it->SeekToFirst(); it->Valid(); it->Next()) { res.emplace_back(it->key().ToString()); diff --git a/src/database/database-postgresql.cpp b/src/database/database-postgresql.cpp index e1bb39928..3d5e1947c 100644 --- a/src/database/database-postgresql.cpp +++ b/src/database/database-postgresql.cpp @@ -23,12 +23,12 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "database-postgresql.h" #ifdef _WIN32 - // Without this some of the network functions are not found on mingw - #ifndef _WIN32_WINNT - #define _WIN32_WINNT 0x0501 - #endif - #include - #include +// Without this some of the network functions are not found on mingw +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0501 +#endif +#include +#include #else #include #endif @@ -40,19 +40,21 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "server/player_sao.h" Database_PostgreSQL::Database_PostgreSQL(const std::string &connect_string) : - m_connect_string(connect_string) + m_connect_string(connect_string) { if (m_connect_string.empty()) { throw SettingNotFoundException( - "Set pgsql_connection string in world.mt to " - "use the postgresql backend\n" - "Notes:\n" - "pgsql_connection has the following form: \n" - "\tpgsql_connection = host=127.0.0.1 port=5432 user=mt_user " - "password=mt_password dbname=minetest_world\n" - "mt_user should have CREATE TABLE, INSERT, SELECT, UPDATE and " - "DELETE rights on the database.\n" - "Don't create mt_user as a SUPERUSER!"); + "Set pgsql_connection string in world.mt to " + "use the postgresql backend\n" + "Notes:\n" + "pgsql_connection has the following form: \n" + "\tpgsql_connection = host=127.0.0.1 port=5432 " + "user=mt_user " + "password=mt_password dbname=minetest_world\n" + "mt_user should have CREATE TABLE, INSERT, SELECT, " + "UPDATE and " + "DELETE rights on the database.\n" + "Don't create mt_user as a SUPERUSER!"); } } @@ -66,25 +68,24 @@ void Database_PostgreSQL::connectToDatabase() m_conn = PQconnectdb(m_connect_string.c_str()); if (PQstatus(m_conn) != CONNECTION_OK) { - throw DatabaseException(std::string( - "PostgreSQL database error: ") + - PQerrorMessage(m_conn)); + throw DatabaseException(std::string("PostgreSQL database error: ") + + PQerrorMessage(m_conn)); } m_pgversion = PQserverVersion(m_conn); /* - * We are using UPSERT feature from PostgreSQL 9.5 - * to have the better performance where possible. - */ + * We are using UPSERT feature from PostgreSQL 9.5 + * to have the better performance where possible. + */ if (m_pgversion < 90500) { warningstream << "Your PostgreSQL server lacks UPSERT " - << "support. Use version 9.5 or better if possible." - << std::endl; + << "support. Use version 9.5 or better if possible." + << std::endl; } infostream << "PostgreSQL Database: Version " << m_pgversion - << " Connection made." << std::endl; + << " Connection made." << std::endl; createDatabase(); initStatements(); @@ -102,9 +103,8 @@ void Database_PostgreSQL::verifyDatabase() void Database_PostgreSQL::ping() { if (PQping(m_connect_string.c_str()) != PQPING_OK) { - throw DatabaseException(std::string( - "PostgreSQL database error: ") + - PQerrorMessage(m_conn)); + throw DatabaseException(std::string("PostgreSQL database error: ") + + PQerrorMessage(m_conn)); } } @@ -123,9 +123,8 @@ PGresult *Database_PostgreSQL::checkResults(PGresult *result, bool clear) break; case PGRES_FATAL_ERROR: default: - throw DatabaseException( - std::string("PostgreSQL database error: ") + - PQresultErrorMessage(result)); + throw DatabaseException(std::string("PostgreSQL database error: ") + + PQresultErrorMessage(result)); } if (clear) @@ -134,11 +133,11 @@ PGresult *Database_PostgreSQL::checkResults(PGresult *result, bool clear) return result; } -void Database_PostgreSQL::createTableIfNotExists(const std::string &table_name, - const std::string &definition) +void Database_PostgreSQL::createTableIfNotExists( + const std::string &table_name, const std::string &definition) { std::string sql_check_table = "SELECT relname FROM pg_class WHERE relname='" + - table_name + "';"; + table_name + "';"; PGresult *result = checkResults(PQexec(m_conn, sql_check_table.c_str()), false); // If table doesn't exist, create it @@ -165,61 +164,57 @@ void Database_PostgreSQL::rollback() checkResults(PQexec(m_conn, "ROLLBACK;")); } -MapDatabasePostgreSQL::MapDatabasePostgreSQL(const std::string &connect_string): - Database_PostgreSQL(connect_string), - MapDatabase() +MapDatabasePostgreSQL::MapDatabasePostgreSQL(const std::string &connect_string) : + Database_PostgreSQL(connect_string), MapDatabase() { connectToDatabase(); } - void MapDatabasePostgreSQL::createDatabase() { - createTableIfNotExists("blocks", - "CREATE TABLE blocks (" - "posX INT NOT NULL," - "posY INT NOT NULL," - "posZ INT NOT NULL," - "data BYTEA," - "PRIMARY KEY (posX,posY,posZ)" - ");" - ); + createTableIfNotExists("blocks", "CREATE TABLE blocks (" + "posX INT NOT NULL," + "posY INT NOT NULL," + "posZ INT NOT NULL," + "data BYTEA," + "PRIMARY KEY (posX,posY,posZ)" + ");"); infostream << "PostgreSQL: Map Database was initialized." << std::endl; } void MapDatabasePostgreSQL::initStatements() { - prepareStatement("read_block", - "SELECT data FROM blocks " - "WHERE posX = $1::int4 AND posY = $2::int4 AND " - "posZ = $3::int4"); + prepareStatement("read_block", "SELECT data FROM blocks " + "WHERE posX = $1::int4 AND posY = $2::int4 AND " + "posZ = $3::int4"); if (getPGVersion() < 90500) { prepareStatement("write_block_insert", - "INSERT INTO blocks (posX, posY, posZ, data) SELECT " + "INSERT INTO blocks (posX, posY, posZ, data) SELECT " "$1::int4, $2::int4, $3::int4, $4::bytea " "WHERE NOT EXISTS (SELECT true FROM blocks " "WHERE posX = $1::int4 AND posY = $2::int4 AND " "posZ = $3::int4)"); prepareStatement("write_block_update", - "UPDATE blocks SET data = $4::bytea " + "UPDATE blocks SET data = $4::bytea " "WHERE posX = $1::int4 AND posY = $2::int4 AND " "posZ = $3::int4"); } else { prepareStatement("write_block", - "INSERT INTO blocks (posX, posY, posZ, data) VALUES " + "INSERT INTO blocks (posX, posY, posZ, data) VALUES " "($1::int4, $2::int4, $3::int4, $4::bytea) " "ON CONFLICT ON CONSTRAINT blocks_pkey DO " "UPDATE SET data = $4::bytea"); } - prepareStatement("delete_block", "DELETE FROM blocks WHERE " - "posX = $1::int4 AND posY = $2::int4 AND posZ = $3::int4"); + prepareStatement("delete_block", + "DELETE FROM blocks WHERE " + "posX = $1::int4 AND posY = $2::int4 AND posZ = $3::int4"); prepareStatement("list_all_loadable_blocks", - "SELECT posX, posY, posZ FROM blocks"); + "SELECT posX, posY, posZ FROM blocks"); } bool MapDatabasePostgreSQL::saveBlock(const v3s16 &pos, const std::string &data) @@ -227,8 +222,8 @@ bool MapDatabasePostgreSQL::saveBlock(const v3s16 &pos, const std::string &data) // Verify if we don't overflow the platform integer with the mapblock size if (data.size() > INT_MAX) { errorstream << "Database_PostgreSQL::saveBlock: Data truncation! " - << "data.size() over 0xFFFFFFFF (== " << data.size() - << ")" << std::endl; + << "data.size() over 0xFFFFFFFF (== " << data.size() << ")" + << std::endl; return false; } @@ -239,11 +234,9 @@ bool MapDatabasePostgreSQL::saveBlock(const v3s16 &pos, const std::string &data) y = htonl(pos.Y); z = htonl(pos.Z); - const void *args[] = { &x, &y, &z, data.c_str() }; - const int argLen[] = { - sizeof(x), sizeof(y), sizeof(z), (int)data.size() - }; - const int argFmt[] = { 1, 1, 1, 1 }; + const void *args[] = {&x, &y, &z, data.c_str()}; + const int argLen[] = {sizeof(x), sizeof(y), sizeof(z), (int)data.size()}; + const int argFmt[] = {1, 1, 1, 1}; if (getPGVersion() < 90500) { execPrepared("write_block_update", ARRLEN(args), args, argLen, argFmt); @@ -263,17 +256,18 @@ void MapDatabasePostgreSQL::loadBlock(const v3s16 &pos, std::string *block) y = htonl(pos.Y); z = htonl(pos.Z); - const void *args[] = { &x, &y, &z }; - const int argLen[] = { sizeof(x), sizeof(y), sizeof(z) }; - const int argFmt[] = { 1, 1, 1 }; + const void *args[] = {&x, &y, &z}; + const int argLen[] = {sizeof(x), sizeof(y), sizeof(z)}; + const int argFmt[] = {1, 1, 1}; - PGresult *results = execPrepared("read_block", ARRLEN(args), args, - argLen, argFmt, false); + PGresult *results = execPrepared( + "read_block", ARRLEN(args), args, argLen, argFmt, false); *block = ""; if (PQntuples(results)) - *block = std::string(PQgetvalue(results, 0, 0), PQgetlength(results, 0, 0)); + *block = std::string( + PQgetvalue(results, 0, 0), PQgetlength(results, 0, 0)); PQclear(results); } @@ -287,9 +281,9 @@ bool MapDatabasePostgreSQL::deleteBlock(const v3s16 &pos) y = htonl(pos.Y); z = htonl(pos.Z); - const void *args[] = { &x, &y, &z }; - const int argLen[] = { sizeof(x), sizeof(y), sizeof(z) }; - const int argFmt[] = { 1, 1, 1 }; + const void *args[] = {&x, &y, &z}; + const int argLen[] = {sizeof(x), sizeof(y), sizeof(z)}; + const int argFmt[] = {1, 1, 1}; execPrepared("delete_block", ARRLEN(args), args, argLen, argFmt); @@ -300,8 +294,8 @@ void MapDatabasePostgreSQL::listAllLoadableBlocks(std::vector &dst) { verifyDatabase(); - PGresult *results = execPrepared("list_all_loadable_blocks", 0, - NULL, NULL, NULL, false, false); + PGresult *results = execPrepared( + "list_all_loadable_blocks", 0, NULL, NULL, NULL, false, false); int numrows = PQntuples(results); @@ -314,67 +308,63 @@ void MapDatabasePostgreSQL::listAllLoadableBlocks(std::vector &dst) /* * Player Database */ -PlayerDatabasePostgreSQL::PlayerDatabasePostgreSQL(const std::string &connect_string): - Database_PostgreSQL(connect_string), - PlayerDatabase() +PlayerDatabasePostgreSQL::PlayerDatabasePostgreSQL(const std::string &connect_string) : + Database_PostgreSQL(connect_string), PlayerDatabase() { connectToDatabase(); } - void PlayerDatabasePostgreSQL::createDatabase() { - createTableIfNotExists("player", - "CREATE TABLE player (" - "name VARCHAR(60) NOT NULL," - "pitch NUMERIC(15, 7) NOT NULL," - "yaw NUMERIC(15, 7) NOT NULL," - "posX NUMERIC(15, 7) NOT NULL," - "posY NUMERIC(15, 7) NOT NULL," - "posZ NUMERIC(15, 7) NOT NULL," - "hp INT NOT NULL," - "breath INT NOT NULL," - "creation_date TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT NOW()," - "modification_date TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT NOW()," - "PRIMARY KEY (name)" - ");" - ); + createTableIfNotExists("player", "CREATE TABLE player (" + "name VARCHAR(60) NOT NULL," + "pitch NUMERIC(15, 7) NOT NULL," + "yaw NUMERIC(15, 7) NOT NULL," + "posX NUMERIC(15, 7) NOT NULL," + "posY NUMERIC(15, 7) NOT NULL," + "posZ NUMERIC(15, 7) NOT NULL," + "hp INT NOT NULL," + "breath INT NOT NULL," + "creation_date TIMESTAMP WITHOUT TIME ZONE NOT " + "NULL DEFAULT NOW()," + "modification_date TIMESTAMP WITHOUT TIME ZONE " + "NOT NULL DEFAULT NOW()," + "PRIMARY KEY (name)" + ");"); - createTableIfNotExists("player_inventories", - "CREATE TABLE player_inventories (" - "player VARCHAR(60) NOT NULL," - "inv_id INT NOT NULL," - "inv_width INT NOT NULL," - "inv_name TEXT NOT NULL DEFAULT ''," - "inv_size INT NOT NULL," - "PRIMARY KEY(player, inv_id)," - "CONSTRAINT player_inventories_fkey FOREIGN KEY (player) REFERENCES " - "player (name) ON DELETE CASCADE" - ");" - ); + createTableIfNotExists("player_inventories", "CREATE TABLE player_inventories (" + "player VARCHAR(60) NOT NULL," + "inv_id INT NOT NULL," + "inv_width INT NOT NULL," + "inv_name TEXT NOT NULL DEFAULT ''," + "inv_size INT NOT NULL," + "PRIMARY KEY(player, inv_id)," + "CONSTRAINT player_inventories_fkey " + "FOREIGN KEY (player) REFERENCES " + "player (name) ON DELETE CASCADE" + ");"); createTableIfNotExists("player_inventory_items", - "CREATE TABLE player_inventory_items (" + "CREATE TABLE player_inventory_items (" "player VARCHAR(60) NOT NULL," "inv_id INT NOT NULL," "slot_id INT NOT NULL," "item TEXT NOT NULL DEFAULT ''," "PRIMARY KEY(player, inv_id, slot_id)," - "CONSTRAINT player_inventory_items_fkey FOREIGN KEY (player) REFERENCES " + "CONSTRAINT player_inventory_items_fkey FOREIGN KEY (player) " + "REFERENCES " "player (name) ON DELETE CASCADE" - ");" - ); + ");"); createTableIfNotExists("player_metadata", - "CREATE TABLE player_metadata (" + "CREATE TABLE player_metadata (" "player VARCHAR(60) NOT NULL," "attr VARCHAR(256) NOT NULL," "value TEXT," "PRIMARY KEY(player, attr)," "CONSTRAINT player_metadata_fkey FOREIGN KEY (player) REFERENCES " "player (name) ON DELETE CASCADE" - ");" - ); + ");"); infostream << "PostgreSQL: Player Database was inited." << std::endl; } @@ -383,18 +373,24 @@ void PlayerDatabasePostgreSQL::initStatements() { if (getPGVersion() < 90500) { prepareStatement("create_player", - "INSERT INTO player(name, pitch, yaw, posX, posY, posZ, hp, breath) VALUES " + "INSERT INTO player(name, pitch, yaw, posX, posY, posZ, " + "hp, breath) VALUES " "($1, $2, $3, $4, $5, $6, $7::int, $8::int)"); prepareStatement("update_player", - "UPDATE SET pitch = $2, yaw = $3, posX = $4, posY = $5, posZ = $6, hp = $7::int, " - "breath = $8::int, modification_date = NOW() WHERE name = $1"); + "UPDATE SET pitch = $2, yaw = $3, posX = $4, posY = $5, " + "posZ = $6, hp = $7::int, " + "breath = $8::int, modification_date = NOW() WHERE name " + "= $1"); } else { prepareStatement("save_player", - "INSERT INTO player(name, pitch, yaw, posX, posY, posZ, hp, breath) VALUES " + "INSERT INTO player(name, pitch, yaw, posX, posY, posZ, " + "hp, breath) VALUES " "($1, $2, $3, $4, $5, $6, $7::int, $8::int)" - "ON CONFLICT ON CONSTRAINT player_pkey DO UPDATE SET pitch = $2, yaw = $3, " - "posX = $4, posY = $5, posZ = $6, hp = $7::int, breath = $8::int, " + "ON CONFLICT ON CONSTRAINT player_pkey DO UPDATE SET " + "pitch = $2, yaw = $3, " + "posX = $4, posY = $5, posZ = $6, hp = $7::int, breath = " + "$8::int, " "modification_date = NOW()"); } @@ -403,46 +399,47 @@ void PlayerDatabasePostgreSQL::initStatements() prepareStatement("load_player_list", "SELECT name FROM player"); prepareStatement("remove_player_inventories", - "DELETE FROM player_inventories WHERE player = $1"); + "DELETE FROM player_inventories WHERE player = $1"); prepareStatement("remove_player_inventory_items", - "DELETE FROM player_inventory_items WHERE player = $1"); + "DELETE FROM player_inventory_items WHERE player = $1"); prepareStatement("add_player_inventory", - "INSERT INTO player_inventories (player, inv_id, inv_width, inv_name, inv_size) VALUES " + "INSERT INTO player_inventories (player, inv_id, inv_width, " + "inv_name, inv_size) VALUES " "($1, $2::int, $3::int, $4, $5::int)"); prepareStatement("add_player_inventory_item", - "INSERT INTO player_inventory_items (player, inv_id, slot_id, item) VALUES " + "INSERT INTO player_inventory_items (player, inv_id, slot_id, " + "item) VALUES " "($1, $2::int, $3::int, $4)"); - prepareStatement("load_player_inventories", - "SELECT inv_id, inv_width, inv_name, inv_size FROM player_inventories " - "WHERE player = $1 ORDER BY inv_id"); + prepareStatement("load_player_inventories", "SELECT inv_id, inv_width, inv_name, " + "inv_size FROM player_inventories " + "WHERE player = $1 ORDER BY inv_id"); prepareStatement("load_player_inventory_items", - "SELECT slot_id, item FROM player_inventory_items WHERE " + "SELECT slot_id, item FROM player_inventory_items WHERE " "player = $1 AND inv_id = $2::int"); - prepareStatement("load_player", - "SELECT pitch, yaw, posX, posY, posZ, hp, breath FROM player WHERE name = $1"); + prepareStatement("load_player", "SELECT pitch, yaw, posX, posY, posZ, hp, breath " + "FROM player WHERE name = $1"); prepareStatement("remove_player_metadata", - "DELETE FROM player_metadata WHERE player = $1"); + "DELETE FROM player_metadata WHERE player = $1"); - prepareStatement("save_player_metadata", - "INSERT INTO player_metadata (player, attr, value) VALUES ($1, $2, $3)"); + prepareStatement("save_player_metadata", "INSERT INTO player_metadata (player, " + "attr, value) VALUES ($1, $2, $3)"); prepareStatement("load_player_metadata", - "SELECT attr, value FROM player_metadata WHERE player = $1"); - + "SELECT attr, value FROM player_metadata WHERE player = $1"); } bool PlayerDatabasePostgreSQL::playerDataExists(const std::string &playername) { verifyDatabase(); - const char *values[] = { playername.c_str() }; + const char *values[] = {playername.c_str()}; PGresult *results = execPrepared("load_player", 1, values, false); bool res = (PQntuples(results) > 0); @@ -452,7 +449,7 @@ bool PlayerDatabasePostgreSQL::playerDataExists(const std::string &playername) void PlayerDatabasePostgreSQL::savePlayer(RemotePlayer *player) { - PlayerSAO* sao = player->getPlayerSAO(); + PlayerSAO *sao = player->getPlayerSAO(); if (!sao) return; @@ -466,16 +463,11 @@ void PlayerDatabasePostgreSQL::savePlayer(RemotePlayer *player) std::string posz = ftos(pos.Z); std::string hp = itos(sao->getHP()); std::string breath = itos(sao->getBreath()); - const char *values[] = { - player->getName(), - pitch.c_str(), - yaw.c_str(), - posx.c_str(), posy.c_str(), posz.c_str(), - hp.c_str(), - breath.c_str() - }; + const char *values[] = {player->getName(), pitch.c_str(), yaw.c_str(), + posx.c_str(), posy.c_str(), posz.c_str(), hp.c_str(), + breath.c_str()}; - const char* rmvalues[] = { player->getName() }; + const char *rmvalues[] = {player->getName()}; beginSave(); if (getPGVersion() < 90500) { @@ -483,28 +475,23 @@ void PlayerDatabasePostgreSQL::savePlayer(RemotePlayer *player) execPrepared("create_player", 8, values, true, false); else execPrepared("update_player", 8, values, true, false); - } - else + } else execPrepared("save_player", 8, values, true, false); // Write player inventories execPrepared("remove_player_inventories", 1, rmvalues); execPrepared("remove_player_inventory_items", 1, rmvalues); - std::vector inventory_lists = sao->getInventory()->getLists(); + std::vector inventory_lists = + sao->getInventory()->getLists(); for (u16 i = 0; i < inventory_lists.size(); i++) { - const InventoryList* list = inventory_lists[i]; + const InventoryList *list = inventory_lists[i]; const std::string &name = list->getName(); - std::string width = itos(list->getWidth()), - inv_id = itos(i), lsize = itos(list->getSize()); + std::string width = itos(list->getWidth()), inv_id = itos(i), + lsize = itos(list->getSize()); - const char* inv_values[] = { - player->getName(), - inv_id.c_str(), - width.c_str(), - name.c_str(), - lsize.c_str() - }; + const char *inv_values[] = {player->getName(), inv_id.c_str(), + width.c_str(), name.c_str(), lsize.c_str()}; execPrepared("add_player_inventory", 5, inv_values); for (u32 j = 0; j < list->getSize(); j++) { @@ -512,12 +499,8 @@ void PlayerDatabasePostgreSQL::savePlayer(RemotePlayer *player) list->getItem(j).serialize(os); std::string itemStr = os.str(), slotId = itos(j); - const char* invitem_values[] = { - player->getName(), - inv_id.c_str(), - slotId.c_str(), - itemStr.c_str() - }; + const char *invitem_values[] = {player->getName(), inv_id.c_str(), + slotId.c_str(), itemStr.c_str()}; execPrepared("add_player_inventory_item", 4, invitem_values); } } @@ -525,11 +508,8 @@ void PlayerDatabasePostgreSQL::savePlayer(RemotePlayer *player) execPrepared("remove_player_metadata", 1, rmvalues); const StringMap &attrs = sao->getMeta().getStrings(); for (const auto &attr : attrs) { - const char *meta_values[] = { - player->getName(), - attr.first.c_str(), - attr.second.c_str() - }; + const char *meta_values[] = {player->getName(), attr.first.c_str(), + attr.second.c_str()}; execPrepared("save_player_metadata", 3, meta_values); } endSave(); @@ -542,7 +522,7 @@ bool PlayerDatabasePostgreSQL::loadPlayer(RemotePlayer *player, PlayerSAO *sao) sanity_check(sao); verifyDatabase(); - const char *values[] = { player->getName() }; + const char *values[] = {player->getName()}; PGresult *results = execPrepared("load_player", 1, values, false, false); // Player not found, return not found @@ -553,13 +533,10 @@ bool PlayerDatabasePostgreSQL::loadPlayer(RemotePlayer *player, PlayerSAO *sao) sao->setLookPitch(pg_to_float(results, 0, 0)); sao->setRotation(v3f(0, pg_to_float(results, 0, 1), 0)); - sao->setBasePosition(v3f( - pg_to_float(results, 0, 2), - pg_to_float(results, 0, 3), - pg_to_float(results, 0, 4)) - ); - sao->setHPRaw((u16) pg_to_int(results, 0, 5)); - sao->setBreath((u16) pg_to_int(results, 0, 6), false); + sao->setBasePosition(v3f(pg_to_float(results, 0, 2), pg_to_float(results, 0, 3), + pg_to_float(results, 0, 4))); + sao->setHPRaw((u16)pg_to_int(results, 0, 5)); + sao->setBreath((u16)pg_to_int(results, 0, 6), false); PQclear(results); @@ -569,19 +546,16 @@ bool PlayerDatabasePostgreSQL::loadPlayer(RemotePlayer *player, PlayerSAO *sao) int resultCount = PQntuples(results); for (int row = 0; row < resultCount; ++row) { - InventoryList* invList = player->inventory. - addList(PQgetvalue(results, row, 2), pg_to_uint(results, row, 3)); + InventoryList *invList = player->inventory.addList( + PQgetvalue(results, row, 2), pg_to_uint(results, row, 3)); invList->setWidth(pg_to_uint(results, row, 1)); u32 invId = pg_to_uint(results, row, 0); std::string invIdStr = itos(invId); - const char* values2[] = { - player->getName(), - invIdStr.c_str() - }; - PGresult *results2 = execPrepared("load_player_inventory_items", 2, - values2, false, false); + const char *values2[] = {player->getName(), invIdStr.c_str()}; + PGresult *results2 = execPrepared( + "load_player_inventory_items", 2, values2, false, false); int resultCount2 = PQntuples(results2); for (int row2 = 0; row2 < resultCount2; row2++) { @@ -601,7 +575,8 @@ bool PlayerDatabasePostgreSQL::loadPlayer(RemotePlayer *player, PlayerSAO *sao) int numrows = PQntuples(results); for (int row = 0; row < numrows; row++) { - sao->getMeta().setString(PQgetvalue(results, row, 0), PQgetvalue(results, row, 1)); + sao->getMeta().setString( + PQgetvalue(results, row, 0), PQgetvalue(results, row, 1)); } sao->getMeta().setModified(false); @@ -617,7 +592,7 @@ bool PlayerDatabasePostgreSQL::removePlayer(const std::string &name) verifyDatabase(); - const char *values[] = { name.c_str() }; + const char *values[] = {name.c_str()}; execPrepared("remove_player", 1, values); return true; @@ -644,43 +619,48 @@ AuthDatabasePostgreSQL::AuthDatabasePostgreSQL(const std::string &connect_string void AuthDatabasePostgreSQL::createDatabase() { - createTableIfNotExists("auth", - "CREATE TABLE auth (" - "id SERIAL," - "name TEXT UNIQUE," - "password TEXT," - "last_login INT NOT NULL DEFAULT 0," - "PRIMARY KEY (id)" - ");"); + createTableIfNotExists("auth", "CREATE TABLE auth (" + "id SERIAL," + "name TEXT UNIQUE," + "password TEXT," + "last_login INT NOT NULL DEFAULT 0," + "PRIMARY KEY (id)" + ");"); - createTableIfNotExists("user_privileges", - "CREATE TABLE user_privileges (" - "id INT," - "privilege TEXT," - "PRIMARY KEY (id, privilege)," - "CONSTRAINT fk_id FOREIGN KEY (id) REFERENCES auth (id) ON DELETE CASCADE" - ");"); + createTableIfNotExists("user_privileges", "CREATE TABLE user_privileges (" + "id INT," + "privilege TEXT," + "PRIMARY KEY (id, privilege)," + "CONSTRAINT fk_id FOREIGN KEY (id) " + "REFERENCES auth (id) ON DELETE CASCADE" + ");"); } void AuthDatabasePostgreSQL::initStatements() { - prepareStatement("auth_read", "SELECT id, name, password, last_login FROM auth WHERE name = $1"); - prepareStatement("auth_write", "UPDATE auth SET name = $1, password = $2, last_login = $3 WHERE id = $4"); - prepareStatement("auth_create", "INSERT INTO auth (name, password, last_login) VALUES ($1, $2, $3) RETURNING id"); + prepareStatement("auth_read", "SELECT id, name, password, last_login FROM auth " + "WHERE name = $1"); + prepareStatement("auth_write", "UPDATE auth SET name = $1, password = $2, " + "last_login = $3 WHERE id = $4"); + prepareStatement("auth_create", "INSERT INTO auth (name, password, last_login) " + "VALUES ($1, $2, $3) RETURNING id"); prepareStatement("auth_delete", "DELETE FROM auth WHERE name = $1"); prepareStatement("auth_list_names", "SELECT name FROM auth ORDER BY name DESC"); - prepareStatement("auth_read_privs", "SELECT privilege FROM user_privileges WHERE id = $1"); - prepareStatement("auth_write_privs", "INSERT INTO user_privileges (id, privilege) VALUES ($1, $2)"); - prepareStatement("auth_delete_privs", "DELETE FROM user_privileges WHERE id = $1"); + prepareStatement("auth_read_privs", + "SELECT privilege FROM user_privileges WHERE id = $1"); + prepareStatement("auth_write_privs", + "INSERT INTO user_privileges (id, privilege) VALUES ($1, $2)"); + prepareStatement( + "auth_delete_privs", "DELETE FROM user_privileges WHERE id = $1"); } bool AuthDatabasePostgreSQL::getAuth(const std::string &name, AuthEntry &res) { verifyDatabase(); - const char *values[] = { name.c_str() }; + const char *values[] = {name.c_str()}; PGresult *result = execPrepared("auth_read", 1, values, false, false); int numrows = PQntuples(result); if (numrows == 0) { @@ -696,7 +676,7 @@ bool AuthDatabasePostgreSQL::getAuth(const std::string &name, AuthEntry &res) PQclear(result); std::string playerIdStr = itos(res.id); - const char *privsValues[] = { playerIdStr.c_str() }; + const char *privsValues[] = {playerIdStr.c_str()}; PGresult *results = execPrepared("auth_read_privs", 1, privsValues, false); numrows = PQntuples(results); @@ -717,10 +697,10 @@ bool AuthDatabasePostgreSQL::saveAuth(const AuthEntry &authEntry) std::string lastLoginStr = itos(authEntry.last_login); std::string idStr = itos(authEntry.id); const char *values[] = { - authEntry.name.c_str() , - authEntry.password.c_str(), - lastLoginStr.c_str(), - idStr.c_str(), + authEntry.name.c_str(), + authEntry.password.c_str(), + lastLoginStr.c_str(), + idStr.c_str(), }; execPrepared("auth_write", 4, values); @@ -735,11 +715,8 @@ bool AuthDatabasePostgreSQL::createAuth(AuthEntry &authEntry) verifyDatabase(); std::string lastLoginStr = itos(authEntry.last_login); - const char *values[] = { - authEntry.name.c_str() , - authEntry.password.c_str(), - lastLoginStr.c_str() - }; + const char *values[] = {authEntry.name.c_str(), authEntry.password.c_str(), + lastLoginStr.c_str()}; beginSave(); @@ -747,7 +724,8 @@ bool AuthDatabasePostgreSQL::createAuth(AuthEntry &authEntry) int numrows = PQntuples(result); if (numrows == 0) { - errorstream << "Strange behaviour on auth creation, no ID returned." << std::endl; + errorstream << "Strange behaviour on auth creation, no ID returned." + << std::endl; PQclear(result); rollback(); return false; @@ -766,7 +744,7 @@ bool AuthDatabasePostgreSQL::deleteAuth(const std::string &name) { verifyDatabase(); - const char *values[] = { name.c_str() }; + const char *values[] = {name.c_str()}; execPrepared("auth_delete", 1, values); // privileges deleted by foreign key on delete cascade @@ -777,8 +755,8 @@ void AuthDatabasePostgreSQL::listNames(std::vector &res) { verifyDatabase(); - PGresult *results = execPrepared("auth_list_names", 0, - NULL, NULL, NULL, false, false); + PGresult *results = execPrepared( + "auth_list_names", 0, NULL, NULL, NULL, false, false); int numrows = PQntuples(results); @@ -796,14 +774,13 @@ void AuthDatabasePostgreSQL::reload() void AuthDatabasePostgreSQL::writePrivileges(const AuthEntry &authEntry) { std::string authIdStr = itos(authEntry.id); - const char *values[] = { authIdStr.c_str() }; + const char *values[] = {authIdStr.c_str()}; execPrepared("auth_delete_privs", 1, values); for (const std::string &privilege : authEntry.privileges) { - const char *values[] = { authIdStr.c_str(), privilege.c_str() }; + const char *values[] = {authIdStr.c_str(), privilege.c_str()}; execPrepared("auth_write_privs", 2, values); } } - #endif // USE_POSTGRESQL diff --git a/src/database/database-postgresql.h b/src/database/database-postgresql.h index f47deda33..36940b112 100644 --- a/src/database/database-postgresql.h +++ b/src/database/database-postgresql.h @@ -26,7 +26,7 @@ with this program; if not, write to the Free Software Foundation, Inc., class Settings; -class Database_PostgreSQL: public Database +class Database_PostgreSQL : public Database { public: Database_PostgreSQL(const std::string &connect_string); @@ -38,7 +38,6 @@ public: bool initialized() const; - protected: // Conversion helpers inline int pg_to_int(PGresult *res, int row, int col) @@ -48,41 +47,41 @@ protected: inline u32 pg_to_uint(PGresult *res, int row, int col) { - return (u32) atoi(PQgetvalue(res, row, col)); + return (u32)atoi(PQgetvalue(res, row, col)); } inline float pg_to_float(PGresult *res, int row, int col) { - return (float) atof(PQgetvalue(res, row, col)); + return (float)atof(PQgetvalue(res, row, col)); } inline v3s16 pg_to_v3s16(PGresult *res, int row, int col) { - return v3s16( - pg_to_int(res, row, col), - pg_to_int(res, row, col + 1), - pg_to_int(res, row, col + 2) - ); + return v3s16(pg_to_int(res, row, col), pg_to_int(res, row, col + 1), + pg_to_int(res, row, col + 2)); } inline PGresult *execPrepared(const char *stmtName, const int paramsNumber, - const void **params, - const int *paramsLengths = NULL, const int *paramsFormats = NULL, - bool clear = true, bool nobinary = true) + const void **params, const int *paramsLengths = NULL, + const int *paramsFormats = NULL, bool clear = true, + bool nobinary = true) { return checkResults(PQexecPrepared(m_conn, stmtName, paramsNumber, - (const char* const*) params, paramsLengths, paramsFormats, - nobinary ? 1 : 0), clear); + (const char *const *)params, + paramsLengths, paramsFormats, + nobinary ? 1 : 0), + clear); } inline PGresult *execPrepared(const char *stmtName, const int paramsNumber, - const char **params, bool clear = true, bool nobinary = true) + const char **params, bool clear = true, bool nobinary = true) { - return execPrepared(stmtName, paramsNumber, - (const void **)params, NULL, NULL, clear, nobinary); + return execPrepared(stmtName, paramsNumber, (const void **)params, NULL, + NULL, clear, nobinary); } - void createTableIfNotExists(const std::string &table_name, const std::string &definition); + void createTableIfNotExists( + const std::string &table_name, const std::string &definition); void verifyDatabase(); // Database initialization @@ -95,6 +94,7 @@ protected: } const int getPGVersion() const { return m_pgversion; } + private: // Database connectivity checks void ping(); diff --git a/src/database/database-redis.cpp b/src/database/database-redis.cpp index 096ea504d..e75063f73 100644 --- a/src/database/database-redis.cpp +++ b/src/database/database-redis.cpp @@ -31,7 +31,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include - Database_Redis::Database_Redis(Settings &conf) { std::string tmp; @@ -39,13 +38,15 @@ Database_Redis::Database_Redis(Settings &conf) tmp = conf.get("redis_address"); hash = conf.get("redis_hash"); } catch (SettingNotFoundException &) { - throw SettingNotFoundException("Set redis_address and " - "redis_hash in world.mt to use the redis backend"); + throw SettingNotFoundException( + "Set redis_address and " + "redis_hash in world.mt to use the redis backend"); } const char *addr = tmp.c_str(); int port = conf.exists("redis_port") ? conf.getU16("redis_port") : 6379; // if redis_address contains '/' assume unix socket, else hostname/ip - ctx = tmp.find('/') != std::string::npos ? redisConnectUnix(addr) : redisConnect(addr, port); + ctx = tmp.find('/') != std::string::npos ? redisConnectUnix(addr) + : redisConnect(addr, port); if (!ctx) { throw DatabaseException("Cannot allocate redis context"); } else if (ctx->err) { @@ -55,11 +56,13 @@ Database_Redis::Database_Redis(Settings &conf) } if (conf.exists("redis_password")) { tmp = conf.get("redis_password"); - redisReply *reply = static_cast(redisCommand(ctx, "AUTH %s", tmp.c_str())); + redisReply *reply = static_cast( + redisCommand(ctx, "AUTH %s", tmp.c_str())); if (!reply) throw DatabaseException("Redis authentication failed"); if (reply->type == REDIS_REPLY_ERROR) { - std::string err = "Redis authentication failed: " + std::string(reply->str, reply->len); + std::string err = "Redis authentication failed: " + + std::string(reply->str, reply->len); freeReplyObject(reply); throw DatabaseException(err); } @@ -72,20 +75,22 @@ Database_Redis::~Database_Redis() redisFree(ctx); } -void Database_Redis::beginSave() { +void Database_Redis::beginSave() +{ redisReply *reply = static_cast(redisCommand(ctx, "MULTI")); if (!reply) { - throw DatabaseException(std::string( - "Redis command 'MULTI' failed: ") + ctx->errstr); + throw DatabaseException(std::string("Redis command 'MULTI' failed: ") + + ctx->errstr); } freeReplyObject(reply); } -void Database_Redis::endSave() { +void Database_Redis::endSave() +{ redisReply *reply = static_cast(redisCommand(ctx, "EXEC")); if (!reply) { - throw DatabaseException(std::string( - "Redis command 'EXEC' failed: ") + ctx->errstr); + throw DatabaseException(std::string("Redis command 'EXEC' failed: ") + + ctx->errstr); } freeReplyObject(reply); } @@ -98,14 +103,16 @@ bool Database_Redis::saveBlock(const v3s16 &pos, const std::string &data) hash.c_str(), tmp.c_str(), data.c_str(), data.size())); if (!reply) { warningstream << "saveBlock: redis command 'HSET' failed on " - "block " << PP(pos) << ": " << ctx->errstr << std::endl; + "block " + << PP(pos) << ": " << ctx->errstr << std::endl; freeReplyObject(reply); return false; } if (reply->type == REDIS_REPLY_ERROR) { warningstream << "saveBlock: saving block " << PP(pos) - << " failed: " << std::string(reply->str, reply->len) << std::endl; + << " failed: " << std::string(reply->str, reply->len) + << std::endl; freeReplyObject(reply); return false; } @@ -117,12 +124,13 @@ bool Database_Redis::saveBlock(const v3s16 &pos, const std::string &data) void Database_Redis::loadBlock(const v3s16 &pos, std::string *block) { std::string tmp = i64tos(getBlockAsInteger(pos)); - redisReply *reply = static_cast(redisCommand(ctx, - "HGET %s %s", hash.c_str(), tmp.c_str())); + redisReply *reply = static_cast( + redisCommand(ctx, "HGET %s %s", hash.c_str(), tmp.c_str())); if (!reply) { - throw DatabaseException(std::string( - "Redis command 'HGET %s %s' failed: ") + ctx->errstr); + throw DatabaseException( + std::string("Redis command 'HGET %s %s' failed: ") + + ctx->errstr); } switch (reply->type) { @@ -136,9 +144,10 @@ void Database_Redis::loadBlock(const v3s16 &pos, std::string *block) std::string errstr(reply->str, reply->len); freeReplyObject(reply); errorstream << "loadBlock: loading block " << PP(pos) - << " failed: " << errstr << std::endl; - throw DatabaseException(std::string( - "Redis command 'HGET %s %s' errored: ") + errstr); + << " failed: " << errstr << std::endl; + throw DatabaseException( + std::string("Redis command 'HGET %s %s' errored: ") + + errstr); } case REDIS_REPLY_NIL: { *block = ""; @@ -149,25 +158,27 @@ void Database_Redis::loadBlock(const v3s16 &pos, std::string *block) } errorstream << "loadBlock: loading block " << PP(pos) - << " returned invalid reply type " << reply->type - << ": " << std::string(reply->str, reply->len) << std::endl; + << " returned invalid reply type " << reply->type << ": " + << std::string(reply->str, reply->len) << std::endl; freeReplyObject(reply); - throw DatabaseException(std::string( - "Redis command 'HGET %s %s' gave invalid reply.")); + throw DatabaseException( + std::string("Redis command 'HGET %s %s' gave invalid reply.")); } bool Database_Redis::deleteBlock(const v3s16 &pos) { std::string tmp = i64tos(getBlockAsInteger(pos)); - redisReply *reply = static_cast(redisCommand(ctx, - "HDEL %s %s", hash.c_str(), tmp.c_str())); + redisReply *reply = static_cast( + redisCommand(ctx, "HDEL %s %s", hash.c_str(), tmp.c_str())); if (!reply) { - throw DatabaseException(std::string( - "Redis command 'HDEL %s %s' failed: ") + ctx->errstr); + throw DatabaseException( + std::string("Redis command 'HDEL %s %s' failed: ") + + ctx->errstr); } else if (reply->type == REDIS_REPLY_ERROR) { warningstream << "deleteBlock: deleting block " << PP(pos) - << " failed: " << std::string(reply->str, reply->len) << std::endl; + << " failed: " << std::string(reply->str, reply->len) + << std::endl; freeReplyObject(reply); return false; } @@ -178,10 +189,11 @@ bool Database_Redis::deleteBlock(const v3s16 &pos) void Database_Redis::listAllLoadableBlocks(std::vector &dst) { - redisReply *reply = static_cast(redisCommand(ctx, "HKEYS %s", hash.c_str())); + redisReply *reply = static_cast( + redisCommand(ctx, "HKEYS %s", hash.c_str())); if (!reply) { - throw DatabaseException(std::string( - "Redis command 'HKEYS %s' failed: ") + ctx->errstr); + throw DatabaseException(std::string("Redis command 'HKEYS %s' failed: ") + + ctx->errstr); } switch (reply->type) { case REDIS_REPLY_ARRAY: @@ -192,12 +204,11 @@ void Database_Redis::listAllLoadableBlocks(std::vector &dst) } break; case REDIS_REPLY_ERROR: - throw DatabaseException(std::string( - "Failed to get keys from database: ") + - std::string(reply->str, reply->len)); + throw DatabaseException( + std::string("Failed to get keys from database: ") + + std::string(reply->str, reply->len)); } freeReplyObject(reply); } #endif // USE_REDIS - diff --git a/src/database/database-sqlite3.cpp b/src/database/database-sqlite3.cpp index 4560743b9..5a9cee5ce 100644 --- a/src/database/database-sqlite3.cpp +++ b/src/database/database-sqlite3.cpp @@ -24,7 +24,6 @@ SQLite format specification: BLOB data */ - #include "database-sqlite3.h" #include "log.h" @@ -40,32 +39,31 @@ SQLite format specification: // When to print messages when the database is being held locked by another process // Note: I've seen occasional delays of over 250ms while running minetestmapper. -#define BUSY_INFO_TRESHOLD 100 // Print first informational message after 100ms. -#define BUSY_WARNING_TRESHOLD 250 // Print warning message after 250ms. Lag is increased. -#define BUSY_ERROR_TRESHOLD 1000 // Print error message after 1000ms. Significant lag. -#define BUSY_FATAL_TRESHOLD 3000 // Allow SQLITE_BUSY to be returned, which will cause a minetest crash. -#define BUSY_ERROR_INTERVAL 10000 // Safety net: report again every 10 seconds +#define BUSY_INFO_TRESHOLD 100 // Print first informational message after 100ms. +#define BUSY_WARNING_TRESHOLD 250 // Print warning message after 250ms. Lag is increased. +#define BUSY_ERROR_TRESHOLD 1000 // Print error message after 1000ms. Significant lag. +#define BUSY_FATAL_TRESHOLD \ + 3000 // Allow SQLITE_BUSY to be returned, which will cause a minetest crash. +#define BUSY_ERROR_INTERVAL 10000 // Safety net: report again every 10 seconds - -#define SQLRES(s, r, m) \ - if ((s) != (r)) { \ - throw DatabaseException(std::string(m) + ": " +\ - sqlite3_errmsg(m_database)); \ +#define SQLRES(s, r, m) \ + if ((s) != (r)) { \ + throw DatabaseException( \ + std::string(m) + ": " + sqlite3_errmsg(m_database)); \ } #define SQLOK(s, m) SQLRES(s, SQLITE_OK, m) -#define PREPARE_STATEMENT(name, query) \ - SQLOK(sqlite3_prepare_v2(m_database, query, -1, &m_stmt_##name, NULL),\ - "Failed to prepare query '" query "'") +#define PREPARE_STATEMENT(name, query) \ + SQLOK(sqlite3_prepare_v2(m_database, query, -1, &m_stmt_##name, NULL), \ + "Failed to prepare query '" query "'") -#define SQLOK_ERRSTREAM(s, m) \ - if ((s) != SQLITE_OK) { \ - errorstream << (m) << ": " \ - << sqlite3_errmsg(m_database) << std::endl; \ +#define SQLOK_ERRSTREAM(s, m) \ + if ((s) != SQLITE_OK) { \ + errorstream << (m) << ": " << sqlite3_errmsg(m_database) << std::endl; \ } -#define FINALIZE_STATEMENT(statement) SQLOK_ERRSTREAM(sqlite3_finalize(statement), \ - "Failed to finalize " #statement) +#define FINALIZE_STATEMENT(statement) \ + SQLOK_ERRSTREAM(sqlite3_finalize(statement), "Failed to finalize " #statement) int Database_SQLite3::busyHandler(void *data, int count) { @@ -78,7 +76,7 @@ int Database_SQLite3::busyHandler(void *data, int count) prev_time = first_time; } else { while (cur_time < prev_time) - cur_time += s64(1)<<32; + cur_time += s64(1) << 32; } if (cur_time - first_time < BUSY_INFO_TRESHOLD) { @@ -86,24 +84,25 @@ int Database_SQLite3::busyHandler(void *data, int count) } else if (cur_time - first_time >= BUSY_INFO_TRESHOLD && prev_time - first_time < BUSY_INFO_TRESHOLD) { infostream << "SQLite3 database has been locked for " - << cur_time - first_time << " ms." << std::endl; + << cur_time - first_time << " ms." << std::endl; } else if (cur_time - first_time >= BUSY_WARNING_TRESHOLD && prev_time - first_time < BUSY_WARNING_TRESHOLD) { warningstream << "SQLite3 database has been locked for " - << cur_time - first_time << " ms." << std::endl; + << cur_time - first_time << " ms." << std::endl; } else if (cur_time - first_time >= BUSY_ERROR_TRESHOLD && prev_time - first_time < BUSY_ERROR_TRESHOLD) { errorstream << "SQLite3 database has been locked for " - << cur_time - first_time << " ms; this causes lag." << std::endl; + << cur_time - first_time << " ms; this causes lag." + << std::endl; } else if (cur_time - first_time >= BUSY_FATAL_TRESHOLD && prev_time - first_time < BUSY_FATAL_TRESHOLD) { errorstream << "SQLite3 database has been locked for " - << cur_time - first_time << " ms - giving up!" << std::endl; + << cur_time - first_time << " ms - giving up!" << std::endl; } else if ((cur_time - first_time) / BUSY_ERROR_INTERVAL != (prev_time - first_time) / BUSY_ERROR_INTERVAL) { // Safety net: keep reporting every BUSY_ERROR_INTERVAL errorstream << "SQLite3 database has been locked for " - << (cur_time - first_time) / 1000 << " seconds!" << std::endl; + << (cur_time - first_time) / 1000 << " seconds!" << std::endl; } prev_time = cur_time; @@ -112,10 +111,10 @@ int Database_SQLite3::busyHandler(void *data, int count) return cur_time - first_time < BUSY_FATAL_TRESHOLD; } - -Database_SQLite3::Database_SQLite3(const std::string &savedir, const std::string &dbname) : - m_savedir(savedir), - m_dbname(dbname) +Database_SQLite3::Database_SQLite3( + const std::string &savedir, const std::string &dbname) : + m_savedir(savedir), + m_dbname(dbname) { } @@ -123,7 +122,7 @@ void Database_SQLite3::beginSave() { verifyDatabase(); SQLRES(sqlite3_step(m_stmt_begin), SQLITE_DONE, - "Failed to start SQLite3 transaction"); + "Failed to start SQLite3 transaction"); sqlite3_reset(m_stmt_begin); } @@ -131,13 +130,14 @@ void Database_SQLite3::endSave() { verifyDatabase(); SQLRES(sqlite3_step(m_stmt_end), SQLITE_DONE, - "Failed to commit SQLite3 transaction"); + "Failed to commit SQLite3 transaction"); sqlite3_reset(m_stmt_end); } void Database_SQLite3::openDatabase() { - if (m_database) return; + if (m_database) + return; std::string dbp = m_savedir + DIR_DELIM + m_dbname + ".sqlite"; @@ -145,35 +145,37 @@ void Database_SQLite3::openDatabase() if (!fs::CreateAllDirs(m_savedir)) { infostream << "Database_SQLite3: Failed to create directory \"" - << m_savedir << "\"" << std::endl; + << m_savedir << "\"" << std::endl; throw FileNotGoodException("Failed to create database " - "save directory"); + "save directory"); } bool needs_create = !fs::PathExists(dbp); SQLOK(sqlite3_open_v2(dbp.c_str(), &m_database, - SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL), - std::string("Failed to open SQLite3 database file ") + dbp); + SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL), + std::string("Failed to open SQLite3 database file ") + dbp); SQLOK(sqlite3_busy_handler(m_database, Database_SQLite3::busyHandler, - m_busy_handler_data), "Failed to set SQLite3 busy handler"); + m_busy_handler_data), + "Failed to set SQLite3 busy handler"); if (needs_create) { createDatabase(); } - std::string query_str = std::string("PRAGMA synchronous = ") - + itos(g_settings->getU16("sqlite_synchronous")); + std::string query_str = std::string("PRAGMA synchronous = ") + + itos(g_settings->getU16("sqlite_synchronous")); SQLOK(sqlite3_exec(m_database, query_str.c_str(), NULL, NULL, NULL), - "Failed to modify sqlite3 synchronous mode"); + "Failed to modify sqlite3 synchronous mode"); SQLOK(sqlite3_exec(m_database, "PRAGMA foreign_keys = ON", NULL, NULL, NULL), - "Failed to enable sqlite3 foreign key support"); + "Failed to enable sqlite3 foreign key support"); } void Database_SQLite3::verifyDatabase() { - if (m_initialized) return; + if (m_initialized) + return; openDatabase(); @@ -197,9 +199,8 @@ Database_SQLite3::~Database_SQLite3() * Map database */ -MapDatabaseSQLite3::MapDatabaseSQLite3(const std::string &savedir): - Database_SQLite3(savedir, "map"), - MapDatabase() +MapDatabaseSQLite3::MapDatabaseSQLite3(const std::string &savedir) : + Database_SQLite3(savedir, "map"), MapDatabase() { } @@ -211,25 +212,24 @@ MapDatabaseSQLite3::~MapDatabaseSQLite3() FINALIZE_STATEMENT(m_stmt_delete) } - void MapDatabaseSQLite3::createDatabase() { assert(m_database); // Pre-condition SQLOK(sqlite3_exec(m_database, - "CREATE TABLE IF NOT EXISTS `blocks` (\n" - " `pos` INT PRIMARY KEY,\n" - " `data` BLOB\n" - ");\n", - NULL, NULL, NULL), - "Failed to create database table"); + "CREATE TABLE IF NOT EXISTS `blocks` (\n" + " `pos` INT PRIMARY KEY,\n" + " `data` BLOB\n" + ");\n", + NULL, NULL, NULL), + "Failed to create database table"); } void MapDatabaseSQLite3::initStatements() { PREPARE_STATEMENT(read, "SELECT `data` FROM `blocks` WHERE `pos` = ? LIMIT 1"); #ifdef __ANDROID__ - PREPARE_STATEMENT(write, "INSERT INTO `blocks` (`pos`, `data`) VALUES (?, ?)"); + PREPARE_STATEMENT(write, "INSERT INTO `blocks` (`pos`, `data`) VALUES (?, ?)"); #else PREPARE_STATEMENT(write, "REPLACE INTO `blocks` (`pos`, `data`) VALUES (?, ?)"); #endif @@ -242,7 +242,8 @@ void MapDatabaseSQLite3::initStatements() inline void MapDatabaseSQLite3::bindPos(sqlite3_stmt *stmt, const v3s16 &pos, int index) { SQLOK(sqlite3_bind_int64(stmt, index, getBlockAsInteger(pos)), - "Internal error: failed to bind query at " __FILE__ ":" TOSTRING(__LINE__)); + "Internal error: failed to bind query at " __FILE__ + ":" TOSTRING(__LINE__)); } bool MapDatabaseSQLite3::deleteBlock(const v3s16 &pos) @@ -255,8 +256,8 @@ bool MapDatabaseSQLite3::deleteBlock(const v3s16 &pos) sqlite3_reset(m_stmt_delete); if (!good) { - warningstream << "deleteBlock: Block failed to delete " - << PP(pos) << ": " << sqlite3_errmsg(m_database) << std::endl; + warningstream << "deleteBlock: Block failed to delete " << PP(pos) << ": " + << sqlite3_errmsg(m_database) << std::endl; } return good; } @@ -280,7 +281,8 @@ bool MapDatabaseSQLite3::saveBlock(const v3s16 &pos, const std::string &data) bindPos(m_stmt_write, pos); SQLOK(sqlite3_bind_blob(m_stmt_write, 2, data.data(), data.size(), NULL), - "Internal error: failed to bind query at " __FILE__ ":" TOSTRING(__LINE__)); + "Internal error: failed to bind query at " __FILE__ + ":" TOSTRING(__LINE__)); SQLRES(sqlite3_step(m_stmt_write), SQLITE_DONE, "Failed to save block") sqlite3_reset(m_stmt_write); @@ -299,7 +301,7 @@ void MapDatabaseSQLite3::loadBlock(const v3s16 &pos, std::string *block) return; } - const char *data = (const char *) sqlite3_column_blob(m_stmt_read, 0); + const char *data = (const char *)sqlite3_column_blob(m_stmt_read, 0); size_t len = sqlite3_column_bytes(m_stmt_read, 0); *block = (data) ? std::string(data, len) : ""; @@ -323,118 +325,128 @@ void MapDatabaseSQLite3::listAllLoadableBlocks(std::vector &dst) * Player Database */ -PlayerDatabaseSQLite3::PlayerDatabaseSQLite3(const std::string &savedir): - Database_SQLite3(savedir, "players"), - PlayerDatabase() +PlayerDatabaseSQLite3::PlayerDatabaseSQLite3(const std::string &savedir) : + Database_SQLite3(savedir, "players"), PlayerDatabase() { } -PlayerDatabaseSQLite3::~PlayerDatabaseSQLite3() -{ - FINALIZE_STATEMENT(m_stmt_player_load) - FINALIZE_STATEMENT(m_stmt_player_add) - FINALIZE_STATEMENT(m_stmt_player_update) - FINALIZE_STATEMENT(m_stmt_player_remove) - FINALIZE_STATEMENT(m_stmt_player_list) - FINALIZE_STATEMENT(m_stmt_player_add_inventory) - FINALIZE_STATEMENT(m_stmt_player_add_inventory_items) - FINALIZE_STATEMENT(m_stmt_player_remove_inventory) - FINALIZE_STATEMENT(m_stmt_player_remove_inventory_items) - FINALIZE_STATEMENT(m_stmt_player_load_inventory) - FINALIZE_STATEMENT(m_stmt_player_load_inventory_items) - FINALIZE_STATEMENT(m_stmt_player_metadata_load) - FINALIZE_STATEMENT(m_stmt_player_metadata_add) - FINALIZE_STATEMENT(m_stmt_player_metadata_remove) -}; - +PlayerDatabaseSQLite3::~PlayerDatabaseSQLite3(){ + FINALIZE_STATEMENT(m_stmt_player_load) FINALIZE_STATEMENT(m_stmt_player_add) FINALIZE_STATEMENT( + m_stmt_player_update) FINALIZE_STATEMENT(m_stmt_player_remove) FINALIZE_STATEMENT(m_stmt_player_list) + FINALIZE_STATEMENT(m_stmt_player_add_inventory) FINALIZE_STATEMENT( + m_stmt_player_add_inventory_items) FINALIZE_STATEMENT(m_stmt_player_remove_inventory) + FINALIZE_STATEMENT(m_stmt_player_remove_inventory_items) FINALIZE_STATEMENT( + m_stmt_player_load_inventory) FINALIZE_STATEMENT(m_stmt_player_load_inventory_items) + FINALIZE_STATEMENT(m_stmt_player_metadata_load) FINALIZE_STATEMENT( + m_stmt_player_metadata_add) + FINALIZE_STATEMENT( + m_stmt_player_metadata_remove)}; void PlayerDatabaseSQLite3::createDatabase() { assert(m_database); // Pre-condition SQLOK(sqlite3_exec(m_database, - "CREATE TABLE IF NOT EXISTS `player` (" - "`name` VARCHAR(50) NOT NULL," - "`pitch` NUMERIC(11, 4) NOT NULL," - "`yaw` NUMERIC(11, 4) NOT NULL," - "`posX` NUMERIC(11, 4) NOT NULL," - "`posY` NUMERIC(11, 4) NOT NULL," - "`posZ` NUMERIC(11, 4) NOT NULL," - "`hp` INT NOT NULL," - "`breath` INT NOT NULL," - "`creation_date` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP," - "`modification_date` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP," - "PRIMARY KEY (`name`));", - NULL, NULL, NULL), - "Failed to create player table"); + "CREATE TABLE IF NOT EXISTS `player` (" + "`name` VARCHAR(50) NOT NULL," + "`pitch` NUMERIC(11, 4) NOT NULL," + "`yaw` NUMERIC(11, 4) NOT NULL," + "`posX` NUMERIC(11, 4) NOT NULL," + "`posY` NUMERIC(11, 4) NOT NULL," + "`posZ` NUMERIC(11, 4) NOT NULL," + "`hp` INT NOT NULL," + "`breath` INT NOT NULL," + "`creation_date` DATETIME NOT NULL DEFAULT " + "CURRENT_TIMESTAMP," + "`modification_date` DATETIME NOT NULL DEFAULT " + "CURRENT_TIMESTAMP," + "PRIMARY KEY (`name`));", + NULL, NULL, NULL), + "Failed to create player table"); SQLOK(sqlite3_exec(m_database, - "CREATE TABLE IF NOT EXISTS `player_metadata` (" - " `player` VARCHAR(50) NOT NULL," - " `metadata` VARCHAR(256) NOT NULL," - " `value` TEXT," - " PRIMARY KEY(`player`, `metadata`)," - " FOREIGN KEY (`player`) REFERENCES player (`name`) ON DELETE CASCADE );", - NULL, NULL, NULL), - "Failed to create player metadata table"); + "CREATE TABLE IF NOT EXISTS `player_metadata` (" + " `player` VARCHAR(50) NOT NULL," + " `metadata` VARCHAR(256) NOT NULL," + " `value` TEXT," + " PRIMARY KEY(`player`, `metadata`)," + " FOREIGN KEY (`player`) REFERENCES player (`name`) ON " + "DELETE CASCADE );", + NULL, NULL, NULL), + "Failed to create player metadata table"); SQLOK(sqlite3_exec(m_database, - "CREATE TABLE IF NOT EXISTS `player_inventories` (" - " `player` VARCHAR(50) NOT NULL," - " `inv_id` INT NOT NULL," - " `inv_width` INT NOT NULL," - " `inv_name` TEXT NOT NULL DEFAULT ''," - " `inv_size` INT NOT NULL," - " PRIMARY KEY(player, inv_id)," - " FOREIGN KEY (`player`) REFERENCES player (`name`) ON DELETE CASCADE );", - NULL, NULL, NULL), - "Failed to create player inventory table"); + "CREATE TABLE IF NOT EXISTS `player_inventories` (" + " `player` VARCHAR(50) NOT NULL," + " `inv_id` INT NOT NULL," + " `inv_width` INT NOT NULL," + " `inv_name` TEXT NOT NULL DEFAULT ''," + " `inv_size` INT NOT NULL," + " PRIMARY KEY(player, inv_id)," + " FOREIGN KEY (`player`) REFERENCES player (`name`) ON " + "DELETE CASCADE );", + NULL, NULL, NULL), + "Failed to create player inventory table"); SQLOK(sqlite3_exec(m_database, - "CREATE TABLE `player_inventory_items` (" - " `player` VARCHAR(50) NOT NULL," - " `inv_id` INT NOT NULL," - " `slot_id` INT NOT NULL," - " `item` TEXT NOT NULL DEFAULT ''," - " PRIMARY KEY(player, inv_id, slot_id)," - " FOREIGN KEY (`player`) REFERENCES player (`name`) ON DELETE CASCADE );", - NULL, NULL, NULL), - "Failed to create player inventory items table"); + "CREATE TABLE `player_inventory_items` (" + " `player` VARCHAR(50) NOT NULL," + " `inv_id` INT NOT NULL," + " `slot_id` INT NOT NULL," + " `item` TEXT NOT NULL DEFAULT ''," + " PRIMARY KEY(player, inv_id, slot_id)," + " FOREIGN KEY (`player`) REFERENCES player (`name`) ON " + "DELETE CASCADE );", + NULL, NULL, NULL), + "Failed to create player inventory items table"); } void PlayerDatabaseSQLite3::initStatements() { - PREPARE_STATEMENT(player_load, "SELECT `pitch`, `yaw`, `posX`, `posY`, `posZ`, `hp`, " - "`breath`" - "FROM `player` WHERE `name` = ?") - PREPARE_STATEMENT(player_add, "INSERT INTO `player` (`name`, `pitch`, `yaw`, `posX`, " - "`posY`, `posZ`, `hp`, `breath`) VALUES (?, ?, ?, ?, ?, ?, ?, ?)") - PREPARE_STATEMENT(player_update, "UPDATE `player` SET `pitch` = ?, `yaw` = ?, " + PREPARE_STATEMENT(player_load, + "SELECT `pitch`, `yaw`, `posX`, `posY`, `posZ`, `hp`, " + "`breath`" + "FROM `player` WHERE `name` = ?") + PREPARE_STATEMENT(player_add, + "INSERT INTO `player` (`name`, `pitch`, `yaw`, `posX`, " + "`posY`, `posZ`, `hp`, `breath`) VALUES (?, ?, ?, ?, ?, ?, ?, ?)") + PREPARE_STATEMENT(player_update, + "UPDATE `player` SET `pitch` = ?, `yaw` = ?, " "`posX` = ?, `posY` = ?, `posZ` = ?, `hp` = ?, `breath` = ?, " "`modification_date` = CURRENT_TIMESTAMP WHERE `name` = ?") PREPARE_STATEMENT(player_remove, "DELETE FROM `player` WHERE `name` = ?") PREPARE_STATEMENT(player_list, "SELECT `name` FROM `player`") - PREPARE_STATEMENT(player_add_inventory, "INSERT INTO `player_inventories` " - "(`player`, `inv_id`, `inv_width`, `inv_name`, `inv_size`) VALUES (?, ?, ?, ?, ?)") - PREPARE_STATEMENT(player_add_inventory_items, "INSERT INTO `player_inventory_items` " - "(`player`, `inv_id`, `slot_id`, `item`) VALUES (?, ?, ?, ?)") + PREPARE_STATEMENT(player_add_inventory, + "INSERT INTO `player_inventories` " + "(`player`, `inv_id`, `inv_width`, `inv_name`, `inv_size`) " + "VALUES (?, ?, ?, ?, ?)") + PREPARE_STATEMENT(player_add_inventory_items, + "INSERT INTO `player_inventory_items` " + "(`player`, `inv_id`, `slot_id`, `item`) VALUES (?, ?, ?, ?)") PREPARE_STATEMENT(player_remove_inventory, "DELETE FROM `player_inventories` " - "WHERE `player` = ?") - PREPARE_STATEMENT(player_remove_inventory_items, "DELETE FROM `player_inventory_items` " - "WHERE `player` = ?") - PREPARE_STATEMENT(player_load_inventory, "SELECT `inv_id`, `inv_width`, `inv_name`, " - "`inv_size` FROM `player_inventories` WHERE `player` = ? ORDER BY inv_id") - PREPARE_STATEMENT(player_load_inventory_items, "SELECT `slot_id`, `item` " - "FROM `player_inventory_items` WHERE `player` = ? AND `inv_id` = ?") + "WHERE `player` = ?") + PREPARE_STATEMENT(player_remove_inventory_items, + "DELETE FROM `player_inventory_items` " + "WHERE `player` = ?") + PREPARE_STATEMENT(player_load_inventory, + "SELECT `inv_id`, `inv_width`, `inv_name`, " + "`inv_size` FROM `player_inventories` WHERE `player` = ? ORDER " + "BY inv_id") + PREPARE_STATEMENT(player_load_inventory_items, + "SELECT `slot_id`, `item` " + "FROM `player_inventory_items` WHERE `player` = ? AND `inv_id` = " + "?") PREPARE_STATEMENT(player_metadata_load, "SELECT `metadata`, `value` FROM " - "`player_metadata` WHERE `player` = ?") - PREPARE_STATEMENT(player_metadata_add, "INSERT INTO `player_metadata` " - "(`player`, `metadata`, `value`) VALUES (?, ?, ?)") + "`player_metadata` WHERE `player` = ?") + PREPARE_STATEMENT(player_metadata_add, + "INSERT INTO `player_metadata` " + "(`player`, `metadata`, `value`) VALUES (?, ?, ?)") PREPARE_STATEMENT(player_metadata_remove, "DELETE FROM `player_metadata` " - "WHERE `player` = ?") - verbosestream << "ServerEnvironment: SQLite3 database opened (players)." << std::endl; + "WHERE `player` = ?") + verbosestream << "ServerEnvironment: SQLite3 database opened (players)." + << std::endl; } bool PlayerDatabaseSQLite3::playerDataExists(const std::string &name) @@ -448,7 +460,7 @@ bool PlayerDatabaseSQLite3::playerDataExists(const std::string &name) void PlayerDatabaseSQLite3::savePlayer(RemotePlayer *player) { - PlayerSAO* sao = player->getPlayerSAO(); + PlayerSAO *sao = player->getPlayerSAO(); sanity_check(sao); const v3f &pos = sao->getBasePosition(); @@ -490,9 +502,10 @@ void PlayerDatabaseSQLite3::savePlayer(RemotePlayer *player) sqlite3_vrfy(sqlite3_step(m_stmt_player_remove_inventory_items), SQLITE_DONE); sqlite3_reset(m_stmt_player_remove_inventory_items); - std::vector inventory_lists = sao->getInventory()->getLists(); + std::vector inventory_lists = + sao->getInventory()->getLists(); for (u16 i = 0; i < inventory_lists.size(); i++) { - const InventoryList* list = inventory_lists[i]; + const InventoryList *list = inventory_lists[i]; str_to_sqlite(m_stmt_player_add_inventory, 1, player->getName()); int_to_sqlite(m_stmt_player_add_inventory, 2, i); @@ -507,11 +520,13 @@ void PlayerDatabaseSQLite3::savePlayer(RemotePlayer *player) list->getItem(j).serialize(os); std::string itemStr = os.str(); - str_to_sqlite(m_stmt_player_add_inventory_items, 1, player->getName()); + str_to_sqlite(m_stmt_player_add_inventory_items, 1, + player->getName()); int_to_sqlite(m_stmt_player_add_inventory_items, 2, i); int_to_sqlite(m_stmt_player_add_inventory_items, 3, j); str_to_sqlite(m_stmt_player_add_inventory_items, 4, itemStr); - sqlite3_vrfy(sqlite3_step(m_stmt_player_add_inventory_items), SQLITE_DONE); + sqlite3_vrfy(sqlite3_step(m_stmt_player_add_inventory_items), + SQLITE_DONE); sqlite3_reset(m_stmt_player_add_inventory_items); } } @@ -546,16 +561,16 @@ bool PlayerDatabaseSQLite3::loadPlayer(RemotePlayer *player, PlayerSAO *sao) sao->setLookPitch(sqlite_to_float(m_stmt_player_load, 0)); sao->setPlayerYaw(sqlite_to_float(m_stmt_player_load, 1)); sao->setBasePosition(sqlite_to_v3f(m_stmt_player_load, 2)); - sao->setHPRaw((u16) MYMIN(sqlite_to_int(m_stmt_player_load, 5), U16_MAX)); - sao->setBreath((u16) MYMIN(sqlite_to_int(m_stmt_player_load, 6), U16_MAX), false); + sao->setHPRaw((u16)MYMIN(sqlite_to_int(m_stmt_player_load, 5), U16_MAX)); + sao->setBreath((u16)MYMIN(sqlite_to_int(m_stmt_player_load, 6), U16_MAX), false); sqlite3_reset(m_stmt_player_load); // Load inventory str_to_sqlite(m_stmt_player_load_inventory, 1, player->getName()); while (sqlite3_step(m_stmt_player_load_inventory) == SQLITE_ROW) { InventoryList *invList = player->inventory.addList( - sqlite_to_string(m_stmt_player_load_inventory, 2), - sqlite_to_uint(m_stmt_player_load_inventory, 3)); + sqlite_to_string(m_stmt_player_load_inventory, 2), + sqlite_to_uint(m_stmt_player_load_inventory, 3)); invList->setWidth(sqlite_to_uint(m_stmt_player_load_inventory, 1)); u32 invId = sqlite_to_uint(m_stmt_player_load_inventory, 0); @@ -563,11 +578,15 @@ bool PlayerDatabaseSQLite3::loadPlayer(RemotePlayer *player, PlayerSAO *sao) str_to_sqlite(m_stmt_player_load_inventory_items, 1, player->getName()); int_to_sqlite(m_stmt_player_load_inventory_items, 2, invId); while (sqlite3_step(m_stmt_player_load_inventory_items) == SQLITE_ROW) { - const std::string itemStr = sqlite_to_string(m_stmt_player_load_inventory_items, 1); + const std::string itemStr = sqlite_to_string( + m_stmt_player_load_inventory_items, 1); if (itemStr.length() > 0) { ItemStack stack; stack.deSerialize(itemStr); - invList->changeItem(sqlite_to_uint(m_stmt_player_load_inventory_items, 0), stack); + invList->changeItem( + sqlite_to_uint(m_stmt_player_load_inventory_items, + 0), + stack); } } sqlite3_reset(m_stmt_player_load_inventory_items); @@ -635,37 +654,43 @@ void AuthDatabaseSQLite3::createDatabase() assert(m_database); // Pre-condition SQLOK(sqlite3_exec(m_database, - "CREATE TABLE IF NOT EXISTS `auth` (" - "`id` INTEGER PRIMARY KEY AUTOINCREMENT," - "`name` VARCHAR(32) UNIQUE," - "`password` VARCHAR(512)," - "`last_login` INTEGER" - ");", - NULL, NULL, NULL), - "Failed to create auth table"); + "CREATE TABLE IF NOT EXISTS `auth` (" + "`id` INTEGER PRIMARY KEY AUTOINCREMENT," + "`name` VARCHAR(32) UNIQUE," + "`password` VARCHAR(512)," + "`last_login` INTEGER" + ");", + NULL, NULL, NULL), + "Failed to create auth table"); SQLOK(sqlite3_exec(m_database, - "CREATE TABLE IF NOT EXISTS `user_privileges` (" - "`id` INTEGER," - "`privilege` VARCHAR(32)," - "PRIMARY KEY (id, privilege)" - "CONSTRAINT fk_id FOREIGN KEY (id) REFERENCES auth (id) ON DELETE CASCADE" - ");", - NULL, NULL, NULL), - "Failed to create auth privileges table"); + "CREATE TABLE IF NOT EXISTS `user_privileges` (" + "`id` INTEGER," + "`privilege` VARCHAR(32)," + "PRIMARY KEY (id, privilege)" + "CONSTRAINT fk_id FOREIGN KEY (id) REFERENCES auth (id) ON " + "DELETE CASCADE" + ");", + NULL, NULL, NULL), + "Failed to create auth privileges table"); } void AuthDatabaseSQLite3::initStatements() { - PREPARE_STATEMENT(read, "SELECT id, name, password, last_login FROM auth WHERE name = ?"); - PREPARE_STATEMENT(write, "UPDATE auth set name = ?, password = ?, last_login = ? WHERE id = ?"); - PREPARE_STATEMENT(create, "INSERT INTO auth (name, password, last_login) VALUES (?, ?, ?)"); + PREPARE_STATEMENT(read, + "SELECT id, name, password, last_login FROM auth WHERE name = ?"); + PREPARE_STATEMENT(write, "UPDATE auth set name = ?, password = ?, last_login = ? " + "WHERE id = ?"); + PREPARE_STATEMENT(create, + "INSERT INTO auth (name, password, last_login) VALUES (?, ?, ?)"); PREPARE_STATEMENT(delete, "DELETE FROM auth WHERE name = ?"); PREPARE_STATEMENT(list_names, "SELECT name FROM auth ORDER BY name DESC"); - PREPARE_STATEMENT(read_privs, "SELECT privilege FROM user_privileges WHERE id = ?"); - PREPARE_STATEMENT(write_privs, "INSERT OR IGNORE INTO user_privileges (id, privilege) VALUES (?, ?)"); + PREPARE_STATEMENT( + read_privs, "SELECT privilege FROM user_privileges WHERE id = ?"); + PREPARE_STATEMENT(write_privs, "INSERT OR IGNORE INTO user_privileges (id, " + "privilege) VALUES (?, ?)"); PREPARE_STATEMENT(delete_privs, "DELETE FROM user_privileges WHERE id = ?"); PREPARE_STATEMENT(last_insert_rowid, "SELECT last_insert_rowid()"); diff --git a/src/database/database-sqlite3.h b/src/database/database-sqlite3.h index d7202a918..ce568ae27 100644 --- a/src/database/database-sqlite3.h +++ b/src/database/database-sqlite3.h @@ -37,6 +37,7 @@ public: void endSave(); bool initialized() const { return m_initialized; } + protected: Database_SQLite3(const std::string &savedir, const std::string &dbname); @@ -61,7 +62,7 @@ protected: inline void int64_to_sqlite(sqlite3_stmt *s, int iCol, s64 val) const { - sqlite3_vrfy(sqlite3_bind_int64(s, iCol, (sqlite3_int64) val)); + sqlite3_vrfy(sqlite3_bind_int64(s, iCol, (sqlite3_int64)val)); } inline void double_to_sqlite(sqlite3_stmt *s, int iCol, double val) const @@ -71,7 +72,8 @@ protected: inline std::string sqlite_to_string(sqlite3_stmt *s, int iCol) { - const char* text = reinterpret_cast(sqlite3_column_text(s, iCol)); + const char *text = reinterpret_cast( + sqlite3_column_text(s, iCol)); return std::string(text ? text : ""); } @@ -82,22 +84,22 @@ protected: inline u32 sqlite_to_uint(sqlite3_stmt *s, int iCol) { - return (u32) sqlite3_column_int(s, iCol); + return (u32)sqlite3_column_int(s, iCol); } inline s64 sqlite_to_int64(sqlite3_stmt *s, int iCol) { - return (s64) sqlite3_column_int64(s, iCol); + return (s64)sqlite3_column_int64(s, iCol); } inline u64 sqlite_to_uint64(sqlite3_stmt *s, int iCol) { - return (u64) sqlite3_column_int64(s, iCol); + return (u64)sqlite3_column_int64(s, iCol); } inline float sqlite_to_float(sqlite3_stmt *s, int iCol) { - return (float) sqlite3_column_double(s, iCol); + return (float)sqlite3_column_double(s, iCol); } inline const v3f sqlite_to_v3f(sqlite3_stmt *s, int iCol) @@ -107,13 +109,15 @@ protected: } // Query verifiers helpers - inline void sqlite3_vrfy(int s, const std::string &m = "", int r = SQLITE_OK) const + inline void sqlite3_vrfy( + int s, const std::string &m = "", int r = SQLITE_OK) const { if (s != r) throw DatabaseException(m + ": " + sqlite3_errmsg(m_database)); } - inline void sqlite3_vrfy(const int s, const int r, const std::string &m = "") const + inline void sqlite3_vrfy( + const int s, const int r, const std::string &m = "") const { sqlite3_vrfy(s, m, r); } @@ -123,6 +127,7 @@ protected: virtual void initStatements() = 0; sqlite3 *m_database = nullptr; + private: // Open the database void openDatabase(); @@ -153,6 +158,7 @@ public: void beginSave() { Database_SQLite3::beginSave(); } void endSave() { Database_SQLite3::endSave(); } + protected: virtual void createDatabase(); virtual void initStatements(); diff --git a/src/database/database.cpp b/src/database/database.cpp index 12e0e1a0f..01cb2b5fc 100644 --- a/src/database/database.cpp +++ b/src/database/database.cpp @@ -20,7 +20,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "database.h" #include "irrlichttypes.h" - /**************** * Black magic! * **************** @@ -37,7 +36,6 @@ static inline s16 unsigned_to_signed(u16 i, u16 max_positive) return i - (max_positive * 2); } - // Modulo of a negative number does not work consistently in C static inline s64 pythonmodulo(s64 i, s16 mod) { @@ -47,15 +45,11 @@ static inline s64 pythonmodulo(s64 i, s16 mod) return mod - ((-i) % mod); } - s64 MapDatabase::getBlockAsInteger(const v3s16 &pos) { - return (u64) pos.Z * 0x1000000 + - (u64) pos.Y * 0x1000 + - (u64) pos.X; + return (u64)pos.Z * 0x1000000 + (u64)pos.Y * 0x1000 + (u64)pos.X; } - v3s16 MapDatabase::getIntegerAsBlock(s64 i) { v3s16 pos; @@ -66,4 +60,3 @@ v3s16 MapDatabase::getIntegerAsBlock(s64 i) pos.Z = unsigned_to_signed(pythonmodulo(i, 4096), 2048); return pos; } - diff --git a/src/daynightratio.h b/src/daynightratio.h index 538767cad..110ff855b 100644 --- a/src/daynightratio.h +++ b/src/daynightratio.h @@ -30,15 +30,15 @@ inline u32 time_to_daynight_ratio(float time_of_day, bool smooth) t = 24000.0f - t; const float values[9][2] = { - {4250.0f + 125.0f, 175.0f}, - {4500.0f + 125.0f, 175.0f}, - {4750.0f + 125.0f, 250.0f}, - {5000.0f + 125.0f, 350.0f}, - {5250.0f + 125.0f, 500.0f}, - {5500.0f + 125.0f, 675.0f}, - {5750.0f + 125.0f, 875.0f}, - {6000.0f + 125.0f, 1000.0f}, - {6250.0f + 125.0f, 1000.0f}, + {4250.0f + 125.0f, 175.0f}, + {4500.0f + 125.0f, 175.0f}, + {4750.0f + 125.0f, 250.0f}, + {5000.0f + 125.0f, 350.0f}, + {5250.0f + 125.0f, 500.0f}, + {5500.0f + 125.0f, 675.0f}, + {5750.0f + 125.0f, 875.0f}, + {6000.0f + 125.0f, 1000.0f}, + {6250.0f + 125.0f, 1000.0f}, }; if (!smooth) { diff --git a/src/debug.cpp b/src/debug.cpp index 3c82ed9e1..5fdfadba7 100644 --- a/src/debug.cpp +++ b/src/debug.cpp @@ -17,7 +17,6 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ - #include "porting.h" #include "debug.h" #include "exceptions.h" @@ -31,45 +30,48 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "config.h" #ifdef _MSC_VER - #include - #include "version.h" - #include "filesys.h" +#include +#include "version.h" +#include "filesys.h" #endif #if USE_CURSES - #include "terminal_chat_console.h" +#include "terminal_chat_console.h" #endif /* Assert */ -void sanity_check_fn(const char *assertion, const char *file, - unsigned int line, const char *function) +void sanity_check_fn(const char *assertion, const char *file, unsigned int line, + const char *function) { #if USE_CURSES g_term_console.stopAndWaitforThread(); #endif - errorstream << std::endl << "In thread " << std::hex - << std::this_thread::get_id() << ":" << std::endl; + errorstream << std::endl + << "In thread " << std::hex << std::this_thread::get_id() << ":" + << std::endl; errorstream << file << ":" << line << ": " << function - << ": An engine assumption '" << assertion << "' failed." << std::endl; + << ": An engine assumption '" << assertion << "' failed." + << std::endl; abort(); } -void fatal_error_fn(const char *msg, const char *file, - unsigned int line, const char *function) +void fatal_error_fn(const char *msg, const char *file, unsigned int line, + const char *function) { #if USE_CURSES g_term_console.stopAndWaitforThread(); #endif - errorstream << std::endl << "In thread " << std::hex - << std::this_thread::get_id() << ":" << std::endl; + errorstream << std::endl + << "In thread " << std::hex << std::this_thread::get_id() << ":" + << std::endl; errorstream << file << ":" << line << ": " << function - << ": A fatal error occurred: " << msg << std::endl; + << ": A fatal error occurred: " << msg << std::endl; abort(); } @@ -141,27 +143,27 @@ long WINAPI Win32ExceptionHandler(struct _EXCEPTION_POINTERS *pExceptInfo) std::string version_str(PROJECT_NAME " "); version_str += g_version_hash; - HANDLE hFile = CreateFileA(dumpfile.c_str(), GENERIC_WRITE, - FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + HANDLE hFile = CreateFileA(dumpfile.c_str(), GENERIC_WRITE, FILE_SHARE_WRITE, + NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) goto minidump_failed; if (SetEndOfFile(hFile) == FALSE) goto minidump_failed; - mdei.ClientPointers = NULL; + mdei.ClientPointers = NULL; mdei.ExceptionPointers = pExceptInfo; - mdei.ThreadId = GetCurrentThreadId(); + mdei.ThreadId = GetCurrentThreadId(); - mdus.Type = CommentStreamA; + mdus.Type = CommentStreamA; mdus.BufferSize = version_str.size(); - mdus.Buffer = (PVOID)version_str.c_str(); + mdus.Buffer = (PVOID)version_str.c_str(); mdusi.UserStreamArray = &mdus; mdusi.UserStreamCount = 1; if (MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile, - MiniDumpNormal, &mdei, &mdusi, NULL) == FALSE) + MiniDumpNormal, &mdei, &mdusi, NULL) == FALSE) goto minidump_failed; minidump_created = true; @@ -172,10 +174,10 @@ minidump_failed: DWORD excode = pExceptInfo->ExceptionRecord->ExceptionCode; _snprintf(buf, sizeof(buf), - " >> === FATAL ERROR ===\n" - " >> %s (Exception 0x%08X) at 0x%p\n", - Win32ExceptionCodeToString(excode), excode, - pExceptInfo->ExceptionRecord->ExceptionAddress); + " >> === FATAL ERROR ===\n" + " >> %s (Exception 0x%08X) at 0x%p\n", + Win32ExceptionCodeToString(excode), excode, + pExceptInfo->ExceptionRecord->ExceptionAddress); dstream << buf; if (minidump_created) @@ -194,4 +196,3 @@ void debug_set_exception_handler() SetUnhandledExceptionFilter(Win32ExceptionHandler); #endif } - diff --git a/src/debug.h b/src/debug.h index 1faeece8d..75acf493f 100644 --- a/src/debug.h +++ b/src/debug.h @@ -26,42 +26,38 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "log.h" #ifdef _WIN32 - #ifndef _WIN32_WINNT - #define _WIN32_WINNT 0x0501 - #endif - #include - #ifdef _MSC_VER - #include - #endif - #define NORETURN __declspec(noreturn) - #define FUNCTION_NAME __FUNCTION__ +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0501 +#endif +#include +#ifdef _MSC_VER +#include +#endif +#define NORETURN __declspec(noreturn) +#define FUNCTION_NAME __FUNCTION__ #else - #define NORETURN __attribute__ ((__noreturn__)) - #define FUNCTION_NAME __PRETTY_FUNCTION__ +#define NORETURN __attribute__((__noreturn__)) +#define FUNCTION_NAME __PRETTY_FUNCTION__ #endif // Whether to catch all std::exceptions. // When "catching", the program will abort with an error message. // In debug mode, leave these for the debugger and don't catch them. #ifdef NDEBUG - #define CATCH_UNHANDLED_EXCEPTIONS 1 +#define CATCH_UNHANDLED_EXCEPTIONS 1 #else - #define CATCH_UNHANDLED_EXCEPTIONS 0 +#define CATCH_UNHANDLED_EXCEPTIONS 0 #endif /* Abort program execution immediately */ -NORETURN extern void fatal_error_fn( - const char *msg, const char *file, - unsigned int line, const char *function); +NORETURN extern void fatal_error_fn(const char *msg, const char *file, unsigned int line, + const char *function); -#define FATAL_ERROR(msg) \ - fatal_error_fn((msg), __FILE__, __LINE__, FUNCTION_NAME) +#define FATAL_ERROR(msg) fatal_error_fn((msg), __FILE__, __LINE__, FUNCTION_NAME) -#define FATAL_ERROR_IF(expr, msg) \ - ((expr) \ - ? fatal_error_fn((msg), __FILE__, __LINE__, FUNCTION_NAME) \ - : (void)(0)) +#define FATAL_ERROR_IF(expr, msg) \ + ((expr) ? fatal_error_fn((msg), __FILE__, __LINE__, FUNCTION_NAME) : (void)(0)) /* sanity_check() @@ -69,18 +65,14 @@ NORETURN extern void fatal_error_fn( defined) */ -NORETURN extern void sanity_check_fn( - const char *assertion, const char *file, +NORETURN extern void sanity_check_fn(const char *assertion, const char *file, unsigned int line, const char *function); -#define SANITY_CHECK(expr) \ - ((expr) \ - ? (void)(0) \ - : sanity_check_fn(#expr, __FILE__, __LINE__, FUNCTION_NAME)) +#define SANITY_CHECK(expr) \ + ((expr) ? (void)(0) : sanity_check_fn(#expr, __FILE__, __LINE__, FUNCTION_NAME)) #define sanity_check(expr) SANITY_CHECK(expr) - void debug_set_exception_handler(); /* @@ -88,15 +80,17 @@ void debug_set_exception_handler(); */ #if CATCH_UNHANDLED_EXCEPTIONS == 1 - #define BEGIN_DEBUG_EXCEPTION_HANDLER try { - #define END_DEBUG_EXCEPTION_HANDLER \ - } catch (std::exception &e) { \ - errorstream << "An unhandled exception occurred: " \ - << e.what() << std::endl; \ - FATAL_ERROR(e.what()); \ - } +#define BEGIN_DEBUG_EXCEPTION_HANDLER try { +#define END_DEBUG_EXCEPTION_HANDLER \ + } \ + catch (std::exception & e) \ + { \ + errorstream << "An unhandled exception occurred: " << e.what() \ + << std::endl; \ + FATAL_ERROR(e.what()); \ + } #else - // Dummy ones - #define BEGIN_DEBUG_EXCEPTION_HANDLER - #define END_DEBUG_EXCEPTION_HANDLER +// Dummy ones +#define BEGIN_DEBUG_EXCEPTION_HANDLER +#define END_DEBUG_EXCEPTION_HANDLER #endif diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index a00d39c30..37425916c 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -74,7 +74,7 @@ void set_default_settings(Settings *settings) settings->setDefault("cheat_menu_font_color_alpha", "195"); settings->setDefault("cheat_menu_selected_font_color", "(255, 255, 255)"); settings->setDefault("cheat_menu_selected_font_color_alpha", "235"); - + // Cheats settings->setDefault("xray", "false"); settings->setDefault("xray_nodes", "default:stone,mcl_core:stone"); @@ -344,7 +344,6 @@ void set_default_settings(Settings *settings) settings->setDefault("enable_waving_leaves", "false"); settings->setDefault("enable_waving_plants", "false"); - // Input settings->setDefault("invert_mouse", "false"); settings->setDefault("mouse_sensitivity", "0.2"); @@ -373,19 +372,29 @@ void set_default_settings(Settings *settings) #if USE_FREETYPE settings->setDefault("freetype", "true"); - settings->setDefault("font_path", porting::getDataPath("fonts" DIR_DELIM "Arimo-Regular.ttf")); - settings->setDefault("font_path_italic", porting::getDataPath("fonts" DIR_DELIM "Arimo-Italic.ttf")); - settings->setDefault("font_path_bold", porting::getDataPath("fonts" DIR_DELIM "Arimo-Bold.ttf")); - settings->setDefault("font_path_bold_italic", porting::getDataPath("fonts" DIR_DELIM "Arimo-BoldItalic.ttf")); + settings->setDefault("font_path", + porting::getDataPath("fonts" DIR_DELIM "Arimo-Regular.ttf")); + settings->setDefault("font_path_italic", + porting::getDataPath("fonts" DIR_DELIM "Arimo-Italic.ttf")); + settings->setDefault("font_path_bold", + porting::getDataPath("fonts" DIR_DELIM "Arimo-Bold.ttf")); + settings->setDefault("font_path_bold_italic", + porting::getDataPath("fonts" DIR_DELIM "Arimo-BoldItalic.ttf")); settings->setDefault("font_bold", "false"); settings->setDefault("font_italic", "false"); settings->setDefault("font_shadow", "1"); settings->setDefault("font_shadow_alpha", "127"); - settings->setDefault("mono_font_path", porting::getDataPath("fonts" DIR_DELIM "Cousine-Regular.ttf")); - settings->setDefault("mono_font_path_italic", porting::getDataPath("fonts" DIR_DELIM "Cousine-Italic.ttf")); - settings->setDefault("mono_font_path_bold", porting::getDataPath("fonts" DIR_DELIM "Cousine-Bold.ttf")); - settings->setDefault("mono_font_path_bold_italic", porting::getDataPath("fonts" DIR_DELIM "Cousine-BoldItalic.ttf")); - settings->setDefault("fallback_font_path", porting::getDataPath("fonts" DIR_DELIM "DroidSansFallbackFull.ttf")); + settings->setDefault("mono_font_path", + porting::getDataPath("fonts" DIR_DELIM "Cousine-Regular.ttf")); + settings->setDefault("mono_font_path_italic", + porting::getDataPath("fonts" DIR_DELIM "Cousine-Italic.ttf")); + settings->setDefault("mono_font_path_bold", + porting::getDataPath("fonts" DIR_DELIM "Cousine-Bold.ttf")); + settings->setDefault("mono_font_path_bold_italic", + porting::getDataPath("fonts" DIR_DELIM "Cousine-BoldItalic.ttf")); + settings->setDefault("fallback_font_path", + porting::getDataPath( + "fonts" DIR_DELIM "DroidSansFallbackFull.ttf")); settings->setDefault("fallback_font_shadow", "1"); settings->setDefault("fallback_font_shadow_alpha", "128"); @@ -395,8 +404,10 @@ void set_default_settings(Settings *settings) settings->setDefault("fallback_font_size", font_size_str); #else settings->setDefault("freetype", "false"); - settings->setDefault("font_path", porting::getDataPath("fonts" DIR_DELIM "mono_dejavu_sans")); - settings->setDefault("mono_font_path", porting::getDataPath("fonts" DIR_DELIM "mono_dejavu_sans")); + settings->setDefault("font_path", + porting::getDataPath("fonts" DIR_DELIM "mono_dejavu_sans")); + settings->setDefault("mono_font_path", + porting::getDataPath("fonts" DIR_DELIM "mono_dejavu_sans")); std::string font_size_str = std::to_string(DEFAULT_FONT_SIZE); #endif @@ -413,7 +424,6 @@ void set_default_settings(Settings *settings) settings->setDefault("contentdb_flag_blacklist", "nonfree, desktop_default"); #endif - // Server settings->setDefault("disable_escape_sequences", "false"); settings->setDefault("strip_color_codes", "false"); @@ -424,7 +434,7 @@ void set_default_settings(Settings *settings) // Network settings->setDefault("enable_ipv6", "true"); settings->setDefault("ipv6_server", "false"); - settings->setDefault("max_packets_per_iteration","1024"); + settings->setDefault("max_packets_per_iteration", "1024"); settings->setDefault("port", "30000"); settings->setDefault("strict_protocol_version_checking", "false"); settings->setDefault("player_transfer_distance", "0"); @@ -450,14 +460,15 @@ void set_default_settings(Settings *settings) #endif settings->setDefault("kick_msg_shutdown", "Server shutting down."); - settings->setDefault("kick_msg_crash", "This server has experienced an internal error. You will now be disconnected."); + settings->setDefault("kick_msg_crash", "This server has experienced an internal " + "error. You will now be disconnected."); settings->setDefault("ask_reconnect_on_crash", "false"); settings->setDefault("chat_message_format", "<@name> @message"); settings->setDefault("profiler_print_interval", "0"); settings->setDefault("active_object_send_range_blocks", "4"); settings->setDefault("active_block_range", "3"); - //settings->setDefault("max_simultaneous_block_sends_per_client", "1"); + // settings->setDefault("max_simultaneous_block_sends_per_client", "1"); // This causes frametime jitter on client side, or does it? settings->setDefault("max_block_send_distance", "10"); settings->setDefault("block_send_optimize_distance", "4"); @@ -545,7 +556,7 @@ void set_default_settings(Settings *settings) settings->setDefault("fullscreen", "true"); settings->setDefault("touchtarget", "true"); settings->setDefault("TMPFolder", porting::path_cache); - settings->setDefault("touchscreen_threshold","20"); + settings->setDefault("touchscreen_threshold", "20"); settings->setDefault("fixed_virtual_joystick", "false"); settings->setDefault("virtual_joystick_triggers_aux", "false"); settings->setDefault("smooth_lighting", "false"); @@ -563,11 +574,11 @@ void set_default_settings(Settings *settings) settings->setDefault("active_block_range", "2"); settings->setDefault("viewing_range", "50"); settings->setDefault("leaves_style", "simple"); - settings->setDefault("curl_verify_cert","false"); + settings->setDefault("curl_verify_cert", "false"); // Apply settings according to screen size - float x_inches = (float) porting::getDisplaySize().X / - (160.f * porting::getDisplayDensity()); + float x_inches = (float)porting::getDisplaySize().X / + (160.f * porting::getDisplayDensity()); if (x_inches < 3.7f) { settings->setDefault("hud_scaling", "0.6"); diff --git a/src/emerge.cpp b/src/emerge.cpp index 0ac26a682..e76383d09 100644 --- a/src/emerge.cpp +++ b/src/emerge.cpp @@ -18,7 +18,6 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ - #include "emerge.h" #include @@ -45,7 +44,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "settings.h" #include "voxel.h" -class EmergeThread : public Thread { +class EmergeThread : public Thread +{ public: bool enable_mapgen_debug_info; int id; @@ -61,9 +61,8 @@ public: void cancelPendingItems(); - static void runCompletionCallbacks( - const v3s16 &pos, EmergeAction action, - const EmergeCallbackList &callbacks); + static void runCompletionCallbacks(const v3s16 &pos, EmergeAction action, + const EmergeCallbackList &callbacks); private: Server *m_server; @@ -76,10 +75,10 @@ private: bool popBlockEmerge(v3s16 *pos, BlockEmergeData *bedata); - EmergeAction getBlockOrStartGen( - const v3s16 &pos, bool allow_gen, MapBlock **block, BlockMakeData *data); + EmergeAction getBlockOrStartGen(const v3s16 &pos, bool allow_gen, + MapBlock **block, BlockMakeData *data); MapBlock *finishGen(v3s16 pos, BlockMakeData *bmdata, - std::map *modified_blocks); + std::map *modified_blocks); friend class EmergeManager; }; @@ -87,10 +86,10 @@ private: class MapEditEventAreaIgnorer { public: - MapEditEventAreaIgnorer(VoxelArea *ignorevariable, const VoxelArea &a): - m_ignorevariable(ignorevariable) + MapEditEventAreaIgnorer(VoxelArea *ignorevariable, const VoxelArea &a) : + m_ignorevariable(ignorevariable) { - if(m_ignorevariable->getVolume() == 0) + if (m_ignorevariable->getVolume() == 0) *m_ignorevariable = a; else m_ignorevariable = NULL; @@ -98,8 +97,7 @@ public: ~MapEditEventAreaIgnorer() { - if(m_ignorevariable) - { + if (m_ignorevariable) { assert(m_ignorevariable->getVolume() != 0); *m_ignorevariable = VoxelArea(); } @@ -120,14 +118,14 @@ EmergeParams::~EmergeParams() } EmergeParams::EmergeParams(EmergeManager *parent, const BiomeManager *biomemgr, - const OreManager *oremgr, const DecorationManager *decomgr, - const SchematicManager *schemmgr) : - ndef(parent->ndef), - enable_mapgen_debug_info(parent->enable_mapgen_debug_info), - gen_notify_on(parent->gen_notify_on), - gen_notify_on_deco_ids(&parent->gen_notify_on_deco_ids), - biomemgr(biomemgr->clone()), oremgr(oremgr->clone()), - decomgr(decomgr->clone()), schemmgr(schemmgr->clone()) + const OreManager *oremgr, const DecorationManager *decomgr, + const SchematicManager *schemmgr) : + ndef(parent->ndef), + enable_mapgen_debug_info(parent->enable_mapgen_debug_info), + gen_notify_on(parent->gen_notify_on), + gen_notify_on_deco_ids(&parent->gen_notify_on_deco_ids), + biomemgr(biomemgr->clone()), oremgr(oremgr->clone()), + decomgr(decomgr->clone()), schemmgr(schemmgr->clone()) { } @@ -137,11 +135,11 @@ EmergeParams::EmergeParams(EmergeManager *parent, const BiomeManager *biomemgr, EmergeManager::EmergeManager(Server *server) { - this->ndef = server->getNodeDefManager(); - this->biomemgr = new BiomeManager(server); - this->oremgr = new OreManager(server); - this->decomgr = new DecorationManager(server); - this->schemmgr = new SchematicManager(server); + this->ndef = server->getNodeDefManager(); + this->biomemgr = new BiomeManager(server); + this->oremgr = new OreManager(server); + this->decomgr = new DecorationManager(server); + this->schemmgr = new SchematicManager(server); // Note that accesses to this variable are not synchronized. // This is because the *only* thread ever starting or stopping @@ -179,7 +177,6 @@ EmergeManager::EmergeManager(Server *server) infostream << "EmergeManager: using " << nthreads << " threads" << std::endl; } - EmergeManager::~EmergeManager() { for (u32 i = 0; i != m_threads.size(); i++) { @@ -204,36 +201,34 @@ EmergeManager::~EmergeManager() delete schemmgr; } - BiomeManager *EmergeManager::getWritableBiomeManager() { FATAL_ERROR_IF(!m_mapgens.empty(), - "Writable managers can only be returned before mapgen init"); + "Writable managers can only be returned before mapgen init"); return biomemgr; } OreManager *EmergeManager::getWritableOreManager() { FATAL_ERROR_IF(!m_mapgens.empty(), - "Writable managers can only be returned before mapgen init"); + "Writable managers can only be returned before mapgen init"); return oremgr; } DecorationManager *EmergeManager::getWritableDecorationManager() { FATAL_ERROR_IF(!m_mapgens.empty(), - "Writable managers can only be returned before mapgen init"); + "Writable managers can only be returned before mapgen init"); return decomgr; } SchematicManager *EmergeManager::getWritableSchematicManager() { FATAL_ERROR_IF(!m_mapgens.empty(), - "Writable managers can only be returned before mapgen init"); + "Writable managers can only be returned before mapgen init"); return schemmgr; } - void EmergeManager::initMapgens(MapgenParams *params) { FATAL_ERROR_IF(!m_mapgens.empty(), "Mapgen already initialised."); @@ -242,14 +237,13 @@ void EmergeManager::initMapgens(MapgenParams *params) for (u32 i = 0; i != m_threads.size(); i++) { EmergeParams *p = new EmergeParams( - this, biomemgr, oremgr, decomgr, schemmgr); - infostream << "EmergeManager: Created params " << p - << " for thread " << i << std::endl; + this, biomemgr, oremgr, decomgr, schemmgr); + infostream << "EmergeManager: Created params " << p << " for thread " << i + << std::endl; m_mapgens.push_back(Mapgen::createMapgen(params->mgtype, params, p)); } } - Mapgen *EmergeManager::getCurrentMapgen() { if (!m_threads_active) @@ -264,7 +258,6 @@ Mapgen *EmergeManager::getCurrentMapgen() return nullptr; } - void EmergeManager::startThreads() { if (m_threads_active) @@ -276,7 +269,6 @@ void EmergeManager::startThreads() m_threads_active = true; } - void EmergeManager::stopThreads() { if (!m_threads_active) @@ -295,18 +287,13 @@ void EmergeManager::stopThreads() m_threads_active = false; } - bool EmergeManager::isRunning() { return m_threads_active; } - -bool EmergeManager::enqueueBlockEmerge( - session_t peer_id, - v3s16 blockpos, - bool allow_generate, - bool ignore_queue_limits) +bool EmergeManager::enqueueBlockEmerge(session_t peer_id, v3s16 blockpos, + bool allow_generate, bool ignore_queue_limits) { u16 flags = 0; if (allow_generate) @@ -317,13 +304,8 @@ bool EmergeManager::enqueueBlockEmerge( return enqueueBlockEmergeEx(blockpos, peer_id, flags, NULL, NULL); } - -bool EmergeManager::enqueueBlockEmergeEx( - v3s16 blockpos, - session_t peer_id, - u16 flags, - EmergeCompletionCallback callback, - void *callback_param) +bool EmergeManager::enqueueBlockEmergeEx(v3s16 blockpos, session_t peer_id, u16 flags, + EmergeCompletionCallback callback, void *callback_param) { EmergeThread *thread = NULL; bool entry_already_exists = false; @@ -331,8 +313,8 @@ bool EmergeManager::enqueueBlockEmergeEx( { MutexAutoLock queuelock(m_queue_mutex); - if (!pushBlockEmergeData(blockpos, peer_id, flags, - callback, callback_param, &entry_already_exists)) + if (!pushBlockEmergeData(blockpos, peer_id, flags, callback, + callback_param, &entry_already_exists)) return false; if (entry_already_exists) @@ -347,12 +329,10 @@ bool EmergeManager::enqueueBlockEmergeEx( return true; } - // // Mapgen-related helper functions // - // TODO(hmmmm): Move this to ServerMap v3s16 EmergeManager::getContainingChunk(v3s16 blockpos) { @@ -365,28 +345,28 @@ v3s16 EmergeManager::getContainingChunk(v3s16 blockpos, s16 chunksize) s16 coff = -chunksize / 2; v3s16 chunk_offset(coff, coff, coff); - return getContainerPos(blockpos - chunk_offset, chunksize) - * chunksize + chunk_offset; + return getContainerPos(blockpos - chunk_offset, chunksize) * chunksize + + chunk_offset; } - int EmergeManager::getSpawnLevelAtPoint(v2s16 p) { if (m_mapgens.empty() || !m_mapgens[0]) { errorstream << "EmergeManager: getSpawnLevelAtPoint() called" - " before mapgen init" << std::endl; + " before mapgen init" + << std::endl; return 0; } return m_mapgens[0]->getSpawnLevelAtPoint(p); } - int EmergeManager::getGroundLevelAtPoint(v2s16 p) { if (m_mapgens.empty() || !m_mapgens[0]) { errorstream << "EmergeManager: getGroundLevelAtPoint() called" - " before mapgen init" << std::endl; + " before mapgen init" + << std::endl; return 0; } @@ -407,13 +387,9 @@ bool EmergeManager::isBlockUnderground(v3s16 blockpos) return blockpos.Y * (MAP_BLOCKSIZE + 1) <= mgparams->water_level; } -bool EmergeManager::pushBlockEmergeData( - v3s16 pos, - u16 peer_requested, - u16 flags, - EmergeCompletionCallback callback, - void *callback_param, - bool *entry_already_exists) +bool EmergeManager::pushBlockEmergeData(v3s16 pos, u16 peer_requested, u16 flags, + EmergeCompletionCallback callback, void *callback_param, + bool *entry_already_exists) { u16 &count_peer = m_peer_queue_count[peer_requested]; @@ -422,8 +398,9 @@ bool EmergeManager::pushBlockEmergeData( return false; if (peer_requested != PEER_ID_INEXISTENT) { - u16 qlimit_peer = (flags & BLOCK_EMERGE_ALLOW_GEN) ? - m_qlimit_generate : m_qlimit_diskonly; + u16 qlimit_peer = (flags & BLOCK_EMERGE_ALLOW_GEN) + ? m_qlimit_generate + : m_qlimit_diskonly; if (count_peer >= qlimit_peer) return false; } @@ -433,7 +410,7 @@ bool EmergeManager::pushBlockEmergeData( findres = m_blocks_enqueued.insert(std::make_pair(pos, BlockEmergeData())); BlockEmergeData &bedata = findres.first->second; - *entry_already_exists = !findres.second; + *entry_already_exists = !findres.second; if (callback) bedata.callbacks.emplace_back(callback, callback_param); @@ -450,7 +427,6 @@ bool EmergeManager::pushBlockEmergeData( return true; } - bool EmergeManager::popBlockEmergeData(v3s16 pos, BlockEmergeData *bedata) { std::map::iterator it; @@ -475,7 +451,6 @@ bool EmergeManager::popBlockEmergeData(v3s16 pos, BlockEmergeData *bedata) return true; } - EmergeThread *EmergeManager::getOptimalThread() { size_t nthreads = m_threads.size(); @@ -496,36 +471,28 @@ EmergeThread *EmergeManager::getOptimalThread() return m_threads[index]; } - //// //// EmergeThread //// EmergeThread::EmergeThread(Server *server, int ethreadid) : - enable_mapgen_debug_info(false), - id(ethreadid), - m_server(server), - m_map(NULL), - m_emerge(NULL), - m_mapgen(NULL) + enable_mapgen_debug_info(false), id(ethreadid), m_server(server), + m_map(NULL), m_emerge(NULL), m_mapgen(NULL) { m_name = "Emerge-" + itos(ethreadid); } - void EmergeThread::signal() { m_queue_event.signal(); } - bool EmergeThread::pushBlock(const v3s16 &pos) { m_block_queue.push(pos); return true; } - void EmergeThread::cancelPendingItems() { MutexAutoLock queuelock(m_emerge->m_queue_mutex); @@ -543,22 +510,20 @@ void EmergeThread::cancelPendingItems() } } - void EmergeThread::runCompletionCallbacks(const v3s16 &pos, EmergeAction action, - const EmergeCallbackList &callbacks) + const EmergeCallbackList &callbacks) { for (size_t i = 0; i != callbacks.size(); i++) { EmergeCompletionCallback callback; void *param; callback = callbacks[i].first; - param = callbacks[i].second; + param = callbacks[i].second; callback(pos, action, param); } } - bool EmergeThread::popBlockEmerge(v3s16 *pos, BlockEmergeData *bedata) { MutexAutoLock queuelock(m_emerge->m_queue_mutex); @@ -574,9 +539,8 @@ bool EmergeThread::popBlockEmerge(v3s16 *pos, BlockEmergeData *bedata) return true; } - EmergeAction EmergeThread::getBlockOrStartGen( - const v3s16 &pos, bool allow_gen, MapBlock **block, BlockMakeData *bmdata) + const v3s16 &pos, bool allow_gen, MapBlock **block, BlockMakeData *bmdata) { MutexAutoLock envlock(m_server->m_env_mutex); @@ -600,13 +564,11 @@ EmergeAction EmergeThread::getBlockOrStartGen( return EMERGE_CANCELLED; } - MapBlock *EmergeThread::finishGen(v3s16 pos, BlockMakeData *bmdata, - std::map *modified_blocks) + std::map *modified_blocks) { MutexAutoLock envlock(m_server->m_env_mutex); - ScopeProfiler sp(g_profiler, - "EmergeThread: after Mapgen::makeChunk", SPT_AVG); + ScopeProfiler sp(g_profiler, "EmergeThread: after Mapgen::makeChunk", SPT_AVG); /* Perform post-processing on blocks (invalidate lighting, queue liquid @@ -617,26 +579,26 @@ MapBlock *EmergeThread::finishGen(v3s16 pos, BlockMakeData *bmdata, MapBlock *block = m_map->getBlockNoCreateNoEx(pos); if (!block) { errorstream << "EmergeThread::finishGen: Couldn't grab block we " - "just generated: " << PP(pos) << std::endl; + "just generated: " + << PP(pos) << std::endl; return NULL; } v3s16 minp = bmdata->blockpos_min * MAP_BLOCKSIZE; v3s16 maxp = bmdata->blockpos_max * MAP_BLOCKSIZE + - v3s16(1,1,1) * (MAP_BLOCKSIZE - 1); + v3s16(1, 1, 1) * (MAP_BLOCKSIZE - 1); // Ignore map edit events, they will not need to be sent // to anybody because the block hasn't been sent to anybody MapEditEventAreaIgnorer ign( - &m_server->m_ignore_map_edit_events_area, - VoxelArea(minp, maxp)); + &m_server->m_ignore_map_edit_events_area, VoxelArea(minp, maxp)); /* Run Lua on_generated callbacks */ try { m_server->getScriptIface()->environment_OnGenerated( - minp, maxp, m_mapgen->blockseed); + minp, maxp, m_mapgen->blockseed); } catch (LuaError &e) { m_server->setAsyncFatalError("Lua: finishGen" + std::string(e.what())); } @@ -656,74 +618,74 @@ MapBlock *EmergeThread::finishGen(v3s16 pos, BlockMakeData *bmdata, return block; } - void *EmergeThread::run() { BEGIN_DEBUG_EXCEPTION_HANDLER v3s16 pos; - m_map = (ServerMap *)&(m_server->m_env->getMap()); + m_map = (ServerMap *)&(m_server->m_env->getMap()); m_emerge = m_server->m_emerge; m_mapgen = m_emerge->m_mapgens[id]; enable_mapgen_debug_info = m_emerge->enable_mapgen_debug_info; try { - while (!stopRequested()) { - std::map modified_blocks; - BlockEmergeData bedata; - BlockMakeData bmdata; - EmergeAction action; - MapBlock *block; + while (!stopRequested()) { + std::map modified_blocks; + BlockEmergeData bedata; + BlockMakeData bmdata; + EmergeAction action; + MapBlock *block; - if (!popBlockEmerge(&pos, &bedata)) { - m_queue_event.wait(); - continue; - } - - if (blockpos_over_max_limit(pos)) - continue; - - bool allow_gen = bedata.flags & BLOCK_EMERGE_ALLOW_GEN; - EMERGE_DBG_OUT("pos=" PP(pos) " allow_gen=" << allow_gen); - - action = getBlockOrStartGen(pos, allow_gen, &block, &bmdata); - if (action == EMERGE_GENERATED) { - { - ScopeProfiler sp(g_profiler, - "EmergeThread: Mapgen::makeChunk", SPT_AVG); - - m_mapgen->makeChunk(&bmdata); + if (!popBlockEmerge(&pos, &bedata)) { + m_queue_event.wait(); + continue; } - block = finishGen(pos, &bmdata, &modified_blocks); + if (blockpos_over_max_limit(pos)) + continue; + + bool allow_gen = bedata.flags & BLOCK_EMERGE_ALLOW_GEN; + EMERGE_DBG_OUT("pos=" PP(pos) " allow_gen=" << allow_gen); + + action = getBlockOrStartGen(pos, allow_gen, &block, &bmdata); + if (action == EMERGE_GENERATED) { + { + ScopeProfiler sp(g_profiler, + "EmergeThread: Mapgen::makeChunk", + SPT_AVG); + + m_mapgen->makeChunk(&bmdata); + } + + block = finishGen(pos, &bmdata, &modified_blocks); + } + + runCompletionCallbacks(pos, action, bedata.callbacks); + + if (block) + modified_blocks[pos] = block; + + if (!modified_blocks.empty()) + m_server->SetBlocksNotSent(modified_blocks); } - - runCompletionCallbacks(pos, action, bedata.callbacks); - - if (block) - modified_blocks[pos] = block; - - if (!modified_blocks.empty()) - m_server->SetBlocksNotSent(modified_blocks); - } } catch (VersionMismatchException &e) { std::ostringstream err; err << "World data version mismatch in MapBlock " << PP(pos) << std::endl - << "----" << std::endl - << "\"" << e.what() << "\"" << std::endl - << "See debug.txt." << std::endl - << "World probably saved by a newer version of " PROJECT_NAME_C "." - << std::endl; + << "----" << std::endl + << "\"" << e.what() << "\"" << std::endl + << "See debug.txt." << std::endl + << "World probably saved by a newer version of " PROJECT_NAME_C "." + << std::endl; m_server->setAsyncFatalError(err.str()); } catch (SerializationError &e) { std::ostringstream err; err << "Invalid data in MapBlock " << PP(pos) << std::endl - << "----" << std::endl - << "\"" << e.what() << "\"" << std::endl - << "See debug.txt." << std::endl - << "You can ignore this using [ignore_world_load_errors = true]." - << std::endl; + << "----" << std::endl + << "\"" << e.what() << "\"" << std::endl + << "See debug.txt." << std::endl + << "You can ignore this using [ignore_world_load_errors = true]." + << std::endl; m_server->setAsyncFatalError(err.str()); } diff --git a/src/emerge.h b/src/emerge.h index 6f204666d..d27aa804d 100644 --- a/src/emerge.h +++ b/src/emerge.h @@ -27,13 +27,14 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "mapgen/mapgen.h" // for MapgenParams #include "map.h" -#define BLOCK_EMERGE_ALLOW_GEN (1 << 0) +#define BLOCK_EMERGE_ALLOW_GEN (1 << 0) #define BLOCK_EMERGE_FORCE_QUEUE (1 << 1) -#define EMERGE_DBG_OUT(x) { \ - if (enable_mapgen_debug_info) \ - infostream << "EmergeThread: " x << std::endl; \ -} +#define EMERGE_DBG_OUT(x) \ + { \ + if (enable_mapgen_debug_info) \ + infostream << "EmergeThread: " x << std::endl; \ + } class EmergeThread; class NodeDefManager; @@ -47,7 +48,8 @@ class Server; class ModApiMapgen; // Structure containing inputs/outputs for chunk generation -struct BlockMakeData { +struct BlockMakeData +{ MMVManip *vmanip = nullptr; u64 seed = 0; v3s16 blockpos_min; @@ -62,7 +64,8 @@ struct BlockMakeData { }; // Result from processing an item on the emerge queue -enum EmergeAction { +enum EmergeAction +{ EMERGE_CANCELLED, EMERGE_ERRORED, EMERGE_FROM_MEMORY, @@ -72,23 +75,21 @@ enum EmergeAction { // Callback typedef void (*EmergeCompletionCallback)( - v3s16 blockpos, EmergeAction action, void *param); + v3s16 blockpos, EmergeAction action, void *param); -typedef std::vector< - std::pair< - EmergeCompletionCallback, - void * - > -> EmergeCallbackList; +typedef std::vector> EmergeCallbackList; -struct BlockEmergeData { +struct BlockEmergeData +{ u16 peer_requested; u16 flags; EmergeCallbackList callbacks; }; -class EmergeParams { +class EmergeParams +{ friend class EmergeManager; + public: EmergeParams() = delete; ~EmergeParams(); @@ -107,16 +108,18 @@ public: private: EmergeParams(EmergeManager *parent, const BiomeManager *biomemgr, - const OreManager *oremgr, const DecorationManager *decomgr, - const SchematicManager *schemmgr); + const OreManager *oremgr, const DecorationManager *decomgr, + const SchematicManager *schemmgr); }; -class EmergeManager { +class EmergeManager +{ /* The mod API needs unchecked access to allow: * - using decomgr or oremgr to place decos/ores * - using schemmgr to load and place schematics */ friend class ModApiMapgen; + public: const NodeDefManager *ndef; bool enable_mapgen_debug_info; @@ -158,18 +161,11 @@ public: void stopThreads(); bool isRunning(); - bool enqueueBlockEmerge( - session_t peer_id, - v3s16 blockpos, - bool allow_generate, - bool ignore_queue_limits=false); + bool enqueueBlockEmerge(session_t peer_id, v3s16 blockpos, bool allow_generate, + bool ignore_queue_limits = false); - bool enqueueBlockEmergeEx( - v3s16 blockpos, - session_t peer_id, - u16 flags, - EmergeCompletionCallback callback, - void *callback_param); + bool enqueueBlockEmergeEx(v3s16 blockpos, session_t peer_id, u16 flags, + EmergeCompletionCallback callback, void *callback_param); v3s16 getContainingChunk(v3s16 blockpos); @@ -205,13 +201,9 @@ private: // Requires m_queue_mutex held EmergeThread *getOptimalThread(); - bool pushBlockEmergeData( - v3s16 pos, - u16 peer_requested, - u16 flags, - EmergeCompletionCallback callback, - void *callback_param, - bool *entry_already_exists); + bool pushBlockEmergeData(v3s16 pos, u16 peer_requested, u16 flags, + EmergeCompletionCallback callback, void *callback_param, + bool *entry_already_exists); bool popBlockEmergeData(v3s16 pos, BlockEmergeData *bedata); diff --git a/src/environment.cpp b/src/environment.cpp index 7acad313e..0a08a1d68 100644 --- a/src/environment.cpp +++ b/src/environment.cpp @@ -26,14 +26,12 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "daynightratio.h" #include "emerge.h" - -Environment::Environment(IGameDef *gamedef): - m_time_of_day_speed(0.0f), - m_day_count(0), - m_gamedef(gamedef) +Environment::Environment(IGameDef *gamedef) : + m_time_of_day_speed(0.0f), m_day_count(0), m_gamedef(gamedef) { m_cache_enable_shaders = g_settings->getBool("enable_shaders"); - m_cache_active_block_mgmt_interval = g_settings->getFloat("active_block_mgmt_interval"); + m_cache_active_block_mgmt_interval = + g_settings->getFloat("active_block_mgmt_interval"); m_cache_abm_interval = g_settings->getFloat("abm_interval"); m_cache_nodetimer_interval = g_settings->getFloat("nodetimer_interval"); @@ -105,14 +103,15 @@ bool Environment::line_of_sight(v3f pos1, v3f pos2, v3s16 *p) /* Check if a node is pointable */ -inline static bool isPointableNode(const MapNode &n, - const NodeDefManager *nodedef , bool liquids_pointable, bool nodes_pointable) +inline static bool isPointableNode(const MapNode &n, const NodeDefManager *nodedef, + bool liquids_pointable, bool nodes_pointable) { - if (! nodes_pointable) + if (!nodes_pointable) return false; const ContentFeatures &features = nodedef->get(n); return features.pointable || - ((liquids_pointable || g_settings->getBool("point_liquids")) && features.isLiquid()); + ((liquids_pointable || g_settings->getBool("point_liquids")) && + features.isLiquid()); } void Environment::continueRaycast(RaycastState *state, PointedThing *result) @@ -140,7 +139,7 @@ void Environment::continueRaycast(RaycastState *state, PointedThing *result) s16 lastIndex = state->m_iterator.m_last_index; if (!state->m_found.empty()) { lastIndex = state->m_iterator.getIndex( - floatToInt(state->m_found.top().intersection_point, BS)); + floatToInt(state->m_found.top().intersection_point, BS)); } Map &map = getMap(); @@ -156,8 +155,8 @@ void Environment::continueRaycast(RaycastState *state, PointedThing *result) new_nodes.MaxEdge += state->m_iterator.m_current_node_pos; // Only check new nodes - v3s16 delta = state->m_iterator.m_current_node_pos - - state->m_previous_node; + v3s16 delta = state->m_iterator.m_current_node_pos - + state->m_previous_node; if (delta.X > 0) { new_nodes.MinEdge.X = new_nodes.MaxEdge.X; } else if (delta.X < 0) { @@ -174,99 +173,113 @@ void Environment::continueRaycast(RaycastState *state, PointedThing *result) // For each untested node for (s16 x = new_nodes.MinEdge.X; x <= new_nodes.MaxEdge.X; x++) - for (s16 y = new_nodes.MinEdge.Y; y <= new_nodes.MaxEdge.Y; y++) - for (s16 z = new_nodes.MinEdge.Z; z <= new_nodes.MaxEdge.Z; z++) { - MapNode n; - v3s16 np(x, y, z); - bool is_valid_position; + for (s16 y = new_nodes.MinEdge.Y; y <= new_nodes.MaxEdge.Y; y++) + for (s16 z = new_nodes.MinEdge.Z; + z <= new_nodes.MaxEdge.Z; z++) { + MapNode n; + v3s16 np(x, y, z); + bool is_valid_position; - n = map.getNode(np, &is_valid_position); - if (!(is_valid_position && isPointableNode(n, nodedef, - state->m_liquids_pointable, state->m_nodes_pointable))) { - continue; - } + n = map.getNode(np, &is_valid_position); + if (!(is_valid_position && + isPointableNode(n, nodedef, + state->m_liquids_pointable, + state->m_nodes_pointable))) { + continue; + } - PointedThing result; + PointedThing result; - std::vector boxes; - n.getSelectionBoxes(nodedef, &boxes, - n.getNeighbors(np, &map)); + std::vector boxes; + n.getSelectionBoxes(nodedef, &boxes, + n.getNeighbors(np, &map)); - // Is there a collision with a selection box? - bool is_colliding = false; - // Minimal distance of all collisions - float min_distance_sq = 10000000; - // ID of the current box (loop counter) - u16 id = 0; + // Is there a collision with a selection box? + bool is_colliding = false; + // Minimal distance of all collisions + float min_distance_sq = 10000000; + // ID of the current box (loop counter) + u16 id = 0; - v3f npf = intToFloat(np, BS); - // This loop translates the boxes to their in-world place. - for (aabb3f &box : boxes) { - box.MinEdge += npf; - box.MaxEdge += npf; + v3f npf = intToFloat(np, BS); + // This loop translates the boxes to their + // in-world place. + for (aabb3f &box : boxes) { + box.MinEdge += npf; + box.MaxEdge += npf; - v3f intersection_point; - v3s16 intersection_normal; - if (!boxLineCollision(box, state->m_shootline.start, - state->m_shootline.getVector(), &intersection_point, - &intersection_normal)) { - ++id; - continue; + v3f intersection_point; + v3s16 intersection_normal; + if (!boxLineCollision(box, + state->m_shootline + .start, + state->m_shootline + .getVector(), + &intersection_point, + &intersection_normal)) { + ++id; + continue; + } + + f32 distanceSq = (intersection_point - + state->m_shootline + .start) + .getLengthSQ(); + // If this is the nearest collision, save + // it + if (min_distance_sq > distanceSq) { + min_distance_sq = distanceSq; + result.intersection_point = + intersection_point; + result.intersection_normal = + intersection_normal; + result.box_id = id; + found_boxcenter = box.getCenter(); + is_colliding = true; + } + ++id; + } + // If there wasn't a collision, stop + if (!is_colliding) { + continue; + } + result.type = POINTEDTHING_NODE; + result.node_undersurface = np; + result.distanceSq = min_distance_sq; + // Set undersurface and abovesurface nodes + f32 d = 0.002 * BS; + v3f fake_intersection = result.intersection_point; + // Move intersection towards its source block. + if (fake_intersection.X < found_boxcenter.X) { + fake_intersection.X += d; + } else { + fake_intersection.X -= d; + } + if (fake_intersection.Y < found_boxcenter.Y) { + fake_intersection.Y += d; + } else { + fake_intersection.Y -= d; + } + if (fake_intersection.Z < found_boxcenter.Z) { + fake_intersection.Z += d; + } else { + fake_intersection.Z -= d; + } + result.node_real_undersurface = + floatToInt(fake_intersection, BS); + result.node_abovesurface = + result.node_real_undersurface + + result.intersection_normal; + // Push found PointedThing + state->m_found.push(result); + // If this is nearer than the old nearest object, + // the search can be shorter + s16 newIndex = state->m_iterator.getIndex( + result.node_real_undersurface); + if (newIndex < lastIndex) { + lastIndex = newIndex; + } } - - f32 distanceSq = (intersection_point - - state->m_shootline.start).getLengthSQ(); - // If this is the nearest collision, save it - if (min_distance_sq > distanceSq) { - min_distance_sq = distanceSq; - result.intersection_point = intersection_point; - result.intersection_normal = intersection_normal; - result.box_id = id; - found_boxcenter = box.getCenter(); - is_colliding = true; - } - ++id; - } - // If there wasn't a collision, stop - if (!is_colliding) { - continue; - } - result.type = POINTEDTHING_NODE; - result.node_undersurface = np; - result.distanceSq = min_distance_sq; - // Set undersurface and abovesurface nodes - f32 d = 0.002 * BS; - v3f fake_intersection = result.intersection_point; - // Move intersection towards its source block. - if (fake_intersection.X < found_boxcenter.X) { - fake_intersection.X += d; - } else { - fake_intersection.X -= d; - } - if (fake_intersection.Y < found_boxcenter.Y) { - fake_intersection.Y += d; - } else { - fake_intersection.Y -= d; - } - if (fake_intersection.Z < found_boxcenter.Z) { - fake_intersection.Z += d; - } else { - fake_intersection.Z -= d; - } - result.node_real_undersurface = floatToInt( - fake_intersection, BS); - result.node_abovesurface = result.node_real_undersurface - + result.intersection_normal; - // Push found PointedThing - state->m_found.push(result); - // If this is nearer than the old nearest object, - // the search can be shorter - s16 newIndex = state->m_iterator.getIndex( - result.node_real_undersurface); - if (newIndex < lastIndex) { - lastIndex = newIndex; - } - } // Next node state->m_previous_node = state->m_iterator.m_current_node_pos; state->m_iterator.next(); diff --git a/src/exceptions.h b/src/exceptions.h index c54307653..b96d6c8d8 100644 --- a/src/exceptions.h +++ b/src/exceptions.h @@ -22,110 +22,120 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include - class BaseException : public std::exception { public: - BaseException(const std::string &s) throw(): m_s(s) {} + BaseException(const std::string &s) throw() : m_s(s) {} ~BaseException() throw() = default; - virtual const char * what() const throw() - { - return m_s.c_str(); - } + virtual const char *what() const throw() { return m_s.c_str(); } + protected: std::string m_s; }; -class AlreadyExistsException : public BaseException { +class AlreadyExistsException : public BaseException +{ public: - AlreadyExistsException(const std::string &s): BaseException(s) {} + AlreadyExistsException(const std::string &s) : BaseException(s) {} }; -class VersionMismatchException : public BaseException { +class VersionMismatchException : public BaseException +{ public: - VersionMismatchException(const std::string &s): BaseException(s) {} + VersionMismatchException(const std::string &s) : BaseException(s) {} }; -class FileNotGoodException : public BaseException { +class FileNotGoodException : public BaseException +{ public: - FileNotGoodException(const std::string &s): BaseException(s) {} + FileNotGoodException(const std::string &s) : BaseException(s) {} }; -class DatabaseException : public BaseException { +class DatabaseException : public BaseException +{ public: - DatabaseException(const std::string &s): BaseException(s) {} + DatabaseException(const std::string &s) : BaseException(s) {} }; -class SerializationError : public BaseException { +class SerializationError : public BaseException +{ public: - SerializationError(const std::string &s): BaseException(s) {} + SerializationError(const std::string &s) : BaseException(s) {} }; -class PacketError : public BaseException { +class PacketError : public BaseException +{ public: - PacketError(const std::string &s): BaseException(s) {} + PacketError(const std::string &s) : BaseException(s) {} }; -class SettingNotFoundException : public BaseException { +class SettingNotFoundException : public BaseException +{ public: - SettingNotFoundException(const std::string &s): BaseException(s) {} + SettingNotFoundException(const std::string &s) : BaseException(s) {} }; -class InvalidFilenameException : public BaseException { +class InvalidFilenameException : public BaseException +{ public: - InvalidFilenameException(const std::string &s): BaseException(s) {} + InvalidFilenameException(const std::string &s) : BaseException(s) {} }; -class ItemNotFoundException : public BaseException { +class ItemNotFoundException : public BaseException +{ public: - ItemNotFoundException(const std::string &s): BaseException(s) {} + ItemNotFoundException(const std::string &s) : BaseException(s) {} }; -class ServerError : public BaseException { +class ServerError : public BaseException +{ public: - ServerError(const std::string &s): BaseException(s) {} + ServerError(const std::string &s) : BaseException(s) {} }; -class ClientStateError : public BaseException { +class ClientStateError : public BaseException +{ public: - ClientStateError(const std::string &s): BaseException(s) {} + ClientStateError(const std::string &s) : BaseException(s) {} }; -class PrngException : public BaseException { +class PrngException : public BaseException +{ public: - PrngException(const std::string &s): BaseException(s) {} + PrngException(const std::string &s) : BaseException(s) {} }; -class ModError : public BaseException { +class ModError : public BaseException +{ public: - ModError(const std::string &s): BaseException(s) {} + ModError(const std::string &s) : BaseException(s) {} }; - /* Some "old-style" interrupts: */ -class InvalidNoiseParamsException : public BaseException { +class InvalidNoiseParamsException : public BaseException +{ public: - InvalidNoiseParamsException(): - BaseException("One or more noise parameters were invalid or require " - "too much memory") - {} + InvalidNoiseParamsException() : + BaseException("One or more noise parameters were invalid or " + "require " + "too much memory") + { + } - InvalidNoiseParamsException(const std::string &s): - BaseException(s) - {} + InvalidNoiseParamsException(const std::string &s) : BaseException(s) {} }; class InvalidPositionException : public BaseException { public: - InvalidPositionException(): - BaseException("Somebody tried to get/set something in a nonexistent position.") - {} - InvalidPositionException(const std::string &s): - BaseException(s) - {} + InvalidPositionException() : + BaseException("Somebody tried to get/set something in a " + "nonexistent position.") + { + } + InvalidPositionException(const std::string &s) : BaseException(s) {} }; diff --git a/src/face_position_cache.cpp b/src/face_position_cache.cpp index 7a8f235fa..8b222aa29 100644 --- a/src/face_position_cache.cpp +++ b/src/face_position_cache.cpp @@ -20,7 +20,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "face_position_cache.h" #include "threading/mutex_auto_lock.h" - std::unordered_map> FacePositionCache::cache; std::mutex FacePositionCache::cache_mutex; @@ -40,39 +39,39 @@ const std::vector &FacePositionCache::generateFacePosition(u16 d) cache[d] = std::vector(); std::vector &c = cache[d]; if (d == 0) { - c.emplace_back(0,0,0); + c.emplace_back(0, 0, 0); return c; } if (d == 1) { // This is an optimized sequence of coordinates. - c.emplace_back(0, 1, 0); // Top - c.emplace_back(0, 0, 1); // Back + c.emplace_back(0, 1, 0); // Top + c.emplace_back(0, 0, 1); // Back c.emplace_back(-1, 0, 0); // Left - c.emplace_back(1, 0, 0); // Right - c.emplace_back(0, 0,-1); // Front - c.emplace_back(0,-1, 0); // Bottom + c.emplace_back(1, 0, 0); // Right + c.emplace_back(0, 0, -1); // Front + c.emplace_back(0, -1, 0); // Bottom // 6 - c.emplace_back(-1, 0, 1); // Back left - c.emplace_back(1, 0, 1); // Back right - c.emplace_back(-1, 0,-1); // Front left - c.emplace_back(1, 0,-1); // Front right - c.emplace_back(-1,-1, 0); // Bottom left - c.emplace_back(1,-1, 0); // Bottom right - c.emplace_back(0,-1, 1); // Bottom back - c.emplace_back(0,-1,-1); // Bottom front - c.emplace_back(-1, 1, 0); // Top left - c.emplace_back(1, 1, 0); // Top right - c.emplace_back(0, 1, 1); // Top back - c.emplace_back(0, 1,-1); // Top front + c.emplace_back(-1, 0, 1); // Back left + c.emplace_back(1, 0, 1); // Back right + c.emplace_back(-1, 0, -1); // Front left + c.emplace_back(1, 0, -1); // Front right + c.emplace_back(-1, -1, 0); // Bottom left + c.emplace_back(1, -1, 0); // Bottom right + c.emplace_back(0, -1, 1); // Bottom back + c.emplace_back(0, -1, -1); // Bottom front + c.emplace_back(-1, 1, 0); // Top left + c.emplace_back(1, 1, 0); // Top right + c.emplace_back(0, 1, 1); // Top back + c.emplace_back(0, 1, -1); // Top front // 18 - c.emplace_back(-1, 1, 1); // Top back-left - c.emplace_back(1, 1, 1); // Top back-right - c.emplace_back(-1, 1,-1); // Top front-left - c.emplace_back(1, 1,-1); // Top front-right - c.emplace_back(-1,-1, 1); // Bottom back-left - c.emplace_back(1,-1, 1); // Bottom back-right - c.emplace_back(-1,-1,-1); // Bottom front-left - c.emplace_back(1,-1,-1); // Bottom front-right + c.emplace_back(-1, 1, 1); // Top back-left + c.emplace_back(1, 1, 1); // Top back-right + c.emplace_back(-1, 1, -1); // Top front-left + c.emplace_back(1, 1, -1); // Top front-right + c.emplace_back(-1, -1, 1); // Bottom back-left + c.emplace_back(1, -1, 1); // Bottom back-right + c.emplace_back(-1, -1, -1); // Bottom front-left + c.emplace_back(1, -1, -1); // Bottom front-right // 26 return c; } @@ -80,7 +79,7 @@ const std::vector &FacePositionCache::generateFacePosition(u16 d) // Take blocks in all sides, starting from y=0 and going +-y for (s16 y = 0; y <= d - 1; y++) { // Left and right side, including borders - for (s16 z =- d; z <= d; z++) { + for (s16 z = -d; z <= d; z++) { c.emplace_back(d, y, z); c.emplace_back(-d, y, z); if (y != 0) { @@ -102,9 +101,9 @@ const std::vector &FacePositionCache::generateFacePosition(u16 d) // Take the bottom and top face with borders // -d < x < d, y = +-d, -d < z < d for (s16 x = -d; x <= d; x++) - for (s16 z = -d; z <= d; z++) { - c.emplace_back(x, -d, z); - c.emplace_back(x, d, z); - } + for (s16 z = -d; z <= d; z++) { + c.emplace_back(x, -d, z); + c.emplace_back(x, d, z); + } return c; } diff --git a/src/face_position_cache.h b/src/face_position_cache.h index 36cb06484..0fc7a21e9 100644 --- a/src/face_position_cache.h +++ b/src/face_position_cache.h @@ -30,7 +30,8 @@ with this program; if not, write to the Free Software Foundation, Inc., * This class permits caching getFacePosition call results. * This reduces CPU usage and vector calls. */ -class FacePositionCache { +class FacePositionCache +{ public: static const std::vector &getFacePositions(u16 d); diff --git a/src/filesys.cpp b/src/filesys.cpp index 0bc351669..e09867d89 100644 --- a/src/filesys.cpp +++ b/src/filesys.cpp @@ -57,7 +57,7 @@ std::vector GetDirListing(const std::string &pathstring) dwError = GetLastError(); if (dwError != ERROR_FILE_NOT_FOUND && dwError != ERROR_PATH_NOT_FOUND) { errorstream << "GetDirListing: FindFirstFile error." - << " Error is " << dwError << std::endl; + << " Error is " << dwError << std::endl; } } else { // NOTE: @@ -74,8 +74,9 @@ std::vector GetDirListing(const std::string &pathstring) while (FindNextFile(hFind, &FindFileData) != 0) { DirListNode node; node.name = FindFileData.cFileName; - node.dir = FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY; - if(node.name != "." && node.name != "..") + node.dir = FindFileData.dwFileAttributes & + FILE_ATTRIBUTE_DIRECTORY; + if (node.name != "." && node.name != "..") listing.push_back(node); } @@ -83,10 +84,10 @@ std::vector GetDirListing(const std::string &pathstring) FindClose(hFind); if (dwError != ERROR_NO_MORE_FILES) { errorstream << "GetDirListing: FindNextFile error." - << " Error is " << dwError << std::endl; + << " Error is " << dwError << std::endl; listing.clear(); return listing; - } + } } return listing; } @@ -94,9 +95,9 @@ std::vector GetDirListing(const std::string &pathstring) bool CreateDir(const std::string &path) { bool r = CreateDirectory(path.c_str(), NULL); - if(r == true) + if (r == true) return true; - if(GetLastError() == ERROR_ALREADY_EXISTS) + if (GetLastError() == ERROR_ALREADY_EXISTS) return true; return false; } @@ -114,8 +115,7 @@ bool IsPathAbsolute(const std::string &path) bool IsDir(const std::string &path) { DWORD attr = GetFileAttributes(path.c_str()); - return (attr != INVALID_FILE_ATTRIBUTES && - (attr & FILE_ATTRIBUTE_DIRECTORY)); + return (attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_DIRECTORY)); } bool IsDirDelimiter(char c) @@ -129,27 +129,27 @@ bool RecursiveDelete(const std::string &path) if (!IsDir(path)) { infostream << "RecursiveDelete: Deleting file " << path << std::endl; if (!DeleteFile(path.c_str())) { - errorstream << "RecursiveDelete: Failed to delete file " - << path << std::endl; + errorstream << "RecursiveDelete: Failed to delete file " << path + << std::endl; return false; } return true; } - infostream << "RecursiveDelete: Deleting content of directory " - << path << std::endl; + infostream << "RecursiveDelete: Deleting content of directory " << path + << std::endl; std::vector content = GetDirListing(path); - for (const DirListNode &n: content) { + for (const DirListNode &n : content) { std::string fullpath = path + DIR_DELIM + n.name; if (!RecursiveDelete(fullpath)) { errorstream << "RecursiveDelete: Failed to recurse to " - << fullpath << std::endl; + << fullpath << std::endl; return false; } } infostream << "RecursiveDelete: Deleting directory " << path << std::endl; if (!RemoveDirectory(path.c_str())) { - errorstream << "Failed to recursively delete directory " - << path << std::endl; + errorstream << "Failed to recursively delete directory " << path + << std::endl; return false; } return true; @@ -159,14 +159,11 @@ bool DeleteSingleFileOrEmptyDirectory(const std::string &path) { DWORD attr = GetFileAttributes(path.c_str()); bool is_directory = (attr != INVALID_FILE_ATTRIBUTES && - (attr & FILE_ATTRIBUTE_DIRECTORY)); - if(!is_directory) - { + (attr & FILE_ATTRIBUTE_DIRECTORY)); + if (!is_directory) { bool did = DeleteFile(path.c_str()); return did; - } - else - { + } else { bool did = RemoveDirectory(path.c_str()); return did; } @@ -175,14 +172,16 @@ bool DeleteSingleFileOrEmptyDirectory(const std::string &path) std::string TempPath() { DWORD bufsize = GetTempPath(0, NULL); - if(bufsize == 0){ - errorstream<<"GetTempPath failed, error = "< buf(bufsize); DWORD len = GetTempPath(bufsize, &buf[0]); - if(len == 0 || len > bufsize){ - errorstream<<"GetTempPath failed, error = "< bufsize) { + errorstream << "GetTempPath failed, error = " << GetLastError() + << std::endl; return ""; } return std::string(buf.begin(), buf.begin() + len); @@ -202,8 +201,8 @@ std::vector GetDirListing(const std::string &pathstring) DIR *dp; struct dirent *dirp; - if((dp = opendir(pathstring.c_str())) == NULL) { - //infostream<<"Error("< GetDirListing(const std::string &pathstring) // NOTE: // Be very sure to not include '..' in the results, it will // result in an epic failure when deleting stuff. - if(strcmp(dirp->d_name, ".") == 0 || strcmp(dirp->d_name, "..") == 0) + if (strcmp(dirp->d_name, ".") == 0 || strcmp(dirp->d_name, "..") == 0) continue; DirListNode node; @@ -227,7 +226,7 @@ std::vector GetDirListing(const std::string &pathstring) Also we don't know whether symlinks are directories or not. */ #ifdef _DIRENT_HAVE_D_TYPE - if(dirp->d_type != DT_UNKNOWN && dirp->d_type != DT_LNK) + if (dirp->d_type != DT_UNKNOWN && dirp->d_type != DT_LNK) isdir = (dirp->d_type == DT_DIR); #endif /* _DIRENT_HAVE_D_TYPE */ @@ -235,8 +234,10 @@ std::vector GetDirListing(const std::string &pathstring) Was d_type DT_UNKNOWN, DT_LNK or nonexistent? If so, try stat(). */ - if(isdir == -1) { - struct stat statbuf{}; + if (isdir == -1) { + struct stat statbuf + { + }; if (stat((pathstring + "/" + node.name).c_str(), &statbuf)) continue; isdir = ((statbuf.st_mode & S_IFDIR) == S_IFDIR); @@ -260,13 +261,14 @@ bool CreateDir(const std::string &path) if (errno == EEXIST) return true; return false; - } bool PathExists(const std::string &path) { - struct stat st{}; - return (stat(path.c_str(),&st) == 0); + struct stat st + { + }; + return (stat(path.c_str(), &st) == 0); } bool IsPathAbsolute(const std::string &path) @@ -276,8 +278,10 @@ bool IsPathAbsolute(const std::string &path) bool IsDir(const std::string &path) { - struct stat statbuf{}; - if(stat(path.c_str(), &statbuf)) + struct stat statbuf + { + }; + if (stat(path.c_str(), &statbuf)) return false; // Actually error; but certainly not a directory return ((statbuf.st_mode & S_IFDIR) == S_IFDIR); } @@ -293,14 +297,13 @@ bool RecursiveDelete(const std::string &path) Execute the 'rm' command directly, by fork() and execve() */ - infostream<<"Removing \""< &dirs, const std::string &dir) { - static const std::set chars_to_ignore = { '_', '.' }; + static const std::set chars_to_ignore = {'_', '.'}; if (dir.empty() || !IsDir(dir)) return; dirs.push_back(dir); @@ -390,10 +391,8 @@ std::vector GetRecursiveDirs(const std::string &dir) return result; } -void GetRecursiveSubPaths(const std::string &path, - std::vector &dst, - bool list_files, - const std::set &ignore) +void GetRecursiveSubPaths(const std::string &path, std::vector &dst, + bool list_files, const std::set &ignore) { std::vector content = GetDirListing(path); for (const auto &n : content) { @@ -411,11 +410,11 @@ bool DeletePaths(const std::vector &paths) { bool success = true; // Go backwards to succesfully delete the output of GetRecursiveSubPaths - for(int i=paths.size()-1; i>=0; i--){ + for (int i = paths.size() - 1; i >= 0; i--) { const std::string &path = paths[i]; bool did = DeleteSingleFileOrEmptyDirectory(path); - if(!did){ - errorstream<<"Failed to delete "< &paths) bool RecursiveDeleteContent(const std::string &path) { - infostream<<"Removing content of \""< list = GetDirListing(path); for (const DirListNode &dln : list) { - if(trim(dln.name) == "." || trim(dln.name) == "..") + if (trim(dln.name) == "." || trim(dln.name) == "..") continue; std::string childpath = path + DIR_DELIM + dln.name; bool r = RecursiveDelete(childpath); - if(!r) { - errorstream << "Removing \"" << childpath << "\" failed" << std::endl; + if (!r) { + errorstream << "Removing \"" << childpath << "\" failed" + << std::endl; return false; } } @@ -444,15 +444,14 @@ bool CreateAllDirs(const std::string &path) std::vector tocreate; std::string basepath = path; - while(!PathExists(basepath)) - { + while (!PathExists(basepath)) { tocreate.push_back(basepath); basepath = RemoveLastPathComponent(basepath); - if(basepath.empty()) + if (basepath.empty()) break; } - for(int i=tocreate.size()-1;i>=0;i--) - if(!CreateDir(tocreate[i])) + for (int i = tocreate.size() - 1; i >= 0; i--) + if (!CreateDir(tocreate[i])) return false; return true; } @@ -460,16 +459,16 @@ bool CreateAllDirs(const std::string &path) bool CopyFileContents(const std::string &source, const std::string &target) { FILE *sourcefile = fopen(source.c_str(), "rb"); - if(sourcefile == NULL){ - errorstream< 0){ + if (readbytes > 0) { fwrite(readbuffer, 1, readbytes, targetfile); } - if(feof(sourcefile) || ferror(sourcefile)){ + if (feof(sourcefile) || ferror(sourcefile)) { // flush destination file to catch write errors // (e.g. disk full) fflush(targetfile); done = true; } - if(ferror(targetfile)){ - errorstream< 0) pos++; @@ -670,7 +665,7 @@ std::string RemoveRelativePathComponents(std::string path) // remove trailing dir delimiters pos = path.size(); - while (pos != 0 && IsDirDelimiter(path[pos-1])) + while (pos != 0 && IsDirDelimiter(path[pos - 1])) pos--; return path.substr(0, pos); } @@ -682,7 +677,8 @@ std::string AbsolutePath(const std::string &path) #else char *abs_path = realpath(path.c_str(), NULL); #endif - if (!abs_path) return ""; + if (!abs_path) + return ""; std::string abs_path_str(abs_path); free(abs_path); return abs_path_str; @@ -721,9 +717,10 @@ bool safeWriteToFile(const std::string &path, const std::string &content) // Move the finished temporary file over the real file #ifdef _WIN32 - // When creating the file, it can cause Windows Search indexer, virus scanners and other apps - // to query the file. This can make the move file call below fail. - // We retry up to 5 times, with a 1ms sleep between, before we consider the whole operation failed + // When creating the file, it can cause Windows Search indexer, virus scanners and + // other apps to query the file. This can make the move file call below fail. We + // retry up to 5 times, with a 1ms sleep between, before we consider the whole + // operation failed int number_attempts = 0; while (number_attempts < 5) { rename_success = MoveFileEx(tmp_file.c_str(), path.c_str(), @@ -756,4 +753,3 @@ bool Rename(const std::string &from, const std::string &to) } } // namespace fs - diff --git a/src/filesys.h b/src/filesys.h index 09f129aa3..41395320c 100644 --- a/src/filesys.h +++ b/src/filesys.h @@ -80,10 +80,8 @@ std::vector GetRecursiveDirs(const std::string &dir); list files - include files in the list of subpaths. ignore - paths that start with these charcters will not be listed. */ -void GetRecursiveSubPaths(const std::string &path, - std::vector &dst, - bool list_files, - const std::set &ignore = {}); +void GetRecursiveSubPaths(const std::string &path, std::vector &dst, + bool list_files, const std::set &ignore = {}); // Tries to delete all, returns false if any failed bool DeletePaths(const std::vector &paths); @@ -110,8 +108,8 @@ bool PathStartsWith(const std::string &path, const std::string &prefix); // returns "" if there is only one path component. // removed: If non-NULL, receives the removed component(s). // count: Number of components to remove -std::string RemoveLastPathComponent(const std::string &path, - std::string *removed = NULL, int count = 1); +std::string RemoveLastPathComponent( + const std::string &path, std::string *removed = NULL, int count = 1); // Remove "." and ".." path components and for every ".." removed, remove // the last normal path component before it. Unlike AbsolutePath, diff --git a/src/gamedef.h b/src/gamedef.h index 723404106..726b9cef4 100644 --- a/src/gamedef.h +++ b/src/gamedef.h @@ -35,10 +35,14 @@ class Camera; class ModChannel; class ModMetadata; -namespace irr { namespace scene { - class IAnimatedMesh; - class ISceneManager; -}} +namespace irr +{ +namespace scene +{ +class IAnimatedMesh; +class ISceneManager; +} +} struct ModSpec; /* @@ -51,27 +55,27 @@ class IGameDef public: // These are thread-safe IF they are not edited while running threads. // Thus, first they are set up and then they are only read. - virtual IItemDefManager* getItemDefManager()=0; - virtual IWritableItemDefManager* getWritableItemDefManager()=0; - virtual const NodeDefManager* getNodeDefManager()=0; - virtual NodeDefManager* getWritableNodeDefManager()=0; - virtual ICraftDefManager* getCraftDefManager()=0; + virtual IItemDefManager *getItemDefManager() = 0; + virtual IWritableItemDefManager *getWritableItemDefManager() = 0; + virtual const NodeDefManager *getNodeDefManager() = 0; + virtual NodeDefManager *getWritableNodeDefManager() = 0; + virtual ICraftDefManager *getCraftDefManager() = 0; // Used for keeping track of names/ids of unknown nodes - virtual u16 allocateUnknownNodeId(const std::string &name)=0; + virtual u16 allocateUnknownNodeId(const std::string &name) = 0; // Only usable on the server, and NOT thread-safe. It is usable from the // environment thread. - virtual IRollbackManager* getRollbackManager() { return NULL; } + virtual IRollbackManager *getRollbackManager() { return NULL; } // Shorthands - IItemDefManager *idef() { return getItemDefManager(); } - const NodeDefManager *ndef() { return getNodeDefManager(); } - ICraftDefManager *cdef() { return getCraftDefManager(); } + IItemDefManager *idef() { return getItemDefManager(); } + const NodeDefManager *ndef() { return getNodeDefManager(); } + ICraftDefManager *cdef() { return getCraftDefManager(); } IRollbackManager *rollback() { return getRollbackManager(); } virtual const std::vector &getMods() const = 0; - virtual const ModSpec* getModSpec(const std::string &modname) const = 0; + virtual const ModSpec *getModSpec(const std::string &modname) const = 0; virtual std::string getWorldPath() const { return ""; } virtual std::string getModStoragePath() const = 0; virtual bool registerModStorage(ModMetadata *storage) = 0; @@ -79,7 +83,7 @@ public: virtual bool joinModChannel(const std::string &channel) = 0; virtual bool leaveModChannel(const std::string &channel) = 0; - virtual bool sendModChannelMessage(const std::string &channel, - const std::string &message) = 0; + virtual bool sendModChannelMessage( + const std::string &channel, const std::string &message) = 0; virtual ModChannel *getModChannel(const std::string &channel) = 0; }; diff --git a/src/gettext.cpp b/src/gettext.cpp index 6818004df..518e3f451 100644 --- a/src/gettext.cpp +++ b/src/gettext.cpp @@ -31,42 +31,32 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include "filesys.h" -#define setlocale(category, localename) \ - setlocale(category, MSVC_LocaleLookup(localename)) +#define setlocale(category, localename) setlocale(category, MSVC_LocaleLookup(localename)) static std::map glb_supported_locales; /******************************************************************************/ BOOL CALLBACK UpdateLocaleCallback(LPTSTR pStr) { - char* endptr = 0; - int LOCALEID = strtol(pStr, &endptr,16); + char *endptr = 0; + int LOCALEID = strtol(pStr, &endptr, 16); wchar_t buffer[LOCALE_NAME_MAX_LENGTH]; memset(buffer, 0, sizeof(buffer)); - if (GetLocaleInfoW( - LOCALEID, - LOCALE_SISO639LANGNAME, - buffer, - LOCALE_NAME_MAX_LENGTH)) { + if (GetLocaleInfoW(LOCALEID, LOCALE_SISO639LANGNAME, buffer, + LOCALE_NAME_MAX_LENGTH)) { std::wstring name = buffer; memset(buffer, 0, sizeof(buffer)); - GetLocaleInfoW( - LOCALEID, - LOCALE_SISO3166CTRYNAME, - buffer, - LOCALE_NAME_MAX_LENGTH); + GetLocaleInfoW(LOCALEID, LOCALE_SISO3166CTRYNAME, buffer, + LOCALE_NAME_MAX_LENGTH); std::wstring country = buffer; memset(buffer, 0, sizeof(buffer)); - GetLocaleInfoW( - LOCALEID, - LOCALE_SENGLISHLANGUAGENAME, - buffer, - LOCALE_NAME_MAX_LENGTH); + GetLocaleInfoW(LOCALEID, LOCALE_SENGLISHLANGUAGENAME, buffer, + LOCALE_NAME_MAX_LENGTH); std::wstring languagename = buffer; @@ -78,14 +68,18 @@ BOOL CALLBACK UpdateLocaleCallback(LPTSTR pStr) } /******************************************************************************/ -const char* MSVC_LocaleLookup(const char* raw_shortname) { +const char *MSVC_LocaleLookup(const char *raw_shortname) +{ /* NULL is used to read locale only so we need to return it too */ - if (raw_shortname == NULL) return NULL; + if (raw_shortname == NULL) + return NULL; std::string shortname(raw_shortname); - if (shortname == "C") return "C"; - if (shortname == "") return ""; + if (shortname == "C") + return "C"; + if (shortname == "") + return ""; static std::string last_raw_value = ""; static std::string last_full_name = ""; @@ -96,29 +90,31 @@ const char* MSVC_LocaleLookup(const char* raw_shortname) { } if (first_use) { - EnumSystemLocalesA(UpdateLocaleCallback, LCID_SUPPORTED | LCID_ALTERNATE_SORTS); + EnumSystemLocalesA(UpdateLocaleCallback, + LCID_SUPPORTED | LCID_ALTERNATE_SORTS); first_use = false; } last_raw_value = shortname; - if (glb_supported_locales.find(utf8_to_wide(shortname)) != glb_supported_locales.end()) { + if (glb_supported_locales.find(utf8_to_wide(shortname)) != + glb_supported_locales.end()) { last_full_name = wide_to_utf8( - glb_supported_locales[utf8_to_wide(shortname)]); + glb_supported_locales[utf8_to_wide(shortname)]); return last_full_name.c_str(); } /* empty string is system default */ errorstream << "MSVC_LocaleLookup: unsupported locale: \"" << shortname - << "\" switching to system default!" << std::endl; + << "\" switching to system default!" << std::endl; return ""; } #endif /******************************************************************************/ -void init_gettext(const char *path, const std::string &configured_language, - int argc, char *argv[]) +void init_gettext(const char *path, const std::string &configured_language, int argc, + char *argv[]) { #if USE_GETTEXT // First, try to set user override environment @@ -142,7 +138,9 @@ void init_gettext(const char *path, const std::string &configured_language, // Hack to force gettext to see the right environment if (current_language != configured_language) { errorstream << "MSVC localization workaround active. " - "Restarting " PROJECT_NAME_C " in a new environment!" << std::endl; + "Restarting " PROJECT_NAME_C + " in a new environment!" + << std::endl; std::string parameters; @@ -166,9 +164,10 @@ void init_gettext(const char *path, const std::string &configured_language, STARTUPINFO startup_info = {0}; PROCESS_INFORMATION process_info = {0}; - bool success = CreateProcess(app_name.c_str(), (char *)ptr_parameters, - NULL, NULL, false, DETACHED_PROCESS | CREATE_UNICODE_ENVIRONMENT, - NULL, NULL, &startup_info, &process_info); + bool success = CreateProcess(app_name.c_str(), + (char *)ptr_parameters, NULL, NULL, false, + DETACHED_PROCESS | CREATE_UNICODE_ENVIRONMENT, + NULL, NULL, &startup_info, &process_info); if (success) { exit(0); @@ -176,33 +175,41 @@ void init_gettext(const char *path, const std::string &configured_language, } else { char buffer[1024]; - FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), - MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT), buffer, - sizeof(buffer) - 1, NULL); + FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, + GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + buffer, sizeof(buffer) - 1, NULL); - errorstream << "*******************************************************" << std::endl; + errorstream << "*****************************************" + "**************" + << std::endl; errorstream << "CMD: " << app_name << std::endl; - errorstream << "Failed to restart with current locale: " << std::endl; + errorstream << "Failed to restart with current locale: " + << std::endl; errorstream << buffer; - errorstream << "Expect language to be broken!" << std::endl; - errorstream << "*******************************************************" << std::endl; + errorstream << "Expect language to be broken!" + << std::endl; + errorstream << "*****************************************" + "**************" + << std::endl; } } #else - errorstream << "*******************************************************" << std::endl; + errorstream << "*******************************************************" + << std::endl; errorstream << "Can't apply locale workaround for server!" << std::endl; errorstream << "Expect language to be broken!" << std::endl; - errorstream << "*******************************************************" << std::endl; + errorstream << "*******************************************************" + << std::endl; #endif setlocale(LC_ALL, configured_language.c_str()); -#else // Mingw +#else // Mingw _putenv(("LANGUAGE=" + configured_language).c_str()); setlocale(LC_ALL, ""); #endif // ifndef _WIN32 - } - else { - /* set current system default locale */ + } else { + /* set current system default locale */ setlocale(LC_ALL, ""); } @@ -218,23 +225,23 @@ void init_gettext(const char *path, const std::string &configured_language, #endif std::string name = lowercase(PROJECT_NAME); - infostream << "Gettext: domainname=\"" << name - << "\" path=\"" << path << "\"" << std::endl; + infostream << "Gettext: domainname=\"" << name << "\" path=\"" << path << "\"" + << std::endl; bindtextdomain(name.c_str(), path); textdomain(name.c_str()); #if defined(_WIN32) // Set character encoding for Win32 - char *tdomain = textdomain( (char *) NULL ); - if( tdomain == NULL ) - { - errorstream << "Warning: domainname parameter is the null pointer" << - ", default domain is not set" << std::endl; - tdomain = (char *) "messages"; + char *tdomain = textdomain((char *)NULL); + if (tdomain == NULL) { + errorstream << "Warning: domainname parameter is the null pointer" + << ", default domain is not set" << std::endl; + tdomain = (char *)"messages"; } - /* char *codeset = */bind_textdomain_codeset( tdomain, "UTF-8" ); - //errorstream << "Gettext debug: domainname = " << tdomain << "; codeset = "<< codeset << std::endl; + /* char *codeset = */ bind_textdomain_codeset(tdomain, "UTF-8"); + // errorstream << "Gettext debug: domainname = " << tdomain << "; codeset = "<< + // codeset << std::endl; #endif // defined(_WIN32) #else @@ -246,6 +253,6 @@ void init_gettext(const char *path, const std::string &configured_language, /* to ensure formspec parameters are evaluated correct! */ setlocale(LC_NUMERIC, "C"); - infostream << "Message locale is now set to: " - << setlocale(LC_ALL, 0) << std::endl; + infostream << "Message locale is now set to: " << setlocale(LC_ALL, 0) + << std::endl; } diff --git a/src/gettext.h b/src/gettext.h index 42b375d86..b3f571afb 100644 --- a/src/gettext.h +++ b/src/gettext.h @@ -23,27 +23,27 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #if USE_GETTEXT - #include +#include #else - // In certain environments, some standard headers like - // and include libintl.h. If libintl.h is included after - // we define our gettext macro below, this causes a syntax error - // at the declaration of the gettext function in libintl.h. - // Fix this by including such a header before defining the macro. - // See issue #4446. - // Note that we can't include libintl.h directly since we're in - // the USE_GETTEXT=0 case and can't assume that gettext is installed. - #include +// In certain environments, some standard headers like +// and include libintl.h. If libintl.h is included after +// we define our gettext macro below, this causes a syntax error +// at the declaration of the gettext function in libintl.h. +// Fix this by including such a header before defining the macro. +// See issue #4446. +// Note that we can't include libintl.h directly since we're in +// the USE_GETTEXT=0 case and can't assume that gettext is installed. +#include - #define gettext(String) String +#define gettext(String) String #endif #define _(String) gettext(String) #define gettext_noop(String) (String) #define N_(String) gettext_noop((String)) -void init_gettext(const char *path, const std::string &configured_language, - int argc, char *argv[]); +void init_gettext(const char *path, const std::string &configured_language, int argc, + char *argv[]); extern wchar_t *utf8_to_wide_c(const char *str); diff --git a/src/gui/StyleSpec.h b/src/gui/StyleSpec.h index 67caf4f7b..aae2d46f8 100644 --- a/src/gui/StyleSpec.h +++ b/src/gui/StyleSpec.h @@ -138,10 +138,7 @@ public: } //! Gets the state that this style is intended for - State getState() const - { - return state_map; - } + State getState() const { return state_map; } //! Set the given state on this style void addState(State state) @@ -153,7 +150,8 @@ public: //! Using a list of styles mapped to state values, calculate the final // combined style for a state by propagating values in its component states - static StyleSpec getStyleFromStatePropagation(const std::array &styles, State state) + static StyleSpec getStyleFromStatePropagation( + const std::array &styles, State state) { StyleSpec temp = styles[StyleSpec::STATE_DEFAULT]; temp.state_map = state; @@ -210,7 +208,8 @@ public: return rect; } - irr::core::vector2d getVector2i(Property prop, irr::core::vector2d def) const + irr::core::vector2d getVector2i( + Property prop, irr::core::vector2d def) const { const auto &val = properties[prop]; if (val.empty()) @@ -260,10 +259,13 @@ public: int calc_size = 1; if (size[0] == '*') { - std::string new_size = size.substr(1); // Remove '*' (invalid for stof) - calc_size = stof(new_size) * g_fontengine->getFontSize(spec.mode); + std::string new_size = size.substr( + 1); // Remove '*' (invalid for stof) + calc_size = stof(new_size) * + g_fontengine->getFontSize(spec.mode); } else if (size[0] == '+' || size[0] == '-') { - calc_size = stoi(size) + g_fontengine->getFontSize(spec.mode); + calc_size = stoi(size) + + g_fontengine->getFontSize(spec.mode); } else { calc_size = stoi(size); } @@ -345,7 +347,7 @@ private: rect.LowerRightCorner = irr::core::vector2di(-x, -x); } else if (v_rect.size() == 2) { s32 x = stoi(v_rect[0]); - s32 y = stoi(v_rect[1]); + s32 y = stoi(v_rect[1]); rect.UpperLeftCorner = irr::core::vector2di(x, y); rect.LowerRightCorner = irr::core::vector2di(-x, -y); // `-x` is interpreted as `w - x` @@ -356,7 +358,7 @@ private: stoi(v_rect[2]), stoi(v_rect[3])); } else { warningstream << "Invalid rectangle string format: \"" << value - << "\"" << std::endl; + << "\"" << std::endl; return false; } @@ -365,7 +367,8 @@ private: return true; } - bool parseVector2i(const std::string &value, irr::core::vector2d *parsed_vec) const + bool parseVector2i(const std::string &value, + irr::core::vector2d *parsed_vec) const { irr::core::vector2d vec; std::vector v_vector = split(value, ','); @@ -376,12 +379,12 @@ private: vec.Y = x; } else if (v_vector.size() == 2) { s32 x = stoi(v_vector[0]); - s32 y = stoi(v_vector[1]); + s32 y = stoi(v_vector[1]); vec.X = x; vec.Y = y; } else { warningstream << "Invalid vector2d string format: \"" << value - << "\"" << std::endl; + << "\"" << std::endl; return false; } diff --git a/src/gui/cheatMenu.cpp b/src/gui/cheatMenu.cpp index e66fbc250..d441e8dfc 100644 --- a/src/gui/cheatMenu.cpp +++ b/src/gui/cheatMenu.cpp @@ -23,8 +23,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "cheatMenu.h" #include -FontMode CheatMenu::fontStringToEnum(std::string str) { - if (str == "FM_Standard") +FontMode CheatMenu::fontStringToEnum(std::string str) +{ + if (str == "FM_Standard") return FM_Standard; else if (str == "FM_Mono") return FM_Mono; @@ -52,18 +53,21 @@ CheatMenu::CheatMenu(Client *client) : m_client(client) font_color = g_settings->getV3F("cheat_menu_font_color"); selected_font_color = g_settings->getV3F("cheat_menu_selected_font_color"); - m_bg_color = video::SColor(g_settings->getU32("cheat_menu_bg_color_alpha"), - bg_color.X, bg_color.Y, bg_color.Z); - - m_active_bg_color = video::SColor(g_settings->getU32("cheat_menu_active_bg_color_alpha"), - active_bg_color.X, active_bg_color.Y, active_bg_color.Z); + m_bg_color = video::SColor(g_settings->getU32("cheat_menu_bg_color_alpha"), + bg_color.X, bg_color.Y, bg_color.Z); + + m_active_bg_color = video::SColor( + g_settings->getU32("cheat_menu_active_bg_color_alpha"), + active_bg_color.X, active_bg_color.Y, active_bg_color.Z); m_font_color = video::SColor(g_settings->getU32("cheat_menu_font_color_alpha"), - font_color.X, font_color.Y, font_color.Z); + font_color.X, font_color.Y, font_color.Z); + + m_selected_font_color = video::SColor( + g_settings->getU32("cheat_menu_selected_font_color_alpha"), + selected_font_color.X, selected_font_color.Y, + selected_font_color.Z); - m_selected_font_color = video::SColor(g_settings->getU32("cheat_menu_selected_font_color_alpha"), - selected_font_color.X, selected_font_color.Y, selected_font_color.Z); - m_font = g_fontengine->getFont(FONT_SIZE_UNSPECIFIED, fontMode); if (!m_font) { @@ -137,64 +141,73 @@ void CheatMenu::draw(video::IVideoDriver *driver, bool show_debug) void CheatMenu::drawHUD(video::IVideoDriver *driver, double dtime) { CHEAT_MENU_GET_SCRIPTPTR - + m_rainbow_offset += dtime; m_rainbow_offset = fmod(m_rainbow_offset, 6.0f); - + std::vector enabled_cheats; - + int cheat_count = 0; - - for (auto category = script->m_cheat_categories.begin(); category != script->m_cheat_categories.end(); category++) { - for (auto cheat = (*category)->m_cheats.begin(); cheat != (*category)->m_cheats.end(); cheat++) { + + for (auto category = script->m_cheat_categories.begin(); + category != script->m_cheat_categories.end(); category++) { + for (auto cheat = (*category)->m_cheats.begin(); + cheat != (*category)->m_cheats.end(); cheat++) { if ((*cheat)->is_enabled()) { enabled_cheats.push_back((*cheat)->m_name); cheat_count++; } } } - + if (enabled_cheats.empty()) return; - + std::vector colors; - + for (int i = 0; i < cheat_count; i++) { video::SColor color; f32 h = (f32)i * 2.0f / (f32)cheat_count - m_rainbow_offset; if (h < 0) h = 6.0f + h; f32 x = (1 - fabs(fmod(h, 2.0f) - 1.0f)) * 255.0f; - switch((int)h) { + switch ((int)h) { case 0: - color = video::SColor(255, 255, x, 0); break; + color = video::SColor(255, 255, x, 0); + break; case 1: - color = video::SColor(255, x, 255, 0); break; + color = video::SColor(255, x, 255, 0); + break; case 2: - color = video::SColor(255, 0, 255, x); break; + color = video::SColor(255, 0, 255, x); + break; case 3: - color = video::SColor(255, 0, x, 255); break; + color = video::SColor(255, 0, x, 255); + break; case 4: - color = video::SColor(255, x, 0, 255); break; + color = video::SColor(255, x, 0, 255); + break; case 5: - color = video::SColor(255, 255, 0, x); break; + color = video::SColor(255, 255, 0, x); + break; } colors.push_back(color); } - + core::dimension2d screensize = driver->getScreenSize(); - + u32 y = 5; - + int i = 0; for (std::string cheat : enabled_cheats) { - core::dimension2d dim = m_font->getDimension(utf8_to_wide(cheat).c_str()); + core::dimension2d dim = + m_font->getDimension(utf8_to_wide(cheat).c_str()); u32 x = screensize.Width - 5 - dim.Width; - + core::rect fontbounds(x, y, x + dim.Width, y + dim.Height); m_font->draw(cheat.c_str(), fontbounds, colors[i], false, false); - + y += dim.Height; i++; } diff --git a/src/gui/cheatMenu.h b/src/gui/cheatMenu.h index 8be73c483..b15858a48 100644 --- a/src/gui/cheatMenu.h +++ b/src/gui/cheatMenu.h @@ -42,18 +42,15 @@ class CheatMenu public: CheatMenu(Client *client); - ClientScripting *getScript() - { - return m_client->getScript(); - } + ClientScripting *getScript() { return m_client->getScript(); } void draw(video::IVideoDriver *driver, bool show_debug); - + void drawHUD(video::IVideoDriver *driver, double dtime); void drawEntry(video::IVideoDriver *driver, std::string name, int number, - bool selected, bool active, - CheatMenuEntryType entry_type = CHEAT_MENU_ENTRY_TYPE_ENTRY); + bool selected, bool active, + CheatMenuEntryType entry_type = CHEAT_MENU_ENTRY_TYPE_ENTRY); void selectUp(); void selectDown(); @@ -82,6 +79,6 @@ private: gui::IGUIFont *m_font = nullptr; v2u32 m_fontsize; - + float m_rainbow_offset = 0.0; }; diff --git a/src/gui/guiAnimatedImage.cpp b/src/gui/guiAnimatedImage.cpp index b1447c45f..d8648111f 100644 --- a/src/gui/guiAnimatedImage.cpp +++ b/src/gui/guiAnimatedImage.cpp @@ -9,13 +9,14 @@ #include GUIAnimatedImage::GUIAnimatedImage(gui::IGUIEnvironment *env, gui::IGUIElement *parent, - s32 id, const core::rect &rectangle, const std::string &texture_name, - s32 frame_count, s32 frame_duration, ISimpleTextureSource *tsrc) : - gui::IGUIElement(gui::EGUIET_ELEMENT, env, parent, id, rectangle), m_tsrc(tsrc) + s32 id, const core::rect &rectangle, const std::string &texture_name, + s32 frame_count, s32 frame_duration, ISimpleTextureSource *tsrc) : + gui::IGUIElement(gui::EGUIET_ELEMENT, env, parent, id, rectangle), + m_tsrc(tsrc) { m_texture = m_tsrc->getTexture(texture_name); - m_frame_count = std::max(frame_count, 1); + m_frame_count = std::max(frame_count, 1); m_frame_duration = std::max(frame_duration, 0); if (m_texture != nullptr) { @@ -41,8 +42,10 @@ void GUIAnimatedImage::draw() size.Height /= m_frame_count; draw2DImageFilterScaled(driver, m_texture, AbsoluteRect, - core::rect(core::position2d(0, size.Height * m_frame_idx), size), - NoClip ? nullptr : &AbsoluteClippingRect, colors, true); + core::rect(core::position2d(0, + size.Height * m_frame_idx), + size), + NoClip ? nullptr : &AbsoluteClippingRect, colors, true); } // Step the animation @@ -64,7 +67,6 @@ void GUIAnimatedImage::draw() } } - void GUIAnimatedImage::setFrameIndex(s32 frame) { s32 idx = std::max(frame, 0); diff --git a/src/gui/guiAnimatedImage.h b/src/gui/guiAnimatedImage.h index f8e6a506e..c814f609c 100644 --- a/src/gui/guiAnimatedImage.h +++ b/src/gui/guiAnimatedImage.h @@ -5,11 +5,12 @@ class ISimpleTextureSource; -class GUIAnimatedImage : public gui::IGUIElement { +class GUIAnimatedImage : public gui::IGUIElement +{ public: - GUIAnimatedImage(gui::IGUIEnvironment *env, gui::IGUIElement *parent, - s32 id, const core::rect &rectangle, const std::string &texture_name, - s32 frame_count, s32 frame_duration, ISimpleTextureSource *tsrc); + GUIAnimatedImage(gui::IGUIEnvironment *env, gui::IGUIElement *parent, s32 id, + const core::rect &rectangle, const std::string &texture_name, + s32 frame_count, s32 frame_duration, ISimpleTextureSource *tsrc); virtual void draw() override; diff --git a/src/gui/guiBackgroundImage.cpp b/src/gui/guiBackgroundImage.cpp index 21c1e88cf..6b9559087 100644 --- a/src/gui/guiBackgroundImage.cpp +++ b/src/gui/guiBackgroundImage.cpp @@ -20,11 +20,11 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include "log.h" GUIBackgroundImage::GUIBackgroundImage(gui::IGUIEnvironment *env, - gui::IGUIElement *parent, s32 id, const core::rect &rectangle, - const std::string &name, const core::rect &middle, - ISimpleTextureSource *tsrc, bool autoclip) : - gui::IGUIElement(gui::EGUIET_ELEMENT, env, parent, id, rectangle), - m_name(name), m_middle(middle), m_tsrc(tsrc), m_autoclip(autoclip) + gui::IGUIElement *parent, s32 id, const core::rect &rectangle, + const std::string &name, const core::rect &middle, + ISimpleTextureSource *tsrc, bool autoclip) : + gui::IGUIElement(gui::EGUIET_ELEMENT, env, parent, id, rectangle), + m_name(name), m_middle(middle), m_tsrc(tsrc), m_autoclip(autoclip) { } @@ -37,7 +37,7 @@ void GUIBackgroundImage::draw() if (!texture) { errorstream << "GUIBackgroundImage::draw() Unable to load texture:" - << std::endl; + << std::endl; errorstream << "\t" << m_name << std::endl; return; } @@ -53,7 +53,8 @@ void GUIBackgroundImage::draw() const video::SColor colors[] = {color, color, color, color}; draw2DImageFilterScaled(driver, texture, rect, core::rect(core::position2d(0, 0), - core::dimension2di(texture->getOriginalSize())), + core::dimension2di( + texture->getOriginalSize())), nullptr, colors, true); } else { core::rect middle = m_middle; diff --git a/src/gui/guiBackgroundImage.h b/src/gui/guiBackgroundImage.h index 31fbfd09c..5c8ca690d 100644 --- a/src/gui/guiBackgroundImage.h +++ b/src/gui/guiBackgroundImage.h @@ -25,8 +25,9 @@ class GUIBackgroundImage : public gui::IGUIElement { public: GUIBackgroundImage(gui::IGUIEnvironment *env, gui::IGUIElement *parent, s32 id, - const core::rect &rectangle, const std::string &name, - const core::rect &middle, ISimpleTextureSource *tsrc, bool autoclip); + const core::rect &rectangle, const std::string &name, + const core::rect &middle, ISimpleTextureSource *tsrc, + bool autoclip); virtual void draw() override; diff --git a/src/gui/guiBox.cpp b/src/gui/guiBox.cpp index 7f329cc32..23551a7c7 100644 --- a/src/gui/guiBox.cpp +++ b/src/gui/guiBox.cpp @@ -20,9 +20,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "guiBox.h" GUIBox::GUIBox(gui::IGUIEnvironment *env, gui::IGUIElement *parent, s32 id, - const core::rect &rectangle, const video::SColor &color) : - gui::IGUIElement(gui::EGUIET_ELEMENT, env, parent, id, rectangle), - m_color(color) + const core::rect &rectangle, const video::SColor &color) : + gui::IGUIElement(gui::EGUIET_ELEMENT, env, parent, id, rectangle), + m_color(color) { } @@ -31,8 +31,8 @@ void GUIBox::draw() if (!IsVisible) return; - Environment->getVideoDriver()->draw2DRectangle(m_color, AbsoluteRect, - &AbsoluteClippingRect); + Environment->getVideoDriver()->draw2DRectangle( + m_color, AbsoluteRect, &AbsoluteClippingRect); IGUIElement::draw(); } diff --git a/src/gui/guiBox.h b/src/gui/guiBox.h index 5306fdf65..2367e43e3 100644 --- a/src/gui/guiBox.h +++ b/src/gui/guiBox.h @@ -25,7 +25,7 @@ class GUIBox : public gui::IGUIElement { public: GUIBox(gui::IGUIEnvironment *env, gui::IGUIElement *parent, s32 id, - const core::rect &rectangle, const video::SColor &color); + const core::rect &rectangle, const video::SColor &color); virtual void draw() override; diff --git a/src/gui/guiButton.cpp b/src/gui/guiButton.cpp index e0d6337cd..2fb7659d7 100644 --- a/src/gui/guiButton.cpp +++ b/src/gui/guiButton.cpp @@ -4,7 +4,6 @@ #include "guiButton.h" - #include "client/guiscalingfilter.h" #include "client/tile.h" #include "IGUISkin.h" @@ -26,16 +25,14 @@ using namespace gui; #define COLOR_PRESSED_MOD 0.85f //! constructor -GUIButton::GUIButton(IGUIEnvironment* environment, IGUIElement* parent, - s32 id, core::rect rectangle, ISimpleTextureSource *tsrc, - bool noclip) -: IGUIButton(environment, parent, id, rectangle), - SpriteBank(0), OverrideFont(0), - OverrideColorEnabled(false), OverrideColor(video::SColor(101,255,255,255)), - ClickTime(0), HoverTime(0), FocusTime(0), - ClickShiftState(false), ClickControlState(false), - IsPushButton(false), Pressed(false), - UseAlphaChannel(false), DrawBorder(true), ScaleImage(false), TSrc(tsrc) +GUIButton::GUIButton(IGUIEnvironment *environment, IGUIElement *parent, s32 id, + core::rect rectangle, ISimpleTextureSource *tsrc, bool noclip) : + IGUIButton(environment, parent, id, rectangle), + SpriteBank(0), OverrideFont(0), OverrideColorEnabled(false), + OverrideColor(video::SColor(101, 255, 255, 255)), ClickTime(0), + HoverTime(0), FocusTime(0), ClickShiftState(false), + ClickControlState(false), IsPushButton(false), Pressed(false), + UseAlphaChannel(false), DrawBorder(true), ScaleImage(false), TSrc(tsrc) { setNotClipped(noclip); @@ -47,7 +44,10 @@ GUIButton::GUIButton(IGUIEnvironment* environment, IGUIElement* parent, for (size_t i = 0; i < 4; i++) { Colors[i] = Environment->getSkin()->getColor((EGUI_DEFAULT_COLOR)i); } - StaticText = gui::StaticText::add(Environment, Text.c_str(), core::rect(0,0,rectangle.getWidth(),rectangle.getHeight()), false, false, this, id); + StaticText = gui::StaticText::add(Environment, Text.c_str(), + core::rect(0, 0, rectangle.getWidth(), + rectangle.getHeight()), + false, false, this, id); StaticText->setTextAlignment(EGUIA_CENTER, EGUIA_CENTER); // END PATCH } @@ -62,29 +62,25 @@ GUIButton::~GUIButton() SpriteBank->drop(); } - //! Sets if the images should be scaled to fit the button void GUIButton::setScaleImage(bool scaleImage) { ScaleImage = scaleImage; } - //! Returns whether the button scale the used images bool GUIButton::isScalingImage() const { return ScaleImage; } - //! Sets if the button should use the skin to draw its border void GUIButton::setDrawBorder(bool border) { DrawBorder = border; } - -void GUIButton::setSpriteBank(IGUISpriteBank* sprites) +void GUIButton::setSpriteBank(IGUISpriteBank *sprites) { if (sprites) sprites->grab(); @@ -95,11 +91,12 @@ void GUIButton::setSpriteBank(IGUISpriteBank* sprites) SpriteBank = sprites; } -void GUIButton::setSprite(EGUI_BUTTON_STATE state, s32 index, video::SColor color, bool loop, bool scale) +void GUIButton::setSprite(EGUI_BUTTON_STATE state, s32 index, video::SColor color, + bool loop, bool scale) { - ButtonSprites[(u32)state].Index = index; - ButtonSprites[(u32)state].Color = color; - ButtonSprites[(u32)state].Loop = loop; + ButtonSprites[(u32)state].Index = index; + ButtonSprites[(u32)state].Color = color; + ButtonSprites[(u32)state].Loop = loop; ButtonSprites[(u32)state].Scale = scale; } @@ -128,17 +125,16 @@ bool GUIButton::getSpriteScale(EGUI_BUTTON_STATE state) const } //! called if an event happened. -bool GUIButton::OnEvent(const SEvent& event) +bool GUIButton::OnEvent(const SEvent &event) { if (!isEnabled()) return IGUIElement::OnEvent(event); - switch(event.EventType) - { + switch (event.EventType) { case EET_KEY_INPUT_EVENT: if (event.KeyInput.PressedDown && - (event.KeyInput.Key == KEY_RETURN || event.KeyInput.Key == KEY_SPACE)) - { + (event.KeyInput.Key == KEY_RETURN || + event.KeyInput.Key == KEY_SPACE)) { if (!IsPushButton) setPressed(true); else @@ -146,21 +142,18 @@ bool GUIButton::OnEvent(const SEvent& event) return true; } - if (Pressed && !IsPushButton && event.KeyInput.PressedDown && event.KeyInput.Key == KEY_ESCAPE) - { + if (Pressed && !IsPushButton && event.KeyInput.PressedDown && + event.KeyInput.Key == KEY_ESCAPE) { setPressed(false); return true; - } - else - if (!event.KeyInput.PressedDown && Pressed && - (event.KeyInput.Key == KEY_RETURN || event.KeyInput.Key == KEY_SPACE)) - { + } else if (!event.KeyInput.PressedDown && Pressed && + (event.KeyInput.Key == KEY_RETURN || + event.KeyInput.Key == KEY_SPACE)) { if (!IsPushButton) setPressed(false); - if (Parent) - { + if (Parent) { ClickShiftState = event.KeyInput.Shift; ClickControlState = event.KeyInput.Control; @@ -175,43 +168,37 @@ bool GUIButton::OnEvent(const SEvent& event) } break; case EET_GUI_EVENT: - if (event.GUIEvent.Caller == this) - { - if (event.GUIEvent.EventType == EGET_ELEMENT_FOCUS_LOST) - { + if (event.GUIEvent.Caller == this) { + if (event.GUIEvent.EventType == EGET_ELEMENT_FOCUS_LOST) { if (!IsPushButton) setPressed(false); FocusTime = (u32)porting::getTimeMs(); - } - else if (event.GUIEvent.EventType == EGET_ELEMENT_FOCUSED) - { + } else if (event.GUIEvent.EventType == EGET_ELEMENT_FOCUSED) { FocusTime = (u32)porting::getTimeMs(); - } - else if (event.GUIEvent.EventType == EGET_ELEMENT_HOVERED || event.GUIEvent.EventType == EGET_ELEMENT_LEFT) - { + } else if (event.GUIEvent.EventType == EGET_ELEMENT_HOVERED || + event.GUIEvent.EventType == EGET_ELEMENT_LEFT) { HoverTime = (u32)porting::getTimeMs(); } } break; case EET_MOUSE_INPUT_EVENT: - if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) - { + if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) { // Sometimes formspec elements can receive mouse events when the - // mouse is outside of the formspec. Thus, we test the position here. - if ( !IsPushButton && AbsoluteClippingRect.isPointInside( - core::position2d(event.MouseInput.X, event.MouseInput.Y ))) { + // mouse is outside of the formspec. Thus, we test the position + // here. + if (!IsPushButton && + AbsoluteClippingRect.isPointInside(core::position2d< + s32>(event.MouseInput.X, + event.MouseInput.Y))) { setPressed(true); } return true; - } - else - if (event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP) - { + } else if (event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP) { bool wasPressed = Pressed; - if ( !AbsoluteClippingRect.isPointInside( core::position2d(event.MouseInput.X, event.MouseInput.Y ) ) ) - { + if (!AbsoluteClippingRect.isPointInside(core::position2d( + event.MouseInput.X, event.MouseInput.Y))) { if (!IsPushButton) setPressed(false); return true; @@ -219,14 +206,12 @@ bool GUIButton::OnEvent(const SEvent& event) if (!IsPushButton) setPressed(false); - else - { + else { setPressed(!Pressed); } if ((!IsPushButton && wasPressed && Parent) || - (IsPushButton && wasPressed != Pressed)) - { + (IsPushButton && wasPressed != Pressed)) { ClickShiftState = event.MouseInput.Shift; ClickControlState = event.MouseInput.Control; @@ -248,7 +233,6 @@ bool GUIButton::OnEvent(const SEvent& event) return Parent ? Parent->OnEvent(event) : false; } - //! draws the element and its children void GUIButton::draw() { @@ -263,21 +247,17 @@ void GUIButton::draw() setFromState(); } - GUISkin* skin = dynamic_cast(Environment->getSkin()); - video::IVideoDriver* driver = Environment->getVideoDriver(); + GUISkin *skin = dynamic_cast(Environment->getSkin()); + video::IVideoDriver *driver = Environment->getVideoDriver(); // END PATCH - if (DrawBorder) - { - if (!Pressed) - { + if (DrawBorder) { + if (!Pressed) { // PATCH skin->drawColored3DButtonPaneStandard(this, AbsoluteRect, &AbsoluteClippingRect, Colors); // END PATCH - } - else - { + } else { // PATCH skin->drawColored3DButtonPanePressed(this, AbsoluteRect, &AbsoluteClippingRect, Colors); @@ -290,94 +270,108 @@ void GUIButton::draw() // The image changes based on the state, so we use the default every time. EGUI_BUTTON_IMAGE_STATE imageState = EGBIS_IMAGE_UP; // END PATCH - if ( ButtonImages[(u32)imageState].Texture ) - { + if (ButtonImages[(u32)imageState].Texture) { core::position2d pos(buttonCenter); core::rect sourceRect(ButtonImages[(u32)imageState].SourceRect); - if ( sourceRect.getWidth() == 0 && sourceRect.getHeight() == 0 ) - sourceRect = core::rect(core::position2di(0,0), ButtonImages[(u32)imageState].Texture->getOriginalSize()); + if (sourceRect.getWidth() == 0 && sourceRect.getHeight() == 0) + sourceRect = core::rect(core::position2di(0, 0), + ButtonImages[(u32)imageState] + .Texture->getOriginalSize()); pos.X -= sourceRect.getWidth() / 2; pos.Y -= sourceRect.getHeight() / 2; - if ( Pressed ) - { - // Create a pressed-down effect by moving the image when it looks identical to the unpressed state image + if (Pressed) { + // Create a pressed-down effect by moving the image when it looks + // identical to the unpressed state image EGUI_BUTTON_IMAGE_STATE unpressedState = getImageState(false); - if ( unpressedState == imageState || ButtonImages[(u32)imageState] == ButtonImages[(u32)unpressedState] ) - { - pos.X += skin->getSize(EGDS_BUTTON_PRESSED_IMAGE_OFFSET_X); - pos.Y += skin->getSize(EGDS_BUTTON_PRESSED_IMAGE_OFFSET_Y); + if (unpressedState == imageState || + ButtonImages[(u32)imageState] == + ButtonImages[(u32)unpressedState]) { + pos.X += skin->getSize( + EGDS_BUTTON_PRESSED_IMAGE_OFFSET_X); + pos.Y += skin->getSize( + EGDS_BUTTON_PRESSED_IMAGE_OFFSET_Y); } } // PATCH - video::ITexture* texture = ButtonImages[(u32)imageState].Texture; + video::ITexture *texture = ButtonImages[(u32)imageState].Texture; if (BgMiddle.getArea() == 0) { driver->draw2DImage(texture, - ScaleImage? AbsoluteRect : core::rect(pos, sourceRect.getSize()), - sourceRect, &AbsoluteClippingRect, - 0, UseAlphaChannel); + ScaleImage ? AbsoluteRect + : core::rect(pos, + sourceRect.getSize()), + sourceRect, &AbsoluteClippingRect, 0, + UseAlphaChannel); } else { core::rect middle = BgMiddle; // `-x` is interpreted as `w - x` if (middle.LowerRightCorner.X < 0) - middle.LowerRightCorner.X += texture->getOriginalSize().Width; + middle.LowerRightCorner.X += + texture->getOriginalSize().Width; if (middle.LowerRightCorner.Y < 0) - middle.LowerRightCorner.Y += texture->getOriginalSize().Height; + middle.LowerRightCorner.Y += + texture->getOriginalSize().Height; draw2DImage9Slice(driver, texture, - ScaleImage ? AbsoluteRect : core::rect(pos, sourceRect.getSize()), + ScaleImage ? AbsoluteRect + : core::rect(pos, + sourceRect.getSize()), middle, &AbsoluteClippingRect); } // END PATCH } - if (SpriteBank) - { + if (SpriteBank) { core::position2di pos(buttonCenter); - if (isEnabled()) - { + if (isEnabled()) { // pressed / unpressed animation - EGUI_BUTTON_STATE state = Pressed ? EGBS_BUTTON_DOWN : EGBS_BUTTON_UP; + EGUI_BUTTON_STATE state = + Pressed ? EGBS_BUTTON_DOWN : EGBS_BUTTON_UP; drawSprite(state, ClickTime, pos); // focused / unfocused animation - state = Environment->hasFocus(this) ? EGBS_BUTTON_FOCUSED : EGBS_BUTTON_NOT_FOCUSED; + state = Environment->hasFocus(this) ? EGBS_BUTTON_FOCUSED + : EGBS_BUTTON_NOT_FOCUSED; drawSprite(state, FocusTime, pos); // mouse over / off animation - state = isHovered() ? EGBS_BUTTON_MOUSE_OVER : EGBS_BUTTON_MOUSE_OFF; + state = isHovered() ? EGBS_BUTTON_MOUSE_OVER + : EGBS_BUTTON_MOUSE_OFF; drawSprite(state, HoverTime, pos); - } - else - { + } else { // draw disabled -// drawSprite(EGBS_BUTTON_DISABLED, 0, pos); + // drawSprite(EGBS_BUTTON_DISABLED, 0, pos); } } IGUIElement::draw(); } -void GUIButton::drawSprite(EGUI_BUTTON_STATE state, u32 startTime, const core::position2di& center) +void GUIButton::drawSprite( + EGUI_BUTTON_STATE state, u32 startTime, const core::position2di ¢er) { u32 stateIdx = (u32)state; - if (ButtonSprites[stateIdx].Index != -1) - { - if ( ButtonSprites[stateIdx].Scale ) - { - const video::SColor colors[] = {ButtonSprites[stateIdx].Color,ButtonSprites[stateIdx].Color,ButtonSprites[stateIdx].Color,ButtonSprites[stateIdx].Color}; - SpriteBank->draw2DSprite(ButtonSprites[stateIdx].Index, AbsoluteRect.UpperLeftCorner, - &AbsoluteClippingRect, colors[0], // FIXME: remove [0] - porting::getTimeMs()-startTime, ButtonSprites[stateIdx].Loop); - } - else - { + if (ButtonSprites[stateIdx].Index != -1) { + if (ButtonSprites[stateIdx].Scale) { + const video::SColor colors[] = {ButtonSprites[stateIdx].Color, + ButtonSprites[stateIdx].Color, + ButtonSprites[stateIdx].Color, + ButtonSprites[stateIdx].Color}; + SpriteBank->draw2DSprite(ButtonSprites[stateIdx].Index, + AbsoluteRect.UpperLeftCorner, + &AbsoluteClippingRect, + colors[0], // FIXME: remove [0] + porting::getTimeMs() - startTime, + ButtonSprites[stateIdx].Loop); + } else { SpriteBank->draw2DSprite(ButtonSprites[stateIdx].Index, center, - &AbsoluteClippingRect, ButtonSprites[stateIdx].Color, startTime, porting::getTimeMs(), - ButtonSprites[stateIdx].Loop, true); + &AbsoluteClippingRect, + ButtonSprites[stateIdx].Color, startTime, + porting::getTimeMs(), + ButtonSprites[stateIdx].Loop, true); } } } @@ -389,32 +383,30 @@ EGUI_BUTTON_IMAGE_STATE GUIButton::getImageState(bool pressed) const // END PATCH } -EGUI_BUTTON_IMAGE_STATE GUIButton::getImageState(bool pressed, const ButtonImage* images) const +EGUI_BUTTON_IMAGE_STATE GUIButton::getImageState( + bool pressed, const ButtonImage *images) const { // figure state we should have EGUI_BUTTON_IMAGE_STATE state = EGBIS_IMAGE_DISABLED; - bool focused = Environment->hasFocus((IGUIElement*)this); + bool focused = Environment->hasFocus((IGUIElement *)this); bool mouseOver = isHovered(); - if (isEnabled()) - { - if ( pressed ) - { - if ( focused && mouseOver ) + if (isEnabled()) { + if (pressed) { + if (focused && mouseOver) state = EGBIS_IMAGE_DOWN_FOCUSED_MOUSEOVER; - else if ( focused ) + else if (focused) state = EGBIS_IMAGE_DOWN_FOCUSED; - else if ( mouseOver ) + else if (mouseOver) state = EGBIS_IMAGE_DOWN_MOUSEOVER; else state = EGBIS_IMAGE_DOWN; - } - else // !pressed + } else // !pressed { - if ( focused && mouseOver ) + if (focused && mouseOver) state = EGBIS_IMAGE_UP_FOCUSED_MOUSEOVER; - else if ( focused ) + else if (focused) state = EGBIS_IMAGE_UP_FOCUSED; - else if ( mouseOver ) + else if (mouseOver) state = EGBIS_IMAGE_UP_MOUSEOVER; else state = EGBIS_IMAGE_UP; @@ -422,34 +414,32 @@ EGUI_BUTTON_IMAGE_STATE GUIButton::getImageState(bool pressed, const ButtonImage } // find a compatible state that has images - while ( state != EGBIS_IMAGE_UP && !images[(u32)state].Texture ) - { + while (state != EGBIS_IMAGE_UP && !images[(u32)state].Texture) { // PATCH - switch ( state ) - { - case EGBIS_IMAGE_UP_FOCUSED: - state = EGBIS_IMAGE_UP; - break; - case EGBIS_IMAGE_UP_FOCUSED_MOUSEOVER: - state = EGBIS_IMAGE_UP_FOCUSED; - break; - case EGBIS_IMAGE_DOWN_MOUSEOVER: + switch (state) { + case EGBIS_IMAGE_UP_FOCUSED: + state = EGBIS_IMAGE_UP; + break; + case EGBIS_IMAGE_UP_FOCUSED_MOUSEOVER: + state = EGBIS_IMAGE_UP_FOCUSED; + break; + case EGBIS_IMAGE_DOWN_MOUSEOVER: + state = EGBIS_IMAGE_DOWN; + break; + case EGBIS_IMAGE_DOWN_FOCUSED: + state = EGBIS_IMAGE_DOWN; + break; + case EGBIS_IMAGE_DOWN_FOCUSED_MOUSEOVER: + state = EGBIS_IMAGE_DOWN_FOCUSED; + break; + case EGBIS_IMAGE_DISABLED: + if (pressed) state = EGBIS_IMAGE_DOWN; - break; - case EGBIS_IMAGE_DOWN_FOCUSED: - state = EGBIS_IMAGE_DOWN; - break; - case EGBIS_IMAGE_DOWN_FOCUSED_MOUSEOVER: - state = EGBIS_IMAGE_DOWN_FOCUSED; - break; - case EGBIS_IMAGE_DISABLED: - if ( pressed ) - state = EGBIS_IMAGE_DOWN; - else - state = EGBIS_IMAGE_UP; - break; - default: + else state = EGBIS_IMAGE_UP; + break; + default: + state = EGBIS_IMAGE_UP; } // END PATCH } @@ -457,8 +447,9 @@ EGUI_BUTTON_IMAGE_STATE GUIButton::getImageState(bool pressed, const ButtonImage return state; } -//! sets another skin independent font. if this is set to zero, the button uses the font of the skin. -void GUIButton::setOverrideFont(IGUIFont* font) +//! sets another skin independent font. if this is set to zero, the button uses the font +//! of the skin. +void GUIButton::setOverrideFont(IGUIFont *font) { if (OverrideFont == font) return; @@ -475,17 +466,17 @@ void GUIButton::setOverrideFont(IGUIFont* font) } //! Gets the override font (if any) -IGUIFont * GUIButton::getOverrideFont() const +IGUIFont *GUIButton::getOverrideFont() const { return OverrideFont; } //! Get the font which is used right now for drawing -IGUIFont* GUIButton::getActiveFont() const +IGUIFont *GUIButton::getActiveFont() const { - if ( OverrideFont ) + if (OverrideFont) return OverrideFont; - IGUISkin* skin = Environment->getSkin(); + IGUISkin *skin = Environment->getSkin(); if (skin) return skin->getFont(EGDF_BUTTON); return 0; @@ -515,16 +506,17 @@ bool GUIButton::isOverrideColorEnabled() const return OverrideColorEnabled; } -void GUIButton::setImage(EGUI_BUTTON_IMAGE_STATE state, video::ITexture* image, const core::rect& sourceRect) +void GUIButton::setImage(EGUI_BUTTON_IMAGE_STATE state, video::ITexture *image, + const core::rect &sourceRect) { - if ( state >= EGBIS_COUNT ) + if (state >= EGBIS_COUNT) return; - if ( image ) + if (image) image->grab(); u32 stateIdx = (u32)state; - if ( ButtonImages[stateIdx].Texture ) + if (ButtonImages[stateIdx].Texture) ButtonImages[stateIdx].Texture->drop(); ButtonImages[stateIdx].Texture = image; @@ -532,28 +524,28 @@ void GUIButton::setImage(EGUI_BUTTON_IMAGE_STATE state, video::ITexture* image, } // PATCH -void GUIButton::setImage(video::ITexture* image) +void GUIButton::setImage(video::ITexture *image) { setImage(gui::EGBIS_IMAGE_UP, image); } -void GUIButton::setImage(video::ITexture* image, const core::rect& pos) +void GUIButton::setImage(video::ITexture *image, const core::rect &pos) { setImage(gui::EGBIS_IMAGE_UP, image, pos); } -void GUIButton::setPressedImage(video::ITexture* image) +void GUIButton::setPressedImage(video::ITexture *image) { setImage(gui::EGBIS_IMAGE_DOWN, image); } -void GUIButton::setPressedImage(video::ITexture* image, const core::rect& pos) +void GUIButton::setPressedImage(video::ITexture *image, const core::rect &pos) { setImage(gui::EGBIS_IMAGE_DOWN, image, pos); } //! Sets the text displayed by the button -void GUIButton::setText(const wchar_t* text) +void GUIButton::setText(const wchar_t *text) { StaticText->setText(text); @@ -569,7 +561,6 @@ void GUIButton::setIsPushButton(bool isPushButton) IsPushButton = isPushButton; } - //! Returns if the button is currently pressed bool GUIButton::isPressed() const { @@ -581,118 +572,110 @@ bool GUIButton::isPressed() const bool GUIButton::isHovered() const { IGUIElement *hovered = Environment->getHovered(); - return hovered == this || (hovered != nullptr && hovered->getParent() == this); + return hovered == this || (hovered != nullptr && hovered->getParent() == this); } // END PATCH //! Sets the pressed state of the button if this is a pushbutton void GUIButton::setPressed(bool pressed) { - if (Pressed != pressed) - { + if (Pressed != pressed) { ClickTime = porting::getTimeMs(); Pressed = pressed; setFromState(); } } - //! Returns whether the button is a push button bool GUIButton::isPushButton() const { return IsPushButton; } - -//! Sets if the alpha channel should be used for drawing images on the button (default is false) +//! Sets if the alpha channel should be used for drawing images on the button (default is +//! false) void GUIButton::setUseAlphaChannel(bool useAlphaChannel) { UseAlphaChannel = useAlphaChannel; } - //! Returns if the alpha channel should be used for drawing images on the button bool GUIButton::isAlphaChannelUsed() const { return UseAlphaChannel; } - bool GUIButton::isDrawingBorder() const { return DrawBorder; } - //! Writes attributes of the element. -void GUIButton::serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options=0) const +void GUIButton::serializeAttributes( + io::IAttributes *out, io::SAttributeReadWriteOptions *options = 0) const { - IGUIButton::serializeAttributes(out,options); + IGUIButton::serializeAttributes(out, options); - out->addBool ("PushButton", IsPushButton ); + out->addBool("PushButton", IsPushButton); if (IsPushButton) - out->addBool("Pressed", Pressed); + out->addBool("Pressed", Pressed); - for ( u32 i=0; i<(u32)EGBIS_COUNT; ++i ) - { - if ( ButtonImages[i].Texture ) - { - core::stringc name( GUIButtonImageStateNames[i] ); + for (u32 i = 0; i < (u32)EGBIS_COUNT; ++i) { + if (ButtonImages[i].Texture) { + core::stringc name(GUIButtonImageStateNames[i]); out->addTexture(name.c_str(), ButtonImages[i].Texture); name += "Rect"; out->addRect(name.c_str(), ButtonImages[i].SourceRect); } } - out->addBool ("UseAlphaChannel", UseAlphaChannel); - out->addBool ("Border", DrawBorder); - out->addBool ("ScaleImage", ScaleImage); + out->addBool("UseAlphaChannel", UseAlphaChannel); + out->addBool("Border", DrawBorder); + out->addBool("ScaleImage", ScaleImage); - for ( u32 i=0; i<(u32)EGBS_COUNT; ++i ) - { - if ( ButtonSprites[i].Index >= 0 ) - { - core::stringc nameIndex( GUIButtonStateNames[i] ); + for (u32 i = 0; i < (u32)EGBS_COUNT; ++i) { + if (ButtonSprites[i].Index >= 0) { + core::stringc nameIndex(GUIButtonStateNames[i]); nameIndex += "Index"; - out->addInt(nameIndex.c_str(), ButtonSprites[i].Index ); + out->addInt(nameIndex.c_str(), ButtonSprites[i].Index); - core::stringc nameColor( GUIButtonStateNames[i] ); + core::stringc nameColor(GUIButtonStateNames[i]); nameColor += "Color"; - out->addColor(nameColor.c_str(), ButtonSprites[i].Color ); + out->addColor(nameColor.c_str(), ButtonSprites[i].Color); - core::stringc nameLoop( GUIButtonStateNames[i] ); + core::stringc nameLoop(GUIButtonStateNames[i]); nameLoop += "Loop"; - out->addBool(nameLoop.c_str(), ButtonSprites[i].Loop ); + out->addBool(nameLoop.c_str(), ButtonSprites[i].Loop); - core::stringc nameScale( GUIButtonStateNames[i] ); + core::stringc nameScale(GUIButtonStateNames[i]); nameScale += "Scale"; - out->addBool(nameScale.c_str(), ButtonSprites[i].Scale ); + out->addBool(nameScale.c_str(), ButtonSprites[i].Scale); } } // out->addString ("OverrideFont", OverrideFont); } - //! Reads attributes of the element -void GUIButton::deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options=0) +void GUIButton::deserializeAttributes( + io::IAttributes *in, io::SAttributeReadWriteOptions *options = 0) { - IGUIButton::deserializeAttributes(in,options); + IGUIButton::deserializeAttributes(in, options); - IsPushButton = in->getAttributeAsBool("PushButton"); - Pressed = IsPushButton ? in->getAttributeAsBool("Pressed") : false; + IsPushButton = in->getAttributeAsBool("PushButton"); + Pressed = IsPushButton ? in->getAttributeAsBool("Pressed") : false; core::rect rec = in->getAttributeAsRect("ImageRect"); if (rec.isValid()) - setImage( in->getAttributeAsTexture("Image"), rec); + setImage(in->getAttributeAsTexture("Image"), rec); else - setImage( in->getAttributeAsTexture("Image") ); + setImage(in->getAttributeAsTexture("Image")); rec = in->getAttributeAsRect("PressedImageRect"); if (rec.isValid()) - setPressedImage( in->getAttributeAsTexture("PressedImage"), rec); + setPressedImage(in->getAttributeAsTexture("PressedImage"), rec); else - setPressedImage( in->getAttributeAsTexture("PressedImage") ); + setPressedImage(in->getAttributeAsTexture("PressedImage")); setDrawBorder(in->getAttributeAsBool("Border")); setUseAlphaChannel(in->getAttributeAsBool("UseAlphaChannel")); @@ -704,17 +687,19 @@ void GUIButton::deserializeAttributes(io::IAttributes* in, io::SAttributeReadWri } // PATCH -GUIButton* GUIButton::addButton(IGUIEnvironment *environment, - const core::rect& rectangle, ISimpleTextureSource *tsrc, - IGUIElement* parent, s32 id, const wchar_t* text, +GUIButton *GUIButton::addButton(IGUIEnvironment *environment, + const core::rect &rectangle, ISimpleTextureSource *tsrc, + IGUIElement *parent, s32 id, const wchar_t *text, const wchar_t *tooltiptext) { - GUIButton* button = new GUIButton(environment, parent ? parent : environment->getRootGUIElement(), id, rectangle, tsrc); + GUIButton *button = new GUIButton(environment, + parent ? parent : environment->getRootGUIElement(), id, rectangle, + tsrc); if (text) button->setText(text); - if ( tooltiptext ) - button->setToolTipText ( tooltiptext ); + if (tooltiptext) + button->setToolTipText(tooltiptext); button->drop(); return button; @@ -724,7 +709,8 @@ void GUIButton::setColor(video::SColor color) { float d = 0.65f; for (size_t i = 0; i < 4; i++) { - video::SColor base = Environment->getSkin()->getColor((gui::EGUI_DEFAULT_COLOR)i); + video::SColor base = Environment->getSkin()->getColor( + (gui::EGUI_DEFAULT_COLOR)i); Colors[i] = base.getInterpolated(color, d); } } @@ -744,7 +730,7 @@ void GUIButton::setFromState() } //! Set element properties from a StyleSpec -void GUIButton::setFromStyle(const StyleSpec& style) +void GUIButton::setFromStyle(const StyleSpec &style) { bool hovered = (style.getState() & StyleSpec::STATE_HOVERED) != 0; bool pressed = (style.getState() & StyleSpec::STATE_PRESSED) != 0; @@ -758,17 +744,19 @@ void GUIButton::setFromStyle(const StyleSpec& style) if (!Styles[style.getState()].isNotDefault(StyleSpec::BGCOLOR)) { for (size_t i = 0; i < 4; i++) { if (pressed) { - Colors[i] = multiplyColorValue(Colors[i], COLOR_PRESSED_MOD); + Colors[i] = multiplyColorValue( + Colors[i], COLOR_PRESSED_MOD); } else if (hovered) { - Colors[i] = multiplyColorValue(Colors[i], COLOR_HOVERED_MOD); + Colors[i] = multiplyColorValue( + Colors[i], COLOR_HOVERED_MOD); } } } } else { for (size_t i = 0; i < 4; i++) { - video::SColor base = - Environment->getSkin()->getColor((gui::EGUI_DEFAULT_COLOR)i); + video::SColor base = Environment->getSkin()->getColor( + (gui::EGUI_DEFAULT_COLOR)i); if (pressed) { Colors[i] = multiplyColorValue(base, COLOR_PRESSED_MOD); } else if (hovered) { @@ -782,7 +770,7 @@ void GUIButton::setFromStyle(const StyleSpec& style) if (style.isNotDefault(StyleSpec::TEXTCOLOR)) { setOverrideColor(style.getColor(StyleSpec::TEXTCOLOR)); } else { - setOverrideColor(video::SColor(255,255,255,255)); + setOverrideColor(video::SColor(255, 255, 255, 255)); OverrideColorEnabled = false; } setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); @@ -791,11 +779,10 @@ void GUIButton::setFromStyle(const StyleSpec& style) setOverrideFont(style.getFont()); if (style.isNotDefault(StyleSpec::BGIMG)) { - video::ITexture *texture = style.getTexture(StyleSpec::BGIMG, - getTextureSource()); - setImage(guiScalingImageButton( - Environment->getVideoDriver(), texture, - AbsoluteRect.getWidth(), AbsoluteRect.getHeight())); + video::ITexture *texture = + style.getTexture(StyleSpec::BGIMG, getTextureSource()); + setImage(guiScalingImageButton(Environment->getVideoDriver(), texture, + AbsoluteRect.getWidth(), AbsoluteRect.getHeight())); setScaleImage(true); } else { setImage(nullptr); @@ -805,23 +792,22 @@ void GUIButton::setFromStyle(const StyleSpec& style) // Child padding and offset Padding = style.getRect(StyleSpec::PADDING, core::rect()); - Padding = core::rect( - Padding.UpperLeftCorner + BgMiddle.UpperLeftCorner, + Padding = core::rect(Padding.UpperLeftCorner + BgMiddle.UpperLeftCorner, Padding.LowerRightCorner + BgMiddle.LowerRightCorner); - GUISkin* skin = dynamic_cast(Environment->getSkin()); + GUISkin *skin = dynamic_cast(Environment->getSkin()); core::vector2d defaultPressOffset( skin->getSize(irr::gui::EGDS_BUTTON_PRESSED_IMAGE_OFFSET_X), skin->getSize(irr::gui::EGDS_BUTTON_PRESSED_IMAGE_OFFSET_Y)); - ContentOffset = style.getVector2i(StyleSpec::CONTENT_OFFSET, isPressed() - ? defaultPressOffset - : core::vector2d(0)); + ContentOffset = style.getVector2i(StyleSpec::CONTENT_OFFSET, + isPressed() ? defaultPressOffset : core::vector2d(0)); - core::rect childBounds( - Padding.UpperLeftCorner.X + ContentOffset.X, - Padding.UpperLeftCorner.Y + ContentOffset.Y, - AbsoluteRect.getWidth() + Padding.LowerRightCorner.X + ContentOffset.X, - AbsoluteRect.getHeight() + Padding.LowerRightCorner.Y + ContentOffset.Y); + core::rect childBounds(Padding.UpperLeftCorner.X + ContentOffset.X, + Padding.UpperLeftCorner.Y + ContentOffset.Y, + AbsoluteRect.getWidth() + Padding.LowerRightCorner.X + + ContentOffset.X, + AbsoluteRect.getHeight() + Padding.LowerRightCorner.Y + + ContentOffset.Y); for (IGUIElement *child : getChildren()) { child->setRelativePosition(childBounds); @@ -829,7 +815,7 @@ void GUIButton::setFromStyle(const StyleSpec& style) } //! Set the styles used for each state -void GUIButton::setStyles(const std::array& styles) +void GUIButton::setStyles(const std::array &styles) { Styles = styles; setFromState(); diff --git a/src/gui/guiButton.h b/src/gui/guiButton.h index 95fa1a2a1..cd0b4ec5f 100644 --- a/src/gui/guiButton.h +++ b/src/gui/guiButton.h @@ -18,52 +18,55 @@ using namespace irr; #if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR <= 8) - namespace irr { namespace gui { +namespace irr +{ +namespace gui +{ - //! State of buttons used for drawing texture images. - //! Note that only a single state is active at a time - //! Also when no image is defined for a state it will use images from another state - //! and if that state is not set from the replacement for that,etc. - //! So in many cases setting EGBIS_IMAGE_UP and EGBIS_IMAGE_DOWN is sufficient. - enum EGUI_BUTTON_IMAGE_STATE { - //! When no other states have images they will all use this one. - EGBIS_IMAGE_UP, - //! When not set EGBIS_IMAGE_UP is used. - EGBIS_IMAGE_UP_MOUSEOVER, - //! When not set EGBIS_IMAGE_UP_MOUSEOVER is used. - EGBIS_IMAGE_UP_FOCUSED, - //! When not set EGBIS_IMAGE_UP_FOCUSED is used. - EGBIS_IMAGE_UP_FOCUSED_MOUSEOVER, - //! When not set EGBIS_IMAGE_UP is used. - EGBIS_IMAGE_DOWN, - //! When not set EGBIS_IMAGE_DOWN is used. - EGBIS_IMAGE_DOWN_MOUSEOVER, - //! When not set EGBIS_IMAGE_DOWN_MOUSEOVER is used. - EGBIS_IMAGE_DOWN_FOCUSED, - //! When not set EGBIS_IMAGE_DOWN_FOCUSED is used. - EGBIS_IMAGE_DOWN_FOCUSED_MOUSEOVER, - //! When not set EGBIS_IMAGE_UP or EGBIS_IMAGE_DOWN are used (depending on button state). - EGBIS_IMAGE_DISABLED, - //! not used, counts the number of enumerated items - EGBIS_COUNT - }; +//! State of buttons used for drawing texture images. +//! Note that only a single state is active at a time +//! Also when no image is defined for a state it will use images from another state +//! and if that state is not set from the replacement for that,etc. +//! So in many cases setting EGBIS_IMAGE_UP and EGBIS_IMAGE_DOWN is sufficient. +enum EGUI_BUTTON_IMAGE_STATE +{ + //! When no other states have images they will all use this one. + EGBIS_IMAGE_UP, + //! When not set EGBIS_IMAGE_UP is used. + EGBIS_IMAGE_UP_MOUSEOVER, + //! When not set EGBIS_IMAGE_UP_MOUSEOVER is used. + EGBIS_IMAGE_UP_FOCUSED, + //! When not set EGBIS_IMAGE_UP_FOCUSED is used. + EGBIS_IMAGE_UP_FOCUSED_MOUSEOVER, + //! When not set EGBIS_IMAGE_UP is used. + EGBIS_IMAGE_DOWN, + //! When not set EGBIS_IMAGE_DOWN is used. + EGBIS_IMAGE_DOWN_MOUSEOVER, + //! When not set EGBIS_IMAGE_DOWN_MOUSEOVER is used. + EGBIS_IMAGE_DOWN_FOCUSED, + //! When not set EGBIS_IMAGE_DOWN_FOCUSED is used. + EGBIS_IMAGE_DOWN_FOCUSED_MOUSEOVER, + //! When not set EGBIS_IMAGE_UP or EGBIS_IMAGE_DOWN are used (depending on button + //! state). + EGBIS_IMAGE_DISABLED, + //! not used, counts the number of enumerated items + EGBIS_COUNT +}; - //! Names for gui button image states - const c8 *const GUIButtonImageStateNames[EGBIS_COUNT + 1] = - { - "Image", // not "ImageUp" as it otherwise breaks serialization of old files - "ImageUpOver", - "ImageUpFocused", - "ImageUpFocusedOver", - "PressedImage", // not "ImageDown" as it otherwise breaks serialization of old files - "ImageDownOver", - "ImageDownFocused", - "ImageDownFocusedOver", - "ImageDisabled", - 0 // count - }; +//! Names for gui button image states +const c8 *const GUIButtonImageStateNames[EGBIS_COUNT + 1] = { + "Image", // not "ImageUp" as it otherwise breaks serialization of old + // files + "ImageUpOver", "ImageUpFocused", "ImageUpFocusedOver", + "PressedImage", // not "ImageDown" as it otherwise breaks serialization of + // old files + "ImageDownOver", "ImageDownFocused", "ImageDownFocusedOver", + "ImageDisabled", + 0 // count +}; - }} +} +} #endif @@ -72,29 +75,29 @@ class ISimpleTextureSource; class GUIButton : public gui::IGUIButton { public: - //! constructor - GUIButton(gui::IGUIEnvironment* environment, gui::IGUIElement* parent, - s32 id, core::rect rectangle, ISimpleTextureSource *tsrc, - bool noclip=false); + GUIButton(gui::IGUIEnvironment *environment, gui::IGUIElement *parent, s32 id, + core::rect rectangle, ISimpleTextureSource *tsrc, + bool noclip = false); //! destructor virtual ~GUIButton(); //! called if an event happened. - virtual bool OnEvent(const SEvent& event) override; + virtual bool OnEvent(const SEvent &event) override; //! draws the element and its children virtual void draw() override; - //! sets another skin independent font. if this is set to zero, the button uses the font of the skin. - virtual void setOverrideFont(gui::IGUIFont* font=0) override; + //! sets another skin independent font. if this is set to zero, the button uses + //! the font of the skin. + virtual void setOverrideFont(gui::IGUIFont *font = 0) override; //! Gets the override font (if any) - virtual gui::IGUIFont* getOverrideFont() const override; + virtual gui::IGUIFont *getOverrideFont() const override; //! Get the font which is used right now for drawing - virtual gui::IGUIFont* getActiveFont() const override; + virtual gui::IGUIFont *getActiveFont() const override; //! Sets another color for the button text. virtual void setOverrideColor(video::SColor color); @@ -102,49 +105,58 @@ public: //! Gets the override color virtual video::SColor getOverrideColor(void) const; - //! Sets if the button text should use the override color or the color in the gui skin. + //! Sets if the button text should use the override color or the color in the gui + //! skin. virtual void enableOverrideColor(bool enable); //! Checks if an override color is enabled virtual bool isOverrideColorEnabled(void) const; // PATCH - //! Sets an image which should be displayed on the button when it is in the given state. + //! Sets an image which should be displayed on the button when it is in the given + //! state. virtual void setImage(gui::EGUI_BUTTON_IMAGE_STATE state, - video::ITexture* image=nullptr, - const core::rect& sourceRect=core::rect(0,0,0,0)); + video::ITexture *image = nullptr, + const core::rect &sourceRect = core::rect(0, 0, 0, 0)); - //! Sets an image which should be displayed on the button when it is in normal state. - virtual void setImage(video::ITexture* image=nullptr) override; + //! Sets an image which should be displayed on the button when it is in normal + //! state. + virtual void setImage(video::ITexture *image = nullptr) override; - //! Sets an image which should be displayed on the button when it is in normal state. - virtual void setImage(video::ITexture* image, const core::rect& pos) override; + //! Sets an image which should be displayed on the button when it is in normal + //! state. + virtual void setImage( + video::ITexture *image, const core::rect &pos) override; - //! Sets an image which should be displayed on the button when it is in pressed state. - virtual void setPressedImage(video::ITexture* image=nullptr) override; + //! Sets an image which should be displayed on the button when it is in pressed + //! state. + virtual void setPressedImage(video::ITexture *image = nullptr) override; - //! Sets an image which should be displayed on the button when it is in pressed state. - virtual void setPressedImage(video::ITexture* image, const core::rect& pos) override; + //! Sets an image which should be displayed on the button when it is in pressed + //! state. + virtual void setPressedImage( + video::ITexture *image, const core::rect &pos) override; //! Sets the text displayed by the button - virtual void setText(const wchar_t* text) override; + virtual void setText(const wchar_t *text) override; // END PATCH //! Sets the sprite bank used by the button - virtual void setSpriteBank(gui::IGUISpriteBank* bank=0) override; + virtual void setSpriteBank(gui::IGUISpriteBank *bank = 0) override; //! Sets the animated sprite for a specific button state - /** \param index: Number of the sprite within the sprite bank, use -1 for no sprite - \param state: State of the button to set the sprite for - \param index: The sprite number from the current sprite bank - \param color: The color of the sprite + /** \param index: Number of the sprite within the sprite bank, use -1 for no + sprite \param state: State of the button to set the sprite for \param index: The + sprite number from the current sprite bank \param color: The color of the sprite */ virtual void setSprite(gui::EGUI_BUTTON_STATE state, s32 index, - video::SColor color=video::SColor(255,255,255,255), - bool loop=false, bool scale=false); + video::SColor color = video::SColor(255, 255, 255, 255), + bool loop = false, bool scale = false); #if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR <= 8) - void setSprite(gui::EGUI_BUTTON_STATE state, s32 index, video::SColor color, bool loop) override { + void setSprite(gui::EGUI_BUTTON_STATE state, s32 index, video::SColor color, + bool loop) override + { setSprite(state, index, color, loop, false); } #endif @@ -152,7 +164,8 @@ public: //! Get the sprite-index for the given state or -1 when no sprite is set virtual s32 getSpriteIndex(gui::EGUI_BUTTON_STATE state) const; - //! Get the sprite color for the given state. Color is only used when a sprite is set. + //! Get the sprite color for the given state. Color is only used when a sprite is + //! set. virtual video::SColor getSpriteColor(gui::EGUI_BUTTON_STATE state) const; //! Returns if the sprite in the given state does loop @@ -164,13 +177,13 @@ public: //! Sets if the button should behave like a push button. Which means it //! can be in two states: Normal or Pressed. With a click on the button, //! the user can change the state of the button. - virtual void setIsPushButton(bool isPushButton=true) override; + virtual void setIsPushButton(bool isPushButton = true) override; //! Checks whether the button is a push button virtual bool isPushButton() const override; //! Sets the pressed state of the button if this is a pushbutton - virtual void setPressed(bool pressed=true) override; + virtual void setPressed(bool pressed = true) override; //! Returns if the button is currently pressed virtual bool isPressed() const override; @@ -181,42 +194,37 @@ public: // END PATCH //! Sets if the button should use the skin to draw its border - virtual void setDrawBorder(bool border=true) override; + virtual void setDrawBorder(bool border = true) override; //! Checks if the button face and border are being drawn virtual bool isDrawingBorder() const override; - //! Sets if the alpha channel should be used for drawing images on the button (default is false) - virtual void setUseAlphaChannel(bool useAlphaChannel=true) override; + //! Sets if the alpha channel should be used for drawing images on the button + //! (default is false) + virtual void setUseAlphaChannel(bool useAlphaChannel = true) override; //! Checks if the alpha channel should be used for drawing images on the button virtual bool isAlphaChannelUsed() const override; //! Sets if the button should scale the button images to fit - virtual void setScaleImage(bool scaleImage=true) override; + virtual void setScaleImage(bool scaleImage = true) override; //! Checks whether the button scales the used images virtual bool isScalingImage() const override; //! Get if the shift key was pressed in last EGET_BUTTON_CLICKED event - virtual bool getClickShiftState() const - { - return ClickShiftState; - } + virtual bool getClickShiftState() const { return ClickShiftState; } //! Get if the control key was pressed in last EGET_BUTTON_CLICKED event - virtual bool getClickControlState() const - { - return ClickControlState; - } + virtual bool getClickControlState() const { return ClickControlState; } //! Writes attributes of the element. - virtual void serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options) const override; + virtual void serializeAttributes(io::IAttributes *out, + io::SAttributeReadWriteOptions *options) const override; //! Reads attributes of the element - virtual void deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options) override; - - + virtual void deserializeAttributes(io::IAttributes *in, + io::SAttributeReadWriteOptions *options) override; void setColor(video::SColor color); // PATCH @@ -224,79 +232,76 @@ public: void setFromState(); //! Set element properties from a StyleSpec - virtual void setFromStyle(const StyleSpec& style); + virtual void setFromStyle(const StyleSpec &style); //! Set the styles used for each state - void setStyles(const std::array& styles); + void setStyles(const std::array &styles); // END PATCH - //! Do not drop returned handle - static GUIButton* addButton(gui::IGUIEnvironment *environment, - const core::rect& rectangle, ISimpleTextureSource *tsrc, - IGUIElement* parent, s32 id, const wchar_t* text, - const wchar_t *tooltiptext=L""); + static GUIButton *addButton(gui::IGUIEnvironment *environment, + const core::rect &rectangle, ISimpleTextureSource *tsrc, + IGUIElement *parent, s32 id, const wchar_t *text, + const wchar_t *tooltiptext = L""); protected: - void drawSprite(gui::EGUI_BUTTON_STATE state, u32 startTime, const core::position2di& center); + void drawSprite(gui::EGUI_BUTTON_STATE state, u32 startTime, + const core::position2di ¢er); gui::EGUI_BUTTON_IMAGE_STATE getImageState(bool pressed) const; ISimpleTextureSource *getTextureSource() { return TSrc; } struct ButtonImage { - ButtonImage() : Texture(0), SourceRect(core::rect(0,0,0,0)) - { - } + ButtonImage() : Texture(0), SourceRect(core::rect(0, 0, 0, 0)) {} - ButtonImage(const ButtonImage& other) : Texture(0), SourceRect(core::rect(0,0,0,0)) + ButtonImage(const ButtonImage &other) : + Texture(0), SourceRect(core::rect(0, 0, 0, 0)) { *this = other; } ~ButtonImage() { - if ( Texture ) + if (Texture) Texture->drop(); } - ButtonImage& operator=(const ButtonImage& other) + ButtonImage &operator=(const ButtonImage &other) { - if ( this == &other ) + if (this == &other) return *this; if (other.Texture) other.Texture->grab(); - if ( Texture ) + if (Texture) Texture->drop(); Texture = other.Texture; SourceRect = other.SourceRect; return *this; } - bool operator==(const ButtonImage& other) const + bool operator==(const ButtonImage &other) const { return Texture == other.Texture && SourceRect == other.SourceRect; } - - video::ITexture* Texture; + video::ITexture *Texture; core::rect SourceRect; }; - gui::EGUI_BUTTON_IMAGE_STATE getImageState(bool pressed, const ButtonImage* images) const; + gui::EGUI_BUTTON_IMAGE_STATE getImageState( + bool pressed, const ButtonImage *images) const; private: - struct ButtonSprite { - ButtonSprite() : Index(-1), Loop(false), Scale(false) - { - } + ButtonSprite() : Index(-1), Loop(false), Scale(false) {} - bool operator==(const ButtonSprite& other) const + bool operator==(const ButtonSprite &other) const { - return Index == other.Index && Color == other.Color && Loop == other.Loop && Scale == other.Scale; + return Index == other.Index && Color == other.Color && + Loop == other.Loop && Scale == other.Scale; } s32 Index; @@ -306,13 +311,13 @@ private: }; ButtonSprite ButtonSprites[gui::EGBS_COUNT]; - gui::IGUISpriteBank* SpriteBank; + gui::IGUISpriteBank *SpriteBank; ButtonImage ButtonImages[gui::EGBIS_COUNT]; std::array Styles; - gui::IGUIFont* OverrideFont; + gui::IGUIFont *OverrideFont; bool OverrideColorEnabled; video::SColor OverrideColor; diff --git a/src/gui/guiButtonImage.cpp b/src/gui/guiButtonImage.cpp index b507ffece..2f574c1a8 100644 --- a/src/gui/guiButtonImage.cpp +++ b/src/gui/guiButtonImage.cpp @@ -31,11 +31,12 @@ using namespace gui; GUIButtonImage::GUIButtonImage(gui::IGUIEnvironment *environment, gui::IGUIElement *parent, s32 id, core::rect rectangle, - ISimpleTextureSource *tsrc, bool noclip) - : GUIButton (environment, parent, id, rectangle, tsrc, noclip) + ISimpleTextureSource *tsrc, bool noclip) : + GUIButton(environment, parent, id, rectangle, tsrc, noclip) { - m_image = Environment->addImage( - core::rect(0,0,rectangle.getWidth(),rectangle.getHeight()), this); + m_image = Environment->addImage(core::rect(0, 0, rectangle.getWidth(), + rectangle.getHeight()), + this); m_image->setScaleImage(isScalingImage()); sendToBack(m_image); } @@ -56,18 +57,18 @@ void GUIButtonImage::setForegroundImage(video::ITexture *image) } //! Set element properties from a StyleSpec -void GUIButtonImage::setFromStyle(const StyleSpec& style) +void GUIButtonImage::setFromStyle(const StyleSpec &style) { GUIButton::setFromStyle(style); video::IVideoDriver *driver = Environment->getVideoDriver(); if (style.isNotDefault(StyleSpec::FGIMG)) { - video::ITexture *texture = style.getTexture(StyleSpec::FGIMG, - getTextureSource()); + video::ITexture *texture = + style.getTexture(StyleSpec::FGIMG, getTextureSource()); setForegroundImage(guiScalingImageButton(driver, texture, - AbsoluteRect.getWidth(), AbsoluteRect.getHeight())); + AbsoluteRect.getWidth(), AbsoluteRect.getHeight())); setScaleImage(true); } else { setForegroundImage(nullptr); @@ -86,7 +87,8 @@ GUIButtonImage *GUIButtonImage::addButton(IGUIEnvironment *environment, const wchar_t *tooltiptext) { GUIButtonImage *button = new GUIButtonImage(environment, - parent ? parent : environment->getRootGUIElement(), id, rectangle, tsrc); + parent ? parent : environment->getRootGUIElement(), id, rectangle, + tsrc); if (text) button->setText(text); diff --git a/src/gui/guiButtonImage.h b/src/gui/guiButtonImage.h index 59a25b4f0..c46ef36f0 100644 --- a/src/gui/guiButtonImage.h +++ b/src/gui/guiButtonImage.h @@ -35,9 +35,9 @@ public: void setForegroundImage(video::ITexture *image = nullptr); //! Set element properties from a StyleSpec - virtual void setFromStyle(const StyleSpec& style) override; + virtual void setFromStyle(const StyleSpec &style) override; - virtual void setScaleImage(bool scaleImage=true) override; + virtual void setScaleImage(bool scaleImage = true) override; //! Do not drop returned handle static GUIButtonImage *addButton(gui::IGUIEnvironment *environment, diff --git a/src/gui/guiButtonItemImage.cpp b/src/gui/guiButtonItemImage.cpp index d8b9042ac..bbd20582b 100644 --- a/src/gui/guiButtonItemImage.cpp +++ b/src/gui/guiButtonItemImage.cpp @@ -31,11 +31,12 @@ using namespace gui; GUIButtonItemImage::GUIButtonItemImage(gui::IGUIEnvironment *environment, gui::IGUIElement *parent, s32 id, core::rect rectangle, ISimpleTextureSource *tsrc, std::string item, Client *client, - bool noclip) - : GUIButton (environment, parent, id, rectangle, tsrc, noclip) + bool noclip) : + GUIButton(environment, parent, id, rectangle, tsrc, noclip) { m_image = new GUIItemImage(environment, this, id, - core::rect(0,0,rectangle.getWidth(),rectangle.getHeight()), + core::rect(0, 0, rectangle.getWidth(), + rectangle.getHeight()), item, getActiveFont(), client); sendToBack(m_image); @@ -49,8 +50,8 @@ GUIButtonItemImage *GUIButtonItemImage::addButton(IGUIEnvironment *environment, Client *client) { GUIButtonItemImage *button = new GUIButtonItemImage(environment, - parent ? parent : environment->getRootGUIElement(), - id, rectangle, tsrc, item, client); + parent ? parent : environment->getRootGUIElement(), id, rectangle, + tsrc, item, client); if (text) button->setText(text); diff --git a/src/gui/guiButtonItemImage.h b/src/gui/guiButtonItemImage.h index aad923bda..bc93c08d2 100644 --- a/src/gui/guiButtonItemImage.h +++ b/src/gui/guiButtonItemImage.h @@ -38,8 +38,8 @@ public: //! Do not drop returned handle static GUIButtonItemImage *addButton(gui::IGUIEnvironment *environment, const core::rect &rectangle, ISimpleTextureSource *tsrc, - IGUIElement *parent, s32 id, const wchar_t *text, std::string item, - Client *client); + IGUIElement *parent, s32 id, const wchar_t *text, + std::string item, Client *client); private: std::string m_item_name; diff --git a/src/gui/guiChatConsole.cpp b/src/gui/guiChatConsole.cpp index 8de00c12f..7170fad15 100644 --- a/src/gui/guiChatConsole.cpp +++ b/src/gui/guiChatConsole.cpp @@ -32,29 +32,20 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #if USE_FREETYPE - #include "irrlicht_changes/CGUITTFont.h" +#include "irrlicht_changes/CGUITTFont.h" #endif inline u32 clamp_u8(s32 value) { - return (u32) MYMIN(MYMAX(value, 0), 255); + return (u32)MYMIN(MYMAX(value, 0), 255); } - -GUIChatConsole::GUIChatConsole( - gui::IGUIEnvironment* env, - gui::IGUIElement* parent, - s32 id, - ChatBackend* backend, - Client* client, - IMenuManager* menumgr -): - IGUIElement(gui::EGUIET_ELEMENT, env, parent, id, - core::rect(0,0,100,100)), - m_chat_backend(backend), - m_client(client), - m_menumgr(menumgr), - m_animate_time_old(porting::getTimeMs()) +GUIChatConsole::GUIChatConsole(gui::IGUIEnvironment *env, gui::IGUIElement *parent, + s32 id, ChatBackend *backend, Client *client, IMenuManager *menumgr) : + IGUIElement(gui::EGUIET_ELEMENT, env, parent, id, + core::rect(0, 0, 100, 100)), + m_chat_backend(backend), m_client(client), m_menumgr(menumgr), + m_animate_time_old(porting::getTimeMs()) { // load background settings s32 console_alpha = g_settings->getS32("console_alpha"); @@ -75,8 +66,9 @@ GUIChatConsole::GUIChatConsole( } u16 chat_font_size = g_settings->getU16("chat_font_size"); - m_font = g_fontengine->getFont(chat_font_size != 0 ? - chat_font_size : FONT_SIZE_UNSPECIFIED, FM_Mono); + m_font = g_fontengine->getFont( + chat_font_size != 0 ? chat_font_size : FONT_SIZE_UNSPECIFIED, + FM_Mono); if (!m_font) { errorstream << "GUIChatConsole: Unable to load mono font" << std::endl; @@ -143,31 +135,24 @@ f32 GUIChatConsole::getDesiredHeight() const void GUIChatConsole::replaceAndAddToHistory(const std::wstring &line) { - ChatPrompt& prompt = m_chat_backend->getPrompt(); + ChatPrompt &prompt = m_chat_backend->getPrompt(); prompt.addToHistory(prompt.getLine()); prompt.replace(line); } - void GUIChatConsole::setCursor( - bool visible, bool blinking, f32 blink_speed, f32 relative_height) + bool visible, bool blinking, f32 blink_speed, f32 relative_height) { - if (visible) - { - if (blinking) - { + if (visible) { + if (blinking) { // leave m_cursor_blink unchanged m_cursor_blink_speed = blink_speed; - } - else - { - m_cursor_blink = 0x8000; // on + } else { + m_cursor_blink = 0x8000; // on m_cursor_blink_speed = 0.0; } - } - else - { - m_cursor_blink = 0; // off + } else { + m_cursor_blink = 0; // off m_cursor_blink_speed = 0.0; } m_cursor_height = relative_height; @@ -175,15 +160,14 @@ void GUIChatConsole::setCursor( void GUIChatConsole::draw() { - if(!IsVisible) + if (!IsVisible) return; - video::IVideoDriver* driver = Environment->getVideoDriver(); + video::IVideoDriver *driver = Environment->getVideoDriver(); // Check screen size v2u32 screensize = driver->getScreenSize(); - if (screensize != m_screensize) - { + if (screensize != m_screensize) { // screen size has changed // scale current console height to new window size if (m_screensize.Y != 0) @@ -199,8 +183,7 @@ void GUIChatConsole::draw() m_animate_time_old = now; // Draw console elements if visible - if (m_height > 0) - { + if (m_height > 0) { drawBackground(); drawText(); drawPrompt(); @@ -211,7 +194,8 @@ void GUIChatConsole::draw() void GUIChatConsole::reformatConsole() { - s32 cols = m_screensize.X / m_fontsize.X - 2; // make room for a margin (looks better) + s32 cols = m_screensize.X / m_fontsize.X - + 2; // make room for a margin (looks better) s32 rows = m_desired_height / m_fontsize.Y - 1; // make room for the input prompt if (cols <= 0 || rows <= 0) cols = rows = 0; @@ -232,27 +216,24 @@ void GUIChatConsole::animate(u32 msec) s32 goal = m_open ? m_desired_height : 0; // Set invisible if close animation finished (reset by openConsole) - // This function (animate()) is never called once its visibility becomes false so do not + // This function (animate()) is never called once its visibility becomes false so + // do not // actually set visible to false before the inhibited period is over if (!m_open && m_height == 0 && m_open_inhibited == 0) IGUIElement::setVisible(false); - if (m_height != goal) - { + if (m_height != goal) { s32 max_change = msec * m_screensize.Y * (m_height_speed / 1000.0); if (max_change == 0) max_change = 1; - if (m_height < goal) - { + if (m_height < goal) { // increase height if (m_height + max_change < goal) m_height += max_change; else m_height = goal; - } - else - { + } else { // decrease height if (m_height > goal + max_change) m_height -= max_change; @@ -264,8 +245,7 @@ void GUIChatConsole::animate(u32 msec) } // blink the cursor - if (m_cursor_blink_speed != 0.0) - { + if (m_cursor_blink_speed != 0.0) { u32 blink_increase = 0x10000 * msec * (m_cursor_blink_speed / 1000.0); if (blink_increase == 0) blink_increase = 1; @@ -281,24 +261,15 @@ void GUIChatConsole::animate(u32 msec) void GUIChatConsole::drawBackground() { - video::IVideoDriver* driver = Environment->getVideoDriver(); - if (m_background != NULL) - { + video::IVideoDriver *driver = Environment->getVideoDriver(); + if (m_background != NULL) { core::rect sourcerect(0, -m_height, m_screensize.X, 0); - driver->draw2DImage( - m_background, - v2s32(0, 0), - sourcerect, - &AbsoluteClippingRect, - m_background_color, - false); - } - else - { - driver->draw2DRectangle( - m_background_color, - core::rect(0, 0, m_screensize.X, m_height), - &AbsoluteClippingRect); + driver->draw2DImage(m_background, v2s32(0, 0), sourcerect, + &AbsoluteClippingRect, m_background_color, false); + } else { + driver->draw2DRectangle(m_background_color, + core::rect(0, 0, m_screensize.X, m_height), + &AbsoluteClippingRect); } } @@ -307,10 +278,9 @@ void GUIChatConsole::drawText() if (m_font == NULL) return; - ChatBuffer& buf = m_chat_backend->getConsoleBuffer(); - for (u32 row = 0; row < buf.getRows(); ++row) - { - const ChatFormattedLine& line = buf.getFormattedLine(row); + ChatBuffer &buf = m_chat_backend->getConsoleBuffer(); + for (u32 row = 0; row < buf.getRows(); ++row) { + const ChatFormattedLine &line = buf.getFormattedLine(row); if (line.fragments.empty()) continue; @@ -321,31 +291,26 @@ void GUIChatConsole::drawText() for (const ChatFormattedFragment &fragment : line.fragments) { s32 x = (fragment.column + 1) * m_fontsize.X; - core::rect destrect( - x, y, x + m_fontsize.X * fragment.text.size(), y + m_fontsize.Y); + core::rect destrect(x, y, + x + m_fontsize.X * fragment.text.size(), + y + m_fontsize.Y); #if USE_FREETYPE if (m_font->getType() == irr::gui::EGFT_CUSTOM) { // Draw colored text if FreeType is enabled - irr::gui::CGUITTFont *tmp = dynamic_cast(m_font); - tmp->draw( - fragment.text, - destrect, - video::SColor(255, 255, 255, 255), - false, - false, - &AbsoluteClippingRect); - } else + irr::gui::CGUITTFont *tmp = + dynamic_cast( + m_font); + tmp->draw(fragment.text, destrect, + video::SColor(255, 255, 255, 255), false, + false, &AbsoluteClippingRect); + } else #endif { // Otherwise use standard text - m_font->draw( - fragment.text.c_str(), - destrect, - video::SColor(255, 255, 255, 255), - false, - false, - &AbsoluteClippingRect); + m_font->draw(fragment.text.c_str(), destrect, + video::SColor(255, 255, 255, 255), false, + false, &AbsoluteClippingRect); } } } @@ -360,58 +325,44 @@ void GUIChatConsole::drawPrompt() s32 line_height = m_fontsize.Y; s32 y = row * line_height + m_height - m_desired_height; - ChatPrompt& prompt = m_chat_backend->getPrompt(); + ChatPrompt &prompt = m_chat_backend->getPrompt(); std::wstring prompt_text = prompt.getVisiblePortion(); // FIXME Draw string at once, not character by character // That will only work with the cursor once we have a monospace font - for (u32 i = 0; i < prompt_text.size(); ++i) - { + for (u32 i = 0; i < prompt_text.size(); ++i) { wchar_t ws[2] = {prompt_text[i], 0}; s32 x = (1 + i) * m_fontsize.X; - core::rect destrect( - x, y, x + m_fontsize.X, y + m_fontsize.Y); - m_font->draw( - ws, - destrect, - video::SColor(255, 255, 255, 255), - false, - false, - &AbsoluteClippingRect); + core::rect destrect(x, y, x + m_fontsize.X, y + m_fontsize.Y); + m_font->draw(ws, destrect, video::SColor(255, 255, 255, 255), false, + false, &AbsoluteClippingRect); } // Draw the cursor during on periods - if ((m_cursor_blink & 0x8000) != 0) - { + if ((m_cursor_blink & 0x8000) != 0) { s32 cursor_pos = prompt.getVisibleCursorPosition(); - if (cursor_pos >= 0) - { + if (cursor_pos >= 0) { s32 cursor_len = prompt.getCursorLength(); - video::IVideoDriver* driver = Environment->getVideoDriver(); + video::IVideoDriver *driver = Environment->getVideoDriver(); s32 x = (1 + cursor_pos) * m_fontsize.X; - core::rect destrect( - x, - y + m_fontsize.Y * (1.0 - m_cursor_height), - x + m_fontsize.X * MYMAX(cursor_len, 1), - y + m_fontsize.Y * (cursor_len ? m_cursor_height+1 : 1) - ); - video::SColor cursor_color(255,255,255,255); + core::rect destrect(x, + y + m_fontsize.Y * (1.0 - m_cursor_height), + x + m_fontsize.X * MYMAX(cursor_len, 1), + y + m_fontsize.Y * (cursor_len ? m_cursor_height + 1 + : 1)); + video::SColor cursor_color(255, 255, 255, 255); driver->draw2DRectangle( - cursor_color, - destrect, - &AbsoluteClippingRect); + cursor_color, destrect, &AbsoluteClippingRect); } } - } -bool GUIChatConsole::OnEvent(const SEvent& event) +bool GUIChatConsole::OnEvent(const SEvent &event) { ChatPrompt &prompt = m_chat_backend->getPrompt(); - if(event.EventType == EET_KEY_INPUT_EVENT && event.KeyInput.PressedDown) - { + if (event.EventType == EET_KEY_INPUT_EVENT && event.KeyInput.PressedDown) { // Key input if (KeyPress(event.KeyInput) == getKeySetting("keymap_console")) { closeConsole(); @@ -426,21 +377,16 @@ bool GUIChatConsole::OnEvent(const SEvent& event) closeConsoleAtOnce(); m_close_on_enter = false; // inhibit open so the_game doesn't reopen immediately - m_open_inhibited = 1; // so the ESCAPE button doesn't open the "pause menu" + m_open_inhibited = 1; // so the ESCAPE button doesn't open the + // "pause menu" return true; - } - else if(event.KeyInput.Key == KEY_PRIOR) - { + } else if (event.KeyInput.Key == KEY_PRIOR) { m_chat_backend->scrollPageUp(); return true; - } - else if(event.KeyInput.Key == KEY_NEXT) - { + } else if (event.KeyInput.Key == KEY_NEXT) { m_chat_backend->scrollPageDown(); return true; - } - else if(event.KeyInput.Key == KEY_RETURN) - { + } else if (event.KeyInput.Key == KEY_RETURN) { prompt.addToHistory(prompt.getLine()); std::wstring text = prompt.replace(L""); m_client->typeChatMessage(text); @@ -449,97 +395,76 @@ bool GUIChatConsole::OnEvent(const SEvent& event) m_close_on_enter = false; } return true; - } - else if(event.KeyInput.Key == KEY_UP) - { + } else if (event.KeyInput.Key == KEY_UP) { // Up pressed // Move back in history prompt.historyPrev(); return true; - } - else if(event.KeyInput.Key == KEY_DOWN) - { + } else if (event.KeyInput.Key == KEY_DOWN) { // Down pressed // Move forward in history prompt.historyNext(); return true; - } - else if(event.KeyInput.Key == KEY_LEFT || event.KeyInput.Key == KEY_RIGHT) - { + } else if (event.KeyInput.Key == KEY_LEFT || + event.KeyInput.Key == KEY_RIGHT) { // Left/right pressed - // Move/select character/word to the left depending on control and shift keys - ChatPrompt::CursorOp op = event.KeyInput.Shift ? - ChatPrompt::CURSOROP_SELECT : - ChatPrompt::CURSOROP_MOVE; - ChatPrompt::CursorOpDir dir = event.KeyInput.Key == KEY_LEFT ? - ChatPrompt::CURSOROP_DIR_LEFT : - ChatPrompt::CURSOROP_DIR_RIGHT; - ChatPrompt::CursorOpScope scope = event.KeyInput.Control ? - ChatPrompt::CURSOROP_SCOPE_WORD : - ChatPrompt::CURSOROP_SCOPE_CHARACTER; + // Move/select character/word to the left depending on control and + // shift keys + ChatPrompt::CursorOp op = + event.KeyInput.Shift ? ChatPrompt::CURSOROP_SELECT + : ChatPrompt::CURSOROP_MOVE; + ChatPrompt::CursorOpDir dir = + event.KeyInput.Key == KEY_LEFT + ? ChatPrompt::CURSOROP_DIR_LEFT + : ChatPrompt::CURSOROP_DIR_RIGHT; + ChatPrompt::CursorOpScope scope = + event.KeyInput.Control + ? ChatPrompt::CURSOROP_SCOPE_WORD + : ChatPrompt::CURSOROP_SCOPE_CHARACTER; prompt.cursorOperation(op, dir, scope); return true; - } - else if(event.KeyInput.Key == KEY_HOME) - { + } else if (event.KeyInput.Key == KEY_HOME) { // Home pressed // move to beginning of line - prompt.cursorOperation( - ChatPrompt::CURSOROP_MOVE, - ChatPrompt::CURSOROP_DIR_LEFT, - ChatPrompt::CURSOROP_SCOPE_LINE); + prompt.cursorOperation(ChatPrompt::CURSOROP_MOVE, + ChatPrompt::CURSOROP_DIR_LEFT, + ChatPrompt::CURSOROP_SCOPE_LINE); return true; - } - else if(event.KeyInput.Key == KEY_END) - { + } else if (event.KeyInput.Key == KEY_END) { // End pressed // move to end of line - prompt.cursorOperation( - ChatPrompt::CURSOROP_MOVE, - ChatPrompt::CURSOROP_DIR_RIGHT, - ChatPrompt::CURSOROP_SCOPE_LINE); + prompt.cursorOperation(ChatPrompt::CURSOROP_MOVE, + ChatPrompt::CURSOROP_DIR_RIGHT, + ChatPrompt::CURSOROP_SCOPE_LINE); return true; - } - else if(event.KeyInput.Key == KEY_BACK) - { + } else if (event.KeyInput.Key == KEY_BACK) { // Backspace or Ctrl-Backspace pressed // delete character / word to the left ChatPrompt::CursorOpScope scope = - event.KeyInput.Control ? - ChatPrompt::CURSOROP_SCOPE_WORD : - ChatPrompt::CURSOROP_SCOPE_CHARACTER; - prompt.cursorOperation( - ChatPrompt::CURSOROP_DELETE, - ChatPrompt::CURSOROP_DIR_LEFT, - scope); + event.KeyInput.Control + ? ChatPrompt::CURSOROP_SCOPE_WORD + : ChatPrompt::CURSOROP_SCOPE_CHARACTER; + prompt.cursorOperation(ChatPrompt::CURSOROP_DELETE, + ChatPrompt::CURSOROP_DIR_LEFT, scope); return true; - } - else if(event.KeyInput.Key == KEY_DELETE) - { + } else if (event.KeyInput.Key == KEY_DELETE) { // Delete or Ctrl-Delete pressed // delete character / word to the right ChatPrompt::CursorOpScope scope = - event.KeyInput.Control ? - ChatPrompt::CURSOROP_SCOPE_WORD : - ChatPrompt::CURSOROP_SCOPE_CHARACTER; - prompt.cursorOperation( - ChatPrompt::CURSOROP_DELETE, - ChatPrompt::CURSOROP_DIR_RIGHT, - scope); + event.KeyInput.Control + ? ChatPrompt::CURSOROP_SCOPE_WORD + : ChatPrompt::CURSOROP_SCOPE_CHARACTER; + prompt.cursorOperation(ChatPrompt::CURSOROP_DELETE, + ChatPrompt::CURSOROP_DIR_RIGHT, scope); return true; - } - else if(event.KeyInput.Key == KEY_KEY_A && event.KeyInput.Control) - { + } else if (event.KeyInput.Key == KEY_KEY_A && event.KeyInput.Control) { // Ctrl-A pressed // Select all text - prompt.cursorOperation( - ChatPrompt::CURSOROP_SELECT, - ChatPrompt::CURSOROP_DIR_LEFT, // Ignored - ChatPrompt::CURSOROP_SCOPE_LINE); + prompt.cursorOperation(ChatPrompt::CURSOROP_SELECT, + ChatPrompt::CURSOROP_DIR_LEFT, // Ignored + ChatPrompt::CURSOROP_SCOPE_LINE); return true; - } - else if(event.KeyInput.Key == KEY_KEY_C && event.KeyInput.Control) - { + } else if (event.KeyInput.Key == KEY_KEY_C && event.KeyInput.Control) { // Ctrl-C pressed // Copy text to clipboard if (prompt.getCursorLength() <= 0) @@ -548,28 +473,23 @@ bool GUIChatConsole::OnEvent(const SEvent& event) std::string selected(wselected.begin(), wselected.end()); Environment->getOSOperator()->copyToClipboard(selected.c_str()); return true; - } - else if(event.KeyInput.Key == KEY_KEY_V && event.KeyInput.Control) - { + } else if (event.KeyInput.Key == KEY_KEY_V && event.KeyInput.Control) { // Ctrl-V pressed // paste text from clipboard if (prompt.getCursorLength() > 0) { // Delete selected section of text - prompt.cursorOperation( - ChatPrompt::CURSOROP_DELETE, - ChatPrompt::CURSOROP_DIR_LEFT, // Ignored - ChatPrompt::CURSOROP_SCOPE_SELECTION); + prompt.cursorOperation(ChatPrompt::CURSOROP_DELETE, + ChatPrompt::CURSOROP_DIR_LEFT, // Ignored + ChatPrompt::CURSOROP_SCOPE_SELECTION); } IOSOperator *os_operator = Environment->getOSOperator(); const c8 *text = os_operator->getTextFromClipboard(); if (!text) return true; - std::basic_string str((const unsigned char*)text); + std::basic_string str((const unsigned char *)text); prompt.input(std::wstring(str.begin(), str.end())); return true; - } - else if(event.KeyInput.Key == KEY_KEY_X && event.KeyInput.Control) - { + } else if (event.KeyInput.Key == KEY_KEY_X && event.KeyInput.Control) { // Ctrl-X pressed // Cut text to clipboard if (prompt.getCursorLength() <= 0) @@ -577,55 +497,45 @@ bool GUIChatConsole::OnEvent(const SEvent& event) std::wstring wselected = prompt.getSelection(); std::string selected(wselected.begin(), wselected.end()); Environment->getOSOperator()->copyToClipboard(selected.c_str()); - prompt.cursorOperation( - ChatPrompt::CURSOROP_DELETE, - ChatPrompt::CURSOROP_DIR_LEFT, // Ignored - ChatPrompt::CURSOROP_SCOPE_SELECTION); + prompt.cursorOperation(ChatPrompt::CURSOROP_DELETE, + ChatPrompt::CURSOROP_DIR_LEFT, // Ignored + ChatPrompt::CURSOROP_SCOPE_SELECTION); return true; - } - else if(event.KeyInput.Key == KEY_KEY_U && event.KeyInput.Control) - { + } else if (event.KeyInput.Key == KEY_KEY_U && event.KeyInput.Control) { // Ctrl-U pressed // kill line to left end - prompt.cursorOperation( - ChatPrompt::CURSOROP_DELETE, - ChatPrompt::CURSOROP_DIR_LEFT, - ChatPrompt::CURSOROP_SCOPE_LINE); + prompt.cursorOperation(ChatPrompt::CURSOROP_DELETE, + ChatPrompt::CURSOROP_DIR_LEFT, + ChatPrompt::CURSOROP_SCOPE_LINE); return true; - } - else if(event.KeyInput.Key == KEY_KEY_K && event.KeyInput.Control) - { + } else if (event.KeyInput.Key == KEY_KEY_K && event.KeyInput.Control) { // Ctrl-K pressed // kill line to right end - prompt.cursorOperation( - ChatPrompt::CURSOROP_DELETE, - ChatPrompt::CURSOROP_DIR_RIGHT, - ChatPrompt::CURSOROP_SCOPE_LINE); + prompt.cursorOperation(ChatPrompt::CURSOROP_DELETE, + ChatPrompt::CURSOROP_DIR_RIGHT, + ChatPrompt::CURSOROP_SCOPE_LINE); return true; - } - else if(event.KeyInput.Key == KEY_TAB) - { + } else if (event.KeyInput.Key == KEY_TAB) { // Tab or Shift-Tab pressed // Nick completion - std::list names = m_client->getConnectedPlayerNames(); + std::list names = + m_client->getConnectedPlayerNames(); bool backwards = event.KeyInput.Shift; prompt.nickCompletion(names, backwards); return true; } else if (!iswcntrl(event.KeyInput.Char) && !event.KeyInput.Control) { - #if defined(__linux__) && (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 9) - wchar_t wc = L'_'; - mbtowc( &wc, (char *) &event.KeyInput.Char, sizeof(event.KeyInput.Char) ); - prompt.input(wc); - #else - prompt.input(event.KeyInput.Char); - #endif +#if defined(__linux__) && (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 9) + wchar_t wc = L'_'; + mbtowc(&wc, (char *)&event.KeyInput.Char, + sizeof(event.KeyInput.Char)); + prompt.input(wc); +#else + prompt.input(event.KeyInput.Char); +#endif return true; } - } - else if(event.EventType == EET_MOUSE_INPUT_EVENT) - { - if(event.MouseInput.Event == EMIE_MOUSE_WHEEL) - { + } else if (event.EventType == EET_MOUSE_INPUT_EVENT) { + if (event.MouseInput.Event == EMIE_MOUSE_WHEEL) { s32 rows = myround(-3.0 * event.MouseInput.Wheel); m_chat_backend->scroll(rows); } @@ -643,4 +553,3 @@ void GUIChatConsole::setVisible(bool visible) recalculateConsolePosition(); } } - diff --git a/src/gui/guiChatConsole.h b/src/gui/guiChatConsole.h index 7be40e27c..adf89e88c 100644 --- a/src/gui/guiChatConsole.h +++ b/src/gui/guiChatConsole.h @@ -29,12 +29,8 @@ class Client; class GUIChatConsole : public gui::IGUIElement { public: - GUIChatConsole(gui::IGUIEnvironment* env, - gui::IGUIElement* parent, - s32 id, - ChatBackend* backend, - Client* client, - IMenuManager* menumgr); + GUIChatConsole(gui::IGUIEnvironment *env, gui::IGUIElement *parent, s32 id, + ChatBackend *backend, Client *client, IMenuManager *menumgr); virtual ~GUIChatConsole(); // Open the console (height = desired fraction of screen size) @@ -63,18 +59,15 @@ public: void replaceAndAddToHistory(const std::wstring &line); // Change how the cursor looks - void setCursor( - bool visible, - bool blinking = false, - f32 blink_speed = 1.0, - f32 relative_height = 1.0); + void setCursor(bool visible, bool blinking = false, f32 blink_speed = 1.0, + f32 relative_height = 1.0); // Irrlicht draw method virtual void draw(); - bool canTakeFocus(gui::IGUIElement* element) { return false; } + bool canTakeFocus(gui::IGUIElement *element) { return false; } - virtual bool OnEvent(const SEvent& event); + virtual bool OnEvent(const SEvent &event); virtual void setVisible(bool visible); @@ -89,9 +82,9 @@ private: void drawPrompt(); private: - ChatBackend* m_chat_backend; - Client* m_client; - IMenuManager* m_menumgr; + ChatBackend *m_chat_backend; + Client *m_client; + IMenuManager *m_menumgr; // current screen size v2u32 m_screensize; diff --git a/src/gui/guiConfirmRegistration.cpp b/src/gui/guiConfirmRegistration.cpp index 55c111df8..d4679b5dc 100644 --- a/src/gui/guiConfirmRegistration.cpp +++ b/src/gui/guiConfirmRegistration.cpp @@ -39,8 +39,8 @@ const int ID_message = 266; GUIConfirmRegistration::GUIConfirmRegistration(gui::IGUIEnvironment *env, gui::IGUIElement *parent, s32 id, IMenuManager *menumgr, Client *client, - const std::string &playername, const std::string &password, - bool *aborted, ISimpleTextureSource *tsrc) : + const std::string &playername, const std::string &password, bool *aborted, + ISimpleTextureSource *tsrc) : GUIModalMenu(env, parent, id, menumgr), m_client(client), m_playername(playername), m_password(password), m_aborted(aborted), m_tsrc(tsrc) @@ -74,12 +74,9 @@ void GUIConfirmRegistration::regenerateGui(v2u32 screensize) Calculate new sizes and positions */ const float s = m_gui_scale; - DesiredRect = core::rect( - screensize.X / 2 - 600 * s / 2, - screensize.Y / 2 - 360 * s / 2, - screensize.X / 2 + 600 * s / 2, - screensize.Y / 2 + 360 * s / 2 - ); + DesiredRect = core::rect(screensize.X / 2 - 600 * s / 2, + screensize.Y / 2 - 360 * s / 2, screensize.X / 2 + 600 * s / 2, + screensize.Y / 2 + 360 * s / 2); recalculateAbsolutePosition(false); v2s32 size = DesiredRect.getSize(); @@ -95,11 +92,14 @@ void GUIConfirmRegistration::regenerateGui(v2u32 screensize) core::rect rect2(0, 0, 540 * s, 180 * s); rect2 += topleft_client + v2s32(30 * s, ypos); static const std::string info_text_template = strgettext( - "You are about to join this server with the name \"%s\" for the " + "You are about to join this server with the name \"%s\" " + "for the " "first time.\n" - "If you proceed, a new account using your credentials will be " + "If you proceed, a new account using your credentials " + "will be " "created on this server.\n" - "Please retype your password and click 'Register and Join' to " + "Please retype your password and click 'Register and " + "Join' to " "confirm account creation, or click 'Cancel' to abort."); char info_text_buf[1024]; porting::mt_snprintf(info_text_buf, sizeof(info_text_buf), @@ -223,7 +223,7 @@ bool GUIConfirmRegistration::OnEvent(const SEvent &event) if (event.GUIEvent.EventType == gui::EGET_ELEMENT_FOCUS_LOST && isVisible()) { if (!canTakeFocus(event.GUIEvent.Element)) { infostream << "GUIConfirmRegistration: Not allowing focus change." - << std::endl; + << std::endl; // Returning true disables focus change return true; } diff --git a/src/gui/guiEditBoxWithScrollbar.cpp b/src/gui/guiEditBoxWithScrollbar.cpp index 442406688..c803ba58c 100644 --- a/src/gui/guiEditBoxWithScrollbar.cpp +++ b/src/gui/guiEditBoxWithScrollbar.cpp @@ -17,31 +17,30 @@ todo: optional scrollbars [done] ctrl+left/right to select word -double click/ctrl click: word select + drag to select whole words, triple click to select line -optional? dragging selected text -numerical +double click/ctrl click: word select + drag to select whole words, triple click to select +line optional? dragging selected text numerical */ - //! constructor -GUIEditBoxWithScrollBar::GUIEditBoxWithScrollBar(const wchar_t* text, bool border, - IGUIEnvironment* environment, IGUIElement* parent, s32 id, - const core::rect& rectangle, bool writable, bool has_vscrollbar) - : IGUIEditBox(environment, parent, id, rectangle), m_mouse_marking(false), - m_border(border), m_background(true), m_override_color_enabled(false), m_mark_begin(0), m_mark_end(0), - m_override_color(video::SColor(101, 255, 255, 255)), m_override_font(0), m_last_break_font(0), - m_operator(0), m_blink_start_time(0), m_cursor_pos(0), m_hscroll_pos(0), m_vscroll_pos(0), m_max(0), - m_word_wrap(false), m_multiline(false), m_autoscroll(true), m_passwordbox(false), - m_passwordchar(L'*'), m_halign(EGUIA_UPPERLEFT), m_valign(EGUIA_CENTER), - m_current_text_rect(0, 0, 1, 1), m_frame_rect(rectangle), - m_scrollbar_width(0), m_vscrollbar(NULL), m_writable(writable), - m_bg_color_used(false) +GUIEditBoxWithScrollBar::GUIEditBoxWithScrollBar(const wchar_t *text, bool border, + IGUIEnvironment *environment, IGUIElement *parent, s32 id, + const core::rect &rectangle, bool writable, bool has_vscrollbar) : + IGUIEditBox(environment, parent, id, rectangle), + m_mouse_marking(false), m_border(border), m_background(true), + m_override_color_enabled(false), m_mark_begin(0), m_mark_end(0), + m_override_color(video::SColor(101, 255, 255, 255)), m_override_font(0), + m_last_break_font(0), m_operator(0), m_blink_start_time(0), + m_cursor_pos(0), m_hscroll_pos(0), m_vscroll_pos(0), m_max(0), + m_word_wrap(false), m_multiline(false), m_autoscroll(true), + m_passwordbox(false), m_passwordchar(L'*'), m_halign(EGUIA_UPPERLEFT), + m_valign(EGUIA_CENTER), m_current_text_rect(0, 0, 1, 1), + m_frame_rect(rectangle), m_scrollbar_width(0), m_vscrollbar(NULL), + m_writable(writable), m_bg_color_used(false) { #ifdef _DEBUG setDebugName("GUIEditBoxWithScrollBar"); #endif - Text = text; if (Environment) @@ -65,7 +64,6 @@ GUIEditBoxWithScrollBar::GUIEditBoxWithScrollBar(const wchar_t* text, bool borde setWritable(writable); } - //! destructor GUIEditBoxWithScrollBar::~GUIEditBoxWithScrollBar() { @@ -79,9 +77,8 @@ GUIEditBoxWithScrollBar::~GUIEditBoxWithScrollBar() m_vscrollbar->drop(); } - //! Sets another skin independent font. -void GUIEditBoxWithScrollBar::setOverrideFont(IGUIFont* font) +void GUIEditBoxWithScrollBar::setOverrideFont(IGUIFont *font) { if (m_override_font == font) return; @@ -98,17 +95,17 @@ void GUIEditBoxWithScrollBar::setOverrideFont(IGUIFont* font) } //! Gets the override font (if any) -IGUIFont * GUIEditBoxWithScrollBar::getOverrideFont() const +IGUIFont *GUIEditBoxWithScrollBar::getOverrideFont() const { return m_override_font; } //! Get the font which is used right now for drawing -IGUIFont* GUIEditBoxWithScrollBar::getActiveFont() const +IGUIFont *GUIEditBoxWithScrollBar::getActiveFont() const { if (m_override_font) return m_override_font; - IGUISkin* skin = Environment->getSkin(); + IGUISkin *skin = Environment->getSkin(); if (skin) return skin->getFont(); return 0; @@ -121,13 +118,11 @@ void GUIEditBoxWithScrollBar::setOverrideColor(video::SColor color) m_override_color_enabled = true; } - video::SColor GUIEditBoxWithScrollBar::getOverrideColor() const { return m_override_color; } - //! Turns the border on or off void GUIEditBoxWithScrollBar::setDrawBorder(bool border) { @@ -158,7 +153,6 @@ void GUIEditBoxWithScrollBar::setWordWrap(bool enable) breakText(); } - void GUIEditBoxWithScrollBar::updateAbsolutePosition() { core::rect old_absolute_rect(AbsoluteRect); @@ -176,21 +170,18 @@ bool GUIEditBoxWithScrollBar::isWordWrapEnabled() const return m_word_wrap; } - //! Enables or disables newlines. void GUIEditBoxWithScrollBar::setMultiLine(bool enable) { m_multiline = enable; } - //! Checks if multi line editing is enabled bool GUIEditBoxWithScrollBar::isMultiLineEnabled() const { return m_multiline; } - void GUIEditBoxWithScrollBar::setPasswordBox(bool password_box, wchar_t password_char) { m_passwordbox = password_box; @@ -202,27 +193,24 @@ void GUIEditBoxWithScrollBar::setPasswordBox(bool password_box, wchar_t password } } - bool GUIEditBoxWithScrollBar::isPasswordBox() const { return m_passwordbox; } - //! Sets text justification -void GUIEditBoxWithScrollBar::setTextAlignment(EGUI_ALIGNMENT horizontal, EGUI_ALIGNMENT vertical) +void GUIEditBoxWithScrollBar::setTextAlignment( + EGUI_ALIGNMENT horizontal, EGUI_ALIGNMENT vertical) { m_halign = horizontal; m_valign = vertical; } - //! called if an event happened. -bool GUIEditBoxWithScrollBar::OnEvent(const SEvent& event) +bool GUIEditBoxWithScrollBar::OnEvent(const SEvent &event) { if (isEnabled()) { - switch (event.EventType) - { + switch (event.EventType) { case EET_GUI_EVENT: if (event.GUIEvent.EventType == EGET_ELEMENT_FOCUS_LOST) { if (event.GUIEvent.Caller == this) { @@ -247,8 +235,7 @@ bool GUIEditBoxWithScrollBar::OnEvent(const SEvent& event) return IGUIElement::OnEvent(event); } - -bool GUIEditBoxWithScrollBar::processKey(const SEvent& event) +bool GUIEditBoxWithScrollBar::processKey(const SEvent &event) { if (!m_writable) { return false; @@ -279,10 +266,13 @@ bool GUIEditBoxWithScrollBar::processKey(const SEvent& event) break; case KEY_KEY_C: // copy to clipboard - if (!m_passwordbox && m_operator && m_mark_begin != m_mark_end) - { - const s32 realmbgn = m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end; - const s32 realmend = m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin; + if (!m_passwordbox && m_operator && m_mark_begin != m_mark_end) { + const s32 realmbgn = m_mark_begin < m_mark_end + ? m_mark_begin + : m_mark_end; + const s32 realmend = m_mark_begin < m_mark_end + ? m_mark_end + : m_mark_begin; core::stringc s; s = Text.subString(realmbgn, realmend - realmbgn).c_str(); @@ -292,20 +282,25 @@ bool GUIEditBoxWithScrollBar::processKey(const SEvent& event) case KEY_KEY_X: // cut to the clipboard if (!m_passwordbox && m_operator && m_mark_begin != m_mark_end) { - const s32 realmbgn = m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end; - const s32 realmend = m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin; + const s32 realmbgn = m_mark_begin < m_mark_end + ? m_mark_begin + : m_mark_end; + const s32 realmend = m_mark_begin < m_mark_end + ? m_mark_end + : m_mark_begin; // copy core::stringc sc; - sc = Text.subString(realmbgn, realmend - realmbgn).c_str(); + sc = Text.subString(realmbgn, realmend - realmbgn) + .c_str(); m_operator->copyToClipboard(sc.c_str()); - if (isEnabled()) - { + if (isEnabled()) { // delete core::stringw s; s = Text.subString(0, realmbgn); - s.append(Text.subString(realmend, Text.size() - realmend)); + s.append(Text.subString(realmend, + Text.size() - realmend)); Text = s; m_cursor_pos = realmbgn; @@ -321,19 +316,28 @@ bool GUIEditBoxWithScrollBar::processKey(const SEvent& event) // paste from the clipboard if (m_operator) { - const s32 realmbgn = m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end; - const s32 realmend = m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin; + const s32 realmbgn = m_mark_begin < m_mark_end + ? m_mark_begin + : m_mark_end; + const s32 realmend = m_mark_begin < m_mark_end + ? m_mark_end + : m_mark_begin; // add new character - const c8* p = m_operator->getTextFromClipboard(); + const c8 *p = m_operator->getTextFromClipboard(); if (p) { if (m_mark_begin == m_mark_end) { // insert text - core::stringw s = Text.subString(0, m_cursor_pos); + core::stringw s = Text.subString( + 0, m_cursor_pos); s.append(p); - s.append(Text.subString(m_cursor_pos, Text.size() - m_cursor_pos)); + s.append(Text.subString(m_cursor_pos, + Text.size() - m_cursor_pos)); - if (!m_max || s.size() <= m_max) // thx to Fish FH for fix + if (!m_max || s.size() <= m_max) // thx to + // Fish + // FH for + // fix { Text = s; s = p; @@ -342,15 +346,21 @@ bool GUIEditBoxWithScrollBar::processKey(const SEvent& event) } else { // replace text - core::stringw s = Text.subString(0, realmbgn); + core::stringw s = Text.subString( + 0, realmbgn); s.append(p); - s.append(Text.subString(realmend, Text.size() - realmend)); + s.append(Text.subString(realmend, + Text.size() - realmend)); - if (!m_max || s.size() <= m_max) // thx to Fish FH for fix + if (!m_max || s.size() <= m_max) // thx to + // Fish + // FH for + // fix { Text = s; s = p; - m_cursor_pos = realmbgn + s.size(); + m_cursor_pos = realmbgn + + s.size(); } } } @@ -390,14 +400,15 @@ bool GUIEditBoxWithScrollBar::processKey(const SEvent& event) } // default keyboard handling else - switch (event.KeyInput.Key) { - case KEY_END: - { + switch (event.KeyInput.Key) { + case KEY_END: { s32 p = Text.size(); if (m_word_wrap || m_multiline) { p = getLineFromPos(m_cursor_pos); - p = m_broken_text_positions[p] + (s32)m_broken_text[p].size(); - if (p > 0 && (Text[p - 1] == L'\r' || Text[p - 1] == L'\n')) + p = m_broken_text_positions[p] + + (s32)m_broken_text[p].size(); + if (p > 0 && (Text[p - 1] == L'\r' || + Text[p - 1] == L'\n')) p -= 1; } @@ -412,10 +423,8 @@ bool GUIEditBoxWithScrollBar::processKey(const SEvent& event) } m_cursor_pos = p; m_blink_start_time = porting::getTimeMs(); - } - break; - case KEY_HOME: - { + } break; + case KEY_HOME: { s32 p = 0; if (m_word_wrap || m_multiline) { @@ -433,8 +442,7 @@ bool GUIEditBoxWithScrollBar::processKey(const SEvent& event) } m_cursor_pos = p; m_blink_start_time = porting::getTimeMs(); - } - break; + } break; case KEY_RETURN: if (m_multiline) { inputChar(L'\n'); @@ -482,13 +490,25 @@ bool GUIEditBoxWithScrollBar::processKey(const SEvent& event) case KEY_UP: if (m_multiline || (m_word_wrap && m_broken_text.size() > 1)) { s32 lineNo = getLineFromPos(m_cursor_pos); - s32 mb = (m_mark_begin == m_mark_end) ? m_cursor_pos : (m_mark_begin > m_mark_end ? m_mark_begin : m_mark_end); - if (lineNo > 0) { - s32 cp = m_cursor_pos - m_broken_text_positions[lineNo]; + s32 mb = (m_mark_begin == m_mark_end) + ? m_cursor_pos + : (m_mark_begin > m_mark_end ? m_mark_begin + : m_mark_end); + if (lineNo > 0) { + s32 cp = m_cursor_pos - + m_broken_text_positions[lineNo]; if ((s32)m_broken_text[lineNo - 1].size() < cp) - m_cursor_pos = m_broken_text_positions[lineNo - 1] + core::max_((u32)1, m_broken_text[lineNo - 1].size()) - 1; + m_cursor_pos = m_broken_text_positions + [lineNo - 1] + + core::max_((u32)1, + m_broken_text[lineNo - + 1] + .size()) - + 1; else - m_cursor_pos = m_broken_text_positions[lineNo - 1] + cp; + m_cursor_pos = m_broken_text_positions + [lineNo - 1] + + cp; } if (event.KeyInput.Shift) { @@ -505,14 +525,25 @@ bool GUIEditBoxWithScrollBar::processKey(const SEvent& event) case KEY_DOWN: if (m_multiline || (m_word_wrap && m_broken_text.size() > 1)) { s32 lineNo = getLineFromPos(m_cursor_pos); - s32 mb = (m_mark_begin == m_mark_end) ? m_cursor_pos : (m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end); - if (lineNo < (s32)m_broken_text.size() - 1) - { - s32 cp = m_cursor_pos - m_broken_text_positions[lineNo]; + s32 mb = (m_mark_begin == m_mark_end) + ? m_cursor_pos + : (m_mark_begin < m_mark_end ? m_mark_begin + : m_mark_end); + if (lineNo < (s32)m_broken_text.size() - 1) { + s32 cp = m_cursor_pos - + m_broken_text_positions[lineNo]; if ((s32)m_broken_text[lineNo + 1].size() < cp) - m_cursor_pos = m_broken_text_positions[lineNo + 1] + core::max_((u32)1, m_broken_text[lineNo + 1].size()) - 1; + m_cursor_pos = m_broken_text_positions + [lineNo + 1] + + core::max_((u32)1, + m_broken_text[lineNo + + 1] + .size()) - + 1; else - m_cursor_pos = m_broken_text_positions[lineNo + 1] + cp; + m_cursor_pos = m_broken_text_positions + [lineNo + 1] + + cp; } if (event.KeyInput.Shift) { @@ -537,11 +568,18 @@ bool GUIEditBoxWithScrollBar::processKey(const SEvent& event) if (m_mark_begin != m_mark_end) { // delete marked text - const s32 realmbgn = m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end; - const s32 realmend = m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin; + const s32 realmbgn = + m_mark_begin < m_mark_end + ? m_mark_begin + : m_mark_end; + const s32 realmend = + m_mark_begin < m_mark_end + ? m_mark_end + : m_mark_begin; s = Text.subString(0, realmbgn); - s.append(Text.subString(realmend, Text.size() - realmend)); + s.append(Text.subString(realmend, + Text.size() - realmend)); Text = s; m_cursor_pos = realmbgn; @@ -551,14 +589,16 @@ bool GUIEditBoxWithScrollBar::processKey(const SEvent& event) s = Text.subString(0, m_cursor_pos - 1); else s = L""; - s.append(Text.subString(m_cursor_pos, Text.size() - m_cursor_pos)); + s.append(Text.subString(m_cursor_pos, + Text.size() - m_cursor_pos)); Text = s; --m_cursor_pos; } if (m_cursor_pos < 0) m_cursor_pos = 0; - m_blink_start_time = porting::getTimeMs(); // os::Timer::getTime(); + m_blink_start_time = porting:: + getTimeMs(); // os::Timer::getTime(); new_mark_begin = 0; new_mark_end = 0; text_changed = true; @@ -573,25 +613,34 @@ bool GUIEditBoxWithScrollBar::processKey(const SEvent& event) if (m_mark_begin != m_mark_end) { // delete marked text - const s32 realmbgn = m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end; - const s32 realmend = m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin; + const s32 realmbgn = + m_mark_begin < m_mark_end + ? m_mark_begin + : m_mark_end; + const s32 realmend = + m_mark_begin < m_mark_end + ? m_mark_end + : m_mark_begin; s = Text.subString(0, realmbgn); - s.append(Text.subString(realmend, Text.size() - realmend)); + s.append(Text.subString(realmend, + Text.size() - realmend)); Text = s; m_cursor_pos = realmbgn; } else { // delete text before cursor s = Text.subString(0, m_cursor_pos); - s.append(Text.subString(m_cursor_pos + 1, Text.size() - m_cursor_pos - 1)); + s.append(Text.subString(m_cursor_pos + 1, + Text.size() - m_cursor_pos - 1)); Text = s; } if (m_cursor_pos > (s32)Text.size()) m_cursor_pos = (s32)Text.size(); - m_blink_start_time = porting::getTimeMs(); // os::Timer::getTime(); + m_blink_start_time = porting:: + getTimeMs(); // os::Timer::getTime(); new_mark_begin = 0; new_mark_end = 0; text_changed = true; @@ -641,16 +690,13 @@ bool GUIEditBoxWithScrollBar::processKey(const SEvent& event) breakText(); calculateScrollPos(); sendGuiEvent(EGET_EDITBOX_CHANGED); - } - else - { + } else { calculateScrollPos(); } return true; } - //! draws the element and its children void GUIEditBoxWithScrollBar::draw() { @@ -659,7 +705,7 @@ void GUIEditBoxWithScrollBar::draw() const bool focus = Environment->hasFocus(this); - IGUISkin* skin = Environment->getSkin(); + IGUISkin *skin = Environment->getSkin(); if (!skin) return; @@ -670,7 +716,8 @@ void GUIEditBoxWithScrollBar::draw() bg_color = m_bg_color_used ? m_bg_color : default_bg_color; if (!m_border && m_background) { - skin->draw2DRectangle(this, bg_color, AbsoluteRect, &AbsoluteClippingRect); + skin->draw2DRectangle( + this, bg_color, AbsoluteRect, &AbsoluteClippingRect); } // draw the border @@ -679,7 +726,7 @@ void GUIEditBoxWithScrollBar::draw() if (m_writable) { skin->draw3DSunkenPane(this, bg_color, false, m_background, - AbsoluteRect, &AbsoluteClippingRect); + AbsoluteRect, &AbsoluteClippingRect); } calculateFrameRect(); @@ -690,7 +737,7 @@ void GUIEditBoxWithScrollBar::draw() // draw the text - IGUIFont* font = getActiveFont(); + IGUIFont *font = getActiveFont(); s32 cursor_line = 0; s32 charcursorpos = 0; @@ -709,10 +756,13 @@ void GUIEditBoxWithScrollBar::draw() // get mark position const bool ml = (!m_passwordbox && (m_word_wrap || m_multiline)); - const s32 realmbgn = m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end; - const s32 realmend = m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin; + const s32 realmbgn = + m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end; + const s32 realmend = + m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin; const s32 hline_start = ml ? getLineFromPos(realmbgn) : 0; - const s32 hline_count = ml ? getLineFromPos(realmend) - hline_start + 1 : 1; + const s32 hline_count = + ml ? getLineFromPos(realmend) - hline_start + 1 : 1; const s32 line_count = ml ? m_broken_text.size() : 1; // Save the override color information. @@ -729,7 +779,8 @@ void GUIEditBoxWithScrollBar::draw() for (s32 i = 0; i < line_count; ++i) { setTextRect(i); - // clipping test - don't draw anything outside the visible area + // clipping test - don't draw anything outside the visible + // area core::rect c = local_clip_rect; c.clipAgainst(m_current_text_rect); if (!c.isValid()) @@ -741,11 +792,11 @@ void GUIEditBoxWithScrollBar::draw() m_broken_text.clear(); m_broken_text.emplace_back(); } - if (m_broken_text[0].size() != Text.size()){ + if (m_broken_text[0].size() != Text.size()) { m_broken_text[0] = Text; - for (u32 q = 0; q < Text.size(); ++q) - { - m_broken_text[0][q] = m_passwordchar; + for (u32 q = 0; q < Text.size(); ++q) { + m_broken_text[0][q] = + m_passwordchar; } } txt_line = &m_broken_text[0]; @@ -755,55 +806,77 @@ void GUIEditBoxWithScrollBar::draw() start_pos = ml ? m_broken_text_positions[i] : 0; } - // draw normal text font->draw(txt_line->c_str(), m_current_text_rect, - m_override_color_enabled ? m_override_color : skin->getColor(EGDC_BUTTON_TEXT), - false, true, &local_clip_rect); + m_override_color_enabled + ? m_override_color + : skin->getColor(EGDC_BUTTON_TEXT), + false, true, &local_clip_rect); // draw mark and marked text - if (focus && m_mark_begin != m_mark_end && i >= hline_start && i < hline_start + hline_count) { + if (focus && m_mark_begin != m_mark_end && + i >= hline_start && + i < hline_start + hline_count) { s32 mbegin = 0, mend = 0; - s32 lineStartPos = 0, lineEndPos = txt_line->size(); + s32 lineStartPos = 0, + lineEndPos = txt_line->size(); if (i == hline_start) { // highlight start is on this line - s = txt_line->subString(0, realmbgn - start_pos); - mbegin = font->getDimension(s.c_str()).Width; + s = txt_line->subString( + 0, realmbgn - start_pos); + mbegin = font->getDimension(s.c_str()) + .Width; // deal with kerning mbegin += font->getKerningWidth( - &((*txt_line)[realmbgn - start_pos]), - realmbgn - start_pos > 0 ? &((*txt_line)[realmbgn - start_pos - 1]) : 0); + &((*txt_line)[realmbgn - + start_pos]), + realmbgn - start_pos > 0 + ? &((*txt_line)[realmbgn - + start_pos - + 1]) + : 0); lineStartPos = realmbgn - start_pos; } if (i == hline_start + hline_count - 1) { // highlight end is on this line - s2 = txt_line->subString(0, realmend - start_pos); - mend = font->getDimension(s2.c_str()).Width; + s2 = txt_line->subString( + 0, realmend - start_pos); + mend = font->getDimension(s2.c_str()) + .Width; lineEndPos = (s32)s2.size(); } else { - mend = font->getDimension(txt_line->c_str()).Width; + mend = font->getDimension(txt_line->c_str()) + .Width; } - m_current_text_rect.UpperLeftCorner.X += mbegin; - m_current_text_rect.LowerRightCorner.X = m_current_text_rect.UpperLeftCorner.X + mend - mbegin; - + m_current_text_rect.LowerRightCorner.X = + m_current_text_rect + .UpperLeftCorner + .X + + mend - mbegin; // draw mark - skin->draw2DRectangle(this, skin->getColor(EGDC_HIGH_LIGHT), m_current_text_rect, &local_clip_rect); + skin->draw2DRectangle(this, + skin->getColor(EGDC_HIGH_LIGHT), + m_current_text_rect, + &local_clip_rect); // draw marked text - s = txt_line->subString(lineStartPos, lineEndPos - lineStartPos); + s = txt_line->subString(lineStartPos, + lineEndPos - lineStartPos); if (s.size()) font->draw(s.c_str(), m_current_text_rect, - m_override_color_enabled ? m_override_color : skin->getColor(EGDC_HIGH_LIGHT_TEXT), - false, true, &local_clip_rect); - + m_override_color_enabled + ? m_override_color + : skin->getColor(EGDC_HIGH_LIGHT_TEXT), + false, true, + &local_clip_rect); } } @@ -821,15 +894,23 @@ void GUIEditBoxWithScrollBar::draw() } s = txt_line->subString(0, m_cursor_pos - start_pos); charcursorpos = font->getDimension(s.c_str()).Width + - font->getKerningWidth(L"_", m_cursor_pos - start_pos > 0 ? &((*txt_line)[m_cursor_pos - start_pos - 1]) : 0); + font->getKerningWidth(L"_", + m_cursor_pos - start_pos > 0 + ? &((*txt_line)[m_cursor_pos - + start_pos - + 1]) + : 0); - if (focus && (porting::getTimeMs() - m_blink_start_time) % 700 < 350) { + if (focus && (porting::getTimeMs() - m_blink_start_time) % 700 < + 350) { setTextRect(cursor_line); m_current_text_rect.UpperLeftCorner.X += charcursorpos; font->draw(L"_", m_current_text_rect, - m_override_color_enabled ? m_override_color : skin->getColor(EGDC_BUTTON_TEXT), - false, true, &local_clip_rect); + m_override_color_enabled + ? m_override_color + : skin->getColor(EGDC_BUTTON_TEXT), + false, true, &local_clip_rect); } } } @@ -838,9 +919,8 @@ void GUIEditBoxWithScrollBar::draw() IGUIElement::draw(); } - //! Sets the new caption of this element. -void GUIEditBoxWithScrollBar::setText(const wchar_t* text) +void GUIEditBoxWithScrollBar::setText(const wchar_t *text) { Text = text; if (u32(m_cursor_pos) > Text.size()) @@ -849,7 +929,6 @@ void GUIEditBoxWithScrollBar::setText(const wchar_t* text) breakText(); } - //! Enables or disables automatic scrolling with cursor position //! \param enable: If set to true, the text will move around with the cursor position void GUIEditBoxWithScrollBar::setAutoScroll(bool enable) @@ -857,7 +936,6 @@ void GUIEditBoxWithScrollBar::setAutoScroll(bool enable) m_autoscroll = enable; } - //! Checks to see if automatic scrolling is enabled //! \return true if automatic scrolling is enabled, false if not bool GUIEditBoxWithScrollBar::isAutoScrollEnabled() const @@ -865,7 +943,6 @@ bool GUIEditBoxWithScrollBar::isAutoScrollEnabled() const return m_autoscroll; } - //! Gets the area of the text in the edit box //! \return Returns the size in pixels of the text core::dimension2du GUIEditBoxWithScrollBar::getTextDimension() @@ -884,7 +961,6 @@ core::dimension2du GUIEditBoxWithScrollBar::getTextDimension() return core::dimension2du(ret.getSize()); } - //! Sets the maximum amount of characters which may be entered in the box. //! \param max: Maximum amount of characters. If 0, the character amount is //! infinity. @@ -896,21 +972,19 @@ void GUIEditBoxWithScrollBar::setMax(u32 max) Text = Text.subString(0, m_max); } - //! Returns maximum amount of characters, previously set by setMax(); u32 GUIEditBoxWithScrollBar::getMax() const { return m_max; } - -bool GUIEditBoxWithScrollBar::processMouse(const SEvent& event) +bool GUIEditBoxWithScrollBar::processMouse(const SEvent &event) { - switch (event.MouseInput.Event) - { + switch (event.MouseInput.Event) { case irr::EMIE_LMOUSE_LEFT_UP: if (Environment->hasFocus(this)) { - m_cursor_pos = getCursorPos(event.MouseInput.X, event.MouseInput.Y); + m_cursor_pos = getCursorPos( + event.MouseInput.X, event.MouseInput.Y); if (m_mouse_marking) { setTextMarkers(m_mark_begin, m_cursor_pos); } @@ -919,32 +993,33 @@ bool GUIEditBoxWithScrollBar::processMouse(const SEvent& event) return true; } break; - case irr::EMIE_MOUSE_MOVED: - { + case irr::EMIE_MOUSE_MOVED: { if (m_mouse_marking) { - m_cursor_pos = getCursorPos(event.MouseInput.X, event.MouseInput.Y); + m_cursor_pos = getCursorPos( + event.MouseInput.X, event.MouseInput.Y); setTextMarkers(m_mark_begin, m_cursor_pos); calculateScrollPos(); return true; } - } - break; + } break; case EMIE_LMOUSE_PRESSED_DOWN: if (!Environment->hasFocus(this)) { m_blink_start_time = porting::getTimeMs(); m_mouse_marking = true; - m_cursor_pos = getCursorPos(event.MouseInput.X, event.MouseInput.Y); + m_cursor_pos = getCursorPos( + event.MouseInput.X, event.MouseInput.Y); setTextMarkers(m_cursor_pos, m_cursor_pos); calculateScrollPos(); return true; } else { - if (!AbsoluteClippingRect.isPointInside( - core::position2d(event.MouseInput.X, event.MouseInput.Y))) { + if (!AbsoluteClippingRect.isPointInside(core::position2d( + event.MouseInput.X, event.MouseInput.Y))) { return false; } else { // move cursor - m_cursor_pos = getCursorPos(event.MouseInput.X, event.MouseInput.Y); + m_cursor_pos = getCursorPos( + event.MouseInput.X, event.MouseInput.Y); s32 newMarkBegin = m_mark_begin; if (!m_mouse_marking) @@ -963,10 +1038,9 @@ bool GUIEditBoxWithScrollBar::processMouse(const SEvent& event) return false; } - s32 GUIEditBoxWithScrollBar::getCursorPos(s32 x, s32 y) { - IGUIFont* font = getActiveFont(); + IGUIFont *font = getActiveFont(); const u32 line_count = (m_word_wrap || m_multiline) ? m_broken_text.size() : 1; @@ -982,10 +1056,14 @@ s32 GUIEditBoxWithScrollBar::getCursorPos(s32 x, s32 y) y = m_current_text_rect.LowerRightCorner.Y; // is it inside this region? - if (y >= m_current_text_rect.UpperLeftCorner.Y && y <= m_current_text_rect.LowerRightCorner.Y) { + if (y >= m_current_text_rect.UpperLeftCorner.Y && + y <= m_current_text_rect.LowerRightCorner.Y) { // we've found the clicked line - txt_line = (m_word_wrap || m_multiline) ? &m_broken_text[i] : &Text; - start_pos = (m_word_wrap || m_multiline) ? m_broken_text_positions[i] : 0; + txt_line = (m_word_wrap || m_multiline) ? &m_broken_text[i] + : &Text; + start_pos = (m_word_wrap || m_multiline) + ? m_broken_text_positions[i] + : 0; break; } } @@ -996,7 +1074,8 @@ s32 GUIEditBoxWithScrollBar::getCursorPos(s32 x, s32 y) if (!txt_line) return 0; - s32 idx = font->getCharacterFromPos(txt_line->c_str(), x - m_current_text_rect.UpperLeftCorner.X); + s32 idx = font->getCharacterFromPos( + txt_line->c_str(), x - m_current_text_rect.UpperLeftCorner.X); // click was on or left of the line if (idx != -1) @@ -1006,7 +1085,6 @@ s32 GUIEditBoxWithScrollBar::getCursorPos(s32 x, s32 y) return txt_line->size() + start_pos; } - //! Breaks the single text line. void GUIEditBoxWithScrollBar::breakText() { @@ -1016,7 +1094,7 @@ void GUIEditBoxWithScrollBar::breakText() m_broken_text.clear(); // need to reallocate :/ m_broken_text_positions.clear(); - IGUIFont* font = getActiveFont(); + IGUIFont *font = getActiveFont(); if (!font) return; @@ -1040,9 +1118,11 @@ void GUIEditBoxWithScrollBar::breakText() line_break = true; c = 0; if (Text[i + 1] == L'\n') { // Windows breaks - // TODO: I (Michael) think that we shouldn't change the text given by the user for whatever reason. - // Instead rework the cursor positioning to be able to handle this (but not in stable release - // branch as users might already expect this behavior). + // TODO: I (Michael) think that we shouldn't change the + // text given by the user for whatever reason. Instead + // rework the cursor positioning to be able to handle this + // (but not in stable release branch as users might + // already expect this behavior). Text.erase(i + 1); --size; if (m_cursor_pos > i) @@ -1060,11 +1140,13 @@ void GUIEditBoxWithScrollBar::breakText() if (c == L' ' || c == 0 || i == (size - 1)) { // here comes the next whitespace, look if // we can break the last word to the next line - // We also break whitespace, otherwise cursor would vanish beside the right border. + // We also break whitespace, otherwise cursor would vanish beside + // the right border. s32 whitelgth = font->getDimension(whitespace.c_str()).Width; s32 worldlgth = font->getDimension(word.c_str()).Width; - if (m_word_wrap && length + worldlgth + whitelgth > el_width && line.size() > 0) { + if (m_word_wrap && length + worldlgth + whitelgth > el_width && + line.size() > 0) { // break to next line length = worldlgth; m_broken_text.push_back(line); @@ -1081,7 +1163,6 @@ void GUIEditBoxWithScrollBar::breakText() word = L""; whitespace = L""; - if (c) whitespace += c; @@ -1121,7 +1202,7 @@ void GUIEditBoxWithScrollBar::setTextRect(s32 line) if (line < 0) return; - IGUIFont* font = getActiveFont(); + IGUIFont *font = getActiveFont(); if (!font) return; @@ -1141,8 +1222,10 @@ void GUIEditBoxWithScrollBar::setTextRect(s32 line) switch (m_halign) { case EGUIA_CENTER: // align to h centre - m_current_text_rect.UpperLeftCorner.X = (m_frame_rect.getWidth() / 2) - (d.Width / 2); - m_current_text_rect.LowerRightCorner.X = (m_frame_rect.getWidth() / 2) + (d.Width / 2); + m_current_text_rect.UpperLeftCorner.X = + (m_frame_rect.getWidth() / 2) - (d.Width / 2); + m_current_text_rect.LowerRightCorner.X = + (m_frame_rect.getWidth() / 2) + (d.Width / 2); break; case EGUIA_LOWERRIGHT: // align to right edge @@ -1153,35 +1236,36 @@ void GUIEditBoxWithScrollBar::setTextRect(s32 line) // align to left edge m_current_text_rect.UpperLeftCorner.X = 0; m_current_text_rect.LowerRightCorner.X = d.Width; - } switch (m_valign) { case EGUIA_CENTER: // align to v centre - m_current_text_rect.UpperLeftCorner.Y = - (m_frame_rect.getHeight() / 2) - (line_count*d.Height) / 2 + d.Height*line; + m_current_text_rect.UpperLeftCorner.Y = (m_frame_rect.getHeight() / 2) - + (line_count * d.Height) / 2 + + d.Height * line; break; case EGUIA_LOWERRIGHT: // align to bottom edge - m_current_text_rect.UpperLeftCorner.Y = - m_frame_rect.getHeight() - line_count*d.Height + d.Height*line; + m_current_text_rect.UpperLeftCorner.Y = m_frame_rect.getHeight() - + line_count * d.Height + + d.Height * line; break; default: // align to top edge - m_current_text_rect.UpperLeftCorner.Y = d.Height*line; + m_current_text_rect.UpperLeftCorner.Y = d.Height * line; break; } m_current_text_rect.UpperLeftCorner.X -= m_hscroll_pos; m_current_text_rect.LowerRightCorner.X -= m_hscroll_pos; m_current_text_rect.UpperLeftCorner.Y -= m_vscroll_pos; - m_current_text_rect.LowerRightCorner.Y = m_current_text_rect.UpperLeftCorner.Y + d.Height; + m_current_text_rect.LowerRightCorner.Y = + m_current_text_rect.UpperLeftCorner.Y + d.Height; m_current_text_rect += m_frame_rect.UpperLeftCorner; } - s32 GUIEditBoxWithScrollBar::getLineFromPos(s32 pos) { if (!m_word_wrap && !m_multiline) @@ -1196,31 +1280,36 @@ s32 GUIEditBoxWithScrollBar::getLineFromPos(s32 pos) return (s32)m_broken_text_positions.size() - 1; } - void GUIEditBoxWithScrollBar::inputChar(wchar_t c) { if (!isEnabled()) return; - if (c != 0) { + if (c != 0) { if (Text.size() < m_max || m_max == 0) { core::stringw s; if (m_mark_begin != m_mark_end) { // replace marked text - const s32 realmbgn = m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end; - const s32 realmend = m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin; + const s32 realmbgn = m_mark_begin < m_mark_end + ? m_mark_begin + : m_mark_end; + const s32 realmend = m_mark_begin < m_mark_end + ? m_mark_end + : m_mark_begin; s = Text.subString(0, realmbgn); s.append(c); - s.append(Text.subString(realmend, Text.size() - realmend)); + s.append(Text.subString( + realmend, Text.size() - realmend)); Text = s; m_cursor_pos = realmbgn + 1; } else { // add new character s = Text.subString(0, m_cursor_pos); s.append(c); - s.append(Text.subString(m_cursor_pos, Text.size() - m_cursor_pos)); + s.append(Text.subString(m_cursor_pos, + Text.size() - m_cursor_pos)); Text = s; ++m_cursor_pos; } @@ -1240,10 +1329,10 @@ void GUIEditBoxWithScrollBar::calculateScrollPos() if (!m_autoscroll) return; - IGUISkin* skin = Environment->getSkin(); + IGUISkin *skin = Environment->getSkin(); if (!skin) return; - IGUIFont* font = m_override_font ? m_override_font : skin->getFont(); + IGUIFont *font = m_override_font ? m_override_font : skin->getFont(); if (!font) return; @@ -1254,46 +1343,59 @@ void GUIEditBoxWithScrollBar::calculateScrollPos() const bool has_broken_text = m_multiline || m_word_wrap; // Check horizonal scrolling - // NOTE: Calculations different to vertical scrolling because setTextRect interprets VAlign relative to line but HAlign not relative to row + // NOTE: Calculations different to vertical scrolling because setTextRect + // interprets VAlign relative to line but HAlign not relative to row { // get cursor position - IGUIFont* font = getActiveFont(); + IGUIFont *font = getActiveFont(); if (!font) return; // get cursor area irr::u32 cursor_width = font->getDimension(L"_").Width; - core::stringw *txt_line = has_broken_text ? &m_broken_text[curs_line] : &Text; - s32 cpos = has_broken_text ? m_cursor_pos - m_broken_text_positions[curs_line] : m_cursor_pos; // column - s32 cstart = font->getDimension(txt_line->subString(0, cpos).c_str()).Width; // pixels from text-start + core::stringw *txt_line = + has_broken_text ? &m_broken_text[curs_line] : &Text; + s32 cpos = has_broken_text ? m_cursor_pos - m_broken_text_positions + [curs_line] + : m_cursor_pos; // column + s32 cstart = font->getDimension(txt_line->subString(0, cpos).c_str()) + .Width; // pixels from text-start s32 cend = cstart + cursor_width; s32 txt_width = font->getDimension(txt_line->c_str()).Width; if (txt_width < m_frame_rect.getWidth()) { - // TODO: Needs a clean left and right gap removal depending on HAlign, similar to vertical scrolling tests for top/bottom. - // This check just fixes the case where it was most noticable (text smaller than clipping area). + // TODO: Needs a clean left and right gap removal depending on + // HAlign, similar to vertical scrolling tests for top/bottom. + // This check just fixes the case where it was most noticable + // (text smaller than clipping area). m_hscroll_pos = 0; setTextRect(curs_line); } - if (m_current_text_rect.UpperLeftCorner.X + cstart < m_frame_rect.UpperLeftCorner.X) { + if (m_current_text_rect.UpperLeftCorner.X + cstart < + m_frame_rect.UpperLeftCorner.X) { // cursor to the left of the clipping area - m_hscroll_pos -= m_frame_rect.UpperLeftCorner.X - (m_current_text_rect.UpperLeftCorner.X + cstart); + m_hscroll_pos -= m_frame_rect.UpperLeftCorner.X - + (m_current_text_rect.UpperLeftCorner.X + cstart); setTextRect(curs_line); - // TODO: should show more characters to the left when we're scrolling left + // TODO: should show more characters to the left when we're + // scrolling left // and the cursor reaches the border. - } else if (m_current_text_rect.UpperLeftCorner.X + cend > m_frame_rect.LowerRightCorner.X) { + } else if (m_current_text_rect.UpperLeftCorner.X + cend > + m_frame_rect.LowerRightCorner.X) { // cursor to the right of the clipping area - m_hscroll_pos += (m_current_text_rect.UpperLeftCorner.X + cend) - m_frame_rect.LowerRightCorner.X; + m_hscroll_pos += (m_current_text_rect.UpperLeftCorner.X + cend) - + m_frame_rect.LowerRightCorner.X; setTextRect(curs_line); } } // calculate vertical scrolling if (has_broken_text) { - irr::u32 line_height = font->getDimension(L"A").Height + font->getKerningHeight(); + irr::u32 line_height = font->getDimension(L"A").Height + + font->getKerningHeight(); // only up to 1 line fits? if (line_height >= (irr::u32)m_frame_rect.getHeight()) { m_vscroll_pos = 0; @@ -1317,27 +1419,39 @@ void GUIEditBoxWithScrollBar::calculateScrollPos() } else { // First 2 checks are necessary when people delete lines setTextRect(0); - if (m_current_text_rect.UpperLeftCorner.Y > m_frame_rect.UpperLeftCorner.Y && m_valign != EGUIA_LOWERRIGHT) { + if (m_current_text_rect.UpperLeftCorner.Y > + m_frame_rect.UpperLeftCorner.Y && + m_valign != EGUIA_LOWERRIGHT) { // first line is leaving a gap on top m_vscroll_pos = 0; } else if (m_valign != EGUIA_UPPERLEFT) { - u32 lastLine = m_broken_text_positions.empty() ? 0 : m_broken_text_positions.size() - 1; + u32 lastLine = m_broken_text_positions.empty() + ? 0 + : m_broken_text_positions.size() - + 1; setTextRect(lastLine); - if (m_current_text_rect.LowerRightCorner.Y < m_frame_rect.LowerRightCorner.Y) - { + if (m_current_text_rect.LowerRightCorner.Y < + m_frame_rect.LowerRightCorner.Y) { // last line is leaving a gap on bottom - m_vscroll_pos -= m_frame_rect.LowerRightCorner.Y - m_current_text_rect.LowerRightCorner.Y; + m_vscroll_pos -= m_frame_rect.LowerRightCorner.Y - + m_current_text_rect + .LowerRightCorner + .Y; } } setTextRect(curs_line); - if (m_current_text_rect.UpperLeftCorner.Y < m_frame_rect.UpperLeftCorner.Y) { + if (m_current_text_rect.UpperLeftCorner.Y < + m_frame_rect.UpperLeftCorner.Y) { // text above valid area - m_vscroll_pos -= m_frame_rect.UpperLeftCorner.Y - m_current_text_rect.UpperLeftCorner.Y; + m_vscroll_pos -= m_frame_rect.UpperLeftCorner.Y - + m_current_text_rect.UpperLeftCorner.Y; setTextRect(curs_line); - } else if (m_current_text_rect.LowerRightCorner.Y > m_frame_rect.LowerRightCorner.Y){ + } else if (m_current_text_rect.LowerRightCorner.Y > + m_frame_rect.LowerRightCorner.Y) { // text below valid area - m_vscroll_pos += m_current_text_rect.LowerRightCorner.Y - m_frame_rect.LowerRightCorner.Y; + m_vscroll_pos += m_current_text_rect.LowerRightCorner.Y - + m_frame_rect.LowerRightCorner.Y; setTextRect(curs_line); } } @@ -1352,15 +1466,16 @@ void GUIEditBoxWithScrollBar::calculateFrameRect() { m_frame_rect = AbsoluteRect; - IGUISkin *skin = 0; if (Environment) skin = Environment->getSkin(); if (m_border && skin) { m_frame_rect.UpperLeftCorner.X += skin->getSize(EGDS_TEXT_DISTANCE_X) + 1; m_frame_rect.UpperLeftCorner.Y += skin->getSize(EGDS_TEXT_DISTANCE_Y) + 1; - m_frame_rect.LowerRightCorner.X -= skin->getSize(EGDS_TEXT_DISTANCE_X) + 1; - m_frame_rect.LowerRightCorner.Y -= skin->getSize(EGDS_TEXT_DISTANCE_Y) + 1; + m_frame_rect.LowerRightCorner.X -= + skin->getSize(EGDS_TEXT_DISTANCE_X) + 1; + m_frame_rect.LowerRightCorner.Y -= + skin->getSize(EGDS_TEXT_DISTANCE_Y) + 1; } updateVScrollBar(); @@ -1403,8 +1518,8 @@ void GUIEditBoxWithScrollBar::createVScrollBar() irr::core::rect scrollbarrect = m_frame_rect; scrollbarrect.UpperLeftCorner.X += m_frame_rect.getWidth() - m_scrollbar_width; - m_vscrollbar = new GUIScrollBar(Environment, getParent(), -1, - scrollbarrect, false, true); + m_vscrollbar = new GUIScrollBar( + Environment, getParent(), -1, scrollbarrect, false, true); m_vscrollbar->setVisible(false); m_vscrollbar->setSmallStep(1); @@ -1436,7 +1551,7 @@ void GUIEditBoxWithScrollBar::updateVScrollBar() } // check if a vertical scrollbar is needed ? - if (getTextDimension().Height > (u32) m_frame_rect.getHeight()) { + if (getTextDimension().Height > (u32)m_frame_rect.getHeight()) { m_frame_rect.LowerRightCorner.X -= m_scrollbar_width; s32 scrollymax = getTextDimension().Height - m_frame_rect.getHeight(); @@ -1449,8 +1564,7 @@ void GUIEditBoxWithScrollBar::updateVScrollBar() m_vscrollbar->setVisible(true); } } else { - if (m_vscrollbar->isVisible()) - { + if (m_vscrollbar->isVisible()) { m_vscrollbar->setVisible(false); m_vscroll_pos = 0; m_vscrollbar->setPos(0); @@ -1458,7 +1572,6 @@ void GUIEditBoxWithScrollBar::updateVScrollBar() m_vscrollbar->setPageSize(s32(getTextDimension().Height)); } } - } //! set true if this editbox is writable @@ -1475,7 +1588,8 @@ void GUIEditBoxWithScrollBar::setBackgroundColor(const video::SColor &bg_color) } //! Writes attributes of the element. -void GUIEditBoxWithScrollBar::serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options = 0) const +void GUIEditBoxWithScrollBar::serializeAttributes( + io::IAttributes *out, io::SAttributeReadWriteOptions *options = 0) const { // IGUIEditBox::serializeAttributes(out,options); @@ -1499,9 +1613,9 @@ void GUIEditBoxWithScrollBar::serializeAttributes(io::IAttributes* out, io::SAtt IGUIEditBox::serializeAttributes(out, options); } - //! Reads attributes of the element -void GUIEditBoxWithScrollBar::deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options = 0) +void GUIEditBoxWithScrollBar::deserializeAttributes( + io::IAttributes *in, io::SAttributeReadWriteOptions *options = 0) { IGUIEditBox::deserializeAttributes(in, options); @@ -1520,16 +1634,34 @@ void GUIEditBoxWithScrollBar::deserializeAttributes(io::IAttributes* in, io::SAt else setPasswordBox(in->getAttributeAsBool("PasswordBox"), ch[0]); - setTextAlignment((EGUI_ALIGNMENT)in->getAttributeAsEnumeration("HTextAlign", GUIAlignmentNames), - (EGUI_ALIGNMENT)in->getAttributeAsEnumeration("VTextAlign", GUIAlignmentNames)); + setTextAlignment((EGUI_ALIGNMENT)in->getAttributeAsEnumeration( + "HTextAlign", GUIAlignmentNames), + (EGUI_ALIGNMENT)in->getAttributeAsEnumeration( + "VTextAlign", GUIAlignmentNames)); // setOverrideFont(in->getAttributeAsFont("OverrideFont")); setWritable(in->getAttributeAsBool("Writable")); } -bool GUIEditBoxWithScrollBar::isDrawBackgroundEnabled() const { return false; } -bool GUIEditBoxWithScrollBar::isDrawBorderEnabled() const { return false; } -void GUIEditBoxWithScrollBar::setCursorChar(const wchar_t cursorChar) { } -wchar_t GUIEditBoxWithScrollBar::getCursorChar() const { return '|'; } -void GUIEditBoxWithScrollBar::setCursorBlinkTime(irr::u32 timeMs) { } -irr::u32 GUIEditBoxWithScrollBar::getCursorBlinkTime() const { return 500; } +bool GUIEditBoxWithScrollBar::isDrawBackgroundEnabled() const +{ + return false; +} +bool GUIEditBoxWithScrollBar::isDrawBorderEnabled() const +{ + return false; +} +void GUIEditBoxWithScrollBar::setCursorChar(const wchar_t cursorChar) +{ +} +wchar_t GUIEditBoxWithScrollBar::getCursorChar() const +{ + return '|'; +} +void GUIEditBoxWithScrollBar::setCursorBlinkTime(irr::u32 timeMs) +{ +} +irr::u32 GUIEditBoxWithScrollBar::getCursorBlinkTime() const +{ + return 500; +} diff --git a/src/gui/guiEditBoxWithScrollbar.h b/src/gui/guiEditBoxWithScrollbar.h index 77538e2f7..ff98fe4cb 100644 --- a/src/gui/guiEditBoxWithScrollbar.h +++ b/src/gui/guiEditBoxWithScrollbar.h @@ -16,26 +16,26 @@ using namespace irr::gui; class GUIEditBoxWithScrollBar : public IGUIEditBox { public: - //! constructor - GUIEditBoxWithScrollBar(const wchar_t* text, bool border, IGUIEnvironment* environment, - IGUIElement* parent, s32 id, const core::rect& rectangle, - bool writable = true, bool has_vscrollbar = true); + GUIEditBoxWithScrollBar(const wchar_t *text, bool border, + IGUIEnvironment *environment, IGUIElement *parent, s32 id, + const core::rect &rectangle, bool writable = true, + bool has_vscrollbar = true); //! destructor virtual ~GUIEditBoxWithScrollBar(); //! Sets another skin independent font. - virtual void setOverrideFont(IGUIFont* font = 0); + virtual void setOverrideFont(IGUIFont *font = 0); //! Gets the override font (if any) /** \return The override font (may be 0) */ - virtual IGUIFont* getOverrideFont() const; + virtual IGUIFont *getOverrideFont() const; //! Get the font which is used right now for drawing /** Currently this is the override font when one is set and the font of the active skin otherwise */ - virtual IGUIFont* getActiveFont() const; + virtual IGUIFont *getActiveFont() const; //! Sets another color for the text. virtual void setOverrideColor(video::SColor color); @@ -74,7 +74,8 @@ public: virtual bool isMultiLineEnabled() const; //! Enables or disables automatic scrolling with cursor position - //! \param enable: If set to true, the text will move around with the cursor position + //! \param enable: If set to true, the text will move around with the cursor + //! position virtual void setAutoScroll(bool enable); //! Checks to see if automatic scrolling is enabled @@ -89,13 +90,13 @@ public: virtual void setTextAlignment(EGUI_ALIGNMENT horizontal, EGUI_ALIGNMENT vertical); //! called if an event happened. - virtual bool OnEvent(const SEvent& event); + virtual bool OnEvent(const SEvent &event); //! draws the element and its children virtual void draw(); //! Sets the new caption of this element. - virtual void setText(const wchar_t* text); + virtual void setText(const wchar_t *text); //! Sets the maximum amount of characters which may be entered in the box. //! \param max: Maximum amount of characters. If 0, the character amount is @@ -123,10 +124,12 @@ public: virtual void setBackgroundColor(const video::SColor &bg_color); //! Writes attributes of the element. - virtual void serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options) const; + virtual void serializeAttributes(io::IAttributes *out, + io::SAttributeReadWriteOptions *options) const; //! Reads attributes of the element - virtual void deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options); + virtual void deserializeAttributes( + io::IAttributes *in, io::SAttributeReadWriteOptions *options); virtual bool isDrawBackgroundEnabled() const; virtual bool isDrawBorderEnabled() const; @@ -157,8 +160,8 @@ protected: //! update the vertical scrollBar (visibilty & position) void updateVScrollBar(); - bool processKey(const SEvent& event); - bool processMouse(const SEvent& event); + bool processKey(const SEvent &event); + bool processMouse(const SEvent &event); s32 getCursorPos(s32 x, s32 y); bool m_mouse_marking; @@ -170,7 +173,7 @@ protected: video::SColor m_override_color; gui::IGUIFont *m_override_font, *m_last_break_font; - IOSOperator* m_operator; + IOSOperator *m_operator; u32 m_blink_start_time; s32 m_cursor_pos; @@ -194,6 +197,4 @@ protected: video::SColor m_bg_color; }; - #endif // GUIEDITBOXWITHSCROLLBAR_HEADER - diff --git a/src/gui/guiEngine.cpp b/src/gui/guiEngine.cpp index b40707d01..2e1bc7fed 100644 --- a/src/gui/guiEngine.cpp +++ b/src/gui/guiEngine.cpp @@ -43,7 +43,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "client/tile.h" #endif - /******************************************************************************/ void TextDestGuiEngine::gotText(const StringMap &fields) { @@ -99,84 +98,72 @@ video::ITexture *MenuTextureSource::getTexture(const std::string &name, u32 *id) /** MenuMusicFetcher */ /******************************************************************************/ void MenuMusicFetcher::fetchSounds(const std::string &name, - std::set &dst_paths, - std::set &dst_datas) + std::set &dst_paths, std::set &dst_datas) { - if(m_fetched.count(name)) + if (m_fetched.count(name)) return; m_fetched.insert(name); std::string base; base = porting::path_share + DIR_DELIM + "sounds"; dst_paths.insert(base + DIR_DELIM + name + ".ogg"); int i; - for(i=0; i<10; i++) - dst_paths.insert(base + DIR_DELIM + name + "."+itos(i)+".ogg"); + for (i = 0; i < 10; i++) + dst_paths.insert(base + DIR_DELIM + name + "." + itos(i) + ".ogg"); base = porting::path_user + DIR_DELIM + "sounds"; dst_paths.insert(base + DIR_DELIM + name + ".ogg"); - for(i=0; i<10; i++) - dst_paths.insert(base + DIR_DELIM + name + "."+itos(i)+".ogg"); + for (i = 0; i < 10; i++) + dst_paths.insert(base + DIR_DELIM + name + "." + itos(i) + ".ogg"); } /******************************************************************************/ /** GUIEngine */ /******************************************************************************/ -GUIEngine::GUIEngine(JoystickController *joystick, - gui::IGUIElement *parent, - IMenuManager *menumgr, - MainMenuData *data, - bool &kill) : - m_parent(parent), - m_menumanager(menumgr), - m_smgr(RenderingEngine::get_scene_manager()), - m_data(data), - m_kill(kill) +GUIEngine::GUIEngine(JoystickController *joystick, gui::IGUIElement *parent, + IMenuManager *menumgr, MainMenuData *data, bool &kill) : + m_parent(parent), + m_menumanager(menumgr), m_smgr(RenderingEngine::get_scene_manager()), + m_data(data), m_kill(kill) { - //initialize texture pointers + // initialize texture pointers for (image_definition &texture : m_textures) { texture.texture = NULL; } // is deleted by guiformspec! m_buttonhandler = new TextDestGuiEngine(this); - //create texture source + // create texture source m_texture_source = new MenuTextureSource(RenderingEngine::get_video_driver()); - //create soundmanager + // create soundmanager MenuMusicFetcher soundfetcher; #if USE_SOUND if (g_settings->getBool("enable_sound") && g_sound_manager_singleton.get()) - m_sound_manager = createOpenALSoundManager(g_sound_manager_singleton.get(), &soundfetcher); + m_sound_manager = createOpenALSoundManager( + g_sound_manager_singleton.get(), &soundfetcher); #endif if (!m_sound_manager) m_sound_manager = &dummySoundManager; - //create topleft header + // create topleft header m_toplefttext = L""; core::rect rect(0, 0, g_fontengine->getTextWidth(m_toplefttext.c_str()), - g_fontengine->getTextHeight()); + g_fontengine->getTextHeight()); rect += v2s32(4, 0); m_irr_toplefttext = gui::StaticText::add(RenderingEngine::get_gui_env(), m_toplefttext, rect, false, true, 0, -1); - //create formspecsource + // create formspecsource m_formspecgui = new FormspecFormSource(""); /* Create menu */ - m_menu = new GUIFormSpecMenu(joystick, - m_parent, - -1, - m_menumanager, - NULL /* &client */, - m_texture_source, - m_formspecgui, - m_buttonhandler, - "", - false); + m_menu = new GUIFormSpecMenu(joystick, m_parent, -1, m_menumanager, + NULL /* &client */, m_texture_source, m_formspecgui, + m_buttonhandler, "", false); m_menu->allowClose(false); - m_menu->lockSize(true,v2u32(800,600)); + m_menu->lockSize(true, v2u32(800, 600)); // Initialize scripting @@ -210,18 +197,20 @@ bool GUIEngine::loadMainMenuScript() // Set main menu path (for core.get_mainmenu_path()) m_scriptdir = g_settings->get("main_menu_path"); if (m_scriptdir.empty()) { - m_scriptdir = porting::path_share + DIR_DELIM + "builtin" + DIR_DELIM + "mainmenu"; + m_scriptdir = porting::path_share + DIR_DELIM + "builtin" + DIR_DELIM + + "mainmenu"; } // Load builtin (which will load the main menu script) - std::string script = porting::path_share + DIR_DELIM "builtin" + DIR_DELIM "init.lua"; + std::string script = + porting::path_share + DIR_DELIM "builtin" + DIR_DELIM "init.lua"; try { m_script->loadScript(script); // Menu script loaded return true; } catch (const ModError &e) { - errorstream << "GUIEngine: execution of menu script failed: " - << e.what() << std::endl; + errorstream << "GUIEngine: execution of menu script failed: " << e.what() + << std::endl; } return false; @@ -238,8 +227,8 @@ void GUIEngine::run() unsigned int text_height = g_fontengine->getTextHeight(); - irr::core::dimension2d previous_screen_size(g_settings->getU16("screen_w"), - g_settings->getU16("screen_h")); + irr::core::dimension2d previous_screen_size( + g_settings->getU16("screen_w"), g_settings->getU16("screen_h")); static const video::SColor sky_color(255, 140, 186, 250); @@ -262,19 +251,20 @@ void GUIEngine::run() while (RenderingEngine::run() && (!m_startgame) && (!m_kill)) { const irr::core::dimension2d ¤t_screen_size = - RenderingEngine::get_video_driver()->getScreenSize(); + RenderingEngine::get_video_driver()->getScreenSize(); // Verify if window size has changed and save it if it's the case // Ensure evaluating settings->getBool after verifying screensize // First condition is cheaper if (previous_screen_size != current_screen_size && - current_screen_size != irr::core::dimension2d(0,0) && + current_screen_size != + irr::core::dimension2d(0, 0) && g_settings->getBool("autosave_screensize")) { g_settings->setU16("screen_w", current_screen_size.Width); g_settings->setU16("screen_h", current_screen_size.Height); previous_screen_size = current_screen_size; } - //check if we need to update the "upper left corner"-text + // check if we need to update the "upper left corner"-text if (text_height != g_fontengine->getTextHeight()) { updateTopLeftTextSize(); text_height = g_fontengine->getTextHeight(); @@ -282,12 +272,10 @@ void GUIEngine::run() driver->beginScene(true, true, sky_color); - if (m_clouds_enabled) - { + if (m_clouds_enabled) { cloudPreProcess(); drawOverlay(driver); - } - else + } else drawBackground(driver); drawHeader(driver); @@ -313,20 +301,21 @@ void GUIEngine::run() /******************************************************************************/ GUIEngine::~GUIEngine() { - if (m_sound_manager != &dummySoundManager){ + if (m_sound_manager != &dummySoundManager) { delete m_sound_manager; m_sound_manager = NULL; } - infostream<<"GUIEngine: Deinitializing scripting"<setText(L""); - //clean up texture pointers + // clean up texture pointers for (image_definition &texture : m_textures) { if (texture.texture) - RenderingEngine::get_video_driver()->removeTexture(texture.texture); + RenderingEngine::get_video_driver()->removeTexture( + texture.texture); } delete m_texture_source; @@ -340,10 +329,9 @@ void GUIEngine::cloudInit() { m_cloud.clouds = new Clouds(m_smgr, -1, rand()); m_cloud.clouds->setHeight(100.0f); - m_cloud.clouds->update(v3f(0, 0, 0), video::SColor(255,240,240,255)); + m_cloud.clouds->update(v3f(0, 0, 0), video::SColor(255, 240, 240, 255)); - m_cloud.camera = m_smgr->addCameraSceneNode(0, - v3f(0,0,0), v3f(0, 60, 100)); + m_cloud.camera = m_smgr->addCameraSceneNode(0, v3f(0, 0, 0), v3f(0, 60, 100)); m_cloud.camera->setFarValue(10000); m_cloud.lasttime = RenderingEngine::get_timer_time(); @@ -354,14 +342,14 @@ void GUIEngine::cloudPreProcess() { u32 time = RenderingEngine::get_timer_time(); - if(time > m_cloud.lasttime) + if (time > m_cloud.lasttime) m_cloud.dtime = (time - m_cloud.lasttime) / 1000.0; else m_cloud.dtime = 0; m_cloud.lasttime = time; - m_cloud.clouds->step(m_cloud.dtime*3); + m_cloud.clouds->step(m_cloud.dtime * 3); m_cloud.clouds->render(); m_smgr->drawAll(); } @@ -375,13 +363,13 @@ void GUIEngine::cloudPostProcess() // not using getRealTime is necessary for wine u32 time = RenderingEngine::get_timer_time(); - if(time > m_cloud.lasttime) + if (time > m_cloud.lasttime) busytime_u32 = time - m_cloud.lasttime; else busytime_u32 = 0; // FPS limiter - u32 frametime_min = 1000./fps_max; + u32 frametime_min = 1000. / fps_max; if (busytime_u32 < frametime_min) { u32 sleeptime = frametime_min - busytime_u32; @@ -397,17 +385,16 @@ void GUIEngine::setFormspecPrepend(const std::string &fs) } } - /******************************************************************************/ void GUIEngine::drawBackground(video::IVideoDriver *driver) { v2u32 screensize = driver->getScreenSize(); - video::ITexture* texture = m_textures[TEX_LAYER_BACKGROUND].texture; + video::ITexture *texture = m_textures[TEX_LAYER_BACKGROUND].texture; /* If no texture, draw background of solid color */ - if(!texture){ - video::SColor color(255,80,58,37); + if (!texture) { + video::SColor color(255, 80, 58, 37); core::rect rect(0, 0, screensize.X, screensize.Y); driver->draw2DRectangle(color, rect, NULL); return; @@ -415,19 +402,19 @@ void GUIEngine::drawBackground(video::IVideoDriver *driver) v2u32 sourcesize = texture->getOriginalSize(); - if (m_textures[TEX_LAYER_BACKGROUND].tile) - { - v2u32 tilesize( - MYMAX(sourcesize.X,m_textures[TEX_LAYER_BACKGROUND].minsize), - MYMAX(sourcesize.Y,m_textures[TEX_LAYER_BACKGROUND].minsize)); - for (unsigned int x = 0; x < screensize.X; x += tilesize.X ) - { - for (unsigned int y = 0; y < screensize.Y; y += tilesize.Y ) - { + if (m_textures[TEX_LAYER_BACKGROUND].tile) { + v2u32 tilesize(MYMAX(sourcesize.X, + m_textures[TEX_LAYER_BACKGROUND].minsize), + MYMAX(sourcesize.Y, m_textures[TEX_LAYER_BACKGROUND] + .minsize)); + for (unsigned int x = 0; x < screensize.X; x += tilesize.X) { + for (unsigned int y = 0; y < screensize.Y; y += tilesize.Y) { draw2DImageFilterScaled(driver, texture, - core::rect(x, y, x+tilesize.X, y+tilesize.Y), - core::rect(0, 0, sourcesize.X, sourcesize.Y), - NULL, NULL, true); + core::rect(x, y, x + tilesize.X, + y + tilesize.Y), + core::rect(0, 0, sourcesize.X, + sourcesize.Y), + NULL, NULL, true); } } return; @@ -435,9 +422,9 @@ void GUIEngine::drawBackground(video::IVideoDriver *driver) /* Draw background texture */ draw2DImageFilterScaled(driver, texture, - core::rect(0, 0, screensize.X, screensize.Y), - core::rect(0, 0, sourcesize.X, sourcesize.Y), - NULL, NULL, true); + core::rect(0, 0, screensize.X, screensize.Y), + core::rect(0, 0, sourcesize.X, sourcesize.Y), NULL, NULL, + true); } /******************************************************************************/ @@ -445,18 +432,18 @@ void GUIEngine::drawOverlay(video::IVideoDriver *driver) { v2u32 screensize = driver->getScreenSize(); - video::ITexture* texture = m_textures[TEX_LAYER_OVERLAY].texture; + video::ITexture *texture = m_textures[TEX_LAYER_OVERLAY].texture; /* If no texture, draw nothing */ - if(!texture) + if (!texture) return; /* Draw background texture */ v2u32 sourcesize = texture->getOriginalSize(); draw2DImageFilterScaled(driver, texture, - core::rect(0, 0, screensize.X, screensize.Y), - core::rect(0, 0, sourcesize.X, sourcesize.Y), - NULL, NULL, true); + core::rect(0, 0, screensize.X, screensize.Y), + core::rect(0, 0, sourcesize.X, sourcesize.Y), NULL, NULL, + true); } /******************************************************************************/ @@ -464,32 +451,33 @@ void GUIEngine::drawHeader(video::IVideoDriver *driver) { core::dimension2d screensize = driver->getScreenSize(); - video::ITexture* texture = m_textures[TEX_LAYER_HEADER].texture; + video::ITexture *texture = m_textures[TEX_LAYER_HEADER].texture; /* If no texture, draw nothing */ - if(!texture) + if (!texture) return; f32 mult = (((f32)screensize.Width / 2.0)) / - ((f32)texture->getOriginalSize().Width); + ((f32)texture->getOriginalSize().Width); v2s32 splashsize(((f32)texture->getOriginalSize().Width) * mult, ((f32)texture->getOriginalSize().Height) * mult); // Don't draw the header if there isn't enough room - s32 free_space = (((s32)screensize.Height)-320)/2; + s32 free_space = (((s32)screensize.Height) - 320) / 2; if (free_space > splashsize.Y) { core::rect splashrect(0, 0, splashsize.X, splashsize.Y); - splashrect += v2s32((screensize.Width/2)-(splashsize.X/2), - ((free_space/2)-splashsize.Y/2)+10); + splashrect += v2s32((screensize.Width / 2) - (splashsize.X / 2), + ((free_space / 2) - splashsize.Y / 2) + 10); - video::SColor bgcolor(255,50,50,50); + video::SColor bgcolor(255, 50, 50, 50); - draw2DImageFilterScaled(driver, texture, splashrect, - core::rect(core::position2d(0,0), - core::dimension2di(texture->getOriginalSize())), - NULL, NULL, true); + draw2DImageFilterScaled(driver, texture, splashrect, + core::rect(core::position2d(0, 0), + core::dimension2di( + texture->getOriginalSize())), + NULL, NULL, true); } } @@ -498,30 +486,30 @@ void GUIEngine::drawFooter(video::IVideoDriver *driver) { core::dimension2d screensize = driver->getScreenSize(); - video::ITexture* texture = m_textures[TEX_LAYER_FOOTER].texture; + video::ITexture *texture = m_textures[TEX_LAYER_FOOTER].texture; /* If no texture, draw nothing */ - if(!texture) + if (!texture) return; - f32 mult = (((f32)screensize.Width)) / - ((f32)texture->getOriginalSize().Width); + f32 mult = (((f32)screensize.Width)) / ((f32)texture->getOriginalSize().Width); v2s32 footersize(((f32)texture->getOriginalSize().Width) * mult, ((f32)texture->getOriginalSize().Height) * mult); // Don't draw the footer if there isn't enough room - s32 free_space = (((s32)screensize.Height)-320)/2; + s32 free_space = (((s32)screensize.Height) - 320) / 2; if (free_space > footersize.Y) { - core::rect rect(0,0,footersize.X,footersize.Y); - rect += v2s32(screensize.Width/2,screensize.Height-footersize.Y); - rect -= v2s32(footersize.X/2, 0); + core::rect rect(0, 0, footersize.X, footersize.Y); + rect += v2s32(screensize.Width / 2, screensize.Height - footersize.Y); + rect -= v2s32(footersize.X / 2, 0); draw2DImageFilterScaled(driver, texture, rect, - core::rect(core::position2d(0,0), - core::dimension2di(texture->getOriginalSize())), - NULL, NULL, true); + core::rect(core::position2d(0, 0), + core::dimension2di( + texture->getOriginalSize())), + NULL, NULL, true); } } @@ -541,7 +529,7 @@ bool GUIEngine::setTexture(texture_layer layer, const std::string &texturepath, } m_textures[layer].texture = driver->getTexture(texturepath.c_str()); - m_textures[layer].tile = tile_image; + m_textures[layer].tile = tile_image; m_textures[layer].minsize = minsize; if (!m_textures[layer].texture) { @@ -592,7 +580,7 @@ void GUIEngine::setTopleftText(const std::string &text) void GUIEngine::updateTopLeftTextSize() { core::rect rect(0, 0, g_fontengine->getTextWidth(m_toplefttext.c_str()), - g_fontengine->getTextHeight()); + g_fontengine->getTextHeight()); rect += v2s32(4, 0); m_irr_toplefttext->remove(); @@ -614,8 +602,8 @@ void GUIEngine::stopSound(s32 handle) } /******************************************************************************/ -unsigned int GUIEngine::queueAsync(const std::string &serialized_func, - const std::string &serialized_params) +unsigned int GUIEngine::queueAsync( + const std::string &serialized_func, const std::string &serialized_params) { return m_script->queueAsync(serialized_func, serialized_params); } diff --git a/src/gui/guiEngine.h b/src/gui/guiEngine.h index f9ad0fb0a..b94bbef12 100644 --- a/src/gui/guiEngine.h +++ b/src/gui/guiEngine.h @@ -32,7 +32,8 @@ with this program; if not, write to the Free Software Foundation, Inc., /* Typedefs and macros */ /******************************************************************************/ /** texture layer ids */ -typedef enum { +typedef enum +{ TEX_LAYER_BACKGROUND = 0, TEX_LAYER_OVERLAY, TEX_LAYER_HEADER, @@ -40,10 +41,11 @@ typedef enum { TEX_LAYER_MAX } texture_layer; -typedef struct { +typedef struct +{ video::ITexture *texture = nullptr; - bool tile; - unsigned int minsize; + bool tile; + unsigned int minsize; } image_definition; /******************************************************************************/ @@ -66,7 +68,7 @@ public: * default constructor * @param engine the engine data is transmitted for further processing */ - TextDestGuiEngine(GUIEngine* engine) : m_engine(engine) {}; + TextDestGuiEngine(GUIEngine *engine) : m_engine(engine){}; /** * receive fields transmitted by guiFormSpecMenu @@ -93,7 +95,7 @@ public: * default constructor * @param driver the video driver to load textures from */ - MenuTextureSource(video::IVideoDriver *driver) : m_driver(driver) {}; + MenuTextureSource(video::IVideoDriver *driver) : m_driver(driver){}; /** * destructor, removes all loaded textures @@ -115,7 +117,7 @@ private: }; /** GUIEngine specific implementation of OnDemandSoundFetcher */ -class MenuMusicFetcher: public OnDemandSoundFetcher +class MenuMusicFetcher : public OnDemandSoundFetcher { public: /** @@ -124,8 +126,7 @@ public: * @param dst_paths receives possible paths to sound files * @param dst_datas receives binary sound data (not used here) */ - void fetchSounds(const std::string &name, - std::set &dst_paths, + void fetchSounds(const std::string &name, std::set &dst_paths, std::set &dst_datas); private: @@ -134,7 +135,8 @@ private: }; /** implementation of main menu based uppon formspecs */ -class GUIEngine { +class GUIEngine +{ /** grant ModApiMainMenu access to private members */ friend class ModApiMainMenu; friend class ModApiSound; @@ -148,11 +150,8 @@ public: * @param smgr scene manager to add scene elements to * @param data struct to transfer data to main game handling */ - GUIEngine(JoystickController *joystick, - gui::IGUIElement *parent, - IMenuManager *menumgr, - MainMenuData *data, - bool &kill); + GUIEngine(JoystickController *joystick, gui::IGUIElement *parent, + IMenuManager *menumgr, MainMenuData *data, bool &kill); /** default destructor */ virtual ~GUIEngine(); @@ -160,25 +159,18 @@ public: /** * return MainMenuScripting interface */ - MainMenuScripting *getScriptIface() - { - return m_script; - } + MainMenuScripting *getScriptIface() { return m_script; } /** * return dir of current menuscript */ - std::string getScriptDir() - { - return m_scriptdir; - } + std::string getScriptDir() { return m_scriptdir; } /** pass async callback to scriptengine **/ unsigned int queueAsync(const std::string &serialized_fct, const std::string &serialized_params); private: - /** find and run the main menu script */ bool loadMainMenuScript(); @@ -189,36 +181,36 @@ private: void updateTopLeftTextSize(); /** parent gui element */ - gui::IGUIElement *m_parent = nullptr; + gui::IGUIElement *m_parent = nullptr; /** manager to add menus to */ - IMenuManager *m_menumanager = nullptr; + IMenuManager *m_menumanager = nullptr; /** scene manager to add scene elements to */ - scene::ISceneManager *m_smgr = nullptr; + scene::ISceneManager *m_smgr = nullptr; /** pointer to data beeing transfered back to main game handling */ - MainMenuData *m_data = nullptr; + MainMenuData *m_data = nullptr; /** pointer to texture source */ - ISimpleTextureSource *m_texture_source = nullptr; + ISimpleTextureSource *m_texture_source = nullptr; /** pointer to soundmanager*/ - ISoundManager *m_sound_manager = nullptr; + ISoundManager *m_sound_manager = nullptr; /** representation of form source to be used in mainmenu formspec */ - FormspecFormSource *m_formspecgui = nullptr; + FormspecFormSource *m_formspecgui = nullptr; /** formspec input receiver */ - TextDestGuiEngine *m_buttonhandler = nullptr; + TextDestGuiEngine *m_buttonhandler = nullptr; /** the formspec menu */ - GUIFormSpecMenu *m_menu = nullptr; + GUIFormSpecMenu *m_menu = nullptr; /** reference to kill variable managed by SIGINT handler */ - bool &m_kill; + bool &m_kill; /** variable used to abort menu and return back to main game handling */ - bool m_startgame = false; + bool m_startgame = false; /** scripting interface */ - MainMenuScripting *m_script = nullptr; + MainMenuScripting *m_script = nullptr; /** script basefolder */ - std::string m_scriptdir = ""; + std::string m_scriptdir = ""; void setFormspecPrepend(const std::string &fs); @@ -280,11 +272,12 @@ private: void cloudPostProcess(); /** internam data required for drawing clouds */ - struct clouddata { + struct clouddata + { /** delta time since last cloud processing */ - f32 dtime; + f32 dtime; /** absolute time of last cloud processing */ - u32 lasttime; + u32 lasttime; /** pointer to cloud class */ Clouds *clouds = nullptr; /** camera required for drawing clouds */ @@ -292,14 +285,12 @@ private: }; /** is drawing of clouds enabled atm */ - bool m_clouds_enabled = true; + bool m_clouds_enabled = true; /** data used to draw clouds */ - clouddata m_cloud; + clouddata m_cloud; /** start playing a sound and return handle */ s32 playSound(const SimpleSoundSpec &spec, bool looped); /** stop playing a sound started with playSound() */ void stopSound(s32 handle); - - }; diff --git a/src/gui/guiFormSpecMenu.cpp b/src/gui/guiFormSpecMenu.cpp index 601c5c18e..5013ffe14 100644 --- a/src/gui/guiFormSpecMenu.cpp +++ b/src/gui/guiFormSpecMenu.cpp @@ -17,7 +17,6 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ - #include #include #include @@ -38,7 +37,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "client/renderingengine.h" #include "log.h" #include "client/tile.h" // ITextureSource -#include "client/hud.h" // drawItemStack +#include "client/hud.h" // drawItemStack #include "filesys.h" #include "gettime.h" #include "gettext.h" @@ -66,18 +65,18 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "intlGUIEditBox.h" #include "guiHyperText.h" -#define MY_CHECKPOS(a,b) \ - if (v_pos.size() != 2) { \ - errorstream<< "Invalid pos for element " << a << "specified: \"" \ - << parts[b] << "\"" << std::endl; \ - return; \ +#define MY_CHECKPOS(a, b) \ + if (v_pos.size() != 2) { \ + errorstream << "Invalid pos for element " << a << "specified: \"" \ + << parts[b] << "\"" << std::endl; \ + return; \ } -#define MY_CHECKGEOM(a,b) \ - if (v_geom.size() != 2) { \ - errorstream<< "Invalid geometry for element " << a << \ - "specified: \"" << parts[b] << "\"" << std::endl; \ - return; \ +#define MY_CHECKGEOM(a, b) \ + if (v_geom.size() != 2) { \ + errorstream << "Invalid geometry for element " << a << "specified: \"" \ + << parts[b] << "\"" << std::endl; \ + return; \ } /* GUIFormSpecMenu @@ -89,21 +88,18 @@ static unsigned int font_line_height(gui::IGUIFont *font) inline u32 clamp_u8(s32 value) { - return (u32) MYMIN(MYMAX(value, 0), 255); + return (u32)MYMIN(MYMAX(value, 0), 255); } -GUIFormSpecMenu::GUIFormSpecMenu(JoystickController *joystick, - gui::IGUIElement *parent, s32 id, IMenuManager *menumgr, - Client *client, ISimpleTextureSource *tsrc, IFormSource *fsrc, TextDest *tdst, - const std::string &formspecPrepend, bool remap_dbl_click): - GUIModalMenu(RenderingEngine::get_gui_env(), parent, id, menumgr, remap_dbl_click), - m_invmgr(client), - m_tsrc(tsrc), - m_client(client), - m_formspec_prepend(formspecPrepend), - m_form_src(fsrc), - m_text_dst(tdst), - m_joystick(joystick) +GUIFormSpecMenu::GUIFormSpecMenu(JoystickController *joystick, gui::IGUIElement *parent, + s32 id, IMenuManager *menumgr, Client *client, ISimpleTextureSource *tsrc, + IFormSource *fsrc, TextDest *tdst, const std::string &formspecPrepend, + bool remap_dbl_click) : + GUIModalMenu(RenderingEngine::get_gui_env(), parent, id, menumgr, + remap_dbl_click), + m_invmgr(client), m_tsrc(tsrc), m_client(client), + m_formspec_prepend(formspecPrepend), m_form_src(fsrc), m_text_dst(tdst), + m_joystick(joystick) { current_keys_pending.key_down = false; current_keys_pending.key_up = false; @@ -141,12 +137,13 @@ GUIFormSpecMenu::~GUIFormSpecMenu() } void GUIFormSpecMenu::create(GUIFormSpecMenu *&cur_formspec, Client *client, - JoystickController *joystick, IFormSource *fs_src, TextDest *txt_dest, - const std::string &formspecPrepend) + JoystickController *joystick, IFormSource *fs_src, TextDest *txt_dest, + const std::string &formspecPrepend) { if (cur_formspec == nullptr) { cur_formspec = new GUIFormSpecMenu(joystick, guiroot, -1, &g_menumgr, - client, client->getTextureSource(), fs_src, txt_dest, formspecPrepend); + client, client->getTextureSource(), fs_src, txt_dest, + formspecPrepend); cur_formspec->doPause = false; /* @@ -166,7 +163,7 @@ void GUIFormSpecMenu::create(GUIFormSpecMenu *&cur_formspec, Client *client, void GUIFormSpecMenu::removeChildren() { - const core::list &children = getChildren(); + const core::list &children = getChildren(); while (!children.empty()) { (*children.getLast())->remove(); @@ -189,10 +186,10 @@ void GUIFormSpecMenu::setInitialFocus() // 5. first focusable (not statictext, not tabheader) // 6. first child element - core::list children = getChildren(); + core::list children = getChildren(); // in case "children" contains any NULL elements, remove them - for (core::list::Iterator it = children.begin(); + for (core::list::Iterator it = children.begin(); it != children.end();) { if (*it) ++it; @@ -202,8 +199,7 @@ void GUIFormSpecMenu::setInitialFocus() // 1. first empty editbox for (gui::IGUIElement *it : children) { - if (it->getType() == gui::EGUIET_EDIT_BOX - && it->getText()[0] == 0) { + if (it->getType() == gui::EGUIET_EDIT_BOX && it->getText()[0] == 0) { Environment->setFocus(it); return; } @@ -226,7 +222,7 @@ void GUIFormSpecMenu::setInitialFocus() } // 4. last button - for (core::list::Iterator it = children.getLast(); + for (core::list::Iterator it = children.getLast(); it != children.end(); --it) { if ((*it)->getType() == gui::EGUIET_BUTTON) { Environment->setFocus(*it); @@ -237,7 +233,7 @@ void GUIFormSpecMenu::setInitialFocus() // 5. first focusable (not statictext, not tabheader) for (gui::IGUIElement *it : children) { if (it->getType() != gui::EGUIET_STATIC_TEXT && - it->getType() != gui::EGUIET_TAB_CONTROL) { + it->getType() != gui::EGUIET_TAB_CONTROL) { Environment->setFocus(it); return; } @@ -250,7 +246,7 @@ void GUIFormSpecMenu::setInitialFocus() Environment->setFocus(*(children.begin())); } -GUITable* GUIFormSpecMenu::getTable(const std::string &tablename) +GUITable *GUIFormSpecMenu::getTable(const std::string &tablename) { for (auto &table : m_tables) { if (tablename == table.first.fname) @@ -259,7 +255,7 @@ GUITable* GUIFormSpecMenu::getTable(const std::string &tablename) return 0; } -std::vector* GUIFormSpecMenu::getDropDownValues(const std::string &name) +std::vector *GUIFormSpecMenu::getDropDownValues(const std::string &name) { for (auto &dropdown : m_dropdowns) { if (name == dropdown.first.fname) @@ -281,7 +277,7 @@ v2s32 GUIFormSpecMenu::getElementBasePos(const std::vector *v_pos) v2s32 GUIFormSpecMenu::getRealCoordinateBasePos(const std::vector &v_pos) { return v2s32((stof(v_pos[0]) + pos_offset.X) * imgsize.X, - (stof(v_pos[1]) + pos_offset.Y) * imgsize.Y); + (stof(v_pos[1]) + pos_offset.Y) * imgsize.Y); } v2s32 GUIFormSpecMenu::getRealCoordinateGeometry(const std::vector &v_geom) @@ -289,15 +285,15 @@ v2s32 GUIFormSpecMenu::getRealCoordinateGeometry(const std::vector return v2s32(stof(v_geom[0]) * imgsize.X, stof(v_geom[1]) * imgsize.Y); } -void GUIFormSpecMenu::parseSize(parserData* data, const std::string &element) +void GUIFormSpecMenu::parseSize(parserData *data, const std::string &element) { - std::vector parts = split(element,','); + std::vector parts = split(element, ','); if (((parts.size() == 2) || parts.size() == 3) || - ((parts.size() > 3) && (m_formspec_version > FORMSPEC_API_VERSION))) - { + ((parts.size() > 3) && + (m_formspec_version > FORMSPEC_API_VERSION))) { if (parts[1].find(';') != std::string::npos) - parts[1] = parts[1].substr(0,parts[1].find(';')); + parts[1] = parts[1].substr(0, parts[1].find(';')); data->invsize.X = MYMAX(0, stof(parts[0])); data->invsize.Y = MYMAX(0, stof(parts[1])); @@ -306,17 +302,18 @@ void GUIFormSpecMenu::parseSize(parserData* data, const std::string &element) #ifndef __ANDROID__ if (parts.size() == 3) { if (parts[2] == "true") { - lockSize(true,v2u32(800,600)); + lockSize(true, v2u32(800, 600)); } } #endif data->explicit_size = true; return; } - errorstream<< "Invalid size element (" << parts.size() << "): '" << element << "'" << std::endl; + errorstream << "Invalid size element (" << parts.size() << "): '" << element + << "'" << std::endl; } -void GUIFormSpecMenu::parseContainer(parserData* data, const std::string &element) +void GUIFormSpecMenu::parseContainer(parserData *data, const std::string &element) { std::vector parts = split(element, ','); @@ -329,13 +326,16 @@ void GUIFormSpecMenu::parseContainer(parserData* data, const std::string &elemen pos_offset.Y += stof(parts[1]); return; } - errorstream<< "Invalid container start element (" << parts.size() << "): '" << element << "'" << std::endl; + errorstream << "Invalid container start element (" << parts.size() << "): '" + << element << "'" << std::endl; } -void GUIFormSpecMenu::parseContainerEnd(parserData* data) +void GUIFormSpecMenu::parseContainerEnd(parserData *data) { if (container_stack.empty()) { - errorstream<< "Invalid container end element, no matching container start element" << std::endl; + errorstream << "Invalid container end element, no matching container " + "start element" + << std::endl; } else { pos_offset = container_stack.top(); container_stack.pop(); @@ -347,13 +347,14 @@ void GUIFormSpecMenu::parseScrollContainer(parserData *data, const std::string & std::vector parts = split(element, ';'); if (parts.size() < 4 || - (parts.size() > 5 && m_formspec_version <= FORMSPEC_API_VERSION)) { + (parts.size() > 5 && + m_formspec_version <= FORMSPEC_API_VERSION)) { errorstream << "Invalid scroll_container start element (" << parts.size() - << "): '" << element << "'" << std::endl; + << "): '" << element << "'" << std::endl; return; } - std::vector v_pos = split(parts[0], ','); + std::vector v_pos = split(parts[0], ','); std::vector v_geom = split(parts[1], ','); std::string scrollbar_name = parts[2]; std::string orientation = parts[3]; @@ -373,8 +374,8 @@ void GUIFormSpecMenu::parseScrollContainer(parserData *data, const std::string & scroll_factor *= -imgsize.X; else warningstream << "GUIFormSpecMenu::parseScrollContainer(): " - << "Invalid scroll_container orientation: " << orientation - << std::endl; + << "Invalid scroll_container orientation: " << orientation + << std::endl; // old parent (at first: this) // ^ is parent of clipper @@ -388,17 +389,12 @@ void GUIFormSpecMenu::parseScrollContainer(parserData *data, const std::string & data->current_parent, 0, rect_clipper); // make mover - FieldSpec spec_mover( - "", - L"", - L"", - 258 + m_fields.size() - ); + FieldSpec spec_mover("", L"", L"", 258 + m_fields.size()); core::rect rect_mover = core::rect(0, 0, geom.X, geom.Y); - GUIScrollContainer *mover = new GUIScrollContainer(Environment, - clipper, spec_mover.fid, rect_mover, orientation, scroll_factor); + GUIScrollContainer *mover = new GUIScrollContainer(Environment, clipper, + spec_mover.fid, rect_mover, orientation, scroll_factor); data->current_parent = mover; @@ -419,7 +415,7 @@ void GUIFormSpecMenu::parseScrollContainerEnd(parserData *data) if (data->current_parent == this || data->current_parent->getParent() == this || container_stack.empty()) { errorstream << "Invalid scroll_container end element, " - << "no matching scroll_container start element" << std::endl; + << "no matching scroll_container start element" << std::endl; return; } @@ -429,7 +425,7 @@ void GUIFormSpecMenu::parseScrollContainerEnd(parserData *data) // not 0,0, it is a normal container that was opened last, not a // scroll_container errorstream << "Invalid scroll_container end element, " - << "an inner container was left open" << std::endl; + << "an inner container was left open" << std::endl; return; } @@ -441,25 +437,25 @@ void GUIFormSpecMenu::parseScrollContainerEnd(parserData *data) void GUIFormSpecMenu::parseList(parserData *data, const std::string &element) { if (m_client == 0) { - warningstream<<"invalid use of 'list' with m_client==0"< parts = split(element,';'); + std::vector parts = split(element, ';'); if (((parts.size() == 4) || (parts.size() == 5)) || - ((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION))) - { + ((parts.size() > 5) && + (m_formspec_version > FORMSPEC_API_VERSION))) { std::string location = parts[0]; std::string listname = parts[1]; - std::vector v_pos = split(parts[2],','); - std::vector v_geom = split(parts[3],','); + std::vector v_pos = split(parts[2], ','); + std::vector v_geom = split(parts[3], ','); std::string startindex; if (parts.size() == 5) startindex = parts[4]; - MY_CHECKPOS("list",2); - MY_CHECKGEOM("list",3); + MY_CHECKPOS("list", 2); + MY_CHECKGEOM("list", 3); InventoryLocation loc; @@ -477,46 +473,47 @@ void GUIFormSpecMenu::parseList(parserData *data, const std::string &element) start_i = stoi(startindex); if (geom.X < 0 || geom.Y < 0 || start_i < 0) { - errorstream << "Invalid list element: '" << element << "'" << std::endl; + errorstream << "Invalid list element: '" << element << "'" + << std::endl; return; } if (!data->explicit_size) - warningstream << "invalid use of list without a size[] element" << std::endl; + warningstream << "invalid use of list without a size[] element" + << std::endl; - FieldSpec spec( - "", - L"", - L"", - 258 + m_fields.size(), - 3 - ); + FieldSpec spec("", L"", L"", 258 + m_fields.size(), 3); - v2f32 slot_spacing = data->real_coordinates ? - v2f32(imgsize.X * 1.25f, imgsize.Y * 1.25f) : spacing; + v2f32 slot_spacing = data->real_coordinates + ? v2f32(imgsize.X * 1.25f, + imgsize.Y * 1.25f) + : spacing; v2s32 pos = data->real_coordinates ? getRealCoordinateBasePos(v_pos) - : getElementBasePos(&v_pos); + : getElementBasePos(&v_pos); core::rect rect = core::rect(pos.X, pos.Y, pos.X + (geom.X - 1) * slot_spacing.X + imgsize.X, pos.Y + (geom.Y - 1) * slot_spacing.Y + imgsize.Y); - GUIInventoryList *e = new GUIInventoryList(Environment, data->current_parent, - spec.fid, rect, m_invmgr, loc, listname, geom, start_i, imgsize, - slot_spacing, this, data->inventorylist_options, m_font); + GUIInventoryList *e = new GUIInventoryList(Environment, + data->current_parent, spec.fid, rect, m_invmgr, loc, + listname, geom, start_i, imgsize, slot_spacing, this, + data->inventorylist_options, m_font); m_inventorylists.push_back(e); m_fields.push_back(spec); return; } - errorstream<< "Invalid list element(" << parts.size() << "): '" << element << "'" << std::endl; + errorstream << "Invalid list element(" << parts.size() << "): '" << element << "'" + << std::endl; } void GUIFormSpecMenu::parseListRing(parserData *data, const std::string &element) { if (m_client == 0) { - errorstream << "WARNING: invalid use of 'listring' with m_client==0" << std::endl; + errorstream << "WARNING: invalid use of 'listring' with m_client==0" + << std::endl; return; } @@ -542,23 +539,25 @@ void GUIFormSpecMenu::parseListRing(parserData *data, const std::string &element // insert the last two inv list elements into the list ring const GUIInventoryList *spa = m_inventorylists[siz - 2]; const GUIInventoryList *spb = m_inventorylists[siz - 1]; - m_inventory_rings.emplace_back(spa->getInventoryloc(), spa->getListname()); - m_inventory_rings.emplace_back(spb->getInventoryloc(), spb->getListname()); + m_inventory_rings.emplace_back( + spa->getInventoryloc(), spa->getListname()); + m_inventory_rings.emplace_back( + spb->getInventoryloc(), spb->getListname()); return; } - errorstream<< "Invalid list ring element(" << parts.size() << ", " - << m_inventorylists.size() << "): '" << element << "'" << std::endl; + errorstream << "Invalid list ring element(" << parts.size() << ", " + << m_inventorylists.size() << "): '" << element << "'" << std::endl; } -void GUIFormSpecMenu::parseCheckbox(parserData* data, const std::string &element) +void GUIFormSpecMenu::parseCheckbox(parserData *data, const std::string &element) { - std::vector parts = split(element,';'); + std::vector parts = split(element, ';'); if (((parts.size() >= 3) && (parts.size() <= 4)) || - ((parts.size() > 4) && (m_formspec_version > FORMSPEC_API_VERSION))) - { - std::vector v_pos = split(parts[0],','); + ((parts.size() > 4) && + (m_formspec_version > FORMSPEC_API_VERSION))) { + std::vector v_pos = split(parts[0], ','); std::string name = parts[1]; std::string label = parts[2]; std::string selected; @@ -566,15 +565,17 @@ void GUIFormSpecMenu::parseCheckbox(parserData* data, const std::string &element if (parts.size() >= 4) selected = parts[3]; - MY_CHECKPOS("checkbox",0); + MY_CHECKPOS("checkbox", 0); bool fselected = false; if (selected == "true") fselected = true; - std::wstring wlabel = translate_string(utf8_to_wide(unescape_string(label))); - const core::dimension2d label_size = m_font->getDimension(wlabel.c_str()); + std::wstring wlabel = + translate_string(utf8_to_wide(unescape_string(label))); + const core::dimension2d label_size = + m_font->getDimension(wlabel.c_str()); s32 cb_size = Environment->getSkin()->getSize(gui::EGDS_CHECK_BOX_WIDTH); s32 y_center = (std::max(label_size.Height, (u32)cb_size) + 1) / 2; @@ -584,28 +585,19 @@ void GUIFormSpecMenu::parseCheckbox(parserData* data, const std::string &element if (data->real_coordinates) { pos = getRealCoordinateBasePos(v_pos); - rect = core::rect( - pos.X, - pos.Y - y_center, + rect = core::rect(pos.X, pos.Y - y_center, pos.X + label_size.Width + cb_size + 7, - pos.Y + y_center - ); + pos.Y + y_center); } else { pos = getElementBasePos(&v_pos); - rect = core::rect( - pos.X, - pos.Y + imgsize.Y / 2 - y_center, + rect = core::rect(pos.X, pos.Y + imgsize.Y / 2 - y_center, pos.X + label_size.Width + cb_size + 7, - pos.Y + imgsize.Y / 2 + y_center - ); + pos.Y + imgsize.Y / 2 + y_center); } - FieldSpec spec( - name, - wlabel, //Needed for displaying text on MSVC - wlabel, - 258+m_fields.size() - ); + FieldSpec spec(name, + wlabel, // Needed for displaying text on MSVC + wlabel, 258 + m_fields.size()); spec.ftype = f_CheckBox; @@ -624,21 +616,22 @@ void GUIFormSpecMenu::parseCheckbox(parserData* data, const std::string &element m_fields.push_back(spec); return; } - errorstream<< "Invalid checkbox element(" << parts.size() << "): '" << element << "'" << std::endl; + errorstream << "Invalid checkbox element(" << parts.size() << "): '" << element + << "'" << std::endl; } -void GUIFormSpecMenu::parseScrollBar(parserData* data, const std::string &element) +void GUIFormSpecMenu::parseScrollBar(parserData *data, const std::string &element) { - std::vector parts = split(element,';'); + std::vector parts = split(element, ';'); if (parts.size() >= 5) { - std::vector v_pos = split(parts[0],','); - std::vector v_geom = split(parts[1],','); + std::vector v_pos = split(parts[0], ','); + std::vector v_geom = split(parts[1], ','); std::string name = parts[3]; std::string value = parts[4]; - MY_CHECKPOS("scrollbar",0); - MY_CHECKGEOM("scrollbar",1); + MY_CHECKPOS("scrollbar", 0); + MY_CHECKGEOM("scrollbar", 1); v2s32 pos; v2s32 dim; @@ -652,15 +645,10 @@ void GUIFormSpecMenu::parseScrollBar(parserData* data, const std::string &elemen dim.Y = stof(v_geom[1]) * spacing.Y; } - core::rect rect = - core::rect(pos.X, pos.Y, pos.X + dim.X, pos.Y + dim.Y); + core::rect rect = core::rect( + pos.X, pos.Y, pos.X + dim.X, pos.Y + dim.Y); - FieldSpec spec( - name, - L"", - L"", - 258+m_fields.size() - ); + FieldSpec spec(name, L"", L"", 258 + m_fields.size()); bool is_horizontal = true; @@ -668,7 +656,7 @@ void GUIFormSpecMenu::parseScrollBar(parserData* data, const std::string &elemen is_horizontal = false; spec.ftype = f_ScrollBar; - spec.send = true; + spec.send = true; GUIScrollBar *e = new GUIScrollBar(Environment, data->current_parent, spec.fid, rect, is_horizontal, true); @@ -689,27 +677,28 @@ void GUIFormSpecMenu::parseScrollBar(parserData* data, const std::string &elemen s32 scrollbar_size = is_horizontal ? dim.X : dim.Y; - e->setPageSize(scrollbar_size * (max - min + 1) / data->scrollbar_options.thumb_size); + e->setPageSize(scrollbar_size * (max - min + 1) / + data->scrollbar_options.thumb_size); if (spec.fname == m_focused_element) { Environment->setFocus(e); } - m_scrollbars.emplace_back(spec,e); + m_scrollbars.emplace_back(spec, e); m_fields.push_back(spec); return; } errorstream << "Invalid scrollbar element(" << parts.size() << "): '" << element - << "'" << std::endl; + << "'" << std::endl; } -void GUIFormSpecMenu::parseScrollBarOptions(parserData* data, const std::string &element) +void GUIFormSpecMenu::parseScrollBarOptions(parserData *data, const std::string &element) { std::vector parts = split(element, ';'); if (parts.size() == 0) { - warningstream << "Invalid scrollbaroptions element(" << parts.size() << "): '" << - element << "'" << std::endl; + warningstream << "Invalid scrollbaroptions element(" << parts.size() + << "): '" << element << "'" << std::endl; return; } @@ -717,8 +706,8 @@ void GUIFormSpecMenu::parseScrollBarOptions(parserData* data, const std::string std::vector options = split(i, '='); if (options.size() != 2) { - warningstream << "Invalid scrollbaroptions option syntax: '" << - element << "'" << std::endl; + warningstream << "Invalid scrollbaroptions option syntax: '" + << element << "'" << std::endl; continue; // Go to next option } @@ -743,28 +732,31 @@ void GUIFormSpecMenu::parseScrollBarOptions(parserData* data, const std::string } else if (options[0] == "arrows") { std::string value = trim(options[1]); if (value == "hide") - data->scrollbar_options.arrow_visiblity = GUIScrollBar::HIDE; + data->scrollbar_options.arrow_visiblity = + GUIScrollBar::HIDE; else if (value == "show") - data->scrollbar_options.arrow_visiblity = GUIScrollBar::SHOW; + data->scrollbar_options.arrow_visiblity = + GUIScrollBar::SHOW; else // Auto hide/show - data->scrollbar_options.arrow_visiblity = GUIScrollBar::DEFAULT; + data->scrollbar_options.arrow_visiblity = + GUIScrollBar::DEFAULT; continue; } - warningstream << "Invalid scrollbaroptions option(" << options[0] << - "): '" << element << "'" << std::endl; + warningstream << "Invalid scrollbaroptions option(" << options[0] + << "): '" << element << "'" << std::endl; } } -void GUIFormSpecMenu::parseImage(parserData* data, const std::string &element) +void GUIFormSpecMenu::parseImage(parserData *data, const std::string &element) { - std::vector parts = split(element,';'); + std::vector parts = split(element, ';'); if ((parts.size() == 3) || - ((parts.size() > 3) && (m_formspec_version > FORMSPEC_API_VERSION))) - { - std::vector v_pos = split(parts[0],','); - std::vector v_geom = split(parts[1],','); + ((parts.size() > 3) && + (m_formspec_version > FORMSPEC_API_VERSION))) { + std::vector v_pos = split(parts[0], ','); + std::vector v_geom = split(parts[1], ','); std::string name = unescape_string(parts[2]); MY_CHECKPOS("image", 0); @@ -783,29 +775,27 @@ void GUIFormSpecMenu::parseImage(parserData* data, const std::string &element) } if (!data->explicit_size) - warningstream<<"invalid use of image without a size[] element"<getTexture(name); if (!texture) { - errorstream << "GUIFormSpecMenu::parseImage() Unable to load texture:" - << std::endl << "\t" << name << std::endl; + errorstream << "GUIFormSpecMenu::parseImage() Unable to load " + "texture:" + << std::endl + << "\t" << name << std::endl; return; } - FieldSpec spec( - name, - L"", - L"", - 258 + m_fields.size(), - 1 - ); + FieldSpec spec(name, L"", L"", 258 + m_fields.size(), 1); core::rect rect(pos, pos + geom); - gui::IGUIImage *e = Environment->addImage(rect, data->current_parent, - spec.fid, 0, true); + gui::IGUIImage *e = Environment->addImage( + rect, data->current_parent, spec.fid, 0, true); e->setImage(texture); e->setScaleImage(true); auto style = getDefaultStyleForElement("image", spec.fname); - e->setNotClipped(style.getBool(StyleSpec::NOCLIP, m_formspec_version < 3)); + e->setNotClipped( + style.getBool(StyleSpec::NOCLIP, m_formspec_version < 3)); m_fields.push_back(spec); // images should let events through @@ -815,7 +805,7 @@ void GUIFormSpecMenu::parseImage(parserData* data, const std::string &element) } if (parts.size() == 2) { - std::vector v_pos = split(parts[0],','); + std::vector v_pos = split(parts[0], ','); std::string name = unescape_string(parts[1]); MY_CHECKPOS("image", 0); @@ -823,25 +813,24 @@ void GUIFormSpecMenu::parseImage(parserData* data, const std::string &element) v2s32 pos = getElementBasePos(&v_pos); if (!data->explicit_size) - warningstream<<"invalid use of image without a size[] element"<getTexture(name); if (!texture) { - errorstream << "GUIFormSpecMenu::parseImage() Unable to load texture:" - << std::endl << "\t" << name << std::endl; + errorstream << "GUIFormSpecMenu::parseImage() Unable to load " + "texture:" + << std::endl + << "\t" << name << std::endl; return; } - FieldSpec spec( - name, - L"", - L"", - 258 + m_fields.size() - ); - gui::IGUIImage *e = Environment->addImage(texture, pos, true, - data->current_parent, spec.fid, 0); + FieldSpec spec(name, L"", L"", 258 + m_fields.size()); + gui::IGUIImage *e = Environment->addImage( + texture, pos, true, data->current_parent, spec.fid, 0); auto style = getDefaultStyleForElement("image", spec.fname); - e->setNotClipped(style.getBool(StyleSpec::NOCLIP, m_formspec_version < 3)); + e->setNotClipped( + style.getBool(StyleSpec::NOCLIP, m_formspec_version < 3)); m_fields.push_back(spec); // images should let events through @@ -849,7 +838,8 @@ void GUIFormSpecMenu::parseImage(parserData* data, const std::string &element) m_clickthrough_elements.push_back(e); return; } - errorstream<< "Invalid image element(" << parts.size() << "): '" << element << "'" << std::endl; + errorstream << "Invalid image element(" << parts.size() << "): '" << element + << "'" << std::endl; } void GUIFormSpecMenu::parseAnimatedImage(parserData *data, const std::string &element) @@ -857,13 +847,14 @@ void GUIFormSpecMenu::parseAnimatedImage(parserData *data, const std::string &el std::vector parts = split(element, ';'); if (parts.size() != 6 && parts.size() != 7 && - !(parts.size() > 7 && m_formspec_version > FORMSPEC_API_VERSION)) { - errorstream << "Invalid animated_image element(" << parts.size() - << "): '" << element << "'" << std::endl; + !(parts.size() > 7 && + m_formspec_version > FORMSPEC_API_VERSION)) { + errorstream << "Invalid animated_image element(" << parts.size() << "): '" + << element << "'" << std::endl; return; } - std::vector v_pos = split(parts[0], ','); + std::vector v_pos = split(parts[0], ','); std::vector v_geom = split(parts[1], ','); std::string name = parts[2]; std::string texture_name = unescape_string(parts[3]); @@ -886,21 +877,17 @@ void GUIFormSpecMenu::parseAnimatedImage(parserData *data, const std::string &el } if (!data->explicit_size) - warningstream << "Invalid use of animated_image without a size[] element" << std::endl; + warningstream << "Invalid use of animated_image without a size[] element" + << std::endl; - FieldSpec spec( - name, - L"", - L"", - 258 + m_fields.size() - ); + FieldSpec spec(name, L"", L"", 258 + m_fields.size()); spec.ftype = f_AnimatedImage; spec.send = true; core::rect rect = core::rect(pos, pos + geom); - GUIAnimatedImage *e = new GUIAnimatedImage(Environment, this, spec.fid, - rect, texture_name, frame_count, frame_duration, m_tsrc); + GUIAnimatedImage *e = new GUIAnimatedImage(Environment, this, spec.fid, rect, + texture_name, frame_count, frame_duration, m_tsrc); if (parts.size() >= 7) e->setFrameIndex(stoi(parts[6]) - 1); @@ -914,19 +901,19 @@ void GUIFormSpecMenu::parseAnimatedImage(parserData *data, const std::string &el m_fields.push_back(spec); } -void GUIFormSpecMenu::parseItemImage(parserData* data, const std::string &element) +void GUIFormSpecMenu::parseItemImage(parserData *data, const std::string &element) { - std::vector parts = split(element,';'); + std::vector parts = split(element, ';'); if ((parts.size() == 3) || - ((parts.size() > 3) && (m_formspec_version > FORMSPEC_API_VERSION))) - { - std::vector v_pos = split(parts[0],','); - std::vector v_geom = split(parts[1],','); + ((parts.size() > 3) && + (m_formspec_version > FORMSPEC_API_VERSION))) { + std::vector v_pos = split(parts[0], ','); + std::vector v_geom = split(parts[1], ','); std::string name = parts[2]; - MY_CHECKPOS("itemimage",0); - MY_CHECKGEOM("itemimage",1); + MY_CHECKPOS("itemimage", 0); + MY_CHECKGEOM("itemimage", 1); v2s32 pos; v2s32 geom; @@ -940,20 +927,17 @@ void GUIFormSpecMenu::parseItemImage(parserData* data, const std::string &elemen geom.Y = stof(v_geom[1]) * (float)imgsize.Y; } - if(!data->explicit_size) - warningstream<<"invalid use of item_image without a size[] element"<explicit_size) + warningstream << "invalid use of item_image without a size[] " + "element" + << std::endl; - FieldSpec spec( - "", - L"", - L"", - 258 + m_fields.size(), - 2 - ); + FieldSpec spec("", L"", L"", 258 + m_fields.size(), 2); spec.ftype = f_ItemImage; - GUIItemImage *e = new GUIItemImage(Environment, data->current_parent, spec.fid, - core::rect(pos, pos + geom), name, m_font, m_client); + GUIItemImage *e = new GUIItemImage(Environment, data->current_parent, + spec.fid, core::rect(pos, pos + geom), name, m_font, + m_client); auto style = getDefaultStyleForElement("item_image", spec.fname); e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); @@ -963,24 +947,25 @@ void GUIFormSpecMenu::parseItemImage(parserData* data, const std::string &elemen m_fields.push_back(spec); return; } - errorstream<< "Invalid ItemImage element(" << parts.size() << "): '" << element << "'" << std::endl; + errorstream << "Invalid ItemImage element(" << parts.size() << "): '" << element + << "'" << std::endl; } -void GUIFormSpecMenu::parseButton(parserData* data, const std::string &element, - const std::string &type) +void GUIFormSpecMenu::parseButton( + parserData *data, const std::string &element, const std::string &type) { - std::vector parts = split(element,';'); + std::vector parts = split(element, ';'); if ((parts.size() == 4) || - ((parts.size() > 4) && (m_formspec_version > FORMSPEC_API_VERSION))) - { - std::vector v_pos = split(parts[0],','); - std::vector v_geom = split(parts[1],','); + ((parts.size() > 4) && + (m_formspec_version > FORMSPEC_API_VERSION))) { + std::vector v_pos = split(parts[0], ','); + std::vector v_geom = split(parts[1], ','); std::string name = parts[2]; std::string label = parts[3]; - MY_CHECKPOS("button",0); - MY_CHECKGEOM("button",1); + MY_CHECKPOS("button", 0); + MY_CHECKGEOM("button", 1); v2s32 pos; v2s32 geom; @@ -989,36 +974,34 @@ void GUIFormSpecMenu::parseButton(parserData* data, const std::string &element, if (data->real_coordinates) { pos = getRealCoordinateBasePos(v_pos); geom = getRealCoordinateGeometry(v_geom); - rect = core::rect(pos.X, pos.Y, pos.X+geom.X, - pos.Y+geom.Y); + rect = core::rect( + pos.X, pos.Y, pos.X + geom.X, pos.Y + geom.Y); } else { pos = getElementBasePos(&v_pos); geom.X = (stof(v_geom[0]) * spacing.X) - (spacing.X - imgsize.X); - pos.Y += (stof(v_geom[1]) * (float)imgsize.Y)/2; + pos.Y += (stof(v_geom[1]) * (float)imgsize.Y) / 2; rect = core::rect(pos.X, pos.Y - m_btn_height, - pos.X + geom.X, pos.Y + m_btn_height); + pos.X + geom.X, pos.Y + m_btn_height); } - if(!data->explicit_size) - warningstream<<"invalid use of button without a size[] element"<explicit_size) + warningstream << "invalid use of button without a size[] element" + << std::endl; - std::wstring wlabel = translate_string(utf8_to_wide(unescape_string(label))); + std::wstring wlabel = + translate_string(utf8_to_wide(unescape_string(label))); - FieldSpec spec( - name, - wlabel, - L"", - 258 + m_fields.size() - ); + FieldSpec spec(name, wlabel, L"", 258 + m_fields.size()); spec.ftype = f_Button; - if(type == "button_exit") + if (type == "button_exit") spec.is_exit = true; GUIButton *e = GUIButton::addButton(Environment, rect, m_tsrc, data->current_parent, spec.fid, spec.flabel.c_str()); - auto style = getStyleForElement(type, name, (type != "button") ? "button" : ""); + auto style = getStyleForElement( + type, name, (type != "button") ? "button" : ""); e->setStyles(style); if (spec.fname == m_focused_element) { @@ -1028,21 +1011,22 @@ void GUIFormSpecMenu::parseButton(parserData* data, const std::string &element, m_fields.push_back(spec); return; } - errorstream<< "Invalid button element(" << parts.size() << "): '" << element << "'" << std::endl; + errorstream << "Invalid button element(" << parts.size() << "): '" << element + << "'" << std::endl; } -void GUIFormSpecMenu::parseBackground(parserData* data, const std::string &element) +void GUIFormSpecMenu::parseBackground(parserData *data, const std::string &element) { - std::vector parts = split(element,';'); + std::vector parts = split(element, ';'); if ((parts.size() >= 3 && parts.size() <= 5) || (parts.size() > 5 && m_formspec_version > FORMSPEC_API_VERSION)) { - std::vector v_pos = split(parts[0],','); - std::vector v_geom = split(parts[1],','); + std::vector v_pos = split(parts[0], ','); + std::vector v_geom = split(parts[1], ','); std::string name = unescape_string(parts[2]); - MY_CHECKPOS("background",0); - MY_CHECKGEOM("background",1); + MY_CHECKPOS("background", 0); + MY_CHECKGEOM("background", 1); v2s32 pos; v2s32 geom; @@ -1065,7 +1049,7 @@ void GUIFormSpecMenu::parseBackground(parserData* data, const std::string &eleme pos = getRealCoordinateBasePos(v_pos) * -1; geom = v2s32(0, 0); } else { - pos.X = stoi(v_pos[0]); //acts as offset + pos.X = stoi(v_pos[0]); // acts as offset pos.Y = stoi(v_pos[1]); } clip = true; @@ -1080,27 +1064,28 @@ void GUIFormSpecMenu::parseBackground(parserData* data, const std::string &eleme middle.LowerRightCorner = core::vector2di(-x, -x); } else if (v_middle.size() == 2) { s32 x = stoi(v_middle[0]); - s32 y = stoi(v_middle[1]); + s32 y = stoi(v_middle[1]); middle.UpperLeftCorner = core::vector2di(x, y); middle.LowerRightCorner = core::vector2di(-x, -y); // `-x` is interpreted as `w - x` } else if (v_middle.size() == 4) { - middle.UpperLeftCorner = core::vector2di(stoi(v_middle[0]), stoi(v_middle[1])); - middle.LowerRightCorner = core::vector2di(stoi(v_middle[2]), stoi(v_middle[3])); + middle.UpperLeftCorner = core::vector2di( + stoi(v_middle[0]), stoi(v_middle[1])); + middle.LowerRightCorner = core::vector2di( + stoi(v_middle[2]), stoi(v_middle[3])); } else { - warningstream << "Invalid rectangle given to middle param of background[] element" << std::endl; + warningstream << "Invalid rectangle given to middle " + "param of background[] element" + << std::endl; } } if (!data->explicit_size && !clip) - warningstream << "invalid use of unclipped background without a size[] element" << std::endl; + warningstream << "invalid use of unclipped background without a " + "size[] element" + << std::endl; - FieldSpec spec( - name, - L"", - L"", - 258 + m_fields.size() - ); + FieldSpec spec(name, L"", L"", 258 + m_fields.size()); core::rect rect; if (!clip) { @@ -1111,8 +1096,8 @@ void GUIFormSpecMenu::parseBackground(parserData* data, const std::string &eleme rect = core::rect(-pos, pos); } - GUIBackgroundImage *e = new GUIBackgroundImage(Environment, this, spec.fid, - rect, name, middle, m_tsrc, clip); + GUIBackgroundImage *e = new GUIBackgroundImage(Environment, this, + spec.fid, rect, name, middle, m_tsrc, clip); FATAL_ERROR_IF(!e, "Failed to create background formspec element"); @@ -1124,12 +1109,13 @@ void GUIFormSpecMenu::parseBackground(parserData* data, const std::string &eleme m_fields.push_back(spec); return; } - errorstream<< "Invalid background element(" << parts.size() << "): '" << element << "'" << std::endl; + errorstream << "Invalid background element(" << parts.size() << "): '" << element + << "'" << std::endl; } -void GUIFormSpecMenu::parseTableOptions(parserData* data, const std::string &element) +void GUIFormSpecMenu::parseTableOptions(parserData *data, const std::string &element) { - std::vector parts = split(element,';'); + std::vector parts = split(element, ';'); data->table_options.clear(); for (const std::string &part : parts) { @@ -1139,13 +1125,13 @@ void GUIFormSpecMenu::parseTableOptions(parserData* data, const std::string &ele } } -void GUIFormSpecMenu::parseTableColumns(parserData* data, const std::string &element) +void GUIFormSpecMenu::parseTableColumns(parserData *data, const std::string &element) { - std::vector parts = split(element,';'); + std::vector parts = split(element, ';'); data->table_columns.clear(); for (const std::string &part : parts) { - std::vector col_parts = split(part,','); + std::vector col_parts = split(part, ','); GUITable::TableColumn column; // Parse column type if (!col_parts.empty()) @@ -1159,25 +1145,25 @@ void GUIFormSpecMenu::parseTableColumns(parserData* data, const std::string &ele } } -void GUIFormSpecMenu::parseTable(parserData* data, const std::string &element) +void GUIFormSpecMenu::parseTable(parserData *data, const std::string &element) { - std::vector parts = split(element,';'); + std::vector parts = split(element, ';'); if (((parts.size() == 4) || (parts.size() == 5)) || - ((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION))) - { - std::vector v_pos = split(parts[0],','); - std::vector v_geom = split(parts[1],','); + ((parts.size() > 5) && + (m_formspec_version > FORMSPEC_API_VERSION))) { + std::vector v_pos = split(parts[0], ','); + std::vector v_geom = split(parts[1], ','); std::string name = parts[2]; - std::vector items = split(parts[3],','); + std::vector items = split(parts[3], ','); std::string str_initial_selection; std::string str_transparent = "false"; if (parts.size() >= 5) str_initial_selection = parts[4]; - MY_CHECKPOS("table",0); - MY_CHECKGEOM("table",1); + MY_CHECKPOS("table", 0); + MY_CHECKGEOM("table", 1); v2s32 pos; v2s32 geom; @@ -1191,22 +1177,19 @@ void GUIFormSpecMenu::parseTable(parserData* data, const std::string &element) geom.Y = stof(v_geom[1]) * spacing.Y; } - core::rect rect = core::rect(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y); + core::rect rect = core::rect( + pos.X, pos.Y, pos.X + geom.X, pos.Y + geom.Y); - FieldSpec spec( - name, - L"", - L"", - 258 + m_fields.size() - ); + FieldSpec spec(name, L"", L"", 258 + m_fields.size()); spec.ftype = f_Table; for (std::string &item : items) { - item = wide_to_utf8(unescape_translate(utf8_to_wide(unescape_string(item)))); + item = wide_to_utf8(unescape_translate( + utf8_to_wide(unescape_string(item)))); } - //now really show table + // now really show table GUITable *e = new GUITable(Environment, data->current_parent, spec.fid, rect, m_tsrc); @@ -1230,20 +1213,21 @@ void GUIFormSpecMenu::parseTable(parserData* data, const std::string &element) m_fields.push_back(spec); return; } - errorstream<< "Invalid table element(" << parts.size() << "): '" << element << "'" << std::endl; + errorstream << "Invalid table element(" << parts.size() << "): '" << element + << "'" << std::endl; } -void GUIFormSpecMenu::parseTextList(parserData* data, const std::string &element) +void GUIFormSpecMenu::parseTextList(parserData *data, const std::string &element) { - std::vector parts = split(element,';'); + std::vector parts = split(element, ';'); if (((parts.size() == 4) || (parts.size() == 5) || (parts.size() == 6)) || - ((parts.size() > 6) && (m_formspec_version > FORMSPEC_API_VERSION))) - { - std::vector v_pos = split(parts[0],','); - std::vector v_geom = split(parts[1],','); + ((parts.size() > 6) && + (m_formspec_version > FORMSPEC_API_VERSION))) { + std::vector v_pos = split(parts[0], ','); + std::vector v_geom = split(parts[1], ','); std::string name = parts[2]; - std::vector items = split(parts[3],','); + std::vector items = split(parts[3], ','); std::string str_initial_selection; std::string str_transparent = "false"; @@ -1253,8 +1237,8 @@ void GUIFormSpecMenu::parseTextList(parserData* data, const std::string &element if (parts.size() >= 6) str_transparent = parts[5]; - MY_CHECKPOS("textlist",0); - MY_CHECKGEOM("textlist",1); + MY_CHECKPOS("textlist", 0); + MY_CHECKGEOM("textlist", 1); v2s32 pos; v2s32 geom; @@ -1268,22 +1252,19 @@ void GUIFormSpecMenu::parseTextList(parserData* data, const std::string &element geom.Y = stof(v_geom[1]) * spacing.Y; } - core::rect rect = core::rect(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y); + core::rect rect = core::rect( + pos.X, pos.Y, pos.X + geom.X, pos.Y + geom.Y); - FieldSpec spec( - name, - L"", - L"", - 258 + m_fields.size() - ); + FieldSpec spec(name, L"", L"", 258 + m_fields.size()); spec.ftype = f_Table; for (std::string &item : items) { - item = wide_to_utf8(unescape_translate(utf8_to_wide(unescape_string(item)))); + item = wide_to_utf8(unescape_translate( + utf8_to_wide(unescape_string(item)))); } - //now really show list + // now really show list GUITable *e = new GUITable(Environment, data->current_parent, spec.fid, rect, m_tsrc); @@ -1307,16 +1288,16 @@ void GUIFormSpecMenu::parseTextList(parserData* data, const std::string &element m_fields.push_back(spec); return; } - errorstream<< "Invalid textlist element(" << parts.size() << "): '" << element << "'" << std::endl; + errorstream << "Invalid textlist element(" << parts.size() << "): '" << element + << "'" << std::endl; } -void GUIFormSpecMenu::parseDropDown(parserData* data, const std::string &element) +void GUIFormSpecMenu::parseDropDown(parserData *data, const std::string &element) { std::vector parts = split(element, ';'); if (parts.size() == 5 || parts.size() == 6 || - (parts.size() > 6 && m_formspec_version > FORMSPEC_API_VERSION)) - { + (parts.size() > 6 && m_formspec_version > FORMSPEC_API_VERSION)) { std::vector v_pos = split(parts[0], ','); std::string name = parts[2]; std::vector items = split(parts[3], ','); @@ -1325,57 +1306,53 @@ void GUIFormSpecMenu::parseDropDown(parserData* data, const std::string &element if (parts.size() >= 6 && is_yes(parts[5])) m_dropdown_index_event[name] = true; - MY_CHECKPOS("dropdown",0); + MY_CHECKPOS("dropdown", 0); v2s32 pos; v2s32 geom; core::rect rect; if (data->real_coordinates) { - std::vector v_geom = split(parts[1],','); + std::vector v_geom = split(parts[1], ','); if (v_geom.size() == 1) v_geom.emplace_back("1"); - MY_CHECKGEOM("dropdown",1); + MY_CHECKGEOM("dropdown", 1); pos = getRealCoordinateBasePos(v_pos); geom = getRealCoordinateGeometry(v_geom); - rect = core::rect(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y); + rect = core::rect( + pos.X, pos.Y, pos.X + geom.X, pos.Y + geom.Y); } else { pos = getElementBasePos(&v_pos); s32 width = stof(parts[1]) * spacing.Y; - rect = core::rect(pos.X, pos.Y, - pos.X + width, pos.Y + (m_btn_height * 2)); + rect = core::rect(pos.X, pos.Y, pos.X + width, + pos.Y + (m_btn_height * 2)); } - FieldSpec spec( - name, - L"", - L"", - 258 + m_fields.size() - ); + FieldSpec spec(name, L"", L"", 258 + m_fields.size()); spec.ftype = f_DropDown; spec.send = true; - //now really show list - gui::IGUIComboBox *e = Environment->addComboBox(rect, data->current_parent, - spec.fid); + // now really show list + gui::IGUIComboBox *e = Environment->addComboBox( + rect, data->current_parent, spec.fid); if (spec.fname == m_focused_element) { Environment->setFocus(e); } for (const std::string &item : items) { - e->addItem(unescape_translate(unescape_string( - utf8_to_wide(item))).c_str()); + e->addItem(unescape_translate(unescape_string(utf8_to_wide(item))) + .c_str()); } if (!str_initial_selection.empty()) - e->setSelected(stoi(str_initial_selection)-1); + e->setSelected(stoi(str_initial_selection) - 1); auto style = getDefaultStyleForElement("dropdown", name); e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); @@ -1391,32 +1368,31 @@ void GUIFormSpecMenu::parseDropDown(parserData* data, const std::string &element return; } errorstream << "Invalid dropdown element(" << parts.size() << "): '" << element - << "'" << std::endl; + << "'" << std::endl; } void GUIFormSpecMenu::parseFieldCloseOnEnter(parserData *data, const std::string &element) { - std::vector parts = split(element,';'); + std::vector parts = split(element, ';'); if (parts.size() == 2 || (parts.size() > 2 && m_formspec_version > FORMSPEC_API_VERSION)) { field_close_on_enter[parts[0]] = is_yes(parts[1]); } } -void GUIFormSpecMenu::parsePwdField(parserData* data, const std::string &element) +void GUIFormSpecMenu::parsePwdField(parserData *data, const std::string &element) { - std::vector parts = split(element,';'); + std::vector parts = split(element, ';'); if (parts.size() == 4 || - (parts.size() > 4 && m_formspec_version > FORMSPEC_API_VERSION)) - { - std::vector v_pos = split(parts[0],','); - std::vector v_geom = split(parts[1],','); + (parts.size() > 4 && m_formspec_version > FORMSPEC_API_VERSION)) { + std::vector v_pos = split(parts[0], ','); + std::vector v_geom = split(parts[1], ','); std::string name = parts[2]; std::string label = parts[3]; - MY_CHECKPOS("pwdfield",0); - MY_CHECKGEOM("pwdfield",1); + MY_CHECKPOS("pwdfield", 0); + MY_CHECKGEOM("pwdfield", 1); v2s32 pos; v2s32 geom; @@ -1430,27 +1406,22 @@ void GUIFormSpecMenu::parsePwdField(parserData* data, const std::string &element geom.X = (stof(v_geom[0]) * spacing.X) - (spacing.X - imgsize.X); - pos.Y += (stof(v_geom[1]) * (float)imgsize.Y)/2; + pos.Y += (stof(v_geom[1]) * (float)imgsize.Y) / 2; pos.Y -= m_btn_height; - geom.Y = m_btn_height*2; + geom.Y = m_btn_height * 2; } - core::rect rect = core::rect(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y); + core::rect rect = core::rect( + pos.X, pos.Y, pos.X + geom.X, pos.Y + geom.Y); - std::wstring wlabel = translate_string(utf8_to_wide(unescape_string(label))); + std::wstring wlabel = + translate_string(utf8_to_wide(unescape_string(label))); - FieldSpec spec( - name, - wlabel, - L"", - 258 + m_fields.size(), - 0, - ECI_IBEAM - ); + FieldSpec spec(name, wlabel, L"", 258 + m_fields.size(), 0, ECI_IBEAM); spec.send = true; - gui::IGUIEditBox *e = Environment->addEditBox(0, rect, true, - data->current_parent, spec.fid); + gui::IGUIEditBox *e = Environment->addEditBox( + 0, rect, true, data->current_parent, spec.fid); if (spec.fname == m_focused_element) { Environment->setFocus(e); @@ -1460,24 +1431,25 @@ void GUIFormSpecMenu::parsePwdField(parserData* data, const std::string &element int font_height = g_fontengine->getTextHeight(); rect.UpperLeftCorner.Y -= font_height; rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + font_height; - gui::StaticText::add(Environment, spec.flabel.c_str(), rect, false, true, - data->current_parent, 0); + gui::StaticText::add(Environment, spec.flabel.c_str(), rect, + false, true, data->current_parent, 0); } - e->setPasswordBox(true,L'*'); + e->setPasswordBox(true, L'*'); auto style = getDefaultStyleForElement("pwdfield", name, "field"); e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); e->setDrawBorder(style.getBool(StyleSpec::BORDER, true)); - e->setOverrideColor(style.getColor(StyleSpec::TEXTCOLOR, video::SColor(0xFFFFFFFF))); + e->setOverrideColor(style.getColor( + StyleSpec::TEXTCOLOR, video::SColor(0xFFFFFFFF))); e->setOverrideFont(style.getFont()); irr::SEvent evt; - evt.EventType = EET_KEY_INPUT_EVENT; - evt.KeyInput.Key = KEY_END; - evt.KeyInput.Char = 0; - evt.KeyInput.Control = false; - evt.KeyInput.Shift = false; + evt.EventType = EET_KEY_INPUT_EVENT; + evt.KeyInput.Key = KEY_END; + evt.KeyInput.Char = 0; + evt.KeyInput.Control = false; + evt.KeyInput.Shift = false; evt.KeyInput.PressedDown = true; e->OnEvent(evt); @@ -1487,15 +1459,17 @@ void GUIFormSpecMenu::parsePwdField(parserData* data, const std::string &element m_fields.push_back(spec); return; } - errorstream<< "Invalid pwdfield element(" << parts.size() << "): '" << element << "'" << std::endl; + errorstream << "Invalid pwdfield element(" << parts.size() << "): '" << element + << "'" << std::endl; } void GUIFormSpecMenu::createTextField(parserData *data, FieldSpec &spec, - core::rect &rect, bool is_multiline) + core::rect &rect, bool is_multiline) { bool is_editable = !spec.fname.empty(); if (!is_editable && !is_multiline) { - // spec field id to 0, this stops submit searching for a value that isn't there + // spec field id to 0, this stops submit searching for a value that isn't + // there gui::StaticText::add(Environment, spec.flabel.c_str(), rect, false, true, data->current_parent, 0); return; @@ -1503,23 +1477,25 @@ void GUIFormSpecMenu::createTextField(parserData *data, FieldSpec &spec, if (is_editable) { spec.send = true; - } else if (is_multiline && - spec.fdefault.empty() && !spec.flabel.empty()) { + } else if (is_multiline && spec.fdefault.empty() && !spec.flabel.empty()) { // Multiline textareas: swap default and label for backwards compat spec.flabel.swap(spec.fdefault); } gui::IGUIEditBox *e = nullptr; static constexpr bool use_intl_edit_box = USE_FREETYPE && - IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 9; + IRRLICHT_VERSION_MAJOR == 1 && + IRRLICHT_VERSION_MINOR < 9; if (use_intl_edit_box && g_settings->getBool("freetype")) { e = new gui::intlGUIEditBox(spec.fdefault.c_str(), true, Environment, - data->current_parent, spec.fid, rect, is_editable, is_multiline); + data->current_parent, spec.fid, rect, is_editable, + is_multiline); } else { if (is_multiline) { - e = new GUIEditBoxWithScrollBar(spec.fdefault.c_str(), true, Environment, - data->current_parent, spec.fid, rect, is_editable, true); + e = new GUIEditBoxWithScrollBar(spec.fdefault.c_str(), true, + Environment, data->current_parent, spec.fid, rect, + is_editable, true); } else if (is_editable) { e = Environment->addEditBox(spec.fdefault.c_str(), rect, true, data->current_parent, spec.fid); @@ -1527,7 +1503,8 @@ void GUIFormSpecMenu::createTextField(parserData *data, FieldSpec &spec, } } - auto style = getDefaultStyleForElement(is_multiline ? "textarea" : "field", spec.fname); + auto style = getDefaultStyleForElement( + is_multiline ? "textarea" : "field", spec.fname); if (e) { if (is_editable && spec.fname == m_focused_element) @@ -1539,18 +1516,19 @@ void GUIFormSpecMenu::createTextField(parserData *data, FieldSpec &spec, e->setTextAlignment(gui::EGUIA_UPPERLEFT, gui::EGUIA_UPPERLEFT); } else { irr::SEvent evt; - evt.EventType = EET_KEY_INPUT_EVENT; - evt.KeyInput.Key = KEY_END; - evt.KeyInput.Char = 0; - evt.KeyInput.Control = 0; - evt.KeyInput.Shift = 0; + evt.EventType = EET_KEY_INPUT_EVENT; + evt.KeyInput.Key = KEY_END; + evt.KeyInput.Char = 0; + evt.KeyInput.Control = 0; + evt.KeyInput.Shift = 0; evt.KeyInput.PressedDown = true; e->OnEvent(evt); } e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); e->setDrawBorder(style.getBool(StyleSpec::BORDER, true)); - e->setOverrideColor(style.getColor(StyleSpec::TEXTCOLOR, video::SColor(0xFFFFFFFF))); + e->setOverrideColor(style.getColor( + StyleSpec::TEXTCOLOR, video::SColor(0xFFFFFFFF))); if (style.get(StyleSpec::BGCOLOR, "") == "transparent") { e->setDrawBackground(false); } @@ -1571,8 +1549,7 @@ void GUIFormSpecMenu::createTextField(parserData *data, FieldSpec &spec, } } -void GUIFormSpecMenu::parseSimpleField(parserData *data, - std::vector &parts) +void GUIFormSpecMenu::parseSimpleField(parserData *data, std::vector &parts) { std::string name = parts[0]; std::string label = parts[1]; @@ -1581,32 +1558,23 @@ void GUIFormSpecMenu::parseSimpleField(parserData *data, core::rect rect; if (data->explicit_size) - warningstream << "invalid use of unpositioned \"field\" in inventory" << std::endl; + warningstream << "invalid use of unpositioned \"field\" in inventory" + << std::endl; v2s32 pos = getElementBasePos(nullptr); pos.Y = (data->simple_field_count + 2) * 60; v2s32 size = DesiredRect.getSize(); - rect = core::rect( - size.X / 2 - 150, pos.Y, - size.X / 2 - 150 + 300, pos.Y + m_btn_height * 2 - ); - + rect = core::rect(size.X / 2 - 150, pos.Y, size.X / 2 - 150 + 300, + pos.Y + m_btn_height * 2); if (m_form_src) default_val = m_form_src->resolveText(default_val); - std::wstring wlabel = translate_string(utf8_to_wide(unescape_string(label))); - FieldSpec spec( - name, - wlabel, - utf8_to_wide(unescape_string(default_val)), - 258 + m_fields.size(), - 0, - ECI_IBEAM - ); + FieldSpec spec(name, wlabel, utf8_to_wide(unescape_string(default_val)), + 258 + m_fields.size(), 0, ECI_IBEAM); createTextField(data, spec, rect, false); @@ -1615,17 +1583,17 @@ void GUIFormSpecMenu::parseSimpleField(parserData *data, data->simple_field_count++; } -void GUIFormSpecMenu::parseTextArea(parserData* data, std::vector& parts, +void GUIFormSpecMenu::parseTextArea(parserData *data, std::vector &parts, const std::string &type) { - std::vector v_pos = split(parts[0],','); - std::vector v_geom = split(parts[1],','); + std::vector v_pos = split(parts[0], ','); + std::vector v_geom = split(parts[1], ','); std::string name = parts[2]; std::string label = parts[3]; std::string default_val = parts[4]; - MY_CHECKPOS(type,0); - MY_CHECKGEOM(type,1); + MY_CHECKPOS(type, 0); + MY_CHECKGEOM(type, 1); v2s32 pos; v2s32 geom; @@ -1639,38 +1607,31 @@ void GUIFormSpecMenu::parseTextArea(parserData* data, std::vector& geom.X = (stof(v_geom[0]) * spacing.X) - (spacing.X - imgsize.X); - if (type == "textarea") - { - geom.Y = (stof(v_geom[1]) * (float)imgsize.Y) - (spacing.Y-imgsize.Y); + if (type == "textarea") { + geom.Y = (stof(v_geom[1]) * (float)imgsize.Y) - + (spacing.Y - imgsize.Y); pos.Y += m_btn_height; - } - else - { - pos.Y += (stof(v_geom[1]) * (float)imgsize.Y)/2; + } else { + pos.Y += (stof(v_geom[1]) * (float)imgsize.Y) / 2; pos.Y -= m_btn_height; - geom.Y = m_btn_height*2; + geom.Y = m_btn_height * 2; } } - core::rect rect = core::rect(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y); + core::rect rect = + core::rect(pos.X, pos.Y, pos.X + geom.X, pos.Y + geom.Y); - if(!data->explicit_size) - warningstream<<"invalid use of positioned "<explicit_size) + warningstream << "invalid use of positioned " << type + << " without a size[] element" << std::endl; - if(m_form_src) + if (m_form_src) default_val = m_form_src->resolveText(default_val); - std::wstring wlabel = translate_string(utf8_to_wide(unescape_string(label))); - FieldSpec spec( - name, - wlabel, - utf8_to_wide(unescape_string(default_val)), - 258 + m_fields.size(), - 0, - ECI_IBEAM - ); + FieldSpec spec(name, wlabel, utf8_to_wide(unescape_string(default_val)), + 258 + m_fields.size(), 0, ECI_IBEAM); createTextField(data, spec, rect, type == "textarea"); @@ -1680,23 +1641,24 @@ void GUIFormSpecMenu::parseTextArea(parserData* data, std::vector& m_fields.push_back(spec); } -void GUIFormSpecMenu::parseField(parserData* data, const std::string &element, - const std::string &type) +void GUIFormSpecMenu::parseField( + parserData *data, const std::string &element, const std::string &type) { - std::vector parts = split(element,';'); + std::vector parts = split(element, ';'); if (parts.size() == 3 || parts.size() == 4) { - parseSimpleField(data,parts); + parseSimpleField(data, parts); return; } if ((parts.size() == 5) || - ((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION))) - { - parseTextArea(data,parts,type); + ((parts.size() > 5) && + (m_formspec_version > FORMSPEC_API_VERSION))) { + parseTextArea(data, parts, type); return; } - errorstream<< "Invalid field element(" << parts.size() << "): '" << element << "'" << std::endl; + errorstream << "Invalid field element(" << parts.size() << "): '" << element + << "'" << std::endl; } void GUIFormSpecMenu::parseHyperText(parserData *data, const std::string &element) @@ -1704,7 +1666,8 @@ void GUIFormSpecMenu::parseHyperText(parserData *data, const std::string &elemen std::vector parts = split(element, ';'); if (parts.size() != 4 && m_formspec_version < FORMSPEC_API_VERSION) { - errorstream << "Invalid text element(" << parts.size() << "): '" << element << "'" << std::endl; + errorstream << "Invalid text element(" << parts.size() << "): '" + << element << "'" << std::endl; return; } @@ -1731,17 +1694,14 @@ void GUIFormSpecMenu::parseHyperText(parserData *data, const std::string &elemen pos.Y += m_btn_height; } - core::rect rect = core::rect(pos.X, pos.Y, pos.X + geom.X, pos.Y + geom.Y); + core::rect rect = + core::rect(pos.X, pos.Y, pos.X + geom.X, pos.Y + geom.Y); - if(m_form_src) + if (m_form_src) text = m_form_src->resolveText(text); - FieldSpec spec( - name, - utf8_to_wide(unescape_string(text)), - L"", - 258 + m_fields.size() - ); + FieldSpec spec(name, utf8_to_wide(unescape_string(text)), L"", + 258 + m_fields.size()); spec.ftype = f_HyperText; GUIHyperText *e = new GUIHyperText(spec.flabel.c_str(), Environment, @@ -1751,20 +1711,21 @@ void GUIFormSpecMenu::parseHyperText(parserData *data, const std::string &elemen m_fields.push_back(spec); } -void GUIFormSpecMenu::parseLabel(parserData* data, const std::string &element) +void GUIFormSpecMenu::parseLabel(parserData *data, const std::string &element) { - std::vector parts = split(element,';'); + std::vector parts = split(element, ';'); if ((parts.size() == 2) || - ((parts.size() > 2) && (m_formspec_version > FORMSPEC_API_VERSION))) - { - std::vector v_pos = split(parts[0],','); + ((parts.size() > 2) && + (m_formspec_version > FORMSPEC_API_VERSION))) { + std::vector v_pos = split(parts[0], ','); std::string text = parts[1]; - MY_CHECKPOS("label",0); + MY_CHECKPOS("label", 0); - if(!data->explicit_size) - warningstream<<"invalid use of label without a size[] element"<explicit_size) + warningstream << "invalid use of label without a size[] element" + << std::endl; std::vector lines = split(text, '\n'); @@ -1775,7 +1736,7 @@ void GUIFormSpecMenu::parseLabel(parserData* data, const std::string &element) for (unsigned int i = 0; i != lines.size(); i++) { std::wstring wlabel_colors = translate_string( - utf8_to_wide(unescape_string(lines[i]))); + utf8_to_wide(unescape_string(lines[i]))); // Without color escapes to get the font dimensions std::wstring wlabel_plain = unescape_enriched(wlabel_colors); @@ -1790,12 +1751,13 @@ void GUIFormSpecMenu::parseLabel(parserData* data, const std::string &element) v2s32 pos = getRealCoordinateBasePos(v_pos); // Labels are positioned by their center, not their top. - pos.Y += (((float) imgsize.Y) / -2) + (((float) imgsize.Y) * i / 2); + pos.Y += (((float)imgsize.Y) / -2) + + (((float)imgsize.Y) * i / 2); - rect = core::rect( - pos.X, pos.Y, - pos.X + font->getDimension(wlabel_plain.c_str()).Width, - pos.Y + imgsize.Y); + rect = core::rect(pos.X, pos.Y, + pos.X + font->getDimension(wlabel_plain.c_str()) + .Width, + pos.Y + imgsize.Y); } else { // Lines are spaced at the nominal distance of @@ -1812,28 +1774,23 @@ void GUIFormSpecMenu::parseLabel(parserData* data, const std::string &element) pos.X += stof(v_pos[0]) * spacing.X; pos.Y += (stof(v_pos[1]) + 7.0f / 30.0f) * spacing.Y; - pos.Y += ((float) i) * spacing.Y * 2.0 / 5.0; + pos.Y += ((float)i) * spacing.Y * 2.0 / 5.0; - rect = core::rect( - pos.X, pos.Y - m_btn_height, - pos.X + font->getDimension(wlabel_plain.c_str()).Width, - pos.Y + m_btn_height); + rect = core::rect(pos.X, pos.Y - m_btn_height, + pos.X + font->getDimension(wlabel_plain.c_str()) + .Width, + pos.Y + m_btn_height); } - FieldSpec spec( - "", - wlabel_colors, - L"", - 258 + m_fields.size(), - 4 - ); + FieldSpec spec("", wlabel_colors, L"", 258 + m_fields.size(), 4); gui::IGUIStaticText *e = gui::StaticText::add(Environment, - spec.flabel.c_str(), rect, false, false, data->current_parent, - spec.fid); + spec.flabel.c_str(), rect, false, false, + data->current_parent, spec.fid); e->setTextAlignment(gui::EGUIA_UPPERLEFT, gui::EGUIA_CENTER); e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); - e->setOverrideColor(style.getColor(StyleSpec::TEXTCOLOR, video::SColor(0xFFFFFFFF))); + e->setOverrideColor(style.getColor( + StyleSpec::TEXTCOLOR, video::SColor(0xFFFFFFFF))); e->setOverrideFont(font); m_fields.push_back(spec); @@ -1846,21 +1803,21 @@ void GUIFormSpecMenu::parseLabel(parserData* data, const std::string &element) return; } errorstream << "Invalid label element(" << parts.size() << "): '" << element - << "'" << std::endl; + << "'" << std::endl; } -void GUIFormSpecMenu::parseVertLabel(parserData* data, const std::string &element) +void GUIFormSpecMenu::parseVertLabel(parserData *data, const std::string &element) { - std::vector parts = split(element,';'); + std::vector parts = split(element, ';'); if ((parts.size() == 2) || - ((parts.size() > 2) && (m_formspec_version > FORMSPEC_API_VERSION))) - { - std::vector v_pos = split(parts[0],','); + ((parts.size() > 2) && + (m_formspec_version > FORMSPEC_API_VERSION))) { + std::vector v_pos = split(parts[0], ','); std::wstring text = unescape_translate( - unescape_string(utf8_to_wide(parts[1]))); + unescape_string(utf8_to_wide(parts[1]))); - MY_CHECKPOS("vertlabel",1); + MY_CHECKPOS("vertlabel", 1); auto style = getDefaultStyleForElement("vertlabel", "", "label"); gui::IGUIFont *font = style.getFont(); @@ -1878,10 +1835,10 @@ void GUIFormSpecMenu::parseVertLabel(parserData* data, const std::string &elemen // We use text.length + 1 because without it, the rect // isn't quite tall enough and cuts off the text. - rect = core::rect(pos.X, pos.Y, - pos.X + imgsize.X, - pos.Y + font_line_height(font) * - (text.length() + 1)); + rect = core::rect(pos.X, pos.Y, pos.X + imgsize.X, + pos.Y + font_line_height(font) * + (text.length() + + 1)); } else { pos = getElementBasePos(&v_pos); @@ -1889,16 +1846,19 @@ void GUIFormSpecMenu::parseVertLabel(parserData* data, const std::string &elemen // As above, the length must be one longer. The width of // the rect (15 pixels) seems rather arbitrary, but // changing it might break something. - rect = core::rect( - pos.X, pos.Y+((imgsize.Y/2) - m_btn_height), - pos.X+15, pos.Y + - font_line_height(font) * - (text.length() + 1) + - ((imgsize.Y/2) - m_btn_height)); + rect = core::rect(pos.X, + pos.Y + ((imgsize.Y / 2) - m_btn_height), + pos.X + 15, + pos.Y + + font_line_height(font) * + (text.length() + + 1) + + ((imgsize.Y / 2) - m_btn_height)); } - if(!data->explicit_size) - warningstream<<"invalid use of label without a size[] element"<explicit_size) + warningstream << "invalid use of label without a size[] element" + << std::endl; std::wstring label; @@ -1907,18 +1867,15 @@ void GUIFormSpecMenu::parseVertLabel(parserData* data, const std::string &elemen label += L"\n"; } - FieldSpec spec( - "", - label, - L"", - 258 + m_fields.size() - ); - gui::IGUIStaticText *e = gui::StaticText::add(Environment, spec.flabel.c_str(), - rect, false, false, data->current_parent, spec.fid); + FieldSpec spec("", label, L"", 258 + m_fields.size()); + gui::IGUIStaticText *e = gui::StaticText::add(Environment, + spec.flabel.c_str(), rect, false, false, + data->current_parent, spec.fid); e->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_CENTER); e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); - e->setOverrideColor(style.getColor(StyleSpec::TEXTCOLOR, video::SColor(0xFFFFFFFF))); + e->setOverrideColor(style.getColor( + StyleSpec::TEXTCOLOR, video::SColor(0xFFFFFFFF))); e->setOverrideFont(font); m_fields.push_back(spec); @@ -1928,25 +1885,26 @@ void GUIFormSpecMenu::parseVertLabel(parserData* data, const std::string &elemen m_clickthrough_elements.push_back(e); return; } - errorstream<< "Invalid vertlabel element(" << parts.size() << "): '" << element << "'" << std::endl; + errorstream << "Invalid vertlabel element(" << parts.size() << "): '" << element + << "'" << std::endl; } -void GUIFormSpecMenu::parseImageButton(parserData* data, const std::string &element, - const std::string &type) +void GUIFormSpecMenu::parseImageButton( + parserData *data, const std::string &element, const std::string &type) { - std::vector parts = split(element,';'); + std::vector parts = split(element, ';'); if ((((parts.size() >= 5) && (parts.size() <= 8)) && (parts.size() != 6)) || - ((parts.size() > 8) && (m_formspec_version > FORMSPEC_API_VERSION))) - { - std::vector v_pos = split(parts[0],','); - std::vector v_geom = split(parts[1],','); + ((parts.size() > 8) && + (m_formspec_version > FORMSPEC_API_VERSION))) { + std::vector v_pos = split(parts[0], ','); + std::vector v_geom = split(parts[1], ','); std::string image_name = parts[2]; std::string name = parts[3]; std::string label = parts[4]; - MY_CHECKPOS("imagebutton",0); - MY_CHECKGEOM("imagebutton",1); + MY_CHECKPOS("imagebutton", 0); + MY_CHECKGEOM("imagebutton", 1); std::string pressed_image_name; @@ -1966,23 +1924,21 @@ void GUIFormSpecMenu::parseImageButton(parserData* data, const std::string &elem geom.Y = (stof(v_geom[1]) * spacing.Y) - (spacing.Y - imgsize.Y); } - core::rect rect = core::rect(pos.X, pos.Y, pos.X+geom.X, - pos.Y+geom.Y); + core::rect rect = core::rect( + pos.X, pos.Y, pos.X + geom.X, pos.Y + geom.Y); if (!data->explicit_size) - warningstream<<"invalid use of image_button without a size[] element"<= 7) { style[StyleSpec::STATE_DEFAULT].set(StyleSpec::NOCLIP, parts[5]); @@ -2015,18 +1972,19 @@ void GUIFormSpecMenu::parseImageButton(parserData* data, const std::string &elem return; } - errorstream<< "Invalid imagebutton element(" << parts.size() << "): '" << element << "'" << std::endl; + errorstream << "Invalid imagebutton element(" << parts.size() << "): '" << element + << "'" << std::endl; } -void GUIFormSpecMenu::parseTabHeader(parserData* data, const std::string &element) +void GUIFormSpecMenu::parseTabHeader(parserData *data, const std::string &element) { std::vector parts = split(element, ';'); - if (((parts.size() == 4) || (parts.size() == 6)) || (parts.size() == 7 && - data->real_coordinates) || ((parts.size() > 6) && - (m_formspec_version > FORMSPEC_API_VERSION))) - { - std::vector v_pos = split(parts[0],','); + if (((parts.size() == 4) || (parts.size() == 6)) || + (parts.size() == 7 && data->real_coordinates) || + ((parts.size() > 6) && + (m_formspec_version > FORMSPEC_API_VERSION))) { + std::vector v_pos = split(parts[0], ','); // If we're using real coordinates, add an extra field for height. // Width is not here because tabs are the width of the text, and @@ -2044,9 +2002,9 @@ void GUIFormSpecMenu::parseTabHeader(parserData* data, const std::string &elemen auto_width = false; } - std::string name = parts[i+1]; - std::vector buttons = split(parts[i+2], ','); - std::string str_index = parts[i+3]; + std::string name = parts[i + 1]; + std::vector buttons = split(parts[i + 2], ','); + std::string str_index = parts[i + 3]; bool show_background = true; bool show_border = true; int tab_index = stoi(str_index) - 1; @@ -2054,18 +2012,13 @@ void GUIFormSpecMenu::parseTabHeader(parserData* data, const std::string &elemen MY_CHECKPOS("tabheader", 0); if (parts.size() == 6 + i) { - if (parts[4+i] == "true") + if (parts[4 + i] == "true") show_background = false; - if (parts[5+i] == "false") + if (parts[5 + i] == "false") show_border = false; } - FieldSpec spec( - name, - L"", - L"", - 258 + m_fields.size() - ); + FieldSpec spec(name, L"", L"", 258 + m_fields.size()); spec.ftype = f_TabHeader; @@ -2094,11 +2047,12 @@ void GUIFormSpecMenu::parseTabHeader(parserData* data, const std::string &elemen geom.X = DesiredRect.getWidth(); } - core::rect rect = core::rect(pos.X, pos.Y, pos.X+geom.X, - pos.Y+geom.Y); + core::rect rect = core::rect( + pos.X, pos.Y, pos.X + geom.X, pos.Y + geom.Y); - gui::IGUITabControl *e = Environment->addTabControl(rect, - data->current_parent, show_background, show_border, spec.fid); + gui::IGUITabControl *e = + Environment->addTabControl(rect, data->current_parent, + show_background, show_border, spec.fid); e->setAlignment(irr::gui::EGUIA_UPPERLEFT, irr::gui::EGUIA_UPPERLEFT, irr::gui::EGUIA_UPPERLEFT, irr::gui::EGUIA_LOWERRIGHT); e->setTabHeight(geom.Y); @@ -2107,41 +2061,45 @@ void GUIFormSpecMenu::parseTabHeader(parserData* data, const std::string &elemen e->setNotClipped(style.getBool(StyleSpec::NOCLIP, true)); for (const std::string &button : buttons) { - auto tab = e->addTab(unescape_translate(unescape_string( - utf8_to_wide(button))).c_str(), -1); + auto tab = e->addTab( + unescape_translate(unescape_string(utf8_to_wide( + button))) + .c_str(), + -1); if (style.isNotDefault(StyleSpec::BGCOLOR)) - tab->setBackgroundColor(style.getColor(StyleSpec::BGCOLOR)); + tab->setBackgroundColor( + style.getColor(StyleSpec::BGCOLOR)); - tab->setTextColor(style.getColor(StyleSpec::TEXTCOLOR, video::SColor(0xFFFFFFFF))); + tab->setTextColor(style.getColor( + StyleSpec::TEXTCOLOR, video::SColor(0xFFFFFFFF))); } - if ((tab_index >= 0) && - (buttons.size() < INT_MAX) && - (tab_index < (int) buttons.size())) + if ((tab_index >= 0) && (buttons.size() < INT_MAX) && + (tab_index < (int)buttons.size())) e->setActiveTab(tab_index); m_fields.push_back(spec); return; } - errorstream << "Invalid TabHeader element(" << parts.size() << "): '" - << element << "'" << std::endl; + errorstream << "Invalid TabHeader element(" << parts.size() << "): '" << element + << "'" << std::endl; } -void GUIFormSpecMenu::parseItemImageButton(parserData* data, const std::string &element) +void GUIFormSpecMenu::parseItemImageButton(parserData *data, const std::string &element) { if (m_client == 0) { warningstream << "invalid use of item_image_button with m_client==0" - << std::endl; + << std::endl; return; } - std::vector parts = split(element,';'); + std::vector parts = split(element, ';'); if ((parts.size() == 5) || - ((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION))) - { - std::vector v_pos = split(parts[0],','); - std::vector v_geom = split(parts[1],','); + ((parts.size() > 5) && + (m_formspec_version > FORMSPEC_API_VERSION))) { + std::vector v_pos = split(parts[0], ','); + std::vector v_geom = split(parts[1], ','); std::string item_name = parts[2]; std::string name = parts[3]; std::string label = parts[4]; @@ -2149,8 +2107,8 @@ void GUIFormSpecMenu::parseItemImageButton(parserData* data, const std::string & label = unescape_string(label); item_name = unescape_string(item_name); - MY_CHECKPOS("itemimagebutton",0); - MY_CHECKGEOM("itemimagebutton",1); + MY_CHECKPOS("itemimagebutton", 0); + MY_CHECKGEOM("itemimagebutton", 1); v2s32 pos; v2s32 geom; @@ -2164,34 +2122,32 @@ void GUIFormSpecMenu::parseItemImageButton(parserData* data, const std::string & geom.Y = (stof(v_geom[1]) * spacing.Y) - (spacing.Y - imgsize.Y); } - core::rect rect = core::rect(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y); + core::rect rect = core::rect( + pos.X, pos.Y, pos.X + geom.X, pos.Y + geom.Y); - if(!data->explicit_size) - warningstream<<"invalid use of item_image_button without a size[] element"<explicit_size) + warningstream << "invalid use of item_image_button without a " + "size[] element" + << std::endl; IItemDefManager *idef = m_client->idef(); ItemStack item; item.deSerialize(item_name, idef); - m_tooltips[name] = - TooltipSpec(utf8_to_wide(item.getDefinition(idef).description), - m_default_tooltip_bgcolor, - m_default_tooltip_color); + m_tooltips[name] = TooltipSpec( + utf8_to_wide(item.getDefinition(idef).description), + m_default_tooltip_bgcolor, m_default_tooltip_color); // the spec for the button - FieldSpec spec_btn( - name, - utf8_to_wide(label), - utf8_to_wide(item_name), - 258 + m_fields.size(), - 2 - ); + FieldSpec spec_btn(name, utf8_to_wide(label), utf8_to_wide(item_name), + 258 + m_fields.size(), 2); GUIButtonItemImage *e_btn = GUIButtonItemImage::addButton(Environment, - rect, m_tsrc, data->current_parent, spec_btn.fid, spec_btn.flabel.c_str(), - item_name, m_client); + rect, m_tsrc, data->current_parent, spec_btn.fid, + spec_btn.flabel.c_str(), item_name, m_client); - auto style = getStyleForElement("item_image_button", spec_btn.fname, "image_button"); + auto style = getStyleForElement( + "item_image_button", spec_btn.fname, "image_button"); e_btn->setStyles(style); if (spec_btn.fname == m_focused_element) { @@ -2199,26 +2155,27 @@ void GUIFormSpecMenu::parseItemImageButton(parserData* data, const std::string & } spec_btn.ftype = f_Button; - rect += data->basepos-padding; + rect += data->basepos - padding; spec_btn.rect = rect; m_fields.push_back(spec_btn); return; } - errorstream<< "Invalid ItemImagebutton element(" << parts.size() << "): '" << element << "'" << std::endl; + errorstream << "Invalid ItemImagebutton element(" << parts.size() << "): '" + << element << "'" << std::endl; } -void GUIFormSpecMenu::parseBox(parserData* data, const std::string &element) +void GUIFormSpecMenu::parseBox(parserData *data, const std::string &element) { - std::vector parts = split(element,';'); + std::vector parts = split(element, ';'); if ((parts.size() == 3) || - ((parts.size() > 3) && (m_formspec_version > FORMSPEC_API_VERSION))) - { - std::vector v_pos = split(parts[0],','); - std::vector v_geom = split(parts[1],','); + ((parts.size() > 3) && + (m_formspec_version > FORMSPEC_API_VERSION))) { + std::vector v_pos = split(parts[0], ','); + std::vector v_geom = split(parts[1], ','); - MY_CHECKPOS("box",0); - MY_CHECKGEOM("box",1); + MY_CHECKPOS("box", 0); + MY_CHECKGEOM("box", 1); v2s32 pos; v2s32 geom; @@ -2235,44 +2192,42 @@ void GUIFormSpecMenu::parseBox(parserData* data, const std::string &element) video::SColor tmp_color; if (parseColorString(parts[2], tmp_color, false, 0x8C)) { - FieldSpec spec( - "", - L"", - L"", - 258 + m_fields.size(), - -2 - ); + FieldSpec spec("", L"", L"", 258 + m_fields.size(), -2); spec.ftype = f_Box; core::rect rect(pos, pos + geom); - GUIBox *e = new GUIBox(Environment, data->current_parent, spec.fid, - rect, tmp_color); + GUIBox *e = new GUIBox(Environment, data->current_parent, + spec.fid, rect, tmp_color); auto style = getDefaultStyleForElement("box", spec.fname); - e->setNotClipped(style.getBool(StyleSpec::NOCLIP, m_formspec_version < 3)); + e->setNotClipped(style.getBool( + StyleSpec::NOCLIP, m_formspec_version < 3)); e->drop(); m_fields.push_back(spec); } else { - errorstream<< "Invalid Box element(" << parts.size() << "): '" << element << "' INVALID COLOR" << std::endl; + errorstream << "Invalid Box element(" << parts.size() << "): '" + << element << "' INVALID COLOR" << std::endl; } return; } - errorstream<< "Invalid Box element(" << parts.size() << "): '" << element << "'" << std::endl; + errorstream << "Invalid Box element(" << parts.size() << "): '" << element << "'" + << std::endl; } -void GUIFormSpecMenu::parseBackgroundColor(parserData* data, const std::string &element) +void GUIFormSpecMenu::parseBackgroundColor(parserData *data, const std::string &element) { - std::vector parts = split(element,';'); + std::vector parts = split(element, ';'); const u32 parameter_count = parts.size(); if ((parameter_count > 2 && m_formspec_version < 3) || - (parameter_count > 3 && m_formspec_version <= FORMSPEC_API_VERSION)) { + (parameter_count > 3 && + m_formspec_version <= FORMSPEC_API_VERSION)) { errorstream << "Invalid bgcolor element(" << parameter_count << "): '" - << element << "'" << std::endl; + << element << "'" << std::endl; return; } @@ -2299,19 +2254,20 @@ void GUIFormSpecMenu::parseBackgroundColor(parserData* data, const std::string & parseColorString(parts[2], m_fullscreen_bgcolor, false); } -void GUIFormSpecMenu::parseListColors(parserData* data, const std::string &element) +void GUIFormSpecMenu::parseListColors(parserData *data, const std::string &element) { - std::vector parts = split(element,';'); + std::vector parts = split(element, ';'); if (((parts.size() == 2) || (parts.size() == 3) || (parts.size() == 5)) || - ((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION))) - { + ((parts.size() > 5) && + (m_formspec_version > FORMSPEC_API_VERSION))) { parseColorString(parts[0], data->inventorylist_options.slotbg_n, false); parseColorString(parts[1], data->inventorylist_options.slotbg_h, false); if (parts.size() >= 3) { - if (parseColorString(parts[2], data->inventorylist_options.slotbordercolor, - false)) { + if (parseColorString(parts[2], + data->inventorylist_options.slotbordercolor, + false)) { data->inventorylist_options.slotborder = true; } } @@ -2333,15 +2289,16 @@ void GUIFormSpecMenu::parseListColors(parserData* data, const std::string &eleme } return; } - errorstream<< "Invalid listcolors element(" << parts.size() << "): '" << element << "'" << std::endl; + errorstream << "Invalid listcolors element(" << parts.size() << "): '" << element + << "'" << std::endl; } -void GUIFormSpecMenu::parseTooltip(parserData* data, const std::string &element) +void GUIFormSpecMenu::parseTooltip(parserData *data, const std::string &element) { - std::vector parts = split(element,';'); + std::vector parts = split(element, ';'); if (parts.size() < 2) { errorstream << "Invalid tooltip element(" << parts.size() << "): '" - << element << "'" << std::endl; + << element << "'" << std::endl; return; } @@ -2350,18 +2307,19 @@ void GUIFormSpecMenu::parseTooltip(parserData* data, const std::string &element) size_t base_size = rect_mode ? 3 : 2; if (parts.size() != base_size && parts.size() != base_size + 2) { errorstream << "Invalid tooltip element(" << parts.size() << "): '" - << element << "'" << std::endl; + << element << "'" << std::endl; return; } // Read colors video::SColor bgcolor = m_default_tooltip_bgcolor; - video::SColor color = m_default_tooltip_color; + video::SColor color = m_default_tooltip_color; if (parts.size() == base_size + 2 && (!parseColorString(parts[base_size], bgcolor, false) || - !parseColorString(parts[base_size + 1], color, false))) { + !parseColorString(parts[base_size + 1], color, + false))) { errorstream << "Invalid color in tooltip element(" << parts.size() - << "): '" << element << "'" << std::endl; + << "): '" << element << "'" << std::endl; return; } @@ -2371,7 +2329,7 @@ void GUIFormSpecMenu::parseTooltip(parserData* data, const std::string &element) // Add tooltip if (rect_mode) { - std::vector v_pos = split(parts[0], ','); + std::vector v_pos = split(parts[0], ','); std::vector v_geom = split(parts[1], ','); MY_CHECKPOS("tooltip", 0); @@ -2389,12 +2347,7 @@ void GUIFormSpecMenu::parseTooltip(parserData* data, const std::string &element) geom.Y = stof(v_geom[1]) * spacing.Y; } - FieldSpec fieldspec( - "", - L"", - L"", - 258 + m_fields.size() - ); + FieldSpec fieldspec("", L"", L"", 258 + m_fields.size()); core::rect rect(pos, pos + geom); @@ -2414,11 +2367,11 @@ void GUIFormSpecMenu::parseTooltip(parserData* data, const std::string &element) bool GUIFormSpecMenu::parseVersionDirect(const std::string &data) { - //some prechecks + // some prechecks if (data.empty()) return false; - std::vector parts = split(data,'['); + std::vector parts = split(data, '['); if (parts.size() < 2) { return false; @@ -2436,12 +2389,12 @@ bool GUIFormSpecMenu::parseVersionDirect(const std::string &data) return false; } -bool GUIFormSpecMenu::parseSizeDirect(parserData* data, const std::string &element) +bool GUIFormSpecMenu::parseSizeDirect(parserData *data, const std::string &element) { if (element.empty()) return false; - std::vector parts = split(element,'['); + std::vector parts = split(element, '['); if (parts.size() < 2) return false; @@ -2453,7 +2406,8 @@ bool GUIFormSpecMenu::parseSizeDirect(parserData* data, const std::string &eleme return false; if (type == "invsize") - warningstream << "Deprecated formspec element \"invsize\" is used" << std::endl; + warningstream << "Deprecated formspec element \"invsize\" is used" + << std::endl; parseSize(data, description); @@ -2491,7 +2445,8 @@ void GUIFormSpecMenu::parsePosition(parserData *data, const std::string &element return; } - errorstream << "Invalid position element (" << parts.size() << "): '" << element << "'" << std::endl; + errorstream << "Invalid position element (" << parts.size() << "): '" << element + << "'" << std::endl; } bool GUIFormSpecMenu::parseAnchorDirect(parserData *data, const std::string &element) @@ -2526,16 +2481,17 @@ void GUIFormSpecMenu::parseAnchor(parserData *data, const std::string &element) } errorstream << "Invalid anchor element (" << parts.size() << "): '" << element - << "'" << std::endl; + << "'" << std::endl; } -bool GUIFormSpecMenu::parseStyle(parserData *data, const std::string &element, bool style_type) +bool GUIFormSpecMenu::parseStyle( + parserData *data, const std::string &element, bool style_type) { std::vector parts = split(element, ';'); if (parts.size() < 2) { - errorstream << "Invalid style element (" << parts.size() << "): '" << element - << "'" << std::endl; + errorstream << "Invalid style element (" << parts.size() << "): '" + << element << "'" << std::endl; return false; } @@ -2545,22 +2501,24 @@ bool GUIFormSpecMenu::parseStyle(parserData *data, const std::string &element, b for (size_t i = 1; i < parts.size(); i++) { size_t equal_pos = parts[i].find('='); if (equal_pos == std::string::npos) { - errorstream << "Invalid style element (Property missing value): '" << element - << "'" << std::endl; + errorstream << "Invalid style element (Property missing value): '" + << element << "'" << std::endl; return false; } std::string propname = trim(parts[i].substr(0, equal_pos)); - std::string value = trim(unescape_string(parts[i].substr(equal_pos + 1))); + std::string value = trim(unescape_string(parts[i].substr(equal_pos + 1))); - std::transform(propname.begin(), propname.end(), propname.begin(), ::tolower); + std::transform(propname.begin(), propname.end(), propname.begin(), + ::tolower); StyleSpec::Property prop = StyleSpec::GetPropertyByName(propname); if (prop == StyleSpec::NONE) { if (property_warned.find(propname) != property_warned.end()) { - warningstream << "Invalid style element (Unknown property " << propname << "): '" - << element - << "'" << std::endl; + warningstream << "Invalid style element (Unknown " + "property " + << propname << "): '" << element << "'" + << std::endl; property_warned.insert(propname); } continue; @@ -2585,16 +2543,18 @@ bool GUIFormSpecMenu::parseStyle(parserData *data, const std::string &element, b selector = selector.substr(0, state_pos); if (state_str.empty()) { - errorstream << "Invalid style element (Invalid state): '" << element - << "'" << std::endl; + errorstream << "Invalid style element (Invalid state): '" + << element << "'" << std::endl; state_valid = false; } else { std::vector states = split(state_str, '+'); for (std::string &state : states) { - StyleSpec::State converted = StyleSpec::getStateByName(state); + StyleSpec::State converted = + StyleSpec::getStateByName(state); if (converted == StyleSpec::STATE_INVALID) { - infostream << "Unknown style state " << state << - " in element '" << element << "'" << std::endl; + infostream << "Unknown style state " + << state << " in element '" + << element << "'" << std::endl; state_valid = false; break; } @@ -2616,20 +2576,29 @@ bool GUIFormSpecMenu::parseStyle(parserData *data, const std::string &element, b } // Backwards-compatibility for existing _hovered/_pressed properties - if (selector_spec.hasProperty(StyleSpec::BGCOLOR_HOVERED) - || selector_spec.hasProperty(StyleSpec::BGIMG_HOVERED) - || selector_spec.hasProperty(StyleSpec::FGIMG_HOVERED)) { + if (selector_spec.hasProperty(StyleSpec::BGCOLOR_HOVERED) || + selector_spec.hasProperty(StyleSpec::BGIMG_HOVERED) || + selector_spec.hasProperty(StyleSpec::FGIMG_HOVERED)) { StyleSpec hover_spec; hover_spec.addState(StyleSpec::STATE_HOVERED); if (selector_spec.hasProperty(StyleSpec::BGCOLOR_HOVERED)) { - hover_spec.set(StyleSpec::BGCOLOR, selector_spec.get(StyleSpec::BGCOLOR_HOVERED, "")); + hover_spec.set(StyleSpec::BGCOLOR, + selector_spec.get( + StyleSpec::BGCOLOR_HOVERED, + "")); } if (selector_spec.hasProperty(StyleSpec::BGIMG_HOVERED)) { - hover_spec.set(StyleSpec::BGIMG, selector_spec.get(StyleSpec::BGIMG_HOVERED, "")); + hover_spec.set(StyleSpec::BGIMG, + selector_spec.get( + StyleSpec::BGIMG_HOVERED, + "")); } if (selector_spec.hasProperty(StyleSpec::FGIMG_HOVERED)) { - hover_spec.set(StyleSpec::FGIMG, selector_spec.get(StyleSpec::FGIMG_HOVERED, "")); + hover_spec.set(StyleSpec::FGIMG, + selector_spec.get( + StyleSpec::FGIMG_HOVERED, + "")); } if (style_type) { @@ -2638,20 +2607,29 @@ bool GUIFormSpecMenu::parseStyle(parserData *data, const std::string &element, b theme_by_name[selector].push_back(hover_spec); } } - if (selector_spec.hasProperty(StyleSpec::BGCOLOR_PRESSED) - || selector_spec.hasProperty(StyleSpec::BGIMG_PRESSED) - || selector_spec.hasProperty(StyleSpec::FGIMG_PRESSED)) { + if (selector_spec.hasProperty(StyleSpec::BGCOLOR_PRESSED) || + selector_spec.hasProperty(StyleSpec::BGIMG_PRESSED) || + selector_spec.hasProperty(StyleSpec::FGIMG_PRESSED)) { StyleSpec press_spec; press_spec.addState(StyleSpec::STATE_PRESSED); if (selector_spec.hasProperty(StyleSpec::BGCOLOR_PRESSED)) { - press_spec.set(StyleSpec::BGCOLOR, selector_spec.get(StyleSpec::BGCOLOR_PRESSED, "")); + press_spec.set(StyleSpec::BGCOLOR, + selector_spec.get( + StyleSpec::BGCOLOR_PRESSED, + "")); } if (selector_spec.hasProperty(StyleSpec::BGIMG_PRESSED)) { - press_spec.set(StyleSpec::BGIMG, selector_spec.get(StyleSpec::BGIMG_PRESSED, "")); + press_spec.set(StyleSpec::BGIMG, + selector_spec.get( + StyleSpec::BGIMG_PRESSED, + "")); } if (selector_spec.hasProperty(StyleSpec::FGIMG_PRESSED)) { - press_spec.set(StyleSpec::FGIMG, selector_spec.get(StyleSpec::FGIMG_PRESSED, "")); + press_spec.set(StyleSpec::FGIMG, + selector_spec.get( + StyleSpec::FGIMG_PRESSED, + "")); } if (style_type) { @@ -2670,8 +2648,7 @@ void GUIFormSpecMenu::parseSetFocus(const std::string &element) std::vector parts = split(element, ';'); if (parts.size() <= 2 || - (parts.size() > 2 && m_formspec_version > FORMSPEC_API_VERSION)) - { + (parts.size() > 2 && m_formspec_version > FORMSPEC_API_VERSION)) { if (m_is_form_regenerated) return; // Never focus on resizing @@ -2683,12 +2660,12 @@ void GUIFormSpecMenu::parseSetFocus(const std::string &element) } errorstream << "Invalid set_focus element (" << parts.size() << "): '" << element - << "'" << std::endl; + << "'" << std::endl; } -void GUIFormSpecMenu::parseElement(parserData* data, const std::string &element) +void GUIFormSpecMenu::parseElement(parserData *data, const std::string &element) { - //some prechecks + // some prechecks if (element.empty()) return; @@ -2700,7 +2677,7 @@ void GUIFormSpecMenu::parseElement(parserData* data, const std::string &element) return; std::string type = trim(element.substr(0, pos)); - std::string description = element.substr(pos+1); + std::string description = element.substr(pos + 1); if (type == "container") { parseContainer(data, description); @@ -2752,28 +2729,28 @@ void GUIFormSpecMenu::parseElement(parserData* data, const std::string &element) return; } - if (type == "tableoptions"){ - parseTableOptions(data,description); + if (type == "tableoptions") { + parseTableOptions(data, description); return; } - if (type == "tablecolumns"){ - parseTableColumns(data,description); + if (type == "tablecolumns") { + parseTableColumns(data, description); return; } - if (type == "table"){ - parseTable(data,description); + if (type == "table") { + parseTable(data, description); return; } - if (type == "textlist"){ - parseTextList(data,description); + if (type == "textlist") { + parseTextList(data, description); return; } - if (type == "dropdown"){ - parseDropDown(data,description); + if (type == "dropdown") { + parseDropDown(data, description); return; } @@ -2783,62 +2760,62 @@ void GUIFormSpecMenu::parseElement(parserData* data, const std::string &element) } if (type == "pwdfield") { - parsePwdField(data,description); + parsePwdField(data, description); return; } - if ((type == "field") || (type == "textarea")){ - parseField(data,description,type); + if ((type == "field") || (type == "textarea")) { + parseField(data, description, type); return; } if (type == "hypertext") { - parseHyperText(data,description); + parseHyperText(data, description); return; } if (type == "label") { - parseLabel(data,description); + parseLabel(data, description); return; } if (type == "vertlabel") { - parseVertLabel(data,description); + parseVertLabel(data, description); return; } if (type == "item_image_button") { - parseItemImageButton(data,description); + parseItemImageButton(data, description); return; } if ((type == "image_button") || (type == "image_button_exit")) { - parseImageButton(data,description,type); + parseImageButton(data, description, type); return; } if (type == "tabheader") { - parseTabHeader(data,description); + parseTabHeader(data, description); return; } if (type == "box") { - parseBox(data,description); + parseBox(data, description); return; } if (type == "bgcolor") { - parseBackgroundColor(data,description); + parseBackgroundColor(data, description); return; } if (type == "listcolors") { - parseListColors(data,description); + parseListColors(data, description); return; } if (type == "tooltip") { - parseTooltip(data,description); + parseTooltip(data, description); return; } @@ -2883,8 +2860,8 @@ void GUIFormSpecMenu::parseElement(parserData* data, const std::string &element) } // Ignore others - infostream << "Unknown DrawSpec: type=" << type << ", data=\"" << description << "\"" - << std::endl; + infostream << "Unknown DrawSpec: type=" << type << ", data=\"" << description + << "\"" << std::endl; } void GUIFormSpecMenu::regenerateGui(v2u32 screensize) @@ -2979,33 +2956,33 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) { v3f formspec_bgcolor = g_settings->getV3F("formspec_default_bg_color"); - m_bgcolor = video::SColor( - (u8) clamp_u8(g_settings->getS32("formspec_default_bg_opacity")), - clamp_u8(myround(formspec_bgcolor.X)), - clamp_u8(myround(formspec_bgcolor.Y)), - clamp_u8(myround(formspec_bgcolor.Z)) - ); + m_bgcolor = video::SColor((u8)clamp_u8(g_settings->getS32( + "formspec_default_bg_opacity")), + clamp_u8(myround(formspec_bgcolor.X)), + clamp_u8(myround(formspec_bgcolor.Y)), + clamp_u8(myround(formspec_bgcolor.Z))); } { v3f formspec_bgcolor = g_settings->getV3F("formspec_fullscreen_bg_color"); m_fullscreen_bgcolor = video::SColor( - (u8) clamp_u8(g_settings->getS32("formspec_fullscreen_bg_opacity")), - clamp_u8(myround(formspec_bgcolor.X)), - clamp_u8(myround(formspec_bgcolor.Y)), - clamp_u8(myround(formspec_bgcolor.Z)) - ); + (u8)clamp_u8(g_settings->getS32( + "formspec_fullscreen_bg_opacity")), + clamp_u8(myround(formspec_bgcolor.X)), + clamp_u8(myround(formspec_bgcolor.Y)), + clamp_u8(myround(formspec_bgcolor.Z))); } - m_default_tooltip_bgcolor = video::SColor(255,110,130,60); - m_default_tooltip_color = video::SColor(255,255,255,255); + m_default_tooltip_bgcolor = video::SColor(255, 110, 130, 60); + m_default_tooltip_color = video::SColor(255, 255, 255, 255); // Add tooltip { assert(!m_tooltip_element); - // Note: parent != this so that the tooltip isn't clipped by the menu rectangle - m_tooltip_element = gui::StaticText::add(Environment, L"", - core::rect(0, 0, 110, 18)); + // Note: parent != this so that the tooltip isn't clipped by the menu + // rectangle + m_tooltip_element = gui::StaticText::add( + Environment, L"", core::rect(0, 0, 110, 18)); m_tooltip_element->enableOverrideColor(true); m_tooltip_element->setBackgroundColor(m_default_tooltip_bgcolor); m_tooltip_element->setDrawBackground(true); @@ -3013,11 +2990,11 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) m_tooltip_element->setOverrideColor(m_default_tooltip_color); m_tooltip_element->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_CENTER); m_tooltip_element->setWordWrap(false); - //we're not parent so no autograb for this one! + // we're not parent so no autograb for this one! m_tooltip_element->grab(); } - std::vector elements = split(m_formspec_string,']'); + std::vector elements = split(m_formspec_string, ']'); unsigned int i = 0; /* try to read version from first element only */ @@ -3029,27 +3006,28 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) /* we need size first in order to calculate image scale */ mydata.explicit_size = false; - for (; i< elements.size(); i++) { + for (; i < elements.size(); i++) { if (!parseSizeDirect(&mydata, elements[i])) { break; } } /* "position" element is always after "size" element if it used */ - for (; i< elements.size(); i++) { + for (; i < elements.size(); i++) { if (!parsePositionDirect(&mydata, elements[i])) { break; } } /* "anchor" element is always after "position" (or "size" element) if it used */ - for (; i< elements.size(); i++) { + for (; i < elements.size(); i++) { if (!parseAnchorDirect(&mydata, elements[i])) { break; } } - /* "no_prepend" element is always after "position" (or "size" element) if it used */ + /* "no_prepend" element is always after "position" (or "size" element) if it used + */ bool enable_prepends = true; for (; i < elements.size(); i++) { if (elements[i].empty()) @@ -3076,7 +3054,8 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) if (mydata.explicit_size) { // compute scaling for specified form size if (m_lock) { - v2u32 current_screensize = RenderingEngine::get_video_driver()->getScreenSize(); + v2u32 current_screensize = RenderingEngine::get_video_driver() + ->getScreenSize(); v2u32 delta = current_screensize - m_lockscreensize; if (current_screensize.Y > m_lockscreensize.Y) @@ -3089,11 +3068,11 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) else delta.X = 0; - offset = v2s32(delta.X,delta.Y); + offset = v2s32(delta.X, delta.Y); mydata.screensize = m_lockscreensize; } else { - offset = v2s32(0,0); + offset = v2s32(0, 0); } double gui_scaling = g_settings->getFloat("gui_scaling"); @@ -3130,21 +3109,23 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) // maximum screen space available. double prefer_imgsize = mydata.screensize.Y / 10 * gui_scaling; double fitx_imgsize = mydata.screensize.X / - ((12.0 / 8.0) * (0.5 + mydata.invsize.X)); + ((12.0 / 8.0) * (0.5 + mydata.invsize.X)); double fity_imgsize = mydata.screensize.Y / - ((15.0 / 11.0) * (0.85 + mydata.invsize.Y)); + ((15.0 / 11.0) * (0.85 + mydata.invsize.Y)); use_imgsize = MYMIN(prefer_imgsize, MYMIN(fitx_imgsize, fity_imgsize)); #else double prefer_imgsize = mydata.screensize.Y / 15 * gui_scaling; double fitx_imgsize = mydata.screensize.X / - ((5.0 / 4.0) * (0.5 + mydata.invsize.X)); + ((5.0 / 4.0) * (0.5 + mydata.invsize.X)); double fity_imgsize = mydata.screensize.Y / - ((15.0 / 13.0) * (0.85 * mydata.invsize.Y)); + ((15.0 / 13.0) * (0.85 * mydata.invsize.Y)); double screen_dpi = RenderingEngine::getDisplayDensity() * 96; double min_imgsize = 0.3 * screen_dpi * gui_scaling; - use_imgsize = MYMAX(min_imgsize, MYMIN(prefer_imgsize, - MYMIN(fitx_imgsize, fity_imgsize))); + use_imgsize = MYMAX(min_imgsize, + MYMIN(prefer_imgsize, + MYMIN(fitx_imgsize, + fity_imgsize))); #endif } @@ -3157,30 +3138,45 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) // is 2/5 vertical inventory slot spacing, and button // half-height is 7/8 of font height. imgsize = v2s32(use_imgsize, use_imgsize); - spacing = v2f32(use_imgsize*5.0/4, use_imgsize*15.0/13); - padding = v2s32(use_imgsize*3.0/8, use_imgsize*3.0/8); - m_btn_height = use_imgsize*15.0/13 * 0.35; + spacing = v2f32(use_imgsize * 5.0 / 4, use_imgsize * 15.0 / 13); + padding = v2s32(use_imgsize * 3.0 / 8, use_imgsize * 3.0 / 8); + m_btn_height = use_imgsize * 15.0 / 13 * 0.35; m_font = g_fontengine->getFont(); if (mydata.real_coordinates) { - mydata.size = v2s32( - mydata.invsize.X*imgsize.X, - mydata.invsize.Y*imgsize.Y - ); + mydata.size = v2s32(mydata.invsize.X * imgsize.X, + mydata.invsize.Y * imgsize.Y); } else { mydata.size = v2s32( - padding.X*2+spacing.X*(mydata.invsize.X-1.0)+imgsize.X, - padding.Y*2+spacing.Y*(mydata.invsize.Y-1.0)+imgsize.Y + m_btn_height*2.0/3.0 - ); + padding.X * 2 + + spacing.X * (mydata.invsize.X - + 1.0) + + imgsize.X, + padding.Y * 2 + + spacing.Y * (mydata.invsize.Y - + 1.0) + + imgsize.Y + + m_btn_height * 2.0 / 3.0); } DesiredRect = mydata.rect = core::rect( - (s32)((f32)mydata.screensize.X * mydata.offset.X) - (s32)(mydata.anchor.X * (f32)mydata.size.X) + offset.X, - (s32)((f32)mydata.screensize.Y * mydata.offset.Y) - (s32)(mydata.anchor.Y * (f32)mydata.size.Y) + offset.Y, - (s32)((f32)mydata.screensize.X * mydata.offset.X) + (s32)((1.0 - mydata.anchor.X) * (f32)mydata.size.X) + offset.X, - (s32)((f32)mydata.screensize.Y * mydata.offset.Y) + (s32)((1.0 - mydata.anchor.Y) * (f32)mydata.size.Y) + offset.Y - ); + (s32)((f32)mydata.screensize.X * mydata.offset.X) - + (s32)(mydata.anchor.X * + (f32)mydata.size.X) + + offset.X, + (s32)((f32)mydata.screensize.Y * mydata.offset.Y) - + (s32)(mydata.anchor.Y * + (f32)mydata.size.Y) + + offset.Y, + (s32)((f32)mydata.screensize.X * mydata.offset.X) + + (s32)((1.0 - mydata.anchor.X) * + (f32)mydata.size.X) + + offset.X, + (s32)((f32)mydata.screensize.Y * mydata.offset.Y) + + (s32)((1.0 - mydata.anchor.Y) * + (f32)mydata.size.Y) + + offset.Y); } else { // Non-size[] form must consist only of text fields and // implicit "Proceed" button. Use default font, and @@ -3188,11 +3184,14 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) m_font = g_fontengine->getFont(); m_btn_height = font_line_height(m_font) * 0.875; DesiredRect = core::rect( - (s32)((f32)mydata.screensize.X * mydata.offset.X) - (s32)(mydata.anchor.X * 580.0), - (s32)((f32)mydata.screensize.Y * mydata.offset.Y) - (s32)(mydata.anchor.Y * 300.0), - (s32)((f32)mydata.screensize.X * mydata.offset.X) + (s32)((1.0 - mydata.anchor.X) * 580.0), - (s32)((f32)mydata.screensize.Y * mydata.offset.Y) + (s32)((1.0 - mydata.anchor.Y) * 300.0) - ); + (s32)((f32)mydata.screensize.X * mydata.offset.X) - + (s32)(mydata.anchor.X * 580.0), + (s32)((f32)mydata.screensize.Y * mydata.offset.Y) - + (s32)(mydata.anchor.Y * 300.0), + (s32)((f32)mydata.screensize.X * mydata.offset.X) + + (s32)((1.0 - mydata.anchor.X) * 580.0), + (s32)((f32)mydata.screensize.Y * mydata.offset.Y) + + (s32)((1.0 - mydata.anchor.Y) * 300.0)); } recalculateAbsolutePosition(false); mydata.basepos = getBasePos(); @@ -3209,12 +3208,14 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) core::list::Iterator legacy_sort_start = Children.getLast(); if (enable_prepends) { - // Backup the coordinates so that prepends can use the coordinates of choice. + // Backup the coordinates so that prepends can use the coordinates of + // choice. bool rc_backup = mydata.real_coordinates; u16 version_backup = m_formspec_version; mydata.real_coordinates = false; // Old coordinates by default. - std::vector prepend_elements = split(m_formspec_prepend, ']'); + std::vector prepend_elements = + split(m_formspec_prepend, ']'); for (const auto &element : prepend_elements) parseElement(&mydata, element); @@ -3230,20 +3231,22 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) mydata.real_coordinates = rc_backup; // Restore coordinates } - for (; i< elements.size(); i++) { + for (; i < elements.size(); i++) { parseElement(&mydata, elements[i]); } if (mydata.current_parent != this) { - errorstream << "Invalid formspec string: scroll_container was never closed!" - << std::endl; + errorstream << "Invalid formspec string: scroll_container was never " + "closed!" + << std::endl; } else if (!container_stack.empty()) { errorstream << "Invalid formspec string: container was never closed!" - << std::endl; + << std::endl; } // get the scrollbar elements for scroll_containers - for (const std::pair &c : m_scroll_containers) { + for (const std::pair &c : + m_scroll_containers) { for (const std::pair &b : m_scrollbars) { if (c.first == b.first.fname) { c.second->setScrollBar(b.second); @@ -3255,12 +3258,11 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) // If there are fields without explicit size[], add a "Proceed" // button and adjust size to fit all the fields. if (mydata.simple_field_count > 0 && !mydata.explicit_size) { - mydata.rect = core::rect( - mydata.screensize.X / 2 - 580 / 2, + mydata.rect = core::rect(mydata.screensize.X / 2 - 580 / 2, mydata.screensize.Y / 2 - 300 / 2, mydata.screensize.X / 2 + 580 / 2, - mydata.screensize.Y / 2 + 240 / 2 + mydata.simple_field_count * 60 - ); + mydata.screensize.Y / 2 + 240 / 2 + + mydata.simple_field_count * 60); DesiredRect = mydata.rect; recalculateAbsolutePosition(false); @@ -3271,21 +3273,19 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) pos.Y = (mydata.simple_field_count + 2) * 60; v2s32 size = DesiredRect.getSize(); - mydata.rect = core::rect( - size.X / 2 - 70, pos.Y, - size.X / 2 - 70 + 140, pos.Y + m_btn_height * 2 - ); + mydata.rect = core::rect(size.X / 2 - 70, pos.Y, + size.X / 2 - 70 + 140, pos.Y + m_btn_height * 2); const wchar_t *text = wgettext("Proceed"); - GUIButton::addButton(Environment, mydata.rect, m_tsrc, this, 257, text); + GUIButton::addButton(Environment, mydata.rect, m_tsrc, this, 257, + text); delete[] text; } } // Set initial focus if parser didn't set it gui::IGUIElement *focused_element = Environment->getFocus(); - if (!focused_element - || !isMyChild(focused_element) - || focused_element->getType() == gui::EGUIET_TAB_CONTROL) + if (!focused_element || !isMyChild(focused_element) || + focused_element->getType() == gui::EGUIET_TAB_CONTROL) setInitialFocus(); skin->setFont(old_font); @@ -3329,12 +3329,12 @@ void GUIFormSpecMenu::legacySortElements(core::list::Iterator fro // 2: Sort the container std::stable_sort(elements.begin(), elements.end(), - [this] (const IGUIElement *a, const IGUIElement *b) -> bool { - const FieldSpec *spec_a = getSpecByID(a->getID()); - const FieldSpec *spec_b = getSpecByID(b->getID()); - return spec_a && spec_b && - spec_a->priority < spec_b->priority; - }); + [this](const IGUIElement *a, const IGUIElement *b) -> bool { + const FieldSpec *spec_a = getSpecByID(a->getID()); + const FieldSpec *spec_b = getSpecByID(b->getID()); + return spec_a && spec_b && + spec_a->priority < spec_b->priority; + }); // 3: Re-assign the pointers for (auto e : elements) { @@ -3379,8 +3379,8 @@ GUIInventoryList::ItemSpec GUIFormSpecMenu::getItemAtPos(v2s32 p) const for (const GUIInventoryList *e : m_inventorylists) { s32 item_index = e->getItemIndexAtPos(p); if (item_index != -1) - return GUIInventoryList::ItemSpec(e->getInventoryloc(), e->getListname(), - item_index); + return GUIInventoryList::ItemSpec(e->getInventoryloc(), + e->getListname(), item_index); } return GUIInventoryList::ItemSpec(InventoryLocation(), "", -1); @@ -3388,13 +3388,13 @@ GUIInventoryList::ItemSpec GUIFormSpecMenu::getItemAtPos(v2s32 p) const void GUIFormSpecMenu::drawSelectedItem() { - video::IVideoDriver* driver = Environment->getVideoDriver(); + video::IVideoDriver *driver = Environment->getVideoDriver(); if (!m_selected_item) { // reset rotation time drawItemStack(driver, m_font, ItemStack(), - core::rect(v2s32(0, 0), v2s32(0, 0)), NULL, - m_client, IT_ROT_DRAGGED); + core::rect(v2s32(0, 0), v2s32(0, 0)), NULL, m_client, + IT_ROT_DRAGGED); return; } @@ -3405,7 +3405,7 @@ void GUIFormSpecMenu::drawSelectedItem() ItemStack stack = list->getItem(m_selected_item->i); stack.count = m_selected_amount; - core::rect imgrect(0,0,imgsize.X,imgsize.Y); + core::rect imgrect(0, 0, imgsize.X, imgsize.Y); core::rect rect = imgrect + (m_pointer - imgrect.getCenter()); rect.constrainTo(driver->getViewPort()); drawItemStack(driver, m_font, stack, rect, NULL, m_client, IT_ROT_DRAGGED); @@ -3422,7 +3422,7 @@ void GUIFormSpecMenu::drawMenu() } } - gui::IGUISkin* skin = Environment->getSkin(); + gui::IGUISkin *skin = Environment->getSkin(); sanity_check(skin != NULL); gui::IGUIFont *old_font = skin->getFont(); skin->setFont(m_font); @@ -3431,7 +3431,7 @@ void GUIFormSpecMenu::drawMenu() updateSelectedItem(); - video::IVideoDriver* driver = Environment->getVideoDriver(); + video::IVideoDriver *driver = Environment->getVideoDriver(); /* Draw background color @@ -3491,8 +3491,8 @@ void GUIFormSpecMenu::drawMenu() if (m_hovered_item_tooltips.empty()) { // reset rotation time drawItemStack(driver, m_font, ItemStack(), - core::rect(v2s32(0, 0), v2s32(0, 0)), - NULL, m_client, IT_ROT_HOVERED); + core::rect(v2s32(0, 0), v2s32(0, 0)), NULL, m_client, + IT_ROT_HOVERED); } /* TODO find way to show tooltips on touchscreen */ @@ -3507,8 +3507,8 @@ void GUIFormSpecMenu::drawMenu() Environment->getRootGUIElement()->getElementFromPoint(m_pointer); #ifndef HAVE_TOUCHSCREENGUI - gui::ICursorControl *cursor_control = RenderingEngine::get_raw_device()-> - getCursorControl(); + gui::ICursorControl *cursor_control = + RenderingEngine::get_raw_device()->getCursorControl(); gui::ECURSOR_ICON current_cursor_icon = cursor_control->getActiveIcon(); #endif bool hovered_element_found = false; @@ -3525,7 +3525,8 @@ void GUIFormSpecMenu::drawMenu() m_old_tooltip_id = id; } else { if (id == m_old_tooltip_id) { - delta = porting::getDeltaMs(m_hovered_time, porting::getTimeMs()); + delta = porting::getDeltaMs( + m_hovered_time, porting::getTimeMs()); } else { m_hovered_time = porting::getTimeMs(); m_old_tooltip_id = id; @@ -3540,14 +3541,19 @@ void GUIFormSpecMenu::drawMenu() continue; if (delta >= m_tooltip_show_delay) { - const std::wstring &text = m_tooltips[field.fname].tooltip; + const std::wstring &text = + m_tooltips[field.fname].tooltip; if (!text.empty()) - showTooltip(text, m_tooltips[field.fname].color, - m_tooltips[field.fname].bgcolor); + showTooltip(text, + m_tooltips[field.fname] + .color, + m_tooltips[field.fname] + .bgcolor); } #ifndef HAVE_TOUCHSCREENGUI - if (field.ftype != f_HyperText && // Handled directly in guiHyperText + if (field.ftype != f_HyperText && // Handled directly in + // guiHyperText current_cursor_icon != field.fcursor_icon) cursor_control->setActiveIcon(field.fcursor_icon); #endif @@ -3577,9 +3583,8 @@ void GUIFormSpecMenu::drawMenu() skin->setFont(old_font); } - void GUIFormSpecMenu::showTooltip(const std::wstring &text, - const irr::video::SColor &color, const irr::video::SColor &bgcolor) + const irr::video::SColor &color, const irr::video::SColor &bgcolor) { EnrichedString ntext(text); ntext.setDefaultColor(color); @@ -3596,7 +3601,7 @@ void GUIFormSpecMenu::showTooltip(const std::wstring &text, int tooltip_offset_y = m_btn_height; #ifdef __ANDROID__ tooltip_offset_x *= 3; - tooltip_offset_y = 0; + tooltip_offset_y = 0; if (m_pointer.X > (s32)screenSize.X / 2) tooltip_offset_x = -(tooltip_offset_x + tooltip_width); @@ -3609,16 +3614,13 @@ void GUIFormSpecMenu::showTooltip(const std::wstring &text, s32 tooltip_x = m_pointer.X + tooltip_offset_x; s32 tooltip_y = m_pointer.Y + tooltip_offset_y; if (tooltip_x + tooltip_width > (s32)screenSize.X) - tooltip_x = (s32)screenSize.X - tooltip_width - m_btn_height; + tooltip_x = (s32)screenSize.X - tooltip_width - m_btn_height; if (tooltip_y + tooltip_height > (s32)screenSize.Y) tooltip_y = (s32)screenSize.Y - tooltip_height - m_btn_height; - m_tooltip_element->setRelativePosition( - core::rect( + m_tooltip_element->setRelativePosition(core::rect( core::position2d(tooltip_x, tooltip_y), - core::dimension2d(tooltip_width, tooltip_height) - ) - ); + core::dimension2d(tooltip_width, tooltip_height))); // Display the tooltip m_tooltip_element->setVisible(true); @@ -3673,17 +3675,23 @@ ItemStack GUIFormSpecMenu::verifySelectedItem() if (m_selected_item) { if (m_selected_item->isValid()) { - Inventory *inv = m_invmgr->getInventory(m_selected_item->inventoryloc); + Inventory *inv = m_invmgr->getInventory( + m_selected_item->inventoryloc); if (inv) { - InventoryList *list = inv->getList(m_selected_item->listname); - if (list && (u32) m_selected_item->i < list->getSize()) { - ItemStack stack = list->getItem(m_selected_item->i); + InventoryList *list = + inv->getList(m_selected_item->listname); + if (list && (u32)m_selected_item->i < list->getSize()) { + ItemStack stack = + list->getItem(m_selected_item->i); if (!m_selected_swap.empty()) { if (m_selected_swap.name == stack.name && - m_selected_swap.count == stack.count) + m_selected_swap.count == + stack.count) m_selected_swap.clear(); } else { - m_selected_amount = std::min(m_selected_amount, stack.count); + m_selected_amount = std::min( + m_selected_amount, + stack.count); } if (!stack.empty()) @@ -3701,10 +3709,9 @@ ItemStack GUIFormSpecMenu::verifySelectedItem() return ItemStack(); } -void GUIFormSpecMenu::acceptInput(FormspecQuitMode quitmode=quit_mode_no) +void GUIFormSpecMenu::acceptInput(FormspecQuitMode quitmode = quit_mode_no) { - if(m_text_dst) - { + if (m_text_dst) { StringMap fields; if (quitmode == quit_mode_accept) { @@ -3753,50 +3760,74 @@ void GUIFormSpecMenu::acceptInput(FormspecQuitMode quitmode=quit_mode_no) fields[name] = table->checkEvent(); } } else if (s.ftype == f_DropDown) { - // No dynamic cast possible due to some distributions shipped - // without rtti support in Irrlicht - IGUIElement *element = getElementFromId(s.fid, true); + // No dynamic cast possible due to some + // distributions shipped without rtti support in + // Irrlicht + IGUIElement *element = + getElementFromId(s.fid, true); gui::IGUIComboBox *e = NULL; - if ((element) && (element->getType() == gui::EGUIET_COMBO_BOX)) { - e = static_cast(element); + if ((element) && + (element->getType() == + gui::EGUIET_COMBO_BOX)) { + e = static_cast( + element); } else { - warningstream << "GUIFormSpecMenu::acceptInput: dropdown " - << "field without dropdown element" << std::endl; + warningstream << "GUIFormSpecMenu::" + "acceptInput: dropdown " + << "field without dropdown " + "element" + << std::endl; continue; } s32 selected = e->getSelected(); if (selected >= 0) { - if (m_dropdown_index_event.find(s.fname) != - m_dropdown_index_event.end()) { - fields[name] = std::to_string(selected + 1); + if (m_dropdown_index_event.find( + s.fname) != + m_dropdown_index_event + .end()) { + fields[name] = std::to_string( + selected + 1); } else { std::vector *dropdown_values = - getDropDownValues(s.fname); - if (dropdown_values && selected < (s32)dropdown_values->size()) - fields[name] = (*dropdown_values)[selected]; + getDropDownValues( + s.fname); + if (dropdown_values && + selected < (s32)dropdown_values->size()) + fields[name] = (*dropdown_values) + [selected]; } } } else if (s.ftype == f_TabHeader) { - // No dynamic cast possible due to some distributions shipped - // without rtti support in Irrlicht - IGUIElement *element = getElementFromId(s.fid, true); + // No dynamic cast possible due to some + // distributions shipped without rtti support in + // Irrlicht + IGUIElement *element = + getElementFromId(s.fid, true); gui::IGUITabControl *e = nullptr; - if ((element) && (element->getType() == gui::EGUIET_TAB_CONTROL)) { - e = static_cast(element); + if ((element) && + (element->getType() == + gui::EGUIET_TAB_CONTROL)) { + e = static_cast( + element); } if (e != 0) { std::stringstream ss; - ss << (e->getActiveTab() +1); + ss << (e->getActiveTab() + 1); fields[name] = ss.str(); } } else if (s.ftype == f_CheckBox) { - // No dynamic cast possible due to some distributions shipped - // without rtti support in Irrlicht - IGUIElement *element = getElementFromId(s.fid, true); + // No dynamic cast possible due to some + // distributions shipped without rtti support in + // Irrlicht + IGUIElement *element = + getElementFromId(s.fid, true); gui::IGUICheckBox *e = nullptr; - if ((element) && (element->getType() == gui::EGUIET_CHECK_BOX)) { - e = static_cast(element); + if ((element) && + (element->getType() == + gui::EGUIET_CHECK_BOX)) { + e = static_cast( + element); } if (e != 0) { @@ -3806,11 +3837,14 @@ void GUIFormSpecMenu::acceptInput(FormspecQuitMode quitmode=quit_mode_no) fields[name] = "false"; } } else if (s.ftype == f_ScrollBar) { - // No dynamic cast possible due to some distributions shipped - // without rtti support in Irrlicht - IGUIElement *element = getElementFromId(s.fid, true); + // No dynamic cast possible due to some + // distributions shipped without rtti support in + // Irrlicht + IGUIElement *element = + getElementFromId(s.fid, true); GUIScrollBar *e = nullptr; - if (element && element->getType() == gui::EGUIET_ELEMENT) + if (element && element->getType() == + gui::EGUIET_ELEMENT) e = static_cast(element); if (e) { @@ -3820,17 +3854,22 @@ void GUIFormSpecMenu::acceptInput(FormspecQuitMode quitmode=quit_mode_no) fields[name] = "CHG:" + os.str(); else fields[name] = "VAL:" + os.str(); - } + } } else if (s.ftype == f_AnimatedImage) { - // No dynamic cast possible due to some distributions shipped - // without rtti support in Irrlicht - IGUIElement *element = getElementFromId(s.fid, true); + // No dynamic cast possible due to some + // distributions shipped without rtti support in + // Irrlicht + IGUIElement *element = + getElementFromId(s.fid, true); GUIAnimatedImage *e = nullptr; - if (element && element->getType() == gui::EGUIET_ELEMENT) - e = static_cast(element); + if (element && element->getType() == + gui::EGUIET_ELEMENT) + e = static_cast( + element); if (e) - fields[name] = std::to_string(e->getFrameIndex() + 1); + fields[name] = std::to_string( + e->getFrameIndex() + 1); } else { IGUIElement *e = getElementFromId(s.fid, true); if (e) @@ -3843,7 +3882,7 @@ void GUIFormSpecMenu::acceptInput(FormspecQuitMode quitmode=quit_mode_no) } } -bool GUIFormSpecMenu::preprocessEvent(const SEvent& event) +bool GUIFormSpecMenu::preprocessEvent(const SEvent &event) { // The IGUITabControl renders visually using the skin's selected // font, which we override for the duration of form drawing, @@ -3856,11 +3895,11 @@ bool GUIFormSpecMenu::preprocessEvent(const SEvent& event) s32 x = event.MouseInput.X; s32 y = event.MouseInput.Y; gui::IGUIElement *hovered = - Environment->getRootGUIElement()->getElementFromPoint( - core::position2d(x, y)); + Environment->getRootGUIElement()->getElementFromPoint( + core::position2d(x, y)); if (hovered && isMyChild(hovered) && hovered->getType() == gui::EGUIET_TAB_CONTROL) { - gui::IGUISkin* skin = Environment->getSkin(); + gui::IGUISkin *skin = Environment->getSkin(); sanity_check(skin != NULL); gui::IGUIFont *old_font = skin->getFont(); skin->setFont(m_font); @@ -3872,16 +3911,19 @@ bool GUIFormSpecMenu::preprocessEvent(const SEvent& event) // Fix Esc/Return key being eaten by checkboxen and tables if (event.EventType == EET_KEY_INPUT_EVENT) { - KeyPress kp(event.KeyInput); - if (kp == EscapeKey || kp == CancelKey - || kp == getKeySetting("keymap_inventory") - || event.KeyInput.Key==KEY_RETURN) { + KeyPress kp(event.KeyInput); + if (kp == EscapeKey || kp == CancelKey || + kp == getKeySetting("keymap_inventory") || + event.KeyInput.Key == KEY_RETURN) { gui::IGUIElement *focused = Environment->getFocus(); if (focused && isMyChild(focused) && (focused->getType() == gui::EGUIET_LIST_BOX || - focused->getType() == gui::EGUIET_CHECK_BOX) && - (focused->getParent()->getType() != gui::EGUIET_COMBO_BOX || - event.KeyInput.Key != KEY_RETURN)) { + focused->getType() == + gui::EGUIET_CHECK_BOX) && + (focused->getParent()->getType() != + gui::EGUIET_COMBO_BOX || + event.KeyInput.Key != + KEY_RETURN)) { OnEvent(event); return true; } @@ -3890,13 +3932,14 @@ bool GUIFormSpecMenu::preprocessEvent(const SEvent& event) // Mouse wheel and move events: send to hovered element instead of focused if (event.EventType == EET_MOUSE_INPUT_EVENT && (event.MouseInput.Event == EMIE_MOUSE_WHEEL || - (event.MouseInput.Event == EMIE_MOUSE_MOVED && - event.MouseInput.ButtonStates == 0))) { + (event.MouseInput.Event == EMIE_MOUSE_MOVED && + event.MouseInput.ButtonStates == + 0))) { s32 x = event.MouseInput.X; s32 y = event.MouseInput.Y; gui::IGUIElement *hovered = - Environment->getRootGUIElement()->getElementFromPoint( - core::position2d(x, y)); + Environment->getRootGUIElement()->getElementFromPoint( + core::position2d(x, y)); if (hovered && isMyChild(hovered)) { hovered->OnEvent(event); return event.MouseInput.Event == EMIE_MOUSE_WHEEL; @@ -3949,13 +3992,19 @@ enum ButtonEventType : u8 BET_OTHER }; -bool GUIFormSpecMenu::OnEvent(const SEvent& event) +bool GUIFormSpecMenu::OnEvent(const SEvent &event) { - if (event.EventType==EET_KEY_INPUT_EVENT) { + if (event.EventType == EET_KEY_INPUT_EVENT) { KeyPress kp(event.KeyInput); - if (event.KeyInput.PressedDown && ( - (kp == EscapeKey) || (kp == CancelKey) || - ((m_client != NULL) && (kp == getKeySetting("keymap_inventory"))))) { + if (event.KeyInput.PressedDown && + ((kp == EscapeKey) || (kp == CancelKey) || + ((m_client != NULL) && + (kp == getKeySetting("key" + "map" + "_in" + "ven" + "tor" + "y"))))) { tryClose(); return true; } @@ -3965,29 +4014,30 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) m_client->makeScreenshot(); } - if (event.KeyInput.PressedDown && kp == getKeySetting("keymap_toggle_debug")) + if (event.KeyInput.PressedDown && + kp == getKeySetting("keymap_toggle_debug")) m_show_debug = !m_show_debug; if (event.KeyInput.PressedDown && - (event.KeyInput.Key==KEY_RETURN || - event.KeyInput.Key==KEY_UP || - event.KeyInput.Key==KEY_DOWN) - ) { + (event.KeyInput.Key == KEY_RETURN || + event.KeyInput.Key == KEY_UP || + event.KeyInput.Key == KEY_DOWN)) { switch (event.KeyInput.Key) { - case KEY_RETURN: - current_keys_pending.key_enter = true; - break; - case KEY_UP: - current_keys_pending.key_up = true; - break; - case KEY_DOWN: - current_keys_pending.key_down = true; - break; + case KEY_RETURN: + current_keys_pending.key_enter = true; + break; + case KEY_UP: + current_keys_pending.key_up = true; + break; + case KEY_DOWN: + current_keys_pending.key_down = true; + break; + break; + default: + // can't happen at all! + FATAL_ERROR("Reached a source line that can't ever been " + "reached"); break; - default: - //can't happen at all! - FATAL_ERROR("Reached a source line that can't ever been reached"); - break; } if (current_keys_pending.key_enter && m_allowclose) { acceptInput(quit_mode_accept); @@ -3997,7 +4047,6 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) } return true; } - } /* Mouse event other than movement, or crossing the border of inventory @@ -4005,9 +4054,11 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) */ if (event.EventType == EET_MOUSE_INPUT_EVENT && (event.MouseInput.Event != EMIE_MOUSE_MOVED || - (event.MouseInput.Event == EMIE_MOUSE_MOVED && - event.MouseInput.isRightPressed() && - getItemAtPos(m_pointer).i != getItemAtPos(m_old_pointer).i))) { + (event.MouseInput.Event == EMIE_MOUSE_MOVED && + event.MouseInput.isRightPressed() && + getItemAtPos(m_pointer).i != + getItemAtPos(m_old_pointer) + .i))) { // Get selected item and hovered/clicked item (s) @@ -4020,74 +4071,88 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) InventoryList *list_s = NULL; if (m_selected_item) { - inv_selected = m_invmgr->getInventory(m_selected_item->inventoryloc); + inv_selected = m_invmgr->getInventory( + m_selected_item->inventoryloc); sanity_check(inv_selected); - sanity_check(inv_selected->getList(m_selected_item->listname) != NULL); + sanity_check(inv_selected->getList(m_selected_item->listname) != + NULL); } u32 s_count = 0; if (s.isValid()) - do { // breakable - inv_s = m_invmgr->getInventory(s.inventoryloc); + do { // breakable + inv_s = m_invmgr->getInventory(s.inventoryloc); - if (!inv_s) { - errorstream << "InventoryMenu: The selected inventory location " - << "\"" << s.inventoryloc.dump() << "\" doesn't exist" - << std::endl; - s.i = -1; // make it invalid again - break; - } + if (!inv_s) { + errorstream << "InventoryMenu: The selected " + "inventory location " + << "\"" << s.inventoryloc.dump() + << "\" doesn't exist" << std::endl; + s.i = -1; // make it invalid again + break; + } - list_s = inv_s->getList(s.listname); - if (list_s == NULL) { - verbosestream << "InventoryMenu: The selected inventory list \"" - << s.listname << "\" does not exist" << std::endl; - s.i = -1; // make it invalid again - break; - } + list_s = inv_s->getList(s.listname); + if (list_s == NULL) { + verbosestream << "InventoryMenu: The selected " + "inventory list \"" + << s.listname << "\" does not exist" + << std::endl; + s.i = -1; // make it invalid again + break; + } - if ((u32)s.i >= list_s->getSize()) { - infostream << "InventoryMenu: The selected inventory list \"" - << s.listname << "\" is too small (i=" << s.i << ", size=" - << list_s->getSize() << ")" << std::endl; - s.i = -1; // make it invalid again - break; - } + if ((u32)s.i >= list_s->getSize()) { + infostream << "InventoryMenu: The selected " + "inventory list \"" + << s.listname + << "\" is too small (i=" << s.i + << ", size=" << list_s->getSize() + << ")" << std::endl; + s.i = -1; // make it invalid again + break; + } - s_count = list_s->getItem(s.i).count; - } while(0); + s_count = list_s->getItem(s.i).count; + } while (0); bool identical = m_selected_item && s.isValid() && - (inv_selected == inv_s) && - (m_selected_item->listname == s.listname) && - (m_selected_item->i == s.i); + (inv_selected == inv_s) && + (m_selected_item->listname == s.listname) && + (m_selected_item->i == s.i); ButtonEventType button = BET_LEFT; ButtonEventType updown = BET_OTHER; switch (event.MouseInput.Event) { case EMIE_LMOUSE_PRESSED_DOWN: - button = BET_LEFT; updown = BET_DOWN; + button = BET_LEFT; + updown = BET_DOWN; break; case EMIE_RMOUSE_PRESSED_DOWN: - button = BET_RIGHT; updown = BET_DOWN; + button = BET_RIGHT; + updown = BET_DOWN; break; case EMIE_MMOUSE_PRESSED_DOWN: - button = BET_MIDDLE; updown = BET_DOWN; + button = BET_MIDDLE; + updown = BET_DOWN; break; case EMIE_MOUSE_WHEEL: - button = (event.MouseInput.Wheel > 0) ? - BET_WHEEL_UP : BET_WHEEL_DOWN; + button = (event.MouseInput.Wheel > 0) ? BET_WHEEL_UP + : BET_WHEEL_DOWN; updown = BET_DOWN; break; case EMIE_LMOUSE_LEFT_UP: - button = BET_LEFT; updown = BET_UP; + button = BET_LEFT; + updown = BET_UP; break; case EMIE_RMOUSE_LEFT_UP: - button = BET_RIGHT; updown = BET_UP; + button = BET_RIGHT; + updown = BET_UP; break; case EMIE_MMOUSE_LEFT_UP: - button = BET_MIDDLE; updown = BET_UP; + button = BET_MIDDLE; + updown = BET_UP; break; case EMIE_MOUSE_MOVED: updown = BET_MOVE; @@ -4115,7 +4180,7 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) case BET_DOWN: // Some mouse button has been pressed - //infostream << "Mouse button " << button << " pressed at p=(" + // infostream << "Mouse button " << button << " pressed at p=(" // << event.MouseInput.X << "," << event.MouseInput.Y << ")" // << std::endl; @@ -4126,8 +4191,10 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) craft_amount = (button == BET_MIDDLE ? 10 : 1); } else if (!m_selected_item) { if (s_count && button != BET_WHEEL_UP) { - // Non-empty stack has been clicked: select or shift-move it - m_selected_item = new GUIInventoryList::ItemSpec(s); + // Non-empty stack has been clicked: select or + // shift-move it + m_selected_item = + new GUIInventoryList::ItemSpec(s); u32 count; if (button == BET_RIGHT) @@ -4136,17 +4203,22 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) count = MYMIN(s_count, 10); else if (button == BET_WHEEL_DOWN) count = 1; - else // left + else // left count = s_count; if (!event.MouseInput.Shift) { // no shift: select item m_selected_amount = count; - m_selected_dragging = button != BET_WHEEL_DOWN; + m_selected_dragging = + button != BET_WHEEL_DOWN; m_auto_place = false; } else { - // shift pressed: move item, right click moves 1 - shift_move_amount = button == BET_RIGHT ? 1 : count; + // shift pressed: move item, right click + // moves 1 + shift_move_amount = + button == BET_RIGHT + ? 1 + : count; } } } else { // m_selected_item != NULL @@ -4157,7 +4229,8 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) if (button == BET_RIGHT || button == BET_WHEEL_UP) move_amount = 1; else if (button == BET_MIDDLE) - move_amount = MYMIN(m_selected_amount, 10); + move_amount = MYMIN( + m_selected_amount, 10); else if (button == BET_LEFT) move_amount = m_selected_amount; // else wheeldown @@ -4167,38 +4240,44 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) if (m_selected_amount < s_count) ++m_selected_amount; } else { - if (move_amount >= m_selected_amount) + if (move_amount >= + m_selected_amount) m_selected_amount = 0; else - m_selected_amount -= move_amount; + m_selected_amount -= + move_amount; move_amount = 0; } } - } else if (!getAbsoluteClippingRect().isPointInside(m_pointer) - && button != BET_WHEEL_DOWN) { + } else if (!getAbsoluteClippingRect().isPointInside( + m_pointer) && + button != BET_WHEEL_DOWN) { // Clicked outside of the window: drop if (button == BET_RIGHT || button == BET_WHEEL_UP) drop_amount = 1; else if (button == BET_MIDDLE) - drop_amount = MYMIN(m_selected_amount, 10); - else // left + drop_amount = MYMIN( + m_selected_amount, 10); + else // left drop_amount = m_selected_amount; } } - break; + break; case BET_UP: // Some mouse button has been released - //infostream<<"Mouse button "<getList(m_selected_item->listname); + // Only move an item if the destination slot is + // empty or contains the same item type as what is + // going to be moved + InventoryList *list_from = inv_selected->getList( + m_selected_item->listname); InventoryList *list_to = list_s; assert(list_from && list_to); - ItemStack stack_from = list_from->getItem(m_selected_item->i); + ItemStack stack_from = list_from->getItem( + m_selected_item->i); ItemStack stack_to = list_to->getItem(s.i); - if (stack_to.empty() || stack_to.name == stack_from.name) + if (stack_to.empty() || + stack_to.name == stack_from.name) move_amount = 1; } } - break; + break; default: break; } @@ -4244,15 +4327,18 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) assert(s.isValid()); assert(inv_selected && inv_s); - InventoryList *list_from = inv_selected->getList(m_selected_item->listname); + InventoryList *list_from = + inv_selected->getList(m_selected_item->listname); InventoryList *list_to = list_s; assert(list_from && list_to); ItemStack stack_from = list_from->getItem(m_selected_item->i); ItemStack stack_to = list_to->getItem(s.i); // Check how many items can be moved - move_amount = stack_from.count = MYMIN(move_amount, stack_from.count); - ItemStack leftover = stack_to.addItem(stack_from, m_client->idef()); + move_amount = stack_from.count = + MYMIN(move_amount, stack_from.count); + ItemStack leftover = + stack_to.addItem(stack_from, m_client->idef()); bool move = true; // If source stack cannot be added to destination stack at all, // they are swapped @@ -4264,7 +4350,8 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) m_selected_dragging = false; // WARNING: BLACK MAGIC, BUT IN A REDUCED SET - // Skip next validation checks due async inventory calls + // Skip next validation checks due async inventory + // calls m_selected_swap = stack_to; } else { move = false; @@ -4281,7 +4368,8 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) } if (move) { - infostream << "Handing IAction::Move to manager" << std::endl; + infostream << "Handing IAction::Move to manager" + << std::endl; IMoveAction *a = new IMoveAction(); a->count = move_amount; a->from_inv = m_selected_item->inventoryloc; @@ -4297,28 +4385,32 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) u32 i = 0; for (; i < mis; i++) { const ListRingSpec &sp = m_inventory_rings[i]; - if (sp.inventoryloc == s.inventoryloc - && sp.listname == s.listname) + if (sp.inventoryloc == s.inventoryloc && + sp.listname == s.listname) break; } do { if (i >= mis) // if not found break; u32 to_inv_ind = (i + 1) % mis; - const ListRingSpec &to_inv_sp = m_inventory_rings[to_inv_ind]; + const ListRingSpec &to_inv_sp = + m_inventory_rings[to_inv_ind]; InventoryList *list_from = list_s; if (!s.isValid()) break; - Inventory *inv_to = m_invmgr->getInventory(to_inv_sp.inventoryloc); + Inventory *inv_to = m_invmgr->getInventory( + to_inv_sp.inventoryloc); if (!inv_to) break; - InventoryList *list_to = inv_to->getList(to_inv_sp.listname); + InventoryList *list_to = + inv_to->getList(to_inv_sp.listname); if (!list_to) break; ItemStack stack_from = list_from->getItem(s.i); assert(shift_move_amount <= stack_from.count); - infostream << "Handing IAction::Move to manager" << std::endl; + infostream << "Handing IAction::Move to manager" + << std::endl; IMoveAction *a = new IMoveAction(); a->count = shift_move_amount; a->from_inv = s.inventoryloc; @@ -4334,12 +4426,14 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) assert(m_selected_item && m_selected_item->isValid()); assert(inv_selected); - InventoryList *list_from = inv_selected->getList(m_selected_item->listname); + InventoryList *list_from = + inv_selected->getList(m_selected_item->listname); assert(list_from); ItemStack stack_from = list_from->getItem(m_selected_item->i); // Check how many items can be dropped - drop_amount = stack_from.count = MYMIN(drop_amount, stack_from.count); + drop_amount = stack_from.count = + MYMIN(drop_amount, stack_from.count); assert(drop_amount > 0 && drop_amount <= m_selected_amount); m_selected_amount -= drop_amount; @@ -4355,13 +4449,14 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) // if there are no items selected or the selected item // belongs to craftresult list, proceed with crafting - if (!m_selected_item || - !m_selected_item->isValid() || m_selected_item->listname == "craftresult") { + if (!m_selected_item || !m_selected_item->isValid() || + m_selected_item->listname == "craftresult") { assert(inv_s); // Send IACTION_CRAFT - infostream << "Handing IACTION_CRAFT to manager" << std::endl; + infostream << "Handing IACTION_CRAFT to manager" + << std::endl; ICraftAction *a = new ICraftAction(); a->count = craft_amount; a->craft_inv = s.inventoryloc; @@ -4381,8 +4476,7 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) } if (event.EventType == EET_GUI_EVENT) { - if (event.GUIEvent.EventType == gui::EGET_TAB_CHANGED - && isVisible()) { + if (event.GUIEvent.EventType == gui::EGET_TAB_CHANGED && isVisible()) { // find the element that was clicked for (GUIFormSpecMenu::FieldSpec &s : m_fields) { if ((s.ftype == f_TabHeader) && @@ -4394,19 +4488,23 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) } } } - if (event.GUIEvent.EventType == gui::EGET_ELEMENT_FOCUS_LOST - && isVisible()) { + if (event.GUIEvent.EventType == gui::EGET_ELEMENT_FOCUS_LOST && + isVisible()) { if (!canTakeFocus(event.GUIEvent.Element)) { - infostream<<"GUIFormSpecMenu: Not allowing focus change." - <getID(); if (caller_id == 257) { @@ -4436,7 +4534,8 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) acceptInput(quit_mode_accept); quitMenu(); } else { - m_text_dst->gotText(L"ExitButton"); + m_text_dst->gotText( + L"ExitButton"); } return true; } @@ -4455,8 +4554,8 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) s.send = true; acceptInput(quit_mode_no); - // revert configuration to make sure dropdowns are sent on - // regular button click + // revert configuration to make sure dropdowns are + // sent on regular button click for (GUIFormSpecMenu::FieldSpec &s2 : m_fields) { if (s2.ftype == f_DropDown) { s2.send = true; @@ -4467,7 +4566,8 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) s.fdefault = L"Changed"; acceptInput(quit_mode_no); s.fdefault = L""; - } else if (s.ftype == f_Unknown || s.ftype == f_HyperText) { + } else if (s.ftype == f_Unknown || + s.ftype == f_HyperText) { s.send = true; acceptInput(); s.send = false; @@ -4477,7 +4577,8 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) if (event.GUIEvent.EventType == gui::EGET_SCROLL_BAR_CHANGED) { // move scroll_containers - for (const std::pair &c : m_scroll_containers) + for (const std::pair &c : + m_scroll_containers) c.second->onScrollEvent(event.GUIEvent.Caller); } @@ -4488,8 +4589,10 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) if (s.ftype == f_Unknown && s.fid == event.GUIEvent.Caller->getID()) { current_field_enter_pending = s.fname; - std::unordered_map::const_iterator it = - field_close_on_enter.find(s.fname); + std::unordered_map::const_iterator it = + field_close_on_enter.find( + s.fname); if (it != field_close_on_enter.end()) close_on_enter = (*it).second; @@ -4517,10 +4620,11 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) for (GUIFormSpecMenu::FieldSpec &s : m_fields) { // if it's a table, set the send field // so lua knows which table was changed - if ((s.ftype == f_Table) && (s.fid == current_id)) { + if ((s.ftype == f_Table) && + (s.fid == current_id)) { s.send = true; acceptInput(); - s.send=false; + s.send = false; } } return true; @@ -4545,7 +4649,6 @@ std::string GUIFormSpecMenu::getNameByID(s32 id) return ""; } - const GUIFormSpecMenu::FieldSpec *GUIFormSpecMenu::getSpecByID(s32 id) { for (FieldSpec &spec : m_fields) { @@ -4570,12 +4673,14 @@ std::wstring GUIFormSpecMenu::getLabelByID(s32 id) } StyleSpec GUIFormSpecMenu::getDefaultStyleForElement(const std::string &type, - const std::string &name, const std::string &parent_type) { + const std::string &name, const std::string &parent_type) +{ return getStyleForElement(type, name, parent_type)[StyleSpec::STATE_DEFAULT]; } std::array GUIFormSpecMenu::getStyleForElement( - const std::string &type, const std::string &name, const std::string &parent_type) + const std::string &type, const std::string &name, + const std::string &parent_type) { std::array ret; diff --git a/src/gui/guiFormSpecMenu.h b/src/gui/guiFormSpecMenu.h index 613acaa04..ea9731258 100644 --- a/src/gui/guiFormSpecMenu.h +++ b/src/gui/guiFormSpecMenu.h @@ -41,7 +41,8 @@ class Client; class TexturePool; class GUIScrollContainer; -typedef enum { +typedef enum +{ f_Button, f_Table, f_TabHeader, @@ -55,7 +56,8 @@ typedef enum { f_Unknown } FormspecFieldType; -typedef enum { +typedef enum +{ quit_mode_no, quit_mode_accept, quit_mode_cancel @@ -88,9 +90,9 @@ class GUIFormSpecMenu : public GUIModalMenu ListRingSpec() = default; ListRingSpec(const InventoryLocation &a_inventoryloc, - const std::string &a_listname): - inventoryloc(a_inventoryloc), - listname(a_listname) + const std::string &a_listname) : + inventoryloc(a_inventoryloc), + listname(a_listname) { } @@ -103,17 +105,15 @@ class GUIFormSpecMenu : public GUIModalMenu FieldSpec() = default; FieldSpec(const std::string &name, const std::wstring &label, - const std::wstring &default_text, s32 id, int priority = 0, + const std::wstring &default_text, s32 id, + int priority = 0, gui::ECURSOR_ICON cursor_icon = ECI_NORMAL) : - fname(name), - flabel(label), - fdefault(unescape_enriched(translate_string(default_text))), - fid(id), - send(false), - ftype(f_Unknown), - is_exit(false), - priority(priority), - fcursor_icon(cursor_icon) + fname(name), + flabel(label), + fdefault(unescape_enriched( + translate_string(default_text))), + fid(id), send(false), ftype(f_Unknown), is_exit(false), + priority(priority), fcursor_icon(cursor_icon) { } @@ -134,10 +134,9 @@ class GUIFormSpecMenu : public GUIModalMenu { TooltipSpec() = default; TooltipSpec(const std::wstring &a_tooltip, irr::video::SColor a_bgcolor, - irr::video::SColor a_color): - tooltip(translate_string(a_tooltip)), - bgcolor(a_bgcolor), - color(a_color) + irr::video::SColor a_color) : + tooltip(translate_string(a_tooltip)), + bgcolor(a_bgcolor), color(a_color) { } @@ -147,21 +146,16 @@ class GUIFormSpecMenu : public GUIModalMenu }; public: - GUIFormSpecMenu(JoystickController *joystick, - gui::IGUIElement* parent, s32 id, - IMenuManager *menumgr, - Client *client, - ISimpleTextureSource *tsrc, - IFormSource* fs_src, - TextDest* txt_dst, - const std::string &formspecPrepend, - bool remap_dbl_click = true); + GUIFormSpecMenu(JoystickController *joystick, gui::IGUIElement *parent, s32 id, + IMenuManager *menumgr, Client *client, ISimpleTextureSource *tsrc, + IFormSource *fs_src, TextDest *txt_dst, + const std::string &formspecPrepend, bool remap_dbl_click = true); ~GUIFormSpecMenu(); static void create(GUIFormSpecMenu *&cur_formspec, Client *client, - JoystickController *joystick, IFormSource *fs_src, TextDest *txt_dest, - const std::string &formspecPrepend); + JoystickController *joystick, IFormSource *fs_src, + TextDest *txt_dest, const std::string &formspecPrepend); void setFormSpec(const std::string &formspec_string, const InventoryLocation ¤t_inventory_location) @@ -196,12 +190,9 @@ public: m_text_dst = text_dst; } - void allowClose(bool value) - { - m_allowclose = value; - } + void allowClose(bool value) { m_allowclose = value; } - void lockSize(bool lock,v2u32 basescreensize=v2u32(0,0)) + void lockSize(bool lock, v2u32 basescreensize = v2u32(0, 0)) { m_lock = lock; m_lockscreensize = basescreensize; @@ -210,30 +201,18 @@ public: void removeChildren(); void setInitialFocus(); - void setFocus(const std::string &elementname) - { - m_focused_element = elementname; - } + void setFocus(const std::string &elementname) { m_focused_element = elementname; } - Client *getClient() const - { - return m_client; - } + Client *getClient() const { return m_client; } const GUIInventoryList::ItemSpec *getSelectedItem() const { return m_selected_item; } - const u16 getSelectedAmount() const - { - return m_selected_amount; - } + const u16 getSelectedAmount() const { return m_selected_amount; } - bool doTooltipAppendItemname() const - { - return m_tooltip_append_itemname; - } + bool doTooltipAppendItemname() const { return m_tooltip_append_itemname; } void addHoveredItemTooltip(const std::string &name) { @@ -252,13 +231,13 @@ public: ItemStack verifySelectedItem(); void acceptInput(FormspecQuitMode quitmode); - bool preprocessEvent(const SEvent& event); - bool OnEvent(const SEvent& event); + bool preprocessEvent(const SEvent &event); + bool OnEvent(const SEvent &event); bool doPause; bool pausesGame() { return doPause; } - GUITable* getTable(const std::string &tablename); - std::vector* getDropDownValues(const std::string &name); + GUITable *getTable(const std::string &tablename); + std::vector *getDropDownValues(const std::string &name); #ifdef __ANDROID__ bool getAndroidUIInput(); @@ -267,7 +246,7 @@ public: protected: v2s32 getBasePos() const { - return padding + offset + AbsoluteRect.UpperLeftCorner; + return padding + offset + AbsoluteRect.UpperLeftCorner; } std::wstring getLabelByID(s32 id); std::string getNameByID(s32 id); @@ -281,9 +260,11 @@ protected: std::unordered_set property_warned; StyleSpec getDefaultStyleForElement(const std::string &type, - const std::string &name="", const std::string &parent_type=""); - std::array getStyleForElement(const std::string &type, - const std::string &name="", const std::string &parent_type=""); + const std::string &name = "", + const std::string &parent_type = ""); + std::array getStyleForElement( + const std::string &type, const std::string &name = "", + const std::string &parent_type = ""); v2s32 padding; v2f32 spacing; @@ -345,15 +326,16 @@ protected: video::SColor m_default_tooltip_color; private: - IFormSource *m_form_src; - TextDest *m_text_dst; - std::string m_last_formname; - u16 m_formspec_version = 1; - std::string m_focused_element = ""; + IFormSource *m_form_src; + TextDest *m_text_dst; + std::string m_last_formname; + u16 m_formspec_version = 1; + std::string m_focused_element = ""; JoystickController *m_joystick; bool m_show_debug = false; - typedef struct { + typedef struct + { bool explicit_size; bool real_coordinates; u8 simple_field_count; @@ -370,20 +352,23 @@ private: GUIInventoryList::Options inventorylist_options; - struct { + struct + { s32 max = 1000; s32 min = 0; s32 small_step = 10; s32 large_step = 100; s32 thumb_size = 1; - GUIScrollBar::ArrowVisibility arrow_visiblity = GUIScrollBar::DEFAULT; + GUIScrollBar::ArrowVisibility arrow_visiblity = + GUIScrollBar::DEFAULT; } scrollbar_options; // used to restore table selection/scroll/treeview state std::unordered_map table_dyndata; } parserData; - typedef struct { + typedef struct + { bool key_up; bool key_down; bool key_enter; @@ -394,49 +379,50 @@ private: std::string current_field_enter_pending = ""; std::vector m_hovered_item_tooltips; - void parseElement(parserData* data, const std::string &element); + void parseElement(parserData *data, const std::string &element); - void parseSize(parserData* data, const std::string &element); - void parseContainer(parserData* data, const std::string &element); - void parseContainerEnd(parserData* data); + void parseSize(parserData *data, const std::string &element); + void parseContainer(parserData *data, const std::string &element); + void parseContainerEnd(parserData *data); void parseScrollContainer(parserData *data, const std::string &element); void parseScrollContainerEnd(parserData *data); - void parseList(parserData* data, const std::string &element); - void parseListRing(parserData* data, const std::string &element); - void parseCheckbox(parserData* data, const std::string &element); - void parseImage(parserData* data, const std::string &element); + void parseList(parserData *data, const std::string &element); + void parseListRing(parserData *data, const std::string &element); + void parseCheckbox(parserData *data, const std::string &element); + void parseImage(parserData *data, const std::string &element); void parseAnimatedImage(parserData *data, const std::string &element); - void parseItemImage(parserData* data, const std::string &element); - void parseButton(parserData* data, const std::string &element, + void parseItemImage(parserData *data, const std::string &element); + void parseButton(parserData *data, const std::string &element, const std::string &typ); - void parseBackground(parserData* data, const std::string &element); - void parseTableOptions(parserData* data, const std::string &element); - void parseTableColumns(parserData* data, const std::string &element); - void parseTable(parserData* data, const std::string &element); - void parseTextList(parserData* data, const std::string &element); - void parseDropDown(parserData* data, const std::string &element); + void parseBackground(parserData *data, const std::string &element); + void parseTableOptions(parserData *data, const std::string &element); + void parseTableColumns(parserData *data, const std::string &element); + void parseTable(parserData *data, const std::string &element); + void parseTextList(parserData *data, const std::string &element); + void parseDropDown(parserData *data, const std::string &element); void parseFieldCloseOnEnter(parserData *data, const std::string &element); - void parsePwdField(parserData* data, const std::string &element); - void parseField(parserData* data, const std::string &element, const std::string &type); - void createTextField(parserData *data, FieldSpec &spec, - core::rect &rect, bool is_multiline); - void parseSimpleField(parserData* data,std::vector &parts); - void parseTextArea(parserData* data,std::vector& parts, + void parsePwdField(parserData *data, const std::string &element); + void parseField(parserData *data, const std::string &element, + const std::string &type); + void createTextField(parserData *data, FieldSpec &spec, core::rect &rect, + bool is_multiline); + void parseSimpleField(parserData *data, std::vector &parts); + void parseTextArea(parserData *data, std::vector &parts, const std::string &type); void parseHyperText(parserData *data, const std::string &element); - void parseLabel(parserData* data, const std::string &element); - void parseVertLabel(parserData* data, const std::string &element); - void parseImageButton(parserData* data, const std::string &element, + void parseLabel(parserData *data, const std::string &element); + void parseVertLabel(parserData *data, const std::string &element); + void parseImageButton(parserData *data, const std::string &element, const std::string &type); - void parseItemImageButton(parserData* data, const std::string &element); - void parseTabHeader(parserData* data, const std::string &element); - void parseBox(parserData* data, const std::string &element); - void parseBackgroundColor(parserData* data, const std::string &element); - void parseListColors(parserData* data, const std::string &element); - void parseTooltip(parserData* data, const std::string &element); + void parseItemImageButton(parserData *data, const std::string &element); + void parseTabHeader(parserData *data, const std::string &element); + void parseBox(parserData *data, const std::string &element); + void parseBackgroundColor(parserData *data, const std::string &element); + void parseListColors(parserData *data, const std::string &element); + void parseTooltip(parserData *data, const std::string &element); bool parseVersionDirect(const std::string &data); - bool parseSizeDirect(parserData* data, const std::string &element); - void parseScrollBar(parserData* data, const std::string &element); + bool parseSizeDirect(parserData *data, const std::string &element); + void parseScrollBar(parserData *data, const std::string &element); void parseScrollBarOptions(parserData *data, const std::string &element); bool parsePositionDirect(parserData *data, const std::string &element); void parsePosition(parserData *data, const std::string &element); @@ -448,7 +434,7 @@ private: void tryClose(); void showTooltip(const std::wstring &text, const irr::video::SColor &color, - const irr::video::SColor &bgcolor); + const irr::video::SColor &bgcolor); /** * In formspec version < 2 the elements were not ordered properly. Some element @@ -461,25 +447,16 @@ private: gui::IGUIFont *m_font = nullptr; }; -class FormspecFormSource: public IFormSource +class FormspecFormSource : public IFormSource { public: - FormspecFormSource(const std::string &formspec): - m_formspec(formspec) - { - } + FormspecFormSource(const std::string &formspec) : m_formspec(formspec) {} ~FormspecFormSource() = default; - void setForm(const std::string &formspec) - { - m_formspec = formspec; - } + void setForm(const std::string &formspec) { m_formspec = formspec; } - const std::string &getForm() const - { - return m_formspec; - } + const std::string &getForm() const { return m_formspec; } std::string m_formspec; }; diff --git a/src/gui/guiHyperText.cpp b/src/gui/guiHyperText.cpp index 88931cdf9..70c5d3f66 100644 --- a/src/gui/guiHyperText.cpp +++ b/src/gui/guiHyperText.cpp @@ -71,8 +71,8 @@ void ParsedText::Element::setStyle(StyleList &style) if (style["fontstyle"] == "mono") font_mode = FM_Mono; - FontSpec spec(font_size, font_mode, - is_yes(style["bold"]), is_yes(style["italic"])); + FontSpec spec(font_size, font_mode, is_yes(style["bold"]), + is_yes(style["italic"])); // TODO: find a way to check font validity // Build a new fontengine ? @@ -247,8 +247,9 @@ void ParsedText::endParagraph(EndReason reason) EndReason previous = m_end_paragraph_reason; m_end_paragraph_reason = reason; - if (m_empty_paragraph && (reason == ER_TAG || - (reason == ER_NEWLINE && previous == ER_TAG))) { + if (m_empty_paragraph && + (reason == ER_TAG || + (reason == ER_NEWLINE && previous == ER_TAG))) { // Ignore last empty paragraph m_paragraph = nullptr; m_paragraphs.pop_back(); @@ -539,10 +540,13 @@ u32 ParsedText::parseTag(const wchar_t *text, u32 cursor) std::string str = attrs["rotate"]; std::vector parts = split(str, ','); if (parts.size() == 3) { - m_element->rotation = v3s16 ( - rangelim(stoi(parts[0]), -1000, 1000), - rangelim(stoi(parts[1]), -1000, 1000), - rangelim(stoi(parts[2]), -1000, 1000)); + m_element->rotation = v3s16( + rangelim(stoi(parts[0]), -1000, + 1000), + rangelim(stoi(parts[1]), -1000, + 1000), + rangelim(stoi(parts[2]), -1000, + 1000)); } } } @@ -614,12 +618,14 @@ TextDrawer::TextDrawer(const wchar_t *text, Client *client, case ParsedText::ELEMENT_SEPARATOR: case ParsedText::ELEMENT_TEXT: if (e.font) { - e.dim.Width = e.font->getDimension(e.text.c_str()).Width; + e.dim.Width = e.font->getDimension(e.text.c_str()) + .Width; e.dim.Height = e.font->getDimension(L"Yy").Height; #if USE_FREETYPE if (e.font->getType() == irr::gui::EGFT_CUSTOM) { e.baseline = e.dim.Height - 1 - - ((irr::gui::CGUITTFont *)e.font)->getAscender() / 64; + ((irr::gui::CGUITTFont *)e.font)->getAscender() / + 64; } #endif } else { @@ -638,8 +644,8 @@ TextDrawer::TextDrawer(const wchar_t *text, Client *client, if (e.type == ParsedText::ELEMENT_IMAGE) { video::ITexture *texture = - m_client->getTextureSource()-> - getTexture(stringw_to_utf8(e.text)); + m_client->getTextureSource()->getTexture( + stringw_to_utf8(e.text)); if (texture) dim = texture->getOriginalSize(); } @@ -649,10 +655,10 @@ TextDrawer::TextDrawer(const wchar_t *text, Client *client, e.dim = dim; else e.dim.Height = dim.Height * e.dim.Width / - dim.Width; + dim.Width; else e.dim.Width = dim.Width * e.dim.Height / - dim.Height; + dim.Height; break; } } @@ -706,7 +712,7 @@ void TextDrawer::place(const core::rect &dest_rect) e->pos.X = m_text.margin; if (e->floating == ParsedText::FLOAT_RIGHT) e->pos.X = dest_rect.getWidth() - e->dim.Width - - m_text.margin; + m_text.margin; RectWithMargin floating; floating.rect = core::rect(e->pos, e->dim); @@ -739,38 +745,54 @@ void TextDrawer::place(const core::rect &dest_rect) for (const auto &f : m_floating) { // Does floating rect intersect paragraph y line? if (f.rect.UpperLeftCorner.Y - f.margin <= y && - f.rect.LowerRightCorner.Y + f.margin >= y) { + f.rect.LowerRightCorner.Y + f.margin >= + y) { // Next Y to try if no room left - if (!nexty || f.rect.LowerRightCorner.Y + - std::max(f.margin, p.margin) < nexty) { - nexty = f.rect.LowerRightCorner.Y + - std::max(f.margin, p.margin) + 1; + if (!nexty || f.rect.LowerRightCorner.Y + std::max(f.margin, + p.margin) < + nexty) { + nexty = f.rect.LowerRightCorner + .Y + + std::max(f.margin, + p.margin) + + 1; } - if (f.rect.UpperLeftCorner.X - f.margin <= left && - f.rect.LowerRightCorner.X + f.margin < right) { + if (f.rect.UpperLeftCorner.X - f.margin <= + left && + f.rect.LowerRightCorner.X + f.margin < + right) { // float on left - if (f.rect.LowerRightCorner.X + - std::max(f.margin, p.margin) > left) { - left = f.rect.LowerRightCorner.X + - std::max(f.margin, p.margin); + if (f.rect.LowerRightCorner.X + std::max(f.margin, + p.margin) > + left) { + left = f.rect.LowerRightCorner + .X + + std::max(f.margin, + p.margin); } - } else if (f.rect.LowerRightCorner.X + f.margin >= right && - f.rect.UpperLeftCorner.X - f.margin > left) { + } else if (f.rect.LowerRightCorner.X + f.margin >= + right && + f.rect.UpperLeftCorner.X - f.margin > + left) { // float on right - if (f.rect.UpperLeftCorner.X - - std::max(f.margin, p.margin) < right) - right = f.rect.UpperLeftCorner.X - - std::max(f.margin, p.margin); + if (f.rect.UpperLeftCorner.X - std::max(f.margin, + p.margin) < + right) + right = f.rect.UpperLeftCorner + .X - + std::max(f.margin, + p.margin); - } else if (f.rect.UpperLeftCorner.X - f.margin <= left && - f.rect.LowerRightCorner.X + f.margin >= right) { + } else if (f.rect.UpperLeftCorner.X - f.margin <= + left && + f.rect.LowerRightCorner.X + f.margin >= + right) { // float taking all space left = right; - } - else - { // float in the middle -- should not occure yet, see that later + } else { // float in the middle -- should + // not occure yet, see that later } } } @@ -796,12 +818,14 @@ void TextDrawer::place(const core::rect &dest_rect) } std::vector::iterator linestart = el; - std::vector::iterator lineend = p.elements.end(); + std::vector::iterator lineend = + p.elements.end(); // First pass, find elements fitting into line // (or at least one element) - while (el != p.elements.end() && (charswidth == 0 || - charswidth + el->dim.Width <= linewidth)) { + while (el != p.elements.end() && + (charswidth == 0 || charswidth + el->dim.Width <= + linewidth)) { if (el->floating == ParsedText::FLOAT_NONE) { if (el->type != ParsedText::ELEMENT_SEPARATOR) { lineend = el; @@ -844,9 +868,14 @@ void TextDrawer::place(const core::rect &dest_rect) x += (linewidth - charswidth) / 2.f; break; case ParsedText::HALIGN_JUSTIFY: - if (wordcount > 1 && // Justification only if at least two words - !(lineend == p.elements.end())) // Don't justify last line - extraspace = ((float)(linewidth - charswidth)) / (wordcount - 1); + if (wordcount > 1 && // Justification only if at least two + // words + !(lineend == p.elements.end())) // Don't + // justify + // last + // line + extraspace = ((float)(linewidth - charswidth)) / + (wordcount - 1); break; case ParsedText::HALIGN_RIGHT: x += linewidth - charswidth; @@ -889,7 +918,7 @@ void TextDrawer::place(const core::rect &dest_rect) } y += charsheight; } // Elements (actually lines) - } // Paragraph + } // Paragraph // Check if float goes under paragraph for (const auto &f : m_floating) { @@ -949,16 +978,18 @@ void TextDrawer::draw(const core::rect &clip_rect, el.font->draw(el.text, rect, color, false, true, &clip_rect); - if (el.underline && el.drawwidth) { + if (el.underline && el.drawwidth) { s32 linepos = el.pos.Y + offset.Y + - el.dim.Height - (el.baseline >> 1); + el.dim.Height - (el.baseline >> 1); core::rect linerect(el.pos.X + offset.X, linepos - (el.baseline >> 3) - 1, - el.pos.X + offset.X + el.drawwidth, + el.pos.X + offset.X + + el.drawwidth, linepos + (el.baseline >> 3)); - driver->draw2DRectangle(color, linerect, &clip_rect); + driver->draw2DRectangle( + color, linerect, &clip_rect); } } break; @@ -970,7 +1001,10 @@ void TextDrawer::draw(const core::rect &clip_rect, m_environment->getVideoDriver()->draw2DImage( texture, rect, irr::core::rect( - core::position2d(0, 0), + core::position2d< + s32>( + 0, + 0), texture->getOriginalSize()), &clip_rect, 0, true); } break; @@ -980,11 +1014,10 @@ void TextDrawer::draw(const core::rect &clip_rect, ItemStack item; item.deSerialize(stringw_to_utf8(el.text), idef); - drawItemStack( - m_environment->getVideoDriver(), - g_fontengine->getFont(), item, rect, &clip_rect, - m_client, IT_ROT_OTHER, el.angle, el.rotation - ); + drawItemStack(m_environment->getVideoDriver(), + g_fontengine->getFont(), item, rect, + &clip_rect, m_client, IT_ROT_OTHER, + el.angle, el.rotation); } break; } } @@ -1089,8 +1122,10 @@ bool GUIHyperText::OnEvent(const SEvent &event) checkHover(event.MouseInput.X, event.MouseInput.Y); if (event.MouseInput.Event == EMIE_MOUSE_WHEEL) { - m_vscrollbar->setPos(m_vscrollbar->getPos() - - event.MouseInput.Wheel * m_vscrollbar->getSmallStep()); + m_vscrollbar->setPos( + m_vscrollbar->getPos() - + event.MouseInput.Wheel * + m_vscrollbar->getSmallStep()); m_text_scrollpos.Y = -m_vscrollbar->getPos(); m_drawer.draw(m_display_text_rect, m_text_scrollpos); checkHover(event.MouseInput.X, event.MouseInput.Y); @@ -1104,13 +1139,16 @@ bool GUIHyperText::OnEvent(const SEvent &event) for (auto &tag : element->tags) { if (tag->name == "action") { Text = core::stringw(L"action:") + - utf8_to_stringw(tag->attrs["name"]); + utf8_to_stringw(tag->attrs["nam" + "e"]); if (Parent) { SEvent newEvent; - newEvent.EventType = EET_GUI_EVENT; + newEvent.EventType = + EET_GUI_EVENT; newEvent.GUIEvent.Caller = this; newEvent.GUIEvent.Element = 0; - newEvent.GUIEvent.EventType = EGET_BUTTON_CLICKED; + newEvent.GUIEvent.EventType = + EGET_BUTTON_CLICKED; Parent->OnEvent(newEvent); } break; @@ -1137,7 +1175,8 @@ void GUIHyperText::draw() if (m_drawer.getHeight() > m_display_text_rect.getHeight()) { m_vscrollbar->setSmallStep(m_display_text_rect.getHeight() * 0.1f); m_vscrollbar->setLargeStep(m_display_text_rect.getHeight() * 0.5f); - m_vscrollbar->setMax(m_drawer.getHeight() - m_display_text_rect.getHeight()); + m_vscrollbar->setMax( + m_drawer.getHeight() - m_display_text_rect.getHeight()); m_vscrollbar->setVisible(true); diff --git a/src/gui/guiHyperText.h b/src/gui/guiHyperText.h index 5b936262e..8371157e2 100644 --- a/src/gui/guiHyperText.h +++ b/src/gui/guiHyperText.h @@ -134,7 +134,12 @@ public: Tag m_root_tag; protected: - typedef enum { ER_NONE, ER_TAG, ER_NEWLINE } EndReason; + typedef enum + { + ER_NONE, + ER_TAG, + ER_NEWLINE + } EndReason; // Parser functions void enterElement(ElementType type); diff --git a/src/gui/guiInventoryList.cpp b/src/gui/guiInventoryList.cpp index 58d7ae771..a0fef4073 100644 --- a/src/gui/guiInventoryList.cpp +++ b/src/gui/guiInventoryList.cpp @@ -19,33 +19,17 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "client/hud.h" #include "client/client.h" -GUIInventoryList::GUIInventoryList(gui::IGUIEnvironment *env, - gui::IGUIElement *parent, - s32 id, - const core::rect &rectangle, - InventoryManager *invmgr, - const InventoryLocation &inventoryloc, - const std::string &listname, - const v2s32 &geom, - const s32 start_item_i, - const v2s32 &slot_size, - const v2f32 &slot_spacing, - GUIFormSpecMenu *fs_menu, - const Options &options, - gui::IGUIFont *font) : - gui::IGUIElement(gui::EGUIET_ELEMENT, env, parent, id, rectangle), - m_invmgr(invmgr), - m_inventoryloc(inventoryloc), - m_listname(listname), - m_geom(geom), - m_start_item_i(start_item_i), - m_slot_size(slot_size), - m_slot_spacing(slot_spacing), - m_fs_menu(fs_menu), - m_options(options), - m_font(font), - m_hovered_i(-1), - m_already_warned(false) +GUIInventoryList::GUIInventoryList(gui::IGUIEnvironment *env, gui::IGUIElement *parent, + s32 id, const core::rect &rectangle, InventoryManager *invmgr, + const InventoryLocation &inventoryloc, const std::string &listname, + const v2s32 &geom, const s32 start_item_i, const v2s32 &slot_size, + const v2f32 &slot_spacing, GUIFormSpecMenu *fs_menu, + const Options &options, gui::IGUIFont *font) : + gui::IGUIElement(gui::EGUIET_ELEMENT, env, parent, id, rectangle), + m_invmgr(invmgr), m_inventoryloc(inventoryloc), m_listname(listname), + m_geom(geom), m_start_item_i(start_item_i), m_slot_size(slot_size), + m_slot_spacing(slot_spacing), m_fs_menu(fs_menu), m_options(options), + m_font(font), m_hovered_i(-1), m_already_warned(false) { } @@ -58,9 +42,9 @@ void GUIInventoryList::draw() if (!inv) { if (!m_already_warned) { warningstream << "GUIInventoryList::draw(): " - << "The inventory location " - << "\"" << m_inventoryloc.dump() << "\" doesn't exist" - << std::endl; + << "The inventory location " + << "\"" << m_inventoryloc.dump() + << "\" doesn't exist" << std::endl; m_already_warned = true; } return; @@ -69,9 +53,9 @@ void GUIInventoryList::draw() if (!ilist) { if (!m_already_warned) { warningstream << "GUIInventoryList::draw(): " - << "The inventory list \"" << m_listname << "\" @ \"" - << m_inventoryloc.dump() << "\" doesn't exist" - << std::endl; + << "The inventory list \"" << m_listname + << "\" @ \"" << m_inventoryloc.dump() + << "\" doesn't exist" << std::endl; m_already_warned = true; } return; @@ -97,21 +81,25 @@ void GUIInventoryList::draw() core::rect rect = imgrect + base_pos + p; ItemStack item = ilist->getItem(item_i); - bool selected = selected_item - && m_invmgr->getInventory(selected_item->inventoryloc) == inv - && selected_item->listname == m_listname - && selected_item->i == item_i; + bool selected = selected_item && + m_invmgr->getInventory(selected_item->inventoryloc) == + inv && + selected_item->listname == m_listname && + selected_item->i == item_i; core::rect clipped_rect(rect); clipped_rect.clipAgainst(AbsoluteClippingRect); bool hovering = m_hovered_i == item_i; - ItemRotationKind rotation_kind = selected ? IT_ROT_SELECTED : - (hovering ? IT_ROT_HOVERED : IT_ROT_NONE); + ItemRotationKind rotation_kind = + selected ? IT_ROT_SELECTED + : (hovering ? IT_ROT_HOVERED : IT_ROT_NONE); // layer 0 if (hovering) { - driver->draw2DRectangle(m_options.slotbg_h, rect, &AbsoluteClippingRect); + driver->draw2DRectangle( + m_options.slotbg_h, rect, &AbsoluteClippingRect); } else { - driver->draw2DRectangle(m_options.slotbg_n, rect, &AbsoluteClippingRect); + driver->draw2DRectangle( + m_options.slotbg_n, rect, &AbsoluteClippingRect); } // Draw inv slot borders @@ -121,21 +109,27 @@ void GUIInventoryList::draw() s32 x2 = rect.LowerRightCorner.X; s32 y2 = rect.LowerRightCorner.Y; s32 border = 1; - core::rect clipping_rect = Parent ? Parent->getAbsoluteClippingRect() - : core::rect(); - core::rect *clipping_rect_ptr = Parent ? &clipping_rect : nullptr; + core::rect clipping_rect = + Parent ? Parent->getAbsoluteClippingRect() + : core::rect(); + core::rect *clipping_rect_ptr = + Parent ? &clipping_rect : nullptr; driver->draw2DRectangle(m_options.slotbordercolor, - core::rect(v2s32(x1 - border, y1 - border), - v2s32(x2 + border, y1)), clipping_rect_ptr); + core::rect(v2s32(x1 - border, y1 - border), + v2s32(x2 + border, y1)), + clipping_rect_ptr); driver->draw2DRectangle(m_options.slotbordercolor, - core::rect(v2s32(x1 - border, y2), - v2s32(x2 + border, y2 + border)), clipping_rect_ptr); + core::rect(v2s32(x1 - border, y2), + v2s32(x2 + border, y2 + border)), + clipping_rect_ptr); driver->draw2DRectangle(m_options.slotbordercolor, - core::rect(v2s32(x1 - border, y1), - v2s32(x1, y2)), clipping_rect_ptr); + core::rect(v2s32(x1 - border, y1), + v2s32(x1, y2)), + clipping_rect_ptr); driver->draw2DRectangle(m_options.slotbordercolor, - core::rect(v2s32(x2, y1), - v2s32(x2 + border, y2)), clipping_rect_ptr); + core::rect(v2s32(x2, y1), + v2s32(x2 + border, y2)), + clipping_rect_ptr); } // layer 1 @@ -179,8 +173,7 @@ bool GUIInventoryList::OnEvent(const SEvent &event) // find the element that would be hovered if this inventorylist was invisible bool was_visible = IsVisible; IsVisible = false; - IGUIElement *hovered = - Environment->getRootGUIElement()->getElementFromPoint( + IGUIElement *hovered = Environment->getRootGUIElement()->getElementFromPoint( core::position2d(event.MouseInput.X, event.MouseInput.Y)); // if the player clicks outside of the formspec window, hovered is not @@ -216,11 +209,10 @@ s32 GUIInventoryList::getItemIndexAtPos(v2s32 p) const v2s32 base_pos = AbsoluteRect.UpperLeftCorner; // instead of looping through each slot, we look where p would be in the grid - s32 i = (p.X - base_pos.X) / (s32)m_slot_spacing.X - + m_geom.X * ((p.Y - base_pos.Y) / (s32)m_slot_spacing.Y); + s32 i = (p.X - base_pos.X) / (s32)m_slot_spacing.X + + m_geom.X * ((p.Y - base_pos.Y) / (s32)m_slot_spacing.Y); - v2s32 p0((i % m_geom.X) * m_slot_spacing.X, - (i / m_geom.X) * m_slot_spacing.Y); + v2s32 p0((i % m_geom.X) * m_slot_spacing.X, (i / m_geom.X) * m_slot_spacing.Y); core::rect rect = imgrect + base_pos + p0; diff --git a/src/gui/guiInventoryList.h b/src/gui/guiInventoryList.h index 934d9ea3a..58d71cabf 100644 --- a/src/gui/guiInventoryList.h +++ b/src/gui/guiInventoryList.h @@ -30,11 +30,9 @@ public: ItemSpec() = default; ItemSpec(const InventoryLocation &a_inventoryloc, - const std::string &a_listname, - s32 a_i) : - inventoryloc(a_inventoryloc), - listname(a_listname), - i(a_i) + const std::string &a_listname, s32 a_i) : + inventoryloc(a_inventoryloc), + listname(a_listname), i(a_i) { } @@ -46,7 +44,8 @@ public: }; // options for inventorylists that are setable with the lua api - struct Options { + struct Options + { // whether a one-pixel border for the slots should be drawn and its color bool slotborder = false; video::SColor slotbordercolor = video::SColor(200, 0, 0, 0); @@ -55,34 +54,21 @@ public: video::SColor slotbg_h = video::SColor(255, 192, 192, 192); }; - GUIInventoryList(gui::IGUIEnvironment *env, - gui::IGUIElement *parent, - s32 id, - const core::rect &rectangle, - InventoryManager *invmgr, - const InventoryLocation &inventoryloc, - const std::string &listname, - const v2s32 &geom, - const s32 start_item_i, - const v2s32 &slot_size, - const v2f32 &slot_spacing, - GUIFormSpecMenu *fs_menu, - const Options &options, - gui::IGUIFont *font); + GUIInventoryList(gui::IGUIEnvironment *env, gui::IGUIElement *parent, s32 id, + const core::rect &rectangle, InventoryManager *invmgr, + const InventoryLocation &inventoryloc, + const std::string &listname, const v2s32 &geom, + const s32 start_item_i, const v2s32 &slot_size, + const v2f32 &slot_spacing, GUIFormSpecMenu *fs_menu, + const Options &options, gui::IGUIFont *font); virtual void draw() override; virtual bool OnEvent(const SEvent &event) override; - const InventoryLocation &getInventoryloc() const - { - return m_inventoryloc; - } + const InventoryLocation &getInventoryloc() const { return m_inventoryloc; } - const std::string &getListname() const - { - return m_listname; - } + const std::string &getListname() const { return m_listname; } void setSlotBGColors(const video::SColor &slotbg_n, const video::SColor &slotbg_h) { diff --git a/src/gui/guiItemImage.cpp b/src/gui/guiItemImage.cpp index f93d5476c..95775dbba 100644 --- a/src/gui/guiItemImage.cpp +++ b/src/gui/guiItemImage.cpp @@ -20,11 +20,12 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "guiItemImage.h" #include "client/client.h" -GUIItemImage::GUIItemImage(gui::IGUIEnvironment *env, gui::IGUIElement *parent, - s32 id, const core::rect &rectangle, const std::string &item_name, - gui::IGUIFont *font, Client *client) : - gui::IGUIElement(gui::EGUIET_ELEMENT, env, parent, id, rectangle), - m_item_name(item_name), m_font(font), m_client(client), m_label(core::stringw()) +GUIItemImage::GUIItemImage(gui::IGUIEnvironment *env, gui::IGUIElement *parent, s32 id, + const core::rect &rectangle, const std::string &item_name, + gui::IGUIFont *font, Client *client) : + gui::IGUIElement(gui::EGUIET_ELEMENT, env, parent, id, rectangle), + m_item_name(item_name), m_font(font), m_client(client), + m_label(core::stringw()) { } diff --git a/src/gui/guiItemImage.h b/src/gui/guiItemImage.h index 6fede6564..ced693ee2 100644 --- a/src/gui/guiItemImage.h +++ b/src/gui/guiItemImage.h @@ -28,15 +28,12 @@ class GUIItemImage : public gui::IGUIElement { public: GUIItemImage(gui::IGUIEnvironment *env, gui::IGUIElement *parent, s32 id, - const core::rect &rectangle, const std::string &item_name, - gui::IGUIFont *font, Client *client); + const core::rect &rectangle, const std::string &item_name, + gui::IGUIFont *font, Client *client); virtual void draw() override; - virtual void setText(const wchar_t *text) override - { - m_label = text; - } + virtual void setText(const wchar_t *text) override { m_label = text; } private: std::string m_item_name; diff --git a/src/gui/guiKeyChangeMenu.cpp b/src/gui/guiKeyChangeMenu.cpp index 94fe0074a..e65a8311c 100644 --- a/src/gui/guiKeyChangeMenu.cpp +++ b/src/gui/guiKeyChangeMenu.cpp @@ -32,7 +32,7 @@ #include "settings.h" #include -#include "mainmenumanager.h" // for g_gamecallback +#include "mainmenumanager.h" // for g_gamecallback #define KMaxButtonPerColumns 12 @@ -40,7 +40,9 @@ extern MainGameCallback *g_gamecallback; enum { - GUI_ID_BACK_BUTTON = 101, GUI_ID_ABORT_BUTTON, GUI_ID_SCROLL_BAR, + GUI_ID_BACK_BUTTON = 101, + GUI_ID_ABORT_BUTTON, + GUI_ID_SCROLL_BAR, // buttons GUI_ID_KEY_FORWARD_BUTTON, GUI_ID_KEY_BACKWARD_BUTTON, @@ -92,9 +94,8 @@ enum GUI_ID_CB_AUTOJUMP, }; -GUIKeyChangeMenu::GUIKeyChangeMenu(gui::IGUIEnvironment* env, - gui::IGUIElement* parent, s32 id, IMenuManager *menumgr, - ISimpleTextureSource *tsrc) : +GUIKeyChangeMenu::GUIKeyChangeMenu(gui::IGUIEnvironment *env, gui::IGUIElement *parent, + s32 id, IMenuManager *menumgr, ISimpleTextureSource *tsrc) : GUIModalMenu(env, parent, id, menumgr), m_tsrc(tsrc) { @@ -114,9 +115,9 @@ GUIKeyChangeMenu::~GUIKeyChangeMenu() void GUIKeyChangeMenu::removeChildren() { - const core::list &children = getChildren(); - core::list children_copy; - for (gui::IGUIElement*i : children) { + const core::list &children = getChildren(); + core::list children_copy; + for (gui::IGUIElement *i : children) { children_copy.push_back(i); } @@ -131,12 +132,9 @@ void GUIKeyChangeMenu::regenerateGui(v2u32 screensize) removeChildren(); const float s = m_gui_scale; - DesiredRect = core::rect( - screensize.X / 2 - 1113 * s / 2, - screensize.Y / 2 - 430 * s / 2, - screensize.X / 2 + 1113 * s / 2, - screensize.Y / 2 + 430 * s / 2 - ); + DesiredRect = core::rect(screensize.X / 2 - 1113 * s / 2, + screensize.Y / 2 - 430 * s / 2, screensize.X / 2 + 1113 * s / 2, + screensize.Y / 2 + 430 * s / 2); recalculateAbsolutePosition(false); v2s32 size = DesiredRect.getSize(); @@ -145,32 +143,33 @@ void GUIKeyChangeMenu::regenerateGui(v2u32 screensize) { core::rect rect(0, 0, 600 * s, 40 * s); rect += topleft + v2s32(25 * s, 3 * s); - //gui::IGUIStaticText *t = - const wchar_t *text = wgettext("Keybindings. (If this menu screws up, remove stuff from minetest.conf)"); - Environment->addStaticText(text, - rect, false, true, this, -1); + // gui::IGUIStaticText *t = + const wchar_t *text = wgettext("Keybindings. (If this menu screws up, " + "remove stuff from minetest.conf)"); + Environment->addStaticText(text, rect, false, true, this, -1); delete[] text; - //t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT); + // t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT); } // Build buttons v2s32 offset(25 * s, 60 * s); - for(size_t i = 0; i < key_settings.size(); i++) - { + for (size_t i = 0; i < key_settings.size(); i++) { key_setting *k = key_settings.at(i); { core::rect rect(0, 0, 150 * s, 20 * s); rect += topleft + v2s32(offset.X, offset.Y); - Environment->addStaticText(k->button_name, rect, false, true, this, -1); + Environment->addStaticText( + k->button_name, rect, false, true, this, -1); } { core::rect rect(0, 0, 100 * s, 30 * s); rect += topleft + v2s32(offset.X + 150 * s, offset.Y - 5 * s); const wchar_t *text = wgettext(k->key.name()); - k->button = GUIButton::addButton(Environment, rect, m_tsrc, this, k->id, text); + k->button = GUIButton::addButton( + Environment, rect, m_tsrc, this, k->id, text); delete[] text; } if ((i + 1) % KMaxButtonPerColumns == 0) { @@ -189,8 +188,8 @@ void GUIKeyChangeMenu::regenerateGui(v2u32 screensize) core::rect rect(0, 0, option_w, 30 * s); rect += topleft + v2s32(option_x, option_y); const wchar_t *text = wgettext("\"Special\" = climb down"); - Environment->addCheckBox(g_settings->getBool("aux1_descends"), rect, this, - GUI_ID_CB_AUX1_DESCENDS, text); + Environment->addCheckBox(g_settings->getBool("aux1_descends"), + rect, this, GUI_ID_CB_AUX1_DESCENDS, text); delete[] text; } offset += v2s32(0, 25 * s); @@ -203,9 +202,10 @@ void GUIKeyChangeMenu::regenerateGui(v2u32 screensize) { core::rect rect(0, 0, option_w, 30 * s); rect += topleft + v2s32(option_x, option_y); - const wchar_t *text = wgettext("Double tap \"jump\" to toggle fly"); - Environment->addCheckBox(g_settings->getBool("doubletap_jump"), rect, this, - GUI_ID_CB_DOUBLETAP_JUMP, text); + const wchar_t *text = + wgettext("Double tap \"jump\" to toggle fly"); + Environment->addCheckBox(g_settings->getBool("doubletap_jump"), + rect, this, GUI_ID_CB_DOUBLETAP_JUMP, text); delete[] text; } offset += v2s32(0, 25 * s); @@ -219,8 +219,8 @@ void GUIKeyChangeMenu::regenerateGui(v2u32 screensize) core::rect rect(0, 0, option_w, 30 * s); rect += topleft + v2s32(option_x, option_y); const wchar_t *text = wgettext("Automatic jumping"); - Environment->addCheckBox(g_settings->getBool("autojump"), rect, this, - GUI_ID_CB_AUTOJUMP, text); + Environment->addCheckBox(g_settings->getBool("autojump"), rect, + this, GUI_ID_CB_AUTOJUMP, text); delete[] text; } offset += v2s32(0, 25); @@ -229,25 +229,27 @@ void GUIKeyChangeMenu::regenerateGui(v2u32 screensize) { core::rect rect(0, 0, 100 * s, 30 * s); rect += topleft + v2s32(size.X / 2 - 105 * s, size.Y - 40 * s); - const wchar_t *text = wgettext("Save"); - GUIButton::addButton(Environment, rect, m_tsrc, this, GUI_ID_BACK_BUTTON, text); + const wchar_t *text = wgettext("Save"); + GUIButton::addButton(Environment, rect, m_tsrc, this, GUI_ID_BACK_BUTTON, + text); delete[] text; } { core::rect rect(0, 0, 100 * s, 30 * s); rect += topleft + v2s32(size.X / 2 + 5 * s, size.Y - 40 * s); const wchar_t *text = wgettext("Cancel"); - GUIButton::addButton(Environment, rect, m_tsrc, this, GUI_ID_ABORT_BUTTON, text); + GUIButton::addButton(Environment, rect, m_tsrc, this, GUI_ID_ABORT_BUTTON, + text); delete[] text; } } void GUIKeyChangeMenu::drawMenu() { - gui::IGUISkin* skin = Environment->getSkin(); + gui::IGUISkin *skin = Environment->getSkin(); if (!skin) return; - video::IVideoDriver* driver = Environment->getVideoDriver(); + video::IVideoDriver *driver = Environment->getVideoDriver(); video::SColor bgcolor(140, 0, 0, 0); driver->draw2DRectangle(bgcolor, AbsoluteRect, &AbsoluteClippingRect); @@ -269,18 +271,21 @@ bool GUIKeyChangeMenu::acceptInput() { gui::IGUIElement *e = getElementFromId(GUI_ID_CB_AUX1_DESCENDS); - if(e && e->getType() == gui::EGUIET_CHECK_BOX) - g_settings->setBool("aux1_descends", ((gui::IGUICheckBox*)e)->isChecked()); + if (e && e->getType() == gui::EGUIET_CHECK_BOX) + g_settings->setBool("aux1_descends", + ((gui::IGUICheckBox *)e)->isChecked()); } { gui::IGUIElement *e = getElementFromId(GUI_ID_CB_DOUBLETAP_JUMP); - if(e && e->getType() == gui::EGUIET_CHECK_BOX) - g_settings->setBool("doubletap_jump", ((gui::IGUICheckBox*)e)->isChecked()); + if (e && e->getType() == gui::EGUIET_CHECK_BOX) + g_settings->setBool("doubletap_jump", + ((gui::IGUICheckBox *)e)->isChecked()); } { gui::IGUIElement *e = getElementFromId(GUI_ID_CB_AUTOJUMP); - if(e && e->getType() == gui::EGUIET_CHECK_BOX) - g_settings->setBool("autojump", ((gui::IGUICheckBox*)e)->isChecked()); + if (e && e->getType() == gui::EGUIET_CHECK_BOX) + g_settings->setBool("autojump", + ((gui::IGUICheckBox *)e)->isChecked()); } clearKeyCache(); @@ -301,10 +306,10 @@ bool GUIKeyChangeMenu::resetMenu() } return true; } -bool GUIKeyChangeMenu::OnEvent(const SEvent& event) +bool GUIKeyChangeMenu::OnEvent(const SEvent &event) { - if (event.EventType == EET_KEY_INPUT_EVENT && active_key - && event.KeyInput.PressedDown) { + if (event.EventType == EET_KEY_INPUT_EVENT && active_key && + event.KeyInput.PressedDown) { bool prefer_character = shift_down; KeyPress kp(event.KeyInput, prefer_character); @@ -315,10 +320,10 @@ bool GUIKeyChangeMenu::OnEvent(const SEvent& event) kp = active_key->key; // Cancel bool shift_went_down = false; - if(!shift_down && + if (!shift_down && (event.KeyInput.Key == irr::KEY_SHIFT || - event.KeyInput.Key == irr::KEY_LSHIFT || - event.KeyInput.Key == irr::KEY_RSHIFT)) + event.KeyInput.Key == irr::KEY_LSHIFT || + event.KeyInput.Key == irr::KEY_RSHIFT)) shift_went_down = true; // Display Key already in use message @@ -336,8 +341,8 @@ bool GUIKeyChangeMenu::OnEvent(const SEvent& event) core::rect rect(0, 0, 600, 40); rect += v2s32(0, 0) + v2s32(25, 30); const wchar_t *text = wgettext("Key already in use"); - this->key_used_text = Environment->addStaticText(text, - rect, false, true, this, -1); + this->key_used_text = Environment->addStaticText( + text, rect, false, true, this, -1); delete[] text; } else if (!key_in_use && this->key_used_text) { this->key_used_text->remove(); @@ -352,7 +357,7 @@ bool GUIKeyChangeMenu::OnEvent(const SEvent& event) delete[] text; // Allow characters made with shift - if (shift_went_down){ + if (shift_went_down) { shift_down = true; return false; } @@ -360,49 +365,46 @@ bool GUIKeyChangeMenu::OnEvent(const SEvent& event) active_key = nullptr; return true; } - } else if (event.EventType == EET_KEY_INPUT_EVENT && !active_key - && event.KeyInput.PressedDown - && event.KeyInput.Key == irr::KEY_ESCAPE) { + } else if (event.EventType == EET_KEY_INPUT_EVENT && !active_key && + event.KeyInput.PressedDown && + event.KeyInput.Key == irr::KEY_ESCAPE) { quitMenu(); return true; } else if (event.EventType == EET_GUI_EVENT) { - if (event.GUIEvent.EventType == gui::EGET_ELEMENT_FOCUS_LOST - && isVisible()) - { - if (!canTakeFocus(event.GUIEvent.Element)) - { - infostream << "GUIKeyChangeMenu: Not allowing focus change." - << std::endl; + if (event.GUIEvent.EventType == gui::EGET_ELEMENT_FOCUS_LOST && + isVisible()) { + if (!canTakeFocus(event.GUIEvent.Element)) { + infostream << "GUIKeyChangeMenu: Not allowing focus " + "change." + << std::endl; // Returning true disables focus change return true; } } - if (event.GUIEvent.EventType == gui::EGET_BUTTON_CLICKED) - { - switch (event.GUIEvent.Caller->getID()) - { - case GUI_ID_BACK_BUTTON: //back - acceptInput(); - quitMenu(); - return true; - case GUI_ID_ABORT_BUTTON: //abort - quitMenu(); - return true; - default: - resetMenu(); - for (key_setting *ks : key_settings) { - if (ks->id == event.GUIEvent.Caller->getID()) { - active_key = ks; - break; - } + if (event.GUIEvent.EventType == gui::EGET_BUTTON_CLICKED) { + switch (event.GUIEvent.Caller->getID()) { + case GUI_ID_BACK_BUTTON: // back + acceptInput(); + quitMenu(); + return true; + case GUI_ID_ABORT_BUTTON: // abort + quitMenu(); + return true; + default: + resetMenu(); + for (key_setting *ks : key_settings) { + if (ks->id == event.GUIEvent.Caller->getID()) { + active_key = ks; + break; } - FATAL_ERROR_IF(!active_key, "Key setting not found"); + } + FATAL_ERROR_IF(!active_key, "Key setting not found"); - shift_down = false; - const wchar_t *text = wgettext("press key"); - active_key->button->setText(text); - delete[] text; - break; + shift_down = false; + const wchar_t *text = wgettext("press key"); + active_key->button->setText(text); + delete[] text; + break; } Environment->setFocus(this); } @@ -410,7 +412,8 @@ bool GUIKeyChangeMenu::OnEvent(const SEvent& event) return Parent ? Parent->OnEvent(event) : false; } -void GUIKeyChangeMenu::add_key(int id, const wchar_t *button_name, const std::string &setting_name) +void GUIKeyChangeMenu::add_key( + int id, const wchar_t *button_name, const std::string &setting_name) { key_setting *k = new key_setting; k->id = id; @@ -423,48 +426,76 @@ void GUIKeyChangeMenu::add_key(int id, const wchar_t *button_name, const std::st void GUIKeyChangeMenu::init_keys() { - this->add_key(GUI_ID_KEY_FORWARD_BUTTON, wgettext("Forward"), "keymap_forward"); - this->add_key(GUI_ID_KEY_BACKWARD_BUTTON, wgettext("Backward"), "keymap_backward"); - this->add_key(GUI_ID_KEY_LEFT_BUTTON, wgettext("Left"), "keymap_left"); - this->add_key(GUI_ID_KEY_RIGHT_BUTTON, wgettext("Right"), "keymap_right"); - this->add_key(GUI_ID_KEY_USE_BUTTON, wgettext("Special"), "keymap_special1"); - this->add_key(GUI_ID_KEY_JUMP_BUTTON, wgettext("Jump"), "keymap_jump"); - this->add_key(GUI_ID_KEY_SNEAK_BUTTON, wgettext("Sneak"), "keymap_sneak"); - this->add_key(GUI_ID_KEY_DROP_BUTTON, wgettext("Drop"), "keymap_drop"); - this->add_key(GUI_ID_KEY_INVENTORY_BUTTON, wgettext("Inventory"), "keymap_inventory"); - this->add_key(GUI_ID_KEY_ENDERCHEST_BUTTON,wgettext("Enderchest"), "keymap_enderchest"); - this->add_key(GUI_ID_KEY_HOTBAR_PREV_BUTTON,wgettext("Prev. item"), "keymap_hotbar_previous"); - this->add_key(GUI_ID_KEY_HOTBAR_NEXT_BUTTON,wgettext("Next item"), "keymap_hotbar_next"); - this->add_key(GUI_ID_KEY_ZOOM_BUTTON, wgettext("Zoom"), "keymap_zoom"); - this->add_key(GUI_ID_KEY_CAMERA_BUTTON, wgettext("Change camera"), "keymap_camera_mode"); - this->add_key(GUI_ID_KEY_MINIMAP_BUTTON, wgettext("Toggle minimap"), "keymap_minimap"); - this->add_key(GUI_ID_KEY_FLY_BUTTON, wgettext("Toggle fly"), "keymap_freemove"); - this->add_key(GUI_ID_KEY_PITCH_MOVE, wgettext("Toggle pitchmove"), "keymap_pitchmove"); - this->add_key(GUI_ID_KEY_FAST_BUTTON, wgettext("Toggle fast"), "keymap_fastmove"); - this->add_key(GUI_ID_KEY_NOCLIP_BUTTON, wgettext("Toggle noclip"), "keymap_noclip"); - this->add_key(GUI_ID_KEY_MUTE_BUTTON, wgettext("Mute"), "keymap_mute"); - this->add_key(GUI_ID_KEY_DEC_VOLUME_BUTTON,wgettext("Dec. volume"), "keymap_decrease_volume"); - this->add_key(GUI_ID_KEY_INC_VOLUME_BUTTON,wgettext("Inc. volume"), "keymap_increase_volume"); - this->add_key(GUI_ID_KEY_AUTOFWD_BUTTON, wgettext("Autoforward"), "keymap_autoforward"); - this->add_key(GUI_ID_KEY_CHAT_BUTTON, wgettext("Chat"), "keymap_chat"); - this->add_key(GUI_ID_KEY_SCREENSHOT_BUTTON,wgettext("Screenshot"), "keymap_screenshot"); - this->add_key(GUI_ID_KEY_RANGE_BUTTON, wgettext("Range select"), "keymap_rangeselect"); - this->add_key(GUI_ID_KEY_DEC_RANGE_BUTTON, wgettext("Dec. range"), "keymap_decrease_viewing_range_min"); - this->add_key(GUI_ID_KEY_INC_RANGE_BUTTON, wgettext("Inc. range"), "keymap_increase_viewing_range_min"); - this->add_key(GUI_ID_KEY_CONSOLE_BUTTON, wgettext("Console"), "keymap_console"); - this->add_key(GUI_ID_KEY_CMD_BUTTON, wgettext("Command"), "keymap_cmd"); - this->add_key(GUI_ID_KEY_CMD_LOCAL_BUTTON, wgettext("Local command"), "keymap_cmd_local"); - this->add_key(GUI_ID_KEY_HUD_BUTTON, wgettext("Toggle HUD"), "keymap_toggle_hud"); - this->add_key(GUI_ID_KEY_CHATLOG_BUTTON, wgettext("Toggle chat log"), "keymap_toggle_chat"); - this->add_key(GUI_ID_KEY_FOG_BUTTON, wgettext("Toggle fog"), "keymap_toggle_fog"); - this->add_key(GUI_ID_KEY_CHEAT_MENU_BUTTON,wgettext("Toggle C. Menu"),"keymap_toggle_cheat_menu"); - this->add_key(GUI_ID_KEY_KILLAURA_BUTTON, wgettext("Killaura"), "keymap_toggle_killaura"); - this->add_key(GUI_ID_KEY_FREECAM_BUTTON, wgettext("Freecam"), "keymap_toggle_freecam"); - this->add_key(GUI_ID_KEY_SCAFFOLD_BUTTON, wgettext("Scaffold"), "keymap_toggle_scaffold"); - this->add_key(GUI_ID_KEY_NEXT_ITEM_BUTTON, wgettext("NextItem"), "keymap_toggle_next_item"); - this->add_key(GUI_ID_KEY_SELECT_UP_BUTTON, wgettext("C. Menu Up"), "keymap_select_up"); - this->add_key(GUI_ID_KEY_SELECT_DOWN_BUTTON,wgettext("C. Menu Down"), "keymap_select_down"); - this->add_key(GUI_ID_KEY_SELECT_LEFT_BUTTON,wgettext("C. Menu Left"), "keymap_select_left"); - this->add_key(GUI_ID_KEY_SELECT_RIGHT_BUTTON,wgettext("C. Menu Right"),"keymap_select_right"); - this->add_key(GUI_ID_KEY_SELECT_CONFIRM_BUTTON,wgettext("C. Menu Enter"),"keymap_select_confirm"); + this->add_key(GUI_ID_KEY_FORWARD_BUTTON, wgettext("Forward"), "keymap_forward"); + this->add_key(GUI_ID_KEY_BACKWARD_BUTTON, wgettext("Backward"), + "keymap_backward"); + this->add_key(GUI_ID_KEY_LEFT_BUTTON, wgettext("Left"), "keymap_left"); + this->add_key(GUI_ID_KEY_RIGHT_BUTTON, wgettext("Right"), "keymap_right"); + this->add_key(GUI_ID_KEY_USE_BUTTON, wgettext("Special"), "keymap_special1"); + this->add_key(GUI_ID_KEY_JUMP_BUTTON, wgettext("Jump"), "keymap_jump"); + this->add_key(GUI_ID_KEY_SNEAK_BUTTON, wgettext("Sneak"), "keymap_sneak"); + this->add_key(GUI_ID_KEY_DROP_BUTTON, wgettext("Drop"), "keymap_drop"); + this->add_key(GUI_ID_KEY_INVENTORY_BUTTON, wgettext("Inventory"), + "keymap_inventory"); + this->add_key(GUI_ID_KEY_ENDERCHEST_BUTTON, wgettext("Enderchest"), + "keymap_enderchest"); + this->add_key(GUI_ID_KEY_HOTBAR_PREV_BUTTON, wgettext("Prev. item"), + "keymap_hotbar_previous"); + this->add_key(GUI_ID_KEY_HOTBAR_NEXT_BUTTON, wgettext("Next item"), + "keymap_hotbar_next"); + this->add_key(GUI_ID_KEY_ZOOM_BUTTON, wgettext("Zoom"), "keymap_zoom"); + this->add_key(GUI_ID_KEY_CAMERA_BUTTON, wgettext("Change camera"), + "keymap_camera_mode"); + this->add_key(GUI_ID_KEY_MINIMAP_BUTTON, wgettext("Toggle minimap"), + "keymap_minimap"); + this->add_key(GUI_ID_KEY_FLY_BUTTON, wgettext("Toggle fly"), "keymap_freemove"); + this->add_key(GUI_ID_KEY_PITCH_MOVE, wgettext("Toggle pitchmove"), + "keymap_pitchmove"); + this->add_key(GUI_ID_KEY_FAST_BUTTON, wgettext("Toggle fast"), "keymap_fastmove"); + this->add_key(GUI_ID_KEY_NOCLIP_BUTTON, wgettext("Toggle noclip"), + "keymap_noclip"); + this->add_key(GUI_ID_KEY_MUTE_BUTTON, wgettext("Mute"), "keymap_mute"); + this->add_key(GUI_ID_KEY_DEC_VOLUME_BUTTON, wgettext("Dec. volume"), + "keymap_decrease_volume"); + this->add_key(GUI_ID_KEY_INC_VOLUME_BUTTON, wgettext("Inc. volume"), + "keymap_increase_volume"); + this->add_key(GUI_ID_KEY_AUTOFWD_BUTTON, wgettext("Autoforward"), + "keymap_autoforward"); + this->add_key(GUI_ID_KEY_CHAT_BUTTON, wgettext("Chat"), "keymap_chat"); + this->add_key(GUI_ID_KEY_SCREENSHOT_BUTTON, wgettext("Screenshot"), + "keymap_screenshot"); + this->add_key(GUI_ID_KEY_RANGE_BUTTON, wgettext("Range select"), + "keymap_rangeselect"); + this->add_key(GUI_ID_KEY_DEC_RANGE_BUTTON, wgettext("Dec. range"), + "keymap_decrease_viewing_range_min"); + this->add_key(GUI_ID_KEY_INC_RANGE_BUTTON, wgettext("Inc. range"), + "keymap_increase_viewing_range_min"); + this->add_key(GUI_ID_KEY_CONSOLE_BUTTON, wgettext("Console"), "keymap_console"); + this->add_key(GUI_ID_KEY_CMD_BUTTON, wgettext("Command"), "keymap_cmd"); + this->add_key(GUI_ID_KEY_CMD_LOCAL_BUTTON, wgettext("Local command"), + "keymap_cmd_local"); + this->add_key(GUI_ID_KEY_HUD_BUTTON, wgettext("Toggle HUD"), "keymap_toggle_hud"); + this->add_key(GUI_ID_KEY_CHATLOG_BUTTON, wgettext("Toggle chat log"), + "keymap_toggle_chat"); + this->add_key(GUI_ID_KEY_FOG_BUTTON, wgettext("Toggle fog"), "keymap_toggle_fog"); + this->add_key(GUI_ID_KEY_CHEAT_MENU_BUTTON, wgettext("Toggle C. Menu"), + "keymap_toggle_cheat_menu"); + this->add_key(GUI_ID_KEY_KILLAURA_BUTTON, wgettext("Killaura"), + "keymap_toggle_killaura"); + this->add_key(GUI_ID_KEY_FREECAM_BUTTON, wgettext("Freecam"), + "keymap_toggle_freecam"); + this->add_key(GUI_ID_KEY_SCAFFOLD_BUTTON, wgettext("Scaffold"), + "keymap_toggle_scaffold"); + this->add_key(GUI_ID_KEY_NEXT_ITEM_BUTTON, wgettext("NextItem"), + "keymap_toggle_next_item"); + this->add_key(GUI_ID_KEY_SELECT_UP_BUTTON, wgettext("C. Menu Up"), + "keymap_select_up"); + this->add_key(GUI_ID_KEY_SELECT_DOWN_BUTTON, wgettext("C. Menu Down"), + "keymap_select_down"); + this->add_key(GUI_ID_KEY_SELECT_LEFT_BUTTON, wgettext("C. Menu Left"), + "keymap_select_left"); + this->add_key(GUI_ID_KEY_SELECT_RIGHT_BUTTON, wgettext("C. Menu Right"), + "keymap_select_right"); + this->add_key(GUI_ID_KEY_SELECT_CONFIRM_BUTTON, wgettext("C. Menu Enter"), + "keymap_select_confirm"); } diff --git a/src/gui/guiMainMenu.h b/src/gui/guiMainMenu.h index 1dca8bf2d..957ffbc41 100644 --- a/src/gui/guiMainMenu.h +++ b/src/gui/guiMainMenu.h @@ -23,7 +23,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include -struct MainMenuDataForScript { +struct MainMenuDataForScript +{ MainMenuDataForScript() = default; @@ -32,7 +33,8 @@ struct MainMenuDataForScript { std::string errormessage = ""; }; -struct MainMenuData { +struct MainMenuData +{ // Client options std::string servername; std::string serverdescription; diff --git a/src/gui/guiPasswordChange.cpp b/src/gui/guiPasswordChange.cpp index 5311c6fef..5eb777173 100644 --- a/src/gui/guiPasswordChange.cpp +++ b/src/gui/guiPasswordChange.cpp @@ -35,15 +35,11 @@ const int ID_change = 259; const int ID_message = 260; const int ID_cancel = 261; -GUIPasswordChange::GUIPasswordChange(gui::IGUIEnvironment* env, - gui::IGUIElement* parent, s32 id, - IMenuManager *menumgr, - Client* client, - ISimpleTextureSource *tsrc -): - GUIModalMenu(env, parent, id, menumgr), - m_client(client), - m_tsrc(tsrc) +GUIPasswordChange::GUIPasswordChange(gui::IGUIEnvironment *env, gui::IGUIElement *parent, + s32 id, IMenuManager *menumgr, Client *client, + ISimpleTextureSource *tsrc) : + GUIModalMenu(env, parent, id, menumgr), + m_client(client), m_tsrc(tsrc) { } @@ -80,12 +76,9 @@ void GUIPasswordChange::regenerateGui(v2u32 screensize) Calculate new sizes and positions */ const float s = m_gui_scale; - DesiredRect = core::rect( - screensize.X / 2 - 580 * s / 2, - screensize.Y / 2 - 300 * s / 2, - screensize.X / 2 + 580 * s / 2, - screensize.Y / 2 + 300 * s / 2 - ); + DesiredRect = core::rect(screensize.X / 2 - 580 * s / 2, + screensize.Y / 2 - 300 * s / 2, screensize.X / 2 + 580 * s / 2, + screensize.Y / 2 + 300 * s / 2); recalculateAbsolutePosition(false); v2s32 size = DesiredRect.getSize(); @@ -138,8 +131,8 @@ void GUIPasswordChange::regenerateGui(v2u32 screensize) { core::rect rect(0, 0, 230 * s, 30 * s); rect += topleft_client + v2s32(160 * s, ypos); - gui::IGUIEditBox *e = Environment->addEditBox( - m_newpass_confirm.c_str(), rect, true, this, ID_newPassword2); + gui::IGUIEditBox *e = Environment->addEditBox(m_newpass_confirm.c_str(), + rect, true, this, ID_newPassword2); e->setPasswordBox(true); } @@ -164,9 +157,8 @@ void GUIPasswordChange::regenerateGui(v2u32 screensize) core::rect rect(0, 0, 300 * s, 20 * s); rect += topleft_client + v2s32(35 * s, ypos); text = wgettext("Passwords do not match!"); - IGUIElement *e = - Environment->addStaticText( - text, rect, false, true, this, ID_message); + IGUIElement *e = Environment->addStaticText( + text, rect, false, true, this, ID_message); e->setVisible(false); delete[] text; } @@ -236,8 +228,9 @@ bool GUIPasswordChange::OnEvent(const SEvent &event) if (event.GUIEvent.EventType == gui::EGET_ELEMENT_FOCUS_LOST && isVisible()) { if (!canTakeFocus(event.GUIEvent.Element)) { - infostream << "GUIPasswordChange: Not allowing focus change." - << std::endl; + infostream << "GUIPasswordChange: Not allowing focus " + "change." + << std::endl; // Returning true disables focus change return true; } diff --git a/src/gui/guiPathSelectMenu.cpp b/src/gui/guiPathSelectMenu.cpp index 489927a11..562778894 100644 --- a/src/gui/guiPathSelectMenu.cpp +++ b/src/gui/guiPathSelectMenu.cpp @@ -19,14 +19,12 @@ #include "guiPathSelectMenu.h" -GUIFileSelectMenu::GUIFileSelectMenu(gui::IGUIEnvironment* env, - gui::IGUIElement* parent, s32 id, IMenuManager *menumgr, - const std::string &title, const std::string &formname, - bool is_file_select) : - GUIModalMenu(env, parent, id, menumgr), - m_title(utf8_to_wide(title)), - m_formname(formname), - m_file_select_dialog(is_file_select) +GUIFileSelectMenu::GUIFileSelectMenu(gui::IGUIEnvironment *env, gui::IGUIElement *parent, + s32 id, IMenuManager *menumgr, const std::string &title, + const std::string &formname, bool is_file_select) : + GUIModalMenu(env, parent, id, menumgr), + m_title(utf8_to_wide(title)), m_formname(formname), + m_file_select_dialog(is_file_select) { } diff --git a/src/gui/guiScrollBar.cpp b/src/gui/guiScrollBar.cpp index b04ccb9d5..4f4b555ab 100644 --- a/src/gui/guiScrollBar.cpp +++ b/src/gui/guiScrollBar.cpp @@ -21,7 +21,8 @@ GUIScrollBar::GUIScrollBar(IGUIEnvironment *environment, IGUIElement *parent, s3 is_horizontal(horizontal), is_auto_scaling(auto_scale), dragged_by_slider(false), tray_clicked(false), scroll_pos(0), draw_center(0), thumb_size(0), min_pos(0), max_pos(100), small_step(10), - large_step(50), last_change(0), drag_offset(0), page_size(100), border_size(0) + large_step(50), last_change(0), drag_offset(0), page_size(100), + border_size(0) { refreshControls(); setNotClipped(false); @@ -115,8 +116,10 @@ bool GUIScrollBar::OnEvent(const SEvent &event) if (is_inside) { is_dragging = true; dragged_by_slider = slider_rect.isPointInside(p); - core::vector2di corner = slider_rect.UpperLeftCorner; - drag_offset = is_horizontal ? p.X - corner.X : p.Y - corner.Y; + core::vector2di corner = + slider_rect.UpperLeftCorner; + drag_offset = is_horizontal ? p.X - corner.X + : p.Y - corner.Y; tray_clicked = !dragged_by_slider; if (tray_clicked) { const s32 new_pos = getPosFromMousePos(p); @@ -130,7 +133,8 @@ bool GUIScrollBar::OnEvent(const SEvent &event) e.EventType = EET_GUI_EVENT; e.GUIEvent.Caller = this; e.GUIEvent.Element = nullptr; - e.GUIEvent.EventType = EGET_SCROLL_BAR_CHANGED; + e.GUIEvent.EventType = + EGET_SCROLL_BAR_CHANGED; Parent->OnEvent(e); } } @@ -247,7 +251,8 @@ s32 GUIScrollBar::getPosFromMousePos(const core::position2di &pos) const w = RelativeRect.getHeight() - border_size * 2 - thumb_size; p = pos.Y - AbsoluteRect.UpperLeftCorner.Y - border_size - offset; } - return core::isnotzero(range()) ? s32(f32(p) / f32(w) * range() + 0.5f) + min_pos : 0; + return core::isnotzero(range()) ? s32(f32(p) / f32(w) * range() + 0.5f) + min_pos + : 0; } void GUIScrollBar::setPos(const s32 &pos) @@ -273,7 +278,7 @@ void GUIScrollBar::setPos(const s32 &pos) f32 f = core::isnotzero(range()) ? (f32(thumb_area) - f32(thumb_size)) / range() : 1.0f; draw_center = s32((f32(scroll_pos - min_pos) * f) + (f32(thumb_size) * 0.5f)) + - border_size; + border_size; } void GUIScrollBar::setSmallStep(const s32 &step) diff --git a/src/gui/guiSkin.cpp b/src/gui/guiSkin.cpp index 8892a00b4..e2a6af5fd 100644 --- a/src/gui/guiSkin.cpp +++ b/src/gui/guiSkin.cpp @@ -18,41 +18,39 @@ namespace irr namespace gui { -GUISkin::GUISkin(EGUI_SKIN_TYPE type, video::IVideoDriver* driver) -: SpriteBank(0), Driver(driver), Type(type) +GUISkin::GUISkin(EGUI_SKIN_TYPE type, video::IVideoDriver *driver) : + SpriteBank(0), Driver(driver), Type(type) { - #ifdef _DEBUG +#ifdef _DEBUG setDebugName("GUISkin"); - #endif - - if ((Type == EGST_WINDOWS_CLASSIC) || (Type == EGST_WINDOWS_METALLIC)) - { - Colors[EGDC_3D_DARK_SHADOW] = video::SColor(101,50,50,50); - Colors[EGDC_3D_SHADOW] = video::SColor(101,130,130,130); - Colors[EGDC_3D_FACE] = video::SColor(101,210,210,210); - Colors[EGDC_3D_HIGH_LIGHT] = video::SColor(101,255,255,255); - Colors[EGDC_3D_LIGHT] = video::SColor(101,210,210,210); - Colors[EGDC_ACTIVE_BORDER] = video::SColor(101,16,14,115); - Colors[EGDC_ACTIVE_CAPTION] = video::SColor(255,255,255,255); - Colors[EGDC_APP_WORKSPACE] = video::SColor(101,100,100,100); - Colors[EGDC_BUTTON_TEXT] = video::SColor(240,10,10,10); - Colors[EGDC_GRAY_TEXT] = video::SColor(240,130,130,130); - Colors[EGDC_HIGH_LIGHT] = video::SColor(101,8,36,107); - Colors[EGDC_HIGH_LIGHT_TEXT] = video::SColor(240,255,255,255); - Colors[EGDC_INACTIVE_BORDER] = video::SColor(101,165,165,165); - Colors[EGDC_INACTIVE_CAPTION] = video::SColor(255,30,30,30); - Colors[EGDC_TOOLTIP] = video::SColor(200,0,0,0); - Colors[EGDC_TOOLTIP_BACKGROUND] = video::SColor(200,255,255,225); - Colors[EGDC_SCROLLBAR] = video::SColor(101,230,230,230); - Colors[EGDC_WINDOW] = video::SColor(101,255,255,255); - Colors[EGDC_WINDOW_SYMBOL] = video::SColor(200,10,10,10); - Colors[EGDC_ICON] = video::SColor(200,255,255,255); - Colors[EGDC_ICON_HIGH_LIGHT] = video::SColor(200,8,36,107); - Colors[EGDC_GRAY_WINDOW_SYMBOL] = video::SColor(240,100,100,100); - Colors[EGDC_EDITABLE] = video::SColor(255,255,255,255); - Colors[EGDC_GRAY_EDITABLE] = video::SColor(255,120,120,120); - Colors[EGDC_FOCUSED_EDITABLE] = video::SColor(255,240,240,255); +#endif + if ((Type == EGST_WINDOWS_CLASSIC) || (Type == EGST_WINDOWS_METALLIC)) { + Colors[EGDC_3D_DARK_SHADOW] = video::SColor(101, 50, 50, 50); + Colors[EGDC_3D_SHADOW] = video::SColor(101, 130, 130, 130); + Colors[EGDC_3D_FACE] = video::SColor(101, 210, 210, 210); + Colors[EGDC_3D_HIGH_LIGHT] = video::SColor(101, 255, 255, 255); + Colors[EGDC_3D_LIGHT] = video::SColor(101, 210, 210, 210); + Colors[EGDC_ACTIVE_BORDER] = video::SColor(101, 16, 14, 115); + Colors[EGDC_ACTIVE_CAPTION] = video::SColor(255, 255, 255, 255); + Colors[EGDC_APP_WORKSPACE] = video::SColor(101, 100, 100, 100); + Colors[EGDC_BUTTON_TEXT] = video::SColor(240, 10, 10, 10); + Colors[EGDC_GRAY_TEXT] = video::SColor(240, 130, 130, 130); + Colors[EGDC_HIGH_LIGHT] = video::SColor(101, 8, 36, 107); + Colors[EGDC_HIGH_LIGHT_TEXT] = video::SColor(240, 255, 255, 255); + Colors[EGDC_INACTIVE_BORDER] = video::SColor(101, 165, 165, 165); + Colors[EGDC_INACTIVE_CAPTION] = video::SColor(255, 30, 30, 30); + Colors[EGDC_TOOLTIP] = video::SColor(200, 0, 0, 0); + Colors[EGDC_TOOLTIP_BACKGROUND] = video::SColor(200, 255, 255, 225); + Colors[EGDC_SCROLLBAR] = video::SColor(101, 230, 230, 230); + Colors[EGDC_WINDOW] = video::SColor(101, 255, 255, 255); + Colors[EGDC_WINDOW_SYMBOL] = video::SColor(200, 10, 10, 10); + Colors[EGDC_ICON] = video::SColor(200, 255, 255, 255); + Colors[EGDC_ICON_HIGH_LIGHT] = video::SColor(200, 8, 36, 107); + Colors[EGDC_GRAY_WINDOW_SYMBOL] = video::SColor(240, 100, 100, 100); + Colors[EGDC_EDITABLE] = video::SColor(255, 255, 255, 255); + Colors[EGDC_GRAY_EDITABLE] = video::SColor(255, 120, 120, 120); + Colors[EGDC_FOCUSED_EDITABLE] = video::SColor(255, 240, 240, 255); Sizes[EGDS_SCROLLBAR_SIZE] = 14; Sizes[EGDS_MENU_HEIGHT] = 30; @@ -68,36 +66,36 @@ GUISkin::GUISkin(EGUI_SKIN_TYPE type, video::IVideoDriver* driver) Sizes[EGDS_TITLEBARTEXT_DISTANCE_X] = 2; Sizes[EGDS_TITLEBARTEXT_DISTANCE_Y] = 0; - } - else - { - //0x80a6a8af - Colors[EGDC_3D_DARK_SHADOW] = 0x60767982; - //Colors[EGDC_3D_FACE] = 0xc0c9ccd4; // tab background - Colors[EGDC_3D_FACE] = 0xc0cbd2d9; // tab background - Colors[EGDC_3D_SHADOW] = 0x50e4e8f1; // tab background, and left-top highlight - Colors[EGDC_3D_HIGH_LIGHT] = 0x40c7ccdc; - Colors[EGDC_3D_LIGHT] = 0x802e313a; - Colors[EGDC_ACTIVE_BORDER] = 0x80404040; // window title - Colors[EGDC_ACTIVE_CAPTION] = 0xffd0d0d0; - Colors[EGDC_APP_WORKSPACE] = 0xc0646464; // unused - Colors[EGDC_BUTTON_TEXT] = 0xd0161616; - Colors[EGDC_GRAY_TEXT] = 0x3c141414; - Colors[EGDC_HIGH_LIGHT] = 0x6c606060; - Colors[EGDC_HIGH_LIGHT_TEXT] = 0xd0e0e0e0; - Colors[EGDC_INACTIVE_BORDER] = 0xf0a5a5a5; - Colors[EGDC_INACTIVE_CAPTION] = 0xffd2d2d2; - Colors[EGDC_TOOLTIP] = 0xf00f2033; - Colors[EGDC_TOOLTIP_BACKGROUND] = 0xc0cbd2d9; - Colors[EGDC_SCROLLBAR] = 0xf0e0e0e0; - Colors[EGDC_WINDOW] = 0xf0f0f0f0; - Colors[EGDC_WINDOW_SYMBOL] = 0xd0161616; - Colors[EGDC_ICON] = 0xd0161616; - Colors[EGDC_ICON_HIGH_LIGHT] = 0xd0606060; - Colors[EGDC_GRAY_WINDOW_SYMBOL] = 0x3c101010; - Colors[EGDC_EDITABLE] = 0xf0ffffff; - Colors[EGDC_GRAY_EDITABLE] = 0xf0cccccc; - Colors[EGDC_FOCUSED_EDITABLE] = 0xf0fffff0; + } else { + // 0x80a6a8af + Colors[EGDC_3D_DARK_SHADOW] = 0x60767982; + // Colors[EGDC_3D_FACE] = 0xc0c9ccd4; // tab + // background + Colors[EGDC_3D_FACE] = 0xc0cbd2d9; // tab background + Colors[EGDC_3D_SHADOW] = + 0x50e4e8f1; // tab background, and left-top highlight + Colors[EGDC_3D_HIGH_LIGHT] = 0x40c7ccdc; + Colors[EGDC_3D_LIGHT] = 0x802e313a; + Colors[EGDC_ACTIVE_BORDER] = 0x80404040; // window title + Colors[EGDC_ACTIVE_CAPTION] = 0xffd0d0d0; + Colors[EGDC_APP_WORKSPACE] = 0xc0646464; // unused + Colors[EGDC_BUTTON_TEXT] = 0xd0161616; + Colors[EGDC_GRAY_TEXT] = 0x3c141414; + Colors[EGDC_HIGH_LIGHT] = 0x6c606060; + Colors[EGDC_HIGH_LIGHT_TEXT] = 0xd0e0e0e0; + Colors[EGDC_INACTIVE_BORDER] = 0xf0a5a5a5; + Colors[EGDC_INACTIVE_CAPTION] = 0xffd2d2d2; + Colors[EGDC_TOOLTIP] = 0xf00f2033; + Colors[EGDC_TOOLTIP_BACKGROUND] = 0xc0cbd2d9; + Colors[EGDC_SCROLLBAR] = 0xf0e0e0e0; + Colors[EGDC_WINDOW] = 0xf0f0f0f0; + Colors[EGDC_WINDOW_SYMBOL] = 0xd0161616; + Colors[EGDC_ICON] = 0xd0161616; + Colors[EGDC_ICON_HIGH_LIGHT] = 0xd0606060; + Colors[EGDC_GRAY_WINDOW_SYMBOL] = 0x3c101010; + Colors[EGDC_EDITABLE] = 0xf0ffffff; + Colors[EGDC_GRAY_EDITABLE] = 0xf0cccccc; + Colors[EGDC_FOCUSED_EDITABLE] = 0xf0fffff0; Sizes[EGDS_SCROLLBAR_SIZE] = 14; Sizes[EGDS_MENU_HEIGHT] = 48; @@ -160,18 +158,16 @@ GUISkin::GUISkin(EGUI_SKIN_TYPE type, video::IVideoDriver* driver) Icons[EGDI_FILE] = 245; Icons[EGDI_DIRECTORY] = 246; - for (u32 i=0; idrop(); } @@ -180,7 +176,6 @@ GUISkin::~GUISkin() SpriteBank->drop(); } - //! returns default color video::SColor GUISkin::getColor(EGUI_DEFAULT_COLOR color) const { @@ -190,7 +185,6 @@ video::SColor GUISkin::getColor(EGUI_DEFAULT_COLOR color) const return video::SColor(); } - //! sets a default color void GUISkin::setColor(EGUI_DEFAULT_COLOR which, video::SColor newColor) { @@ -198,7 +192,6 @@ void GUISkin::setColor(EGUI_DEFAULT_COLOR which, video::SColor newColor) Colors[which] = newColor; } - //! returns size for the given size type s32 GUISkin::getSize(EGUI_DEFAULT_SIZE size) const { @@ -208,7 +201,6 @@ s32 GUISkin::getSize(EGUI_DEFAULT_SIZE size) const return 0; } - //! sets a default size void GUISkin::setSize(EGUI_DEFAULT_SIZE which, s32 size) { @@ -216,9 +208,8 @@ void GUISkin::setSize(EGUI_DEFAULT_SIZE which, s32 size) Sizes[which] = size; } - //! returns the default font -IGUIFont* GUISkin::getFont(EGUI_DEFAULT_FONT which) const +IGUIFont *GUISkin::getFont(EGUI_DEFAULT_FONT which) const { if (((u32)which < EGDF_COUNT) && Fonts[which]) return Fonts[which]; @@ -226,15 +217,13 @@ IGUIFont* GUISkin::getFont(EGUI_DEFAULT_FONT which) const return Fonts[EGDF_DEFAULT]; } - //! sets a default font -void GUISkin::setFont(IGUIFont* font, EGUI_DEFAULT_FONT which) +void GUISkin::setFont(IGUIFont *font, EGUI_DEFAULT_FONT which) { if ((u32)which >= EGDF_COUNT) return; - if (font) - { + if (font) { font->grab(); if (Fonts[which]) Fonts[which]->drop(); @@ -243,16 +232,14 @@ void GUISkin::setFont(IGUIFont* font, EGUI_DEFAULT_FONT which) } } - //! gets the sprite bank stored -IGUISpriteBank* GUISkin::getSpriteBank() const +IGUISpriteBank *GUISkin::getSpriteBank() const { return SpriteBank; } - //! set a new sprite bank or remove one by passing 0 -void GUISkin::setSpriteBank(IGUISpriteBank* bank) +void GUISkin::setSpriteBank(IGUISpriteBank *bank) { if (bank) bank->grab(); @@ -263,7 +250,6 @@ void GUISkin::setSpriteBank(IGUISpriteBank* bank) SpriteBank = bank; } - //! Returns a default icon u32 GUISkin::getIcon(EGUI_DEFAULT_ICON icon) const { @@ -273,7 +259,6 @@ u32 GUISkin::getIcon(EGUI_DEFAULT_ICON icon) const return 0; } - //! Sets a default icon void GUISkin::setIcon(EGUI_DEFAULT_ICON icon, u32 index) { @@ -281,10 +266,9 @@ void GUISkin::setIcon(EGUI_DEFAULT_ICON icon, u32 index) Icons[icon] = index; } - //! Returns a default text. For example for Message box button captions: //! "OK", "Cancel", "Yes", "No" and so on. -const wchar_t* GUISkin::getDefaultText(EGUI_DEFAULT_TEXT text) const +const wchar_t *GUISkin::getDefaultText(EGUI_DEFAULT_TEXT text) const { if ((u32)text < EGDT_COUNT) return Texts[text].c_str(); @@ -292,16 +276,14 @@ const wchar_t* GUISkin::getDefaultText(EGUI_DEFAULT_TEXT text) const return Texts[0].c_str(); } - //! Sets a default text. For example for Message box button captions: //! "OK", "Cancel", "Yes", "No" and so on. -void GUISkin::setDefaultText(EGUI_DEFAULT_TEXT which, const wchar_t* newText) +void GUISkin::setDefaultText(EGUI_DEFAULT_TEXT which, const wchar_t *newText) { if ((u32)which < EGDT_COUNT) Texts[which] = newText; } - //! draws a standard 3d button pane /** Used for drawing for example buttons in normal state. It uses the colors EGDC_3D_DARK_SHADOW, EGDC_3D_HIGH_LIGHT, EGDC_3D_SHADOW and @@ -312,10 +294,9 @@ EGDC_3D_FACE for this. See EGUI_DEFAULT_COLOR for details. is usually not used by ISkin, but can be used for example by more complex implementations to find out how to draw the part exactly. */ // PATCH -void GUISkin::drawColored3DButtonPaneStandard(IGUIElement* element, - const core::rect& r, - const core::rect* clip, - const video::SColor* colors) +void GUISkin::drawColored3DButtonPaneStandard(IGUIElement *element, + const core::rect &r, const core::rect *clip, + const video::SColor *colors) { if (!Driver) return; @@ -325,15 +306,14 @@ void GUISkin::drawColored3DButtonPaneStandard(IGUIElement* element, core::rect rect = r; - if ( Type == EGST_BURNING_SKIN ) - { + if (Type == EGST_BURNING_SKIN) { rect.UpperLeftCorner.X -= 1; rect.UpperLeftCorner.Y -= 1; rect.LowerRightCorner.X += 1; rect.LowerRightCorner.Y += 1; draw3DSunkenPane(element, - colors[ EGDC_WINDOW ].getInterpolated( 0xFFFFFFFF, 0.9f ) - ,false, true, rect, clip); + colors[EGDC_WINDOW].getInterpolated(0xFFFFFFFF, 0.9f), + false, true, rect, clip); return; } @@ -350,20 +330,17 @@ void GUISkin::drawColored3DButtonPaneStandard(IGUIElement* element, rect.LowerRightCorner.X -= 1; rect.LowerRightCorner.Y -= 1; - if (!UseGradient) - { + if (!UseGradient) { Driver->draw2DRectangle(colors[EGDC_3D_FACE], rect, clip); - } - else - { + } else { const video::SColor c1 = colors[EGDC_3D_FACE]; - const video::SColor c2 = c1.getInterpolated(colors[EGDC_3D_DARK_SHADOW], 0.4f); + const video::SColor c2 = + c1.getInterpolated(colors[EGDC_3D_DARK_SHADOW], 0.4f); Driver->draw2DRectangle(rect, c1, c1, c2, c2, clip); } } // END PATCH - //! draws a pressed 3d button pane /** Used for drawing for example buttons in pressed state. It uses the colors EGDC_3D_DARK_SHADOW, EGDC_3D_HIGH_LIGHT, EGDC_3D_SHADOW and @@ -374,10 +351,9 @@ EGDC_3D_FACE for this. See EGUI_DEFAULT_COLOR for details. is usually not used by ISkin, but can be used for example by more complex implementations to find out how to draw the part exactly. */ // PATCH -void GUISkin::drawColored3DButtonPanePressed(IGUIElement* element, - const core::rect& r, - const core::rect* clip, - const video::SColor* colors) +void GUISkin::drawColored3DButtonPanePressed(IGUIElement *element, + const core::rect &r, const core::rect *clip, + const video::SColor *colors) { if (!Driver) return; @@ -399,20 +375,17 @@ void GUISkin::drawColored3DButtonPanePressed(IGUIElement* element, rect.UpperLeftCorner.X += 1; rect.UpperLeftCorner.Y += 1; - if (!UseGradient) - { + if (!UseGradient) { Driver->draw2DRectangle(colors[EGDC_3D_FACE], rect, clip); - } - else - { + } else { const video::SColor c1 = colors[EGDC_3D_FACE]; - const video::SColor c2 = c1.getInterpolated(colors[EGDC_3D_DARK_SHADOW], 0.4f); + const video::SColor c2 = + c1.getInterpolated(colors[EGDC_3D_DARK_SHADOW], 0.4f); Driver->draw2DRectangle(rect, c1, c1, c2, c2, clip); } } // END PATCH - //! draws a sunken 3d pane /** Used for drawing the background of edit, combo or check boxes. \param element: Pointer to the element which wishes to draw this. This parameter @@ -424,11 +397,9 @@ deep into the ground. \param rect: Defining area where to draw. \param clip: Clip area. */ // PATCH -void GUISkin::drawColored3DSunkenPane(IGUIElement* element, video::SColor bgcolor, - bool flat, bool fillBackGround, - const core::rect& r, - const core::rect* clip, - const video::SColor* colors) +void GUISkin::drawColored3DSunkenPane(IGUIElement *element, video::SColor bgcolor, + bool flat, bool fillBackGround, const core::rect &r, + const core::rect *clip, const video::SColor *colors) { if (!Driver) return; @@ -441,34 +412,31 @@ void GUISkin::drawColored3DSunkenPane(IGUIElement* element, video::SColor bgcolo if (fillBackGround) Driver->draw2DRectangle(bgcolor, rect, clip); - if (flat) - { + if (flat) { // draw flat sunken pane rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + 1; - Driver->draw2DRectangle(colors[EGDC_3D_SHADOW], rect, clip); // top + Driver->draw2DRectangle(colors[EGDC_3D_SHADOW], rect, clip); // top ++rect.UpperLeftCorner.Y; rect.LowerRightCorner.Y = r.LowerRightCorner.Y; rect.LowerRightCorner.X = rect.UpperLeftCorner.X + 1; - Driver->draw2DRectangle(colors[EGDC_3D_SHADOW], rect, clip); // left + Driver->draw2DRectangle(colors[EGDC_3D_SHADOW], rect, clip); // left rect = r; ++rect.UpperLeftCorner.Y; rect.UpperLeftCorner.X = rect.LowerRightCorner.X - 1; - Driver->draw2DRectangle(colors[EGDC_3D_HIGH_LIGHT], rect, clip); // right + Driver->draw2DRectangle(colors[EGDC_3D_HIGH_LIGHT], rect, clip); // right rect = r; ++rect.UpperLeftCorner.X; rect.UpperLeftCorner.Y = r.LowerRightCorner.Y - 1; --rect.LowerRightCorner.X; - Driver->draw2DRectangle(colors[EGDC_3D_HIGH_LIGHT], rect, clip); // bottom - } - else - { + Driver->draw2DRectangle(colors[EGDC_3D_HIGH_LIGHT], rect, clip); // bottom + } else { // draw deep sunken pane rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + 1; - Driver->draw2DRectangle(colors[EGDC_3D_SHADOW], rect, clip); // top + Driver->draw2DRectangle(colors[EGDC_3D_SHADOW], rect, clip); // top ++rect.UpperLeftCorner.X; ++rect.UpperLeftCorner.Y; --rect.LowerRightCorner.X; @@ -476,10 +444,10 @@ void GUISkin::drawColored3DSunkenPane(IGUIElement* element, video::SColor bgcolo Driver->draw2DRectangle(colors[EGDC_3D_DARK_SHADOW], rect, clip); rect.UpperLeftCorner.X = r.UpperLeftCorner.X; - rect.UpperLeftCorner.Y = r.UpperLeftCorner.Y+1; + rect.UpperLeftCorner.Y = r.UpperLeftCorner.Y + 1; rect.LowerRightCorner.X = rect.UpperLeftCorner.X + 1; rect.LowerRightCorner.Y = r.LowerRightCorner.Y; - Driver->draw2DRectangle(colors[EGDC_3D_SHADOW], rect, clip); // left + Driver->draw2DRectangle(colors[EGDC_3D_SHADOW], rect, clip); // left ++rect.UpperLeftCorner.X; ++rect.UpperLeftCorner.Y; ++rect.LowerRightCorner.X; @@ -489,7 +457,7 @@ void GUISkin::drawColored3DSunkenPane(IGUIElement* element, video::SColor bgcolo rect = r; rect.UpperLeftCorner.X = rect.LowerRightCorner.X - 1; ++rect.UpperLeftCorner.Y; - Driver->draw2DRectangle(colors[EGDC_3D_HIGH_LIGHT], rect, clip); // right + Driver->draw2DRectangle(colors[EGDC_3D_HIGH_LIGHT], rect, clip); // right --rect.UpperLeftCorner.X; ++rect.UpperLeftCorner.Y; --rect.LowerRightCorner.X; @@ -500,7 +468,7 @@ void GUISkin::drawColored3DSunkenPane(IGUIElement* element, video::SColor bgcolo ++rect.UpperLeftCorner.X; rect.UpperLeftCorner.Y = r.LowerRightCorner.Y - 1; --rect.LowerRightCorner.X; - Driver->draw2DRectangle(colors[EGDC_3D_HIGH_LIGHT], rect, clip); // bottom + Driver->draw2DRectangle(colors[EGDC_3D_HIGH_LIGHT], rect, clip); // bottom ++rect.UpperLeftCorner.X; --rect.UpperLeftCorner.Y; --rect.LowerRightCorner.X; @@ -513,17 +481,13 @@ void GUISkin::drawColored3DSunkenPane(IGUIElement* element, video::SColor bgcolo //! draws a window background // return where to draw title bar text. // PATCH -core::rect GUISkin::drawColored3DWindowBackground(IGUIElement* element, - bool drawTitleBar, video::SColor titleBarColor, - const core::rect& r, - const core::rect* clip, - core::rect* checkClientArea, - const video::SColor* colors) +core::rect GUISkin::drawColored3DWindowBackground(IGUIElement *element, + bool drawTitleBar, video::SColor titleBarColor, const core::rect &r, + const core::rect *clip, core::rect *checkClientArea, + const video::SColor *colors) { - if (!Driver) - { - if ( checkClientArea ) - { + if (!Driver) { + if (checkClientArea) { *checkClientArea = r; } return r; @@ -536,16 +500,14 @@ core::rect GUISkin::drawColored3DWindowBackground(IGUIElement* element, // top border rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + 1; - if ( !checkClientArea ) - { + if (!checkClientArea) { Driver->draw2DRectangle(colors[EGDC_3D_HIGH_LIGHT], rect, clip); } // left border rect.LowerRightCorner.Y = r.LowerRightCorner.Y; rect.LowerRightCorner.X = rect.UpperLeftCorner.X + 1; - if ( !checkClientArea ) - { + if (!checkClientArea) { Driver->draw2DRectangle(colors[EGDC_3D_HIGH_LIGHT], rect, clip); } @@ -554,8 +516,7 @@ core::rect GUISkin::drawColored3DWindowBackground(IGUIElement* element, rect.LowerRightCorner.X = r.LowerRightCorner.X; rect.UpperLeftCorner.Y = r.UpperLeftCorner.Y; rect.LowerRightCorner.Y = r.LowerRightCorner.Y; - if ( !checkClientArea ) - { + if (!checkClientArea) { Driver->draw2DRectangle(colors[EGDC_3D_DARK_SHADOW], rect, clip); } @@ -564,8 +525,7 @@ core::rect GUISkin::drawColored3DWindowBackground(IGUIElement* element, rect.LowerRightCorner.X -= 1; rect.UpperLeftCorner.Y += 1; rect.LowerRightCorner.Y -= 1; - if ( !checkClientArea ) - { + if (!checkClientArea) { Driver->draw2DRectangle(colors[EGDC_3D_SHADOW], rect, clip); } @@ -574,8 +534,7 @@ core::rect GUISkin::drawColored3DWindowBackground(IGUIElement* element, rect.UpperLeftCorner.Y = r.LowerRightCorner.Y - 1; rect.LowerRightCorner.Y = r.LowerRightCorner.Y; rect.LowerRightCorner.X = r.LowerRightCorner.X; - if ( !checkClientArea ) - { + if (!checkClientArea) { Driver->draw2DRectangle(colors[EGDC_3D_DARK_SHADOW], rect, clip); } @@ -584,37 +543,31 @@ core::rect GUISkin::drawColored3DWindowBackground(IGUIElement* element, rect.LowerRightCorner.X -= 1; rect.UpperLeftCorner.Y -= 1; rect.LowerRightCorner.Y -= 1; - if ( !checkClientArea ) - { + if (!checkClientArea) { Driver->draw2DRectangle(colors[EGDC_3D_SHADOW], rect, clip); } // client area for background rect = r; - rect.UpperLeftCorner.X +=1; - rect.UpperLeftCorner.Y +=1; + rect.UpperLeftCorner.X += 1; + rect.UpperLeftCorner.Y += 1; rect.LowerRightCorner.X -= 2; rect.LowerRightCorner.Y -= 2; - if (checkClientArea) - { + if (checkClientArea) { *checkClientArea = rect; } - if ( !checkClientArea ) - { - if (!UseGradient) - { + if (!checkClientArea) { + if (!UseGradient) { Driver->draw2DRectangle(colors[EGDC_3D_FACE], rect, clip); - } - else if ( Type == EGST_BURNING_SKIN ) - { - const video::SColor c1 = colors[EGDC_WINDOW].getInterpolated ( 0xFFFFFFFF, 0.9f ); - const video::SColor c2 = colors[EGDC_WINDOW].getInterpolated ( 0xFFFFFFFF, 0.8f ); + } else if (Type == EGST_BURNING_SKIN) { + const video::SColor c1 = colors[EGDC_WINDOW].getInterpolated( + 0xFFFFFFFF, 0.9f); + const video::SColor c2 = colors[EGDC_WINDOW].getInterpolated( + 0xFFFFFFFF, 0.8f); Driver->draw2DRectangle(rect, c1, c1, c2, c2, clip); - } - else - { + } else { const video::SColor c2 = colors[EGDC_3D_SHADOW]; const video::SColor c1 = colors[EGDC_3D_FACE]; Driver->draw2DRectangle(rect, c1, c1, c1, c2, clip); @@ -626,29 +579,31 @@ core::rect GUISkin::drawColored3DWindowBackground(IGUIElement* element, rect.UpperLeftCorner.X += 2; rect.UpperLeftCorner.Y += 2; rect.LowerRightCorner.X -= 2; - rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + getSize(EGDS_WINDOW_BUTTON_WIDTH) + 2; + rect.LowerRightCorner.Y = + rect.UpperLeftCorner.Y + getSize(EGDS_WINDOW_BUTTON_WIDTH) + 2; - if (drawTitleBar ) - { - if (checkClientArea) - { + if (drawTitleBar) { + if (checkClientArea) { (*checkClientArea).UpperLeftCorner.Y = rect.LowerRightCorner.Y; - } - else - { + } else { // draw title bar - //if (!UseGradient) + // if (!UseGradient) // Driver->draw2DRectangle(titleBarColor, rect, clip); - //else - if ( Type == EGST_BURNING_SKIN ) - { - const video::SColor c = titleBarColor.getInterpolated( video::SColor(titleBarColor.getAlpha(),255,255,255), 0.8f); - Driver->draw2DRectangle(rect, titleBarColor, titleBarColor, c, c, clip); - } - else - { - const video::SColor c = titleBarColor.getInterpolated(video::SColor(titleBarColor.getAlpha(),0,0,0), 0.2f); - Driver->draw2DRectangle(rect, titleBarColor, c, titleBarColor, c, clip); + // else + if (Type == EGST_BURNING_SKIN) { + const video::SColor c = titleBarColor.getInterpolated( + video::SColor(titleBarColor.getAlpha(), + 255, 255, 255), + 0.8f); + Driver->draw2DRectangle(rect, titleBarColor, + titleBarColor, c, c, clip); + } else { + const video::SColor c = titleBarColor.getInterpolated( + video::SColor(titleBarColor.getAlpha(), 0, + 0, 0), + 0.2f); + Driver->draw2DRectangle(rect, titleBarColor, c, + titleBarColor, c, clip); } } } @@ -657,7 +612,6 @@ core::rect GUISkin::drawColored3DWindowBackground(IGUIElement* element, } // END PATCH - //! draws a standard 3d menu pane /** Used for drawing for menus and context menus. It uses the colors EGDC_3D_DARK_SHADOW, EGDC_3D_HIGH_LIGHT, EGDC_3D_SHADOW and @@ -668,9 +622,8 @@ implementations to find out how to draw the part exactly. \param rect: Defining area where to draw. \param clip: Clip area. */ // PATCH -void GUISkin::drawColored3DMenuPane(IGUIElement* element, - const core::rect& r, const core::rect* clip, - const video::SColor* colors) +void GUISkin::drawColored3DMenuPane(IGUIElement *element, const core::rect &r, + const core::rect *clip, const video::SColor *colors) { if (!Driver) return; @@ -680,8 +633,7 @@ void GUISkin::drawColored3DMenuPane(IGUIElement* element, core::rect rect = r; - if ( Type == EGST_BURNING_SKIN ) - { + if (Type == EGST_BURNING_SKIN) { rect.UpperLeftCorner.Y -= 3; draw3DButtonPaneStandard(element, rect, clip); return; @@ -727,15 +679,14 @@ void GUISkin::drawColored3DMenuPane(IGUIElement* element, Driver->draw2DRectangle(colors[EGDC_3D_SHADOW], rect, clip); rect = r; - rect.UpperLeftCorner.X +=1; - rect.UpperLeftCorner.Y +=1; + rect.UpperLeftCorner.X += 1; + rect.UpperLeftCorner.Y += 1; rect.LowerRightCorner.X -= 2; rect.LowerRightCorner.Y -= 2; if (!UseGradient) Driver->draw2DRectangle(colors[EGDC_3D_FACE], rect, clip); - else - { + else { const video::SColor c1 = colors[EGDC_3D_FACE]; const video::SColor c2 = colors[EGDC_3D_SHADOW]; Driver->draw2DRectangle(rect, c1, c1, c2, c2, clip); @@ -743,7 +694,6 @@ void GUISkin::drawColored3DMenuPane(IGUIElement* element, } // END PATCH - //! draws a standard 3d tool bar /** Used for drawing for toolbars and menus. \param element: Pointer to the element which wishes to draw this. This parameter @@ -752,10 +702,8 @@ implementations to find out how to draw the part exactly. \param rect: Defining area where to draw. \param clip: Clip area. */ // PATCH -void GUISkin::drawColored3DToolBar(IGUIElement* element, - const core::rect& r, - const core::rect* clip, - const video::SColor* colors) +void GUISkin::drawColored3DToolBar(IGUIElement *element, const core::rect &r, + const core::rect *clip, const video::SColor *colors) { if (!Driver) return; @@ -774,21 +722,15 @@ void GUISkin::drawColored3DToolBar(IGUIElement* element, rect = r; rect.LowerRightCorner.Y -= 1; - if (!UseGradient) - { + if (!UseGradient) { Driver->draw2DRectangle(colors[EGDC_3D_FACE], rect, clip); - } - else - if ( Type == EGST_BURNING_SKIN ) - { + } else if (Type == EGST_BURNING_SKIN) { const video::SColor c1 = 0xF0000000 | colors[EGDC_3D_FACE].color; const video::SColor c2 = 0xF0000000 | colors[EGDC_3D_SHADOW].color; rect.LowerRightCorner.Y += 1; Driver->draw2DRectangle(rect, c1, c2, c1, c2, clip); - } - else - { + } else { const video::SColor c1 = colors[EGDC_3D_FACE]; const video::SColor c2 = colors[EGDC_3D_SHADOW]; Driver->draw2DRectangle(rect, c1, c1, c2, c2, clip); @@ -805,9 +747,9 @@ implementations to find out how to draw the part exactly. \param rect: Defining area where to draw. \param clip: Clip area. */ // PATCH -void GUISkin::drawColored3DTabButton(IGUIElement* element, bool active, - const core::rect& frameRect, const core::rect* clip, EGUI_ALIGNMENT alignment, - const video::SColor* colors) +void GUISkin::drawColored3DTabButton(IGUIElement *element, bool active, + const core::rect &frameRect, const core::rect *clip, + EGUI_ALIGNMENT alignment, const video::SColor *colors) { if (!Driver) return; @@ -817,8 +759,7 @@ void GUISkin::drawColored3DTabButton(IGUIElement* element, bool active, core::rect tr = frameRect; - if ( alignment == EGUIA_UPPERLEFT ) - { + if (alignment == EGUIA_UPPERLEFT) { tr.LowerRightCorner.X -= 2; tr.LowerRightCorner.Y = tr.UpperLeftCorner.Y + 1; tr.UpperLeftCorner.X += 1; @@ -846,9 +787,7 @@ void GUISkin::drawColored3DTabButton(IGUIElement* element, bool active, tr.UpperLeftCorner.X += 1; tr.UpperLeftCorner.Y += 1; Driver->draw2DRectangle(colors[EGDC_3D_DARK_SHADOW], tr, clip); - } - else - { + } else { tr.LowerRightCorner.X -= 2; tr.UpperLeftCorner.Y = tr.LowerRightCorner.Y - 1; tr.UpperLeftCorner.X += 1; @@ -871,7 +810,7 @@ void GUISkin::drawColored3DTabButton(IGUIElement* element, bool active, // draw right middle gray shadow tr.LowerRightCorner.X += 1; tr.UpperLeftCorner.X = tr.LowerRightCorner.X - 1; - //tr.LowerRightCorner.Y -= 1; + // tr.LowerRightCorner.Y -= 1; Driver->draw2DRectangle(colors[EGDC_3D_SHADOW], tr, clip); tr.LowerRightCorner.X += 1; @@ -882,7 +821,6 @@ void GUISkin::drawColored3DTabButton(IGUIElement* element, bool active, } // END PATCH - //! draws a tab control body /** \param element: Pointer to the element which wishes to draw this. This parameter is usually not used by ISkin, but can be used for example by more complex @@ -892,9 +830,9 @@ implementations to find out how to draw the part exactly. \param rect: Defining area where to draw. \param clip: Clip area. */ // PATCH -void GUISkin::drawColored3DTabBody(IGUIElement* element, bool border, bool background, - const core::rect& rect, const core::rect* clip, s32 tabHeight, EGUI_ALIGNMENT alignment, - const video::SColor* colors) +void GUISkin::drawColored3DTabBody(IGUIElement *element, bool border, bool background, + const core::rect &rect, const core::rect *clip, s32 tabHeight, + EGUI_ALIGNMENT alignment, const video::SColor *colors) { if (!Driver) return; @@ -904,14 +842,12 @@ void GUISkin::drawColored3DTabBody(IGUIElement* element, bool border, bool backg core::rect tr = rect; - if ( tabHeight == -1 ) + if (tabHeight == -1) tabHeight = getSize(gui::EGDS_BUTTON_HEIGHT); // draw border. - if (border) - { - if ( alignment == EGUIA_UPPERLEFT ) - { + if (border) { + if (alignment == EGUIA_UPPERLEFT) { // draw left hightlight tr.UpperLeftCorner.Y += tabHeight + 2; tr.LowerRightCorner.X = tr.UpperLeftCorner.X + 1; @@ -926,9 +862,7 @@ void GUISkin::drawColored3DTabBody(IGUIElement* element, bool border, bool backg tr = rect; tr.UpperLeftCorner.Y = tr.LowerRightCorner.Y - 1; Driver->draw2DRectangle(colors[EGDC_3D_SHADOW], tr, clip); - } - else - { + } else { // draw left hightlight tr.LowerRightCorner.Y -= tabHeight + 2; tr.LowerRightCorner.X = tr.UpperLeftCorner.X + 1; @@ -946,30 +880,25 @@ void GUISkin::drawColored3DTabBody(IGUIElement* element, bool border, bool backg } } - if (background) - { - if ( alignment == EGUIA_UPPERLEFT ) - { + if (background) { + if (alignment == EGUIA_UPPERLEFT) { tr = rect; tr.UpperLeftCorner.Y += tabHeight + 2; tr.LowerRightCorner.X -= 1; tr.UpperLeftCorner.X += 1; tr.LowerRightCorner.Y -= 1; - } - else - { + } else { tr = rect; tr.UpperLeftCorner.X += 1; tr.UpperLeftCorner.Y -= 1; tr.LowerRightCorner.X -= 1; tr.LowerRightCorner.Y -= tabHeight + 2; - //tr.UpperLeftCorner.X += 1; + // tr.UpperLeftCorner.X += 1; } if (!UseGradient) Driver->draw2DRectangle(colors[EGDC_3D_FACE], tr, clip); - else - { + else { video::SColor c1 = colors[EGDC_3D_FACE]; video::SColor c2 = colors[EGDC_3D_SHADOW]; Driver->draw2DRectangle(tr, c1, c1, c2, c2, clip); @@ -978,7 +907,6 @@ void GUISkin::drawColored3DTabBody(IGUIElement* element, bool border, bool backg } // END PATCH - //! draws an icon, usually from the skin's sprite bank /** \param parent: Pointer to the element which wishes to draw this icon. This parameter is usually not used by IGUISkin, but can be used for example @@ -990,11 +918,9 @@ by more complex implementations to find out how to draw the part exactly. \param loop: Whether the animation should loop or not \param clip: Clip area. */ // PATCH -void GUISkin::drawColoredIcon(IGUIElement* element, EGUI_DEFAULT_ICON icon, - const core::position2di position, - u32 starttime, u32 currenttime, - bool loop, const core::rect* clip, - const video::SColor* colors) +void GUISkin::drawColoredIcon(IGUIElement *element, EGUI_DEFAULT_ICON icon, + const core::position2di position, u32 starttime, u32 currenttime, + bool loop, const core::rect *clip, const video::SColor *colors) { if (!SpriteBank) return; @@ -1004,74 +930,71 @@ void GUISkin::drawColoredIcon(IGUIElement* element, EGUI_DEFAULT_ICON icon, bool gray = element && !element->isEnabled(); SpriteBank->draw2DSprite(Icons[icon], position, clip, - colors[gray? EGDC_GRAY_WINDOW_SYMBOL : EGDC_WINDOW_SYMBOL], starttime, currenttime, loop, true); + colors[gray ? EGDC_GRAY_WINDOW_SYMBOL : EGDC_WINDOW_SYMBOL], + starttime, currenttime, loop, true); } // END PATCH - EGUI_SKIN_TYPE GUISkin::getType() const { return Type; } - //! draws a 2d rectangle. -void GUISkin::draw2DRectangle(IGUIElement* element, - const video::SColor &color, const core::rect& pos, - const core::rect* clip) +void GUISkin::draw2DRectangle(IGUIElement *element, const video::SColor &color, + const core::rect &pos, const core::rect *clip) { Driver->draw2DRectangle(color, pos, clip); } - //! Writes attributes of the object. //! Implement this to expose the attributes of your scene node animator for //! scripting languages, editors, debuggers or xml serialization purposes. -void GUISkin::serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options) const +void GUISkin::serializeAttributes( + io::IAttributes *out, io::SAttributeReadWriteOptions *options) const { u32 i; - for (i=0; iaddColor(GUISkinColorNames[i], Colors[i]); - for (i=0; iaddInt(GUISkinSizeNames[i], Sizes[i]); - for (i=0; iaddString(GUISkinTextNames[i], Texts[i].c_str()); - for (i=0; iaddInt(GUISkinIconNames[i], Icons[i]); } - //! Reads attributes of the object. //! Implement this to set the attributes of your scene node animator for //! scripting languages, editors, debuggers or xml deserialization purposes. -void GUISkin::deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options) +void GUISkin::deserializeAttributes( + io::IAttributes *in, io::SAttributeReadWriteOptions *options) { - // TODO: This is not nice code for downward compatibility, whenever new values are added and users - // load an old skin the corresponding values will be set to 0. + // TODO: This is not nice code for downward compatibility, whenever new values are + // added and users load an old skin the corresponding values will be set to 0. u32 i; - for (i=0; igetAttributeAsColor(GUISkinColorNames[i]); - for (i=0; igetAttributeAsInt(GUISkinSizeNames[i]); - for (i=0; igetAttributeAsStringW(GUISkinTextNames[i]); - for (i=0; igetAttributeAsInt(GUISkinIconNames[i]); } - //! gets the colors // PATCH -void GUISkin::getColors(video::SColor* colors) +void GUISkin::getColors(video::SColor *colors) { u32 i; - for (i=0; i &rect, const core::rect *clip = 0) { - public: + drawColored3DButtonPaneStandard(element, rect, clip); + } - GUISkin(EGUI_SKIN_TYPE type, video::IVideoDriver* driver); + virtual void drawColored3DButtonPaneStandard(IGUIElement *element, + const core::rect &rect, const core::rect *clip = 0, + const video::SColor *colors = 0); - //! destructor - virtual ~GUISkin(); + //! draws a pressed 3d button pane + /** Used for drawing for example buttons in pressed state. + It uses the colors EGDC_3D_DARK_SHADOW, EGDC_3D_HIGH_LIGHT, EGDC_3D_SHADOW and + EGDC_3D_FACE for this. See EGUI_DEFAULT_COLOR for details. + \param rect: Defining area where to draw. + \param clip: Clip area. + \param element: Pointer to the element which wishes to draw this. This parameter + is usually not used by ISkin, but can be used for example by more complex + implementations to find out how to draw the part exactly. */ + virtual void draw3DButtonPanePressed(IGUIElement *element, + const core::rect &rect, const core::rect *clip = 0) + { + drawColored3DButtonPanePressed(element, rect, clip); + } - //! returns default color - virtual video::SColor getColor(EGUI_DEFAULT_COLOR color) const; + virtual void drawColored3DButtonPanePressed(IGUIElement *element, + const core::rect &rect, const core::rect *clip = 0, + const video::SColor *colors = 0); - //! sets a default color - virtual void setColor(EGUI_DEFAULT_COLOR which, video::SColor newColor); + //! draws a sunken 3d pane + /** Used for drawing the background of edit, combo or check boxes. + \param element: Pointer to the element which wishes to draw this. This parameter + is usually not used by ISkin, but can be used for example by more complex + implementations to find out how to draw the part exactly. + \param bgcolor: Background color. + \param flat: Specifies if the sunken pane should be flat or displayed as sunken + deep into the ground. + \param rect: Defining area where to draw. + \param clip: Clip area. */ + virtual void draw3DSunkenPane(IGUIElement *element, video::SColor bgcolor, + bool flat, bool fillBackGround, const core::rect &rect, + const core::rect *clip = 0) + { + drawColored3DSunkenPane( + element, bgcolor, flat, fillBackGround, rect, clip); + } - //! returns size for the given size type - virtual s32 getSize(EGUI_DEFAULT_SIZE size) const; + virtual void drawColored3DSunkenPane(IGUIElement *element, video::SColor bgcolor, + bool flat, bool fillBackGround, const core::rect &rect, + const core::rect *clip = 0, const video::SColor *colors = 0); - //! sets a default size - virtual void setSize(EGUI_DEFAULT_SIZE which, s32 size); - - //! returns the default font - virtual IGUIFont* getFont(EGUI_DEFAULT_FONT which=EGDF_DEFAULT) const; - - //! sets a default font - virtual void setFont(IGUIFont* font, EGUI_DEFAULT_FONT which=EGDF_DEFAULT); - - //! sets the sprite bank used for drawing icons - virtual void setSpriteBank(IGUISpriteBank* bank); - - //! gets the sprite bank used for drawing icons - virtual IGUISpriteBank* getSpriteBank() const; - - //! Returns a default icon - /** Returns the sprite index within the sprite bank */ - virtual u32 getIcon(EGUI_DEFAULT_ICON icon) const; - - //! Sets a default icon - /** Sets the sprite index used for drawing icons like arrows, - close buttons and ticks in checkboxes - \param icon: Enum specifying which icon to change - \param index: The sprite index used to draw this icon */ - virtual void setIcon(EGUI_DEFAULT_ICON icon, u32 index); - - //! Returns a default text. - /** For example for Message box button captions: - "OK", "Cancel", "Yes", "No" and so on. */ - virtual const wchar_t* getDefaultText(EGUI_DEFAULT_TEXT text) const; - - //! Sets a default text. - /** For example for Message box button captions: - "OK", "Cancel", "Yes", "No" and so on. */ - virtual void setDefaultText(EGUI_DEFAULT_TEXT which, const wchar_t* newText); - - //! draws a standard 3d button pane - /** Used for drawing for example buttons in normal state. - It uses the colors EGDC_3D_DARK_SHADOW, EGDC_3D_HIGH_LIGHT, EGDC_3D_SHADOW and - EGDC_3D_FACE for this. See EGUI_DEFAULT_COLOR for details. - \param rect: Defining area where to draw. - \param clip: Clip area. - \param element: Pointer to the element which wishes to draw this. This parameter - is usually not used by ISkin, but can be used for example by more complex - implementations to find out how to draw the part exactly. */ - virtual void draw3DButtonPaneStandard(IGUIElement* element, - const core::rect& rect, - const core::rect* clip=0) - { - drawColored3DButtonPaneStandard(element, rect,clip); - } - - virtual void drawColored3DButtonPaneStandard(IGUIElement* element, - const core::rect& rect, - const core::rect* clip=0, - const video::SColor* colors=0); - - //! draws a pressed 3d button pane - /** Used for drawing for example buttons in pressed state. - It uses the colors EGDC_3D_DARK_SHADOW, EGDC_3D_HIGH_LIGHT, EGDC_3D_SHADOW and - EGDC_3D_FACE for this. See EGUI_DEFAULT_COLOR for details. - \param rect: Defining area where to draw. - \param clip: Clip area. - \param element: Pointer to the element which wishes to draw this. This parameter - is usually not used by ISkin, but can be used for example by more complex - implementations to find out how to draw the part exactly. */ - virtual void draw3DButtonPanePressed(IGUIElement* element, - const core::rect& rect, - const core::rect* clip=0) - { - drawColored3DButtonPanePressed(element, rect, clip); - } - - virtual void drawColored3DButtonPanePressed(IGUIElement* element, - const core::rect& rect, - const core::rect* clip=0, - const video::SColor* colors=0); - - //! draws a sunken 3d pane - /** Used for drawing the background of edit, combo or check boxes. - \param element: Pointer to the element which wishes to draw this. This parameter - is usually not used by ISkin, but can be used for example by more complex - implementations to find out how to draw the part exactly. - \param bgcolor: Background color. - \param flat: Specifies if the sunken pane should be flat or displayed as sunken - deep into the ground. - \param rect: Defining area where to draw. - \param clip: Clip area. */ - virtual void draw3DSunkenPane(IGUIElement* element, - video::SColor bgcolor, bool flat, - bool fillBackGround, - const core::rect& rect, - const core::rect* clip=0) - { - drawColored3DSunkenPane(element, bgcolor, flat, fillBackGround, rect, clip); - } - - virtual void drawColored3DSunkenPane(IGUIElement* element, - video::SColor bgcolor, bool flat, - bool fillBackGround, - const core::rect& rect, - const core::rect* clip=0, - const video::SColor* colors=0); - - //! draws a window background - /** Used for drawing the background of dialogs and windows. - \param element: Pointer to the element which wishes to draw this. This parameter - is usually not used by ISkin, but can be used for example by more complex - implementations to find out how to draw the part exactly. - \param titleBarColor: Title color. - \param drawTitleBar: True to enable title drawing. - \param rect: Defining area where to draw. - \param clip: Clip area. - \param checkClientArea: When set to non-null the function will not draw anything, - but will instead return the clientArea which can be used for drawing by the calling window. - That is the area without borders and without titlebar. - \return Returns rect where it would be good to draw title bar text. This will - work even when checkClientArea is set to a non-null value.*/ - virtual core::rect draw3DWindowBackground(IGUIElement* element, - bool drawTitleBar, video::SColor titleBarColor, - const core::rect& rect, - const core::rect* clip, - core::rect* checkClientArea) - { - return drawColored3DWindowBackground(element, drawTitleBar, titleBarColor, + //! draws a window background + /** Used for drawing the background of dialogs and windows. + \param element: Pointer to the element which wishes to draw this. This parameter + is usually not used by ISkin, but can be used for example by more complex + implementations to find out how to draw the part exactly. + \param titleBarColor: Title color. + \param drawTitleBar: True to enable title drawing. + \param rect: Defining area where to draw. + \param clip: Clip area. + \param checkClientArea: When set to non-null the function will not draw anything, + but will instead return the clientArea which can be used for drawing by the + calling window. That is the area without borders and without titlebar. \return + Returns rect where it would be good to draw title bar text. This will work even + when checkClientArea is set to a non-null value.*/ + virtual core::rect draw3DWindowBackground(IGUIElement *element, + bool drawTitleBar, video::SColor titleBarColor, + const core::rect &rect, const core::rect *clip, + core::rect *checkClientArea) + { + return drawColored3DWindowBackground(element, drawTitleBar, titleBarColor, rect, clip, checkClientArea); - } + } - virtual core::rect drawColored3DWindowBackground(IGUIElement* element, - bool drawTitleBar, video::SColor titleBarColor, - const core::rect& rect, - const core::rect* clip, - core::rect* checkClientArea, - const video::SColor* colors=0); + virtual core::rect drawColored3DWindowBackground(IGUIElement *element, + bool drawTitleBar, video::SColor titleBarColor, + const core::rect &rect, const core::rect *clip, + core::rect *checkClientArea, + const video::SColor *colors = 0); - //! draws a standard 3d menu pane - /** Used for drawing for menus and context menus. - It uses the colors EGDC_3D_DARK_SHADOW, EGDC_3D_HIGH_LIGHT, EGDC_3D_SHADOW and - EGDC_3D_FACE for this. See EGUI_DEFAULT_COLOR for details. - \param element: Pointer to the element which wishes to draw this. This parameter - is usually not used by ISkin, but can be used for example by more complex - implementations to find out how to draw the part exactly. - \param rect: Defining area where to draw. - \param clip: Clip area. */ - virtual void draw3DMenuPane(IGUIElement* element, - const core::rect& rect, - const core::rect* clip=0) - { - drawColored3DMenuPane(element, rect, clip); - } + //! draws a standard 3d menu pane + /** Used for drawing for menus and context menus. + It uses the colors EGDC_3D_DARK_SHADOW, EGDC_3D_HIGH_LIGHT, EGDC_3D_SHADOW and + EGDC_3D_FACE for this. See EGUI_DEFAULT_COLOR for details. + \param element: Pointer to the element which wishes to draw this. This parameter + is usually not used by ISkin, but can be used for example by more complex + implementations to find out how to draw the part exactly. + \param rect: Defining area where to draw. + \param clip: Clip area. */ + virtual void draw3DMenuPane(IGUIElement *element, const core::rect &rect, + const core::rect *clip = 0) + { + drawColored3DMenuPane(element, rect, clip); + } - virtual void drawColored3DMenuPane(IGUIElement* element, - const core::rect& rect, - const core::rect* clip=0, - const video::SColor* colors=0); + virtual void drawColored3DMenuPane(IGUIElement *element, + const core::rect &rect, const core::rect *clip = 0, + const video::SColor *colors = 0); - //! draws a standard 3d tool bar - /** Used for drawing for toolbars and menus. - \param element: Pointer to the element which wishes to draw this. This parameter - is usually not used by ISkin, but can be used for example by more complex - implementations to find out how to draw the part exactly. - \param rect: Defining area where to draw. - \param clip: Clip area. */ - virtual void draw3DToolBar(IGUIElement* element, - const core::rect& rect, - const core::rect* clip=0) - { - drawColored3DToolBar(element, rect, clip); - } + //! draws a standard 3d tool bar + /** Used for drawing for toolbars and menus. + \param element: Pointer to the element which wishes to draw this. This parameter + is usually not used by ISkin, but can be used for example by more complex + implementations to find out how to draw the part exactly. + \param rect: Defining area where to draw. + \param clip: Clip area. */ + virtual void draw3DToolBar(IGUIElement *element, const core::rect &rect, + const core::rect *clip = 0) + { + drawColored3DToolBar(element, rect, clip); + } - virtual void drawColored3DToolBar(IGUIElement* element, - const core::rect& rect, - const core::rect* clip=0, - const video::SColor* colors=0); + virtual void drawColored3DToolBar(IGUIElement *element, + const core::rect &rect, const core::rect *clip = 0, + const video::SColor *colors = 0); - //! draws a tab button - /** Used for drawing for tab buttons on top of tabs. - \param element: Pointer to the element which wishes to draw this. This parameter - is usually not used by ISkin, but can be used for example by more complex - implementations to find out how to draw the part exactly. - \param active: Specifies if the tab is currently active. - \param rect: Defining area where to draw. - \param clip: Clip area. */ - virtual void draw3DTabButton(IGUIElement* element, bool active, - const core::rect& rect, const core::rect* clip=0, EGUI_ALIGNMENT alignment=EGUIA_UPPERLEFT) - { - drawColored3DTabButton(element, active, rect, clip, alignment); - } + //! draws a tab button + /** Used for drawing for tab buttons on top of tabs. + \param element: Pointer to the element which wishes to draw this. This parameter + is usually not used by ISkin, but can be used for example by more complex + implementations to find out how to draw the part exactly. + \param active: Specifies if the tab is currently active. + \param rect: Defining area where to draw. + \param clip: Clip area. */ + virtual void draw3DTabButton(IGUIElement *element, bool active, + const core::rect &rect, const core::rect *clip = 0, + EGUI_ALIGNMENT alignment = EGUIA_UPPERLEFT) + { + drawColored3DTabButton(element, active, rect, clip, alignment); + } - virtual void drawColored3DTabButton(IGUIElement* element, bool active, - const core::rect& rect, const core::rect* clip=0, EGUI_ALIGNMENT alignment=EGUIA_UPPERLEFT, - const video::SColor* colors=0); + virtual void drawColored3DTabButton(IGUIElement *element, bool active, + const core::rect &rect, const core::rect *clip = 0, + EGUI_ALIGNMENT alignment = EGUIA_UPPERLEFT, + const video::SColor *colors = 0); - //! draws a tab control body - /** \param element: Pointer to the element which wishes to draw this. This parameter - is usually not used by ISkin, but can be used for example by more complex - implementations to find out how to draw the part exactly. - \param border: Specifies if the border should be drawn. - \param background: Specifies if the background should be drawn. - \param rect: Defining area where to draw. - \param clip: Clip area. */ - virtual void draw3DTabBody(IGUIElement* element, bool border, bool background, - const core::rect& rect, const core::rect* clip=0, s32 tabHeight=-1, EGUI_ALIGNMENT alignment=EGUIA_UPPERLEFT) - { - drawColored3DTabBody(element, border, background, rect, clip, tabHeight, alignment); - } + //! draws a tab control body + /** \param element: Pointer to the element which wishes to draw this. This + parameter is usually not used by ISkin, but can be used for example by more + complex implementations to find out how to draw the part exactly. \param border: + Specifies if the border should be drawn. \param background: Specifies if the + background should be drawn. \param rect: Defining area where to draw. + \param clip: Clip area. */ + virtual void draw3DTabBody(IGUIElement *element, bool border, bool background, + const core::rect &rect, const core::rect *clip = 0, + s32 tabHeight = -1, EGUI_ALIGNMENT alignment = EGUIA_UPPERLEFT) + { + drawColored3DTabBody(element, border, background, rect, clip, tabHeight, + alignment); + } - virtual void drawColored3DTabBody(IGUIElement* element, bool border, bool background, - const core::rect& rect, const core::rect* clip=0, s32 tabHeight=-1, EGUI_ALIGNMENT alignment=EGUIA_UPPERLEFT, - const video::SColor* colors=0); + virtual void drawColored3DTabBody(IGUIElement *element, bool border, + bool background, const core::rect &rect, + const core::rect *clip = 0, s32 tabHeight = -1, + EGUI_ALIGNMENT alignment = EGUIA_UPPERLEFT, + const video::SColor *colors = 0); - //! draws an icon, usually from the skin's sprite bank - /** \param element: Pointer to the element which wishes to draw this icon. - This parameter is usually not used by IGUISkin, but can be used for example - by more complex implementations to find out how to draw the part exactly. - \param icon: Specifies the icon to be drawn. - \param position: The position to draw the icon - \param starttime: The time at the start of the animation - \param currenttime: The present time, used to calculate the frame number - \param loop: Whether the animation should loop or not - \param clip: Clip area. */ - virtual void drawIcon(IGUIElement* element, EGUI_DEFAULT_ICON icon, - const core::position2di position, - u32 starttime=0, u32 currenttime=0, - bool loop=false, const core::rect* clip=0) - { - drawColoredIcon(element, icon, position, starttime, currenttime, loop, clip); - } + //! draws an icon, usually from the skin's sprite bank + /** \param element: Pointer to the element which wishes to draw this icon. + This parameter is usually not used by IGUISkin, but can be used for example + by more complex implementations to find out how to draw the part exactly. + \param icon: Specifies the icon to be drawn. + \param position: The position to draw the icon + \param starttime: The time at the start of the animation + \param currenttime: The present time, used to calculate the frame number + \param loop: Whether the animation should loop or not + \param clip: Clip area. */ + virtual void drawIcon(IGUIElement *element, EGUI_DEFAULT_ICON icon, + const core::position2di position, u32 starttime = 0, + u32 currenttime = 0, bool loop = false, + const core::rect *clip = 0) + { + drawColoredIcon(element, icon, position, starttime, currenttime, loop, + clip); + } - virtual void drawColoredIcon(IGUIElement* element, EGUI_DEFAULT_ICON icon, - const core::position2di position, - u32 starttime=0, u32 currenttime=0, - bool loop=false, const core::rect* clip=0, - const video::SColor* colors=0); + virtual void drawColoredIcon(IGUIElement *element, EGUI_DEFAULT_ICON icon, + const core::position2di position, u32 starttime = 0, + u32 currenttime = 0, bool loop = false, + const core::rect *clip = 0, const video::SColor *colors = 0); - //! draws a 2d rectangle. - /** \param element: Pointer to the element which wishes to draw this icon. - This parameter is usually not used by IGUISkin, but can be used for example - by more complex implementations to find out how to draw the part exactly. - \param color: Color of the rectangle to draw. The alpha component specifies how - transparent the rectangle will be. - \param pos: Position of the rectangle. - \param clip: Pointer to rectangle against which the rectangle will be clipped. - If the pointer is null, no clipping will be performed. */ - virtual void draw2DRectangle(IGUIElement* element, const video::SColor &color, - const core::rect& pos, const core::rect* clip = 0); + //! draws a 2d rectangle. + /** \param element: Pointer to the element which wishes to draw this icon. + This parameter is usually not used by IGUISkin, but can be used for example + by more complex implementations to find out how to draw the part exactly. + \param color: Color of the rectangle to draw. The alpha component specifies how + transparent the rectangle will be. + \param pos: Position of the rectangle. + \param clip: Pointer to rectangle against which the rectangle will be clipped. + If the pointer is null, no clipping will be performed. */ + virtual void draw2DRectangle(IGUIElement *element, const video::SColor &color, + const core::rect &pos, const core::rect *clip = 0); + //! get the type of this skin + virtual EGUI_SKIN_TYPE getType() const; - //! get the type of this skin - virtual EGUI_SKIN_TYPE getType() const; + //! Writes attributes of the object. + //! Implement this to expose the attributes of your scene node animator for + //! scripting languages, editors, debuggers or xml serialization purposes. + virtual void serializeAttributes(io::IAttributes *out, + io::SAttributeReadWriteOptions *options = 0) const; - //! Writes attributes of the object. - //! Implement this to expose the attributes of your scene node animator for - //! scripting languages, editors, debuggers or xml serialization purposes. - virtual void serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options=0) const; + //! Reads attributes of the object. + //! Implement this to set the attributes of your scene node animator for + //! scripting languages, editors, debuggers or xml deserialization purposes. + virtual void deserializeAttributes( + io::IAttributes *in, io::SAttributeReadWriteOptions *options = 0); - //! Reads attributes of the object. - //! Implement this to set the attributes of your scene node animator for - //! scripting languages, editors, debuggers or xml deserialization purposes. - virtual void deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options=0); + //! gets the colors + virtual void getColors(video::SColor *colors); // ::PATCH: - //! gets the colors - virtual void getColors(video::SColor* colors); // ::PATCH: +private: + video::SColor Colors[EGDC_COUNT]; + s32 Sizes[EGDS_COUNT]; + u32 Icons[EGDI_COUNT]; + IGUIFont *Fonts[EGDF_COUNT]; + IGUISpriteBank *SpriteBank; + core::stringw Texts[EGDT_COUNT]; + video::IVideoDriver *Driver; + bool UseGradient; - private: + EGUI_SKIN_TYPE Type; +}; - video::SColor Colors[EGDC_COUNT]; - s32 Sizes[EGDS_COUNT]; - u32 Icons[EGDI_COUNT]; - IGUIFont* Fonts[EGDF_COUNT]; - IGUISpriteBank* SpriteBank; - core::stringw Texts[EGDT_COUNT]; - video::IVideoDriver* Driver; - bool UseGradient; +#define set3DSkinColors(skin, button_color) \ + { \ + skin->setColor(EGDC_3D_FACE, button_color); \ + skin->setColor(EGDC_3D_DARK_SHADOW, button_color, 0.25f); \ + skin->setColor(EGDC_3D_SHADOW, button_color, 0.5f); \ + skin->setColor(EGDC_3D_LIGHT, button_color); \ + skin->setColor(EGDC_3D_HIGH_LIGHT, button_color, 1.5f); \ + } - EGUI_SKIN_TYPE Type; - }; +#define getElementSkinColor(color) \ + { \ + if (!Colors) { \ + IGUISkin *skin = Environment->getSkin(); \ + if (skin) \ + return skin->getColor(color); \ + } \ + return Colors[color]; \ + } - #define set3DSkinColors(skin, button_color) \ - { \ - skin->setColor(EGDC_3D_FACE, button_color); \ - skin->setColor(EGDC_3D_DARK_SHADOW, button_color, 0.25f); \ - skin->setColor(EGDC_3D_SHADOW, button_color, 0.5f); \ - skin->setColor(EGDC_3D_LIGHT, button_color); \ - skin->setColor(EGDC_3D_HIGH_LIGHT, button_color, 1.5f); \ - } - - #define getElementSkinColor(color) \ - { \ - if (!Colors) \ - { \ - IGUISkin* skin = Environment->getSkin(); \ - if (skin) \ - return skin->getColor(color); \ - } \ - return Colors[color]; \ - } - - #define setElementSkinColor(which, newColor, shading) \ - { \ - if (!Colors) \ - { \ - Colors = new video::SColor[EGDC_COUNT]; \ - GUISkin* skin = (GUISkin *)Environment->getSkin(); \ - if (skin) \ - skin->getColors(Colors); \ - } \ - Colors[which] = newColor; \ - setShading(Colors[which],shading); \ - } +#define setElementSkinColor(which, newColor, shading) \ + { \ + if (!Colors) { \ + Colors = new video::SColor[EGDC_COUNT]; \ + GUISkin *skin = (GUISkin *)Environment->getSkin(); \ + if (skin) \ + skin->getColors(Colors); \ + } \ + Colors[which] = newColor; \ + setShading(Colors[which], shading); \ + } } // end namespace gui //! Sets the shading -inline void setShading(video::SColor &color,f32 s) // :PATCH: +inline void setShading(video::SColor &color, f32 s) // :PATCH: { - if (s < 1.0f) - { + if (s < 1.0f) { color.setRed(color.getRed() * s); color.setGreen(color.getGreen() * s); color.setBlue(color.getBlue() * s); - } - else if (s > 1.0f) - { + } else if (s > 1.0f) { s -= 1.0f; color.setRed(color.getRed() + (255 - color.getRed()) * s); @@ -370,7 +355,6 @@ inline void setShading(video::SColor &color,f32 s) // :PATCH: } } // end namespace irr - #endif // _IRR_COMPILE_WITH_GUI_ #endif diff --git a/src/gui/guiTable.cpp b/src/gui/guiTable.cpp index c705e17fb..2f761cc77 100644 --- a/src/gui/guiTable.cpp +++ b/src/gui/guiTable.cpp @@ -17,7 +17,6 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ - #include "guiTable.h" #include #include @@ -33,25 +32,22 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/string.h" #include "util/numeric.h" #include "util/string.h" // for parseColorString() -#include "settings.h" // for settings -#include "porting.h" // for dpi +#include "settings.h" // for settings +#include "porting.h" // for dpi #include "client/guiscalingfilter.h" /* GUITable */ -GUITable::GUITable(gui::IGUIEnvironment *env, - gui::IGUIElement* parent, s32 id, - core::rect rectangle, - ISimpleTextureSource *tsrc -): - gui::IGUIElement(gui::EGUIET_ELEMENT, env, parent, id, rectangle), - m_tsrc(tsrc) +GUITable::GUITable(gui::IGUIEnvironment *env, gui::IGUIElement *parent, s32 id, + core::rect rectangle, ISimpleTextureSource *tsrc) : + gui::IGUIElement(gui::EGUIET_ELEMENT, env, parent, id, rectangle), + m_tsrc(tsrc) { assert(tsrc != NULL); - gui::IGUISkin* skin = Environment->getSkin(); + gui::IGUISkin *skin = Environment->getSkin(); m_font = skin->getFont(); if (m_font) { @@ -62,8 +58,7 @@ GUITable::GUITable(gui::IGUIEnvironment *env, const s32 s = skin->getSize(gui::EGDS_SCROLLBAR_SIZE); m_scrollbar = new GUIScrollBar(Environment, this, -1, - core::rect(RelativeRect.getWidth() - s, - 0, + core::rect(RelativeRect.getWidth() - s, 0, RelativeRect.getWidth(), RelativeRect.getHeight()), false, true); @@ -83,11 +78,11 @@ GUITable::GUITable(gui::IGUIEnvironment *env, #endif core::rect relative_rect = m_scrollbar->getRelativePosition(); s32 width = (relative_rect.getWidth() / (2.0 / 3.0)) * density * - g_settings->getFloat("gui_scaling"); + g_settings->getFloat("gui_scaling"); m_scrollbar->setRelativePosition(core::rect( - relative_rect.LowerRightCorner.X-width,relative_rect.UpperLeftCorner.Y, - relative_rect.LowerRightCorner.X,relative_rect.LowerRightCorner.Y - )); + relative_rect.LowerRightCorner.X - width, + relative_rect.UpperLeftCorner.Y, relative_rect.LowerRightCorner.X, + relative_rect.LowerRightCorner.Y)); } GUITable::~GUITable() @@ -108,12 +103,10 @@ GUITable::Option GUITable::splitOption(const std::string &str) if (equal_pos == std::string::npos) return GUITable::Option(str, ""); - return GUITable::Option(str.substr(0, equal_pos), - str.substr(equal_pos + 1)); + return GUITable::Option(str.substr(0, equal_pos), str.substr(equal_pos + 1)); } -void GUITable::setTextList(const std::vector &content, - bool transparent) +void GUITable::setTextList(const std::vector &content, bool transparent) { clear(); @@ -127,7 +120,7 @@ void GUITable::setTextList(const std::vector &content, s32 empty_string_index = allocString(""); m_rows.resize(content.size()); - for (s32 i = 0; i < (s32) content.size(); ++i) { + for (s32 i = 0; i < (s32)content.size(); ++i) { Row *row = &m_rows[i]; row->cells = new Cell[1]; row->cellcount = 1; @@ -137,7 +130,7 @@ void GUITable::setTextList(const std::vector &content, Cell *cell = row->cells; cell->xmin = 0; - cell->xmax = 0x7fff; // something large enough + cell->xmax = 0x7fff; // something large enough cell->xpos = 6; cell->content_type = COLUMN_TYPE_TEXT; cell->content_index = empty_string_index; @@ -151,19 +144,15 @@ void GUITable::setTextList(const std::vector &content, if (s[0] == '#' && s[1] == '#') { // double # to escape cell->content_index = allocString(s.substr(2)); - } - else if (s[0] == '#' && s.size() >= 7 && - parseColorString( - s.substr(0,7), cell->color, false)) { + } else if (s[0] == '#' && s.size() >= 7 && + parseColorString(s.substr(0, 7), cell->color, false)) { // single # for color cell->color_defined = true; cell->content_index = allocString(s.substr(7)); - } - else { + } else { // no #, just text cell->content_index = allocString(s); } - } allocationComplete(); @@ -172,8 +161,7 @@ void GUITable::setTextList(const std::vector &content, updateScrollBar(); } -void GUITable::setTable(const TableOptions &options, - const TableColumns &columns, +void GUITable::setTable(const TableOptions &options, const TableColumns &columns, std::vector &content) { clear(); @@ -212,8 +200,8 @@ void GUITable::setTable(const TableOptions &options, else if (name == "opendepth") opendepth = stoi(value); else - errorstream<<"Invalid table option: \""<= 0); // Append empty strings to content if there is an incomplete row s32 cellcount = rowcount * colcount; - while (content.size() < (u32) cellcount) + while (content.size() < (u32)cellcount) content.emplace_back(""); // Create temporary rows (for processing columns) - struct TempRow { + struct TempRow + { // Current horizontal position (may different between rows due // to indent/tree columns, or text/image columns with width<0) s32 x; @@ -242,9 +231,9 @@ void GUITable::setTable(const TableOptions &options, // Vector of completed cells in this row std::vector cells; // Stores colors and how long they last (maximum column index) - std::vector > colors; + std::vector> colors; - TempRow(): x(0), indent(0), content_index(0), content_width(0) {} + TempRow() : x(0), indent(0), content_index(0), content_width(0) {} }; TempRow *rows = new TempRow[rowcount]; @@ -273,8 +262,8 @@ void GUITable::setTable(const TableOptions &options, else if (columns[j].type == "tree") columntype = COLUMN_TYPE_TREE; else - errorstream<<"Invalid table column type: \"" - <colors.empty() && row->colors.back().second < j) + while (!row->colors.empty() && + row->colors.back().second < j) row->colors.pop_back(); } } @@ -338,17 +326,20 @@ void GUITable::setTable(const TableOptions &options, Cell newcell; newcell.content_type = columntype; newcell.tooltip_index = tooltip_index; - newcell.reported_column = j+1; + newcell.reported_column = j + 1; if (columntype == COLUMN_TYPE_TEXT) { // Find right edge of column s32 xmax = 0; for (s32 i = 0; i < rowcount; ++i) { TempRow *row = &rows[i]; - row->content_index = allocString(content[i * colcount + j]); + row->content_index = + allocString(content[i * colcount + j]); const core::stringw &text = m_strings[row->content_index]; - row->content_width = m_font ? - m_font->getDimension(text.c_str()).Width : 0; + row->content_width = + m_font ? m_font->getDimension(text.c_str()) + .Width + : 0; row->content_width = MYMAX(row->content_width, width); s32 row_xmax = row->x + padding + row->content_width; xmax = MYMAX(xmax, row_xmax); @@ -356,7 +347,8 @@ void GUITable::setTable(const TableOptions &options, // Add a new cell (of text type) to each row for (s32 i = 0; i < rowcount; ++i) { newcell.xmin = rows[i].x + padding; - alignContent(&newcell, xmax, rows[i].content_width, align); + alignContent(&newcell, xmax, rows[i].content_width, + align); newcell.content_index = rows[i].content_index; newcell.color_defined = !rows[i].colors.empty(); if (newcell.color_defined) @@ -364,8 +356,7 @@ void GUITable::setTable(const TableOptions &options, rows[i].cells.push_back(newcell); rows[i].x = newcell.xmax; } - } - else if (columntype == COLUMN_TYPE_IMAGE) { + } else if (columntype == COLUMN_TYPE_IMAGE) { // Find right edge of column s32 xmax = 0; for (s32 i = 0; i < rowcount; ++i) { @@ -376,7 +367,7 @@ void GUITable::setTable(const TableOptions &options, // column options so check active_image_indices. s32 image_index = stoi(content[i * colcount + j]); std::map::iterator image_iter = - active_image_indices.find(image_index); + active_image_indices.find(image_index); if (image_iter != active_image_indices.end()) row->content_index = image_iter->second; @@ -386,7 +377,9 @@ void GUITable::setTable(const TableOptions &options, image = m_images[row->content_index]; // Get content width and update xmax - row->content_width = image ? image->getOriginalSize().Width : 0; + row->content_width = + image ? image->getOriginalSize().Width + : 0; row->content_width = MYMAX(row->content_width, width); s32 row_xmax = row->x + padding + row->content_width; xmax = MYMAX(xmax, row_xmax); @@ -394,27 +387,28 @@ void GUITable::setTable(const TableOptions &options, // Add a new cell (of image type) to each row for (s32 i = 0; i < rowcount; ++i) { newcell.xmin = rows[i].x + padding; - alignContent(&newcell, xmax, rows[i].content_width, align); + alignContent(&newcell, xmax, rows[i].content_width, + align); newcell.content_index = rows[i].content_index; rows[i].cells.push_back(newcell); rows[i].x = newcell.xmax; } active_image_indices.clear(); - } - else if (columntype == COLUMN_TYPE_COLOR) { + } else if (columntype == COLUMN_TYPE_COLOR) { for (s32 i = 0; i < rowcount; ++i) { video::SColor cellcolor(255, 255, 255, 255); - if (parseColorString(content[i * colcount + j], cellcolor, true)) - rows[i].colors.emplace_back(cellcolor, j+span); + if (parseColorString(content[i * colcount + j], cellcolor, + true)) + rows[i].colors.emplace_back(cellcolor, j + span); } - } - else if (columntype == COLUMN_TYPE_INDENT || + } else if (columntype == COLUMN_TYPE_INDENT || columntype == COLUMN_TYPE_TREE) { // For column type "tree", reserve additional space for +/- // Also enable special processing for treeview-type tables s32 content_width = 0; if (columntype == COLUMN_TYPE_TREE) { - content_width = m_font ? m_font->getDimension(L"+").Width : 0; + content_width = m_font ? m_font->getDimension(L"+").Width + : 0; m_has_tree_column = true; } // Add a new cell (of indent or tree type) to each row @@ -446,7 +440,7 @@ void GUITable::setTable(const TableOptions &options, Row *row = &m_rows[i]; row->cellcount = rows[i].cells.size(); row->cells = new Cell[row->cellcount]; - memcpy((void*) row->cells, (void*) &rows[i].cells[0], + memcpy((void *)row->cells, (void *)&rows[i].cells[0], row->cellcount * sizeof(Cell)); row->indent = rows[i].indent; row->visible_index = i; @@ -457,10 +451,12 @@ void GUITable::setTable(const TableOptions &options, if (m_has_tree_column) { // Treeview: convert tree to indent cells on leaf rows for (s32 i = 0; i < rowcount; ++i) { - if (i == rowcount-1 || m_rows[i].indent >= m_rows[i+1].indent) + if (i == rowcount - 1 || m_rows[i].indent >= m_rows[i + 1].indent) for (s32 j = 0; j < m_rows[i].cellcount; ++j) - if (m_rows[i].cells[j].content_type == COLUMN_TYPE_TREE) - m_rows[i].cells[j].content_type = COLUMN_TYPE_INDENT; + if (m_rows[i].cells[j].content_type == + COLUMN_TYPE_TREE) + m_rows[i].cells[j].content_type = + COLUMN_TYPE_INDENT; } // Treeview: close rows according to opendepth option @@ -489,9 +485,9 @@ void GUITable::clear() // Get colors from skin gui::IGUISkin *skin = Environment->getSkin(); - m_color = skin->getColor(gui::EGDC_BUTTON_TEXT); - m_background = skin->getColor(gui::EGDC_3D_HIGH_LIGHT); - m_highlight = skin->getColor(gui::EGDC_HIGH_LIGHT); + m_color = skin->getColor(gui::EGDC_BUTTON_TEXT); + m_background = skin->getColor(gui::EGDC_3D_HIGH_LIGHT); + m_highlight = skin->getColor(gui::EGDC_HIGH_LIGHT); m_highlight_text = skin->getColor(gui::EGDC_HIGH_LIGHT_TEXT); // Reset members @@ -520,15 +516,14 @@ std::string GUITable::checkEvent() std::ostringstream os(std::ios::binary); if (m_sel_doubleclick) { - os<<"DCL:"; + os << "DCL:"; m_sel_doubleclick = false; + } else { + os << "CHG:"; } - else { - os<<"CHG:"; - } - os<= 0 && m_selected < (s32) m_visible_rows.size()); + assert(m_selected >= 0 && m_selected < (s32)m_visible_rows.size()); return m_visible_rows[m_selected] + 1; } @@ -578,7 +573,7 @@ void GUITable::setSelected(s32 index) if (index >= 0) { m_selected = m_rows[index].visible_index; - assert(m_selected >= 0 && m_selected < (s32) m_visible_rows.size()); + assert(m_selected >= 0 && m_selected < (s32)m_visible_rows.size()); } if (m_selected != old_selected || selection_invisible) { @@ -613,7 +608,7 @@ void GUITable::setDynamicData(const DynamicData &dyndata) m_scrollbar->setPos(dyndata.scrollpos); } -const c8* GUITable::getTypeName() const +const c8 *GUITable::getTypeName() const { return "GUITable"; } @@ -635,12 +630,11 @@ void GUITable::draw() bool draw_background = m_background.getAlpha() > 0; if (m_border) - skin->draw3DSunkenPane(this, m_background, - true, draw_background, + skin->draw3DSunkenPane(this, m_background, true, draw_background, AbsoluteRect, &AbsoluteClippingRect); else if (draw_background) - skin->draw2DRectangle(this, m_background, - AbsoluteRect, &AbsoluteClippingRect); + skin->draw2DRectangle( + this, m_background, AbsoluteRect, &AbsoluteClippingRect); // get clipping rect @@ -659,14 +653,12 @@ void GUITable::draw() s32 scrollpos = m_scrollbar->getPos(); s32 row_min = scrollpos / m_rowheight; - s32 row_max = (scrollpos + AbsoluteRect.getHeight() - 1) - / m_rowheight + 1; - row_max = MYMIN(row_max, (s32) m_visible_rows.size()); + s32 row_max = (scrollpos + AbsoluteRect.getHeight() - 1) / m_rowheight + 1; + row_max = MYMIN(row_max, (s32)m_visible_rows.size()); core::rect row_rect(AbsoluteRect); if (m_scrollbar->isVisible()) - row_rect.LowerRightCorner.X -= - skin->getSize(gui::EGDS_SCROLLBAR_SIZE); + row_rect.LowerRightCorner.X -= skin->getSize(gui::EGDS_SCROLLBAR_SIZE); row_rect.UpperLeftCorner.Y += row_min * m_rowheight - scrollpos; row_rect.LowerRightCorner.Y = row_rect.UpperLeftCorner.Y + m_rowheight; @@ -692,33 +684,27 @@ void GUITable::draw() } void GUITable::drawCell(const Cell *cell, video::SColor color, - const core::rect &row_rect, - const core::rect &client_clip) + const core::rect &row_rect, const core::rect &client_clip) { - if ((cell->content_type == COLUMN_TYPE_TEXT) - || (cell->content_type == COLUMN_TYPE_TREE)) { + if ((cell->content_type == COLUMN_TYPE_TEXT) || + (cell->content_type == COLUMN_TYPE_TREE)) { core::rect text_rect = row_rect; - text_rect.UpperLeftCorner.X = row_rect.UpperLeftCorner.X - + cell->xpos; - text_rect.LowerRightCorner.X = row_rect.UpperLeftCorner.X - + cell->xmax; + text_rect.UpperLeftCorner.X = row_rect.UpperLeftCorner.X + cell->xpos; + text_rect.LowerRightCorner.X = row_rect.UpperLeftCorner.X + cell->xmax; if (cell->color_defined) color = cell->color; if (m_font) { if (cell->content_type == COLUMN_TYPE_TEXT) - m_font->draw(m_strings[cell->content_index], - text_rect, color, - false, true, &client_clip); + m_font->draw(m_strings[cell->content_index], text_rect, + color, false, true, &client_clip); else // tree - m_font->draw(cell->content_index ? L"+" : L"-", - text_rect, color, - false, true, &client_clip); + m_font->draw(cell->content_index ? L"+" : L"-", text_rect, + color, false, true, &client_clip); } - } - else if (cell->content_type == COLUMN_TYPE_IMAGE) { + } else if (cell->content_type == COLUMN_TYPE_IMAGE) { if (cell->content_index < 0) return; @@ -727,11 +713,9 @@ void GUITable::drawCell(const Cell *cell, video::SColor color, video::ITexture *image = m_images[cell->content_index]; if (image) { - core::position2d dest_pos = - row_rect.UpperLeftCorner; + core::position2d dest_pos = row_rect.UpperLeftCorner; dest_pos.X += cell->xpos; - core::rect source_rect( - core::position2d(0, 0), + core::rect source_rect(core::position2d(0, 0), image->getOriginalSize()); s32 imgh = source_rect.LowerRightCorner.Y; s32 rowh = row_rect.getHeight(); @@ -742,8 +726,8 @@ void GUITable::drawCell(const Cell *cell, video::SColor color, video::SColor color(255, 255, 255, 255); - driver->draw2DImage(image, dest_pos, source_rect, - &client_clip, color, true); + driver->draw2DImage(image, dest_pos, source_rect, &client_clip, + color, true); } } } @@ -754,40 +738,41 @@ bool GUITable::OnEvent(const SEvent &event) return IGUIElement::OnEvent(event); if (event.EventType == EET_KEY_INPUT_EVENT) { - if (event.KeyInput.PressedDown && ( - event.KeyInput.Key == KEY_DOWN || - event.KeyInput.Key == KEY_UP || - event.KeyInput.Key == KEY_HOME || - event.KeyInput.Key == KEY_END || - event.KeyInput.Key == KEY_NEXT || - event.KeyInput.Key == KEY_PRIOR)) { + if (event.KeyInput.PressedDown && + (event.KeyInput.Key == KEY_DOWN || + event.KeyInput.Key == KEY_UP || + event.KeyInput.Key == KEY_HOME || + event.KeyInput.Key == KEY_END || + event.KeyInput.Key == KEY_NEXT || + event.KeyInput.Key == KEY_PRIOR)) { s32 offset = 0; switch (event.KeyInput.Key) { - case KEY_DOWN: - offset = 1; - break; - case KEY_UP: - offset = -1; - break; - case KEY_HOME: - offset = - (s32) m_visible_rows.size(); - break; - case KEY_END: - offset = m_visible_rows.size(); - break; - case KEY_NEXT: - offset = AbsoluteRect.getHeight() / m_rowheight; - break; - case KEY_PRIOR: - offset = - (s32) (AbsoluteRect.getHeight() / m_rowheight); - break; - default: - break; + case KEY_DOWN: + offset = 1; + break; + case KEY_UP: + offset = -1; + break; + case KEY_HOME: + offset = -(s32)m_visible_rows.size(); + break; + case KEY_END: + offset = m_visible_rows.size(); + break; + case KEY_NEXT: + offset = AbsoluteRect.getHeight() / m_rowheight; + break; + case KEY_PRIOR: + offset = -(s32)(AbsoluteRect.getHeight() / m_rowheight); + break; + default: + break; } s32 old_selected = m_selected; s32 rowcount = m_visible_rows.size(); if (rowcount != 0) { - m_selected = rangelim(m_selected + offset, 0, rowcount-1); + m_selected = rangelim( + m_selected + offset, 0, rowcount - 1); autoScroll(); } @@ -797,27 +782,24 @@ bool GUITable::OnEvent(const SEvent &event) return true; } - if (event.KeyInput.PressedDown && ( - event.KeyInput.Key == KEY_LEFT || - event.KeyInput.Key == KEY_RIGHT)) { + if (event.KeyInput.PressedDown && + (event.KeyInput.Key == KEY_LEFT || + event.KeyInput.Key == KEY_RIGHT)) { // Open/close subtree via keyboard if (m_selected >= 0) { int dir = event.KeyInput.Key == KEY_LEFT ? -1 : 1; toggleVisibleTree(m_selected, dir, true); } return true; - } - else if (!event.KeyInput.PressedDown && ( - event.KeyInput.Key == KEY_RETURN || - event.KeyInput.Key == KEY_SPACE)) { + } else if (!event.KeyInput.PressedDown && + (event.KeyInput.Key == KEY_RETURN || + event.KeyInput.Key == KEY_SPACE)) { sendTableEvent(0, true); return true; - } - else if (event.KeyInput.Key == KEY_ESCAPE || + } else if (event.KeyInput.Key == KEY_ESCAPE || event.KeyInput.Key == KEY_SPACE) { // pass to parent - } - else if (event.KeyInput.PressedDown && event.KeyInput.Char) { + } else if (event.KeyInput.PressedDown && event.KeyInput.Char) { // change selection based on text as it is typed u64 now = porting::getTimeMs(); if (now - m_keynav_time >= 500) @@ -826,12 +808,13 @@ bool GUITable::OnEvent(const SEvent &event) // add to key buffer if not a key repeat if (!(m_keynav_buffer.size() == 1 && - m_keynav_buffer[0] == event.KeyInput.Char)) { + m_keynav_buffer[0] == event.KeyInput.Char)) { m_keynav_buffer.append(event.KeyInput.Char); } // find the selected item, starting at the current selection - // don't change selection if the key buffer matches the current item + // don't change selection if the key buffer matches the current + // item s32 old_selected = m_selected; s32 start = MYMAX(m_selected, 0); s32 rowcount = m_visible_rows.size(); @@ -856,8 +839,8 @@ bool GUITable::OnEvent(const SEvent &event) if (event.MouseInput.Event == EMIE_MOUSE_WHEEL) { m_scrollbar->setPos(m_scrollbar->getPos() + - (event.MouseInput.Wheel < 0 ? -3 : 3) * - - (s32) m_rowheight / 2); + (event.MouseInput.Wheel < 0 ? -3 : 3) * + -(s32)m_rowheight / 2); return true; } @@ -878,24 +861,24 @@ bool GUITable::OnEvent(const SEvent &event) // IGUIScrollBar passes double click events to its parent, // which we don't want. Detect this case and discard the event if (event.MouseInput.Event != EMIE_MOUSE_MOVED && - m_scrollbar->isVisible() && - m_scrollbar->isPointInside(p)) + m_scrollbar->isVisible() && m_scrollbar->isPointInside(p)) return true; if (event.MouseInput.isLeftPressed() && - (isPointInside(p) || - event.MouseInput.Event == EMIE_MOUSE_MOVED)) { + (isPointInside(p) || event.MouseInput.Event == + EMIE_MOUSE_MOVED)) { s32 sel_column = 0; - bool sel_doubleclick = (event.MouseInput.Event - == EMIE_LMOUSE_DOUBLE_CLICK); + bool sel_doubleclick = (event.MouseInput.Event == + EMIE_LMOUSE_DOUBLE_CLICK); bool plusminus_clicked = false; // For certain events (left click), report column // Also open/close subtrees when the +/- is clicked - if (cell && ( - event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN || - event.MouseInput.Event == EMIE_LMOUSE_DOUBLE_CLICK || - event.MouseInput.Event == EMIE_LMOUSE_TRIPLE_CLICK)) { + if (cell && (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN || + event.MouseInput.Event == + EMIE_LMOUSE_DOUBLE_CLICK || + event.MouseInput.Event == + EMIE_LMOUSE_TRIPLE_CLICK)) { sel_column = cell->reported_column; if (cell->content_type == COLUMN_TYPE_TREE) plusminus_clicked = true; @@ -905,15 +888,13 @@ bool GUITable::OnEvent(const SEvent &event) if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) { toggleVisibleTree(row_i, 0, false); } - } - else { + } else { // Normal selection s32 old_selected = m_selected; m_selected = row_i; autoScroll(); - if (m_selected != old_selected || - sel_column >= 1 || + if (m_selected != old_selected || sel_column >= 1 || sel_doubleclick) { sendTableEvent(sel_column, sel_doubleclick); } @@ -975,9 +956,9 @@ void GUITable::allocationComplete() m_alloc_images.clear(); } -const GUITable::Row* GUITable::getRow(s32 i) const +const GUITable::Row *GUITable::getRow(s32 i) const { - if (i >= 0 && i < (s32) m_visible_rows.size()) + if (i >= 0 && i < (s32)m_visible_rows.size()) return &m_rows[m_visible_rows[i]]; return NULL; @@ -993,7 +974,8 @@ bool GUITable::doesRowStartWith(const Row *row, const core::stringw &str) const if (cell->content_type == COLUMN_TYPE_TEXT) { const core::stringw &cellstr = m_strings[cell->content_index]; if (cellstr.size() >= str.size() && - str.equals_ignore_case(cellstr.subString(0, str.size()))) + str.equals_ignore_case( + cellstr.subString(0, str.size()))) return true; } } @@ -1046,8 +1028,7 @@ s32 GUITable::getCellAt(s32 x, s32 row_i) const jmin = pivot + 1; } - if (jmin >= 0 && jmin < row->cellcount && - rel_x >= row->cells[jmin].xmin && + if (jmin >= 0 && jmin < row->cellcount && rel_x >= row->cells[jmin].xmin && rel_x <= row->cells[jmin].xmax) return jmin; @@ -1098,8 +1079,8 @@ void GUITable::getOpenedTrees(std::set &opened_trees) const opened_trees.clear(); s32 rowcount = m_rows.size(); for (s32 i = 0; i < rowcount - 1; ++i) { - if (m_rows[i].indent < m_rows[i+1].indent && - m_rows[i+1].visible_index != -2) + if (m_rows[i].indent < m_rows[i + 1].indent && + m_rows[i + 1].visible_index != -2) opened_trees.insert(i); } } @@ -1131,18 +1112,16 @@ void GUITable::setOpenedTrees(const std::set &opened_trees) // Visible row row->visible_index = m_visible_rows.size(); m_visible_rows.push_back(i); - } - else if (parents.back() == closed_parents.back()) { + } else if (parents.back() == closed_parents.back()) { // Invisible row, direct parent is closed row->visible_index = -2; - } - else { + } else { // Invisible row, direct parent is open, some ancestor is closed row->visible_index = -1; } // If not a leaf, add to parents list - if (i < m_rows.size()-1 && row->indent < m_rows[i+1].indent) { + if (i < m_rows.size() - 1 && row->indent < m_rows[i + 1].indent) { parents.push_back(i); s32 content_index = 0; // "-", open @@ -1220,14 +1199,13 @@ void GUITable::toggleVisibleTree(s32 row_i, int dir, bool move_selection) const Row *maybe_child = getRow(sel + 1); if (maybe_child && maybe_child->indent > row->indent) sel++; - } - else if (!was_open && !do_open) { + } else if (!was_open && !do_open) { // Move selection to parent assert(getRow(sel) != NULL); while (sel > 0 && getRow(sel - 1)->indent >= row->indent) sel--; sel--; - if (sel < 0) // was root already selected? + if (sel < 0) // was root already selected? sel = row_i; } if (sel != m_selected) { @@ -1245,16 +1223,13 @@ void GUITable::alignContent(Cell *cell, s32 xmax, s32 content_width, s32 align) if (align == 0) { cell->xpos = cell->xmin; cell->xmax = xmax; - } - else if (align == 1) { + } else if (align == 1) { cell->xpos = (cell->xmin + xmax - content_width) / 2; cell->xmax = xmax; - } - else if (align == 2) { + } else if (align == 2) { cell->xpos = xmax - content_width; cell->xmax = xmax; - } - else { + } else { // inline alignment: the cells of the column don't have an aligned // right border, the right border of each cell depends on the content cell->xpos = cell->xmin; diff --git a/src/gui/guiTable.h b/src/gui/guiTable.h index 11093ea72..95cf05922 100644 --- a/src/gui/guiTable.h +++ b/src/gui/guiTable.h @@ -67,9 +67,9 @@ public: std::string value; Option(const std::string &name_, const std::string &value_) : - name(name_), - value(value_) - {} + name(name_), value(value_) + { + } }; /* @@ -87,11 +87,8 @@ public: }; typedef std::vector TableColumns; - - GUITable(gui::IGUIEnvironment *env, - gui::IGUIElement *parent, s32 id, - core::rect rectangle, - ISimpleTextureSource *tsrc); + GUITable(gui::IGUIEnvironment *env, gui::IGUIElement *parent, s32 id, + core::rect rectangle, ISimpleTextureSource *tsrc); virtual ~GUITable(); @@ -99,13 +96,11 @@ public: static Option splitOption(const std::string &str); /* Set textlist-like options, columns and data */ - void setTextList(const std::vector &content, - bool transparent); + void setTextList(const std::vector &content, bool transparent); /* Set generic table options, columns and content */ // Adds empty strings to end of content if there is an incomplete row - void setTable(const TableOptions &options, - const TableColumns &columns, + void setTable(const TableOptions &options, const TableColumns &columns, std::vector &content); /* Clear the table */ @@ -130,7 +125,7 @@ public: void setDynamicData(const DynamicData &dyndata); /* Returns "GUITable" */ - virtual const c8* getTypeName() const; + virtual const c8 *getTypeName() const; /* Must be called when position or size changes */ virtual void updateAbsolutePosition(); @@ -142,7 +137,8 @@ public: virtual bool OnEvent(const SEvent &event); protected: - enum ColumnType { + enum ColumnType + { COLUMN_TYPE_TEXT, COLUMN_TYPE_IMAGE, COLUMN_TYPE_COLOR, @@ -150,7 +146,8 @@ protected: COLUMN_TYPE_TREE, }; - struct Cell { + struct Cell + { s32 xmin; s32 xmax; s32 xpos; @@ -162,7 +159,8 @@ protected: s32 reported_column; }; - struct Row { + struct Row + { Cell *cells; s32 cellcount; s32 indent; @@ -203,7 +201,7 @@ protected: // Allocated strings and images std::vector m_strings; - std::vector m_images; + std::vector m_images; std::map m_alloc_strings; std::map m_alloc_images; @@ -252,6 +250,5 @@ protected: // Aligns cell content in column according to alignment specification // align = 0: left aligned, 1: centered, 2: right aligned, 3: inline - static void alignContent(Cell *cell, s32 xmax, s32 content_width, - s32 align); + static void alignContent(Cell *cell, s32 xmax, s32 content_width, s32 align); }; diff --git a/src/gui/guiVolumeChange.cpp b/src/gui/guiVolumeChange.cpp index f17cfa986..12874594e 100644 --- a/src/gui/guiVolumeChange.cpp +++ b/src/gui/guiVolumeChange.cpp @@ -36,12 +36,10 @@ const int ID_soundExitButton = 264; const int ID_soundSlider = 265; const int ID_soundMuteButton = 266; -GUIVolumeChange::GUIVolumeChange(gui::IGUIEnvironment* env, - gui::IGUIElement* parent, s32 id, - IMenuManager *menumgr, ISimpleTextureSource *tsrc -): - GUIModalMenu(env, parent, id, menumgr), - m_tsrc(tsrc) +GUIVolumeChange::GUIVolumeChange(gui::IGUIEnvironment *env, gui::IGUIElement *parent, + s32 id, IMenuManager *menumgr, ISimpleTextureSource *tsrc) : + GUIModalMenu(env, parent, id, menumgr), + m_tsrc(tsrc) { } @@ -75,12 +73,9 @@ void GUIVolumeChange::regenerateGui(v2u32 screensize) Calculate new sizes and positions */ const float s = m_gui_scale; - DesiredRect = core::rect( - screensize.X / 2 - 380 * s / 2, - screensize.Y / 2 - 200 * s / 2, - screensize.X / 2 + 380 * s / 2, - screensize.Y / 2 + 200 * s / 2 - ); + DesiredRect = core::rect(screensize.X / 2 - 380 * s / 2, + screensize.Y / 2 - 200 * s / 2, screensize.X / 2 + 380 * s / 2, + screensize.Y / 2 + 200 * s / 2); recalculateAbsolutePosition(false); v2s32 size = DesiredRect.getSize(); @@ -95,24 +90,25 @@ void GUIVolumeChange::regenerateGui(v2u32 screensize) const wchar_t *text = wgettext("Sound Volume: "); core::stringw volume_text = text; - delete [] text; + delete[] text; volume_text += core::stringw(volume) + core::stringw("%"); - Environment->addStaticText(volume_text.c_str(), rect, false, - true, this, ID_soundText); + Environment->addStaticText(volume_text.c_str(), rect, false, true, this, + ID_soundText); } { core::rect rect(0, 0, 80 * s, 30 * s); rect = rect + v2s32(size.X / 2 - 80 * s / 2, size.Y / 2 + 55 * s); const wchar_t *text = wgettext("Exit"); - GUIButton::addButton(Environment, rect, m_tsrc, this, ID_soundExitButton, text); + GUIButton::addButton(Environment, rect, m_tsrc, this, ID_soundExitButton, + text); delete[] text; } { core::rect rect(0, 0, 300 * s, 20 * s); rect = rect + v2s32(size.X / 2 - 150 * s, size.Y / 2); - gui::IGUIScrollBar *e = Environment->addScrollBar(true, - rect, this, ID_soundSlider); + gui::IGUIScrollBar *e = Environment->addScrollBar( + true, rect, this, ID_soundSlider); e->setMax(100); e->setPos(volume); } @@ -128,16 +124,16 @@ void GUIVolumeChange::regenerateGui(v2u32 screensize) void GUIVolumeChange::drawMenu() { - gui::IGUISkin* skin = Environment->getSkin(); + gui::IGUISkin *skin = Environment->getSkin(); if (!skin) return; - video::IVideoDriver* driver = Environment->getVideoDriver(); + video::IVideoDriver *driver = Environment->getVideoDriver(); video::SColor bgcolor(140, 0, 0, 0); driver->draw2DRectangle(bgcolor, AbsoluteRect, &AbsoluteClippingRect); gui::IGUIElement::draw(); } -bool GUIVolumeChange::OnEvent(const SEvent& event) +bool GUIVolumeChange::OnEvent(const SEvent &event) { if (event.EventType == EET_KEY_INPUT_EVENT) { if (event.KeyInput.Key == KEY_ESCAPE && event.KeyInput.PressedDown) { @@ -153,7 +149,8 @@ bool GUIVolumeChange::OnEvent(const SEvent& event) if (event.GUIEvent.EventType == gui::EGET_CHECKBOX_CHANGED) { gui::IGUIElement *e = getElementFromId(ID_soundMuteButton); if (e != NULL && e->getType() == gui::EGUIET_CHECK_BOX) { - g_settings->setBool("mute_sound", ((gui::IGUICheckBox*)e)->isChecked()); + g_settings->setBool("mute_sound", + ((gui::IGUICheckBox *)e)->isChecked()); } Environment->setFocus(this); @@ -168,31 +165,32 @@ bool GUIVolumeChange::OnEvent(const SEvent& event) Environment->setFocus(this); } - if (event.GUIEvent.EventType == gui::EGET_ELEMENT_FOCUS_LOST - && isVisible()) { + if (event.GUIEvent.EventType == gui::EGET_ELEMENT_FOCUS_LOST && + isVisible()) { if (!canTakeFocus(event.GUIEvent.Element)) { - infostream << "GUIVolumeChange: Not allowing focus change." - << std::endl; + infostream << "GUIVolumeChange: Not allowing focus " + "change." + << std::endl; // Returning true disables focus change return true; } } if (event.GUIEvent.EventType == gui::EGET_SCROLL_BAR_CHANGED) { if (event.GUIEvent.Caller->getID() == ID_soundSlider) { - s32 pos = ((gui::IGUIScrollBar*)event.GUIEvent.Caller)->getPos(); - g_settings->setFloat("sound_volume", (float) pos / 100); + s32 pos = ((gui::IGUIScrollBar *)event.GUIEvent.Caller) + ->getPos(); + g_settings->setFloat("sound_volume", (float)pos / 100); gui::IGUIElement *e = getElementFromId(ID_soundText); const wchar_t *text = wgettext("Sound Volume: "); core::stringw volume_text = text; - delete [] text; + delete[] text; volume_text += core::stringw(pos) + core::stringw("%"); e->setText(volume_text.c_str()); return true; } } - } return Parent ? Parent->OnEvent(event) : false; diff --git a/src/gui/guiVolumeChange.h b/src/gui/guiVolumeChange.h index 466e17f9d..f1347a491 100644 --- a/src/gui/guiVolumeChange.h +++ b/src/gui/guiVolumeChange.h @@ -28,8 +28,7 @@ class ISimpleTextureSource; class GUIVolumeChange : public GUIModalMenu { public: - GUIVolumeChange(gui::IGUIEnvironment* env, - gui::IGUIElement* parent, s32 id, + GUIVolumeChange(gui::IGUIEnvironment *env, gui::IGUIElement *parent, s32 id, IMenuManager *menumgr, ISimpleTextureSource *tsrc); ~GUIVolumeChange(); @@ -41,7 +40,7 @@ public: void drawMenu(); - bool OnEvent(const SEvent& event); + bool OnEvent(const SEvent &event); bool pausesGame() { return true; } diff --git a/src/gui/intlGUIEditBox.cpp b/src/gui/intlGUIEditBox.cpp index 10395423c..1c8bb64de 100644 --- a/src/gui/intlGUIEditBox.cpp +++ b/src/gui/intlGUIEditBox.cpp @@ -45,9 +45,8 @@ todo: optional scrollbars ctrl+left/right to select word - double click/ctrl click: word select + drag to select whole words, triple click to select line - optional? dragging selected text - numerical + double click/ctrl click: word select + drag to select whole words, triple click to + select line optional? dragging selected text numerical */ namespace irr @@ -56,16 +55,16 @@ namespace gui { //! constructor -intlGUIEditBox::intlGUIEditBox(const wchar_t* text, bool border, - IGUIEnvironment* environment, IGUIElement* parent, s32 id, - const core::rect& rectangle, bool writable, bool has_vscrollbar) - : IGUIEditBox(environment, parent, id, rectangle), - Border(border), FrameRect(rectangle), - m_scrollbar_width(0), m_vscrollbar(NULL), m_writable(writable) +intlGUIEditBox::intlGUIEditBox(const wchar_t *text, bool border, + IGUIEnvironment *environment, IGUIElement *parent, s32 id, + const core::rect &rectangle, bool writable, bool has_vscrollbar) : + IGUIEditBox(environment, parent, id, rectangle), + Border(border), FrameRect(rectangle), m_scrollbar_width(0), + m_vscrollbar(NULL), m_writable(writable) { - #ifdef _DEBUG +#ifdef _DEBUG setDebugName("intlintlGUIEditBox"); - #endif +#endif Text = text; @@ -82,12 +81,11 @@ intlGUIEditBox::intlGUIEditBox(const wchar_t* text, bool border, IGUISkin *skin = 0; if (Environment) skin = Environment->getSkin(); - if (Border && skin) - { - FrameRect.UpperLeftCorner.X += skin->getSize(EGDS_TEXT_DISTANCE_X)+1; - FrameRect.UpperLeftCorner.Y += skin->getSize(EGDS_TEXT_DISTANCE_Y)+1; - FrameRect.LowerRightCorner.X -= skin->getSize(EGDS_TEXT_DISTANCE_X)+1; - FrameRect.LowerRightCorner.Y -= skin->getSize(EGDS_TEXT_DISTANCE_Y)+1; + if (Border && skin) { + FrameRect.UpperLeftCorner.X += skin->getSize(EGDS_TEXT_DISTANCE_X) + 1; + FrameRect.UpperLeftCorner.Y += skin->getSize(EGDS_TEXT_DISTANCE_Y) + 1; + FrameRect.LowerRightCorner.X -= skin->getSize(EGDS_TEXT_DISTANCE_X) + 1; + FrameRect.LowerRightCorner.Y -= skin->getSize(EGDS_TEXT_DISTANCE_Y) + 1; } if (skin && has_vscrollbar) { @@ -104,7 +102,6 @@ intlGUIEditBox::intlGUIEditBox(const wchar_t* text, bool border, setWritable(writable); } - //! destructor intlGUIEditBox::~intlGUIEditBox() { @@ -118,9 +115,8 @@ intlGUIEditBox::~intlGUIEditBox() m_vscrollbar->drop(); } - //! Sets another skin independent font. -void intlGUIEditBox::setOverrideFont(IGUIFont* font) +void intlGUIEditBox::setOverrideFont(IGUIFont *font) { if (OverrideFont == font) return; @@ -136,17 +132,17 @@ void intlGUIEditBox::setOverrideFont(IGUIFont* font) breakText(); } -IGUIFont * intlGUIEditBox::getOverrideFont() const +IGUIFont *intlGUIEditBox::getOverrideFont() const { return OverrideFont; } //! Get the font which is used right now for drawing -IGUIFont* intlGUIEditBox::getActiveFont() const +IGUIFont *intlGUIEditBox::getActiveFont() const { - if ( OverrideFont ) + if (OverrideFont) return OverrideFont; - IGUISkin* skin = Environment->getSkin(); + IGUISkin *skin = Environment->getSkin(); if (skin) return skin->getFont(); return 0; @@ -193,44 +189,37 @@ void intlGUIEditBox::setWordWrap(bool enable) breakText(); } - void intlGUIEditBox::updateAbsolutePosition() { - core::rect oldAbsoluteRect(AbsoluteRect); + core::rect oldAbsoluteRect(AbsoluteRect); IGUIElement::updateAbsolutePosition(); - if ( oldAbsoluteRect != AbsoluteRect ) - { - breakText(); + if (oldAbsoluteRect != AbsoluteRect) { + breakText(); } } - //! Checks if word wrap is enabled bool intlGUIEditBox::isWordWrapEnabled() const { return WordWrap; } - //! Enables or disables newlines. void intlGUIEditBox::setMultiLine(bool enable) { MultiLine = enable; } - //! Checks if multi line editing is enabled bool intlGUIEditBox::isMultiLineEnabled() const { return MultiLine; } - void intlGUIEditBox::setPasswordBox(bool passwordBox, wchar_t passwordChar) { PasswordBox = passwordBox; - if (PasswordBox) - { + if (PasswordBox) { PasswordChar = passwordChar; setMultiLine(false); setWordWrap(false); @@ -238,13 +227,11 @@ void intlGUIEditBox::setPasswordBox(bool passwordBox, wchar_t passwordChar) } } - bool intlGUIEditBox::isPasswordBox() const { return PasswordBox; } - //! Sets text justification void intlGUIEditBox::setTextAlignment(EGUI_ALIGNMENT horizontal, EGUI_ALIGNMENT vertical) { @@ -252,40 +239,35 @@ void intlGUIEditBox::setTextAlignment(EGUI_ALIGNMENT horizontal, EGUI_ALIGNMENT VAlign = vertical; } - //! called if an event happened. -bool intlGUIEditBox::OnEvent(const SEvent& event) +bool intlGUIEditBox::OnEvent(const SEvent &event) { - if (IsEnabled) - { + if (IsEnabled) { - switch(event.EventType) - { + switch (event.EventType) { case EET_GUI_EVENT: - if (event.GUIEvent.EventType == EGET_ELEMENT_FOCUS_LOST) - { - if (event.GUIEvent.Caller == this) - { + if (event.GUIEvent.EventType == EGET_ELEMENT_FOCUS_LOST) { + if (event.GUIEvent.Caller == this) { MouseMarking = false; - setTextMarkers(0,0); + setTextMarkers(0, 0); } } break; - case EET_KEY_INPUT_EVENT: - { + case EET_KEY_INPUT_EVENT: { #if (defined(__linux__) || defined(__FreeBSD__)) || defined(__DragonFly__) - // ################################################################ + // ################################################################ // ValkaTR: - // This part is the difference from the original intlGUIEditBox - // It converts UTF-8 character into a UCS-2 (wchar_t) - wchar_t wc = L'_'; - mbtowc( &wc, (char *) &event.KeyInput.Char, sizeof(event.KeyInput.Char) ); + // This part is the difference from the original intlGUIEditBox + // It converts UTF-8 character into a UCS-2 (wchar_t) + wchar_t wc = L'_'; + mbtowc(&wc, (char *)&event.KeyInput.Char, + sizeof(event.KeyInput.Char)); - //printf( "char: %lc (%u) \r\n", wc, wc ); + // printf( "char: %lc (%u) \r\n", wc, wc ); - SEvent irrevent(event); - irrevent.KeyInput.Char = wc; - // ################################################################ + SEvent irrevent(event); + irrevent.KeyInput.Char = wc; + // ################################################################ if (processKey(irrevent)) return true; @@ -295,7 +277,7 @@ bool intlGUIEditBox::OnEvent(const SEvent& event) #endif // defined(linux) break; - } + } case EET_MOUSE_INPUT_EVENT: if (processMouse(event)) return true; @@ -308,8 +290,7 @@ bool intlGUIEditBox::OnEvent(const SEvent& event) return IGUIElement::OnEvent(event); } - -bool intlGUIEditBox::processKey(const SEvent& event) +bool intlGUIEditBox::processKey(const SEvent &event) { if (!event.KeyInput.PressedDown) return false; @@ -320,17 +301,14 @@ bool intlGUIEditBox::processKey(const SEvent& event) // control shortcut handling - if (event.KeyInput.Control) - { + if (event.KeyInput.Control) { // german backlash '\' entered with control + '?' - if ( event.KeyInput.Char == '\\' ) - { + if (event.KeyInput.Char == '\\') { inputChar(event.KeyInput.Char); return true; } - switch(event.KeyInput.Key) - { + switch (event.KeyInput.Key) { case KEY_KEY_A: // select all newMarkBegin = 0; @@ -338,10 +316,11 @@ bool intlGUIEditBox::processKey(const SEvent& event) break; case KEY_KEY_C: // copy to clipboard - if (!PasswordBox && Operator && MarkBegin != MarkEnd) - { - const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd; - const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin; + if (!PasswordBox && Operator && MarkBegin != MarkEnd) { + const s32 realmbgn = + MarkBegin < MarkEnd ? MarkBegin : MarkEnd; + const s32 realmend = + MarkBegin < MarkEnd ? MarkEnd : MarkBegin; core::stringc s; s = Text.subString(realmbgn, realmend - realmbgn).c_str(); @@ -351,19 +330,23 @@ bool intlGUIEditBox::processKey(const SEvent& event) case KEY_KEY_X: // cut to the clipboard if (!PasswordBox && Operator && MarkBegin != MarkEnd) { - const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd; - const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin; + const s32 realmbgn = + MarkBegin < MarkEnd ? MarkBegin : MarkEnd; + const s32 realmend = + MarkBegin < MarkEnd ? MarkEnd : MarkBegin; // copy core::stringc sc; - sc = Text.subString(realmbgn, realmend - realmbgn).c_str(); + sc = Text.subString(realmbgn, realmend - realmbgn) + .c_str(); Operator->copyToClipboard(sc.c_str()); if (IsEnabled && m_writable) { // delete core::stringw s; s = Text.subString(0, realmbgn); - s.append( Text.subString(realmend, Text.size()-realmend) ); + s.append(Text.subString(realmend, + Text.size() - realmend)); Text = s; CursorPos = realmbgn; @@ -378,38 +361,43 @@ bool intlGUIEditBox::processKey(const SEvent& event) break; // paste from the clipboard - if (Operator) - { - const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd; - const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin; + if (Operator) { + const s32 realmbgn = + MarkBegin < MarkEnd ? MarkBegin : MarkEnd; + const s32 realmend = + MarkBegin < MarkEnd ? MarkEnd : MarkBegin; // add new character - const c8* p = Operator->getTextFromClipboard(); - if (p) - { - if (MarkBegin == MarkEnd) - { + const c8 *p = Operator->getTextFromClipboard(); + if (p) { + if (MarkBegin == MarkEnd) { // insert text - core::stringw s = Text.subString(0, CursorPos); + core::stringw s = Text.subString( + 0, CursorPos); s.append(p); - s.append( Text.subString(CursorPos, Text.size()-CursorPos) ); + s.append(Text.subString(CursorPos, + Text.size() - CursorPos)); - if (!Max || s.size()<=Max) // thx to Fish FH for fix + if (!Max || s.size() <= Max) // thx to + // Fish FH + // for fix { Text = s; s = p; CursorPos += s.size(); } - } - else - { + } else { // replace text - core::stringw s = Text.subString(0, realmbgn); + core::stringw s = Text.subString( + 0, realmbgn); s.append(p); - s.append( Text.subString(realmend, Text.size()-realmend) ); + s.append(Text.subString(realmend, + Text.size() - realmend)); - if (!Max || s.size()<=Max) // thx to Fish FH for fix + if (!Max || s.size() <= Max) // thx to + // Fish FH + // for fix { Text = s; s = p; @@ -425,14 +413,11 @@ bool intlGUIEditBox::processKey(const SEvent& event) break; case KEY_HOME: // move/highlight to start of text - if (event.KeyInput.Shift) - { + if (event.KeyInput.Shift) { newMarkEnd = CursorPos; newMarkBegin = 0; CursorPos = 0; - } - else - { + } else { CursorPos = 0; newMarkBegin = 0; newMarkEnd = 0; @@ -440,14 +425,11 @@ bool intlGUIEditBox::processKey(const SEvent& event) break; case KEY_END: // move/highlight to end of text - if (event.KeyInput.Shift) - { + if (event.KeyInput.Shift) { newMarkBegin = CursorPos; newMarkEnd = Text.size(); CursorPos = 0; - } - else - { + } else { CursorPos = Text.size(); newMarkBegin = 0; newMarkEnd = 0; @@ -459,293 +441,281 @@ bool intlGUIEditBox::processKey(const SEvent& event) } // default keyboard handling else - switch(event.KeyInput.Key) - { - case KEY_END: - { + switch (event.KeyInput.Key) { + case KEY_END: { s32 p = Text.size(); - if (WordWrap || MultiLine) - { + if (WordWrap || MultiLine) { p = getLineFromPos(CursorPos); p = BrokenTextPositions[p] + (s32)BrokenText[p].size(); - if (p > 0 && (Text[p-1] == L'\r' || Text[p-1] == L'\n' )) - p-=1; + if (p > 0 && (Text[p - 1] == L'\r' || + Text[p - 1] == L'\n')) + p -= 1; } - if (event.KeyInput.Shift) - { + if (event.KeyInput.Shift) { if (MarkBegin == MarkEnd) newMarkBegin = CursorPos; newMarkEnd = p; - } - else - { + } else { newMarkBegin = 0; newMarkEnd = 0; } CursorPos = p; BlinkStartTime = porting::getTimeMs(); - } - break; - case KEY_HOME: - { + } break; + case KEY_HOME: { s32 p = 0; - if (WordWrap || MultiLine) - { + if (WordWrap || MultiLine) { p = getLineFromPos(CursorPos); p = BrokenTextPositions[p]; } - if (event.KeyInput.Shift) - { + if (event.KeyInput.Shift) { if (MarkBegin == MarkEnd) newMarkBegin = CursorPos; newMarkEnd = p; - } - else - { + } else { newMarkBegin = 0; newMarkEnd = 0; } CursorPos = p; BlinkStartTime = porting::getTimeMs(); - } - break; - case KEY_RETURN: - if (MultiLine) - { - inputChar(L'\n'); + } break; + case KEY_RETURN: + if (MultiLine) { + inputChar(L'\n'); + return true; + } else { + sendGuiEvent(EGET_EDITBOX_ENTER); + } + break; + case KEY_LEFT: + + if (event.KeyInput.Shift) { + if (CursorPos > 0) { + if (MarkBegin == MarkEnd) + newMarkBegin = CursorPos; + + newMarkEnd = CursorPos - 1; + } + } else { + newMarkBegin = 0; + newMarkEnd = 0; + } + + if (CursorPos > 0) + CursorPos--; + BlinkStartTime = porting::getTimeMs(); + break; + + case KEY_RIGHT: + if (event.KeyInput.Shift) { + if (Text.size() > (u32)CursorPos) { + if (MarkBegin == MarkEnd) + newMarkBegin = CursorPos; + + newMarkEnd = CursorPos + 1; + } + } else { + newMarkBegin = 0; + newMarkEnd = 0; + } + + if (Text.size() > (u32)CursorPos) + CursorPos++; + BlinkStartTime = porting::getTimeMs(); + break; + case KEY_UP: + if (MultiLine || (WordWrap && BrokenText.size() > 1)) { + s32 lineNo = getLineFromPos(CursorPos); + s32 mb = (MarkBegin == MarkEnd) + ? CursorPos + : (MarkBegin > MarkEnd ? MarkBegin + : MarkEnd); + if (lineNo > 0) { + s32 cp = CursorPos - BrokenTextPositions[lineNo]; + if ((s32)BrokenText[lineNo - 1].size() < cp) + CursorPos = BrokenTextPositions[lineNo - + 1] + + (s32)BrokenText[lineNo - 1] + .size() - + 1; + else + CursorPos = BrokenTextPositions[lineNo - + 1] + + cp; + } + + if (event.KeyInput.Shift) { + newMarkBegin = mb; + newMarkEnd = CursorPos; + } else { + newMarkBegin = 0; + newMarkEnd = 0; + } + + } else { + return false; + } + break; + case KEY_DOWN: + if (MultiLine || (WordWrap && BrokenText.size() > 1)) { + s32 lineNo = getLineFromPos(CursorPos); + s32 mb = (MarkBegin == MarkEnd) + ? CursorPos + : (MarkBegin < MarkEnd ? MarkBegin + : MarkEnd); + if (lineNo < (s32)BrokenText.size() - 1) { + s32 cp = CursorPos - BrokenTextPositions[lineNo]; + if ((s32)BrokenText[lineNo + 1].size() < cp) + CursorPos = BrokenTextPositions[lineNo + + 1] + + BrokenText[lineNo + 1] + .size() - + 1; + else + CursorPos = BrokenTextPositions[lineNo + + 1] + + cp; + } + + if (event.KeyInput.Shift) { + newMarkBegin = mb; + newMarkEnd = CursorPos; + } else { + newMarkBegin = 0; + newMarkEnd = 0; + } + + } else { + return false; + } + break; + + case KEY_BACK: + if (!this->IsEnabled || !m_writable) + break; + + if (!Text.empty()) { + core::stringw s; + + if (MarkBegin != MarkEnd) { + // delete marked text + const s32 realmbgn = MarkBegin < MarkEnd + ? MarkBegin + : MarkEnd; + const s32 realmend = MarkBegin < MarkEnd + ? MarkEnd + : MarkBegin; + + s = Text.subString(0, realmbgn); + s.append(Text.subString(realmend, + Text.size() - realmend)); + Text = s; + + CursorPos = realmbgn; + } else { + // delete text behind cursor + if (CursorPos > 0) + s = Text.subString(0, CursorPos - 1); + else + s = L""; + s.append(Text.subString(CursorPos, + Text.size() - CursorPos)); + Text = s; + --CursorPos; + } + + if (CursorPos < 0) + CursorPos = 0; + BlinkStartTime = porting::getTimeMs(); + newMarkBegin = 0; + newMarkEnd = 0; + textChanged = true; + } + break; + case KEY_DELETE: + if (!this->IsEnabled || !m_writable) + break; + + if (!Text.empty()) { + core::stringw s; + + if (MarkBegin != MarkEnd) { + // delete marked text + const s32 realmbgn = MarkBegin < MarkEnd + ? MarkBegin + : MarkEnd; + const s32 realmend = MarkBegin < MarkEnd + ? MarkEnd + : MarkBegin; + + s = Text.subString(0, realmbgn); + s.append(Text.subString(realmend, + Text.size() - realmend)); + Text = s; + + CursorPos = realmbgn; + } else { + // delete text before cursor + s = Text.subString(0, CursorPos); + s.append(Text.subString(CursorPos + 1, + Text.size() - CursorPos - 1)); + Text = s; + } + + if (CursorPos > (s32)Text.size()) + CursorPos = (s32)Text.size(); + + BlinkStartTime = porting::getTimeMs(); + newMarkBegin = 0; + newMarkEnd = 0; + textChanged = true; + } + break; + + case KEY_ESCAPE: + case KEY_TAB: + case KEY_SHIFT: + case KEY_F1: + case KEY_F2: + case KEY_F3: + case KEY_F4: + case KEY_F5: + case KEY_F6: + case KEY_F7: + case KEY_F8: + case KEY_F9: + case KEY_F10: + case KEY_F11: + case KEY_F12: + case KEY_F13: + case KEY_F14: + case KEY_F15: + case KEY_F16: + case KEY_F17: + case KEY_F18: + case KEY_F19: + case KEY_F20: + case KEY_F21: + case KEY_F22: + case KEY_F23: + case KEY_F24: + // ignore these keys + return false; + + default: + inputChar(event.KeyInput.Char); return true; } - else - { - sendGuiEvent( EGET_EDITBOX_ENTER ); - } - break; - case KEY_LEFT: - if (event.KeyInput.Shift) - { - if (CursorPos > 0) - { - if (MarkBegin == MarkEnd) - newMarkBegin = CursorPos; - - newMarkEnd = CursorPos-1; - } - } - else - { - newMarkBegin = 0; - newMarkEnd = 0; - } - - if (CursorPos > 0) CursorPos--; - BlinkStartTime = porting::getTimeMs(); - break; - - case KEY_RIGHT: - if (event.KeyInput.Shift) - { - if (Text.size() > (u32)CursorPos) - { - if (MarkBegin == MarkEnd) - newMarkBegin = CursorPos; - - newMarkEnd = CursorPos+1; - } - } - else - { - newMarkBegin = 0; - newMarkEnd = 0; - } - - if (Text.size() > (u32)CursorPos) CursorPos++; - BlinkStartTime = porting::getTimeMs(); - break; - case KEY_UP: - if (MultiLine || (WordWrap && BrokenText.size() > 1) ) - { - s32 lineNo = getLineFromPos(CursorPos); - s32 mb = (MarkBegin == MarkEnd) ? CursorPos : (MarkBegin > MarkEnd ? MarkBegin : MarkEnd); - if (lineNo > 0) - { - s32 cp = CursorPos - BrokenTextPositions[lineNo]; - if ((s32)BrokenText[lineNo-1].size() < cp) - CursorPos = BrokenTextPositions[lineNo-1] + (s32)BrokenText[lineNo-1].size()-1; - else - CursorPos = BrokenTextPositions[lineNo-1] + cp; - } - - if (event.KeyInput.Shift) - { - newMarkBegin = mb; - newMarkEnd = CursorPos; - } - else - { - newMarkBegin = 0; - newMarkEnd = 0; - } - - } - else - { - return false; - } - break; - case KEY_DOWN: - if (MultiLine || (WordWrap && BrokenText.size() > 1) ) - { - s32 lineNo = getLineFromPos(CursorPos); - s32 mb = (MarkBegin == MarkEnd) ? CursorPos : (MarkBegin < MarkEnd ? MarkBegin : MarkEnd); - if (lineNo < (s32)BrokenText.size()-1) - { - s32 cp = CursorPos - BrokenTextPositions[lineNo]; - if ((s32)BrokenText[lineNo+1].size() < cp) - CursorPos = BrokenTextPositions[lineNo+1] + BrokenText[lineNo+1].size()-1; - else - CursorPos = BrokenTextPositions[lineNo+1] + cp; - } - - if (event.KeyInput.Shift) - { - newMarkBegin = mb; - newMarkEnd = CursorPos; - } - else - { - newMarkBegin = 0; - newMarkEnd = 0; - } - - } - else - { - return false; - } - break; - - case KEY_BACK: - if (!this->IsEnabled || !m_writable) - break; - - if (!Text.empty()) { - core::stringw s; - - if (MarkBegin != MarkEnd) - { - // delete marked text - const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd; - const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin; - - s = Text.subString(0, realmbgn); - s.append( Text.subString(realmend, Text.size()-realmend) ); - Text = s; - - CursorPos = realmbgn; - } - else - { - // delete text behind cursor - if (CursorPos>0) - s = Text.subString(0, CursorPos-1); - else - s = L""; - s.append( Text.subString(CursorPos, Text.size()-CursorPos) ); - Text = s; - --CursorPos; - } - - if (CursorPos < 0) - CursorPos = 0; - BlinkStartTime = porting::getTimeMs(); - newMarkBegin = 0; - newMarkEnd = 0; - textChanged = true; - } - break; - case KEY_DELETE: - if (!this->IsEnabled || !m_writable) - break; - - if (!Text.empty()) { - core::stringw s; - - if (MarkBegin != MarkEnd) - { - // delete marked text - const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd; - const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin; - - s = Text.subString(0, realmbgn); - s.append( Text.subString(realmend, Text.size()-realmend) ); - Text = s; - - CursorPos = realmbgn; - } - else - { - // delete text before cursor - s = Text.subString(0, CursorPos); - s.append( Text.subString(CursorPos+1, Text.size()-CursorPos-1) ); - Text = s; - } - - if (CursorPos > (s32)Text.size()) - CursorPos = (s32)Text.size(); - - BlinkStartTime = porting::getTimeMs(); - newMarkBegin = 0; - newMarkEnd = 0; - textChanged = true; - } - break; - - case KEY_ESCAPE: - case KEY_TAB: - case KEY_SHIFT: - case KEY_F1: - case KEY_F2: - case KEY_F3: - case KEY_F4: - case KEY_F5: - case KEY_F6: - case KEY_F7: - case KEY_F8: - case KEY_F9: - case KEY_F10: - case KEY_F11: - case KEY_F12: - case KEY_F13: - case KEY_F14: - case KEY_F15: - case KEY_F16: - case KEY_F17: - case KEY_F18: - case KEY_F19: - case KEY_F20: - case KEY_F21: - case KEY_F22: - case KEY_F23: - case KEY_F24: - // ignore these keys - return false; - - default: - inputChar(event.KeyInput.Char); - return true; - } - - // Set new text markers - setTextMarkers( newMarkBegin, newMarkEnd ); + // Set new text markers + setTextMarkers(newMarkBegin, newMarkEnd); // break the text if it has changed - if (textChanged) - { + if (textChanged) { breakText(); sendGuiEvent(EGET_EDITBOX_CHANGED); } @@ -755,7 +725,6 @@ bool intlGUIEditBox::processKey(const SEvent& event) return true; } - //! draws the element and its children void intlGUIEditBox::draw() { @@ -764,7 +733,7 @@ void intlGUIEditBox::draw() const bool focus = Environment->hasFocus(this); - IGUISkin* skin = Environment->getSkin(); + IGUISkin *skin = Environment->getSkin(); if (!skin) return; @@ -772,17 +741,16 @@ void intlGUIEditBox::draw() // draw the border - if (Border) - { + if (Border) { if (m_writable) { - skin->draw3DSunkenPane(this, skin->getColor(EGDC_WINDOW), - false, true, FrameRect, &AbsoluteClippingRect); + skin->draw3DSunkenPane(this, skin->getColor(EGDC_WINDOW), false, + true, FrameRect, &AbsoluteClippingRect); } - FrameRect.UpperLeftCorner.X += skin->getSize(EGDS_TEXT_DISTANCE_X)+1; - FrameRect.UpperLeftCorner.Y += skin->getSize(EGDS_TEXT_DISTANCE_Y)+1; - FrameRect.LowerRightCorner.X -= skin->getSize(EGDS_TEXT_DISTANCE_X)+1; - FrameRect.LowerRightCorner.Y -= skin->getSize(EGDS_TEXT_DISTANCE_Y)+1; + FrameRect.UpperLeftCorner.X += skin->getSize(EGDS_TEXT_DISTANCE_X) + 1; + FrameRect.UpperLeftCorner.Y += skin->getSize(EGDS_TEXT_DISTANCE_Y) + 1; + FrameRect.LowerRightCorner.X -= skin->getSize(EGDS_TEXT_DISTANCE_X) + 1; + FrameRect.LowerRightCorner.Y -= skin->getSize(EGDS_TEXT_DISTANCE_Y) + 1; } updateVScrollBar(); @@ -791,17 +759,15 @@ void intlGUIEditBox::draw() // draw the text - IGUIFont* font = OverrideFont; + IGUIFont *font = OverrideFont; if (!OverrideFont) font = skin->getFont(); s32 cursorLine = 0; s32 charcursorpos = 0; - if (font) - { - if (LastBreakFont != font) - { + if (font) { + if (LastBreakFont != font) { breakText(); } @@ -826,97 +792,107 @@ void intlGUIEditBox::draw() const video::SColor prevColor = OverrideColor; if (!Text.empty()) { - if (!IsEnabled && !OverrideColorEnabled) - { + if (!IsEnabled && !OverrideColorEnabled) { OverrideColorEnabled = true; OverrideColor = skin->getColor(EGDC_GRAY_TEXT); } - for (s32 i=0; i < lineCount; ++i) - { + for (s32 i = 0; i < lineCount; ++i) { setTextRect(i); - // clipping test - don't draw anything outside the visible area + // clipping test - don't draw anything outside the visible + // area core::rect c = localClipRect; c.clipAgainst(CurrentTextRect); if (!c.isValid()) continue; // get current line - if (PasswordBox) - { - if (BrokenText.size() != 1) - { + if (PasswordBox) { + if (BrokenText.size() != 1) { BrokenText.clear(); BrokenText.push_back(core::stringw()); } - if (BrokenText[0].size() != Text.size()) - { + if (BrokenText[0].size() != Text.size()) { BrokenText[0] = Text; - for (u32 q = 0; q < Text.size(); ++q) - { - BrokenText[0] [q] = PasswordChar; + for (u32 q = 0; q < Text.size(); ++q) { + BrokenText[0][q] = PasswordChar; } } txtLine = &BrokenText[0]; startPos = 0; - } - else - { + } else { txtLine = ml ? &BrokenText[i] : &Text; startPos = ml ? BrokenTextPositions[i] : 0; } - // draw normal text font->draw(txtLine->c_str(), CurrentTextRect, - OverrideColorEnabled ? OverrideColor : skin->getColor(EGDC_BUTTON_TEXT), - false, true, &localClipRect); + OverrideColorEnabled + ? OverrideColor + : skin->getColor(EGDC_BUTTON_TEXT), + false, true, &localClipRect); // draw mark and marked text - if (focus && MarkBegin != MarkEnd && i >= hlineStart && i < hlineStart + hlineCount) - { + if (focus && MarkBegin != MarkEnd && i >= hlineStart && + i < hlineStart + hlineCount) { s32 mbegin = 0, mend = 0; - s32 lineStartPos = 0, lineEndPos = txtLine->size(); + s32 lineStartPos = 0, + lineEndPos = txtLine->size(); - if (i == hlineStart) - { + if (i == hlineStart) { // highlight start is on this line - s = txtLine->subString(0, realmbgn - startPos); - mbegin = font->getDimension(s.c_str()).Width; + s = txtLine->subString( + 0, realmbgn - startPos); + mbegin = font->getDimension(s.c_str()) + .Width; // deal with kerning mbegin += font->getKerningWidth( - &((*txtLine)[realmbgn - startPos]), - realmbgn - startPos > 0 ? &((*txtLine)[realmbgn - startPos - 1]) : 0); + &((*txtLine)[realmbgn - + startPos]), + realmbgn - startPos > 0 + ? &((*txtLine)[realmbgn - + startPos - + 1]) + : 0); lineStartPos = realmbgn - startPos; } - if (i == hlineStart + hlineCount - 1) - { + if (i == hlineStart + hlineCount - 1) { // highlight end is on this line - s2 = txtLine->subString(0, realmend - startPos); - mend = font->getDimension(s2.c_str()).Width; + s2 = txtLine->subString( + 0, realmend - startPos); + mend = font->getDimension(s2.c_str()) + .Width; lineEndPos = (s32)s2.size(); - } - else - mend = font->getDimension(txtLine->c_str()).Width; + } else + mend = font->getDimension(txtLine->c_str()) + .Width; CurrentTextRect.UpperLeftCorner.X += mbegin; - CurrentTextRect.LowerRightCorner.X = CurrentTextRect.UpperLeftCorner.X + mend - mbegin; + CurrentTextRect.LowerRightCorner.X = + CurrentTextRect.UpperLeftCorner + .X + + mend - mbegin; // draw mark - skin->draw2DRectangle(this, skin->getColor(EGDC_HIGH_LIGHT), CurrentTextRect, &localClipRect); + skin->draw2DRectangle(this, + skin->getColor(EGDC_HIGH_LIGHT), + CurrentTextRect, &localClipRect); // draw marked text - s = txtLine->subString(lineStartPos, lineEndPos - lineStartPos); + s = txtLine->subString(lineStartPos, + lineEndPos - lineStartPos); if (!s.empty()) font->draw(s.c_str(), CurrentTextRect, - OverrideColorEnabled ? OverrideColor : skin->getColor(EGDC_HIGH_LIGHT_TEXT), - false, true, &localClipRect); - + OverrideColorEnabled + ? OverrideColor + : skin->getColor(EGDC_HIGH_LIGHT_TEXT), + false, true, + &localClipRect); } } @@ -927,24 +903,31 @@ void intlGUIEditBox::draw() // draw cursor - if (WordWrap || MultiLine) - { + if (WordWrap || MultiLine) { cursorLine = getLineFromPos(CursorPos); txtLine = &BrokenText[cursorLine]; startPos = BrokenTextPositions[cursorLine]; } - s = txtLine->subString(0,CursorPos-startPos); + s = txtLine->subString(0, CursorPos - startPos); charcursorpos = font->getDimension(s.c_str()).Width + - font->getKerningWidth(L"_", CursorPos-startPos > 0 ? &((*txtLine)[CursorPos-startPos-1]) : 0); + font->getKerningWidth(L"_", + CursorPos - startPos > 0 + ? &((*txtLine)[CursorPos - + startPos - + 1]) + : 0); - if (m_writable) { - if (focus && (porting::getTimeMs() - BlinkStartTime) % 700 < 350) { + if (m_writable) { + if (focus && (porting::getTimeMs() - BlinkStartTime) % 700 < + 350) { setTextRect(cursorLine); CurrentTextRect.UpperLeftCorner.X += charcursorpos; font->draw(L"_", CurrentTextRect, - OverrideColorEnabled ? OverrideColor : skin->getColor(EGDC_BUTTON_TEXT), - false, true, &localClipRect); + OverrideColorEnabled + ? OverrideColor + : skin->getColor(EGDC_BUTTON_TEXT), + false, true, &localClipRect); } } } @@ -953,9 +936,8 @@ void intlGUIEditBox::draw() IGUIElement::draw(); } - //! Sets the new caption of this element. -void intlGUIEditBox::setText(const wchar_t* text) +void intlGUIEditBox::setText(const wchar_t *text) { Text = text; if (u32(CursorPos) > Text.size()) @@ -964,7 +946,6 @@ void intlGUIEditBox::setText(const wchar_t* text) breakText(); } - //! Enables or disables automatic scrolling with cursor position //! \param enable: If set to true, the text will move around with the cursor position void intlGUIEditBox::setAutoScroll(bool enable) @@ -972,7 +953,6 @@ void intlGUIEditBox::setAutoScroll(bool enable) AutoScroll = enable; } - //! Checks to see if automatic scrolling is enabled //! \return true if automatic scrolling is enabled, false if not bool intlGUIEditBox::isAutoScrollEnabled() const @@ -980,7 +960,6 @@ bool intlGUIEditBox::isAutoScrollEnabled() const return AutoScroll; } - //! Gets the area of the text in the edit box //! \return Returns the size in pixels of the text core::dimension2du intlGUIEditBox::getTextDimension() @@ -990,8 +969,7 @@ core::dimension2du intlGUIEditBox::getTextDimension() setTextRect(0); ret = CurrentTextRect; - for (u32 i=1; i < BrokenText.size(); ++i) - { + for (u32 i = 1; i < BrokenText.size(); ++i) { setTextRect(i); ret.addInternalPoint(CurrentTextRect.UpperLeftCorner); ret.addInternalPoint(CurrentTextRect.LowerRightCorner); @@ -1000,7 +978,6 @@ core::dimension2du intlGUIEditBox::getTextDimension() return core::dimension2du(ret.getSize()); } - //! Sets the maximum amount of characters which may be entered in the box. //! \param max: Maximum amount of characters. If 0, the character amount is //! infinity. @@ -1012,60 +989,48 @@ void intlGUIEditBox::setMax(u32 max) Text = Text.subString(0, Max); } - //! Returns maximum amount of characters, previously set by setMax(); u32 intlGUIEditBox::getMax() const { return Max; } - -bool intlGUIEditBox::processMouse(const SEvent& event) +bool intlGUIEditBox::processMouse(const SEvent &event) { - switch(event.MouseInput.Event) - { + switch (event.MouseInput.Event) { case irr::EMIE_LMOUSE_LEFT_UP: - if (Environment->hasFocus(this)) - { + if (Environment->hasFocus(this)) { CursorPos = getCursorPos(event.MouseInput.X, event.MouseInput.Y); - if (MouseMarking) - { - setTextMarkers( MarkBegin, CursorPos ); + if (MouseMarking) { + setTextMarkers(MarkBegin, CursorPos); } MouseMarking = false; calculateScrollPos(); return true; } break; - case irr::EMIE_MOUSE_MOVED: - { - if (MouseMarking) - { - CursorPos = getCursorPos(event.MouseInput.X, event.MouseInput.Y); - setTextMarkers( MarkBegin, CursorPos ); - calculateScrollPos(); - return true; - } - } - break; - case EMIE_LMOUSE_PRESSED_DOWN: - if (!Environment->hasFocus(this)) - { - BlinkStartTime = porting::getTimeMs(); - MouseMarking = true; + case irr::EMIE_MOUSE_MOVED: { + if (MouseMarking) { CursorPos = getCursorPos(event.MouseInput.X, event.MouseInput.Y); - setTextMarkers(CursorPos, CursorPos ); + setTextMarkers(MarkBegin, CursorPos); calculateScrollPos(); return true; } - else - { - if (!AbsoluteClippingRect.isPointInside( - core::position2d(event.MouseInput.X, event.MouseInput.Y))) { + } break; + case EMIE_LMOUSE_PRESSED_DOWN: + if (!Environment->hasFocus(this)) { + BlinkStartTime = porting::getTimeMs(); + MouseMarking = true; + CursorPos = getCursorPos(event.MouseInput.X, event.MouseInput.Y); + setTextMarkers(CursorPos, CursorPos); + calculateScrollPos(); + return true; + } else { + if (!AbsoluteClippingRect.isPointInside(core::position2d( + event.MouseInput.X, event.MouseInput.Y))) { return false; } - // move cursor CursorPos = getCursorPos(event.MouseInput.X, event.MouseInput.Y); @@ -1074,7 +1039,7 @@ bool intlGUIEditBox::processMouse(const SEvent& event) newMarkBegin = CursorPos; MouseMarking = true; - setTextMarkers( newMarkBegin, CursorPos); + setTextMarkers(newMarkBegin, CursorPos); calculateScrollPos(); return true; } @@ -1093,11 +1058,10 @@ bool intlGUIEditBox::processMouse(const SEvent& event) return false; } - s32 intlGUIEditBox::getCursorPos(s32 x, s32 y) { - IGUIFont* font = OverrideFont; - IGUISkin* skin = Environment->getSkin(); + IGUIFont *font = OverrideFont; + IGUISkin *skin = Environment->getSkin(); if (!OverrideFont) font = skin->getFont(); @@ -1112,14 +1076,19 @@ s32 intlGUIEditBox::getCursorPos(s32 x, s32 y) setTextRect(curr_line_idx); if (curr_line_idx == 0 && y < CurrentTextRect.UpperLeftCorner.Y) y = CurrentTextRect.UpperLeftCorner.Y; - if (curr_line_idx == lineCount - 1 && y > CurrentTextRect.LowerRightCorner.Y) + if (curr_line_idx == lineCount - 1 && + y > CurrentTextRect.LowerRightCorner.Y) y = CurrentTextRect.LowerRightCorner.Y; // is it inside this region? - if (y >= CurrentTextRect.UpperLeftCorner.Y && y <= CurrentTextRect.LowerRightCorner.Y) { + if (y >= CurrentTextRect.UpperLeftCorner.Y && + y <= CurrentTextRect.LowerRightCorner.Y) { // we've found the clicked line - txtLine = (WordWrap || MultiLine) ? &BrokenText[curr_line_idx] : &Text; - startPos = (WordWrap || MultiLine) ? BrokenTextPositions[curr_line_idx] : 0; + txtLine = (WordWrap || MultiLine) ? &BrokenText[curr_line_idx] + : &Text; + startPos = (WordWrap || MultiLine) + ? BrokenTextPositions[curr_line_idx] + : 0; break; } } @@ -1129,20 +1098,20 @@ s32 intlGUIEditBox::getCursorPos(s32 x, s32 y) else if (x > CurrentTextRect.LowerRightCorner.X) x = CurrentTextRect.LowerRightCorner.X; - s32 idx = font->getCharacterFromPos(txtLine->c_str(), x - CurrentTextRect.UpperLeftCorner.X); - // Special handling for last line, if we are on limits, add 1 extra shift because idx - // will be the last char, not null char of the wstring + s32 idx = font->getCharacterFromPos( + txtLine->c_str(), x - CurrentTextRect.UpperLeftCorner.X); + // Special handling for last line, if we are on limits, add 1 extra shift because + // idx will be the last char, not null char of the wstring if (curr_line_idx == lineCount - 1 && x == CurrentTextRect.LowerRightCorner.X) idx++; return rangelim(idx + startPos, 0, S32_MAX); } - //! Breaks the single text line. void intlGUIEditBox::breakText() { - IGUISkin* skin = Environment->getSkin(); + IGUISkin *skin = Environment->getSkin(); if ((!WordWrap && !MultiLine) || !skin) return; @@ -1150,7 +1119,7 @@ void intlGUIEditBox::breakText() BrokenText.clear(); // need to reallocate :/ BrokenTextPositions.set_used(0); - IGUIFont* font = OverrideFont; + IGUIFont *font = OverrideFont; if (!OverrideFont) font = skin->getFont(); @@ -1168,8 +1137,7 @@ void intlGUIEditBox::breakText() s32 elWidth = RelativeRect.getWidth() - 6; wchar_t c; - for (s32 i=0; igetDimension(whitespace.c_str()).Width; + s32 whitelgth = font->getDimension(whitespace.c_str()) + .Width; s32 worldlgth = font->getDimension(word.c_str()).Width; - if (WordWrap && length + worldlgth + whitelgth > elWidth) - { + if (WordWrap && length + worldlgth + whitelgth > + elWidth) { // break to next line length = worldlgth; BrokenText.push_back(line); BrokenTextPositions.push_back(lastLineStart); lastLineStart = i - (s32)word.size(); line = word; - } - else - { + } else { // add word to line line += whitespace; line += word; @@ -1225,21 +1190,18 @@ void intlGUIEditBox::breakText() whitespace += c; // compute line break - if (lineBreak) - { + if (lineBreak) { line += whitespace; line += word; BrokenText.push_back(line); BrokenTextPositions.push_back(lastLineStart); - lastLineStart = i+1; + lastLineStart = i + 1; line = L""; word = L""; whitespace = L""; length = 0; } - } - else - { + } else { // yippee this is a word.. word += c; } @@ -1251,40 +1213,37 @@ void intlGUIEditBox::breakText() BrokenTextPositions.push_back(lastLineStart); } - void intlGUIEditBox::setTextRect(s32 line) { core::dimension2du d; - IGUISkin* skin = Environment->getSkin(); + IGUISkin *skin = Environment->getSkin(); if (!skin) return; - IGUIFont* font = OverrideFont ? OverrideFont : skin->getFont(); + IGUIFont *font = OverrideFont ? OverrideFont : skin->getFont(); if (!font) return; // get text dimension const u32 lineCount = (WordWrap || MultiLine) ? BrokenText.size() : 1; - if (WordWrap || MultiLine) - { + if (WordWrap || MultiLine) { d = font->getDimension(BrokenText[line].c_str()); - } - else - { + } else { d = font->getDimension(Text.c_str()); d.Height = AbsoluteRect.getHeight(); } d.Height += font->getKerningHeight(); // justification - switch (HAlign) - { + switch (HAlign) { case EGUIA_CENTER: // align to h centre - CurrentTextRect.UpperLeftCorner.X = (FrameRect.getWidth()/2) - (d.Width/2); - CurrentTextRect.LowerRightCorner.X = (FrameRect.getWidth()/2) + (d.Width/2); + CurrentTextRect.UpperLeftCorner.X = + (FrameRect.getWidth() / 2) - (d.Width / 2); + CurrentTextRect.LowerRightCorner.X = + (FrameRect.getWidth() / 2) + (d.Width / 2); break; case EGUIA_LOWERRIGHT: // align to right edge @@ -1295,82 +1254,77 @@ void intlGUIEditBox::setTextRect(s32 line) // align to left edge CurrentTextRect.UpperLeftCorner.X = 0; CurrentTextRect.LowerRightCorner.X = d.Width; - } - switch (VAlign) - { + switch (VAlign) { case EGUIA_CENTER: // align to v centre - CurrentTextRect.UpperLeftCorner.Y = - (FrameRect.getHeight()/2) - (lineCount*d.Height)/2 + d.Height*line; + CurrentTextRect.UpperLeftCorner.Y = (FrameRect.getHeight() / 2) - + (lineCount * d.Height) / 2 + + d.Height * line; break; case EGUIA_LOWERRIGHT: // align to bottom edge - CurrentTextRect.UpperLeftCorner.Y = - FrameRect.getHeight() - lineCount*d.Height + d.Height*line; + CurrentTextRect.UpperLeftCorner.Y = FrameRect.getHeight() - + lineCount * d.Height + + d.Height * line; break; default: // align to top edge - CurrentTextRect.UpperLeftCorner.Y = d.Height*line; + CurrentTextRect.UpperLeftCorner.Y = d.Height * line; break; } - CurrentTextRect.UpperLeftCorner.X -= HScrollPos; + CurrentTextRect.UpperLeftCorner.X -= HScrollPos; CurrentTextRect.LowerRightCorner.X -= HScrollPos; - CurrentTextRect.UpperLeftCorner.Y -= VScrollPos; + CurrentTextRect.UpperLeftCorner.Y -= VScrollPos; CurrentTextRect.LowerRightCorner.Y = CurrentTextRect.UpperLeftCorner.Y + d.Height; CurrentTextRect += FrameRect.UpperLeftCorner; - } - s32 intlGUIEditBox::getLineFromPos(s32 pos) { if (!WordWrap && !MultiLine) return 0; - s32 i=0; - while (i < (s32)BrokenTextPositions.size()) - { + s32 i = 0; + while (i < (s32)BrokenTextPositions.size()) { if (BrokenTextPositions[i] > pos) - return i-1; + return i - 1; ++i; } return (s32)BrokenTextPositions.size() - 1; } - void intlGUIEditBox::inputChar(wchar_t c) { if (!IsEnabled || !m_writable) return; - if (c != 0) - { - if (Text.size() < Max || Max == 0) - { + if (c != 0) { + if (Text.size() < Max || Max == 0) { core::stringw s; - if (MarkBegin != MarkEnd) - { + if (MarkBegin != MarkEnd) { // replace marked text - const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd; - const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin; + const s32 realmbgn = + MarkBegin < MarkEnd ? MarkBegin : MarkEnd; + const s32 realmend = + MarkBegin < MarkEnd ? MarkEnd : MarkBegin; s = Text.subString(0, realmbgn); s.append(c); - s.append( Text.subString(realmend, Text.size()-realmend) ); + s.append(Text.subString( + realmend, Text.size() - realmend)); Text = s; - CursorPos = realmbgn+1; - } - else - { + CursorPos = realmbgn + 1; + } else { // add new character s = Text.subString(0, CursorPos); s.append(c); - s.append( Text.subString(CursorPos, Text.size()-CursorPos) ); + s.append(Text.subString( + CursorPos, Text.size() - CursorPos)); Text = s; ++CursorPos; } @@ -1384,7 +1338,6 @@ void intlGUIEditBox::inputChar(wchar_t c) calculateScrollPos(); } - void intlGUIEditBox::calculateScrollPos() { if (!AutoScroll) @@ -1395,21 +1348,22 @@ void intlGUIEditBox::calculateScrollPos() setTextRect(cursLine); // don't do horizontal scrolling when wordwrap is enabled. - if (!WordWrap) - { + if (!WordWrap) { // get cursor position - IGUISkin* skin = Environment->getSkin(); + IGUISkin *skin = Environment->getSkin(); if (!skin) return; - IGUIFont* font = OverrideFont ? OverrideFont : skin->getFont(); + IGUIFont *font = OverrideFont ? OverrideFont : skin->getFont(); if (!font) return; core::stringw *txtLine = MultiLine ? &BrokenText[cursLine] : &Text; - s32 cPos = MultiLine ? CursorPos - BrokenTextPositions[cursLine] : CursorPos; + s32 cPos = MultiLine ? CursorPos - BrokenTextPositions[cursLine] + : CursorPos; s32 cStart = CurrentTextRect.UpperLeftCorner.X + HScrollPos + - font->getDimension(txtLine->subString(0, cPos).c_str()).Width; + font->getDimension(txtLine->subString(0, cPos).c_str()) + .Width; s32 cEnd = cStart + font->getDimension(L"_ ").Width; @@ -1428,9 +1382,11 @@ void intlGUIEditBox::calculateScrollPos() // vertical scroll position if (FrameRect.LowerRightCorner.Y < CurrentTextRect.LowerRightCorner.Y) - VScrollPos += CurrentTextRect.LowerRightCorner.Y - FrameRect.LowerRightCorner.Y; // scrolling downwards + VScrollPos += CurrentTextRect.LowerRightCorner.Y - + FrameRect.LowerRightCorner.Y; // scrolling downwards else if (FrameRect.UpperLeftCorner.Y > CurrentTextRect.UpperLeftCorner.Y) - VScrollPos += CurrentTextRect.UpperLeftCorner.Y - FrameRect.UpperLeftCorner.Y; // scrolling upwards + VScrollPos += CurrentTextRect.UpperLeftCorner.Y - + FrameRect.UpperLeftCorner.Y; // scrolling upwards // todo: adjust scrollbar if (m_vscrollbar) @@ -1440,26 +1396,24 @@ void intlGUIEditBox::calculateScrollPos() //! set text markers void intlGUIEditBox::setTextMarkers(s32 begin, s32 end) { - if ( begin != MarkBegin || end != MarkEnd ) - { - MarkBegin = begin; - MarkEnd = end; - sendGuiEvent(EGET_EDITBOX_MARKING_CHANGED); - } + if (begin != MarkBegin || end != MarkEnd) { + MarkBegin = begin; + MarkEnd = end; + sendGuiEvent(EGET_EDITBOX_MARKING_CHANGED); + } } //! send some gui event to parent void intlGUIEditBox::sendGuiEvent(EGUI_EVENT_TYPE type) { - if ( Parent ) - { - SEvent e; - e.EventType = EET_GUI_EVENT; - e.GUIEvent.Caller = this; - e.GUIEvent.Element = 0; - e.GUIEvent.EventType = type; + if (Parent) { + SEvent e; + e.EventType = EET_GUI_EVENT; + e.GUIEvent.Caller = this; + e.GUIEvent.Element = 0; + e.GUIEvent.EventType = type; - Parent->OnEvent(e); + Parent->OnEvent(e); } } @@ -1471,8 +1425,8 @@ void intlGUIEditBox::createVScrollBar() if (OverrideFont) { fontHeight = OverrideFont->getDimension(L"").Height; } else { - if (IGUISkin* skin = Environment->getSkin()) { - if (IGUIFont* font = skin->getFont()) { + if (IGUISkin *skin = Environment->getSkin()) { + if (IGUIFont *font = skin->getFont()) { fontHeight = font->getDimension(L"").Height; } } @@ -1482,8 +1436,8 @@ void intlGUIEditBox::createVScrollBar() irr::core::rect scrollbarrect = FrameRect; scrollbarrect.UpperLeftCorner.X += FrameRect.getWidth() - m_scrollbar_width; - m_vscrollbar = new GUIScrollBar(Environment, getParent(), -1, - scrollbarrect, false, true); + m_vscrollbar = new GUIScrollBar( + Environment, getParent(), -1, scrollbarrect, false, true); m_vscrollbar->setVisible(false); m_vscrollbar->setSmallStep(3 * fontHeight); @@ -1515,7 +1469,7 @@ void intlGUIEditBox::updateVScrollBar() } // check if a vertical scrollbar is needed ? - if (getTextDimension().Height > (u32) FrameRect.getHeight()) { + if (getTextDimension().Height > (u32)FrameRect.getHeight()) { s32 scrollymax = getTextDimension().Height - FrameRect.getHeight(); if (scrollymax != m_vscrollbar->getMax()) { m_vscrollbar->setMax(scrollymax); @@ -1546,33 +1500,34 @@ void intlGUIEditBox::setWritable(bool can_write_text) } //! Writes attributes of the element. -void intlGUIEditBox::serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options=0) const +void intlGUIEditBox::serializeAttributes( + io::IAttributes *out, io::SAttributeReadWriteOptions *options = 0) const { // IGUIEditBox::serializeAttributes(out,options); - out->addBool ("OverrideColorEnabled",OverrideColorEnabled ); - out->addColor ("OverrideColor", OverrideColor); + out->addBool("OverrideColorEnabled", OverrideColorEnabled); + out->addColor("OverrideColor", OverrideColor); // out->addFont("OverrideFont",OverrideFont); - out->addInt ("MaxChars", Max); - out->addBool ("WordWrap", WordWrap); - out->addBool ("MultiLine", MultiLine); - out->addBool ("AutoScroll", AutoScroll); - out->addBool ("PasswordBox", PasswordBox); + out->addInt("MaxChars", Max); + out->addBool("WordWrap", WordWrap); + out->addBool("MultiLine", MultiLine); + out->addBool("AutoScroll", AutoScroll); + out->addBool("PasswordBox", PasswordBox); core::stringw ch = L" "; ch[0] = PasswordChar; - out->addString("PasswordChar", ch.c_str()); - out->addEnum ("HTextAlign", HAlign, GUIAlignmentNames); - out->addEnum ("VTextAlign", VAlign, GUIAlignmentNames); - out->addBool ("Writable", m_writable); + out->addString("PasswordChar", ch.c_str()); + out->addEnum("HTextAlign", HAlign, GUIAlignmentNames); + out->addEnum("VTextAlign", VAlign, GUIAlignmentNames); + out->addBool("Writable", m_writable); - IGUIEditBox::serializeAttributes(out,options); + IGUIEditBox::serializeAttributes(out, options); } - //! Reads attributes of the element -void intlGUIEditBox::deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options=0) +void intlGUIEditBox::deserializeAttributes( + io::IAttributes *in, io::SAttributeReadWriteOptions *options = 0) { - IGUIEditBox::deserializeAttributes(in,options); + IGUIEditBox::deserializeAttributes(in, options); setOverrideColor(in->getAttributeAsColor("OverrideColor")); enableOverrideColor(in->getAttributeAsBool("OverrideColorEnabled")); @@ -1587,13 +1542,14 @@ void intlGUIEditBox::deserializeAttributes(io::IAttributes* in, io::SAttributeRe else setPasswordBox(in->getAttributeAsBool("PasswordBox"), ch[0]); - setTextAlignment( (EGUI_ALIGNMENT) in->getAttributeAsEnumeration("HTextAlign", GUIAlignmentNames), - (EGUI_ALIGNMENT) in->getAttributeAsEnumeration("VTextAlign", GUIAlignmentNames)); + setTextAlignment((EGUI_ALIGNMENT)in->getAttributeAsEnumeration( + "HTextAlign", GUIAlignmentNames), + (EGUI_ALIGNMENT)in->getAttributeAsEnumeration( + "VTextAlign", GUIAlignmentNames)); setWritable(in->getAttributeAsBool("Writable")); // setOverrideFont(in->getAttributeAsFont("OverrideFont")); } - } // end namespace gui } // end namespace irr diff --git a/src/gui/intlGUIEditBox.h b/src/gui/intlGUIEditBox.h index 9d643495e..7b5f015fd 100644 --- a/src/gui/intlGUIEditBox.h +++ b/src/gui/intlGUIEditBox.h @@ -16,193 +16,193 @@ namespace irr { namespace gui { - class intlGUIEditBox : public IGUIEditBox - { - public: - - //! constructor - intlGUIEditBox(const wchar_t* text, bool border, IGUIEnvironment* environment, - IGUIElement* parent, s32 id, const core::rect& rectangle, +class intlGUIEditBox : public IGUIEditBox +{ +public: + //! constructor + intlGUIEditBox(const wchar_t *text, bool border, IGUIEnvironment *environment, + IGUIElement *parent, s32 id, const core::rect &rectangle, bool writable = true, bool has_vscrollbar = false); - //! destructor - virtual ~intlGUIEditBox(); + //! destructor + virtual ~intlGUIEditBox(); - //! Sets another skin independent font. - virtual void setOverrideFont(IGUIFont* font=0); + //! Sets another skin independent font. + virtual void setOverrideFont(IGUIFont *font = 0); - //! Gets the override font (if any) - /** \return The override font (may be 0) */ - virtual IGUIFont* getOverrideFont() const; + //! Gets the override font (if any) + /** \return The override font (may be 0) */ + virtual IGUIFont *getOverrideFont() const; - //! Get the font which is used right now for drawing - /** Currently this is the override font when one is set and the - font of the active skin otherwise */ - virtual IGUIFont* getActiveFont() const; + //! Get the font which is used right now for drawing + /** Currently this is the override font when one is set and the + font of the active skin otherwise */ + virtual IGUIFont *getActiveFont() const; - //! Sets another color for the text. - virtual void setOverrideColor(video::SColor color); + //! Sets another color for the text. + virtual void setOverrideColor(video::SColor color); - //! Gets the override color - virtual video::SColor getOverrideColor() const; + //! Gets the override color + virtual video::SColor getOverrideColor() const; - //! Sets if the text should use the overide color or the - //! color in the gui skin. - virtual void enableOverrideColor(bool enable); + //! Sets if the text should use the overide color or the + //! color in the gui skin. + virtual void enableOverrideColor(bool enable); - //! Checks if an override color is enabled - /** \return true if the override color is enabled, false otherwise */ - virtual bool isOverrideColorEnabled(void) const; + //! Checks if an override color is enabled + /** \return true if the override color is enabled, false otherwise */ + virtual bool isOverrideColorEnabled(void) const; - //! Sets whether to draw the background - virtual void setDrawBackground(bool draw); + //! Sets whether to draw the background + virtual void setDrawBackground(bool draw); - virtual bool isDrawBackgroundEnabled() const { return true; } + virtual bool isDrawBackgroundEnabled() const { return true; } - //! Turns the border on or off - virtual void setDrawBorder(bool border); + //! Turns the border on or off + virtual void setDrawBorder(bool border); - virtual bool isDrawBorderEnabled() const { return Border; } + virtual bool isDrawBorderEnabled() const { return Border; } - //! Enables or disables word wrap for using the edit box as multiline text editor. - virtual void setWordWrap(bool enable); + //! Enables or disables word wrap for using the edit box as multiline text editor. + virtual void setWordWrap(bool enable); - //! Checks if word wrap is enabled - //! \return true if word wrap is enabled, false otherwise - virtual bool isWordWrapEnabled() const; + //! Checks if word wrap is enabled + //! \return true if word wrap is enabled, false otherwise + virtual bool isWordWrapEnabled() const; - //! Enables or disables newlines. - /** \param enable: If set to true, the EGET_EDITBOX_ENTER event will not be fired, - instead a newline character will be inserted. */ - virtual void setMultiLine(bool enable); + //! Enables or disables newlines. + /** \param enable: If set to true, the EGET_EDITBOX_ENTER event will not be fired, + instead a newline character will be inserted. */ + virtual void setMultiLine(bool enable); - //! Checks if multi line editing is enabled - //! \return true if mult-line is enabled, false otherwise - virtual bool isMultiLineEnabled() const; + //! Checks if multi line editing is enabled + //! \return true if mult-line is enabled, false otherwise + virtual bool isMultiLineEnabled() const; - //! Enables or disables automatic scrolling with cursor position - //! \param enable: If set to true, the text will move around with the cursor position - virtual void setAutoScroll(bool enable); + //! Enables or disables automatic scrolling with cursor position + //! \param enable: If set to true, the text will move around with the cursor + //! position + virtual void setAutoScroll(bool enable); - //! Checks to see if automatic scrolling is enabled - //! \return true if automatic scrolling is enabled, false if not - virtual bool isAutoScrollEnabled() const; + //! Checks to see if automatic scrolling is enabled + //! \return true if automatic scrolling is enabled, false if not + virtual bool isAutoScrollEnabled() const; - //! Gets the size area of the text in the edit box - //! \return Returns the size in pixels of the text - virtual core::dimension2du getTextDimension(); + //! Gets the size area of the text in the edit box + //! \return Returns the size in pixels of the text + virtual core::dimension2du getTextDimension(); - //! Sets text justification - virtual void setTextAlignment(EGUI_ALIGNMENT horizontal, EGUI_ALIGNMENT vertical); + //! Sets text justification + virtual void setTextAlignment(EGUI_ALIGNMENT horizontal, EGUI_ALIGNMENT vertical); - //! called if an event happened. - virtual bool OnEvent(const SEvent& event); + //! called if an event happened. + virtual bool OnEvent(const SEvent &event); - //! draws the element and its children - virtual void draw(); + //! draws the element and its children + virtual void draw(); - //! Sets the new caption of this element. - virtual void setText(const wchar_t* text); + //! Sets the new caption of this element. + virtual void setText(const wchar_t *text); - //! Sets the maximum amount of characters which may be entered in the box. - //! \param max: Maximum amount of characters. If 0, the character amount is - //! infinity. - virtual void setMax(u32 max); + //! Sets the maximum amount of characters which may be entered in the box. + //! \param max: Maximum amount of characters. If 0, the character amount is + //! infinity. + virtual void setMax(u32 max); - //! Returns maximum amount of characters, previously set by setMax(); - virtual u32 getMax() const; + //! Returns maximum amount of characters, previously set by setMax(); + virtual u32 getMax() const; - //! Sets whether the edit box is a password box. Setting this to true will - /** disable MultiLine, WordWrap and the ability to copy with ctrl+c or ctrl+x - \param passwordBox: true to enable password, false to disable - \param passwordChar: the character that is displayed instead of letters */ - virtual void setPasswordBox(bool passwordBox, wchar_t passwordChar = L'*'); + //! Sets whether the edit box is a password box. Setting this to true will + /** disable MultiLine, WordWrap and the ability to copy with ctrl+c or ctrl+x + \param passwordBox: true to enable password, false to disable + \param passwordChar: the character that is displayed instead of letters */ + virtual void setPasswordBox(bool passwordBox, wchar_t passwordChar = L'*'); - //! Returns true if the edit box is currently a password box. - virtual bool isPasswordBox() const; + //! Returns true if the edit box is currently a password box. + virtual bool isPasswordBox() const; - //! Updates the absolute position, splits text if required - virtual void updateAbsolutePosition(); + //! Updates the absolute position, splits text if required + virtual void updateAbsolutePosition(); - //! set true if this EditBox is writable - virtual void setWritable(bool can_write_text); + //! set true if this EditBox is writable + virtual void setWritable(bool can_write_text); - //! Writes attributes of the element. - virtual void serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options) const; + //! Writes attributes of the element. + virtual void serializeAttributes(io::IAttributes *out, + io::SAttributeReadWriteOptions *options) const; - //! Reads attributes of the element - virtual void deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options); + //! Reads attributes of the element + virtual void deserializeAttributes( + io::IAttributes *in, io::SAttributeReadWriteOptions *options); - virtual void setCursorChar(const wchar_t cursorChar) {} + virtual void setCursorChar(const wchar_t cursorChar) {} - virtual wchar_t getCursorChar() const { return L'|'; } + virtual wchar_t getCursorChar() const { return L'|'; } - virtual void setCursorBlinkTime(u32 timeMs) {} + virtual void setCursorBlinkTime(u32 timeMs) {} - virtual u32 getCursorBlinkTime() const { return 500; } + virtual u32 getCursorBlinkTime() const { return 500; } - protected: - //! Breaks the single text line. - void breakText(); - //! sets the area of the given line - void setTextRect(s32 line); - //! returns the line number that the cursor is on - s32 getLineFromPos(s32 pos); - //! adds a letter to the edit box - void inputChar(wchar_t c); - //! calculates the current scroll position - void calculateScrollPos(); - //! send some gui event to parent - void sendGuiEvent(EGUI_EVENT_TYPE type); - //! set text markers - void setTextMarkers(s32 begin, s32 end); +protected: + //! Breaks the single text line. + void breakText(); + //! sets the area of the given line + void setTextRect(s32 line); + //! returns the line number that the cursor is on + s32 getLineFromPos(s32 pos); + //! adds a letter to the edit box + void inputChar(wchar_t c); + //! calculates the current scroll position + void calculateScrollPos(); + //! send some gui event to parent + void sendGuiEvent(EGUI_EVENT_TYPE type); + //! set text markers + void setTextMarkers(s32 begin, s32 end); - bool processKey(const SEvent& event); - bool processMouse(const SEvent& event); - s32 getCursorPos(s32 x, s32 y); + bool processKey(const SEvent &event); + bool processMouse(const SEvent &event); + s32 getCursorPos(s32 x, s32 y); - //! Create a vertical scrollbar - void createVScrollBar(); + //! Create a vertical scrollbar + void createVScrollBar(); - //! Update the vertical scrollbar (visibilty & scroll position) - void updateVScrollBar(); + //! Update the vertical scrollbar (visibilty & scroll position) + void updateVScrollBar(); - bool MouseMarking = false; - bool Border; - bool OverrideColorEnabled = false; - s32 MarkBegin = 0; - s32 MarkEnd = 0; + bool MouseMarking = false; + bool Border; + bool OverrideColorEnabled = false; + s32 MarkBegin = 0; + s32 MarkEnd = 0; - video::SColor OverrideColor = video::SColor(101,255,255,255); - gui::IGUIFont *OverrideFont = nullptr; - gui::IGUIFont *LastBreakFont = nullptr; - IOSOperator *Operator = nullptr; + video::SColor OverrideColor = video::SColor(101, 255, 255, 255); + gui::IGUIFont *OverrideFont = nullptr; + gui::IGUIFont *LastBreakFont = nullptr; + IOSOperator *Operator = nullptr; - u64 BlinkStartTime = 0; - s32 CursorPos = 0; - s32 HScrollPos = 0; - s32 VScrollPos = 0; // scroll position in characters - u32 Max = 0; + u64 BlinkStartTime = 0; + s32 CursorPos = 0; + s32 HScrollPos = 0; + s32 VScrollPos = 0; // scroll position in characters + u32 Max = 0; - bool WordWrap = false; - bool MultiLine = false; - bool AutoScroll = true; - bool PasswordBox = false; - wchar_t PasswordChar = L'*'; - EGUI_ALIGNMENT HAlign = EGUIA_UPPERLEFT; - EGUI_ALIGNMENT VAlign = EGUIA_CENTER; + bool WordWrap = false; + bool MultiLine = false; + bool AutoScroll = true; + bool PasswordBox = false; + wchar_t PasswordChar = L'*'; + EGUI_ALIGNMENT HAlign = EGUIA_UPPERLEFT; + EGUI_ALIGNMENT VAlign = EGUIA_CENTER; - core::array BrokenText; - core::array BrokenTextPositions; - - core::rect CurrentTextRect = core::rect(0,0,1,1); - core::rect FrameRect; // temporary values - u32 m_scrollbar_width; - GUIScrollBar *m_vscrollbar; - bool m_writable; - - }; + core::array BrokenText; + core::array BrokenTextPositions; + core::rect CurrentTextRect = core::rect(0, 0, 1, 1); + core::rect FrameRect; // temporary values + u32 m_scrollbar_width; + GUIScrollBar *m_vscrollbar; + bool m_writable; +}; } // end namespace gui } // end namespace irr diff --git a/src/gui/mainmenumanager.h b/src/gui/mainmenumanager.h index 102492255..e2107ab64 100644 --- a/src/gui/mainmenumanager.h +++ b/src/gui/mainmenumanager.h @@ -54,7 +54,7 @@ public: } #endif - if(!m_stack.empty()) + if (!m_stack.empty()) m_stack.back()->setVisible(false); m_stack.push_back(menu); } @@ -68,35 +68,32 @@ public: assert(*i == menu); m_stack.erase(i);*/ - if(!m_stack.empty()) + if (!m_stack.empty()) m_stack.back()->setVisible(true); } // Returns true to prevent further processing - virtual bool preprocessEvent(const SEvent& event) + virtual bool preprocessEvent(const SEvent &event) { if (m_stack.empty()) return false; - GUIModalMenu *mm = dynamic_cast(m_stack.back()); + GUIModalMenu *mm = dynamic_cast(m_stack.back()); return mm && mm->preprocessEvent(event); } - u32 menuCount() - { - return m_stack.size(); - } + u32 menuCount() { return m_stack.size(); } bool pausesGame() { for (gui::IGUIElement *i : m_stack) { - GUIModalMenu *mm = dynamic_cast(i); + GUIModalMenu *mm = dynamic_cast(i); if (mm && mm->pausesGame()) return true; } return false; } - std::list m_stack; + std::list m_stack; }; extern MainMenuManager g_menumgr; @@ -109,36 +106,17 @@ public: MainGameCallback() = default; virtual ~MainGameCallback() = default; - virtual void exitToOS() - { - shutdown_requested = true; - } + virtual void exitToOS() { shutdown_requested = true; } - virtual void disconnect() - { - disconnect_requested = true; - } + virtual void disconnect() { disconnect_requested = true; } - virtual void changePassword() - { - changepassword_requested = true; - } + virtual void changePassword() { changepassword_requested = true; } - virtual void changeVolume() - { - changevolume_requested = true; - } + virtual void changeVolume() { changevolume_requested = true; } - virtual void keyConfig() - { - keyconfig_requested = true; - } - - virtual void signalKeyConfigChange() - { - keyconfig_changed = true; - } + virtual void keyConfig() { keyconfig_requested = true; } + virtual void signalKeyConfigChange() { keyconfig_changed = true; } bool disconnect_requested = false; bool changepassword_requested = false; diff --git a/src/gui/modalMenu.h b/src/gui/modalMenu.h index 1cb687f82..3fc9124ea 100644 --- a/src/gui/modalMenu.h +++ b/src/gui/modalMenu.h @@ -38,8 +38,8 @@ public: class GUIModalMenu : public gui::IGUIElement { public: - GUIModalMenu(gui::IGUIEnvironment* env, gui::IGUIElement* parent, s32 id, - IMenuManager *menumgr, bool remap_dbl_click = true); + GUIModalMenu(gui::IGUIEnvironment *env, gui::IGUIElement *parent, s32 id, + IMenuManager *menumgr, bool remap_dbl_click = true); virtual ~GUIModalMenu(); void allowFocusRemoval(bool allow); @@ -70,7 +70,7 @@ protected: bool DoubleClickDetection(const SEvent &event); v2s32 m_pointer; - v2s32 m_old_pointer; // Mouse position after previous mouse event + v2s32 m_old_pointer; // Mouse position after previous mouse event v2u32 m_screensize_old; float m_gui_scale; #ifdef __ANDROID__ @@ -92,7 +92,7 @@ private: IMenuManager *m_menumgr; /* If true, remap a double-click (or double-tap) action to ESC. This is so * that, for example, Android users can double-tap to close a formspec. - * + * * This value can (currently) only be set by the class constructor * and the default value for the setting is true. */ diff --git a/src/gui/touchscreengui.cpp b/src/gui/touchscreengui.cpp index 0d64aa618..1f9da71c9 100644 --- a/src/gui/touchscreengui.cpp +++ b/src/gui/touchscreengui.cpp @@ -36,82 +36,75 @@ with this program; if not, write to the Free Software Foundation, Inc., using namespace irr::core; -const char **button_imagenames = (const char *[]) { - "jump_btn.png", - "down.png", - "zoom.png", - "aux_btn.png" -}; +const char **button_imagenames = + (const char *[]){"jump_btn.png", "down.png", "zoom.png", "aux_btn.png"}; -const char **joystick_imagenames = (const char *[]) { - "joystick_off.png", - "joystick_bg.png", - "joystick_center.png" -}; +const char **joystick_imagenames = (const char *[]){ + "joystick_off.png", "joystick_bg.png", "joystick_center.png"}; static irr::EKEY_CODE id2keycode(touch_gui_button_id id) { std::string key = ""; switch (id) { - case forward_id: - key = "forward"; - break; - case left_id: - key = "left"; - break; - case right_id: - key = "right"; - break; - case backward_id: - key = "backward"; - break; - case inventory_id: - key = "inventory"; - break; - case drop_id: - key = "drop"; - break; - case jump_id: - key = "jump"; - break; - case crunch_id: - key = "sneak"; - break; - case zoom_id: - key = "zoom"; - break; - case special1_id: - key = "special1"; - break; - case fly_id: - key = "freemove"; - break; - case noclip_id: - key = "noclip"; - break; - case fast_id: - key = "fastmove"; - break; - case debug_id: - key = "toggle_debug"; - break; - case toggle_chat_id: - key = "toggle_chat"; - break; - case minimap_id: - key = "minimap"; - break; - case chat_id: - key = "chat"; - break; - case camera_id: - key = "camera_mode"; - break; - case range_id: - key = "rangeselect"; - break; - default: - break; + case forward_id: + key = "forward"; + break; + case left_id: + key = "left"; + break; + case right_id: + key = "right"; + break; + case backward_id: + key = "backward"; + break; + case inventory_id: + key = "inventory"; + break; + case drop_id: + key = "drop"; + break; + case jump_id: + key = "jump"; + break; + case crunch_id: + key = "sneak"; + break; + case zoom_id: + key = "zoom"; + break; + case special1_id: + key = "special1"; + break; + case fly_id: + key = "freemove"; + break; + case noclip_id: + key = "noclip"; + break; + case fast_id: + key = "fastmove"; + break; + case debug_id: + key = "toggle_debug"; + break; + case toggle_chat_id: + key = "toggle_chat"; + break; + case minimap_id: + key = "minimap"; + break; + case chat_id: + key = "chat"; + break; + case camera_id: + key = "camera_mode"; + break; + case range_id: + key = "rangeselect"; + break; + default: + break; } assert(!key.empty()); return keyname_to_keycode(g_settings->get("keymap_" + key).c_str()); @@ -120,16 +113,18 @@ static irr::EKEY_CODE id2keycode(touch_gui_button_id id) TouchScreenGUI *g_touchscreengui; static void load_button_texture(button_info *btn, const char *path, - const rect &button_rect, ISimpleTextureSource *tsrc, video::IVideoDriver *driver) + const rect &button_rect, ISimpleTextureSource *tsrc, + video::IVideoDriver *driver) { unsigned int tid; - video::ITexture *texture = guiScalingImageButton(driver, - tsrc->getTexture(path, &tid), button_rect.getWidth(), - button_rect.getHeight()); + video::ITexture *texture = + guiScalingImageButton(driver, tsrc->getTexture(path, &tid), + button_rect.getWidth(), button_rect.getHeight()); if (texture) { btn->guibutton->setUseAlphaChannel(true); if (g_settings->getBool("gui_scaling_filter")) { - rect txr_rect = rect(0, 0, button_rect.getWidth(), button_rect.getHeight()); + rect txr_rect = rect(0, 0, button_rect.getWidth(), + button_rect.getHeight()); btn->guibutton->setImage(texture, txr_rect); btn->guibutton->setPressedImage(texture, txr_rect); btn->guibutton->setScaleImage(false); @@ -143,17 +138,15 @@ static void load_button_texture(button_info *btn, const char *path, } } -AutoHideButtonBar::AutoHideButtonBar(IrrlichtDevice *device, - IEventReceiver *receiver) : - m_driver(device->getVideoDriver()), - m_guienv(device->getGUIEnvironment()), - m_receiver(receiver) +AutoHideButtonBar::AutoHideButtonBar(IrrlichtDevice *device, IEventReceiver *receiver) : + m_driver(device->getVideoDriver()), m_guienv(device->getGUIEnvironment()), + m_receiver(receiver) { } -void AutoHideButtonBar::init(ISimpleTextureSource *tsrc, - const char *starter_img, int button_id, const v2s32 &UpperLeft, - const v2s32 &LowerRight, autohide_button_bar_dir dir, float timeout) +void AutoHideButtonBar::init(ISimpleTextureSource *tsrc, const char *starter_img, + int button_id, const v2s32 &UpperLeft, const v2s32 &LowerRight, + autohide_button_bar_dir dir, float timeout) { m_texturesource = tsrc; @@ -162,18 +155,19 @@ void AutoHideButtonBar::init(ISimpleTextureSource *tsrc, // init settings bar - irr::core::rect current_button = rect(UpperLeft.X, UpperLeft.Y, - LowerRight.X, LowerRight.Y); + irr::core::rect current_button = + rect(UpperLeft.X, UpperLeft.Y, LowerRight.X, LowerRight.Y); - m_starter.guibutton = m_guienv->addButton(current_button, nullptr, button_id, L"", nullptr); + m_starter.guibutton = m_guienv->addButton( + current_button, nullptr, button_id, L"", nullptr); m_starter.guibutton->grab(); - m_starter.repeatcounter = -1; - m_starter.keycode = KEY_OEM_8; // use invalid keycode as it's not relevant + m_starter.repeatcounter = -1; + m_starter.keycode = KEY_OEM_8; // use invalid keycode as it's not relevant m_starter.immediate_release = true; m_starter.ids.clear(); - load_button_texture(&m_starter, starter_img, current_button, - m_texturesource, m_driver); + load_button_texture(&m_starter, starter_img, current_button, m_texturesource, + m_driver); m_dir = dir; m_timeout_value = timeout; @@ -189,13 +183,13 @@ AutoHideButtonBar::~AutoHideButtonBar() } } -void AutoHideButtonBar::addButton(touch_gui_button_id button_id, - const wchar_t *caption, const char *btn_image) +void AutoHideButtonBar::addButton(touch_gui_button_id button_id, const wchar_t *caption, + const char *btn_image) { if (!m_initialized) { errorstream << "AutoHideButtonBar::addButton not yet initialized!" - << std::endl; + << std::endl; return; } int button_size = 0; @@ -212,55 +206,55 @@ void AutoHideButtonBar::addButton(touch_gui_button_id button_id, int x_end = 0; if (m_dir == AHBB_Dir_Left_Right) { - x_start = m_lower_right.X + (button_size * 1.25 * m_buttons.size()) - + (button_size * 0.25); + x_start = m_lower_right.X + + (button_size * 1.25 * m_buttons.size()) + + (button_size * 0.25); x_end = x_start + button_size; } else { - x_end = m_upper_left.X - (button_size * 1.25 * m_buttons.size()) - - (button_size * 0.25); + x_end = m_upper_left.X - (button_size * 1.25 * m_buttons.size()) - + (button_size * 0.25); x_start = x_end - button_size; } - current_button = rect(x_start, m_upper_left.Y, x_end, - m_lower_right.Y); + current_button = rect( + x_start, m_upper_left.Y, x_end, m_lower_right.Y); } else { double y_start = 0; double y_end = 0; if (m_dir == AHBB_Dir_Top_Bottom) { - y_start = m_lower_right.X + (button_size * 1.25 * m_buttons.size()) - + (button_size * 0.25); + y_start = m_lower_right.X + + (button_size * 1.25 * m_buttons.size()) + + (button_size * 0.25); y_end = y_start + button_size; } else { - y_end = m_upper_left.X - (button_size * 1.25 * m_buttons.size()) - - (button_size * 0.25); + y_end = m_upper_left.X - (button_size * 1.25 * m_buttons.size()) - + (button_size * 0.25); y_start = y_end - button_size; } - current_button = rect(m_upper_left.X, y_start, - m_lower_right.Y, y_end); + current_button = rect( + m_upper_left.X, y_start, m_lower_right.Y, y_end); } - auto *btn = new button_info(); - btn->guibutton = m_guienv->addButton(current_button, - nullptr, button_id, caption, nullptr); + auto *btn = new button_info(); + btn->guibutton = m_guienv->addButton( + current_button, nullptr, button_id, caption, nullptr); btn->guibutton->grab(); btn->guibutton->setVisible(false); btn->guibutton->setEnabled(false); - btn->repeatcounter = -1; - btn->keycode = id2keycode(button_id); + btn->repeatcounter = -1; + btn->keycode = id2keycode(button_id); btn->immediate_release = true; btn->ids.clear(); - load_button_texture(btn, btn_image, current_button, m_texturesource, - m_driver); + load_button_texture(btn, btn_image, current_button, m_texturesource, m_driver); m_buttons.push_back(btn); } void AutoHideButtonBar::addToggleButton(touch_gui_button_id button_id, - const wchar_t *caption, const char *btn_image_1, - const char *btn_image_2) + const wchar_t *caption, const char *btn_image_1, const char *btn_image_2) { addButton(button_id, caption, btn_image_1); button_info *btn = m_buttons.back(); @@ -291,11 +285,11 @@ bool AutoHideButtonBar::isButton(const SEvent &event) auto *translated = new SEvent(); memset(translated, 0, sizeof(SEvent)); - translated->EventType = irr::EET_KEY_INPUT_EVENT; - translated->KeyInput.Key = (*iter)->keycode; - translated->KeyInput.Control = false; - translated->KeyInput.Shift = false; - translated->KeyInput.Char = 0; + translated->EventType = irr::EET_KEY_INPUT_EVENT; + translated->KeyInput.Key = (*iter)->keycode; + translated->KeyInput.Control = false; + translated->KeyInput.Shift = false; + translated->KeyInput.Char = 0; // add this event translated->KeyInput.PressedDown = true; @@ -314,12 +308,14 @@ bool AutoHideButtonBar::isButton(const SEvent &event) if ((*iter)->togglable == 1) { (*iter)->togglable = 2; load_button_texture(*iter, (*iter)->textures[1], - (*iter)->guibutton->getRelativePosition(), + (*iter)->guibutton + ->getRelativePosition(), m_texturesource, m_driver); } else if ((*iter)->togglable == 2) { (*iter)->togglable = 1; load_button_texture(*iter, (*iter)->textures[0], - (*iter)->guibutton->getRelativePosition(), + (*iter)->guibutton + ->getRelativePosition(), m_texturesource, m_driver); } @@ -410,42 +406,41 @@ void AutoHideButtonBar::show() } } -TouchScreenGUI::TouchScreenGUI(IrrlichtDevice *device, IEventReceiver *receiver): - m_device(device), - m_guienv(device->getGUIEnvironment()), - m_receiver(receiver), - m_settingsbar(device, receiver), - m_rarecontrolsbar(device, receiver) +TouchScreenGUI::TouchScreenGUI(IrrlichtDevice *device, IEventReceiver *receiver) : + m_device(device), m_guienv(device->getGUIEnvironment()), + m_receiver(receiver), m_settingsbar(device, receiver), + m_rarecontrolsbar(device, receiver) { for (auto &button : m_buttons) { - button.guibutton = nullptr; + button.guibutton = nullptr; button.repeatcounter = -1; - button.repeatdelay = BUTTON_REPEAT_DELAY; + button.repeatdelay = BUTTON_REPEAT_DELAY; } m_touchscreen_threshold = g_settings->getU16("touchscreen_threshold"); m_fixed_joystick = g_settings->getBool("fixed_virtual_joystick"); - m_joystick_triggers_special1 = g_settings->getBool("virtual_joystick_triggers_aux"); + m_joystick_triggers_special1 = + g_settings->getBool("virtual_joystick_triggers_aux"); m_screensize = m_device->getVideoDriver()->getScreenSize(); button_size = MYMIN(m_screensize.Y / 4.5f, porting::getDisplayDensity() * - g_settings->getFloat("hud_scaling") * 65.0f); + g_settings->getFloat("hud_scaling") * 65.0f); } void TouchScreenGUI::initButton(touch_gui_button_id id, const rect &button_rect, const std::wstring &caption, bool immediate_release, float repeat_delay) { - button_info *btn = &m_buttons[id]; - btn->guibutton = m_guienv->addButton(button_rect, nullptr, id, caption.c_str()); + button_info *btn = &m_buttons[id]; + btn->guibutton = m_guienv->addButton(button_rect, nullptr, id, caption.c_str()); btn->guibutton->grab(); - btn->repeatcounter = -1; - btn->repeatdelay = repeat_delay; - btn->keycode = id2keycode(id); + btn->repeatcounter = -1; + btn->repeatdelay = repeat_delay; + btn->keycode = id2keycode(id); btn->immediate_release = immediate_release; btn->ids.clear(); - load_button_texture(btn, button_imagenames[id], button_rect, - m_texturesource, m_device->getVideoDriver()); + load_button_texture(btn, button_imagenames[id], button_rect, m_texturesource, + m_device->getVideoDriver()); } button_info *TouchScreenGUI::initJoystickButton(touch_gui_button_id id, @@ -457,8 +452,8 @@ button_info *TouchScreenGUI::initJoystickButton(touch_gui_button_id id, btn->guibutton->grab(); btn->ids.clear(); - load_button_texture(btn, joystick_imagenames[texture_id], - button_rect, m_texturesource, m_device->getVideoDriver()); + load_button_texture(btn, joystick_imagenames[texture_id], button_rect, + m_texturesource, m_device->getVideoDriver()); return btn; } @@ -467,7 +462,7 @@ void TouchScreenGUI::init(ISimpleTextureSource *tsrc) { assert(tsrc); - m_visible = true; + m_visible = true; m_texturesource = tsrc; /* Init joystick display "button" @@ -475,23 +470,21 @@ void TouchScreenGUI::init(ISimpleTextureSource *tsrc) */ if (m_fixed_joystick) { m_joystick_btn_off = initJoystickButton(joystick_off_id, - rect(button_size, - m_screensize.Y - button_size * 4, + rect(button_size, m_screensize.Y - button_size * 4, button_size * 4, - m_screensize.Y - button_size), 0); + m_screensize.Y - button_size), + 0); } else { m_joystick_btn_off = initJoystickButton(joystick_off_id, - rect(button_size, - m_screensize.Y - button_size * 3, + rect(button_size, m_screensize.Y - button_size * 3, button_size * 3, - m_screensize.Y - button_size), 0); + m_screensize.Y - button_size), + 0); } m_joystick_btn_bg = initJoystickButton(joystick_bg_id, - rect(button_size, - m_screensize.Y - button_size * 4, - button_size * 4, - m_screensize.Y - button_size), + rect(button_size, m_screensize.Y - button_size * 4, + button_size * 4, m_screensize.Y - button_size), 1, false); m_joystick_btn_center = initJoystickButton(joystick_center_id, @@ -531,39 +524,48 @@ void TouchScreenGUI::init(ISimpleTextureSource *tsrc) L"spc1", false); m_settingsbar.init(m_texturesource, "gear_icon.png", settings_starter_id, - v2s32(m_screensize.X - (1.25 * button_size), - m_screensize.Y - ((SETTINGS_BAR_Y_OFFSET + 1.0) * button_size) - + (0.5 * button_size)), - v2s32(m_screensize.X - (0.25 * button_size), - m_screensize.Y - (SETTINGS_BAR_Y_OFFSET * button_size) - + (0.5 * button_size)), - AHBB_Dir_Right_Left, 3.0); + v2s32(m_screensize.X - (1.25 * button_size), + m_screensize.Y - + ((SETTINGS_BAR_Y_OFFSET + 1.0) * + button_size) + + (0.5 * button_size)), + v2s32(m_screensize.X - (0.25 * button_size), + m_screensize.Y - + (SETTINGS_BAR_Y_OFFSET * + button_size) + + (0.5 * button_size)), + AHBB_Dir_Right_Left, 3.0); - m_settingsbar.addButton(fly_id, L"fly", "fly_btn.png"); - m_settingsbar.addButton(noclip_id, L"noclip", "noclip_btn.png"); - m_settingsbar.addButton(fast_id, L"fast", "fast_btn.png"); - m_settingsbar.addButton(debug_id, L"debug", "debug_btn.png"); - m_settingsbar.addButton(camera_id, L"camera", "camera_btn.png"); - m_settingsbar.addButton(range_id, L"rangeview", "rangeview_btn.png"); - m_settingsbar.addButton(minimap_id, L"minimap", "minimap_btn.png"); + m_settingsbar.addButton(fly_id, L"fly", "fly_btn.png"); + m_settingsbar.addButton(noclip_id, L"noclip", "noclip_btn.png"); + m_settingsbar.addButton(fast_id, L"fast", "fast_btn.png"); + m_settingsbar.addButton(debug_id, L"debug", "debug_btn.png"); + m_settingsbar.addButton(camera_id, L"camera", "camera_btn.png"); + m_settingsbar.addButton(range_id, L"rangeview", "rangeview_btn.png"); + m_settingsbar.addButton(minimap_id, L"minimap", "minimap_btn.png"); // Chat is shown by default, so chat_hide_btn.png is shown first. - m_settingsbar.addToggleButton(toggle_chat_id, L"togglechat", - "chat_hide_btn.png", "chat_show_btn.png"); + m_settingsbar.addToggleButton(toggle_chat_id, L"togglechat", "chat_hide_btn.png", + "chat_show_btn.png"); m_rarecontrolsbar.init(m_texturesource, "rare_controls.png", - rare_controls_starter_id, - v2s32(0.25 * button_size, - m_screensize.Y - ((RARE_CONTROLS_BAR_Y_OFFSET + 1.0) * button_size) - + (0.5 * button_size)), - v2s32(0.75 * button_size, - m_screensize.Y - (RARE_CONTROLS_BAR_Y_OFFSET * button_size) - + (0.5 * button_size)), - AHBB_Dir_Left_Right, 2.0); + rare_controls_starter_id, + v2s32(0.25 * button_size, + m_screensize.Y - + ((RARE_CONTROLS_BAR_Y_OFFSET + + 1.0) * + button_size) + + (0.5 * button_size)), + v2s32(0.75 * button_size, + m_screensize.Y - + (RARE_CONTROLS_BAR_Y_OFFSET * + button_size) + + (0.5 * button_size)), + AHBB_Dir_Left_Right, 2.0); - m_rarecontrolsbar.addButton(chat_id, L"Chat", "chat_btn.png"); - m_rarecontrolsbar.addButton(inventory_id, L"inv", "inventory_btn.png"); - m_rarecontrolsbar.addButton(drop_id, L"drop", "drop_btn.png"); + m_rarecontrolsbar.addButton(chat_id, L"Chat", "chat_btn.png"); + m_rarecontrolsbar.addButton(inventory_id, L"inv", "inventory_btn.png"); + m_rarecontrolsbar.addButton(drop_id, L"drop", "drop_btn.png"); } touch_gui_button_id TouchScreenGUI::getButtonID(s32 x, s32 y) @@ -571,13 +573,13 @@ touch_gui_button_id TouchScreenGUI::getButtonID(s32 x, s32 y) IGUIElement *rootguielement = m_guienv->getRootGUIElement(); if (rootguielement != nullptr) { - gui::IGUIElement *element = - rootguielement->getElementFromPoint(core::position2d(x, y)); + gui::IGUIElement *element = rootguielement->getElementFromPoint( + core::position2d(x, y)); if (element) for (unsigned int i = 0; i < after_last_element_id; i++) if (element == m_buttons[i].guibutton) - return (touch_gui_button_id) i; + return (touch_gui_button_id)i; } return after_last_element_id; @@ -591,7 +593,7 @@ touch_gui_button_id TouchScreenGUI::getButtonID(size_t eventID) auto id = std::find(btn->ids.begin(), btn->ids.end(), eventID); if (id != btn->ids.end()) - return (touch_gui_button_id) i; + return (touch_gui_button_id)i; } return after_last_element_id; @@ -601,17 +603,18 @@ bool TouchScreenGUI::isHUDButton(const SEvent &event) { // check if hud item is pressed for (auto &hud_rect : m_hud_rects) { - if (hud_rect.second.isPointInside(v2s32(event.TouchInput.X, - event.TouchInput.Y))) { + if (hud_rect.second.isPointInside( + v2s32(event.TouchInput.X, event.TouchInput.Y))) { auto *translated = new SEvent(); memset(translated, 0, sizeof(SEvent)); translated->EventType = irr::EET_KEY_INPUT_EVENT; - translated->KeyInput.Key = (irr::EKEY_CODE) (KEY_KEY_1 + hud_rect.first); - translated->KeyInput.Control = false; - translated->KeyInput.Shift = false; + translated->KeyInput.Key = + (irr::EKEY_CODE)(KEY_KEY_1 + hud_rect.first); + translated->KeyInput.Control = false; + translated->KeyInput.Shift = false; translated->KeyInput.PressedDown = true; m_receiver->OnEvent(*translated); - m_hud_ids[event.TouchInput.ID] = translated->KeyInput.Key; + m_hud_ids[event.TouchInput.ID] = translated->KeyInput.Key; delete translated; return true; } @@ -619,25 +622,27 @@ bool TouchScreenGUI::isHUDButton(const SEvent &event) return false; } -void TouchScreenGUI::handleButtonEvent(touch_gui_button_id button, - size_t eventID, bool action) +void TouchScreenGUI::handleButtonEvent( + touch_gui_button_id button, size_t eventID, bool action) { button_info *btn = &m_buttons[button]; auto *translated = new SEvent(); memset(translated, 0, sizeof(SEvent)); - translated->EventType = irr::EET_KEY_INPUT_EVENT; - translated->KeyInput.Key = btn->keycode; - translated->KeyInput.Control = false; - translated->KeyInput.Shift = false; - translated->KeyInput.Char = 0; + translated->EventType = irr::EET_KEY_INPUT_EVENT; + translated->KeyInput.Key = btn->keycode; + translated->KeyInput.Control = false; + translated->KeyInput.Shift = false; + translated->KeyInput.Char = 0; // add this event if (action) { - assert(std::find(btn->ids.begin(), btn->ids.end(), eventID) == btn->ids.end()); + assert(std::find(btn->ids.begin(), btn->ids.end(), eventID) == + btn->ids.end()); btn->ids.push_back(eventID); - if (btn->ids.size() > 1) return; + if (btn->ids.size() > 1) + return; btn->repeatcounter = 0; translated->KeyInput.PressedDown = true; @@ -656,7 +661,7 @@ void TouchScreenGUI::handleButtonEvent(touch_gui_button_id button, return; translated->KeyInput.PressedDown = false; - btn->repeatcounter = -1; + btn->repeatcounter = -1; m_receiver->OnEvent(*translated); } delete translated; @@ -666,7 +671,6 @@ void TouchScreenGUI::handleReleaseEvent(size_t evt_id) { touch_gui_button_id button = getButtonID(evt_id); - if (button != after_last_element_id) { // handle button events handleButtonEvent(button, evt_id, false); @@ -678,13 +682,13 @@ void TouchScreenGUI::handleReleaseEvent(size_t evt_id) if (m_move_sent_as_mouse_event) { auto *translated = new SEvent; memset(translated, 0, sizeof(SEvent)); - translated->EventType = EET_MOUSE_INPUT_EVENT; - translated->MouseInput.X = m_move_downlocation.X; - translated->MouseInput.Y = m_move_downlocation.Y; - translated->MouseInput.Shift = false; - translated->MouseInput.Control = false; + translated->EventType = EET_MOUSE_INPUT_EVENT; + translated->MouseInput.X = m_move_downlocation.X; + translated->MouseInput.Y = m_move_downlocation.Y; + translated->MouseInput.Shift = false; + translated->MouseInput.Control = false; translated->MouseInput.ButtonStates = 0; - translated->MouseInput.Event = EMIE_LMOUSE_LEFT_UP; + translated->MouseInput.Event = EMIE_LMOUSE_LEFT_UP; m_receiver->OnEvent(*translated); delete translated; } else { @@ -706,13 +710,11 @@ void TouchScreenGUI::handleReleaseEvent(size_t evt_id) m_joystick_btn_bg->guibutton->setVisible(false); m_joystick_btn_center->guibutton->setVisible(false); } else { - infostream - << "TouchScreenGUI::translateEvent released unknown button: " - << evt_id << std::endl; + infostream << "TouchScreenGUI::translateEvent released unknown button: " + << evt_id << std::endl; } - for (auto iter = m_known_ids.begin(); - iter != m_known_ids.end(); ++iter) { + for (auto iter = m_known_ids.begin(); iter != m_known_ids.end(); ++iter) { if (iter->id == evt_id) { m_known_ids.erase(iter); break; @@ -723,9 +725,8 @@ void TouchScreenGUI::handleReleaseEvent(size_t evt_id) void TouchScreenGUI::translateEvent(const SEvent &event) { if (!m_visible) { - infostream - << "TouchScreenGUI::translateEvent got event but not visible!" - << std::endl; + infostream << "TouchScreenGUI::translateEvent got event but not visible!" + << std::endl; return; } @@ -740,8 +741,8 @@ void TouchScreenGUI::translateEvent(const SEvent &event) */ id_status toadd{}; toadd.id = event.TouchInput.ID; - toadd.X = event.TouchInput.X; - toadd.Y = event.TouchInput.Y; + toadd.X = event.TouchInput.X; + toadd.Y = event.TouchInput.Y; m_known_ids.push_back(toadd); size_t eventID = event.TouchInput.ID; @@ -770,49 +771,67 @@ void TouchScreenGUI::translateEvent(const SEvent &event) m_rarecontrolsbar.deactivate(); s32 dxj = event.TouchInput.X - button_size * 5.0f / 2.0f; - s32 dyj = event.TouchInput.Y - m_screensize.Y + button_size * 5.0f / 2.0f; + s32 dyj = event.TouchInput.Y - m_screensize.Y + + button_size * 5.0f / 2.0f; /* Select joystick when left 1/3 of screen dragged or * when joystick tapped (fixed joystick position) */ - if ((m_fixed_joystick && dxj * dxj + dyj * dyj <= button_size * button_size * 1.5 * 1.5) || - (!m_fixed_joystick && event.TouchInput.X < m_screensize.X / 3.0f)) { - // If we don't already have a starting point for joystick make this the one. + if ((m_fixed_joystick && + dxj * dxj + dyj * dyj <= + button_size * button_size * + 1.5 * 1.5) || + (!m_fixed_joystick && + event.TouchInput.X < + m_screensize.X / + 3.0f)) { + // If we don't already have a starting point for joystick + // make this the one. if (m_joystick_id == -1) { - m_joystick_id = event.TouchInput.ID; + m_joystick_id = event.TouchInput.ID; m_joystick_has_really_moved = false; m_joystick_btn_off->guibutton->setVisible(false); m_joystick_btn_bg->guibutton->setVisible(true); - m_joystick_btn_center->guibutton->setVisible(true); + m_joystick_btn_center->guibutton->setVisible( + true); - // If it's a fixed joystick, don't move the joystick "button". + // If it's a fixed joystick, don't move the + // joystick "button". if (!m_fixed_joystick) m_joystick_btn_bg->guibutton->setRelativePosition(v2s32( - event.TouchInput.X - button_size * 3.0f / 2.0f, - event.TouchInput.Y - button_size * 3.0f / 2.0f)); + event.TouchInput.X - + button_size * 3.0f / + 2.0f, + event.TouchInput.Y - + button_size * 3.0f / + 2.0f)); m_joystick_btn_center->guibutton->setRelativePosition(v2s32( - event.TouchInput.X - button_size / 2.0f, - event.TouchInput.Y - button_size / 2.0f)); + event.TouchInput.X - + button_size / 2.0f, + event.TouchInput.Y - + button_size / 2.0f)); } } else { - // If we don't already have a moving point make this the moving one. + // If we don't already have a moving point make this the + // moving one. if (m_move_id == -1) { - m_move_id = event.TouchInput.ID; - m_move_has_really_moved = false; - m_move_downtime = porting::getTimeMs(); - m_move_downlocation = v2s32(event.TouchInput.X, event.TouchInput.Y); + m_move_id = event.TouchInput.ID; + m_move_has_really_moved = false; + m_move_downtime = porting::getTimeMs(); + m_move_downlocation = v2s32(event.TouchInput.X, + event.TouchInput.Y); m_move_sent_as_mouse_event = false; } } } - m_pointerpos[event.TouchInput.ID] = v2s32(event.TouchInput.X, event.TouchInput.Y); - } - else if (event.TouchInput.Event == ETIE_LEFT_UP) { - verbosestream - << "Up event for pointerid: " << event.TouchInput.ID << std::endl; + m_pointerpos[event.TouchInput.ID] = + v2s32(event.TouchInput.X, event.TouchInput.Y); + } else if (event.TouchInput.Event == ETIE_LEFT_UP) { + verbosestream << "Up event for pointerid: " << event.TouchInput.ID + << std::endl; handleReleaseEvent(event.TouchInput.ID); } else { assert(event.TouchInput.Event == ETIE_MOVED); @@ -823,13 +842,19 @@ void TouchScreenGUI::translateEvent(const SEvent &event) if (m_move_id != -1) { if ((event.TouchInput.ID == m_move_id) && - (!m_move_sent_as_mouse_event)) { + (!m_move_sent_as_mouse_event)) { double distance = sqrt( - (m_pointerpos[event.TouchInput.ID].X - event.TouchInput.X) * - (m_pointerpos[event.TouchInput.ID].X - event.TouchInput.X) + - (m_pointerpos[event.TouchInput.ID].Y - event.TouchInput.Y) * - (m_pointerpos[event.TouchInput.ID].Y - event.TouchInput.Y)); + (m_pointerpos[event.TouchInput.ID].X - + event.TouchInput.X) * + (m_pointerpos[event.TouchInput.ID] + .X - + event.TouchInput.X) + + (m_pointerpos[event.TouchInput.ID].Y - + event.TouchInput.Y) * + (m_pointerpos[event.TouchInput.ID] + .Y - + event.TouchInput.Y)); if ((distance > m_touchscreen_threshold) || (m_move_has_really_moved)) { @@ -842,25 +867,31 @@ void TouchScreenGUI::translateEvent(const SEvent &event) s32 dy = Y - m_pointerpos[event.TouchInput.ID].Y; // adapt to similar behaviour as pc screen - double d = g_settings->getFloat("mouse_sensitivity") * 3.0f; + double d = g_settings->getFloat( + "mouse_sensitivity") * + 3.0f; m_camera_yaw_change -= dx * d; - m_camera_pitch = MYMIN(MYMAX(m_camera_pitch + (dy * d), -180), 180); + m_camera_pitch = MYMIN( + MYMAX(m_camera_pitch + (dy * d), + -180), + 180); // update shootline - m_shootline = m_device - ->getSceneManager() - ->getSceneCollisionManager() - ->getRayFromScreenCoordinates(v2s32(X, Y)); + m_shootline = m_device->getSceneManager() + ->getSceneCollisionManager() + ->getRayFromScreenCoordinates(v2s32( + X, + Y)); m_pointerpos[event.TouchInput.ID] = v2s32(X, Y); } } else if ((event.TouchInput.ID == m_move_id) && (m_move_sent_as_mouse_event)) { - m_shootline = m_device - ->getSceneManager() - ->getSceneCollisionManager() - ->getRayFromScreenCoordinates( - v2s32(event.TouchInput.X, event.TouchInput.Y)); + m_shootline = m_device->getSceneManager() + ->getSceneCollisionManager() + ->getRayFromScreenCoordinates(v2s32( + event.TouchInput.X, + event.TouchInput.Y)); } } @@ -878,13 +909,17 @@ void TouchScreenGUI::translateEvent(const SEvent &event) double distance_sq = dx * dx + dy * dy; s32 dxj = event.TouchInput.X - button_size * 5.0f / 2.0f; - s32 dyj = event.TouchInput.Y - m_screensize.Y + button_size * 5.0f / 2.0f; - bool inside_joystick = (dxj * dxj + dyj * dyj <= button_size * button_size * 1.5 * 1.5); + s32 dyj = event.TouchInput.Y - m_screensize.Y + + button_size * 5.0f / 2.0f; + bool inside_joystick = (dxj * dxj + dyj * dyj <= + button_size * button_size * 1.5 * 1.5); if (m_joystick_has_really_moved || - (!m_joystick_has_really_moved && inside_joystick) || + (!m_joystick_has_really_moved && + inside_joystick) || (!m_fixed_joystick && - distance_sq > m_touchscreen_threshold * m_touchscreen_threshold)) { + distance_sq > m_touchscreen_threshold * + m_touchscreen_threshold)) { m_joystick_has_really_moved = true; double distance = sqrt(distance_sq); @@ -896,7 +931,7 @@ void TouchScreenGUI::translateEvent(const SEvent &event) angle = fmod(angle + 180 + 22.5, 360); // reset state before applying - for (bool & joystick_status : m_joystick_status) + for (bool &joystick_status : m_joystick_status) joystick_status = false; if (distance <= m_touchscreen_threshold) { @@ -926,20 +961,31 @@ void TouchScreenGUI::translateEvent(const SEvent &event) if (distance > button_size) { m_joystick_status[j_special1] = true; // move joystick "button" - s32 ndx = button_size * dx / distance - button_size / 2.0f; - s32 ndy = button_size * dy / distance - button_size / 2.0f; + s32 ndx = button_size * dx / distance - + button_size / 2.0f; + s32 ndy = button_size * dy / distance - + button_size / 2.0f; if (m_fixed_joystick) { m_joystick_btn_center->guibutton->setRelativePosition(v2s32( - button_size * 5 / 2 + ndx, - m_screensize.Y - button_size * 5 / 2 + ndy)); + button_size * 5 / 2 + ndx, + m_screensize.Y - + button_size * 5 / + 2 + + ndy)); } else { m_joystick_btn_center->guibutton->setRelativePosition(v2s32( - m_pointerpos[event.TouchInput.ID].X + ndx, - m_pointerpos[event.TouchInput.ID].Y + ndy)); + m_pointerpos[event.TouchInput.ID] + .X + + ndx, + m_pointerpos[event.TouchInput.ID] + .Y + + ndy)); } } else { - m_joystick_btn_center->guibutton->setRelativePosition( - v2s32(X - button_size / 2, Y - button_size / 2)); + m_joystick_btn_center->guibutton + ->setRelativePosition(v2s32( + X - button_size / 2, + Y - button_size / 2)); } } } @@ -955,22 +1001,23 @@ void TouchScreenGUI::handleChangedButton(const SEvent &event) if (m_buttons[i].ids.empty()) continue; - for (auto iter = m_buttons[i].ids.begin(); - iter != m_buttons[i].ids.end(); ++iter) { + for (auto iter = m_buttons[i].ids.begin(); iter != m_buttons[i].ids.end(); + ++iter) { if (event.TouchInput.ID == *iter) { - int current_button_id = - getButtonID(event.TouchInput.X, event.TouchInput.Y); + int current_button_id = getButtonID( + event.TouchInput.X, event.TouchInput.Y); if (current_button_id == i) continue; // remove old button - handleButtonEvent((touch_gui_button_id) i, *iter, false); + handleButtonEvent((touch_gui_button_id)i, *iter, false); if (current_button_id == after_last_element_id) return; - handleButtonEvent((touch_gui_button_id) current_button_id, *iter, true); + handleButtonEvent((touch_gui_button_id)current_button_id, + *iter, true); return; } } @@ -982,48 +1029,48 @@ void TouchScreenGUI::handleChangedButton(const SEvent &event) return; button_info *btn = &m_buttons[current_button_id]; - if (std::find(btn->ids.begin(), btn->ids.end(), event.TouchInput.ID) - == btn->ids.end()) - handleButtonEvent((touch_gui_button_id) current_button_id, + if (std::find(btn->ids.begin(), btn->ids.end(), event.TouchInput.ID) == + btn->ids.end()) + handleButtonEvent((touch_gui_button_id)current_button_id, event.TouchInput.ID, true); } bool TouchScreenGUI::doubleTapDetection() { m_key_events[0].down_time = m_key_events[1].down_time; - m_key_events[0].x = m_key_events[1].x; - m_key_events[0].y = m_key_events[1].y; + m_key_events[0].x = m_key_events[1].x; + m_key_events[0].y = m_key_events[1].y; m_key_events[1].down_time = m_move_downtime; - m_key_events[1].x = m_move_downlocation.X; - m_key_events[1].y = m_move_downlocation.Y; + m_key_events[1].x = m_move_downlocation.X; + m_key_events[1].y = m_move_downlocation.Y; u64 delta = porting::getDeltaMs(m_key_events[0].down_time, porting::getTimeMs()); if (delta > 400) return false; - double distance = sqrt( - (m_key_events[0].x - m_key_events[1].x) * - (m_key_events[0].x - m_key_events[1].x) + - (m_key_events[0].y - m_key_events[1].y) * - (m_key_events[0].y - m_key_events[1].y)); + double distance = sqrt((m_key_events[0].x - m_key_events[1].x) * + (m_key_events[0].x - m_key_events[1].x) + + (m_key_events[0].y - m_key_events[1].y) * + (m_key_events[0].y - m_key_events[1].y)); if (distance > (20 + m_touchscreen_threshold)) return false; auto *translated = new SEvent(); memset(translated, 0, sizeof(SEvent)); - translated->EventType = EET_MOUSE_INPUT_EVENT; - translated->MouseInput.X = m_key_events[0].x; - translated->MouseInput.Y = m_key_events[0].y; - translated->MouseInput.Shift = false; - translated->MouseInput.Control = false; + translated->EventType = EET_MOUSE_INPUT_EVENT; + translated->MouseInput.X = m_key_events[0].x; + translated->MouseInput.Y = m_key_events[0].y; + translated->MouseInput.Shift = false; + translated->MouseInput.Control = false; translated->MouseInput.ButtonStates = EMBSM_RIGHT; // update shootline - m_shootline = m_device - ->getSceneManager() - ->getSceneCollisionManager() - ->getRayFromScreenCoordinates(v2s32(m_key_events[0].x, m_key_events[0].y)); + m_shootline = m_device->getSceneManager() + ->getSceneCollisionManager() + ->getRayFromScreenCoordinates( + v2s32(m_key_events[0].x, + m_key_events[0].y)); translated->MouseInput.Event = EMIE_RMOUSE_PRESSED_DOWN; verbosestream << "TouchScreenGUI::translateEvent right click press" << std::endl; @@ -1031,7 +1078,8 @@ bool TouchScreenGUI::doubleTapDetection() translated->MouseInput.ButtonStates = 0; translated->MouseInput.Event = EMIE_RMOUSE_LEFT_UP; - verbosestream << "TouchScreenGUI::translateEvent right click release" << std::endl; + verbosestream << "TouchScreenGUI::translateEvent right click release" + << std::endl; m_receiver->OnEvent(*translated); delete translated; return true; @@ -1044,8 +1092,8 @@ void TouchScreenGUI::applyJoystickStatus() continue; SEvent translated{}; - translated.EventType = irr::EET_KEY_INPUT_EVENT; - translated.KeyInput.Key = id2keycode(m_joystick_names[i]); + translated.EventType = irr::EET_KEY_INPUT_EVENT; + translated.KeyInput.Key = id2keycode(m_joystick_names[i]); translated.KeyInput.PressedDown = false; m_receiver->OnEvent(translated); @@ -1095,11 +1143,11 @@ void TouchScreenGUI::step(float dtime) if (button.repeatcounter < button.repeatdelay) continue; - button.repeatcounter = 0; + button.repeatcounter = 0; SEvent translated; memset(&translated, 0, sizeof(SEvent)); - translated.EventType = irr::EET_KEY_INPUT_EVENT; - translated.KeyInput.Key = button.keycode; + translated.EventType = irr::EET_KEY_INPUT_EVENT; + translated.KeyInput.Key = button.keycode; translated.KeyInput.PressedDown = false; m_receiver->OnEvent(translated); @@ -1117,31 +1165,33 @@ void TouchScreenGUI::step(float dtime) } // if a new placed pointer isn't moved for some time start digging - if ((m_move_id != -1) && - (!m_move_has_really_moved) && + if ((m_move_id != -1) && (!m_move_has_really_moved) && (!m_move_sent_as_mouse_event)) { u64 delta = porting::getDeltaMs(m_move_downtime, porting::getTimeMs()); if (delta > MIN_DIG_TIME_MS) { - m_shootline = m_device - ->getSceneManager() - ->getSceneCollisionManager() - ->getRayFromScreenCoordinates( - v2s32(m_move_downlocation.X,m_move_downlocation.Y)); + m_shootline = m_device->getSceneManager() + ->getSceneCollisionManager() + ->getRayFromScreenCoordinates(v2s32( + m_move_downlocation + .X, + m_move_downlocation + .Y)); SEvent translated; memset(&translated, 0, sizeof(SEvent)); - translated.EventType = EET_MOUSE_INPUT_EVENT; - translated.MouseInput.X = m_move_downlocation.X; - translated.MouseInput.Y = m_move_downlocation.Y; - translated.MouseInput.Shift = false; - translated.MouseInput.Control = false; + translated.EventType = EET_MOUSE_INPUT_EVENT; + translated.MouseInput.X = m_move_downlocation.X; + translated.MouseInput.Y = m_move_downlocation.Y; + translated.MouseInput.Shift = false; + translated.MouseInput.Control = false; translated.MouseInput.ButtonStates = EMBSM_LEFT; - translated.MouseInput.Event = EMIE_LMOUSE_PRESSED_DOWN; - verbosestream << "TouchScreenGUI::step left click press" << std::endl; + translated.MouseInput.Event = EMIE_LMOUSE_PRESSED_DOWN; + verbosestream << "TouchScreenGUI::step left click press" + << std::endl; m_receiver->OnEvent(translated); - m_move_sent_as_mouse_event = true; + m_move_sent_as_mouse_event = true; } } diff --git a/src/httpfetch.cpp b/src/httpfetch.cpp index 326b5052f..1883cf263 100644 --- a/src/httpfetch.cpp +++ b/src/httpfetch.cpp @@ -38,17 +38,16 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "noise.h" std::mutex g_httpfetch_mutex; -std::map > g_httpfetch_results; +std::map> g_httpfetch_results; PcgRandom g_callerid_randomness; HTTPFetchRequest::HTTPFetchRequest() : - timeout(g_settings->getS32("curl_timeout")), - connect_timeout(timeout), - useragent(std::string(PROJECT_NAME_C "/") + g_version_hash + " (" + porting::get_sysinfo() + ")") + timeout(g_settings->getS32("curl_timeout")), connect_timeout(timeout), + useragent(std::string(PROJECT_NAME_C "/") + g_version_hash + " (" + + porting::get_sysinfo() + ")") { } - static void httpfetch_deliver_result(const HTTPFetchResult &fetch_result) { unsigned long caller = fetch_result.caller; @@ -67,11 +66,11 @@ unsigned long httpfetch_caller_alloc() // Check each caller ID except HTTPFETCH_DISCARD const unsigned long discard = HTTPFETCH_DISCARD; for (unsigned long caller = discard + 1; caller != discard; ++caller) { - std::map >::iterator - it = g_httpfetch_results.find(caller); + std::map>::iterator it = + g_httpfetch_results.find(caller); if (it == g_httpfetch_results.end()) { - verbosestream << "httpfetch_caller_alloc: allocating " - << caller << std::endl; + verbosestream << "httpfetch_caller_alloc: allocating " << caller + << std::endl; // Access element to create it g_httpfetch_results[caller]; return caller; @@ -93,17 +92,18 @@ unsigned long httpfetch_caller_alloc_secure() unsigned long caller; do { - caller = (((u64) g_callerid_randomness.next()) << 32) | - g_callerid_randomness.next(); + caller = (((u64)g_callerid_randomness.next()) << 32) | + g_callerid_randomness.next(); if (--tries < 1) { - FATAL_ERROR("httpfetch_caller_alloc_secure: ran out of caller IDs"); + FATAL_ERROR("httpfetch_caller_alloc_secure: ran out of caller " + "IDs"); return HTTPFETCH_DISCARD; } } while (g_httpfetch_results.find(caller) != g_httpfetch_results.end()); - verbosestream << "httpfetch_caller_alloc_secure: allocating " - << caller << std::endl; + verbosestream << "httpfetch_caller_alloc_secure: allocating " << caller + << std::endl; // Access element to create it g_httpfetch_results[caller]; @@ -112,8 +112,7 @@ unsigned long httpfetch_caller_alloc_secure() void httpfetch_caller_free(unsigned long caller) { - verbosestream<<"httpfetch_caller_free: freeing " - < >::iterator - it = g_httpfetch_results.find(caller); + std::map>::iterator it = + g_httpfetch_results.find(caller); if (it == g_httpfetch_results.end()) return false; @@ -153,7 +152,7 @@ bool httpfetch_async_get(unsigned long caller, HTTPFetchResult &fetch_result) static size_t httpfetch_writefunction( char *ptr, size_t size, size_t nmemb, void *userdata) { - std::ostringstream *stream = (std::ostringstream*)userdata; + std::ostringstream *stream = (std::ostringstream *)userdata; size_t count = size * nmemb; stream->write(ptr, count); return count; @@ -167,28 +166,28 @@ static size_t httpfetch_discardfunction( class CurlHandlePool { - std::list handles; + std::list handles; public: CurlHandlePool() = default; ~CurlHandlePool() { - for (std::list::iterator it = handles.begin(); + for (std::list::iterator it = handles.begin(); it != handles.end(); ++it) { curl_easy_cleanup(*it); } } - CURL * alloc() + CURL *alloc() { CURL *curl; if (handles.empty()) { curl = curl_easy_init(); if (curl == NULL) { - errorstream<<"curl_easy_init returned NULL"<alloc(); if (curl == NULL) { @@ -262,21 +255,15 @@ HTTPFetchOngoing::HTTPFetchOngoing(const HTTPFetchRequest &request_, // other protocols don't affect us. // These settings were introduced in curl 7.19.4. long protocols = - CURLPROTO_HTTP | - CURLPROTO_HTTPS | - CURLPROTO_FTP | - CURLPROTO_FTPS; + CURLPROTO_HTTP | CURLPROTO_HTTPS | CURLPROTO_FTP | CURLPROTO_FTPS; curl_easy_setopt(curl, CURLOPT_PROTOCOLS, protocols); curl_easy_setopt(curl, CURLOPT_REDIR_PROTOCOLS, protocols); #endif // Set cURL options based on HTTPFetchRequest - curl_easy_setopt(curl, CURLOPT_URL, - request.url.c_str()); - curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, - request.timeout); - curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT_MS, - request.connect_timeout); + curl_easy_setopt(curl, CURLOPT_URL, request.url.c_str()); + curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, request.timeout); + curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT_MS, request.connect_timeout); if (!request.useragent.empty()) curl_easy_setopt(curl, CURLOPT_USERAGENT, request.useragent.c_str()); @@ -285,12 +272,10 @@ HTTPFetchOngoing::HTTPFetchOngoing(const HTTPFetchRequest &request_, // ostringstream ongoing->oss, unless the data // is to be discarded if (request.caller == HTTPFETCH_DISCARD) { - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, - httpfetch_discardfunction); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, httpfetch_discardfunction); curl_easy_setopt(curl, CURLOPT_WRITEDATA, NULL); } else { - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, - httpfetch_writefunction); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, httpfetch_writefunction); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &oss); } @@ -301,8 +286,7 @@ HTTPFetchOngoing::HTTPFetchOngoing(const HTTPFetchRequest &request_, curl_httppost *last = NULL; for (StringMap::iterator it = request.post_fields.begin(); it != request.post_fields.end(); ++it) { - curl_formadd(&post, &last, - CURLFORM_NAMELENGTH, it->first.size(), + curl_formadd(&post, &last, CURLFORM_NAMELENGTH, it->first.size(), CURLFORM_PTRNAME, it->first.c_str(), CURLFORM_CONTENTSLENGTH, it->second.size(), CURLFORM_PTRCONTENTS, it->second.c_str(), @@ -321,16 +305,12 @@ HTTPFetchOngoing::HTTPFetchOngoing(const HTTPFetchRequest &request_, str += "="; str += urlencode(post_field.second); } - curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, - str.size()); - curl_easy_setopt(curl, CURLOPT_COPYPOSTFIELDS, - str.c_str()); + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, str.size()); + curl_easy_setopt(curl, CURLOPT_COPYPOSTFIELDS, str.c_str()); } else { curl_easy_setopt(curl, CURLOPT_POST, 1); - curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, - request.post_data.size()); - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, - request.post_data.c_str()); + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, request.post_data.size()); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request.post_data.c_str()); // request.post_data must now *never* be // modified until CURLOPT_POSTFIELDS is cleared } @@ -359,15 +339,14 @@ CURLcode HTTPFetchOngoing::start(CURLM *multi_) CURLMcode mres = curl_multi_add_handle(multi_, curl); if (mres != CURLM_OK) { errorstream << "curl_multi_add_handle" - << " returned error code " << mres - << std::endl; + << " returned error code " << mres << std::endl; return CURLE_FAILED_INIT; } multi = multi_; // store for curl_multi_remove_handle return CURLE_OK; } -const HTTPFetchResult * HTTPFetchOngoing::complete(CURLcode res) +const HTTPFetchResult *HTTPFetchOngoing::complete(CURLcode res) { result.succeeded = (res == CURLE_OK); result.timeout = (res == CURLE_OPERATION_TIMEDOUT); @@ -376,16 +355,16 @@ const HTTPFetchResult * HTTPFetchOngoing::complete(CURLcode res) // Get HTTP/FTP response code result.response_code = 0; if (curl && (curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, - &result.response_code) != CURLE_OK)) { + &result.response_code) != CURLE_OK)) { // We failed to get a return code, make sure it is still 0 result.response_code = 0; } if (res != CURLE_OK) { - errorstream << request.url << " not found (" - << curl_easy_strerror(res) << ")" - << " (response code " << result.response_code << ")" - << std::endl; + errorstream << request.url << " not found (" << curl_easy_strerror(res) + << ")" + << " (response code " << result.response_code << ")" + << std::endl; } return &result; @@ -397,14 +376,12 @@ HTTPFetchOngoing::~HTTPFetchOngoing() CURLMcode mres = curl_multi_remove_handle(multi, curl); if (mres != CURLM_OK) { errorstream << "curl_multi_remove_handle" - << " returned error code " << mres - << std::endl; + << " returned error code " << mres << std::endl; } } // Set safe options for the reusable cURL handle - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, - httpfetch_discardfunction); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, httpfetch_discardfunction); curl_easy_setopt(curl, CURLOPT_WRITEDATA, NULL); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, NULL); if (http_header) { @@ -420,17 +397,18 @@ HTTPFetchOngoing::~HTTPFetchOngoing() pool->free(curl); } - class CurlFetchThread : public Thread { protected: - enum RequestType { + enum RequestType + { RT_FETCH, RT_CLEAR, RT_WAKEUP, }; - struct Request { + struct Request + { RequestType type; HTTPFetchRequest fetch_request; Event *event; @@ -441,12 +419,11 @@ protected: size_t m_parallel_limit; // Variables exclusively used within thread - std::vector m_all_ongoing; + std::vector m_all_ongoing; std::list m_queued_fetches; public: - CurlFetchThread(int parallel_limit) : - Thread("CurlFetch") + CurlFetchThread(int parallel_limit) : Thread("CurlFetch") { if (parallel_limit >= 1) m_parallel_limit = parallel_limit; @@ -492,13 +469,12 @@ protected: // see processQueued() for what happens next - } - else if (req.type == RT_CLEAR) { + } else if (req.type == RT_CLEAR) { unsigned long caller = req.fetch_request.caller; // Abort all ongoing fetches for the caller - for (std::vector::iterator - it = m_all_ongoing.begin(); + for (std::vector::iterator it = + m_all_ongoing.begin(); it != m_all_ongoing.end();) { if ((*it)->getRequest().caller == caller) { delete (*it); @@ -509,16 +485,15 @@ protected: } // Also abort all queued fetches for the caller - for (std::list::iterator - it = m_queued_fetches.begin(); + for (std::list::iterator it = + m_queued_fetches.begin(); it != m_queued_fetches.end();) { if ((*it).caller == caller) it = m_queued_fetches.erase(it); else ++it; } - } - else if (req.type == RT_WAKEUP) { + } else if (req.type == RT_WAKEUP) { // Wakeup: Nothing to do, thread is awake at this point } @@ -536,15 +511,13 @@ protected: // Create ongoing fetch data and make a cURL handle // Set cURL options based on HTTPFetchRequest - HTTPFetchOngoing *ongoing = - new HTTPFetchOngoing(request, pool); + HTTPFetchOngoing *ongoing = new HTTPFetchOngoing(request, pool); // Initiate the connection (curl_multi_add_handle) CURLcode res = ongoing->start(m_multi); if (res == CURLE_OK) { m_all_ongoing.push_back(ongoing); - } - else { + } else { httpfetch_deliver_result(*ongoing->complete(res)); delete ongoing; } @@ -579,8 +552,8 @@ protected: try { Request req = m_requests.pop_front(timeout); processRequest(req); + } catch (ItemNotFoundException &e) { } - catch (ItemNotFoundException &e) {} } } @@ -599,20 +572,18 @@ protected: FD_ZERO(&write_fd_set); FD_ZERO(&exc_fd_set); - mres = curl_multi_fdset(m_multi, &read_fd_set, - &write_fd_set, &exc_fd_set, &max_fd); + mres = curl_multi_fdset(m_multi, &read_fd_set, &write_fd_set, &exc_fd_set, + &max_fd); if (mres != CURLM_OK) { - errorstream<<"curl_multi_fdset" - <<" returned error code "<stop(); g_httpfetch_thread->requestWakeUp(); @@ -776,8 +744,7 @@ static void httpfetch_request_clear(unsigned long caller) } } -void httpfetch_sync(const HTTPFetchRequest &fetch_request, - HTTPFetchResult &fetch_result) +void httpfetch_sync(const HTTPFetchRequest &fetch_request, HTTPFetchResult &fetch_result) { // Create ongoing fetch data and make a cURL handle // Set cURL options based on HTTPFetchRequest @@ -789,7 +756,7 @@ void httpfetch_sync(const HTTPFetchRequest &fetch_request, fetch_result = *ongoing.complete(res); } -#else // USE_CURL +#else // USE_CURL /* USE_CURL is off: @@ -808,7 +775,7 @@ void httpfetch_cleanup() void httpfetch_async(const HTTPFetchRequest &fetch_request) { errorstream << "httpfetch_async: unable to fetch " << fetch_request.url - << " because USE_CURL=0" << std::endl; + << " because USE_CURL=0" << std::endl; HTTPFetchResult fetch_result(fetch_request); // sets succeeded = false etc. httpfetch_deliver_result(fetch_result); @@ -818,13 +785,12 @@ static void httpfetch_request_clear(unsigned long caller) { } -void httpfetch_sync(const HTTPFetchRequest &fetch_request, - HTTPFetchResult &fetch_result) +void httpfetch_sync(const HTTPFetchRequest &fetch_request, HTTPFetchResult &fetch_result) { errorstream << "httpfetch_sync: unable to fetch " << fetch_request.url - << " because USE_CURL=0" << std::endl; + << " because USE_CURL=0" << std::endl; fetch_result = HTTPFetchResult(fetch_request); // sets succeeded = false etc. } -#endif // USE_CURL +#endif // USE_CURL diff --git a/src/hud.cpp b/src/hud.cpp index 3079b5cd8..705d0cfb3 100644 --- a/src/hud.cpp +++ b/src/hud.cpp @@ -20,44 +20,41 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "hud.h" #include -const struct EnumString es_HudElementType[] = -{ - {HUD_ELEM_IMAGE, "image"}, - {HUD_ELEM_TEXT, "text"}, - {HUD_ELEM_STATBAR, "statbar"}, - {HUD_ELEM_INVENTORY, "inventory"}, - {HUD_ELEM_WAYPOINT, "waypoint"}, - {HUD_ELEM_IMAGE_WAYPOINT, "image_waypoint"}, - {0, NULL}, +const struct EnumString es_HudElementType[] = { + {HUD_ELEM_IMAGE, "image"}, + {HUD_ELEM_TEXT, "text"}, + {HUD_ELEM_STATBAR, "statbar"}, + {HUD_ELEM_INVENTORY, "inventory"}, + {HUD_ELEM_WAYPOINT, "waypoint"}, + {HUD_ELEM_IMAGE_WAYPOINT, "image_waypoint"}, + {0, NULL}, }; -const struct EnumString es_HudElementStat[] = -{ - {HUD_STAT_POS, "position"}, - {HUD_STAT_POS, "pos"}, /* Deprecated, only for compatibility's sake */ - {HUD_STAT_NAME, "name"}, - {HUD_STAT_SCALE, "scale"}, - {HUD_STAT_TEXT, "text"}, - {HUD_STAT_NUMBER, "number"}, - {HUD_STAT_ITEM, "item"}, - {HUD_STAT_DIR, "direction"}, - {HUD_STAT_ALIGN, "alignment"}, - {HUD_STAT_OFFSET, "offset"}, - {HUD_STAT_WORLD_POS, "world_pos"}, - {HUD_STAT_SIZE, "size"}, - {HUD_STAT_Z_INDEX, "z_index"}, - {HUD_STAT_TEXT2, "text2"}, - {0, NULL}, +const struct EnumString es_HudElementStat[] = { + {HUD_STAT_POS, "position"}, + {HUD_STAT_POS, "pos"}, /* Deprecated, only for compatibility's sake */ + {HUD_STAT_NAME, "name"}, + {HUD_STAT_SCALE, "scale"}, + {HUD_STAT_TEXT, "text"}, + {HUD_STAT_NUMBER, "number"}, + {HUD_STAT_ITEM, "item"}, + {HUD_STAT_DIR, "direction"}, + {HUD_STAT_ALIGN, "alignment"}, + {HUD_STAT_OFFSET, "offset"}, + {HUD_STAT_WORLD_POS, "world_pos"}, + {HUD_STAT_SIZE, "size"}, + {HUD_STAT_Z_INDEX, "z_index"}, + {HUD_STAT_TEXT2, "text2"}, + {0, NULL}, }; -const struct EnumString es_HudBuiltinElement[] = -{ - {HUD_FLAG_HOTBAR_VISIBLE, "hotbar"}, - {HUD_FLAG_HEALTHBAR_VISIBLE, "healthbar"}, - {HUD_FLAG_CROSSHAIR_VISIBLE, "crosshair"}, - {HUD_FLAG_WIELDITEM_VISIBLE, "wielditem"}, - {HUD_FLAG_BREATHBAR_VISIBLE, "breathbar"}, - {HUD_FLAG_MINIMAP_VISIBLE, "minimap"}, - {HUD_FLAG_MINIMAP_RADAR_VISIBLE, "minimap_radar"}, - {0, NULL}, +const struct EnumString es_HudBuiltinElement[] = { + {HUD_FLAG_HOTBAR_VISIBLE, "hotbar"}, + {HUD_FLAG_HEALTHBAR_VISIBLE, "healthbar"}, + {HUD_FLAG_CROSSHAIR_VISIBLE, "crosshair"}, + {HUD_FLAG_WIELDITEM_VISIBLE, "wielditem"}, + {HUD_FLAG_BREATHBAR_VISIBLE, "breathbar"}, + {HUD_FLAG_MINIMAP_VISIBLE, "minimap"}, + {HUD_FLAG_MINIMAP_RADAR_VISIBLE, "minimap_radar"}, + {0, NULL}, }; diff --git a/src/hud.h b/src/hud.h index e015baec1..904109580 100644 --- a/src/hud.h +++ b/src/hud.h @@ -29,19 +29,19 @@ with this program; if not, write to the Free Software Foundation, Inc., #define HUD_DIR_TOP_BOTTOM 2 #define HUD_DIR_BOTTOM_TOP 3 -#define HUD_CORNER_UPPER 0 -#define HUD_CORNER_LOWER 1 +#define HUD_CORNER_UPPER 0 +#define HUD_CORNER_LOWER 1 #define HUD_CORNER_CENTER 2 // Note that these visibility flags do not determine if the hud items are // actually drawn, but rather, whether to draw the item should the rest // of the game state permit it. -#define HUD_FLAG_HOTBAR_VISIBLE (1 << 0) -#define HUD_FLAG_HEALTHBAR_VISIBLE (1 << 1) -#define HUD_FLAG_CROSSHAIR_VISIBLE (1 << 2) -#define HUD_FLAG_WIELDITEM_VISIBLE (1 << 3) -#define HUD_FLAG_BREATHBAR_VISIBLE (1 << 4) -#define HUD_FLAG_MINIMAP_VISIBLE (1 << 5) +#define HUD_FLAG_HOTBAR_VISIBLE (1 << 0) +#define HUD_FLAG_HEALTHBAR_VISIBLE (1 << 1) +#define HUD_FLAG_CROSSHAIR_VISIBLE (1 << 2) +#define HUD_FLAG_WIELDITEM_VISIBLE (1 << 3) +#define HUD_FLAG_BREATHBAR_VISIBLE (1 << 4) +#define HUD_FLAG_MINIMAP_VISIBLE (1 << 5) #define HUD_FLAG_MINIMAP_RADAR_VISIBLE (1 << 6) #define HUD_PARAM_HOTBAR_ITEMCOUNT 1 @@ -49,21 +49,22 @@ with this program; if not, write to the Free Software Foundation, Inc., #define HUD_PARAM_HOTBAR_SELECTED_IMAGE 3 #define HUD_HOTBAR_ITEMCOUNT_DEFAULT 8 -#define HUD_HOTBAR_ITEMCOUNT_MAX 32 - +#define HUD_HOTBAR_ITEMCOUNT_MAX 32 #define HOTBAR_IMAGE_SIZE 48 -enum HudElementType { - HUD_ELEM_IMAGE = 0, - HUD_ELEM_TEXT = 1, - HUD_ELEM_STATBAR = 2, +enum HudElementType +{ + HUD_ELEM_IMAGE = 0, + HUD_ELEM_TEXT = 1, + HUD_ELEM_STATBAR = 2, HUD_ELEM_INVENTORY = 3, - HUD_ELEM_WAYPOINT = 4, + HUD_ELEM_WAYPOINT = 4, HUD_ELEM_IMAGE_WAYPOINT = 5 }; -enum HudElementStat { +enum HudElementStat +{ HUD_STAT_POS = 0, HUD_STAT_NAME, HUD_STAT_SCALE, @@ -79,7 +80,8 @@ enum HudElementStat { HUD_STAT_TEXT2, }; -struct HudElement { +struct HudElement +{ HudElementType type; v2f pos; std::string name; @@ -99,4 +101,3 @@ struct HudElement { extern const EnumString es_HudElementType[]; extern const EnumString es_HudElementStat[]; extern const EnumString es_HudBuiltinElement[]; - diff --git a/src/inventory.cpp b/src/inventory.cpp index 349ee503d..8c4909e01 100644 --- a/src/inventory.cpp +++ b/src/inventory.cpp @@ -26,7 +26,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "itemdef.h" #include "util/strfnd.h" #include "content_mapnode.h" // For loading legacy MaterialItems -#include "nameidmapping.h" // For loading legacy MaterialItems +#include "nameidmapping.h" // For loading legacy MaterialItems #include "util/serialize.h" #include "util/string.h" @@ -37,18 +37,17 @@ with this program; if not, write to the Free Software Foundation, Inc., static content_t content_translate_from_19_to_internal(content_t c_from) { for (const auto &tt : trans_table_19) { - if(tt[1] == c_from) { + if (tt[1] == c_from) { return tt[0]; } } return c_from; } -ItemStack::ItemStack(const std::string &name_, u16 count_, - u16 wear_, IItemDefManager *itemdef) : - name(itemdef->getAlias(name_)), - count(count_), - wear(wear_) +ItemStack::ItemStack(const std::string &name_, u16 count_, u16 wear_, + IItemDefManager *itemdef) : + name(itemdef->getAlias(name_)), + count(count_), wear(wear_) { if (name.empty() || count == 0) clear(); @@ -94,55 +93,50 @@ void ItemStack::deSerialize(std::istream &is, IItemDefManager *itemdef) // Skip space std::string tmp; std::getline(is, tmp, ' '); - if(!tmp.empty()) + if (!tmp.empty()) throw SerializationError("Unexpected text after item name"); - if(name == "MaterialItem") - { + if (name == "MaterialItem") { // Obsoleted on 2011-07-30 u16 material; - is>>material; + is >> material; u16 materialcount; - is>>materialcount; + is >> materialcount; // Convert old materials - if(material <= 0xff) + if (material <= 0xff) material = content_translate_from_19_to_internal(material); - if(material > 0xfff) + if (material > 0xfff) throw SerializationError("Too large material number"); // Convert old id to name NameIdMapping legacy_nimap; content_mapnode_get_name_id_mapping(&legacy_nimap); legacy_nimap.getName(material, name); - if(name.empty()) + if (name.empty()) name = "unknown_block"; if (itemdef) name = itemdef->getAlias(name); count = materialcount; - } - else if(name == "MaterialItem2") - { + } else if (name == "MaterialItem2") { // Obsoleted on 2011-11-16 u16 material; - is>>material; + is >> material; u16 materialcount; - is>>materialcount; - if(material > 0xfff) + is >> materialcount; + if (material > 0xfff) throw SerializationError("Too large material number"); // Convert old id to name NameIdMapping legacy_nimap; content_mapnode_get_name_id_mapping(&legacy_nimap); legacy_nimap.getName(material, name); - if(name.empty()) + if (name.empty()) name = "unknown_block"; if (itemdef) name = itemdef->getAlias(name); count = materialcount; - } - else if(name == "node" || name == "NodeItem" || name == "MaterialItem3" - || name == "craft" || name == "CraftItem") - { + } else if (name == "node" || name == "NodeItem" || name == "MaterialItem3" || + name == "craft" || name == "CraftItem") { // Obsoleted on 2012-01-07 std::string all; @@ -151,7 +145,7 @@ void ItemStack::deSerialize(std::istream &is, IItemDefManager *itemdef) Strfnd fnd(all); fnd.next("\""); // If didn't skip to end, we have ""s - if(!fnd.at_end()){ + if (!fnd.at_end()) { name = fnd.next("\""); } else { // No luck, just read a word then fnd.start(all); @@ -161,16 +155,12 @@ void ItemStack::deSerialize(std::istream &is, IItemDefManager *itemdef) if (itemdef) name = itemdef->getAlias(name); count = stoi(trim(fnd.next(""))); - if(count == 0) + if (count == 0) count = 1; - } - else if(name == "MBOItem") - { + } else if (name == "MBOItem") { // Obsoleted on 2011-10-14 throw SerializationError("MBOItem not supported anymore"); - } - else if(name == "tool" || name == "ToolItem") - { + } else if (name == "tool" || name == "ToolItem") { // Obsoleted on 2012-01-07 std::string all; @@ -179,7 +169,7 @@ void ItemStack::deSerialize(std::istream &is, IItemDefManager *itemdef) Strfnd fnd(all); fnd.next("\""); // If didn't skip to end, we have ""s - if(!fnd.at_end()){ + if (!fnd.at_end()) { name = fnd.next("\""); } else { // No luck, just read a word then fnd.start(all); @@ -191,10 +181,8 @@ void ItemStack::deSerialize(std::istream &is, IItemDefManager *itemdef) if (itemdef) name = itemdef->getAlias(name); wear = stoi(trim(fnd.next(""))); - } - else - { - do // This loop is just to allow "break;" + } else { + do // This loop is just to allow "break;" { // The real thing @@ -215,7 +203,7 @@ void ItemStack::deSerialize(std::istream &is, IItemDefManager *itemdef) // Read the wear std::string wear_str; std::getline(is, wear_str, ' '); - if(wear_str.empty()) + if (wear_str.empty()) break; wear = stoi(wear_str); @@ -224,11 +212,12 @@ void ItemStack::deSerialize(std::istream &is, IItemDefManager *itemdef) metadata.deSerialize(is); // In case fields are added after metadata, skip space here: - //std::getline(is, tmp, ' '); - //if(!tmp.empty()) - // throw SerializationError("Unexpected text after metadata"); + // std::getline(is, tmp, ' '); + // if(!tmp.empty()) + // throw SerializationError("Unexpected text after + // metadata"); - } while(false); + } while (false); } if (name.empty() || count == 0) @@ -258,36 +247,29 @@ std::string ItemStack::getDescription(IItemDefManager *itemdef) const return desc.empty() ? name : desc; } - ItemStack ItemStack::addItem(ItemStack newitem, IItemDefManager *itemdef) { // If the item is empty or the position invalid, bail out - if(newitem.empty()) - { + if (newitem.empty()) { // nothing can be added trivially } // If this is an empty item, it's an easy job. - else if(empty()) - { + else if (empty()) { *this = newitem; newitem.clear(); } // If item name or metadata differs, bail out - else if (name != newitem.name - || metadata != newitem.metadata) - { + else if (name != newitem.name || metadata != newitem.metadata) { // cannot be added } // If the item fits fully, add counter and delete it - else if(newitem.count <= freeSpace(itemdef)) - { + else if (newitem.count <= freeSpace(itemdef)) { add(newitem.count); newitem.clear(); } // Else the item does not fit fully. Add all that fits and return // the rest. - else - { + else { u16 freespace = freeSpace(itemdef); add(freespace); newitem.remove(freespace); @@ -296,40 +278,33 @@ ItemStack ItemStack::addItem(ItemStack newitem, IItemDefManager *itemdef) return newitem; } -bool ItemStack::itemFits(ItemStack newitem, - ItemStack *restitem, - IItemDefManager *itemdef) const +bool ItemStack::itemFits( + ItemStack newitem, ItemStack *restitem, IItemDefManager *itemdef) const { // If the item is empty or the position invalid, bail out - if(newitem.empty()) - { + if (newitem.empty()) { // nothing can be added trivially } // If this is an empty item, it's an easy job. - else if(empty()) - { + else if (empty()) { newitem.clear(); } // If item name or metadata differs, bail out - else if (name != newitem.name - || metadata != newitem.metadata) - { + else if (name != newitem.name || metadata != newitem.metadata) { // cannot be added } // If the item fits fully, delete it - else if(newitem.count <= freeSpace(itemdef)) - { + else if (newitem.count <= freeSpace(itemdef)) { newitem.clear(); } // Else the item does not fit fully. Return the rest. - else - { + else { u16 freespace = freeSpace(itemdef); newitem.remove(freespace); } - if(restitem) + if (restitem) *restitem = newitem; return newitem.empty(); @@ -337,17 +312,14 @@ bool ItemStack::itemFits(ItemStack newitem, ItemStack ItemStack::takeItem(u32 takecount) { - if(takecount == 0 || count == 0) + if (takecount == 0 || count == 0) return ItemStack(); ItemStack result = *this; - if(takecount >= count) - { + if (takecount >= count) { // Take all clear(); - } - else - { + } else { // Take part remove(takecount); result.count = takecount; @@ -357,11 +329,11 @@ ItemStack ItemStack::takeItem(u32 takecount) ItemStack ItemStack::peekItem(u32 peekcount) const { - if(peekcount == 0 || count == 0) + if (peekcount == 0 || count == 0) return ItemStack(); ItemStack result = *this; - if(peekcount < count) + if (peekcount < count) result.count = peekcount; return result; } @@ -370,10 +342,10 @@ ItemStack ItemStack::peekItem(u32 peekcount) const Inventory */ -InventoryList::InventoryList(const std::string &name, u32 size, IItemDefManager *itemdef): - m_name(name), - m_size(size), - m_itemdef(itemdef) +InventoryList::InventoryList( + const std::string &name, u32 size, IItemDefManager *itemdef) : + m_name(name), + m_size(size), m_itemdef(itemdef) { clearItems(); } @@ -382,7 +354,7 @@ void InventoryList::clearItems() { m_items.clear(); - for (u32 i=0; i < m_size; i++) { + for (u32 i = 0; i < m_size; i++) { m_items.emplace_back(); } @@ -413,29 +385,29 @@ void InventoryList::setName(const std::string &name) void InventoryList::serialize(std::ostream &os, bool incremental) const { - //os.imbue(std::locale("C")); + // os.imbue(std::locale("C")); - os<<"Width "<> m_width; if (iss.fail()) throw SerializationError("incorrect width property"); - } - else if(name == "Item") - { - if(item_i > getSize() - 1) + } else if (name == "Item") { + if (item_i > getSize() - 1) throw SerializationError("too many items"); ItemStack item; item.deSerialize(iss, m_itemdef); m_items[item_i++] = item; - } - else if(name == "Empty") - { - if(item_i > getSize() - 1) + } else if (name == "Empty") { + if (item_i > getSize() - 1) throw SerializationError("too many items"); m_items[item_i++].clear(); } else if (name == "Keep") { @@ -484,9 +453,8 @@ void InventoryList::deSerialize(std::istream &is) // Contents given to deSerialize() were not terminated properly: throw error. std::ostringstream ss; - ss << "Malformatted inventory list. list=" - << m_name << ", read " << item_i << " of " << getSize() - << " ItemStacks." << std::endl; + ss << "Malformatted inventory list. list=" << m_name << ", read " << item_i + << " of " << getSize() << " ItemStacks." << std::endl; throw SerializationError(ss.str()); } @@ -495,25 +463,25 @@ InventoryList::InventoryList(const InventoryList &other) *this = other; } -InventoryList & InventoryList::operator = (const InventoryList &other) +InventoryList &InventoryList::operator=(const InventoryList &other) { m_items = other.m_items; m_size = other.m_size; m_width = other.m_width; m_name = other.m_name; m_itemdef = other.m_itemdef; - //setDirty(true); + // setDirty(true); return *this; } -bool InventoryList::operator == (const InventoryList &other) const +bool InventoryList::operator==(const InventoryList &other) const { - if(m_size != other.m_size) + if (m_size != other.m_size) return false; - if(m_width != other.m_width) + if (m_width != other.m_width) return false; - if(m_name != other.m_name) + if (m_name != other.m_name) return false; for (u32 i = 0; i < m_items.size(); i++) if (m_items[i] != other.m_items[i]) @@ -552,13 +520,13 @@ u32 InventoryList::getFreeSlots() const return getSize() - getUsedSlots(); } -const ItemStack& InventoryList::getItem(u32 i) const +const ItemStack &InventoryList::getItem(u32 i) const { assert(i < m_size); // Pre-condition return m_items[i]; } -ItemStack& InventoryList::getItem(u32 i) +ItemStack &InventoryList::getItem(u32 i) { assert(i < m_size); // Pre-condition return m_items[i]; @@ -566,7 +534,7 @@ ItemStack& InventoryList::getItem(u32 i) ItemStack InventoryList::changeItem(u32 i, const ItemStack &newitem) { - if(i >= m_items.size()) + if (i >= m_items.size()) return newitem; ItemStack olditem = m_items[i]; @@ -586,34 +554,32 @@ ItemStack InventoryList::addItem(const ItemStack &newitem_) { ItemStack newitem = newitem_; - if(newitem.empty()) + if (newitem.empty()) return newitem; /* First try to find if it could be added to some existing items */ - for(u32 i=0; i= m_items.size()) + if (i >= m_items.size()) return newitem; ItemStack leftover = m_items[i].addItem(newitem, m_itemdef); @@ -632,12 +598,11 @@ ItemStack InventoryList::addItem(u32 i, const ItemStack &newitem) return leftover; } -bool InventoryList::itemFits(const u32 i, const ItemStack &newitem, - ItemStack *restitem) const +bool InventoryList::itemFits( + const u32 i, const ItemStack &newitem, ItemStack *restitem) const { - if(i >= m_items.size()) - { - if(restitem) + if (i >= m_items.size()) { + if (restitem) *restitem = newitem; return false; } @@ -649,9 +614,8 @@ bool InventoryList::roomForItem(const ItemStack &item_) const { ItemStack item = item_; ItemStack leftover; - for(u32 i=0; iname == item.name && (!match_meta || (i->metadata == item.metadata))) { + if (i->name == item.name && + (!match_meta || (i->metadata == item.metadata))) { if (i->count >= count) return true; @@ -683,8 +648,8 @@ ItemStack InventoryList::removeItem(const ItemStack &item) for (auto i = m_items.rbegin(); i != m_items.rend(); ++i) { if (i->name == item.name) { u32 still_to_remove = item.count - removed.count; - ItemStack leftover = removed.addItem(i->takeItem(still_to_remove), - m_itemdef); + ItemStack leftover = removed.addItem( + i->takeItem(still_to_remove), m_itemdef); // Allow oversized stacks removed.count += leftover.count; @@ -699,7 +664,7 @@ ItemStack InventoryList::removeItem(const ItemStack &item) ItemStack InventoryList::takeItem(u32 i, u32 takecount) { - if(i >= m_items.size()) + if (i >= m_items.size()) return ItemStack(); ItemStack taken = m_items[i].takeItem(takecount); @@ -729,20 +694,20 @@ void InventoryList::moveItemSomewhere(u32 i, InventoryList *dest, u32 count) } } -u32 InventoryList::moveItem(u32 i, InventoryList *dest, u32 dest_i, - u32 count, bool swap_if_needed, bool *did_swap) +u32 InventoryList::moveItem(u32 i, InventoryList *dest, u32 dest_i, u32 count, + bool swap_if_needed, bool *did_swap) { - if(this == dest && i == dest_i) + if (this == dest && i == dest_i) return count; // Take item from source list ItemStack item1; - if(count == 0) + if (count == 0) item1 = changeItem(i, ItemStack()); else item1 = takeItem(i, count); - if(item1.empty()) + if (item1.empty()) return 0; // Try to add the item to destination list @@ -750,8 +715,7 @@ u32 InventoryList::moveItem(u32 i, InventoryList *dest, u32 dest_i, item1 = dest->addItem(dest_i, item1); // If something is returned, the item was not fully added - if(!item1.empty()) - { + if (!item1.empty()) { // If olditem is returned, nothing was added. bool nothing_added = (item1.count == oldcount); @@ -806,11 +770,10 @@ Inventory::Inventory(const Inventory &other) *this = other; } -Inventory & Inventory::operator = (const Inventory &other) +Inventory &Inventory::operator=(const Inventory &other) { // Gracefully handle self assignment - if(this != &other) - { + if (this != &other) { clear(); m_itemdef = other.m_itemdef; for (InventoryList *list : other.m_lists) { @@ -821,14 +784,13 @@ Inventory & Inventory::operator = (const Inventory &other) return *this; } -bool Inventory::operator == (const Inventory &other) const +bool Inventory::operator==(const Inventory &other) const { - if(m_lists.size() != other.m_lists.size()) + if (m_lists.size() != other.m_lists.size()) return false; - for(u32 i=0; igetName() << " " << list->getSize() << "\n"; + os << "List " << list->getName() << " " << list->getSize() + << "\n"; list->serialize(os, incremental); } else { os << "KeepList " << list->getName() << "\n"; } } - os<<"EndInventory\n"; + os << "EndInventory\n"; } void Inventory::deSerialize(std::istream &is) @@ -866,7 +830,8 @@ void Inventory::deSerialize(std::istream &is) if (name == "EndInventory" || name == "end") { // Remove all lists that were not sent for (auto &list : m_lists) { - if (std::find(new_lists.begin(), new_lists.end(), list) != new_lists.end()) + if (std::find(new_lists.begin(), new_lists.end(), list) != + new_lists.end()) continue; delete list; @@ -874,7 +839,8 @@ void Inventory::deSerialize(std::istream &is) setModified(); } m_lists.erase(std::remove(m_lists.begin(), m_lists.end(), - nullptr), m_lists.end()); + nullptr), + m_lists.end()); return; } @@ -883,7 +849,7 @@ void Inventory::deSerialize(std::istream &is) u32 listsize; std::getline(iss, listname, ' '); - iss>>listsize; + iss >> listsize; InventoryList *list = getList(listname); bool create_new = !list; @@ -906,8 +872,10 @@ void Inventory::deSerialize(std::istream &is) if (list) { new_lists.push_back(list); } else { - errorstream << "Inventory::deSerialize(): Tried to keep list '" << - listname << "' which is non-existent." << std::endl; + errorstream << "Inventory::deSerialize(): Tried to keep " + "list '" + << listname << "' which is non-existent." + << std::endl; } } // Any additional fields will throw errors when received by a client @@ -917,19 +885,17 @@ void Inventory::deSerialize(std::istream &is) // Contents given to deSerialize() were not terminated properly: throw error. std::ostringstream ss; - ss << "Malformatted inventory (damaged?). " - << m_lists.size() << " lists read." << std::endl; + ss << "Malformatted inventory (damaged?). " << m_lists.size() << " lists read." + << std::endl; throw SerializationError(ss.str()); } -InventoryList * Inventory::addList(const std::string &name, u32 size) +InventoryList *Inventory::addList(const std::string &name, u32 size) { setModified(); s32 i = getListIndex(name); - if(i != -1) - { - if(m_lists[i]->getSize() != size) - { + if (i != -1) { + if (m_lists[i]->getSize() != size) { delete m_lists[i]; m_lists[i] = new InventoryList(name, size, m_itemdef); m_lists[i]->setModified(); @@ -937,8 +903,7 @@ InventoryList * Inventory::addList(const std::string &name, u32 size) return m_lists[i]; } - - //don't create list with invalid name + // don't create list with invalid name if (name.find(' ') != std::string::npos) return nullptr; @@ -948,17 +913,17 @@ InventoryList * Inventory::addList(const std::string &name, u32 size) return list; } -InventoryList * Inventory::getList(const std::string &name) +InventoryList *Inventory::getList(const std::string &name) { s32 i = getListIndex(name); - if(i == -1) + if (i == -1) return NULL; return m_lists[i]; } -std::vector Inventory::getLists() +std::vector Inventory::getLists() { - std::vector lists; + std::vector lists; for (auto list : m_lists) { lists.push_back(list); } @@ -968,7 +933,7 @@ std::vector Inventory::getLists() bool Inventory::deleteList(const std::string &name) { s32 i = getListIndex(name); - if(i == -1) + if (i == -1) return false; setModified(); @@ -977,22 +942,21 @@ bool Inventory::deleteList(const std::string &name) return true; } -const InventoryList * Inventory::getList(const std::string &name) const +const InventoryList *Inventory::getList(const std::string &name) const { s32 i = getListIndex(name); - if(i == -1) + if (i == -1) return NULL; return m_lists[i]; } const s32 Inventory::getListIndex(const std::string &name) const { - for(u32 i=0; igetName() == name) + for (u32 i = 0; i < m_lists.size(); i++) { + if (m_lists[i]->getName() == name) return i; } return -1; } -//END +// END diff --git a/src/inventory.h b/src/inventory.h index 67a7859ed..c87e7ecba 100644 --- a/src/inventory.h +++ b/src/inventory.h @@ -34,8 +34,8 @@ struct ItemStack { ItemStack() = default; - ItemStack(const std::string &name_, u16 count_, - u16 wear, IItemDefManager *itemdef); + ItemStack(const std::string &name_, u16 count_, u16 wear, + IItemDefManager *itemdef); ~ItemStack() = default; @@ -54,10 +54,7 @@ struct ItemStack Quantity methods */ - bool empty() const - { - return count == 0; - } + bool empty() const { return count == 0; } void clear() { @@ -67,16 +64,13 @@ struct ItemStack metadata.clear(); } - void add(u16 n) - { - count += n; - } + void add(u16 n) { count += n; } void remove(u16 n) { assert(count >= n); // Pre-condition count -= n; - if(count == 0) + if (count == 0) clear(); // reset name, wear and metadata too } @@ -96,25 +90,19 @@ struct ItemStack } // Returns false if item is not known and cannot be used - bool isKnown(IItemDefManager *itemdef) const - { - return itemdef->isKnown(name); - } + bool isKnown(IItemDefManager *itemdef) const { return itemdef->isKnown(name); } // Returns a pointer to the item definition struct, // or a fallback one (name="unknown") if the item is unknown. - const ItemDefinition& getDefinition( - IItemDefManager *itemdef) const + const ItemDefinition &getDefinition(IItemDefManager *itemdef) const { return itemdef->get(name); } // Get tool digging properties, or those of the hand if not a tool - const ToolCapabilities& getToolCapabilities( - IItemDefManager *itemdef) const + const ToolCapabilities &getToolCapabilities(IItemDefManager *itemdef) const { - const ToolCapabilities *item_cap = - itemdef->get(name).tool_capabilities; + const ToolCapabilities *item_cap = itemdef->get(name).tool_capabilities; if (item_cap == NULL) // Fall back to the hand's tool capabilities @@ -128,11 +116,10 @@ struct ItemStack // Returns true if the item is (was) a tool bool addWear(s32 amount, IItemDefManager *itemdef) { - if(getDefinition(itemdef).type == ITEM_TOOL) - { - if(amount > 65535 - wear) + if (getDefinition(itemdef).type == ITEM_TOOL) { + if (amount > 65535 - wear) clear(); - else if(amount < -wear) + else if (amount < -wear) wear = 0; else wear += amount; @@ -152,7 +139,7 @@ struct ItemStack // If restitem is non-NULL, it receives the part of newitem that // would be left over after adding. bool itemFits(ItemStack newitem, - ItemStack *restitem, // may be NULL + ItemStack *restitem, // may be NULL IItemDefManager *itemdef) const; // Takes some items. @@ -163,18 +150,13 @@ struct ItemStack // Similar to takeItem, but keeps this ItemStack intact. ItemStack peekItem(u32 peekcount) const; - bool operator ==(const ItemStack &s) const + bool operator==(const ItemStack &s) const { - return (this->name == s.name && - this->count == s.count && - this->wear == s.wear && - this->metadata == s.metadata); + return (this->name == s.name && this->count == s.count && + this->wear == s.wear && this->metadata == s.metadata); } - bool operator !=(const ItemStack &s) const - { - return !(*this == s); - } + bool operator!=(const ItemStack &s) const { return !(*this == s); } /* Properties @@ -198,12 +180,9 @@ public: void deSerialize(std::istream &is); InventoryList(const InventoryList &other); - InventoryList & operator = (const InventoryList &other); - bool operator == (const InventoryList &other) const; - bool operator != (const InventoryList &other) const - { - return !(*this == other); - } + InventoryList &operator=(const InventoryList &other); + bool operator==(const InventoryList &other) const; + bool operator!=(const InventoryList &other) const { return !(*this == other); } const std::string &getName() const; u32 getSize() const; @@ -213,8 +192,8 @@ public: u32 getFreeSlots() const; // Get reference to item - const ItemStack& getItem(u32 i) const; - ItemStack& getItem(u32 i); + const ItemStack &getItem(u32 i) const; + ItemStack &getItem(u32 i); // Returns old item. Parameter can be an empty item. ItemStack changeItem(u32 i, const ItemStack &newitem); // Delete item @@ -258,8 +237,8 @@ public: // Move an item to a different list (or a different stack in the same list) // count is the maximum number of items to move (0 for everything) // returns number of moved items - u32 moveItem(u32 i, InventoryList *dest, u32 dest_i, - u32 count = 0, bool swap_if_needed = true, bool *did_swap = NULL); + u32 moveItem(u32 i, InventoryList *dest, u32 dest_i, u32 count = 0, + bool swap_if_needed = true, bool *did_swap = NULL); // like moveItem, but without a fixed destination index // also with optional rollback recording @@ -286,27 +265,24 @@ public: Inventory(IItemDefManager *itemdef); Inventory(const Inventory &other); - Inventory & operator = (const Inventory &other); - bool operator == (const Inventory &other) const; - bool operator != (const Inventory &other) const - { - return !(*this == other); - } + Inventory &operator=(const Inventory &other); + bool operator==(const Inventory &other) const; + bool operator!=(const Inventory &other) const { return !(*this == other); } // Never ever serialize to disk using "incremental"! void serialize(std::ostream &os, bool incremental = false) const; void deSerialize(std::istream &is); - InventoryList * addList(const std::string &name, u32 size); - InventoryList * getList(const std::string &name); - const InventoryList * getList(const std::string &name) const; - std::vector getLists(); + InventoryList *addList(const std::string &name, u32 size); + InventoryList *getList(const std::string &name); + const InventoryList *getList(const std::string &name) const; + std::vector getLists(); bool deleteList(const std::string &name); // A shorthand for adding items. Returns leftover item (possibly empty). ItemStack addItem(const std::string &listname, const ItemStack &newitem) { InventoryList *list = getList(listname); - if(list == NULL) + if (list == NULL) return newitem; return list->addItem(newitem); } @@ -332,11 +308,12 @@ public: list->setModified(dirty); } } + private: // -1 if not found const s32 getListIndex(const std::string &name) const; - std::vector m_lists; + std::vector m_lists; IItemDefManager *m_itemdef; bool m_dirty = true; }; diff --git a/src/inventorymanager.cpp b/src/inventorymanager.cpp index b6f464901..6b5a79fd9 100644 --- a/src/inventorymanager.cpp +++ b/src/inventorymanager.cpp @@ -29,7 +29,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/strfnd.h" #include "util/basic_macros.h" -#define PLAYER_TO_SA(p) p->getEnv()->getScriptIface() +#define PLAYER_TO_SA(p) p->getEnv()->getScriptIface() /* InventoryLocation @@ -46,19 +46,19 @@ void InventoryLocation::serialize(std::ostream &os) const { switch (type) { case InventoryLocation::UNDEFINED: - os<<"undefined"; + os << "undefined"; break; case InventoryLocation::CURRENT_PLAYER: - os<<"current_player"; + os << "current_player"; break; case InventoryLocation::PLAYER: - os<<"player:"<getInventory(from_inv); Inventory *inv_to = mgr->getInventory(to_inv); if (!inv_from) { infostream << "IMoveAction::apply(): FAIL: source inventory not found: " - << "from_inv=\""<getSize(); @@ -235,17 +234,17 @@ void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame } if ((u16)to_i > list_to->getSize()) { - infostream << "IMoveAction::apply(): FAIL: destination index out of bounds: " - << "to_i=" << to_i - << ", size=" << list_to->getSize() << std::endl; + infostream << "IMoveAction::apply(): FAIL: destination index out of " + "bounds: " + << "to_i=" << to_i << ", size=" << list_to->getSize() + << std::endl; return; } /* Do not handle rollback if both inventories are that of the same player */ - bool ignore_rollback = ( - from_inv.type == InventoryLocation::PLAYER && - from_inv == to_inv); + bool ignore_rollback = (from_inv.type == InventoryLocation::PLAYER && + from_inv == to_inv); /* Collect information of endpoints @@ -261,25 +260,28 @@ void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame /* Query detached inventories */ // Move occurs in the same detached inventory - if (from_inv.type == InventoryLocation::DETACHED && - from_inv == to_inv) { + if (from_inv.type == InventoryLocation::DETACHED && from_inv == to_inv) { src_can_take_count = PLAYER_TO_SA(player)->detached_inventory_AllowMove( - *this, try_take_count, player); + *this, try_take_count, player); dst_can_put_count = src_can_take_count; } else { // Destination is detached if (to_inv.type == InventoryLocation::DETACHED) { ItemStack src_item = list_from->getItem(from_i); src_item.count = try_take_count; - dst_can_put_count = PLAYER_TO_SA(player)->detached_inventory_AllowPut( - *this, src_item, player); + dst_can_put_count = + PLAYER_TO_SA(player)->detached_inventory_AllowPut( + *this, src_item, player); } // Source is detached if (from_inv.type == InventoryLocation::DETACHED) { ItemStack src_item = list_from->getItem(from_i); src_item.count = try_take_count; - src_can_take_count = PLAYER_TO_SA(player)->detached_inventory_AllowTake( - *this, src_item, player); + src_can_take_count = + PLAYER_TO_SA(player) + ->detached_inventory_AllowTake( + *this, src_item, + player); } } @@ -287,50 +289,54 @@ void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame // Both endpoints are nodemeta // Move occurs in the same nodemeta inventory - if (from_inv.type == InventoryLocation::NODEMETA && - from_inv == to_inv) { + if (from_inv.type == InventoryLocation::NODEMETA && from_inv == to_inv) { src_can_take_count = PLAYER_TO_SA(player)->nodemeta_inventory_AllowMove( - *this, try_take_count, player); + *this, try_take_count, player); dst_can_put_count = src_can_take_count; } else { // Destination is nodemeta if (to_inv.type == InventoryLocation::NODEMETA) { ItemStack src_item = list_from->getItem(from_i); src_item.count = try_take_count; - dst_can_put_count = PLAYER_TO_SA(player)->nodemeta_inventory_AllowPut( - *this, src_item, player); + dst_can_put_count = + PLAYER_TO_SA(player)->nodemeta_inventory_AllowPut( + *this, src_item, player); } // Source is nodemeta if (from_inv.type == InventoryLocation::NODEMETA) { ItemStack src_item = list_from->getItem(from_i); src_item.count = try_take_count; - src_can_take_count = PLAYER_TO_SA(player)->nodemeta_inventory_AllowTake( - *this, src_item, player); + src_can_take_count = + PLAYER_TO_SA(player) + ->nodemeta_inventory_AllowTake( + *this, src_item, + player); } } // Query player inventories // Move occurs in the same player inventory - if (from_inv.type == InventoryLocation::PLAYER && - from_inv == to_inv) { + if (from_inv.type == InventoryLocation::PLAYER && from_inv == to_inv) { src_can_take_count = PLAYER_TO_SA(player)->player_inventory_AllowMove( - *this, try_take_count, player); + *this, try_take_count, player); dst_can_put_count = src_can_take_count; } else { // Destination is a player if (to_inv.type == InventoryLocation::PLAYER) { ItemStack src_item = list_from->getItem(from_i); src_item.count = try_take_count; - dst_can_put_count = PLAYER_TO_SA(player)->player_inventory_AllowPut( - *this, src_item, player); + dst_can_put_count = + PLAYER_TO_SA(player)->player_inventory_AllowPut( + *this, src_item, player); } // Source is a player if (from_inv.type == InventoryLocation::PLAYER) { ItemStack src_item = list_from->getItem(from_i); src_item.count = try_take_count; - src_can_take_count = PLAYER_TO_SA(player)->player_inventory_AllowTake( - *this, src_item, player); + src_can_take_count = + PLAYER_TO_SA(player)->player_inventory_AllowTake( + *this, src_item, player); } } @@ -355,15 +361,13 @@ void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame if (to_inv.type == InventoryLocation::PLAYER) list_to->setModified(); - infostream<<"IMoveAction::apply(): move was completely disallowed:" - <<" count="<getItem(from_i).empty()) { - infostream<<"IDropAction::apply(): FAIL: source item not found: " - <<"from_inv=\""<takeItem(from_i, actually_dropped_count); + ItemStack item2 = list_from->takeItem( + from_i, actually_dropped_count); if (item2.count != actually_dropped_count) - errorstream<<"Could not take dropped count of items"<setInventoryModified(from_inv); } - infostream<<"IDropAction::apply(): dropped " - <<" from inv=\""<getSize() < 1) { infostream << "ICraftAction::apply(): FAIL: craftresult list too short: " - << "craft_inv=\"" << craft_inv.dump() << "\"" << std::endl; + << "craft_inv=\"" << craft_inv.dump() << "\"" << std::endl; return; } @@ -825,7 +816,8 @@ void ICraftAction::apply(InventoryManager *mgr, std::vector temp; // Decrement input and add crafting output getCraftingResult(inv_craft, crafted, temp, true, gamedef); - PLAYER_TO_SA(player)->item_OnCraft(crafted, player, &saved_craft_list, craft_inv); + PLAYER_TO_SA(player)->item_OnCraft( + crafted, player, &saved_craft_list, craft_inv); list_craftresult->addItem(0, crafted); mgr->setInventoryModified(craft_inv); @@ -834,7 +826,8 @@ void ICraftAction::apply(InventoryManager *mgr, for (auto &itemstack : temp) { for (auto &output_replacement : output_replacements) { if (itemstack.name == output_replacement.name) { - itemstack = output_replacement.addItem(itemstack, itemdef); + itemstack = output_replacement.addItem( + itemstack, itemdef); if (itemstack.empty()) continue; } @@ -842,10 +835,8 @@ void ICraftAction::apply(InventoryManager *mgr, output_replacements.push_back(itemstack); } - actionstream << player->getDescription() - << " crafts " - << crafted.getItemString() - << std::endl; + actionstream << player->getDescription() << " crafts " + << crafted.getItemString() << std::endl; // Decrement counter if (count_remaining == 1) @@ -856,7 +847,8 @@ void ICraftAction::apply(InventoryManager *mgr, // Get next crafting result getCraftingResult(inv_craft, crafted, temp, false, gamedef); - PLAYER_TO_SA(player)->item_CraftPredict(crafted, player, list_craft, craft_inv); + PLAYER_TO_SA(player)->item_CraftPredict( + crafted, player, list_craft, craft_inv); found = !crafted.empty(); } @@ -870,20 +862,21 @@ void ICraftAction::apply(InventoryManager *mgr, u16 count = output_replacement.count; do { PLAYER_TO_SA(player)->item_OnDrop(output_replacement, player, - player->getBasePosition()); + player->getBasePosition()); if (count >= output_replacement.count) { - errorstream << "Couldn't drop replacement stack " << - output_replacement.getItemString() << " because drop loop didn't " - "decrease count." << std::endl; + errorstream << "Couldn't drop replacement stack " + << output_replacement.getItemString() + << " because drop loop didn't " + "decrease count." + << std::endl; break; } } while (!output_replacement.empty()); } - infostream<<"ICraftAction::apply(): crafted " - <<" craft_inv=\""< &output_replacements, - bool decrementInput, IGameDef *gamedef) + std::vector &output_replacements, bool decrementInput, + IGameDef *gamedef) { result.clear(); @@ -909,7 +901,7 @@ bool getCraftingResult(Inventory *inv, ItemStack &result, CraftInput ci; ci.method = CRAFT_METHOD_NORMAL; ci.width = clist->getWidth() ? clist->getWidth() : 3; - for (u16 i=0; i < clist->getSize(); i++) + for (u16 i = 0; i < clist->getSize(); i++) ci.items.push_back(clist->getItem(i)); // Find out what is crafted and add it to result item slot @@ -921,11 +913,10 @@ bool getCraftingResult(Inventory *inv, ItemStack &result, if (found && decrementInput) { // CraftInput has been changed, apply changes in clist - for (u16 i=0; i < clist->getSize(); i++) { + for (u16 i = 0; i < clist->getSize(); i++) { clist->changeItem(i, ci.items[i]); } } return found; } - diff --git a/src/inventorymanager.h b/src/inventorymanager.h index 69bf30169..fa9e94b63 100644 --- a/src/inventorymanager.h +++ b/src/inventorymanager.h @@ -26,29 +26,21 @@ class ServerActiveObject; struct InventoryLocation { - enum Type{ + enum Type + { UNDEFINED, CURRENT_PLAYER, PLAYER, NODEMETA, - DETACHED, + DETACHED, } type; std::string name; // PLAYER, DETACHED - v3s16 p; // NODEMETA + v3s16 p; // NODEMETA - InventoryLocation() - { - setUndefined(); - } - void setUndefined() - { - type = UNDEFINED; - } - void setCurrentPlayer() - { - type = CURRENT_PLAYER; - } + InventoryLocation() { setUndefined(); } + void setUndefined() { type = UNDEFINED; } + void setCurrentPlayer() { type = CURRENT_PLAYER; } void setPlayer(const std::string &name_) { type = PLAYER; @@ -67,9 +59,9 @@ struct InventoryLocation bool operator==(const InventoryLocation &other) const { - if(type != other.type) + if (type != other.type) return false; - switch(type){ + switch (type) { case UNDEFINED: return false; case CURRENT_PLAYER: @@ -90,7 +82,7 @@ struct InventoryLocation void applyCurrentPlayer(const std::string &name_) { - if(type == CURRENT_PLAYER) + if (type == CURRENT_PLAYER) setPlayer(name_); } @@ -109,14 +101,15 @@ public: virtual ~InventoryManager() = default; // Get an inventory (server and client) - virtual Inventory* getInventory(const InventoryLocation &loc){return NULL;} - // Set modified (will be saved and sent over network; only on server) + virtual Inventory *getInventory(const InventoryLocation &loc) { return NULL; } + // Set modified (will be saved and sent over network; only on server) virtual void setInventoryModified(const InventoryLocation &loc) {} - // Send inventory action to server (only on client) - virtual void inventoryAction(InventoryAction *a){} + // Send inventory action to server (only on client) + virtual void inventoryAction(InventoryAction *a) {} }; -enum class IAction : u16 { +enum class IAction : u16 +{ Move, Drop, Craft @@ -131,7 +124,8 @@ struct InventoryAction virtual void apply(InventoryManager *mgr, ServerActiveObject *player, IGameDef *gamedef) = 0; virtual void clientApply(InventoryManager *mgr, IGameDef *gamedef) = 0; - virtual ~InventoryAction() = default;; + virtual ~InventoryAction() = default; + ; }; struct MoveAction @@ -159,10 +153,7 @@ struct IMoveAction : public InventoryAction, public MoveAction IMoveAction(std::istream &is, bool somewhere); - IAction getType() const - { - return IAction::Move; - } + IAction getType() const { return IAction::Move; } void serialize(std::ostream &os) const { @@ -194,18 +185,15 @@ struct IDropAction : public InventoryAction, public MoveAction IDropAction(std::istream &is); - IAction getType() const - { - return IAction::Drop; - } + IAction getType() const { return IAction::Drop; } void serialize(std::ostream &os) const { - os<<"Drop "; - os< &output_replacements, - bool decrementInput, IGameDef *gamedef); + std::vector &output_replacements, bool decrementInput, + IGameDef *gamedef); diff --git a/src/irrlicht_changes/CGUITTFont.cpp b/src/irrlicht_changes/CGUITTFont.cpp index bd4e700de..3bd6957f5 100644 --- a/src/irrlicht_changes/CGUITTFont.cpp +++ b/src/irrlicht_changes/CGUITTFont.cpp @@ -43,7 +43,7 @@ struct SGUITTFace : public virtual irr::IReferenceCounted { SGUITTFace() : face_buffer(0), face_buffer_size(0) { - memset((void*)&face, 0, sizeof(FT_Face)); + memset((void *)&face, 0, sizeof(FT_Face)); } ~SGUITTFace() @@ -53,15 +53,15 @@ struct SGUITTFace : public virtual irr::IReferenceCounted } FT_Face face; - FT_Byte* face_buffer; + FT_Byte *face_buffer; FT_Long face_buffer_size; }; // Static variables. FT_Library CGUITTFont::c_library; -core::map CGUITTFont::c_faces; +core::map CGUITTFont::c_faces; bool CGUITTFont::c_libraryLoaded = false; -scene::IMesh* CGUITTFont::shared_plane_ptr_ = 0; +scene::IMesh *CGUITTFont::shared_plane_ptr_ = 0; scene::SMesh CGUITTFont::shared_plane_; // @@ -72,14 +72,14 @@ scene::SMesh CGUITTFont::shared_plane_; inline void checkFontBitmapSize(const FT_Bitmap &bits) { if ((s32)bits.rows < 0 || (s32)bits.width < 0) { - std::cout << "Insane font glyph size. File: " - << __FILE__ << " Line " << __LINE__ - << std::endl; + std::cout << "Insane font glyph size. File: " << __FILE__ << " Line " + << __LINE__ << std::endl; abort(); } } -video::IImage* SGUITTGlyph::createGlyphImage(const FT_Bitmap& bits, video::IVideoDriver* driver) const +video::IImage *SGUITTGlyph::createGlyphImage( + const FT_Bitmap &bits, video::IVideoDriver *driver) const { // Make sure our casts to s32 in the loops below will not cause problems checkFontBitmapSize(bits); @@ -88,76 +88,83 @@ video::IImage* SGUITTGlyph::createGlyphImage(const FT_Bitmap& bits, video::IVide // Add 1 because textures are inclusive-exclusive. core::dimension2du d(bits.width + 1, bits.rows + 1); core::dimension2du texture_size; - //core::dimension2du texture_size(bits.width + 1, bits.rows + 1); + // core::dimension2du texture_size(bits.width + 1, bits.rows + 1); // Create and load our image now. - video::IImage* image = 0; - switch (bits.pixel_mode) - { - case FT_PIXEL_MODE_MONO: - { - // Create a blank image and fill it with transparent pixels. - texture_size = d.getOptimalSize(true, true); - image = driver->createImage(video::ECF_A1R5G5B5, texture_size); - image->fill(video::SColor(0, 255, 255, 255)); + video::IImage *image = 0; + switch (bits.pixel_mode) { + case FT_PIXEL_MODE_MONO: { + // Create a blank image and fill it with transparent pixels. + texture_size = d.getOptimalSize(true, true); + image = driver->createImage(video::ECF_A1R5G5B5, texture_size); + image->fill(video::SColor(0, 255, 255, 255)); - // Load the monochrome data in. - const u32 image_pitch = image->getPitch() / sizeof(u16); - u16* image_data = (u16*)image->lock(); - u8* glyph_data = bits.buffer; + // Load the monochrome data in. + const u32 image_pitch = image->getPitch() / sizeof(u16); + u16 *image_data = (u16 *)image->lock(); + u8 *glyph_data = bits.buffer; - for (s32 y = 0; y < (s32)bits.rows; ++y) - { - u16* row = image_data; - for (s32 x = 0; x < (s32)bits.width; ++x) - { - // Monochrome bitmaps store 8 pixels per byte. The left-most pixel is the bit 0x80. - // So, we go through the data each bit at a time. - if ((glyph_data[y * bits.pitch + (x / 8)] & (0x80 >> (x % 8))) != 0) - *row = 0xFFFF; - ++row; - } - image_data += image_pitch; + for (s32 y = 0; y < (s32)bits.rows; ++y) { + u16 *row = image_data; + for (s32 x = 0; x < (s32)bits.width; ++x) { + // Monochrome bitmaps store 8 pixels per byte. The + // left-most pixel is the bit 0x80. So, we go through the + // data each bit at a time. + if ((glyph_data[y * bits.pitch + (x / 8)] & + (0x80 >> (x % 8))) != 0) + *row = 0xFFFF; + ++row; } - image->unlock(); - break; + image_data += image_pitch; } + image->unlock(); + break; + } - case FT_PIXEL_MODE_GRAY: - { - // Create our blank image. - texture_size = d.getOptimalSize(!driver->queryFeature(video::EVDF_TEXTURE_NPOT), !driver->queryFeature(video::EVDF_TEXTURE_NSQUARE), true, 0); - image = driver->createImage(video::ECF_A8R8G8B8, texture_size); - image->fill(video::SColor(0, 255, 255, 255)); + case FT_PIXEL_MODE_GRAY: { + // Create our blank image. + texture_size = d.getOptimalSize( + !driver->queryFeature(video::EVDF_TEXTURE_NPOT), + !driver->queryFeature(video::EVDF_TEXTURE_NSQUARE), true, + 0); + image = driver->createImage(video::ECF_A8R8G8B8, texture_size); + image->fill(video::SColor(0, 255, 255, 255)); - // Load the grayscale data in. - const float gray_count = static_cast(bits.num_grays); - const u32 image_pitch = image->getPitch() / sizeof(u32); - u32* image_data = (u32*)image->lock(); - u8* glyph_data = bits.buffer; - for (s32 y = 0; y < (s32)bits.rows; ++y) - { - u8* row = glyph_data; - for (s32 x = 0; x < (s32)bits.width; ++x) - { - image_data[y * image_pitch + x] |= static_cast(255.0f * (static_cast(*row++) / gray_count)) << 24; - //data[y * image_pitch + x] |= ((u32)(*bitsdata++) << 24); - } - glyph_data += bits.pitch; + // Load the grayscale data in. + const float gray_count = static_cast(bits.num_grays); + const u32 image_pitch = image->getPitch() / sizeof(u32); + u32 *image_data = (u32 *)image->lock(); + u8 *glyph_data = bits.buffer; + for (s32 y = 0; y < (s32)bits.rows; ++y) { + u8 *row = glyph_data; + for (s32 x = 0; x < (s32)bits.width; ++x) { + image_data[y * image_pitch + x] |= + static_cast( + 255.0f * + (static_cast( + *row++) / + gray_count)) + << 24; + // data[y * image_pitch + x] |= ((u32)(*bitsdata++) << + // 24); } - image->unlock(); - break; + glyph_data += bits.pitch; } - default: - // TODO: error message? - return 0; + image->unlock(); + break; + } + default: + // TODO: error message? + return 0; } return image; } -void SGUITTGlyph::preload(u32 char_index, FT_Face face, video::IVideoDriver* driver, u32 font_size, const FT_Int32 loadFlags) +void SGUITTGlyph::preload(u32 char_index, FT_Face face, video::IVideoDriver *driver, + u32 font_size, const FT_Int32 loadFlags) { - if (isLoaded) return; + if (isLoaded) + return; // Set the size of the glyph. FT_Set_Pixel_Sizes(face, 0, font_size); @@ -175,11 +182,10 @@ void SGUITTGlyph::preload(u32 char_index, FT_Face face, video::IVideoDriver* dri offset = core::vector2di(glyph->bitmap_left, glyph->bitmap_top); // Try to get the last page with available slots. - CGUITTGlyphPage* page = parent->getLastGlyphPage(); + CGUITTGlyphPage *page = parent->getLastGlyphPage(); // If we need to make a new page, do that now. - if (!page) - { + if (!page) { page = parent->createGlyphPage(bits.pixel_mode); if (!page) // TODO: add error message? @@ -189,17 +195,20 @@ void SGUITTGlyph::preload(u32 char_index, FT_Face face, video::IVideoDriver* dri glyph_page = parent->getLastGlyphPageIndex(); u32 texture_side_length = page->texture->getOriginalSize().Width; core::vector2di page_position( - (page->used_slots % (texture_side_length / font_size)) * font_size, - (page->used_slots / (texture_side_length / font_size)) * font_size - ); + (page->used_slots % (texture_side_length / font_size)) * + font_size, + (page->used_slots / (texture_side_length / font_size)) * + font_size); source_rect.UpperLeftCorner = page_position; - source_rect.LowerRightCorner = core::vector2di(page_position.X + bits.width, page_position.Y + bits.rows); + source_rect.LowerRightCorner = core::vector2di( + page_position.X + bits.width, page_position.Y + bits.rows); page->dirty = true; ++page->used_slots; --page->available_slots; - // We grab the glyph bitmap here so the data won't be removed when the next glyph is loaded. + // We grab the glyph bitmap here so the data won't be removed when the next glyph + // is loaded. surface = createGlyphImage(bits, driver); // Set our glyph as loaded. @@ -208,8 +217,7 @@ void SGUITTGlyph::preload(u32 char_index, FT_Face face, video::IVideoDriver* dri void SGUITTGlyph::unload() { - if (surface) - { + if (surface) { surface->drop(); surface = 0; } @@ -218,19 +226,19 @@ void SGUITTGlyph::unload() ////////////////////// -CGUITTFont* CGUITTFont::createTTFont(IGUIEnvironment *env, const io::path& filename, const u32 size, const bool antialias, const bool transparency, const u32 shadow, const u32 shadow_alpha) +CGUITTFont *CGUITTFont::createTTFont(IGUIEnvironment *env, const io::path &filename, + const u32 size, const bool antialias, const bool transparency, + const u32 shadow, const u32 shadow_alpha) { - if (!c_libraryLoaded) - { + if (!c_libraryLoaded) { if (FT_Init_FreeType(&c_library)) return 0; c_libraryLoaded = true; } - CGUITTFont* font = new CGUITTFont(env); + CGUITTFont *font = new CGUITTFont(env); bool ret = font->load(filename, size, antialias, transparency); - if (!ret) - { + if (!ret) { font->drop(); return 0; } @@ -241,20 +249,19 @@ CGUITTFont* CGUITTFont::createTTFont(IGUIEnvironment *env, const io::path& filen return font; } -CGUITTFont* CGUITTFont::createTTFont(IrrlichtDevice *device, const io::path& filename, const u32 size, const bool antialias, const bool transparency) +CGUITTFont *CGUITTFont::createTTFont(IrrlichtDevice *device, const io::path &filename, + const u32 size, const bool antialias, const bool transparency) { - if (!c_libraryLoaded) - { + if (!c_libraryLoaded) { if (FT_Init_FreeType(&c_library)) return 0; c_libraryLoaded = true; } - CGUITTFont* font = new CGUITTFont(device->getGUIEnvironment()); + CGUITTFont *font = new CGUITTFont(device->getGUIEnvironment()); font->Device = device; bool ret = font->load(filename, size, antialias, transparency); - if (!ret) - { + if (!ret) { font->drop(); return 0; } @@ -262,12 +269,14 @@ CGUITTFont* CGUITTFont::createTTFont(IrrlichtDevice *device, const io::path& fil return font; } -CGUITTFont* CGUITTFont::create(IGUIEnvironment *env, const io::path& filename, const u32 size, const bool antialias, const bool transparency) +CGUITTFont *CGUITTFont::create(IGUIEnvironment *env, const io::path &filename, + const u32 size, const bool antialias, const bool transparency) { return CGUITTFont::createTTFont(env, filename, size, antialias, transparency); } -CGUITTFont* CGUITTFont::create(IrrlichtDevice *device, const io::path& filename, const u32 size, const bool antialias, const bool transparency) +CGUITTFont *CGUITTFont::create(IrrlichtDevice *device, const io::path &filename, + const u32 size, const bool antialias, const bool transparency) { return CGUITTFont::createTTFont(device, filename, size, antialias, transparency); } @@ -275,16 +284,16 @@ CGUITTFont* CGUITTFont::create(IrrlichtDevice *device, const io::path& filename, ////////////////////// //! Constructor. -CGUITTFont::CGUITTFont(IGUIEnvironment *env) -: use_monochrome(false), use_transparency(true), use_hinting(true), use_auto_hinting(true), -batch_load_size(1), Device(0), Environment(env), Driver(0), GlobalKerningWidth(0), GlobalKerningHeight(0) +CGUITTFont::CGUITTFont(IGUIEnvironment *env) : + use_monochrome(false), use_transparency(true), use_hinting(true), + use_auto_hinting(true), batch_load_size(1), Device(0), Environment(env), + Driver(0), GlobalKerningWidth(0), GlobalKerningHeight(0) { - #ifdef _DEBUG +#ifdef _DEBUG setDebugName("CGUITTFont"); - #endif +#endif - if (Environment) - { + if (Environment) { // don't grab environment, to avoid circular references Driver = Environment->getVideoDriver(); } @@ -294,19 +303,24 @@ batch_load_size(1), Device(0), Environment(env), Driver(0), GlobalKerningWidth(0 setInvisibleCharacters(L" "); - // Glyphs aren't reference counted, so don't try to delete them when we free the array. + // Glyphs aren't reference counted, so don't try to delete them when we free the + // array. Glyphs.set_free_when_destroyed(false); } -bool CGUITTFont::load(const io::path& filename, const u32 size, const bool antialias, const bool transparency) +bool CGUITTFont::load(const io::path &filename, const u32 size, const bool antialias, + const bool transparency) { // Some sanity checks. - if (Environment == 0 || Driver == 0) return false; - if (size == 0) return false; - if (filename.size() == 0) return false; + if (Environment == 0 || Driver == 0) + return false; + if (size == 0) + return false; + if (filename.size() == 0) + return false; - io::IFileSystem* filesystem = Environment->getFileSystem(); - irr::ILogger* logger = (Device != 0 ? Device->getLogger() : 0); + io::IFileSystem *filesystem = Environment->getFileSystem(); + irr::ILogger *logger = (Device != 0 ? Device->getLogger() : 0); this->size = size; this->filename = filename; @@ -317,23 +331,32 @@ bool CGUITTFont::load(const io::path& filename, const u32 size, const bool antia // Log. if (logger) - logger->log(L"CGUITTFont", core::stringw(core::stringw(L"Creating new font: ") + core::ustring(filename).toWCHAR_s() + L" " + core::stringc(size) + L"pt " + (antialias ? L"+antialias " : L"-antialias ") + (transparency ? L"+transparency" : L"-transparency")).c_str(), irr::ELL_INFORMATION); + logger->log(L"CGUITTFont", + core::stringw(core::stringw(L"Creating new font: ") + + core::ustring(filename).toWCHAR_s() + + L" " + core::stringc(size) + L"pt " + + (antialias ? L"+antialias " + : L"-antialias ") + + (transparency ? L"+transparency" + : L"-transparency")) + .c_str(), + irr::ELL_INFORMATION); // Grab the face. - SGUITTFace* face = 0; - core::map::Node* node = c_faces.find(filename); - if (node == 0) - { + SGUITTFace *face = 0; + core::map::Node *node = c_faces.find(filename); + if (node == 0) { face = new SGUITTFace(); c_faces.set(filename, face); - if (filesystem) - { + if (filesystem) { // Read in the file data. - io::IReadFile* file = filesystem->createAndOpenFile(filename); - if (file == 0) - { - if (logger) logger->log(L"CGUITTFont", L"Failed to open the file.", irr::ELL_INFORMATION); + io::IReadFile *file = filesystem->createAndOpenFile(filename); + if (file == 0) { + if (logger) + logger->log(L"CGUITTFont", + L"Failed to open the file.", + irr::ELL_INFORMATION); c_faces.remove(filename); delete face; @@ -346,22 +369,27 @@ bool CGUITTFont::load(const io::path& filename, const u32 size, const bool antia file->drop(); // Create the face. - if (FT_New_Memory_Face(c_library, face->face_buffer, face->face_buffer_size, 0, &face->face)) - { - if (logger) logger->log(L"CGUITTFont", L"FT_New_Memory_Face failed.", irr::ELL_INFORMATION); + if (FT_New_Memory_Face(c_library, face->face_buffer, + face->face_buffer_size, 0, &face->face)) { + if (logger) + logger->log(L"CGUITTFont", + L"FT_New_Memory_Face failed.", + irr::ELL_INFORMATION); c_faces.remove(filename); delete face; face = 0; return false; } - } - else - { + } else { core::ustring converter(filename); - if (FT_New_Face(c_library, reinterpret_cast(converter.toUTF8_s().c_str()), 0, &face->face)) - { - if (logger) logger->log(L"CGUITTFont", L"FT_New_Face failed.", irr::ELL_INFORMATION); + if (FT_New_Face(c_library, + reinterpret_cast( + converter.toUTF8_s().c_str()), + 0, &face->face)) { + if (logger) + logger->log(L"CGUITTFont", L"FT_New_Face failed.", + irr::ELL_INFORMATION); c_faces.remove(filename); delete face; @@ -369,9 +397,7 @@ bool CGUITTFont::load(const io::path& filename, const u32 size, const bool antia return false; } } - } - else - { + } else { // Using another instance of this face. face = node->getValue(); face->grab(); @@ -388,8 +414,7 @@ bool CGUITTFont::load(const io::path& filename, const u32 size, const bool antia Glyphs.clear(); Glyphs.reallocate(tt_face->num_glyphs); Glyphs.set_used(tt_face->num_glyphs); - for (FT_Long i = 0; i < tt_face->num_glyphs; ++i) - { + for (FT_Long i = 0; i < tt_face->num_glyphs; ++i) { Glyphs[i].isLoaded = false; Glyphs[i].glyph_page = 0; Glyphs[i].source_rect = core::recti(); @@ -413,21 +438,20 @@ CGUITTFont::~CGUITTFont() // Delete the glyphs and glyph pages. reset_images(); CGUITTAssistDelete::Delete(Glyphs); - //Glyphs.clear(); + // Glyphs.clear(); // We aren't using this face anymore. - core::map::Node* n = c_faces.find(filename); - if (n) - { - SGUITTFace* f = n->getValue(); + core::map::Node *n = c_faces.find(filename); + if (n) { + SGUITTFace *f = n->getValue(); - // Drop our face. If this was the last face, the destructor will clean up. + // Drop our face. If this was the last face, the destructor will clean + // up. if (f->drop()) c_faces.remove(filename); // If there are no more faces referenced by FreeType, clean up. - if (c_faces.size() == 0) - { + if (c_faces.size() == 0) { FT_Done_FreeType(c_library); c_libraryLoaded = false; } @@ -455,20 +479,18 @@ void CGUITTFont::reset_images() void CGUITTFont::update_glyph_pages() const { - for (u32 i = 0; i != Glyph_Pages.size(); ++i) - { + for (u32 i = 0; i != Glyph_Pages.size(); ++i) { if (Glyph_Pages[i]->dirty) Glyph_Pages[i]->updateTexture(); } } -CGUITTGlyphPage* CGUITTFont::getLastGlyphPage() const +CGUITTGlyphPage *CGUITTFont::getLastGlyphPage() const { - CGUITTGlyphPage* page = 0; + CGUITTGlyphPage *page = 0; if (Glyph_Pages.empty()) return 0; - else - { + else { page = Glyph_Pages[getLastGlyphPageIndex()]; if (page->available_slots == 0) page = 0; @@ -476,10 +498,10 @@ CGUITTGlyphPage* CGUITTFont::getLastGlyphPage() const return page; } -CGUITTGlyphPage* CGUITTFont::createGlyphPage(const u8& pixel_mode) +CGUITTGlyphPage *CGUITTFont::createGlyphPage(const u8 &pixel_mode) { - CGUITTGlyphPage* page = 0; - + CGUITTGlyphPage *page = 0; + // Name of our page. io::path name("TTFontGlyphPage_"); name += tt_face->family_name; @@ -488,7 +510,8 @@ CGUITTGlyphPage* CGUITTFont::createGlyphPage(const u8& pixel_mode) name += "."; name += size; name += "_"; - name += Glyph_Pages.size(); // The newly created page will be at the end of the collection. + name += Glyph_Pages.size(); // The newly created page will be at the end of the + // collection. // Create the new page. page = new CGUITTGlyphPage(Driver, name); @@ -503,13 +526,19 @@ CGUITTGlyphPage* CGUITTFont::createGlyphPage(const u8& pixel_mode) // We want to try to put at least 144 glyphs on a single texture. core::dimension2du page_texture_size; - if (size <= 21) page_texture_size = core::dimension2du(256, 256); - else if (size <= 42) page_texture_size = core::dimension2du(512, 512); - else if (size <= 84) page_texture_size = core::dimension2du(1024, 1024); - else if (size <= 168) page_texture_size = core::dimension2du(2048, 2048); - else page_texture_size = core::dimension2du(4096, 4096); + if (size <= 21) + page_texture_size = core::dimension2du(256, 256); + else if (size <= 42) + page_texture_size = core::dimension2du(512, 512); + else if (size <= 84) + page_texture_size = core::dimension2du(1024, 1024); + else if (size <= 168) + page_texture_size = core::dimension2du(2048, 2048); + else + page_texture_size = core::dimension2du(4096, 4096); - if (page_texture_size.Width > max_texture_size.Width || page_texture_size.Height > max_texture_size.Height) + if (page_texture_size.Width > max_texture_size.Width || + page_texture_size.Height > max_texture_size.Height) page_texture_size = max_texture_size; if (!page->createPageTexture(pixel_mode, page_texture_size)) { @@ -518,10 +547,11 @@ CGUITTGlyphPage* CGUITTFont::createGlyphPage(const u8& pixel_mode) return 0; } - if (page) - { - // Determine the number of glyph slots on the page and add it to the list of pages. - page->available_slots = (page_texture_size.Width / size) * (page_texture_size.Height / size); + if (page) { + // Determine the number of glyph slots on the page and add it to the list + // of pages. + page->available_slots = (page_texture_size.Width / size) * + (page_texture_size.Height / size); Glyph_Pages.push_back(page); } return page; @@ -546,12 +576,17 @@ void CGUITTFont::setFontHinting(const bool enable, const bool enable_auto_hintin reset_images(); } -void CGUITTFont::draw(const core::stringw& text, const core::rect& position, video::SColor color, bool hcenter, bool vcenter, const core::rect* clip) +void CGUITTFont::draw(const core::stringw &text, const core::rect &position, + video::SColor color, bool hcenter, bool vcenter, + const core::rect *clip) { - draw(EnrichedString(std::wstring(text.c_str()), color), position, color, hcenter, vcenter, clip); + draw(EnrichedString(std::wstring(text.c_str()), color), position, color, hcenter, + vcenter, clip); } -void CGUITTFont::draw(const EnrichedString &text, const core::rect& position, video::SColor color, bool hcenter, bool vcenter, const core::rect* clip) +void CGUITTFont::draw(const EnrichedString &text, const core::rect &position, + video::SColor color, bool hcenter, bool vcenter, + const core::rect *clip) { std::vector colors = text.getColors(); @@ -559,8 +594,7 @@ void CGUITTFont::draw(const EnrichedString &text, const core::rect& positio return; // Clear the glyph pages of their render information. - for (u32 i = 0; i < Glyph_Pages.size(); ++i) - { + for (u32 i = 0; i < Glyph_Pages.size(); ++i) { Glyph_Pages[i]->render_positions.clear(); Glyph_Pages[i]->render_source_rects.clear(); } @@ -570,62 +604,60 @@ void CGUITTFont::draw(const EnrichedString &text, const core::rect& positio core::position2d offset = position.UpperLeftCorner; // Determine offset positions. - if (hcenter || vcenter) - { + if (hcenter || vcenter) { textDimension = getDimension(text.c_str()); if (hcenter) - offset.X = ((position.getWidth() - textDimension.Width) >> 1) + offset.X; + offset.X = ((position.getWidth() - textDimension.Width) >> 1) + + offset.X; if (vcenter) - offset.Y = ((position.getHeight() - textDimension.Height) >> 1) + offset.Y; + offset.Y = ((position.getHeight() - textDimension.Height) >> 1) + + offset.Y; } // Convert to a unicode string. core::ustring utext = text.getString(); // Set up our render map. - core::map Render_Map; + core::map Render_Map; // Start parsing characters. u32 n; uchar32_t previousChar = 0; core::ustring::const_iterator iter(utext); std::vector applied_colors; - while (!iter.atEnd()) - { + while (!iter.atEnd()) { uchar32_t currentChar = *iter; n = getGlyphIndexByChar(currentChar); bool visible = (Invisible.findFirst(currentChar) == -1); - bool lineBreak=false; + bool lineBreak = false; if (currentChar == L'\r') // Mac or Windows breaks { lineBreak = true; - if (*(iter + 1) == (uchar32_t)'\n') // Windows line breaks. + if (*(iter + 1) == (uchar32_t)'\n') // Windows line breaks. currentChar = *(++iter); - } - else if (currentChar == (uchar32_t)'\n') // Unix breaks + } else if (currentChar == (uchar32_t)'\n') // Unix breaks { lineBreak = true; } - if (lineBreak) - { + if (lineBreak) { previousChar = 0; offset.Y += font_metrics.height / 64; offset.X = position.UpperLeftCorner.X; if (hcenter) - offset.X += (position.getWidth() - textDimension.Width) >> 1; + offset.X += (position.getWidth() - textDimension.Width) >> + 1; ++iter; continue; } - if (n > 0 && visible) - { + if (n > 0 && visible) { // Calculate the glyph offset. - s32 offx = Glyphs[n-1].offset.X; - s32 offy = (font_metrics.ascender / 64) - Glyphs[n-1].offset.Y; + s32 offx = Glyphs[n - 1].offset.X; + s32 offy = (font_metrics.ascender / 64) - Glyphs[n - 1].offset.Y; // Apply kerning. core::vector2di k = getKerning(currentChar, previousChar); @@ -633,9 +665,10 @@ void CGUITTFont::draw(const EnrichedString &text, const core::rect& positio offset.Y += k.Y; // Determine rendering information. - SGUITTGlyph& glyph = Glyphs[n-1]; - CGUITTGlyphPage* const page = Glyph_Pages[glyph.glyph_page]; - page->render_positions.push_back(core::position2di(offset.X + offx, offset.Y + offy)); + SGUITTGlyph &glyph = Glyphs[n - 1]; + CGUITTGlyphPage *const page = Glyph_Pages[glyph.glyph_page]; + page->render_positions.push_back(core::position2di( + offset.X + offx, offset.Y + offy)); page->render_source_rects.push_back(glyph.source_rect); Render_Map.set(glyph.glyph_page, page); u32 current_color = iter.getPos(); @@ -650,21 +683,25 @@ void CGUITTFont::draw(const EnrichedString &text, const core::rect& positio // Draw now. update_glyph_pages(); - core::map::Iterator j = Render_Map.getIterator(); - while (!j.atEnd()) - { - core::map::Node* n = j.getNode(); + core::map::Iterator j = Render_Map.getIterator(); + while (!j.atEnd()) { + core::map::Node *n = j.getNode(); j++; - if (n == 0) continue; + if (n == 0) + continue; - CGUITTGlyphPage* page = n->getValue(); + CGUITTGlyphPage *page = n->getValue(); if (shadow_offset) { for (size_t i = 0; i < page->render_positions.size(); ++i) - page->render_positions[i] += core::vector2di(shadow_offset, shadow_offset); - Driver->draw2DImageBatch(page->texture, page->render_positions, page->render_source_rects, clip, video::SColor(shadow_alpha,0,0,0), true); + page->render_positions[i] += core::vector2di( + shadow_offset, shadow_offset); + Driver->draw2DImageBatch(page->texture, page->render_positions, + page->render_source_rects, clip, + video::SColor(shadow_alpha, 0, 0, 0), true); for (size_t i = 0; i < page->render_positions.size(); ++i) - page->render_positions[i] -= core::vector2di(shadow_offset, shadow_offset); + page->render_positions[i] -= core::vector2di( + shadow_offset, shadow_offset); } for (size_t i = 0; i < page->render_positions.size(); ++i) { irr::video::SColor col; @@ -675,22 +712,24 @@ void CGUITTFont::draw(const EnrichedString &text, const core::rect& positio } if (!use_transparency) col.color |= 0xff000000; - Driver->draw2DImage(page->texture, page->render_positions[i], page->render_source_rects[i], clip, col, true); + Driver->draw2DImage(page->texture, page->render_positions[i], + page->render_source_rects[i], clip, col, true); } } } core::dimension2d CGUITTFont::getCharDimension(const wchar_t ch) const { - return core::dimension2d(getWidthFromCharacter(ch), getHeightFromCharacter(ch)); + return core::dimension2d( + getWidthFromCharacter(ch), getHeightFromCharacter(ch)); } -core::dimension2d CGUITTFont::getDimension(const wchar_t* text) const +core::dimension2d CGUITTFont::getDimension(const wchar_t *text) const { return getDimension(core::ustring(text)); } -core::dimension2d CGUITTFont::getDimension(const core::ustring& text) const +core::dimension2d CGUITTFont::getDimension(const core::ustring &text) const { // Get the maximum font height. Unfortunately, we have to do this hack as // Irrlicht will draw things wrong. In FreeType, the font size is the @@ -709,20 +748,17 @@ core::dimension2d CGUITTFont::getDimension(const core::ustring& text) const uchar32_t previousChar = 0; core::ustring::const_iterator iter = text.begin(); - for (; !iter.atEnd(); ++iter) - { + for (; !iter.atEnd(); ++iter) { uchar32_t p = *iter; bool lineBreak = false; - if (p == '\r') // Mac or Windows line breaks. + if (p == '\r') // Mac or Windows line breaks. { lineBreak = true; - if (*(iter + 1) == '\n') - { + if (*(iter + 1) == '\n') { ++iter; p = *iter; } - } - else if (p == '\n') // Unix line breaks. + } else if (p == '\n') // Unix line breaks. { lineBreak = true; } @@ -733,8 +769,7 @@ core::dimension2d CGUITTFont::getDimension(const core::ustring& text) const previousChar = p; // Check for linebreak. - if (lineBreak) - { + if (lineBreak) { previousChar = 0; text_dimension.Height += line.Height; if (text_dimension.Width < line.Width) @@ -759,18 +794,19 @@ inline u32 CGUITTFont::getWidthFromCharacter(wchar_t c) const inline u32 CGUITTFont::getWidthFromCharacter(uchar32_t c) const { // Set the size of the face. - // This is because we cache faces and the face may have been set to a different size. - //FT_Set_Pixel_Sizes(tt_face, 0, size); + // This is because we cache faces and the face may have been set to a different + // size. + // FT_Set_Pixel_Sizes(tt_face, 0, size); u32 n = getGlyphIndexByChar(c); - if (n > 0) - { - int w = Glyphs[n-1].advance.x / 64; + if (n > 0) { + int w = Glyphs[n - 1].advance.x / 64; return w; } if (c >= 0x2000) return (font_metrics.ascender / 64); - else return (font_metrics.ascender / 64) / 2; + else + return (font_metrics.ascender / 64) / 2; } inline u32 CGUITTFont::getHeightFromCharacter(wchar_t c) const @@ -781,19 +817,22 @@ inline u32 CGUITTFont::getHeightFromCharacter(wchar_t c) const inline u32 CGUITTFont::getHeightFromCharacter(uchar32_t c) const { // Set the size of the face. - // This is because we cache faces and the face may have been set to a different size. - //FT_Set_Pixel_Sizes(tt_face, 0, size); + // This is because we cache faces and the face may have been set to a different + // size. + // FT_Set_Pixel_Sizes(tt_face, 0, size); u32 n = getGlyphIndexByChar(c); - if (n > 0) - { - // Grab the true height of the character, taking into account underhanging glyphs. - s32 height = (font_metrics.ascender / 64) - Glyphs[n-1].offset.Y + Glyphs[n-1].source_rect.getHeight(); + if (n > 0) { + // Grab the true height of the character, taking into account underhanging + // glyphs. + s32 height = (font_metrics.ascender / 64) - Glyphs[n - 1].offset.Y + + Glyphs[n - 1].source_rect.getHeight(); return height; } if (c >= 0x2000) return (font_metrics.ascender / 64); - else return (font_metrics.ascender / 64) / 2; + else + return (font_metrics.ascender / 64) / 2; } u32 CGUITTFont::getGlyphIndexByChar(wchar_t c) const @@ -806,9 +845,11 @@ u32 CGUITTFont::getGlyphIndexByChar(uchar32_t c) const // Get the glyph. u32 glyph = FT_Get_Char_Index(tt_face, c); - // Check for a valid glyph. If it is invalid, attempt to use the replacement character. + // Check for a valid glyph. If it is invalid, attempt to use the replacement + // character. if (glyph == 0) - glyph = FT_Get_Char_Index(tt_face, core::unicode::UTF_REPLACEMENT_CHARACTER); + glyph = FT_Get_Char_Index( + tt_face, core::unicode::UTF_REPLACEMENT_CHARACTER); // If our glyph is already loaded, don't bother doing any batch loading code. if (glyph != 0 && Glyphs[glyph - 1].isLoaded) @@ -817,47 +858,44 @@ u32 CGUITTFont::getGlyphIndexByChar(uchar32_t c) const // Determine our batch loading positions. u32 half_size = (batch_load_size / 2); u32 start_pos = 0; - if (c > half_size) start_pos = c - half_size; + if (c > half_size) + start_pos = c - half_size; u32 end_pos = start_pos + batch_load_size; // Load all our characters. - do - { + do { // Get the character we are going to load. u32 char_index = FT_Get_Char_Index(tt_face, start_pos); // If the glyph hasn't been loaded yet, do it now. - if (char_index) - { - SGUITTGlyph& glyph = Glyphs[char_index - 1]; - if (!glyph.isLoaded) - { - glyph.preload(char_index, tt_face, Driver, size, load_flags); + if (char_index) { + SGUITTGlyph &glyph = Glyphs[char_index - 1]; + if (!glyph.isLoaded) { + glyph.preload(char_index, tt_face, Driver, size, + load_flags); Glyph_Pages[glyph.glyph_page]->pushGlyphToBePaged(&glyph); } } - } - while (++start_pos < end_pos); + } while (++start_pos < end_pos); // Return our original character. return glyph; } -s32 CGUITTFont::getCharacterFromPos(const wchar_t* text, s32 pixel_x) const +s32 CGUITTFont::getCharacterFromPos(const wchar_t *text, s32 pixel_x) const { return getCharacterFromPos(core::ustring(text), pixel_x); } -s32 CGUITTFont::getCharacterFromPos(const core::ustring& text, s32 pixel_x) const +s32 CGUITTFont::getCharacterFromPos(const core::ustring &text, s32 pixel_x) const { s32 x = 0; - //s32 idx = 0; + // s32 idx = 0; u32 character = 0; uchar32_t previousChar = 0; core::ustring::const_iterator iter = text.begin(); - while (!iter.atEnd()) - { + while (!iter.atEnd()) { uchar32_t c = *iter; x += getWidthFromCharacter(c); @@ -886,7 +924,8 @@ void CGUITTFont::setKerningHeight(s32 kerning) GlobalKerningHeight = kerning; } -s32 CGUITTFont::getKerningWidth(const wchar_t* thisLetter, const wchar_t* previousLetter) const +s32 CGUITTFont::getKerningWidth( + const wchar_t *thisLetter, const wchar_t *previousLetter) const { if (tt_face == 0) return GlobalKerningWidth; @@ -896,7 +935,8 @@ s32 CGUITTFont::getKerningWidth(const wchar_t* thisLetter, const wchar_t* previo return getKerningWidth((uchar32_t)*thisLetter, (uchar32_t)*previousLetter); } -s32 CGUITTFont::getKerningWidth(const uchar32_t thisLetter, const uchar32_t previousLetter) const +s32 CGUITTFont::getKerningWidth( + const uchar32_t thisLetter, const uchar32_t previousLetter) const { // Return only the kerning width. return getKerning(thisLetter, previousLetter).X; @@ -908,18 +948,21 @@ s32 CGUITTFont::getKerningHeight() const return GlobalKerningHeight; } -core::vector2di CGUITTFont::getKerning(const wchar_t thisLetter, const wchar_t previousLetter) const +core::vector2di CGUITTFont::getKerning( + const wchar_t thisLetter, const wchar_t previousLetter) const { return getKerning((uchar32_t)thisLetter, (uchar32_t)previousLetter); } -core::vector2di CGUITTFont::getKerning(const uchar32_t thisLetter, const uchar32_t previousLetter) const +core::vector2di CGUITTFont::getKerning( + const uchar32_t thisLetter, const uchar32_t previousLetter) const { if (tt_face == 0 || thisLetter == 0 || previousLetter == 0) return core::vector2di(); // Set the size of the face. - // This is because we cache faces and the face may have been set to a different size. + // This is because we cache faces and the face may have been set to a different + // size. FT_Set_Pixel_Sizes(tt_face, 0, size); core::vector2di ret(GlobalKerningWidth, GlobalKerningHeight); @@ -930,17 +973,15 @@ core::vector2di CGUITTFont::getKerning(const uchar32_t thisLetter, const uchar32 // Get the kerning information. FT_Vector v; - FT_Get_Kerning(tt_face, getGlyphIndexByChar(previousLetter), getGlyphIndexByChar(thisLetter), FT_KERNING_DEFAULT, &v); + FT_Get_Kerning(tt_face, getGlyphIndexByChar(previousLetter), + getGlyphIndexByChar(thisLetter), FT_KERNING_DEFAULT, &v); // If we have a scalable font, the return value will be in font points. - if (FT_IS_SCALABLE(tt_face)) - { + if (FT_IS_SCALABLE(tt_face)) { // Font points, so divide by 64. ret.X += (v.x / 64); ret.Y += (v.y / 64); - } - else - { + } else { // Pixel units. ret.X += v.x; ret.Y += v.y; @@ -954,43 +995,44 @@ void CGUITTFont::setInvisibleCharacters(const wchar_t *s) Invisible = us; } -void CGUITTFont::setInvisibleCharacters(const core::ustring& s) +void CGUITTFont::setInvisibleCharacters(const core::ustring &s) { Invisible = s; } -video::IImage* CGUITTFont::createTextureFromChar(const uchar32_t& ch) +video::IImage *CGUITTFont::createTextureFromChar(const uchar32_t &ch) { u32 n = getGlyphIndexByChar(ch); - const SGUITTGlyph& glyph = Glyphs[n-1]; - CGUITTGlyphPage* page = Glyph_Pages[glyph.glyph_page]; + const SGUITTGlyph &glyph = Glyphs[n - 1]; + CGUITTGlyphPage *page = Glyph_Pages[glyph.glyph_page]; if (page->dirty) page->updateTexture(); - video::ITexture* tex = page->texture; + video::ITexture *tex = page->texture; - // Acquire a read-only lock of the corresponding page texture. - #if IRRLICHT_VERSION_MAJOR==1 && IRRLICHT_VERSION_MINOR>=8 - void* ptr = tex->lock(video::ETLM_READ_ONLY); - #else - void* ptr = tex->lock(true); - #endif +// Acquire a read-only lock of the corresponding page texture. +#if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR >= 8 + void *ptr = tex->lock(video::ETLM_READ_ONLY); +#else + void *ptr = tex->lock(true); +#endif video::ECOLOR_FORMAT format = tex->getColorFormat(); core::dimension2du tex_size = tex->getOriginalSize(); - video::IImage* pageholder = Driver->createImageFromData(format, tex_size, ptr, true, false); + video::IImage *pageholder = + Driver->createImageFromData(format, tex_size, ptr, true, false); // Copy the image data out of the page texture. core::dimension2du glyph_size(glyph.source_rect.getSize()); - video::IImage* image = Driver->createImage(format, glyph_size); + video::IImage *image = Driver->createImage(format, glyph_size); pageholder->copyTo(image, core::position2di(0, 0), glyph.source_rect); tex->unlock(); return image; } -video::ITexture* CGUITTFont::getPageTextureByIndex(const u32& page_index) const +video::ITexture *CGUITTFont::getPageTextureByIndex(const u32 &page_index) const { if (page_index < Glyph_Pages.size()) return Glyph_Pages[page_index]->texture; @@ -1012,60 +1054,71 @@ void CGUITTFont::createSharedPlane() using namespace video; using namespace scene; S3DVertex vertices[4]; - u16 indices[6] = {0,2,3,3,1,0}; - vertices[0] = S3DVertex(vector3df(0,-1,0), vector3df(0,0,-1), SColor(255,255,255,255), vector2df(0,1)); - vertices[1] = S3DVertex(vector3df(1,-1,0), vector3df(0,0,-1), SColor(255,255,255,255), vector2df(1,1)); - vertices[2] = S3DVertex(vector3df(0, 0,0), vector3df(0,0,-1), SColor(255,255,255,255), vector2df(0,0)); - vertices[3] = S3DVertex(vector3df(1, 0,0), vector3df(0,0,-1), SColor(255,255,255,255), vector2df(1,0)); + u16 indices[6] = {0, 2, 3, 3, 1, 0}; + vertices[0] = S3DVertex(vector3df(0, -1, 0), vector3df(0, 0, -1), + SColor(255, 255, 255, 255), vector2df(0, 1)); + vertices[1] = S3DVertex(vector3df(1, -1, 0), vector3df(0, 0, -1), + SColor(255, 255, 255, 255), vector2df(1, 1)); + vertices[2] = S3DVertex(vector3df(0, 0, 0), vector3df(0, 0, -1), + SColor(255, 255, 255, 255), vector2df(0, 0)); + vertices[3] = S3DVertex(vector3df(1, 0, 0), vector3df(0, 0, -1), + SColor(255, 255, 255, 255), vector2df(1, 0)); - SMeshBuffer* buf = new SMeshBuffer(); + SMeshBuffer *buf = new SMeshBuffer(); buf->append(vertices, 4, indices, 6); - shared_plane_.addMeshBuffer( buf ); + shared_plane_.addMeshBuffer(buf); shared_plane_ptr_ = &shared_plane_; - buf->drop(); //the addMeshBuffer method will grab it, so we can drop this ptr. + buf->drop(); // the addMeshBuffer method will grab it, so we can drop this ptr. } -core::dimension2d CGUITTFont::getDimensionUntilEndOfLine(const wchar_t* p) const +core::dimension2d CGUITTFont::getDimensionUntilEndOfLine(const wchar_t *p) const { core::stringw s; - for (const wchar_t* temp = p; temp && *temp != '\0' && *temp != L'\r' && *temp != L'\n'; ++temp ) + for (const wchar_t *temp = p; + temp && *temp != '\0' && *temp != L'\r' && *temp != L'\n'; ++temp) s.append(*temp); return getDimension(s.c_str()); } -core::array CGUITTFont::addTextSceneNode(const wchar_t* text, scene::ISceneManager* smgr, scene::ISceneNode* parent, const video::SColor& color, bool center) +core::array CGUITTFont::addTextSceneNode(const wchar_t *text, + scene::ISceneManager *smgr, scene::ISceneNode *parent, + const video::SColor &color, bool center) { using namespace core; using namespace video; using namespace scene; - array container; + array container; - if (!Driver || !smgr) return container; + if (!Driver || !smgr) + return container; if (!parent) parent = smgr->addEmptySceneNode(smgr->getRootSceneNode(), -1); // if you don't specify parent, then we add a empty node attached to the root node // this is generally undesirable. - if (!shared_plane_ptr_) //this points to a static mesh that contains the plane - createSharedPlane(); //if it's not initialized, we create one. + if (!shared_plane_ptr_) // this points to a static mesh that contains the plane + createSharedPlane(); // if it's not initialized, we create one. - dimension2d text_size(getDimension(text)); //convert from unsigned to signed. + dimension2d text_size(getDimension(text)); // convert from unsigned to + // signed. vector3df start_point(0, 0, 0), offset; /** NOTICE: - Because we are considering adding texts into 3D world, all Y axis vectors are inverted. + Because we are considering adding texts into 3D world, all Y axis vectors + are inverted. **/ - // There's currently no "vertical center" concept when you apply text scene node to the 3D world. - if (center) - { + // There's currently no "vertical center" concept when you apply text scene node + // to the 3D world. + if (center) { offset.X = start_point.X = -text_size.Width / 2.f; - offset.Y = start_point.Y = +text_size.Height/ 2.f; - offset.X += (text_size.Width - getDimensionUntilEndOfLine(text).Width) >> 1; + offset.Y = start_point.Y = +text_size.Height / 2.f; + offset.X += (text_size.Width - getDimensionUntilEndOfLine(text).Width) >> + 1; } // the default font material @@ -1074,7 +1127,8 @@ core::array CGUITTFont::addTextSceneNode(const wchar_t* text mat.setFlag(video::EMF_ZWRITE_ENABLE, false); mat.setFlag(video::EMF_NORMALIZE_NORMALS, true); mat.ColorMaterial = video::ECM_NONE; - mat.MaterialType = use_transparency ? video::EMT_TRANSPARENT_ALPHA_CHANNEL : video::EMT_SOLID; + mat.MaterialType = use_transparency ? video::EMT_TRANSPARENT_ALPHA_CHANNEL + : video::EMT_SOLID; mat.MaterialTypeParam = 0.01f; mat.DiffuseColor = color; @@ -1083,39 +1137,36 @@ core::array CGUITTFont::addTextSceneNode(const wchar_t* text array glyph_indices; - while (*text) - { + while (*text) { current_char = *text; - bool line_break=false; + bool line_break = false; if (current_char == L'\r') // Mac or Windows breaks { line_break = true; if (*(text + 1) == L'\n') // Windows line breaks. current_char = *(++text); - } - else if (current_char == L'\n') // Unix breaks + } else if (current_char == L'\n') // Unix breaks { line_break = true; } - if (line_break) - { + if (line_break) { previous_char = 0; offset.Y -= tt_face->size->metrics.ascender / 64; offset.X = start_point.X; if (center) - offset.X += (text_size.Width - getDimensionUntilEndOfLine(text+1).Width) >> 1; + offset.X += (text_size.Width - getDimensionUntilEndOfLine( + text + 1) + .Width) >> + 1; ++text; - } - else - { + } else { n = getGlyphIndexByChar(current_char); - if (n > 0) - { - glyph_indices.push_back( n ); + if (n > 0) { + glyph_indices.push_back(n); // Store glyph size and offset informations. - SGUITTGlyph const& glyph = Glyphs[n-1]; + SGUITTGlyph const &glyph = Glyphs[n - 1]; u32 texw = glyph.source_rect.getWidth(); u32 texh = glyph.source_rect.getHeight(); s32 offx = glyph.offset.X; @@ -1126,25 +1177,37 @@ core::array CGUITTFont::addTextSceneNode(const wchar_t* text offset.X += k.X; offset.Y += k.Y; - vector3df current_pos(offset.X + offx, offset.Y - offy, 0); - dimension2d letter_size = dimension2d(texw, texh); + vector3df current_pos( + offset.X + offx, offset.Y - offy, 0); + dimension2d letter_size = + dimension2d(texw, texh); // Now we copy planes corresponding to the letter size. - IMeshManipulator* mani = smgr->getMeshManipulator(); - IMesh* meshcopy = mani->createMeshCopy(shared_plane_ptr_); - #if IRRLICHT_VERSION_MAJOR==1 && IRRLICHT_VERSION_MINOR>=8 - mani->scale(meshcopy, vector3df((f32)letter_size.Width, (f32)letter_size.Height, 1)); - #else - mani->scaleMesh(meshcopy, vector3df((f32)letter_size.Width, (f32)letter_size.Height, 1)); - #endif + IMeshManipulator *mani = smgr->getMeshManipulator(); + IMesh *meshcopy = mani->createMeshCopy(shared_plane_ptr_); +#if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR >= 8 + mani->scale(meshcopy, + vector3df((f32)letter_size.Width, + (f32)letter_size.Height, + 1)); +#else + mani->scaleMesh(meshcopy, + vector3df((f32)letter_size.Width, + (f32)letter_size.Height, + 1)); +#endif - ISceneNode* current_node = smgr->addMeshSceneNode(meshcopy, parent, -1, current_pos); + ISceneNode *current_node = smgr->addMeshSceneNode( + meshcopy, parent, -1, current_pos); meshcopy->drop(); current_node->getMaterial(0) = mat; current_node->setAutomaticCulling(EAC_OFF); - current_node->setIsDebugObject(true); //so the picking won't have any effect on individual letter - //current_node->setDebugDataVisible(EDS_BBOX); //de-comment this when debugging + current_node->setIsDebugObject( + true); // so the picking won't have any + // effect on individual letter + // current_node->setDebugDataVisible(EDS_BBOX); + // //de-comment this when debugging container.push_back(current_node); } @@ -1155,33 +1218,37 @@ core::array CGUITTFont::addTextSceneNode(const wchar_t* text } update_glyph_pages(); - //only after we update the textures can we use the glyph page textures. + // only after we update the textures can we use the glyph page textures. - for (u32 i = 0; i < glyph_indices.size(); ++i) - { + for (u32 i = 0; i < glyph_indices.size(); ++i) { u32 n = glyph_indices[i]; - SGUITTGlyph const& glyph = Glyphs[n-1]; - ITexture* current_tex = Glyph_Pages[glyph.glyph_page]->texture; + SGUITTGlyph const &glyph = Glyphs[n - 1]; + ITexture *current_tex = Glyph_Pages[glyph.glyph_page]->texture; f32 page_texture_size = (f32)current_tex->getSize().Width; - //Now we calculate the UV position according to the texture size and the source rect. + // Now we calculate the UV position according to the texture size and the + // source rect. // // 2___3 // | /| - // | / | <-- plane mesh is like this, point 2 is (0,0), point 0 is (0, -1) - // |/ | <-- the texture coords of point 2 is (0,0, point 0 is (0, 1) - // 0---1 + // | / | <-- plane mesh is like this, point 2 is (0,0), point 0 is (0, + // -1) + // |/ | <-- the texture coords of point 2 is (0,0, point 0 is (0, + // 1) 0---1 // f32 u1 = glyph.source_rect.UpperLeftCorner.X / page_texture_size; f32 u2 = u1 + (glyph.source_rect.getWidth() / page_texture_size); f32 v1 = glyph.source_rect.UpperLeftCorner.Y / page_texture_size; f32 v2 = v1 + (glyph.source_rect.getHeight() / page_texture_size); - //we can be quite sure that this is IMeshSceneNode, because we just added them in the above loop. - IMeshSceneNode* node = static_cast(container[i]); + // we can be quite sure that this is IMeshSceneNode, because we just added + // them in the above loop. + IMeshSceneNode *node = static_cast(container[i]); - S3DVertex* pv = static_cast(node->getMesh()->getMeshBuffer(0)->getVertices()); - //pv[0].TCoords.Y = pv[1].TCoords.Y = (letter_size.Height - 1) / static_cast(letter_size.Height); - //pv[1].TCoords.X = pv[3].TCoords.X = (letter_size.Width - 1) / static_cast(letter_size.Width); + S3DVertex *pv = static_cast( + node->getMesh()->getMeshBuffer(0)->getVertices()); + // pv[0].TCoords.Y = pv[1].TCoords.Y = (letter_size.Height - 1) / + // static_cast(letter_size.Height); pv[1].TCoords.X = pv[3].TCoords.X + // = (letter_size.Width - 1) / static_cast(letter_size.Width); pv[0].TCoords = vector2df(u1, v2); pv[1].TCoords = vector2df(u2, v2); pv[2].TCoords = vector2df(u1, v1); diff --git a/src/irrlicht_changes/CGUITTFont.h b/src/irrlicht_changes/CGUITTFont.h index cf64934a2..bccc5b2f8 100644 --- a/src/irrlicht_changes/CGUITTFont.h +++ b/src/irrlicht_changes/CGUITTFont.h @@ -42,352 +42,393 @@ namespace irr { namespace gui { - struct SGUITTFace; - class CGUITTFont; +struct SGUITTFace; +class CGUITTFont; - //! Class to assist in deleting glyphs. - class CGUITTAssistDelete +//! Class to assist in deleting glyphs. +class CGUITTAssistDelete +{ +public: + template static void Delete(core::array &a) { - public: - template - static void Delete(core::array& a) - { - TAlloc allocator; - allocator.deallocate(a.pointer()); - } - }; + TAlloc allocator; + allocator.deallocate(a.pointer()); + } +}; - //! Structure representing a single TrueType glyph. - struct SGUITTGlyph +//! Structure representing a single TrueType glyph. +struct SGUITTGlyph +{ + //! Constructor. + SGUITTGlyph() : isLoaded(false), glyph_page(0), surface(0), parent(0) {} + + //! Destructor. + ~SGUITTGlyph() { unload(); } + + //! Preload the glyph. + //! The preload process occurs when the program tries to cache the glyph from + //!FT_Library. + //! However, it simply defines the SGUITTGlyph's properties and will only create + //! the page textures if necessary. The actual creation of the textures should + //! only occur right before the batch draw call. + void preload(u32 char_index, FT_Face face, video::IVideoDriver *driver, + u32 font_size, const FT_Int32 loadFlags); + + //! Unloads the glyph. + void unload(); + + //! Creates the IImage object from the FT_Bitmap. + video::IImage *createGlyphImage( + const FT_Bitmap &bits, video::IVideoDriver *driver) const; + + //! If true, the glyph has been loaded. + bool isLoaded; + + //! The page the glyph is on. + u32 glyph_page; + + //! The source rectangle for the glyph. + core::recti source_rect; + + //! The offset of glyph when drawn. + core::vector2di offset; + + //! Glyph advance information. + FT_Vector advance; + + //! This is just the temporary image holder. After this glyph is paged, + //! it will be dropped. + mutable video::IImage *surface; + + //! The pointer pointing to the parent (CGUITTFont) + CGUITTFont *parent; +}; + +//! Holds a sheet of glyphs. +class CGUITTGlyphPage +{ +public: + CGUITTGlyphPage(video::IVideoDriver *Driver, const io::path &texture_name) : + texture(0), available_slots(0), used_slots(0), dirty(false), + driver(Driver), name(texture_name) { - //! Constructor. - SGUITTGlyph() : isLoaded(false), glyph_page(0), surface(0), parent(0) {} - - //! Destructor. - ~SGUITTGlyph() { unload(); } - - //! Preload the glyph. - //! The preload process occurs when the program tries to cache the glyph from FT_Library. - //! However, it simply defines the SGUITTGlyph's properties and will only create the page - //! textures if necessary. The actual creation of the textures should only occur right - //! before the batch draw call. - void preload(u32 char_index, FT_Face face, video::IVideoDriver* driver, u32 font_size, const FT_Int32 loadFlags); - - //! Unloads the glyph. - void unload(); - - //! Creates the IImage object from the FT_Bitmap. - video::IImage* createGlyphImage(const FT_Bitmap& bits, video::IVideoDriver* driver) const; - - //! If true, the glyph has been loaded. - bool isLoaded; - - //! The page the glyph is on. - u32 glyph_page; - - //! The source rectangle for the glyph. - core::recti source_rect; - - //! The offset of glyph when drawn. - core::vector2di offset; - - //! Glyph advance information. - FT_Vector advance; - - //! This is just the temporary image holder. After this glyph is paged, - //! it will be dropped. - mutable video::IImage* surface; - - //! The pointer pointing to the parent (CGUITTFont) - CGUITTFont* parent; - }; - - //! Holds a sheet of glyphs. - class CGUITTGlyphPage + } + ~CGUITTGlyphPage() { - public: - CGUITTGlyphPage(video::IVideoDriver* Driver, const io::path& texture_name) :texture(0), available_slots(0), used_slots(0), dirty(false), driver(Driver), name(texture_name) {} - ~CGUITTGlyphPage() - { - if (texture) - { - if (driver) - driver->removeTexture(texture); - else texture->drop(); - } - } + if (texture) { + if (driver) + driver->removeTexture(texture); + else + texture->drop(); + } + } - //! Create the actual page texture, - bool createPageTexture(const u8& pixel_mode, const core::dimension2du& texture_size) - { - if( texture ) - return false; + //! Create the actual page texture, + bool createPageTexture( + const u8 &pixel_mode, const core::dimension2du &texture_size) + { + if (texture) + return false; - bool flgmip = driver->getTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS); - driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, false); + bool flgmip = driver->getTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS); + driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, false); #if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR > 8 - bool flgcpy = driver->getTextureCreationFlag(video::ETCF_ALLOW_MEMORY_COPY); - driver->setTextureCreationFlag(video::ETCF_ALLOW_MEMORY_COPY, true); + bool flgcpy = driver->getTextureCreationFlag( + video::ETCF_ALLOW_MEMORY_COPY); + driver->setTextureCreationFlag(video::ETCF_ALLOW_MEMORY_COPY, true); #endif - // Set the texture color format. - switch (pixel_mode) - { - case FT_PIXEL_MODE_MONO: - texture = driver->addTexture(texture_size, name, video::ECF_A1R5G5B5); - break; - case FT_PIXEL_MODE_GRAY: - default: - texture = driver->addTexture(texture_size, name, video::ECF_A8R8G8B8); - break; - } + // Set the texture color format. + switch (pixel_mode) { + case FT_PIXEL_MODE_MONO: + texture = driver->addTexture( + texture_size, name, video::ECF_A1R5G5B5); + break; + case FT_PIXEL_MODE_GRAY: + default: + texture = driver->addTexture( + texture_size, name, video::ECF_A8R8G8B8); + break; + } - // Restore our texture creation flags. - driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, flgmip); + // Restore our texture creation flags. + driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, flgmip); #if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR > 8 - driver->setTextureCreationFlag(video::ETCF_ALLOW_MEMORY_COPY, flgcpy); + driver->setTextureCreationFlag(video::ETCF_ALLOW_MEMORY_COPY, flgcpy); #endif - return texture ? true : false; - } + return texture ? true : false; + } - //! Add the glyph to a list of glyphs to be paged. - //! This collection will be cleared after updateTexture is called. - void pushGlyphToBePaged(const SGUITTGlyph* glyph) - { - glyph_to_be_paged.push_back(glyph); - } - - //! Updates the texture atlas with new glyphs. - void updateTexture() - { - if (!dirty) return; - - void* ptr = texture->lock(); - video::ECOLOR_FORMAT format = texture->getColorFormat(); - core::dimension2du size = texture->getOriginalSize(); - video::IImage* pageholder = driver->createImageFromData(format, size, ptr, true, false); - - for (u32 i = 0; i < glyph_to_be_paged.size(); ++i) - { - const SGUITTGlyph* glyph = glyph_to_be_paged[i]; - if (glyph && glyph->isLoaded) - { - if (glyph->surface) - { - glyph->surface->copyTo(pageholder, glyph->source_rect.UpperLeftCorner); - glyph->surface->drop(); - glyph->surface = 0; - } - else - { - ; // TODO: add error message? - //currently, if we failed to create the image, just ignore this operation. - } - } - } - - pageholder->drop(); - texture->unlock(); - glyph_to_be_paged.clear(); - dirty = false; - } - - video::ITexture* texture; - u32 available_slots; - u32 used_slots; - bool dirty; - - core::array render_positions; - core::array render_source_rects; - - private: - core::array glyph_to_be_paged; - video::IVideoDriver* driver; - io::path name; - }; - - //! Class representing a TrueType font. - class CGUITTFont : public IGUIFont + //! Add the glyph to a list of glyphs to be paged. + //! This collection will be cleared after updateTexture is called. + void pushGlyphToBePaged(const SGUITTGlyph *glyph) { - public: - //! Creates a new TrueType font and returns a pointer to it. The pointer must be drop()'ed when finished. - //! \param env The IGUIEnvironment the font loads out of. - //! \param filename The filename of the font. - //! \param size The size of the font glyphs in pixels. Since this is the size of the individual glyphs, the true height of the font may change depending on the characters used. - //! \param antialias set the use_monochrome (opposite to antialias) flag - //! \param transparency set the use_transparency flag - //! \return Returns a pointer to a CGUITTFont. Will return 0 if the font failed to load. - static CGUITTFont* createTTFont(IGUIEnvironment *env, const io::path& filename, const u32 size, const bool antialias = true, const bool transparency = true, const u32 shadow = 0, const u32 shadow_alpha = 255); - static CGUITTFont* createTTFont(IrrlichtDevice *device, const io::path& filename, const u32 size, const bool antialias = true, const bool transparency = true); - static CGUITTFont* create(IGUIEnvironment *env, const io::path& filename, const u32 size, const bool antialias = true, const bool transparency = true); - static CGUITTFont* create(IrrlichtDevice *device, const io::path& filename, const u32 size, const bool antialias = true, const bool transparency = true); + glyph_to_be_paged.push_back(glyph); + } - //! Destructor - virtual ~CGUITTFont(); + //! Updates the texture atlas with new glyphs. + void updateTexture() + { + if (!dirty) + return; - //! Sets the amount of glyphs to batch load. - virtual void setBatchLoadSize(u32 batch_size) { batch_load_size = batch_size; } + void *ptr = texture->lock(); + video::ECOLOR_FORMAT format = texture->getColorFormat(); + core::dimension2du size = texture->getOriginalSize(); + video::IImage *pageholder = driver->createImageFromData( + format, size, ptr, true, false); - //! Sets the maximum texture size for a page of glyphs. - virtual void setMaxPageTextureSize(const core::dimension2du& texture_size) { max_page_texture_size = texture_size; } - - //! Get the font size. - virtual u32 getFontSize() const { return size; } - - //! Check the font's transparency. - virtual bool isTransparent() const { return use_transparency; } - - //! Check if the font auto-hinting is enabled. - //! Auto-hinting is FreeType's built-in font hinting engine. - virtual bool useAutoHinting() const { return use_auto_hinting; } - - //! Check if the font hinting is enabled. - virtual bool useHinting() const { return use_hinting; } - - //! Check if the font is being loaded as a monochrome font. - //! The font can either be a 256 color grayscale font, or a 2 color monochrome font. - virtual bool useMonochrome() const { return use_monochrome; } - - //! Tells the font to allow transparency when rendering. - //! Default: true. - //! \param flag If true, the font draws using transparency. - virtual void setTransparency(const bool flag); - - //! Tells the font to use monochrome rendering. - //! Default: false. - //! \param flag If true, the font draws using a monochrome image. If false, the font uses a grayscale image. - virtual void setMonochrome(const bool flag); - - //! Enables or disables font hinting. - //! Default: Hinting and auto-hinting true. - //! \param enable If false, font hinting is turned off. If true, font hinting is turned on. - //! \param enable_auto_hinting If true, FreeType uses its own auto-hinting algorithm. If false, it tries to use the algorithm specified by the font. - virtual void setFontHinting(const bool enable, const bool enable_auto_hinting = true); - - //! Draws some text and clips it to the specified rectangle if wanted. - virtual void draw(const core::stringw& text, const core::rect& position, - video::SColor color, bool hcenter=false, bool vcenter=false, - const core::rect* clip=0); - - virtual void draw(const EnrichedString& text, const core::rect& position, - video::SColor color, bool hcenter=false, bool vcenter=false, - const core::rect* clip=0); - - //! Returns the dimension of a character produced by this font. - virtual core::dimension2d getCharDimension(const wchar_t ch) const; - - //! Returns the dimension of a text string. - virtual core::dimension2d getDimension(const wchar_t* text) const; - virtual core::dimension2d getDimension(const core::ustring& text) const; - - //! Calculates the index of the character in the text which is on a specific position. - virtual s32 getCharacterFromPos(const wchar_t* text, s32 pixel_x) const; - virtual s32 getCharacterFromPos(const core::ustring& text, s32 pixel_x) const; - - //! Sets global kerning width for the font. - virtual void setKerningWidth(s32 kerning); - - //! Sets global kerning height for the font. - virtual void setKerningHeight(s32 kerning); - - //! Gets kerning values (distance between letters) for the font. If no parameters are provided, - virtual s32 getKerningWidth(const wchar_t* thisLetter=0, const wchar_t* previousLetter=0) const; - virtual s32 getKerningWidth(const uchar32_t thisLetter=0, const uchar32_t previousLetter=0) const; - - //! Returns the distance between letters - virtual s32 getKerningHeight() const; - - //! Define which characters should not be drawn by the font. - virtual void setInvisibleCharacters(const wchar_t *s); - virtual void setInvisibleCharacters(const core::ustring& s); - - //! Get the last glyph page if there's still available slots. - //! If not, it will return zero. - CGUITTGlyphPage* getLastGlyphPage() const; - - //! Create a new glyph page texture. - //! \param pixel_mode the pixel mode defined by FT_Pixel_Mode - //should be better typed. fix later. - CGUITTGlyphPage* createGlyphPage(const u8& pixel_mode); - - //! Get the last glyph page's index. - u32 getLastGlyphPageIndex() const { return Glyph_Pages.size() - 1; } - - //! Create corresponding character's software image copy from the font, - //! so you can use this data just like any ordinary video::IImage. - //! \param ch The character you need - virtual video::IImage* createTextureFromChar(const uchar32_t& ch); - - //! This function is for debugging mostly. If the page doesn't exist it returns zero. - //! \param page_index Simply return the texture handle of a given page index. - virtual video::ITexture* getPageTextureByIndex(const u32& page_index) const; - - //! Add a list of scene nodes generated by putting font textures on the 3D planes. - virtual core::array addTextSceneNode - (const wchar_t* text, scene::ISceneManager* smgr, scene::ISceneNode* parent = 0, - const video::SColor& color = video::SColor(255, 0, 0, 0), bool center = false ); - - inline s32 getAscender() const { return font_metrics.ascender; } - - protected: - bool use_monochrome; - bool use_transparency; - bool use_hinting; - bool use_auto_hinting; - u32 size; - u32 batch_load_size; - core::dimension2du max_page_texture_size; - - private: - // Manages the FreeType library. - static FT_Library c_library; - static core::map c_faces; - static bool c_libraryLoaded; - static scene::IMesh* shared_plane_ptr_; - static scene::SMesh shared_plane_; - - CGUITTFont(IGUIEnvironment *env); - bool load(const io::path& filename, const u32 size, const bool antialias, const bool transparency); - void reset_images(); - void update_glyph_pages() const; - void update_load_flags() - { - // Set up our loading flags. - load_flags = FT_LOAD_DEFAULT | FT_LOAD_RENDER; - if (!useHinting()) load_flags |= FT_LOAD_NO_HINTING; - if (!useAutoHinting()) load_flags |= FT_LOAD_NO_AUTOHINT; - if (useMonochrome()) load_flags |= FT_LOAD_MONOCHROME | FT_LOAD_TARGET_MONO | FT_RENDER_MODE_MONO; - else load_flags |= FT_LOAD_TARGET_NORMAL; + for (u32 i = 0; i < glyph_to_be_paged.size(); ++i) { + const SGUITTGlyph *glyph = glyph_to_be_paged[i]; + if (glyph && glyph->isLoaded) { + if (glyph->surface) { + glyph->surface->copyTo(pageholder, + glyph->source_rect + .UpperLeftCorner); + glyph->surface->drop(); + glyph->surface = 0; + } else { + ; // TODO: add error message? + // currently, if we failed to create the image, + // just ignore this operation. + } } - u32 getWidthFromCharacter(wchar_t c) const; - u32 getWidthFromCharacter(uchar32_t c) const; - u32 getHeightFromCharacter(wchar_t c) const; - u32 getHeightFromCharacter(uchar32_t c) const; - u32 getGlyphIndexByChar(wchar_t c) const; - u32 getGlyphIndexByChar(uchar32_t c) const; - core::vector2di getKerning(const wchar_t thisLetter, const wchar_t previousLetter) const; - core::vector2di getKerning(const uchar32_t thisLetter, const uchar32_t previousLetter) const; - core::dimension2d getDimensionUntilEndOfLine(const wchar_t* p) const; + } - void createSharedPlane(); + pageholder->drop(); + texture->unlock(); + glyph_to_be_paged.clear(); + dirty = false; + } - irr::IrrlichtDevice* Device; - gui::IGUIEnvironment* Environment; - video::IVideoDriver* Driver; - io::path filename; - FT_Face tt_face; - FT_Size_Metrics font_metrics; - FT_Int32 load_flags; + video::ITexture *texture; + u32 available_slots; + u32 used_slots; + bool dirty; - mutable core::array Glyph_Pages; - mutable core::array Glyphs; + core::array render_positions; + core::array render_source_rects; - s32 GlobalKerningWidth; - s32 GlobalKerningHeight; - core::ustring Invisible; - u32 shadow_offset; - u32 shadow_alpha; - }; +private: + core::array glyph_to_be_paged; + video::IVideoDriver *driver; + io::path name; +}; + +//! Class representing a TrueType font. +class CGUITTFont : public IGUIFont +{ +public: + //! Creates a new TrueType font and returns a pointer to it. The pointer must be + //! drop()'ed when finished. \param env The IGUIEnvironment the font loads out of. + //! \param filename The filename of the font. + //! \param size The size of the font glyphs in pixels. Since this is the size of + //! the individual glyphs, the true height of the font may change depending on the + //! characters used. \param antialias set the use_monochrome (opposite to + //! antialias) flag \param transparency set the use_transparency flag \return + //! Returns a pointer to a CGUITTFont. Will return 0 if the font failed to load. + static CGUITTFont *createTTFont(IGUIEnvironment *env, const io::path &filename, + const u32 size, const bool antialias = true, + const bool transparency = true, const u32 shadow = 0, + const u32 shadow_alpha = 255); + static CGUITTFont *createTTFont(IrrlichtDevice *device, const io::path &filename, + const u32 size, const bool antialias = true, + const bool transparency = true); + static CGUITTFont *create(IGUIEnvironment *env, const io::path &filename, + const u32 size, const bool antialias = true, + const bool transparency = true); + static CGUITTFont *create(IrrlichtDevice *device, const io::path &filename, + const u32 size, const bool antialias = true, + const bool transparency = true); + + //! Destructor + virtual ~CGUITTFont(); + + //! Sets the amount of glyphs to batch load. + virtual void setBatchLoadSize(u32 batch_size) { batch_load_size = batch_size; } + + //! Sets the maximum texture size for a page of glyphs. + virtual void setMaxPageTextureSize(const core::dimension2du &texture_size) + { + max_page_texture_size = texture_size; + } + + //! Get the font size. + virtual u32 getFontSize() const { return size; } + + //! Check the font's transparency. + virtual bool isTransparent() const { return use_transparency; } + + //! Check if the font auto-hinting is enabled. + //! Auto-hinting is FreeType's built-in font hinting engine. + virtual bool useAutoHinting() const { return use_auto_hinting; } + + //! Check if the font hinting is enabled. + virtual bool useHinting() const { return use_hinting; } + + //! Check if the font is being loaded as a monochrome font. + //! The font can either be a 256 color grayscale font, or a 2 color monochrome + //! font. + virtual bool useMonochrome() const { return use_monochrome; } + + //! Tells the font to allow transparency when rendering. + //! Default: true. + //! \param flag If true, the font draws using transparency. + virtual void setTransparency(const bool flag); + + //! Tells the font to use monochrome rendering. + //! Default: false. + //! \param flag If true, the font draws using a monochrome image. If false, the + //! font uses a grayscale image. + virtual void setMonochrome(const bool flag); + + //! Enables or disables font hinting. + //! Default: Hinting and auto-hinting true. + //! \param enable If false, font hinting is turned off. If true, font hinting is + //! turned on. \param enable_auto_hinting If true, FreeType uses its own + //! auto-hinting algorithm. If false, it tries to use the algorithm specified by + //! the font. + virtual void setFontHinting( + const bool enable, const bool enable_auto_hinting = true); + + //! Draws some text and clips it to the specified rectangle if wanted. + virtual void draw(const core::stringw &text, const core::rect &position, + video::SColor color, bool hcenter = false, bool vcenter = false, + const core::rect *clip = 0); + + virtual void draw(const EnrichedString &text, const core::rect &position, + video::SColor color, bool hcenter = false, bool vcenter = false, + const core::rect *clip = 0); + + //! Returns the dimension of a character produced by this font. + virtual core::dimension2d getCharDimension(const wchar_t ch) const; + + //! Returns the dimension of a text string. + virtual core::dimension2d getDimension(const wchar_t *text) const; + virtual core::dimension2d getDimension(const core::ustring &text) const; + + //! Calculates the index of the character in the text which is on a specific + //! position. + virtual s32 getCharacterFromPos(const wchar_t *text, s32 pixel_x) const; + virtual s32 getCharacterFromPos(const core::ustring &text, s32 pixel_x) const; + + //! Sets global kerning width for the font. + virtual void setKerningWidth(s32 kerning); + + //! Sets global kerning height for the font. + virtual void setKerningHeight(s32 kerning); + + //! Gets kerning values (distance between letters) for the font. If no parameters + //! are provided, + virtual s32 getKerningWidth(const wchar_t *thisLetter = 0, + const wchar_t *previousLetter = 0) const; + virtual s32 getKerningWidth(const uchar32_t thisLetter = 0, + const uchar32_t previousLetter = 0) const; + + //! Returns the distance between letters + virtual s32 getKerningHeight() const; + + //! Define which characters should not be drawn by the font. + virtual void setInvisibleCharacters(const wchar_t *s); + virtual void setInvisibleCharacters(const core::ustring &s); + + //! Get the last glyph page if there's still available slots. + //! If not, it will return zero. + CGUITTGlyphPage *getLastGlyphPage() const; + + //! Create a new glyph page texture. + //! \param pixel_mode the pixel mode defined by FT_Pixel_Mode + // should be better typed. fix later. + CGUITTGlyphPage *createGlyphPage(const u8 &pixel_mode); + + //! Get the last glyph page's index. + u32 getLastGlyphPageIndex() const { return Glyph_Pages.size() - 1; } + + //! Create corresponding character's software image copy from the font, + //! so you can use this data just like any ordinary video::IImage. + //! \param ch The character you need + virtual video::IImage *createTextureFromChar(const uchar32_t &ch); + + //! This function is for debugging mostly. If the page doesn't exist it returns + //! zero. \param page_index Simply return the texture handle of a given page + //! index. + virtual video::ITexture *getPageTextureByIndex(const u32 &page_index) const; + + //! Add a list of scene nodes generated by putting font textures on the 3D planes. + virtual core::array addTextSceneNode(const wchar_t *text, + scene::ISceneManager *smgr, scene::ISceneNode *parent = 0, + const video::SColor &color = video::SColor(255, 0, 0, 0), + bool center = false); + + inline s32 getAscender() const { return font_metrics.ascender; } + +protected: + bool use_monochrome; + bool use_transparency; + bool use_hinting; + bool use_auto_hinting; + u32 size; + u32 batch_load_size; + core::dimension2du max_page_texture_size; + +private: + // Manages the FreeType library. + static FT_Library c_library; + static core::map c_faces; + static bool c_libraryLoaded; + static scene::IMesh *shared_plane_ptr_; + static scene::SMesh shared_plane_; + + CGUITTFont(IGUIEnvironment *env); + bool load(const io::path &filename, const u32 size, const bool antialias, + const bool transparency); + void reset_images(); + void update_glyph_pages() const; + void update_load_flags() + { + // Set up our loading flags. + load_flags = FT_LOAD_DEFAULT | FT_LOAD_RENDER; + if (!useHinting()) + load_flags |= FT_LOAD_NO_HINTING; + if (!useAutoHinting()) + load_flags |= FT_LOAD_NO_AUTOHINT; + if (useMonochrome()) + load_flags |= FT_LOAD_MONOCHROME | FT_LOAD_TARGET_MONO | + FT_RENDER_MODE_MONO; + else + load_flags |= FT_LOAD_TARGET_NORMAL; + } + u32 getWidthFromCharacter(wchar_t c) const; + u32 getWidthFromCharacter(uchar32_t c) const; + u32 getHeightFromCharacter(wchar_t c) const; + u32 getHeightFromCharacter(uchar32_t c) const; + u32 getGlyphIndexByChar(wchar_t c) const; + u32 getGlyphIndexByChar(uchar32_t c) const; + core::vector2di getKerning( + const wchar_t thisLetter, const wchar_t previousLetter) const; + core::vector2di getKerning( + const uchar32_t thisLetter, const uchar32_t previousLetter) const; + core::dimension2d getDimensionUntilEndOfLine(const wchar_t *p) const; + + void createSharedPlane(); + + irr::IrrlichtDevice *Device; + gui::IGUIEnvironment *Environment; + video::IVideoDriver *Driver; + io::path filename; + FT_Face tt_face; + FT_Size_Metrics font_metrics; + FT_Int32 load_flags; + + mutable core::array Glyph_Pages; + mutable core::array Glyphs; + + s32 GlobalKerningWidth; + s32 GlobalKerningHeight; + core::ustring Invisible; + u32 shadow_offset; + u32 shadow_alpha; +}; } // end namespace gui } // end namespace irr diff --git a/src/irrlicht_changes/irrUString.h b/src/irrlicht_changes/irrUString.h index b628c092c..9ff3d1b4d 100644 --- a/src/irrlicht_changes/irrUString.h +++ b/src/irrlicht_changes/irrUString.h @@ -31,10 +31,11 @@ #pragma once #if (__cplusplus > 199711L) || (_MSC_VER >= 1600) || defined(__GXX_EXPERIMENTAL_CXX0X__) -# define USTRING_CPP0X -# if defined(__GXX_EXPERIMENTAL_CXX0X__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 5))) -# define USTRING_CPP0X_NEWLITERALS -# endif +#define USTRING_CPP0X +#if defined(__GXX_EXPERIMENTAL_CXX0X__) && \ + ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 5))) +#define USTRING_CPP0X_NEWLITERALS +#endif #endif #include @@ -55,13 +56,13 @@ #endif #ifdef USTRING_CPP0X -# include +#include #endif #ifndef USTRING_NO_STL -# include -# include -# include +#include +#include +#include #endif #include "irrTypes.h" @@ -76,23 +77,22 @@ static const irr::u16 UTF16_HI_SURROGATE = 0xD800; static const irr::u16 UTF16_LO_SURROGATE = 0xDC00; //! Is a UTF-16 code point a surrogate? -#define UTF16_IS_SURROGATE(c) (((c) & 0xF800) == 0xD800) -#define UTF16_IS_SURROGATE_HI(c) (((c) & 0xFC00) == 0xD800) -#define UTF16_IS_SURROGATE_LO(c) (((c) & 0xFC00) == 0xDC00) - +#define UTF16_IS_SURROGATE(c) (((c)&0xF800) == 0xD800) +#define UTF16_IS_SURROGATE_HI(c) (((c)&0xFC00) == 0xD800) +#define UTF16_IS_SURROGATE_LO(c) (((c)&0xFC00) == 0xDC00) namespace irr { - // Define our character types. -#ifdef USTRING_CPP0X_NEWLITERALS // C++0x - typedef char32_t uchar32_t; - typedef char16_t uchar16_t; - typedef char uchar8_t; +// Define our character types. +#ifdef USTRING_CPP0X_NEWLITERALS // C++0x +typedef char32_t uchar32_t; +typedef char16_t uchar16_t; +typedef char uchar8_t; #else - typedef u32 uchar32_t; - typedef u16 uchar16_t; - typedef u8 uchar8_t; +typedef u32 uchar32_t; +typedef u16 uchar16_t; +typedef u8 uchar8_t; #endif namespace core @@ -111,26 +111,24 @@ const irr::u16 UTF_REPLACEMENT_CHARACTER = 0xFFFD; inline uchar32_t toUTF32(uchar16_t high, uchar16_t low) { // Convert the surrogate pair into a single UTF-32 character. - uchar32_t x = ((high & ((1 << 6) -1)) << 10) | (low & ((1 << 10) -1)); + uchar32_t x = ((high & ((1 << 6) - 1)) << 10) | (low & ((1 << 10) - 1)); uchar32_t wu = ((high >> 6) & ((1 << 5) - 1)) + 1; return (wu << 16) | x; } //! Swaps the endianness of a 16-bit value. //! \return The new value. -inline uchar16_t swapEndian16(const uchar16_t& c) +inline uchar16_t swapEndian16(const uchar16_t &c) { return ((c >> 8) & 0x00FF) | ((c << 8) & 0xFF00); } //! Swaps the endianness of a 32-bit value. //! \return The new value. -inline uchar32_t swapEndian32(const uchar32_t& c) +inline uchar32_t swapEndian32(const uchar32_t &c) { - return ((c >> 24) & 0x000000FF) | - ((c >> 8) & 0x0000FF00) | - ((c << 8) & 0x00FF0000) | - ((c << 24) & 0xFF000000); + return ((c >> 24) & 0x000000FF) | ((c >> 8) & 0x0000FF00) | + ((c << 8) & 0x00FF0000) | ((c << 24) & 0xFF000000); } //! The Unicode byte order mark. @@ -142,11 +140,11 @@ const u8 BOM_UTF16_LEN = 1; const u8 BOM_UTF32_LEN = 1; //! Unicode byte order marks for file operations. -const u8 BOM_ENCODE_UTF8[3] = { 0xEF, 0xBB, 0xBF }; -const u8 BOM_ENCODE_UTF16_BE[2] = { 0xFE, 0xFF }; -const u8 BOM_ENCODE_UTF16_LE[2] = { 0xFF, 0xFE }; -const u8 BOM_ENCODE_UTF32_BE[4] = { 0x00, 0x00, 0xFE, 0xFF }; -const u8 BOM_ENCODE_UTF32_LE[4] = { 0xFF, 0xFE, 0x00, 0x00 }; +const u8 BOM_ENCODE_UTF8[3] = {0xEF, 0xBB, 0xBF}; +const u8 BOM_ENCODE_UTF16_BE[2] = {0xFE, 0xFF}; +const u8 BOM_ENCODE_UTF16_LE[2] = {0xFF, 0xFE}; +const u8 BOM_ENCODE_UTF32_BE[4] = {0x00, 0x00, 0xFE, 0xFF}; +const u8 BOM_ENCODE_UTF32_LE[4] = {0xFF, 0xFE, 0x00, 0x00}; //! The size in bytes of the Unicode byte marks for file operations. const u8 BOM_ENCODE_UTF8_LEN = 3; @@ -156,7 +154,7 @@ const u8 BOM_ENCODE_UTF32_LEN = 4; //! Unicode encoding type. enum EUTF_ENCODE { - EUTFE_NONE = 0, + EUTFE_NONE = 0, EUTFE_UTF8, EUTFE_UTF16, EUTFE_UTF16_LE, @@ -169,7 +167,7 @@ enum EUTF_ENCODE //! Unicode endianness. enum EUTF_ENDIAN { - EUTFEE_NATIVE = 0, + EUTFEE_NATIVE = 0, EUTFEE_LITTLE, EUTFEE_BIG }; @@ -177,50 +175,50 @@ enum EUTF_ENDIAN //! Returns the specified unicode byte order mark in a byte array. //! The byte order mark is the first few bytes in a text file that signifies its encoding. /** \param mode The Unicode encoding method that we want to get the byte order mark for. - If EUTFE_UTF16 or EUTFE_UTF32 is passed, it uses the native system endianness. **/ + If EUTFE_UTF16 or EUTFE_UTF32 is passed, it uses the native system + endianness. **/ //! \return An array that contains a byte order mark. inline core::array getUnicodeBOM(EUTF_ENCODE mode) { -#define COPY_ARRAY(source, size) \ - memcpy(ret.pointer(), source, size); \ +#define COPY_ARRAY(source, size) \ + memcpy(ret.pointer(), source, size); \ ret.set_used(size) core::array ret(4); - switch (mode) - { - case EUTFE_UTF8: - COPY_ARRAY(BOM_ENCODE_UTF8, BOM_ENCODE_UTF8_LEN); - break; - case EUTFE_UTF16: - #ifdef __BIG_ENDIAN__ - COPY_ARRAY(BOM_ENCODE_UTF16_BE, BOM_ENCODE_UTF16_LEN); - #else - COPY_ARRAY(BOM_ENCODE_UTF16_LE, BOM_ENCODE_UTF16_LEN); - #endif - break; - case EUTFE_UTF16_BE: - COPY_ARRAY(BOM_ENCODE_UTF16_BE, BOM_ENCODE_UTF16_LEN); - break; - case EUTFE_UTF16_LE: - COPY_ARRAY(BOM_ENCODE_UTF16_LE, BOM_ENCODE_UTF16_LEN); - break; - case EUTFE_UTF32: - #ifdef __BIG_ENDIAN__ - COPY_ARRAY(BOM_ENCODE_UTF32_BE, BOM_ENCODE_UTF32_LEN); - #else - COPY_ARRAY(BOM_ENCODE_UTF32_LE, BOM_ENCODE_UTF32_LEN); - #endif - break; - case EUTFE_UTF32_BE: - COPY_ARRAY(BOM_ENCODE_UTF32_BE, BOM_ENCODE_UTF32_LEN); - break; - case EUTFE_UTF32_LE: - COPY_ARRAY(BOM_ENCODE_UTF32_LE, BOM_ENCODE_UTF32_LEN); - break; - case EUTFE_NONE: - // TODO sapier: fixed warning only, - // don't know if something needs to be done here - break; + switch (mode) { + case EUTFE_UTF8: + COPY_ARRAY(BOM_ENCODE_UTF8, BOM_ENCODE_UTF8_LEN); + break; + case EUTFE_UTF16: +#ifdef __BIG_ENDIAN__ + COPY_ARRAY(BOM_ENCODE_UTF16_BE, BOM_ENCODE_UTF16_LEN); +#else + COPY_ARRAY(BOM_ENCODE_UTF16_LE, BOM_ENCODE_UTF16_LEN); +#endif + break; + case EUTFE_UTF16_BE: + COPY_ARRAY(BOM_ENCODE_UTF16_BE, BOM_ENCODE_UTF16_LEN); + break; + case EUTFE_UTF16_LE: + COPY_ARRAY(BOM_ENCODE_UTF16_LE, BOM_ENCODE_UTF16_LEN); + break; + case EUTFE_UTF32: +#ifdef __BIG_ENDIAN__ + COPY_ARRAY(BOM_ENCODE_UTF32_BE, BOM_ENCODE_UTF32_LEN); +#else + COPY_ARRAY(BOM_ENCODE_UTF32_LE, BOM_ENCODE_UTF32_LEN); +#endif + break; + case EUTFE_UTF32_BE: + COPY_ARRAY(BOM_ENCODE_UTF32_BE, BOM_ENCODE_UTF32_LEN); + break; + case EUTFE_UTF32_LE: + COPY_ARRAY(BOM_ENCODE_UTF32_LE, BOM_ENCODE_UTF32_LEN); + break; + case EUTFE_NONE: + // TODO sapier: fixed warning only, + // don't know if something needs to be done here + break; } return ret; @@ -229,26 +227,29 @@ inline core::array getUnicodeBOM(EUTF_ENCODE mode) //! Detects if the given data stream starts with a unicode BOM. //! \param data The data stream to check. -//! \return The unicode BOM associated with the data stream, or EUTFE_NONE if none was found. -inline EUTF_ENCODE determineUnicodeBOM(const char* data) +//! \return The unicode BOM associated with the data stream, or EUTFE_NONE if none was +//! found. +inline EUTF_ENCODE determineUnicodeBOM(const char *data) { - if (memcmp(data, BOM_ENCODE_UTF8, 3) == 0) return EUTFE_UTF8; - if (memcmp(data, BOM_ENCODE_UTF16_BE, 2) == 0) return EUTFE_UTF16_BE; - if (memcmp(data, BOM_ENCODE_UTF16_LE, 2) == 0) return EUTFE_UTF16_LE; - if (memcmp(data, BOM_ENCODE_UTF32_BE, 4) == 0) return EUTFE_UTF32_BE; - if (memcmp(data, BOM_ENCODE_UTF32_LE, 4) == 0) return EUTFE_UTF32_LE; + if (memcmp(data, BOM_ENCODE_UTF8, 3) == 0) + return EUTFE_UTF8; + if (memcmp(data, BOM_ENCODE_UTF16_BE, 2) == 0) + return EUTFE_UTF16_BE; + if (memcmp(data, BOM_ENCODE_UTF16_LE, 2) == 0) + return EUTFE_UTF16_LE; + if (memcmp(data, BOM_ENCODE_UTF32_BE, 4) == 0) + return EUTFE_UTF32_BE; + if (memcmp(data, BOM_ENCODE_UTF32_LE, 4) == 0) + return EUTFE_UTF32_LE; return EUTFE_NONE; } } // end namespace unicode - //! UTF-16 string class. -template > -class ustring16 +template > class ustring16 { public: - ///------------------/// /// iterator classes /// ///------------------/// @@ -256,555 +257,533 @@ public: //! Access an element in a unicode string, allowing one to change it. class _ustring16_iterator_access { - public: - _ustring16_iterator_access(const ustring16* s, u32 p) : ref(s), pos(p) {} + public: + _ustring16_iterator_access(const ustring16 *s, u32 p) : + ref(s), pos(p) + { + } - //! Allow the class to be interpreted as a single UTF-32 character. - operator uchar32_t() const - { - return _get(); + //! Allow the class to be interpreted as a single UTF-32 character. + operator uchar32_t() const { return _get(); } + + //! Allow one to change the character in the unicode string. + //! \param c The new character to use. + //! \return Myself. + _ustring16_iterator_access &operator=(const uchar32_t c) + { + _set(c); + return *this; + } + + //! Increments the value by 1. + //! \return Myself. + _ustring16_iterator_access &operator++() + { + _set(_get() + 1); + return *this; + } + + //! Increments the value by 1, returning the old value. + //! \return A unicode character. + uchar32_t operator++(int) + { + uchar32_t old = _get(); + _set(old + 1); + return old; + } + + //! Decrements the value by 1. + //! \return Myself. + _ustring16_iterator_access &operator--() + { + _set(_get() - 1); + return *this; + } + + //! Decrements the value by 1, returning the old value. + //! \return A unicode character. + uchar32_t operator--(int) + { + uchar32_t old = _get(); + _set(old - 1); + return old; + } + + //! Adds to the value by a specified amount. + //! \param val The amount to add to this character. + //! \return Myself. + _ustring16_iterator_access &operator+=(int val) + { + _set(_get() + val); + return *this; + } + + //! Subtracts from the value by a specified amount. + //! \param val The amount to subtract from this character. + //! \return Myself. + _ustring16_iterator_access &operator-=(int val) + { + _set(_get() - val); + return *this; + } + + //! Multiples the value by a specified amount. + //! \param val The amount to multiply this character by. + //! \return Myself. + _ustring16_iterator_access &operator*=(int val) + { + _set(_get() * val); + return *this; + } + + //! Divides the value by a specified amount. + //! \param val The amount to divide this character by. + //! \return Myself. + _ustring16_iterator_access &operator/=(int val) + { + _set(_get() / val); + return *this; + } + + //! Modulos the value by a specified amount. + //! \param val The amount to modulo this character by. + //! \return Myself. + _ustring16_iterator_access &operator%=(int val) + { + _set(_get() % val); + return *this; + } + + //! Adds to the value by a specified amount. + //! \param val The amount to add to this character. + //! \return A unicode character. + uchar32_t operator+(int val) const { return _get() + val; } + + //! Subtracts from the value by a specified amount. + //! \param val The amount to subtract from this character. + //! \return A unicode character. + uchar32_t operator-(int val) const { return _get() - val; } + + //! Multiplies the value by a specified amount. + //! \param val The amount to multiply this character by. + //! \return A unicode character. + uchar32_t operator*(int val) const { return _get() * val; } + + //! Divides the value by a specified amount. + //! \param val The amount to divide this character by. + //! \return A unicode character. + uchar32_t operator/(int val) const { return _get() / val; } + + //! Modulos the value by a specified amount. + //! \param val The amount to modulo this character by. + //! \return A unicode character. + uchar32_t operator%(int val) const { return _get() % val; } + + private: + //! Gets a uchar32_t from our current position. + uchar32_t _get() const + { + const uchar16_t *a = ref->c_str(); + if (!UTF16_IS_SURROGATE(a[pos])) + return static_cast(a[pos]); + else { + if (pos + 1 >= ref->size_raw()) + return 0; + + return unicode::toUTF32(a[pos], a[pos + 1]); } + } - //! Allow one to change the character in the unicode string. - //! \param c The new character to use. - //! \return Myself. - _ustring16_iterator_access& operator=(const uchar32_t c) - { - _set(c); - return *this; - } + //! Sets a uchar32_t at our current position. + void _set(uchar32_t c) + { + ustring16 *ref2 = const_cast *>(ref); + const uchar16_t *a = ref2->c_str(); + if (c > 0xFFFF) { + // c will be multibyte, so split it up into the high and + // low surrogate pairs. + uchar16_t x = static_cast(c); + uchar16_t vh = UTF16_HI_SURROGATE | + ((((c >> 16) & ((1 << 5) - 1)) - 1) << 6) | + (x >> 10); + uchar16_t vl = UTF16_LO_SURROGATE | (x & ((1 << 10) - 1)); - //! Increments the value by 1. - //! \return Myself. - _ustring16_iterator_access& operator++() - { - _set(_get() + 1); - return *this; - } - - //! Increments the value by 1, returning the old value. - //! \return A unicode character. - uchar32_t operator++(int) - { - uchar32_t old = _get(); - _set(old + 1); - return old; - } - - //! Decrements the value by 1. - //! \return Myself. - _ustring16_iterator_access& operator--() - { - _set(_get() - 1); - return *this; - } - - //! Decrements the value by 1, returning the old value. - //! \return A unicode character. - uchar32_t operator--(int) - { - uchar32_t old = _get(); - _set(old - 1); - return old; - } - - //! Adds to the value by a specified amount. - //! \param val The amount to add to this character. - //! \return Myself. - _ustring16_iterator_access& operator+=(int val) - { - _set(_get() + val); - return *this; - } - - //! Subtracts from the value by a specified amount. - //! \param val The amount to subtract from this character. - //! \return Myself. - _ustring16_iterator_access& operator-=(int val) - { - _set(_get() - val); - return *this; - } - - //! Multiples the value by a specified amount. - //! \param val The amount to multiply this character by. - //! \return Myself. - _ustring16_iterator_access& operator*=(int val) - { - _set(_get() * val); - return *this; - } - - //! Divides the value by a specified amount. - //! \param val The amount to divide this character by. - //! \return Myself. - _ustring16_iterator_access& operator/=(int val) - { - _set(_get() / val); - return *this; - } - - //! Modulos the value by a specified amount. - //! \param val The amount to modulo this character by. - //! \return Myself. - _ustring16_iterator_access& operator%=(int val) - { - _set(_get() % val); - return *this; - } - - //! Adds to the value by a specified amount. - //! \param val The amount to add to this character. - //! \return A unicode character. - uchar32_t operator+(int val) const - { - return _get() + val; - } - - //! Subtracts from the value by a specified amount. - //! \param val The amount to subtract from this character. - //! \return A unicode character. - uchar32_t operator-(int val) const - { - return _get() - val; - } - - //! Multiplies the value by a specified amount. - //! \param val The amount to multiply this character by. - //! \return A unicode character. - uchar32_t operator*(int val) const - { - return _get() * val; - } - - //! Divides the value by a specified amount. - //! \param val The amount to divide this character by. - //! \return A unicode character. - uchar32_t operator/(int val) const - { - return _get() / val; - } - - //! Modulos the value by a specified amount. - //! \param val The amount to modulo this character by. - //! \return A unicode character. - uchar32_t operator%(int val) const - { - return _get() % val; - } - - private: - //! Gets a uchar32_t from our current position. - uchar32_t _get() const - { - const uchar16_t* a = ref->c_str(); - if (!UTF16_IS_SURROGATE(a[pos])) - return static_cast(a[pos]); + // If the previous position was a surrogate pair, just + // replace them. Else, insert the low pair. + if (UTF16_IS_SURROGATE_HI(a[pos]) && + pos + 1 != ref2->size_raw()) + ref2->replace_raw(vl, static_cast(pos) + 1); else - { - if (pos + 1 >= ref->size_raw()) - return 0; + ref2->insert_raw(vl, static_cast(pos) + 1); - return unicode::toUTF32(a[pos], a[pos + 1]); - } + ref2->replace_raw(vh, static_cast(pos)); + } else { + // c will be a single byte. + uchar16_t vh = static_cast(c); + + // If the previous position was a surrogate pair, remove + // the extra byte. + if (UTF16_IS_SURROGATE_HI(a[pos])) + ref2->erase_raw(static_cast(pos) + 1); + + ref2->replace_raw(vh, static_cast(pos)); } + } - //! Sets a uchar32_t at our current position. - void _set(uchar32_t c) - { - ustring16* ref2 = const_cast*>(ref); - const uchar16_t* a = ref2->c_str(); - if (c > 0xFFFF) - { - // c will be multibyte, so split it up into the high and low surrogate pairs. - uchar16_t x = static_cast(c); - uchar16_t vh = UTF16_HI_SURROGATE | ((((c >> 16) & ((1 << 5) - 1)) - 1) << 6) | (x >> 10); - uchar16_t vl = UTF16_LO_SURROGATE | (x & ((1 << 10) - 1)); - - // If the previous position was a surrogate pair, just replace them. Else, insert the low pair. - if (UTF16_IS_SURROGATE_HI(a[pos]) && pos + 1 != ref2->size_raw()) - ref2->replace_raw(vl, static_cast(pos) + 1); - else ref2->insert_raw(vl, static_cast(pos) + 1); - - ref2->replace_raw(vh, static_cast(pos)); - } - else - { - // c will be a single byte. - uchar16_t vh = static_cast(c); - - // If the previous position was a surrogate pair, remove the extra byte. - if (UTF16_IS_SURROGATE_HI(a[pos])) - ref2->erase_raw(static_cast(pos) + 1); - - ref2->replace_raw(vh, static_cast(pos)); - } - } - - const ustring16* ref; - u32 pos; + const ustring16 *ref; + u32 pos; }; typedef typename ustring16::_ustring16_iterator_access access; - //! Iterator to iterate through a UTF-16 string. #ifndef USTRING_NO_STL - class _ustring16_const_iterator : public std::iterator< - std::bidirectional_iterator_tag, // iterator_category - access, // value_type - ptrdiff_t, // difference_type - const access, // pointer - const access // reference - > + class _ustring16_const_iterator + : public std::iterator< + std::bidirectional_iterator_tag, // iterator_category + access, // value_type + ptrdiff_t, // difference_type + const access, // pointer + const access // reference + > #else class _ustring16_const_iterator #endif { - public: - typedef _ustring16_const_iterator _Iter; - typedef std::iterator _Base; - typedef const access const_pointer; - typedef const access const_reference; + public: + typedef _ustring16_const_iterator _Iter; + typedef std::iterator + _Base; + typedef const access const_pointer; + typedef const access const_reference; #ifndef USTRING_NO_STL - typedef typename _Base::value_type value_type; - typedef typename _Base::difference_type difference_type; - typedef typename _Base::difference_type distance_type; - typedef typename _Base::pointer pointer; - typedef const_reference reference; + typedef typename _Base::value_type value_type; + typedef typename _Base::difference_type difference_type; + typedef typename _Base::difference_type distance_type; + typedef typename _Base::pointer pointer; + typedef const_reference reference; #else - typedef access value_type; - typedef u32 difference_type; - typedef u32 distance_type; - typedef const_pointer pointer; - typedef const_reference reference; + typedef access value_type; + typedef u32 difference_type; + typedef u32 distance_type; + typedef const_pointer pointer; + typedef const_reference reference; #endif - //! Constructors. - _ustring16_const_iterator(const _Iter& i) : ref(i.ref), pos(i.pos) {} - _ustring16_const_iterator(const ustring16& s) : ref(&s), pos(0) {} - _ustring16_const_iterator(const ustring16& s, const u32 p) : ref(&s), pos(0) - { - if (ref->size_raw() == 0 || p == 0) - return; + //! Constructors. + _ustring16_const_iterator(const _Iter &i) : ref(i.ref), pos(i.pos) {} + _ustring16_const_iterator(const ustring16 &s) : ref(&s), pos(0) {} + _ustring16_const_iterator(const ustring16 &s, const u32 p) : + ref(&s), pos(0) + { + if (ref->size_raw() == 0 || p == 0) + return; - // Go to the appropriate position. - u32 i = p; - u32 sr = ref->size_raw(); - const uchar16_t* a = ref->c_str(); - while (i != 0 && pos < sr) - { - if (UTF16_IS_SURROGATE_HI(a[pos])) - pos += 2; - else ++pos; - --i; - } - } - - //! Test for equalness. - bool operator==(const _Iter& iter) const - { - if (ref == iter.ref && pos == iter.pos) - return true; - return false; - } - - //! Test for unequalness. - bool operator!=(const _Iter& iter) const - { - if (ref != iter.ref || pos != iter.pos) - return true; - return false; - } - - //! Switch to the next full character in the string. - _Iter& operator++() - { // ++iterator - if (pos == ref->size_raw()) return *this; - const uchar16_t* a = ref->c_str(); + // Go to the appropriate position. + u32 i = p; + u32 sr = ref->size_raw(); + const uchar16_t *a = ref->c_str(); + while (i != 0 && pos < sr) { if (UTF16_IS_SURROGATE_HI(a[pos])) - pos += 2; // TODO: check for valid low surrogate? - else ++pos; - if (pos > ref->size_raw()) pos = ref->size_raw(); + pos += 2; + else + ++pos; + --i; + } + } + + //! Test for equalness. + bool operator==(const _Iter &iter) const + { + if (ref == iter.ref && pos == iter.pos) + return true; + return false; + } + + //! Test for unequalness. + bool operator!=(const _Iter &iter) const + { + if (ref != iter.ref || pos != iter.pos) + return true; + return false; + } + + //! Switch to the next full character in the string. + _Iter &operator++() + { // ++iterator + if (pos == ref->size_raw()) return *this; - } - - //! Switch to the next full character in the string, returning the previous position. - _Iter operator++(int) - { // iterator++ - _Iter _tmp(*this); - ++*this; - return _tmp; - } - - //! Switch to the previous full character in the string. - _Iter& operator--() - { // --iterator - if (pos == 0) return *this; - const uchar16_t* a = ref->c_str(); - --pos; - if (UTF16_IS_SURROGATE_LO(a[pos]) && pos != 0) // low surrogate, go back one more. - --pos; - return *this; - } - - //! Switch to the previous full character in the string, returning the previous position. - _Iter operator--(int) - { // iterator-- - _Iter _tmp(*this); - --*this; - return _tmp; - } - - //! Advance a specified number of full characters in the string. - //! \return Myself. - _Iter& operator+=(const difference_type v) - { - if (v == 0) return *this; - if (v < 0) return operator-=(v * -1); - - if (pos >= ref->size_raw()) - return *this; - - // Go to the appropriate position. - // TODO: Don't force u32 on an x64 OS. Make it agnostic. - u32 i = (u32)v; - u32 sr = ref->size_raw(); - const uchar16_t* a = ref->c_str(); - while (i != 0 && pos < sr) - { - if (UTF16_IS_SURROGATE_HI(a[pos])) - pos += 2; - else ++pos; - --i; - } - if (pos > sr) - pos = sr; - - return *this; - } - - //! Go back a specified number of full characters in the string. - //! \return Myself. - _Iter& operator-=(const difference_type v) - { - if (v == 0) return *this; - if (v > 0) return operator+=(v * -1); - - if (pos == 0) - return *this; - - // Go to the appropriate position. - // TODO: Don't force u32 on an x64 OS. Make it agnostic. - u32 i = (u32)v; - const uchar16_t* a = ref->c_str(); - while (i != 0 && pos != 0) - { - --pos; - if (UTF16_IS_SURROGATE_LO(a[pos]) != 0 && pos != 0) - --pos; - --i; - } - - return *this; - } - - //! Return a new iterator that is a variable number of full characters forward from the current position. - _Iter operator+(const difference_type v) const - { - _Iter ret(*this); - ret += v; - return ret; - } - - //! Return a new iterator that is a variable number of full characters backward from the current position. - _Iter operator-(const difference_type v) const - { - _Iter ret(*this); - ret -= v; - return ret; - } - - //! Returns the distance between two iterators. - difference_type operator-(const _Iter& iter) const - { - // Make sure we reference the same object! - if (ref != iter.ref) - return difference_type(); - - _Iter i = iter; - difference_type ret; - - // Walk up. - if (pos > i.pos) - { - while (pos > i.pos) - { - ++i; - ++ret; - } - return ret; - } - - // Walk down. - while (pos < i.pos) - { - --i; - --ret; - } - return ret; - } - - //! Accesses the full character at the iterator's position. - const_reference operator*() const - { - if (pos >= ref->size_raw()) - { - const uchar16_t* a = ref->c_str(); - u32 p = ref->size_raw(); - if (UTF16_IS_SURROGATE_LO(a[p])) - --p; - reference ret(ref, p); - return ret; - } - const_reference ret(ref, pos); - return ret; - } - - //! Accesses the full character at the iterator's position. - reference operator*() - { - if (pos >= ref->size_raw()) - { - const uchar16_t* a = ref->c_str(); - u32 p = ref->size_raw(); - if (UTF16_IS_SURROGATE_LO(a[p])) - --p; - reference ret(ref, p); - return ret; - } - reference ret(ref, pos); - return ret; - } - - //! Accesses the full character at the iterator's position. - const_pointer operator->() const - { - return operator*(); - } - - //! Accesses the full character at the iterator's position. - pointer operator->() - { - return operator*(); - } - - //! Is the iterator at the start of the string? - bool atStart() const - { - return pos == 0; - } - - //! Is the iterator at the end of the string? - bool atEnd() const - { - const uchar16_t* a = ref->c_str(); - if (UTF16_IS_SURROGATE(a[pos])) - return (pos + 1) >= ref->size_raw(); - else return pos >= ref->size_raw(); - } - - //! Moves the iterator to the start of the string. - void toStart() - { - pos = 0; - } - - //! Moves the iterator to the end of the string. - void toEnd() - { + const uchar16_t *a = ref->c_str(); + if (UTF16_IS_SURROGATE_HI(a[pos])) + pos += 2; // TODO: check for valid low surrogate? + else + ++pos; + if (pos > ref->size_raw()) pos = ref->size_raw(); + return *this; + } + + //! Switch to the next full character in the string, returning the + //! previous position. + _Iter operator++(int) + { // iterator++ + _Iter _tmp(*this); + ++*this; + return _tmp; + } + + //! Switch to the previous full character in the string. + _Iter &operator--() + { // --iterator + if (pos == 0) + return *this; + const uchar16_t *a = ref->c_str(); + --pos; + if (UTF16_IS_SURROGATE_LO(a[pos]) && + pos != 0) // low surrogate, go back one more. + --pos; + return *this; + } + + //! Switch to the previous full character in the string, returning the + //! previous position. + _Iter operator--(int) + { // iterator-- + _Iter _tmp(*this); + --*this; + return _tmp; + } + + //! Advance a specified number of full characters in the string. + //! \return Myself. + _Iter &operator+=(const difference_type v) + { + if (v == 0) + return *this; + if (v < 0) + return operator-=(v * -1); + + if (pos >= ref->size_raw()) + return *this; + + // Go to the appropriate position. + // TODO: Don't force u32 on an x64 OS. Make it agnostic. + u32 i = (u32)v; + u32 sr = ref->size_raw(); + const uchar16_t *a = ref->c_str(); + while (i != 0 && pos < sr) { + if (UTF16_IS_SURROGATE_HI(a[pos])) + pos += 2; + else + ++pos; + --i; + } + if (pos > sr) + pos = sr; + + return *this; + } + + //! Go back a specified number of full characters in the string. + //! \return Myself. + _Iter &operator-=(const difference_type v) + { + if (v == 0) + return *this; + if (v > 0) + return operator+=(v * -1); + + if (pos == 0) + return *this; + + // Go to the appropriate position. + // TODO: Don't force u32 on an x64 OS. Make it agnostic. + u32 i = (u32)v; + const uchar16_t *a = ref->c_str(); + while (i != 0 && pos != 0) { + --pos; + if (UTF16_IS_SURROGATE_LO(a[pos]) != 0 && pos != 0) + --pos; + --i; } - //! Returns the iterator's position. - //! \return The iterator's position. - u32 getPos() const - { - return pos; + return *this; + } + + //! Return a new iterator that is a variable number of full characters + //! forward from the current position. + _Iter operator+(const difference_type v) const + { + _Iter ret(*this); + ret += v; + return ret; + } + + //! Return a new iterator that is a variable number of full characters + //! backward from the current position. + _Iter operator-(const difference_type v) const + { + _Iter ret(*this); + ret -= v; + return ret; + } + + //! Returns the distance between two iterators. + difference_type operator-(const _Iter &iter) const + { + // Make sure we reference the same object! + if (ref != iter.ref) + return difference_type(); + + _Iter i = iter; + difference_type ret; + + // Walk up. + if (pos > i.pos) { + while (pos > i.pos) { + ++i; + ++ret; + } + return ret; } - protected: - const ustring16* ref; - u32 pos; + // Walk down. + while (pos < i.pos) { + --i; + --ret; + } + return ret; + } + + //! Accesses the full character at the iterator's position. + const_reference operator*() const + { + if (pos >= ref->size_raw()) { + const uchar16_t *a = ref->c_str(); + u32 p = ref->size_raw(); + if (UTF16_IS_SURROGATE_LO(a[p])) + --p; + reference ret(ref, p); + return ret; + } + const_reference ret(ref, pos); + return ret; + } + + //! Accesses the full character at the iterator's position. + reference operator*() + { + if (pos >= ref->size_raw()) { + const uchar16_t *a = ref->c_str(); + u32 p = ref->size_raw(); + if (UTF16_IS_SURROGATE_LO(a[p])) + --p; + reference ret(ref, p); + return ret; + } + reference ret(ref, pos); + return ret; + } + + //! Accesses the full character at the iterator's position. + const_pointer operator->() const { return operator*(); } + + //! Accesses the full character at the iterator's position. + pointer operator->() { return operator*(); } + + //! Is the iterator at the start of the string? + bool atStart() const { return pos == 0; } + + //! Is the iterator at the end of the string? + bool atEnd() const + { + const uchar16_t *a = ref->c_str(); + if (UTF16_IS_SURROGATE(a[pos])) + return (pos + 1) >= ref->size_raw(); + else + return pos >= ref->size_raw(); + } + + //! Moves the iterator to the start of the string. + void toStart() { pos = 0; } + + //! Moves the iterator to the end of the string. + void toEnd() { pos = ref->size_raw(); } + + //! Returns the iterator's position. + //! \return The iterator's position. + u32 getPos() const { return pos; } + + protected: + const ustring16 *ref; + u32 pos; }; //! Iterator to iterate through a UTF-16 string. class _ustring16_iterator : public _ustring16_const_iterator { - public: - typedef _ustring16_iterator _Iter; - typedef _ustring16_const_iterator _Base; - typedef typename _Base::const_pointer const_pointer; - typedef typename _Base::const_reference const_reference; + public: + typedef _ustring16_iterator _Iter; + typedef _ustring16_const_iterator _Base; + typedef typename _Base::const_pointer const_pointer; + typedef typename _Base::const_reference const_reference; + typedef typename _Base::value_type value_type; + typedef typename _Base::difference_type difference_type; + typedef typename _Base::distance_type distance_type; + typedef access pointer; + typedef access reference; - typedef typename _Base::value_type value_type; - typedef typename _Base::difference_type difference_type; - typedef typename _Base::distance_type distance_type; - typedef access pointer; - typedef access reference; + using _Base::pos; + using _Base::ref; - using _Base::pos; - using _Base::ref; + //! Constructors. + _ustring16_iterator(const _Iter &i) : _ustring16_const_iterator(i) {} + _ustring16_iterator(const ustring16 &s) : + _ustring16_const_iterator(s) + { + } + _ustring16_iterator(const ustring16 &s, const u32 p) : + _ustring16_const_iterator(s, p) + { + } - //! Constructors. - _ustring16_iterator(const _Iter& i) : _ustring16_const_iterator(i) {} - _ustring16_iterator(const ustring16& s) : _ustring16_const_iterator(s) {} - _ustring16_iterator(const ustring16& s, const u32 p) : _ustring16_const_iterator(s, p) {} - - //! Accesses the full character at the iterator's position. - reference operator*() const - { - if (pos >= ref->size_raw()) - { - const uchar16_t* a = ref->c_str(); - u32 p = ref->size_raw(); - if (UTF16_IS_SURROGATE_LO(a[p])) - --p; - reference ret(ref, p); - return ret; - } - reference ret(ref, pos); + //! Accesses the full character at the iterator's position. + reference operator*() const + { + if (pos >= ref->size_raw()) { + const uchar16_t *a = ref->c_str(); + u32 p = ref->size_raw(); + if (UTF16_IS_SURROGATE_LO(a[p])) + --p; + reference ret(ref, p); return ret; } + reference ret(ref, pos); + return ret; + } - //! Accesses the full character at the iterator's position. - reference operator*() - { - if (pos >= ref->size_raw()) - { - const uchar16_t* a = ref->c_str(); - u32 p = ref->size_raw(); - if (UTF16_IS_SURROGATE_LO(a[p])) - --p; - reference ret(ref, p); - return ret; - } - reference ret(ref, pos); + //! Accesses the full character at the iterator's position. + reference operator*() + { + if (pos >= ref->size_raw()) { + const uchar16_t *a = ref->c_str(); + u32 p = ref->size_raw(); + if (UTF16_IS_SURROGATE_LO(a[p])) + --p; + reference ret(ref, p); return ret; } + reference ret(ref, pos); + return ret; + } - //! Accesses the full character at the iterator's position. - pointer operator->() const - { - return operator*(); - } + //! Accesses the full character at the iterator's position. + pointer operator->() const { return operator*(); } - //! Accesses the full character at the iterator's position. - pointer operator->() - { - return operator*(); - } + //! Accesses the full character at the iterator's position. + pointer operator->() { return operator*(); } }; typedef typename ustring16::_ustring16_iterator iterator; @@ -815,8 +794,7 @@ public: ///----------------------/// //! Default constructor - ustring16() - : array(0), allocated(1), used(0) + ustring16() : array(0), allocated(1), used(0) { #if __BYTE_ORDER == __BIG_ENDIAN encoding = unicode::EUTFE_UTF16_BE; @@ -827,10 +805,8 @@ public: array[0] = 0x0; } - //! Constructor - ustring16(const ustring16& other) - : array(0), allocated(0), used(0) + ustring16(const ustring16 &other) : array(0), allocated(0), used(0) { #if __BYTE_ORDER == __BIG_ENDIAN encoding = unicode::EUTFE_UTF16_BE; @@ -840,11 +816,9 @@ public: *this = other; } - //! Constructor from other string types template - ustring16(const string& other) - : array(0), allocated(0), used(0) + ustring16(const string &other) : array(0), allocated(0), used(0) { #if __BYTE_ORDER == __BIG_ENDIAN encoding = unicode::EUTFE_UTF16_BE; @@ -854,12 +828,11 @@ public: *this = other; } - #ifndef USTRING_NO_STL //! Constructor from std::string template - ustring16(const std::basic_string& other) - : array(0), allocated(0), used(0) + ustring16(const std::basic_string &other) : + array(0), allocated(0), used(0) { #if __BYTE_ORDER == __BIG_ENDIAN encoding = unicode::EUTFE_UTF16_BE; @@ -869,11 +842,9 @@ public: *this = other.c_str(); } - //! Constructor from iterator. template - ustring16(Itr first, Itr last) - : array(0), allocated(0), used(0) + ustring16(Itr first, Itr last) : array(0), allocated(0), used(0) { #if __BYTE_ORDER == __BIG_ENDIAN encoding = unicode::EUTFE_UTF16_BE; @@ -888,11 +859,9 @@ public: } #endif - #ifndef USTRING_CPP0X_NEWLITERALS //! Constructor for copying a character string from a pointer. - ustring16(const char* const c) - : array(0), allocated(0), used(0) + ustring16(const char *const c) : array(0), allocated(0), used(0) { #if __BYTE_ORDER == __BIG_ENDIAN encoding = unicode::EUTFE_UTF16_BE; @@ -901,13 +870,11 @@ public: #endif loadDataStream(c, strlen(c)); - //append((uchar8_t*)c); + // append((uchar8_t*)c); } - //! Constructor for copying a character string from a pointer with a given length. - ustring16(const char* const c, u32 length) - : array(0), allocated(0), used(0) + ustring16(const char *const c, u32 length) : array(0), allocated(0), used(0) { #if __BYTE_ORDER == __BIG_ENDIAN encoding = unicode::EUTFE_UTF16_BE; @@ -919,10 +886,8 @@ public: } #endif - //! Constructor for copying a UTF-8 string from a pointer. - ustring16(const uchar8_t* const c) - : array(0), allocated(0), used(0) + ustring16(const uchar8_t *const c) : array(0), allocated(0), used(0) { #if __BYTE_ORDER == __BIG_ENDIAN encoding = unicode::EUTFE_UTF16_BE; @@ -933,10 +898,8 @@ public: append(c); } - //! Constructor for copying a UTF-8 string from a single char. - ustring16(const char c) - : array(0), allocated(0), used(0) + ustring16(const char c) : array(0), allocated(0), used(0) { #if __BYTE_ORDER == __BIG_ENDIAN encoding = unicode::EUTFE_UTF16_BE; @@ -947,10 +910,8 @@ public: append((uchar32_t)c); } - //! Constructor for copying a UTF-8 string from a pointer with a given length. - ustring16(const uchar8_t* const c, u32 length) - : array(0), allocated(0), used(0) + ustring16(const uchar8_t *const c, u32 length) : array(0), allocated(0), used(0) { #if __BYTE_ORDER == __BIG_ENDIAN encoding = unicode::EUTFE_UTF16_BE; @@ -961,10 +922,8 @@ public: append(c, length); } - //! Constructor for copying a UTF-16 string from a pointer. - ustring16(const uchar16_t* const c) - : array(0), allocated(0), used(0) + ustring16(const uchar16_t *const c) : array(0), allocated(0), used(0) { #if __BYTE_ORDER == __BIG_ENDIAN encoding = unicode::EUTFE_UTF16_BE; @@ -975,10 +934,8 @@ public: append(c); } - //! Constructor for copying a UTF-16 string from a pointer with a given length - ustring16(const uchar16_t* const c, u32 length) - : array(0), allocated(0), used(0) + ustring16(const uchar16_t *const c, u32 length) : array(0), allocated(0), used(0) { #if __BYTE_ORDER == __BIG_ENDIAN encoding = unicode::EUTFE_UTF16_BE; @@ -989,10 +946,8 @@ public: append(c, length); } - //! Constructor for copying a UTF-32 string from a pointer. - ustring16(const uchar32_t* const c) - : array(0), allocated(0), used(0) + ustring16(const uchar32_t *const c) : array(0), allocated(0), used(0) { #if __BYTE_ORDER == __BIG_ENDIAN encoding = unicode::EUTFE_UTF16_BE; @@ -1003,10 +958,8 @@ public: append(c); } - //! Constructor for copying a UTF-32 from a pointer with a given length. - ustring16(const uchar32_t* const c, u32 length) - : array(0), allocated(0), used(0) + ustring16(const uchar32_t *const c, u32 length) : array(0), allocated(0), used(0) { #if __BYTE_ORDER == __BIG_ENDIAN encoding = unicode::EUTFE_UTF16_BE; @@ -1017,10 +970,8 @@ public: append(c, length); } - //! Constructor for copying a wchar_t string from a pointer. - ustring16(const wchar_t* const c) - : array(0), allocated(0), used(0) + ustring16(const wchar_t *const c) : array(0), allocated(0), used(0) { #if __BYTE_ORDER == __BIG_ENDIAN encoding = unicode::EUTFE_UTF16_BE; @@ -1029,17 +980,15 @@ public: #endif if (sizeof(wchar_t) == 4) - append(reinterpret_cast(c)); + append(reinterpret_cast(c)); else if (sizeof(wchar_t) == 2) - append(reinterpret_cast(c)); + append(reinterpret_cast(c)); else if (sizeof(wchar_t) == 1) - append(reinterpret_cast(c)); + append(reinterpret_cast(c)); } - //! Constructor for copying a wchar_t string from a pointer with a given length. - ustring16(const wchar_t* const c, u32 length) - : array(0), allocated(0), used(0) + ustring16(const wchar_t *const c, u32 length) : array(0), allocated(0), used(0) { #if __BYTE_ORDER == __BIG_ENDIAN encoding = unicode::EUTFE_UTF16_BE; @@ -1048,50 +997,47 @@ public: #endif if (sizeof(wchar_t) == 4) - append(reinterpret_cast(c), length); + append(reinterpret_cast(c), length); else if (sizeof(wchar_t) == 2) - append(reinterpret_cast(c), length); + append(reinterpret_cast(c), length); else if (sizeof(wchar_t) == 1) - append(reinterpret_cast(c), length); + append(reinterpret_cast(c), length); } - #ifdef USTRING_CPP0X //! Constructor for moving a ustring16 - ustring16(ustring16&& other) - : array(other.array), encoding(other.encoding), allocated(other.allocated), used(other.used) + ustring16(ustring16 &&other) : + array(other.array), encoding(other.encoding), + allocated(other.allocated), used(other.used) { - //std::cout << "MOVE constructor" << std::endl; + // std::cout << "MOVE constructor" << std::endl; other.array = 0; other.allocated = 0; other.used = 0; } #endif - //! Destructor ~ustring16() { allocator.deallocate(array); // delete [] array; } - //! Assignment operator - ustring16& operator=(const ustring16& other) + ustring16 &operator=(const ustring16 &other) { if (this == &other) return *this; used = other.size_raw(); - if (used >= allocated) - { + if (used >= allocated) { allocator.deallocate(array); // delete [] array; allocated = used + 1; - array = allocator.allocate(used + 1); //new u16[used]; + array = allocator.allocate(used + 1); // new u16[used]; } - const uchar16_t* p = other.c_str(); - for (u32 i=0; i<=used; ++i, ++p) + const uchar16_t *p = other.c_str(); + for (u32 i = 0; i <= used; ++i, ++p) array[i] = *p; array[used] = 0; @@ -1102,14 +1048,12 @@ public: return *this; } - #ifdef USTRING_CPP0X //! Move assignment operator - ustring16& operator=(ustring16&& other) + ustring16 &operator=(ustring16 &&other) { - if (this != &other) - { - //std::cout << "MOVE operator=" << std::endl; + if (this != &other) { + // std::cout << "MOVE operator=" << std::endl; allocator.deallocate(array); array = other.array; @@ -1123,215 +1067,188 @@ public: } #endif - //! Assignment operator for other string types template - ustring16& operator=(const string& other) + ustring16 &operator=(const string &other) { *this = other.c_str(); return *this; } - //! Assignment operator for UTF-8 strings - ustring16& operator=(const uchar8_t* const c) + ustring16 &operator=(const uchar8_t *const c) { - if (!array) - { - array = allocator.allocate(1); //new u16[1]; + if (!array) { + array = allocator.allocate(1); // new u16[1]; allocated = 1; } used = 0; array[used] = 0x0; - if (!c) return *this; + if (!c) + return *this; //! Append our string now. append(c); return *this; } - //! Assignment operator for UTF-16 strings - ustring16& operator=(const uchar16_t* const c) + ustring16 &operator=(const uchar16_t *const c) { - if (!array) - { - array = allocator.allocate(1); //new u16[1]; + if (!array) { + array = allocator.allocate(1); // new u16[1]; allocated = 1; } used = 0; array[used] = 0x0; - if (!c) return *this; + if (!c) + return *this; //! Append our string now. append(c); return *this; } - //! Assignment operator for UTF-32 strings - ustring16& operator=(const uchar32_t* const c) + ustring16 &operator=(const uchar32_t *const c) { - if (!array) - { - array = allocator.allocate(1); //new u16[1]; + if (!array) { + array = allocator.allocate(1); // new u16[1]; allocated = 1; } used = 0; array[used] = 0x0; - if (!c) return *this; + if (!c) + return *this; //! Append our string now. append(c); return *this; } - //! Assignment operator for wchar_t strings. - /** Note that this assumes that a correct unicode string is stored in the wchar_t string. - Since wchar_t changes depending on its platform, it could either be a UTF-8, -16, or -32 string. - This function assumes you are storing the correct unicode encoding inside the wchar_t string. **/ - ustring16& operator=(const wchar_t* const c) + /** Note that this assumes that a correct unicode string is stored in the wchar_t + string. Since wchar_t changes depending on its platform, it could either be a + UTF-8, -16, or -32 string. This function assumes you are storing the correct + unicode encoding inside the wchar_t string. **/ + ustring16 &operator=(const wchar_t *const c) { if (sizeof(wchar_t) == 4) - *this = reinterpret_cast(c); + *this = reinterpret_cast(c); else if (sizeof(wchar_t) == 2) - *this = reinterpret_cast(c); + *this = reinterpret_cast(c); else if (sizeof(wchar_t) == 1) - *this = reinterpret_cast(c); + *this = reinterpret_cast(c); return *this; } - //! Assignment operator for other strings. - /** Note that this assumes that a correct unicode string is stored in the string. **/ - template - ustring16& operator=(const B* const c) + /** Note that this assumes that a correct unicode string is stored in the string. + * **/ + template ustring16 &operator=(const B *const c) { if (sizeof(B) == 4) - *this = reinterpret_cast(c); + *this = reinterpret_cast(c); else if (sizeof(B) == 2) - *this = reinterpret_cast(c); + *this = reinterpret_cast(c); else if (sizeof(B) == 1) - *this = reinterpret_cast(c); + *this = reinterpret_cast(c); return *this; } - //! Direct access operator - access operator [](const u32 index) + access operator[](const u32 index) { - _IRR_DEBUG_BREAK_IF(index>=size()) // bad index + _IRR_DEBUG_BREAK_IF(index >= size()) // bad index iterator iter(*this, index); return iter.operator*(); } - //! Direct access operator - const access operator [](const u32 index) const + const access operator[](const u32 index) const { - _IRR_DEBUG_BREAK_IF(index>=size()) // bad index + _IRR_DEBUG_BREAK_IF(index >= size()) // bad index const_iterator iter(*this, index); return iter.operator*(); } - //! Equality operator - bool operator ==(const uchar16_t* const str) const + bool operator==(const uchar16_t *const str) const { if (!str) return false; u32 i; - for(i=0; array[i] && str[i]; ++i) + for (i = 0; array[i] && str[i]; ++i) if (array[i] != str[i]) return false; return !array[i] && !str[i]; } - //! Equality operator - bool operator ==(const ustring16& other) const + bool operator==(const ustring16 &other) const { - for(u32 i=0; array[i] && other.array[i]; ++i) + for (u32 i = 0; array[i] && other.array[i]; ++i) if (array[i] != other.array[i]) return false; return used == other.used; } - //! Is smaller comparator - bool operator <(const ustring16& other) const + bool operator<(const ustring16 &other) const { - for(u32 i=0; array[i] && other.array[i]; ++i) - { + for (u32 i = 0; array[i] && other.array[i]; ++i) { s32 diff = array[i] - other.array[i]; - if ( diff ) + if (diff) return diff < 0; } return used < other.used; } + //! Inequality operator + bool operator!=(const uchar16_t *const str) const { return !(*this == str); } //! Inequality operator - bool operator !=(const uchar16_t* const str) const - { - return !(*this == str); - } - - - //! Inequality operator - bool operator !=(const ustring16& other) const + bool operator!=(const ustring16 &other) const { return !(*this == other); } - //! Returns the length of a ustring16 in full characters. //! \return Length of a ustring16 in full characters. u32 size() const { const_iterator i(*this, 0); u32 pos = 0; - while (!i.atEnd()) - { + while (!i.atEnd()) { ++i; ++pos; } return pos; } - //! Informs if the ustring is empty or not. //! \return True if the ustring is empty, false if not. - bool empty() const - { - return (size_raw() == 0); - } - + bool empty() const { return (size_raw() == 0); } //! Returns a pointer to the raw UTF-16 string data. //! \return pointer to C-style NUL terminated array of UTF-16 code points. - const uchar16_t* c_str() const - { - return array; - } - + const uchar16_t *c_str() const { return array; } //! Compares the first n characters of this string with another. //! \param other Other string to compare to. //! \param n Number of characters to compare. //! \return True if the n first characters of both strings are equal. - bool equalsn(const ustring16& other, u32 n) const + bool equalsn(const ustring16 &other, u32 n) const { u32 i; - const uchar16_t* oa = other.c_str(); - for(i=0; array[i] && oa[i] && i < n; ++i) + const uchar16_t *oa = other.c_str(); + for (i = 0; array[i] && oa[i] && i < n; ++i) if (array[i] != oa[i]) return false; @@ -1340,17 +1257,16 @@ public: return (i == n) || (used == other.used); } - //! Compares the first n characters of this string with another. //! \param str Other string to compare to. //! \param n Number of characters to compare. //! \return True if the n first characters of both strings are equal. - bool equalsn(const uchar16_t* const str, u32 n) const + bool equalsn(const uchar16_t *const str, u32 n) const { if (!str) return false; u32 i; - for(i=0; array[i] && str[i] && i < n; ++i) + for (i = 0; array[i] && str[i] && i < n; ++i) if (array[i] != str[i]) return false; @@ -1359,66 +1275,62 @@ public: return (i == n) || (array[i] == 0 && str[i] == 0); } - //! Appends a character to this ustring16 //! \param character The character to append. //! \return A reference to our current string. - ustring16& append(uchar32_t character) + ustring16 &append(uchar32_t character) { if (used + 2 >= allocated) reallocate(used + 2); - if (character > 0xFFFF) - { + if (character > 0xFFFF) { used += 2; - // character will be multibyte, so split it up into a surrogate pair. + // character will be multibyte, so split it up into a surrogate + // pair. uchar16_t x = static_cast(character); - uchar16_t vh = UTF16_HI_SURROGATE | ((((character >> 16) & ((1 << 5) - 1)) - 1) << 6) | (x >> 10); + uchar16_t vh = UTF16_HI_SURROGATE | + ((((character >> 16) & ((1 << 5) - 1)) - 1) << 6) | + (x >> 10); uchar16_t vl = UTF16_LO_SURROGATE | (x & ((1 << 10) - 1)); - array[used-2] = vh; - array[used-1] = vl; - } - else - { + array[used - 2] = vh; + array[used - 1] = vl; + } else { ++used; - array[used-1] = character; + array[used - 1] = character; } array[used] = 0; return *this; } - //! Appends a UTF-8 string to this ustring16 //! \param other The UTF-8 string to append. //! \param length The length of the string to append. //! \return A reference to our current string. - ustring16& append(const uchar8_t* const other, u32 length=0xffffffff) + ustring16 &append(const uchar8_t *const other, u32 length = 0xffffffff) { if (!other) return *this; // Determine if the string is long enough for a BOM. u32 len = 0; - const uchar8_t* p = other; - do - { + const uchar8_t *p = other; + do { ++len; } while (*p++ && len < unicode::BOM_ENCODE_UTF8_LEN); // Check for BOM. unicode::EUTF_ENCODE c_bom = unicode::EUTFE_NONE; - if (len == unicode::BOM_ENCODE_UTF8_LEN) - { - if (memcmp(other, unicode::BOM_ENCODE_UTF8, unicode::BOM_ENCODE_UTF8_LEN) == 0) + if (len == unicode::BOM_ENCODE_UTF8_LEN) { + if (memcmp(other, unicode::BOM_ENCODE_UTF8, + unicode::BOM_ENCODE_UTF8_LEN) == 0) c_bom = unicode::EUTFE_UTF8; } // If a BOM was found, don't include it in the string. - const uchar8_t* c2 = other; - if (c_bom != unicode::EUTFE_NONE) - { + const uchar8_t *c2 = other; + if (c_bom != unicode::EUTFE_NONE) { c2 = other + unicode::BOM_UTF8_LEN; length -= unicode::BOM_UTF8_LEN; } @@ -1426,10 +1338,9 @@ public: // Calculate the size of the string to read in. len = 0; p = c2; - do - { + do { ++len; - } while(*p++ && len < length); + } while (*p++ && len < length); if (len > length) len = length; @@ -1440,24 +1351,19 @@ public: // Convert UTF-8 to UTF-16. u32 pos = start; - for (u32 l = 0; l> 6) & 0x03) == 0x02) - { // Invalid continuation byte. + if (((c2[l] >> 6) & 0x03) == 0x02) { // Invalid continuation byte. array[pos++] = unicode::UTF_REPLACEMENT_CHARACTER; ++l; - } - else if (c2[l] == 0xC0 || c2[l] == 0xC1) - { // Invalid byte - overlong encoding. + } else if (c2[l] == 0xC0 || c2[l] == 0xC1) { // Invalid byte - + // overlong encoding. array[pos++] = unicode::UTF_REPLACEMENT_CHARACTER; ++l; - } - else if ((c2[l] & 0xF8) == 0xF0) - { // 4 bytes UTF-8, 2 bytes UTF-16. + } else if ((c2[l] & 0xF8) == + 0xF0) { // 4 bytes UTF-8, 2 bytes UTF-16. // Check for a full string. - if ((l + 3) >= len) - { + if ((l + 3) >= len) { array[pos++] = unicode::UTF_REPLACEMENT_CHARACTER; l += 3; break; @@ -1466,37 +1372,50 @@ public: // Validate. bool valid = true; u8 l2 = 0; - if (valid && (((c2[l+1] >> 6) & 0x03) == 0x02)) ++l2; else valid = false; - if (valid && (((c2[l+2] >> 6) & 0x03) == 0x02)) ++l2; else valid = false; - if (valid && (((c2[l+3] >> 6) & 0x03) == 0x02)) ++l2; else valid = false; - if (!valid) - { + if (valid && (((c2[l + 1] >> 6) & 0x03) == 0x02)) + ++l2; + else + valid = false; + if (valid && (((c2[l + 2] >> 6) & 0x03) == 0x02)) + ++l2; + else + valid = false; + if (valid && (((c2[l + 3] >> 6) & 0x03) == 0x02)) + ++l2; + else + valid = false; + if (!valid) { array[pos++] = unicode::UTF_REPLACEMENT_CHARACTER; l += l2; continue; } // Decode. - uchar8_t b1 = ((c2[l] & 0x7) << 2) | ((c2[l+1] >> 4) & 0x3); - uchar8_t b2 = ((c2[l+1] & 0xF) << 4) | ((c2[l+2] >> 2) & 0xF); - uchar8_t b3 = ((c2[l+2] & 0x3) << 6) | (c2[l+3] & 0x3F); - uchar32_t v = b3 | ((uchar32_t)b2 << 8) | ((uchar32_t)b1 << 16); + uchar8_t b1 = ((c2[l] & 0x7) << 2) | + ((c2[l + 1] >> 4) & 0x3); + uchar8_t b2 = ((c2[l + 1] & 0xF) << 4) | + ((c2[l + 2] >> 2) & 0xF); + uchar8_t b3 = ((c2[l + 2] & 0x3) << 6) | + (c2[l + 3] & 0x3F); + uchar32_t v = b3 | ((uchar32_t)b2 << 8) | + ((uchar32_t)b1 << 16); // Split v up into a surrogate pair. uchar16_t x = static_cast(v); - uchar16_t vh = UTF16_HI_SURROGATE | ((((v >> 16) & ((1 << 5) - 1)) - 1) << 6) | (x >> 10); + uchar16_t vh = UTF16_HI_SURROGATE | + ((((v >> 16) & ((1 << 5) - 1)) - 1) << 6) | + (x >> 10); uchar16_t vl = UTF16_LO_SURROGATE | (x & ((1 << 10) - 1)); array[pos++] = vh; array[pos++] = vl; l += 4; - ++used; // Using two shorts this time, so increase used by 1. - } - else if ((c2[l] & 0xF0) == 0xE0) - { // 3 bytes UTF-8, 1 byte UTF-16. + ++used; // Using two shorts this time, so increase used + // by 1. + } else if ((c2[l] & 0xF0) == + 0xE0) { // 3 bytes UTF-8, 1 byte UTF-16. // Check for a full string. - if ((l + 2) >= len) - { + if ((l + 2) >= len) { array[pos++] = unicode::UTF_REPLACEMENT_CHARACTER; l += 2; break; @@ -1505,35 +1424,39 @@ public: // Validate. bool valid = true; u8 l2 = 0; - if (valid && (((c2[l+1] >> 6) & 0x03) == 0x02)) ++l2; else valid = false; - if (valid && (((c2[l+2] >> 6) & 0x03) == 0x02)) ++l2; else valid = false; - if (!valid) - { + if (valid && (((c2[l + 1] >> 6) & 0x03) == 0x02)) + ++l2; + else + valid = false; + if (valid && (((c2[l + 2] >> 6) & 0x03) == 0x02)) + ++l2; + else + valid = false; + if (!valid) { array[pos++] = unicode::UTF_REPLACEMENT_CHARACTER; l += l2; continue; } // Decode. - uchar8_t b1 = ((c2[l] & 0xF) << 4) | ((c2[l+1] >> 2) & 0xF); - uchar8_t b2 = ((c2[l+1] & 0x3) << 6) | (c2[l+2] & 0x3F); + uchar8_t b1 = ((c2[l] & 0xF) << 4) | + ((c2[l + 1] >> 2) & 0xF); + uchar8_t b2 = ((c2[l + 1] & 0x3) << 6) | + (c2[l + 2] & 0x3F); uchar16_t ch = b2 | ((uchar16_t)b1 << 8); array[pos++] = ch; l += 3; - } - else if ((c2[l] & 0xE0) == 0xC0) - { // 2 bytes UTF-8, 1 byte UTF-16. + } else if ((c2[l] & 0xE0) == + 0xC0) { // 2 bytes UTF-8, 1 byte UTF-16. // Check for a full string. - if ((l + 1) >= len) - { + if ((l + 1) >= len) { array[pos++] = unicode::UTF_REPLACEMENT_CHARACTER; l += 1; break; } // Validate. - if (((c2[l+1] >> 6) & 0x03) != 0x02) - { + if (((c2[l + 1] >> 6) & 0x03) != 0x02) { array[pos++] = unicode::UTF_REPLACEMENT_CHARACTER; ++l; continue; @@ -1541,19 +1464,18 @@ public: // Decode. uchar8_t b1 = (c2[l] >> 2) & 0x7; - uchar8_t b2 = ((c2[l] & 0x3) << 6) | (c2[l+1] & 0x3F); + uchar8_t b2 = ((c2[l] & 0x3) << 6) | (c2[l + 1] & 0x3F); uchar16_t ch = b2 | ((uchar16_t)b1 << 8); array[pos++] = ch; l += 2; - } - else - { // 1 byte UTF-8, 1 byte UTF-16. + } else { // 1 byte UTF-8, 1 byte UTF-16. // Validate. - if (c2[l] > 0x7F) - { // Values above 0xF4 are restricted and aren't used. By now, anything above 0x7F is invalid. + if (c2[l] > 0x7F) { // Values above 0xF4 are restricted + // and aren't used. By now, anything + // above 0x7F is invalid. array[pos++] = unicode::UTF_REPLACEMENT_CHARACTER; - } - else array[pos++] = static_cast(c2[l]); + } else + array[pos++] = static_cast(c2[l]); ++l; } } @@ -1565,35 +1487,34 @@ public: return *this; } - //! Appends a UTF-16 string to this ustring16 //! \param other The UTF-16 string to append. //! \param length The length of the string to append. //! \return A reference to our current string. - ustring16& append(const uchar16_t* const other, u32 length=0xffffffff) + ustring16 &append(const uchar16_t *const other, u32 length = 0xffffffff) { if (!other) return *this; // Determine if the string is long enough for a BOM. u32 len = 0; - const uchar16_t* p = other; - do - { + const uchar16_t *p = other; + do { ++len; } while (*p++ && len < unicode::BOM_ENCODE_UTF16_LEN); // Check for the BOM to determine the string's endianness. unicode::EUTF_ENDIAN c_end = unicode::EUTFEE_NATIVE; - if (memcmp(other, unicode::BOM_ENCODE_UTF16_LE, unicode::BOM_ENCODE_UTF16_LEN) == 0) + if (memcmp(other, unicode::BOM_ENCODE_UTF16_LE, + unicode::BOM_ENCODE_UTF16_LEN) == 0) c_end = unicode::EUTFEE_LITTLE; - else if (memcmp(other, unicode::BOM_ENCODE_UTF16_BE, unicode::BOM_ENCODE_UTF16_LEN) == 0) + else if (memcmp(other, unicode::BOM_ENCODE_UTF16_BE, + unicode::BOM_ENCODE_UTF16_LEN) == 0) c_end = unicode::EUTFEE_BIG; // If a BOM was found, don't include it in the string. - const uchar16_t* c2 = other; - if (c_end != unicode::EUTFEE_NATIVE) - { + const uchar16_t *c2 = other; + if (c_end != unicode::EUTFEE_NATIVE) { c2 = other + unicode::BOM_UTF16_LEN; length -= unicode::BOM_UTF16_LEN; } @@ -1601,10 +1522,9 @@ public: // Calculate the size of the string to read in. len = 0; p = c2; - do - { + do { ++len; - } while(*p++ && len < length); + } while (*p++ && len < length); if (len > length) len = length; @@ -1616,8 +1536,7 @@ public: // Copy the string now. unicode::EUTF_ENDIAN m_end = getEndianness(); - for (u32 l = start; l < start + len; ++l) - { + for (u32 l = start; l < start + len; ++l) { array[l] = (uchar16_t)c2[l]; if (c_end != unicode::EUTFEE_NATIVE && c_end != m_end) array[l] = unicode::swapEndian16(array[l]); @@ -1630,43 +1549,43 @@ public: return *this; } - //! Appends a UTF-32 string to this ustring16 //! \param other The UTF-32 string to append. //! \param length The length of the string to append. //! \return A reference to our current string. - ustring16& append(const uchar32_t* const other, u32 length=0xffffffff) + ustring16 &append(const uchar32_t *const other, u32 length = 0xffffffff) { if (!other) return *this; // Check for the BOM to determine the string's endianness. unicode::EUTF_ENDIAN c_end = unicode::EUTFEE_NATIVE; - if (memcmp(other, unicode::BOM_ENCODE_UTF32_LE, unicode::BOM_ENCODE_UTF32_LEN) == 0) + if (memcmp(other, unicode::BOM_ENCODE_UTF32_LE, + unicode::BOM_ENCODE_UTF32_LEN) == 0) c_end = unicode::EUTFEE_LITTLE; - else if (memcmp(other, unicode::BOM_ENCODE_UTF32_BE, unicode::BOM_ENCODE_UTF32_LEN) == 0) + else if (memcmp(other, unicode::BOM_ENCODE_UTF32_BE, + unicode::BOM_ENCODE_UTF32_LEN) == 0) c_end = unicode::EUTFEE_BIG; // If a BOM was found, don't include it in the string. - const uchar32_t* c2 = other; - if (c_end != unicode::EUTFEE_NATIVE) - { + const uchar32_t *c2 = other; + if (c_end != unicode::EUTFEE_NATIVE) { c2 = other + unicode::BOM_UTF32_LEN; length -= unicode::BOM_UTF32_LEN; } // Calculate the size of the string to read in. u32 len = 0; - const uchar32_t* p = c2; - do - { + const uchar32_t *p = c2; + do { ++len; - } while(*p++ && len < length); + } while (*p++ && len < length); if (len > length) len = length; // If we need to grow the size of the array, do it now. - // In case all of the UTF-32 string is split into surrogate pairs, do len * 2. + // In case all of the UTF-32 string is split into surrogate pairs, do len + // * 2. if (used + (len * 2) >= allocated) reallocate(used + ((len * 2) * 2)); u32 start = used; @@ -1674,30 +1593,30 @@ public: // Convert UTF-32 to UTF-16. unicode::EUTF_ENDIAN m_end = getEndianness(); u32 pos = start; - for (u32 l = 0; l 0xFFFF) - { - // Split ch up into a surrogate pair as it is over 16 bits long. + if (ch > 0xFFFF) { + // Split ch up into a surrogate pair as it is over 16 bits + // long. uchar16_t x = static_cast(ch); - uchar16_t vh = UTF16_HI_SURROGATE | ((((ch >> 16) & ((1 << 5) - 1)) - 1) << 6) | (x >> 10); + uchar16_t vh = UTF16_HI_SURROGATE | + ((((ch >> 16) & ((1 << 5) - 1)) - 1) + << 6) | + (x >> 10); uchar16_t vl = UTF16_LO_SURROGATE | (x & ((1 << 10) - 1)); array[pos++] = vh; array[pos++] = vl; - ++used; // Using two shorts, so increased used again. - } - else if (ch >= 0xD800 && ch <= 0xDFFF) - { + ++used; // Using two shorts, so increased used again. + } else if (ch >= 0xD800 && ch <= 0xDFFF) { // Between possible UTF-16 surrogates (invalid!) array[pos++] = unicode::UTF_REPLACEMENT_CHARACTER; - } - else array[pos++] = static_cast(ch); + } else + array[pos++] = static_cast(ch); } array[used] = 0; @@ -1707,21 +1626,20 @@ public: return *this; } - //! Appends a ustring16 to this ustring16 //! \param other The string to append to this one. //! \return A reference to our current string. - ustring16& append(const ustring16& other) + ustring16 &append(const ustring16 &other) { - const uchar16_t* oa = other.c_str(); + const uchar16_t *oa = other.c_str(); u32 len = other.size_raw(); if (used + len >= allocated) reallocate(used + len); - for (u32 l=0; l& append(const ustring16& other, u32 length) + ustring16 &append(const ustring16 &other, u32 length) { if (other.size() == 0) return *this; - if (other.size() < length) - { + if (other.size() < length) { append(other); return *this; } @@ -1750,8 +1666,7 @@ public: const_iterator iter(other, 0); u32 l = length; - while (!iter.atEnd() && l) - { + while (!iter.atEnd() && l) { uchar32_t c = *iter; append(c); ++iter; @@ -1761,7 +1676,6 @@ public: return *this; } - //! Reserves some memory. //! \param count The amount of characters to reserve. void reserve(u32 count) @@ -1772,7 +1686,6 @@ public: reallocate(count); } - //! Finds first occurrence of character. //! \param c The character to search for. //! \return Position where the character has been found, or -1 if not found. @@ -1781,8 +1694,7 @@ public: const_iterator i(*this, 0); s32 pos = 0; - while (!i.atEnd()) - { + while (!i.atEnd()) { uchar32_t t = *i; if (c == t) return pos; @@ -1794,10 +1706,12 @@ public: } //! Finds first occurrence of a character of a list. - //! \param c A list of characters to find. For example if the method should find the first occurrence of 'a' or 'b', this parameter should be "ab". - //! \param count The amount of characters in the list. Usually, this should be strlen(c). - //! \return Position where one of the characters has been found, or -1 if not found. - s32 findFirstChar(const uchar32_t* const c, u32 count=1) const + //! \param c A list of characters to find. For example if the method should find + //! the first occurrence of 'a' or 'b', this parameter should be "ab". \param + //! count The amount of characters in the list. Usually, this should be strlen(c). + //! \return Position where one of the characters has been found, or -1 if not + //! found. + s32 findFirstChar(const uchar32_t *const c, u32 count = 1) const { if (!c || !count) return -1; @@ -1805,10 +1719,9 @@ public: const_iterator i(*this, 0); s32 pos = 0; - while (!i.atEnd()) - { + while (!i.atEnd()) { uchar32_t t = *i; - for (u32 j=0; j& str, const u32 start = 0) const + s32 find(const ustring16 &str, const u32 start = 0) const { u32 my_size = size(); u32 their_size = str.size(); @@ -1965,14 +1875,12 @@ public: const_iterator i(*this, start); s32 pos = start; - while (!i.atEnd()) - { + while (!i.atEnd()) { const_iterator i2(i); const_iterator j(str, 0); uchar32_t t1 = (uchar32_t)*i2; uchar32_t t2 = (uchar32_t)*j; - while (t1 == t2) - { + while (t1 == t2) { ++i2; ++j; if (j.atEnd()) @@ -1987,16 +1895,14 @@ public: return -1; } - //! Finds another ustring16 in this ustring16. //! \param str The string to find. //! \param start The start position of the search. //! \return Positions where the string has been found, or -1 if not found. - s32 find_raw(const ustring16& str, const u32 start = 0) const + s32 find_raw(const ustring16 &str, const u32 start = 0) const { - const uchar16_t* data = str.c_str(); - if (data && *data) - { + const uchar16_t *data = str.c_str(); + if (data && *data) { u32 len = 0; while (data[len]) @@ -2005,11 +1911,10 @@ public: if (len > used) return -1; - for (u32 i=start; i<=used-len; ++i) - { - u32 j=0; + for (u32 i = start; i <= used - len; ++i) { + u32 j = 0; - while(data[j] && array[i+j] == data[j]) + while (data[j] && array[i + j] == data[j]) ++j; if (!data[j]) @@ -2020,7 +1925,6 @@ public: return -1; } - //! Returns a substring. //! \param begin: Start of substring. //! \param length: Length of substring. @@ -2030,18 +1934,17 @@ public: u32 len = size(); // if start after ustring16 // or no proper substring length - if ((length <= 0) || (begin>=len)) + if ((length <= 0) || (begin >= len)) return ustring16(""); // clamp length to maximal value - if ((length+begin) > len) - length = len-begin; + if ((length + begin) > len) + length = len - begin; ustring16 o; - o.reserve((length+1) * 2); + o.reserve((length + 1) * 2); const_iterator i(*this, begin); - while (!i.atEnd() && length) - { + while (!i.atEnd() && length) { o.append(*i); ++i; --length; @@ -2050,128 +1953,115 @@ public: return o; } - //! Appends a character to this ustring16. //! \param c Character to append. //! \return A reference to our current string. - ustring16& operator += (char c) + ustring16 &operator+=(char c) { append((uchar32_t)c); return *this; } - //! Appends a character to this ustring16. //! \param c Character to append. //! \return A reference to our current string. - ustring16& operator += (uchar32_t c) + ustring16 &operator+=(uchar32_t c) { append(c); return *this; } - //! Appends a number to this ustring16. //! \param c Number to append. //! \return A reference to our current string. - ustring16& operator += (short c) + ustring16 &operator+=(short c) { append(core::stringc(c)); return *this; } - //! Appends a number to this ustring16. //! \param c Number to append. //! \return A reference to our current string. - ustring16& operator += (unsigned short c) + ustring16 &operator+=(unsigned short c) { append(core::stringc(c)); return *this; } - #ifdef USTRING_CPP0X_NEWLITERALS //! Appends a number to this ustring16. //! \param c Number to append. //! \return A reference to our current string. - ustring16& operator += (int c) + ustring16 &operator+=(int c) { append(core::stringc(c)); return *this; } - //! Appends a number to this ustring16. //! \param c Number to append. //! \return A reference to our current string. - ustring16& operator += (unsigned int c) + ustring16 &operator+=(unsigned int c) { append(core::stringc(c)); return *this; } #endif - //! Appends a number to this ustring16. //! \param c Number to append. //! \return A reference to our current string. - ustring16& operator += (long c) + ustring16 &operator+=(long c) { append(core::stringc(c)); return *this; } - //! Appends a number to this ustring16. //! \param c Number to append. //! \return A reference to our current string. - ustring16& operator += (unsigned long c) + ustring16 &operator+=(unsigned long c) { append(core::stringc(c)); return *this; } - //! Appends a number to this ustring16. //! \param c Number to append. //! \return A reference to our current string. - ustring16& operator += (double c) + ustring16 &operator+=(double c) { append(core::stringc(c)); return *this; } - //! Appends a char ustring16 to this ustring16. //! \param c Char ustring16 to append. //! \return A reference to our current string. - ustring16& operator += (const uchar16_t* const c) + ustring16 &operator+=(const uchar16_t *const c) { append(c); return *this; } - //! Appends a ustring16 to this ustring16. //! \param other ustring16 to append. //! \return A reference to our current string. - ustring16& operator += (const ustring16& other) + ustring16 &operator+=(const ustring16 &other) { append(other); return *this; } - //! Replaces all characters of a given type with another one. //! \param toReplace Character to replace. //! \param replaceWith Character replacing the old one. //! \return A reference to our current string. - ustring16& replace(uchar32_t toReplace, uchar32_t replaceWith) + ustring16 &replace(uchar32_t toReplace, uchar32_t replaceWith) { iterator i(*this, 0); - while (!i.atEnd()) - { + while (!i.atEnd()) { typename ustring16::access a = *i; if ((uchar32_t)a == toReplace) a = replaceWith; @@ -2180,18 +2070,18 @@ public: return *this; } - //! Replaces all instances of a string with another one. //! \param toReplace The string to replace. //! \param replaceWith The string replacing the old one. //! \return A reference to our current string. - ustring16& replace(const ustring16& toReplace, const ustring16& replaceWith) + ustring16 &replace(const ustring16 &toReplace, + const ustring16 &replaceWith) { if (toReplace.size() == 0) return *this; - const uchar16_t* other = toReplace.c_str(); - const uchar16_t* replace = replaceWith.c_str(); + const uchar16_t *other = toReplace.c_str(); + const uchar16_t *replace = replaceWith.c_str(); const u32 other_size = toReplace.size_raw(); const u32 replace_size = replaceWith.size_raw(); @@ -2199,11 +2089,9 @@ public: s32 delta = replace_size - other_size; // A character for character replace. The string will not shrink or grow. - if (delta == 0) - { + if (delta == 0) { s32 pos = 0; - while ((pos = find_raw(other, pos)) != -1) - { + while ((pos = find_raw(other, pos)) != -1) { for (u32 i = 0; i < replace_size; ++i) array[pos + i] = replace[i]; ++pos; @@ -2212,25 +2100,20 @@ public: } // We are going to be removing some characters. The string will shrink. - if (delta < 0) - { + if (delta < 0) { u32 i = 0; - for (u32 pos = 0; pos <= used; ++i, ++pos) - { + for (u32 pos = 0; pos <= used; ++i, ++pos) { // Is this potentially a match? - if (array[pos] == *other) - { + if (array[pos] == *other) { // Check to see if we have a match. u32 j; - for (j = 0; j < other_size; ++j) - { + for (j = 0; j < other_size; ++j) { if (array[pos + j] != other[j]) break; } // If we have a match, replace characters. - if (j == other_size) - { + if (j == other_size) { for (j = 0; j < replace_size; ++j) array[i + j] = replace[j]; i += replace_size - 1; @@ -2249,11 +2132,11 @@ public: } // We are going to be adding characters, so the string size will increase. - // Count the number of times toReplace exists in the string so we can allocate the new size. + // Count the number of times toReplace exists in the string so we can + // allocate the new size. u32 find_count = 0; s32 pos = 0; - while ((pos = find_raw(other, pos)) != -1) - { + while ((pos = find_raw(other, pos)) != -1) { ++find_count; ++pos; } @@ -2265,15 +2148,13 @@ public: // Start replacing. pos = 0; - while ((pos = find_raw(other, pos)) != -1) - { - uchar16_t* start = array + pos + other_size - 1; - uchar16_t* ptr = array + used; - uchar16_t* end = array + used + delta; + while ((pos = find_raw(other, pos)) != -1) { + uchar16_t *start = array + pos + other_size - 1; + uchar16_t *ptr = array + used; + uchar16_t *end = array + used + delta; // Shift characters to make room for the string. - while (ptr != start) - { + while (ptr != start) { *end = *ptr; --ptr; --end; @@ -2292,29 +2173,27 @@ public: return *this; } - //! Removes characters from a ustring16.. //! \param c The character to remove. //! \return A reference to our current string. - ustring16& remove(uchar32_t c) + ustring16 &remove(uchar32_t c) { u32 pos = 0; u32 found = 0; - u32 len = (c > 0xFFFF ? 2 : 1); // Remove characters equal to the size of c as a UTF-16 character. - for (u32 i=0; i<=used; ++i) - { + u32 len = (c > 0xFFFF ? 2 : 1); // Remove characters equal to the size of + // c as a UTF-16 character. + for (u32 i = 0; i <= used; ++i) { uchar32_t uc32 = 0; if (!UTF16_IS_SURROGATE_HI(array[i])) uc32 |= array[i]; - else if (i + 1 <= used) - { - // Convert the surrogate pair into a single UTF-32 character. + else if (i + 1 <= used) { + // Convert the surrogate pair into a single UTF-32 + // character. uc32 = unicode::toUTF32(array[i], array[i + 1]); } u32 len2 = (uc32 > 0xFFFF ? 2 : 1); - if (uc32 == c) - { + if (uc32 == c) { found += len; continue; } @@ -2328,29 +2207,26 @@ public: return *this; } - //! Removes a ustring16 from the ustring16. //! \param toRemove The string to remove. //! \return A reference to our current string. - ustring16& remove(const ustring16& toRemove) + ustring16 &remove(const ustring16 &toRemove) { u32 size = toRemove.size_raw(); - if (size == 0) return *this; + if (size == 0) + return *this; - const uchar16_t* tra = toRemove.c_str(); + const uchar16_t *tra = toRemove.c_str(); u32 pos = 0; u32 found = 0; - for (u32 i=0; i<=used; ++i) - { + for (u32 i = 0; i <= used; ++i) { u32 j = 0; - while (j < size) - { + while (j < size) { if (array[i + j] != tra[j]) break; ++j; } - if (j == size) - { + if (j == size) { found += size; i += size - 1; continue; @@ -2363,11 +2239,10 @@ public: return *this; } - //! Removes characters from the ustring16. //! \param characters The characters to remove. //! \return A reference to our current string. - ustring16& removeChars(const ustring16& characters) + ustring16 &removeChars(const ustring16 &characters) { if (characters.size_raw() == 0) return *this; @@ -2375,33 +2250,35 @@ public: u32 pos = 0; u32 found = 0; const_iterator iter(characters); - for (u32 i=0; i<=used; ++i) - { + for (u32 i = 0; i <= used; ++i) { uchar32_t uc32 = 0; if (!UTF16_IS_SURROGATE_HI(array[i])) uc32 |= array[i]; - else if (i + 1 <= used) - { - // Convert the surrogate pair into a single UTF-32 character. - uc32 = unicode::toUTF32(array[i], array[i+1]); + else if (i + 1 <= used) { + // Convert the surrogate pair into a single UTF-32 + // character. + uc32 = unicode::toUTF32(array[i], array[i + 1]); } u32 len2 = (uc32 > 0xFFFF ? 2 : 1); bool cont = false; iter.toStart(); - while (!iter.atEnd()) - { + while (!iter.atEnd()) { uchar32_t c = *iter; - if (uc32 == c) - { - found += (c > 0xFFFF ? 2 : 1); // Remove characters equal to the size of c as a UTF-16 character. + if (uc32 == c) { + found += (c > 0xFFFF ? 2 + : 1); // Remove characters + // equal to the size of + // c as a UTF-16 + // character. ++i; cont = true; break; } ++iter; } - if (cont) continue; + if (cont) + continue; array[pos++] = array[i]; if (len2 == 2) @@ -2412,33 +2289,33 @@ public: return *this; } - //! Trims the ustring16. - //! Removes the specified characters (by default, Latin-1 whitespace) from the begining and the end of the ustring16. - //! \param whitespace The characters that are to be considered as whitespace. - //! \return A reference to our current string. - ustring16& trim(const ustring16& whitespace = " \t\n\r") + //! Removes the specified characters (by default, Latin-1 whitespace) from the + //! begining and the end of the ustring16. \param whitespace The characters that + //! are to be considered as whitespace. \return A reference to our current string. + ustring16 &trim(const ustring16 &whitespace = " \t\n\r") { core::array utf32white = whitespace.toUTF32(); // find start and end of the substring without the specified characters - const s32 begin = findFirstCharNotInList(utf32white.const_pointer(), whitespace.used + 1); + const s32 begin = findFirstCharNotInList( + utf32white.const_pointer(), whitespace.used + 1); if (begin == -1) - return (*this=""); + return (*this = ""); - const s32 end = findLastCharNotInList(utf32white.const_pointer(), whitespace.used + 1); + const s32 end = findLastCharNotInList( + utf32white.const_pointer(), whitespace.used + 1); - return (*this = subString(begin, (end +1) - begin)); + return (*this = subString(begin, (end + 1) - begin)); } - //! Erases a character from the ustring16. - //! May be slow, because all elements following after the erased element have to be copied. - //! \param index Index of element to be erased. - //! \return A reference to our current string. - ustring16& erase(u32 index) + //! May be slow, because all elements following after the erased element have to + //! be copied. \param index Index of element to be erased. \return A reference to + //! our current string. + ustring16 &erase(u32 index) { - _IRR_DEBUG_BREAK_IF(index>used) // access violation + _IRR_DEBUG_BREAK_IF(index > used) // access violation iterator i(*this, index); @@ -2454,25 +2331,23 @@ public: return *this; } - - //! Validate the existing ustring16, checking for valid surrogate pairs and checking for proper termination. - //! \return A reference to our current string. - ustring16& validate() + //! Validate the existing ustring16, checking for valid surrogate pairs and + //! checking for proper termination. \return A reference to our current string. + ustring16 &validate() { // Validate all unicode characters. - for (u32 i=0; i= allocated) || UTF16_IS_SURROGATE_LO(array[i])) + if (UTF16_IS_SURROGATE(array[i])) { + if (((i + 1) >= allocated) || + UTF16_IS_SURROGATE_LO(array[i])) array[i] = unicode::UTF_REPLACEMENT_CHARACTER; - else if (UTF16_IS_SURROGATE_HI(array[i]) && !UTF16_IS_SURROGATE_LO(array[i+1])) + else if (UTF16_IS_SURROGATE_HI(array[i]) && + !UTF16_IS_SURROGATE_LO(array[i + 1])) array[i] = unicode::UTF_REPLACEMENT_CHARACTER; ++i; } @@ -2482,15 +2357,13 @@ public: // terminate used = 0; - if (allocated > 0) - { + if (allocated > 0) { used = allocated - 1; array[used] = 0; } return *this; } - //! Gets the last char of the ustring16, or 0. //! \return The last char of the ustring16, or 0. uchar32_t lastChar() const @@ -2498,26 +2371,22 @@ public: if (used < 1) return 0; - if (UTF16_IS_SURROGATE_LO(array[used-1])) - { + if (UTF16_IS_SURROGATE_LO(array[used - 1])) { // Make sure we have a paired surrogate. if (used < 2) return 0; // Check for an invalid surrogate. - if (!UTF16_IS_SURROGATE_HI(array[used-2])) + if (!UTF16_IS_SURROGATE_HI(array[used - 2])) return 0; // Convert the surrogate pair into a single UTF-32 character. - return unicode::toUTF32(array[used-2], array[used-1]); - } - else - { - return array[used-1]; + return unicode::toUTF32(array[used - 2], array[used - 1]); + } else { + return array[used - 1]; } } - //! Split the ustring16 into parts. /** This method will split a ustring16 at certain delimiter characters into the container passed in as reference. The type of the container @@ -2536,32 +2405,33 @@ public: characters between the delimiters are returned. \return The number of resulting substrings */ - template - u32 split(container& ret, const uchar32_t* const c, u32 count=1, bool ignoreEmptyTokens=true, bool keepSeparators=false) const + template + u32 split(container &ret, const uchar32_t *const c, u32 count = 1, + bool ignoreEmptyTokens = true, bool keepSeparators = false) const { if (!c) return 0; const_iterator i(*this); - const u32 oldSize=ret.size(); + const u32 oldSize = ret.size(); u32 pos = 0; u32 lastpos = 0; u32 lastpospos = 0; bool lastWasSeparator = false; - while (!i.atEnd()) - { + while (!i.atEnd()) { uchar32_t ch = *i; bool foundSeparator = false; - for (u32 j=0; j(&array[lastpospos], pos - lastpos)); + ret.push_back(ustring16( + &array[lastpospos], + pos - lastpos)); foundSeparator = true; lastpos = (keepSeparators ? pos : pos + 1); - lastpospos = (keepSeparators ? i.getPos() : i.getPos() + 1); + lastpospos = (keepSeparators ? i.getPos() + : i.getPos() + 1); break; } } @@ -2572,10 +2442,9 @@ public: u32 s = size() + 1; if (s > lastpos) ret.push_back(ustring16(&array[lastpospos], s - lastpos)); - return ret.size()-oldSize; + return ret.size() - oldSize; } - //! Split the ustring16 into parts. /** This method will split a ustring16 at certain delimiter characters into the container passed in as reference. The type of the container @@ -2593,35 +2462,29 @@ public: characters between the delimiters are returned. \return The number of resulting substrings */ - template - u32 split(container& ret, const ustring16& c, bool ignoreEmptyTokens=true, bool keepSeparators=false) const + template + u32 split(container &ret, const ustring16 &c, + bool ignoreEmptyTokens = true, bool keepSeparators = false) const { core::array v = c.toUTF32(); - return split(ret, v.pointer(), v.size(), ignoreEmptyTokens, keepSeparators); + return split(ret, v.pointer(), v.size(), ignoreEmptyTokens, + keepSeparators); } - //! Gets the size of the allocated memory buffer for the string. //! \return The size of the allocated memory buffer. - u32 capacity() const - { - return allocated; - } - - - //! Returns the raw number of UTF-16 code points in the string which includes the individual surrogates. - //! \return The raw number of UTF-16 code points, excluding the trialing NUL. - u32 size_raw() const - { - return used; - } + u32 capacity() const { return allocated; } + //! Returns the raw number of UTF-16 code points in the string which includes the + //! individual surrogates. \return The raw number of UTF-16 code points, excluding + //! the trialing NUL. + u32 size_raw() const { return used; } //! Inserts a character into the string. //! \param c The character to insert. //! \param pos The position to insert the character. //! \return A reference to our current string. - ustring16& insert(uchar32_t c, u32 pos) + ustring16 &insert(uchar32_t c, u32 pos) { u8 len = (c > 0xFFFF ? 2 : 1); @@ -2634,32 +2497,31 @@ public: for (u32 i = used - 2; i > iter.getPos(); --i) array[i] = array[i - len]; - if (c > 0xFFFF) - { + if (c > 0xFFFF) { // c will be multibyte, so split it up into a surrogate pair. uchar16_t x = static_cast(c); - uchar16_t vh = UTF16_HI_SURROGATE | ((((c >> 16) & ((1 << 5) - 1)) - 1) << 6) | (x >> 10); + uchar16_t vh = UTF16_HI_SURROGATE | + ((((c >> 16) & ((1 << 5) - 1)) - 1) << 6) | + (x >> 10); uchar16_t vl = UTF16_LO_SURROGATE | (x & ((1 << 10) - 1)); array[iter.getPos()] = vh; - array[iter.getPos()+1] = vl; - } - else - { + array[iter.getPos() + 1] = vl; + } else { array[iter.getPos()] = static_cast(c); } array[used] = 0; return *this; } - //! Inserts a string into the string. //! \param c The string to insert. //! \param pos The position to insert the string. //! \return A reference to our current string. - ustring16& insert(const ustring16& c, u32 pos) + ustring16 &insert(const ustring16 &c, u32 pos) { u32 len = c.size_raw(); - if (len == 0) return *this; + if (len == 0) + return *this; if (used + len >= allocated) reallocate(used + len); @@ -2670,9 +2532,8 @@ public: for (u32 i = used - 2; i > iter.getPos() + len; --i) array[i] = array[i - len]; - const uchar16_t* s = c.c_str(); - for (u32 i = 0; i < len; ++i) - { + const uchar16_t *s = c.c_str(); + for (u32 i = 0; i < len; ++i) { array[pos++] = *s; ++s; } @@ -2681,12 +2542,11 @@ public: return *this; } - //! Inserts a character into the string. //! \param c The character to insert. //! \param pos The position to insert the character. //! \return A reference to our current string. - ustring16& insert_raw(uchar16_t c, u32 pos) + ustring16 &insert_raw(uchar16_t c, u32 pos) { if (used + 1 >= allocated) reallocate(used + 1); @@ -2701,14 +2561,12 @@ public: return *this; } - //! Removes a character from string. //! \param pos Position of the character to remove. //! \return A reference to our current string. - ustring16& erase_raw(u32 pos) + ustring16 &erase_raw(u32 pos) { - for (u32 i=pos; i<=used; ++i) - { + for (u32 i = pos; i <= used; ++i) { array[i] = array[i + 1]; } --used; @@ -2716,18 +2574,16 @@ public: return *this; } - //! Replaces a character in the string. //! \param c The new character. //! \param pos The position of the character to replace. //! \return A reference to our current string. - ustring16& replace_raw(uchar16_t c, u32 pos) + ustring16 &replace_raw(uchar16_t c, u32 pos) { array[pos] = c; return *this; } - //! Returns an iterator to the beginning of the string. //! \return An iterator to the beginning of the string. iterator begin() @@ -2736,7 +2592,6 @@ public: return i; } - //! Returns an iterator to the beginning of the string. //! \return An iterator to the beginning of the string. const_iterator begin() const @@ -2745,7 +2600,6 @@ public: return i; } - //! Returns an iterator to the beginning of the string. //! \return An iterator to the beginning of the string. const_iterator cbegin() const @@ -2754,7 +2608,6 @@ public: return i; } - //! Returns an iterator to the end of the string. //! \return An iterator to the end of the string. iterator end() @@ -2764,7 +2617,6 @@ public: return i; } - //! Returns an iterator to the end of the string. //! \return An iterator to the end of the string. const_iterator end() const @@ -2774,7 +2626,6 @@ public: return i; } - //! Returns an iterator to the end of the string. //! \return An iterator to the end of the string. const_iterator cend() const @@ -2784,10 +2635,9 @@ public: return i; } - //! Converts the string to a UTF-8 encoded string. - //! \param addBOM If true, the proper unicode byte-order mark will be prefixed to the string. - //! \return A string containing the UTF-8 encoded string. + //! \param addBOM If true, the proper unicode byte-order mark will be prefixed to + //! the string. \return A string containing the UTF-8 encoded string. core::string toUTF8_s(const bool addBOM = false) const { core::string ret; @@ -2795,18 +2645,15 @@ public: const_iterator iter(*this, 0); // Add the byte order mark if the user wants it. - if (addBOM) - { + if (addBOM) { ret.append(unicode::BOM_ENCODE_UTF8[0]); ret.append(unicode::BOM_ENCODE_UTF8[1]); ret.append(unicode::BOM_ENCODE_UTF8[2]); } - while (!iter.atEnd()) - { + while (!iter.atEnd()) { uchar32_t c = *iter; - if (c > 0xFFFF) - { // 4 bytes + if (c > 0xFFFF) { // 4 bytes uchar8_t b1 = (0x1E << 3) | ((c >> 18) & 0x7); uchar8_t b2 = (0x2 << 6) | ((c >> 12) & 0x3F); uchar8_t b3 = (0x2 << 6) | ((c >> 6) & 0x3F); @@ -2815,25 +2662,19 @@ public: ret.append(b2); ret.append(b3); ret.append(b4); - } - else if (c > 0x7FF) - { // 3 bytes + } else if (c > 0x7FF) { // 3 bytes uchar8_t b1 = (0xE << 4) | ((c >> 12) & 0xF); uchar8_t b2 = (0x2 << 6) | ((c >> 6) & 0x3F); uchar8_t b3 = (0x2 << 6) | (c & 0x3F); ret.append(b1); ret.append(b2); ret.append(b3); - } - else if (c > 0x7F) - { // 2 bytes + } else if (c > 0x7F) { // 2 bytes uchar8_t b1 = (0x6 << 5) | ((c >> 6) & 0x1F); uchar8_t b2 = (0x2 << 6) | (c & 0x3F); ret.append(b1); ret.append(b2); - } - else - { // 1 byte + } else { // 1 byte ret.append(static_cast(c)); } ++iter; @@ -2841,28 +2682,25 @@ public: return ret; } - //! Converts the string to a UTF-8 encoded string array. - //! \param addBOM If true, the proper unicode byte-order mark will be prefixed to the string. - //! \return An array containing the UTF-8 encoded string. + //! \param addBOM If true, the proper unicode byte-order mark will be prefixed to + //! the string. \return An array containing the UTF-8 encoded string. core::array toUTF8(const bool addBOM = false) const { - core::array ret(used * 4 + (addBOM ? unicode::BOM_UTF8_LEN : 0) + 1); + core::array ret( + used * 4 + (addBOM ? unicode::BOM_UTF8_LEN : 0) + 1); const_iterator iter(*this, 0); // Add the byte order mark if the user wants it. - if (addBOM) - { + if (addBOM) { ret.push_back(unicode::BOM_ENCODE_UTF8[0]); ret.push_back(unicode::BOM_ENCODE_UTF8[1]); ret.push_back(unicode::BOM_ENCODE_UTF8[2]); } - while (!iter.atEnd()) - { + while (!iter.atEnd()) { uchar32_t c = *iter; - if (c > 0xFFFF) - { // 4 bytes + if (c > 0xFFFF) { // 4 bytes uchar8_t b1 = (0x1E << 3) | ((c >> 18) & 0x7); uchar8_t b2 = (0x2 << 6) | ((c >> 12) & 0x3F); uchar8_t b3 = (0x2 << 6) | ((c >> 6) & 0x3F); @@ -2871,25 +2709,19 @@ public: ret.push_back(b2); ret.push_back(b3); ret.push_back(b4); - } - else if (c > 0x7FF) - { // 3 bytes + } else if (c > 0x7FF) { // 3 bytes uchar8_t b1 = (0xE << 4) | ((c >> 12) & 0xF); uchar8_t b2 = (0x2 << 6) | ((c >> 6) & 0x3F); uchar8_t b3 = (0x2 << 6) | (c & 0x3F); ret.push_back(b1); ret.push_back(b2); ret.push_back(b3); - } - else if (c > 0x7F) - { // 2 bytes + } else if (c > 0x7F) { // 2 bytes uchar8_t b1 = (0x6 << 5) | ((c >> 6) & 0x1F); uchar8_t b2 = (0x2 << 6) | (c & 0x3F); ret.push_back(b1); ret.push_back(b2); - } - else - { // 1 byte + } else { // 1 byte ret.push_back(static_cast(c)); } ++iter; @@ -2898,40 +2730,36 @@ public: return ret; } - -#ifdef USTRING_CPP0X_NEWLITERALS // C++0x +#ifdef USTRING_CPP0X_NEWLITERALS // C++0x //! Converts the string to a UTF-16 encoded string. //! \param endian The desired endianness of the string. - //! \param addBOM If true, the proper unicode byte-order mark will be prefixed to the string. - //! \return A string containing the UTF-16 encoded string. - core::string toUTF16_s(const unicode::EUTF_ENDIAN endian = unicode::EUTFEE_NATIVE, const bool addBOM = false) const + //! \param addBOM If true, the proper unicode byte-order mark will be prefixed to + //! the string. \return A string containing the UTF-16 encoded string. + core::string toUTF16_s( + const unicode::EUTF_ENDIAN endian = unicode::EUTFEE_NATIVE, + const bool addBOM = false) const { core::string ret; ret.reserve(used + (addBOM ? unicode::BOM_UTF16_LEN : 0) + 1); // Add the BOM if specified. - if (addBOM) - { + if (addBOM) { if (endian == unicode::EUTFEE_NATIVE) ret[0] = unicode::BOM; - else if (endian == unicode::EUTFEE_LITTLE) - { - uchar8_t* ptr8 = reinterpret_cast(&ret[0]); + else if (endian == unicode::EUTFEE_LITTLE) { + uchar8_t *ptr8 = reinterpret_cast(&ret[0]); *ptr8++ = unicode::BOM_ENCODE_UTF16_LE[0]; *ptr8 = unicode::BOM_ENCODE_UTF16_LE[1]; - } - else - { - uchar8_t* ptr8 = reinterpret_cast(&ret[0]); + } else { + uchar8_t *ptr8 = reinterpret_cast(&ret[0]); *ptr8++ = unicode::BOM_ENCODE_UTF16_BE[0]; *ptr8 = unicode::BOM_ENCODE_UTF16_BE[1]; } } ret.append(array); - if (endian != unicode::EUTFEE_NATIVE && getEndianness() != endian) - { - char16_t* ptr = ret.c_str(); + if (endian != unicode::EUTFEE_NATIVE && getEndianness() != endian) { + char16_t *ptr = ret.c_str(); for (u32 i = 0; i < ret.size(); ++i) *ptr++ = unicode::swapEndian16(*ptr); } @@ -2939,40 +2767,37 @@ public: } #endif - //! Converts the string to a UTF-16 encoded string array. - //! Unfortunately, no toUTF16_s() version exists due to limitations with Irrlicht's string class. - //! \param endian The desired endianness of the string. - //! \param addBOM If true, the proper unicode byte-order mark will be prefixed to the string. - //! \return An array containing the UTF-16 encoded string. - core::array toUTF16(const unicode::EUTF_ENDIAN endian = unicode::EUTFEE_NATIVE, const bool addBOM = false) const + //! Unfortunately, no toUTF16_s() version exists due to limitations with + //! Irrlicht's string class. \param endian The desired endianness of the string. + //! \param addBOM If true, the proper unicode byte-order mark will be prefixed to + //! the string. \return An array containing the UTF-16 encoded string. + core::array toUTF16( + const unicode::EUTF_ENDIAN endian = unicode::EUTFEE_NATIVE, + const bool addBOM = false) const { - core::array ret(used + (addBOM ? unicode::BOM_UTF16_LEN : 0) + 1); - uchar16_t* ptr = ret.pointer(); + core::array ret( + used + (addBOM ? unicode::BOM_UTF16_LEN : 0) + 1); + uchar16_t *ptr = ret.pointer(); // Add the BOM if specified. - if (addBOM) - { + if (addBOM) { if (endian == unicode::EUTFEE_NATIVE) *ptr = unicode::BOM; - else if (endian == unicode::EUTFEE_LITTLE) - { - uchar8_t* ptr8 = reinterpret_cast(ptr); + else if (endian == unicode::EUTFEE_LITTLE) { + uchar8_t *ptr8 = reinterpret_cast(ptr); *ptr8++ = unicode::BOM_ENCODE_UTF16_LE[0]; *ptr8 = unicode::BOM_ENCODE_UTF16_LE[1]; - } - else - { - uchar8_t* ptr8 = reinterpret_cast(ptr); + } else { + uchar8_t *ptr8 = reinterpret_cast(ptr); *ptr8++ = unicode::BOM_ENCODE_UTF16_BE[0]; *ptr8 = unicode::BOM_ENCODE_UTF16_BE[1]; } ++ptr; } - memcpy((void*)ptr, (void*)array, used * sizeof(uchar16_t)); - if (endian != unicode::EUTFEE_NATIVE && getEndianness() != endian) - { + memcpy((void *)ptr, (void *)array, used * sizeof(uchar16_t)); + if (endian != unicode::EUTFEE_NATIVE && getEndianness() != endian) { for (u32 i = 0; i <= used; ++i) ptr[i] = unicode::swapEndian16(ptr[i]); } @@ -2981,40 +2806,36 @@ public: return ret; } - -#ifdef USTRING_CPP0X_NEWLITERALS // C++0x +#ifdef USTRING_CPP0X_NEWLITERALS // C++0x //! Converts the string to a UTF-32 encoded string. //! \param endian The desired endianness of the string. - //! \param addBOM If true, the proper unicode byte-order mark will be prefixed to the string. - //! \return A string containing the UTF-32 encoded string. - core::string toUTF32_s(const unicode::EUTF_ENDIAN endian = unicode::EUTFEE_NATIVE, const bool addBOM = false) const + //! \param addBOM If true, the proper unicode byte-order mark will be prefixed to + //! the string. \return A string containing the UTF-32 encoded string. + core::string toUTF32_s( + const unicode::EUTF_ENDIAN endian = unicode::EUTFEE_NATIVE, + const bool addBOM = false) const { core::string ret; ret.reserve(size() + 1 + (addBOM ? unicode::BOM_UTF32_LEN : 0)); const_iterator iter(*this, 0); // Add the BOM if specified. - if (addBOM) - { + if (addBOM) { if (endian == unicode::EUTFEE_NATIVE) ret.append(unicode::BOM); - else - { + else { union { uchar32_t full; u8 chunk[4]; } t; - if (endian == unicode::EUTFEE_LITTLE) - { + if (endian == unicode::EUTFEE_LITTLE) { t.chunk[0] = unicode::BOM_ENCODE_UTF32_LE[0]; t.chunk[1] = unicode::BOM_ENCODE_UTF32_LE[1]; t.chunk[2] = unicode::BOM_ENCODE_UTF32_LE[2]; t.chunk[3] = unicode::BOM_ENCODE_UTF32_LE[3]; - } - else - { + } else { t.chunk[0] = unicode::BOM_ENCODE_UTF32_BE[0]; t.chunk[1] = unicode::BOM_ENCODE_UTF32_BE[1]; t.chunk[2] = unicode::BOM_ENCODE_UTF32_BE[2]; @@ -3024,8 +2845,7 @@ public: } } - while (!iter.atEnd()) - { + while (!iter.atEnd()) { uchar32_t c = *iter; if (endian != unicode::EUTFEE_NATIVE && getEndianness() != endian) c = unicode::swapEndian32(c); @@ -3036,39 +2856,36 @@ public: } #endif - //! Converts the string to a UTF-32 encoded string array. - //! Unfortunately, no toUTF32_s() version exists due to limitations with Irrlicht's string class. - //! \param endian The desired endianness of the string. - //! \param addBOM If true, the proper unicode byte-order mark will be prefixed to the string. - //! \return An array containing the UTF-32 encoded string. - core::array toUTF32(const unicode::EUTF_ENDIAN endian = unicode::EUTFEE_NATIVE, const bool addBOM = false) const + //! Unfortunately, no toUTF32_s() version exists due to limitations with + //! Irrlicht's string class. \param endian The desired endianness of the string. + //! \param addBOM If true, the proper unicode byte-order mark will be prefixed to + //! the string. \return An array containing the UTF-32 encoded string. + core::array toUTF32( + const unicode::EUTF_ENDIAN endian = unicode::EUTFEE_NATIVE, + const bool addBOM = false) const { - core::array ret(size() + (addBOM ? unicode::BOM_UTF32_LEN : 0) + 1); + core::array ret( + size() + (addBOM ? unicode::BOM_UTF32_LEN : 0) + 1); const_iterator iter(*this, 0); // Add the BOM if specified. - if (addBOM) - { + if (addBOM) { if (endian == unicode::EUTFEE_NATIVE) ret.push_back(unicode::BOM); - else - { + else { union { uchar32_t full; u8 chunk[4]; } t; - if (endian == unicode::EUTFEE_LITTLE) - { + if (endian == unicode::EUTFEE_LITTLE) { t.chunk[0] = unicode::BOM_ENCODE_UTF32_LE[0]; t.chunk[1] = unicode::BOM_ENCODE_UTF32_LE[1]; t.chunk[2] = unicode::BOM_ENCODE_UTF32_LE[2]; t.chunk[3] = unicode::BOM_ENCODE_UTF32_LE[3]; - } - else - { + } else { t.chunk[0] = unicode::BOM_ENCODE_UTF32_BE[0]; t.chunk[1] = unicode::BOM_ENCODE_UTF32_BE[1]; t.chunk[2] = unicode::BOM_ENCODE_UTF32_BE[2]; @@ -3079,8 +2896,7 @@ public: } ret.push_back(0); - while (!iter.atEnd()) - { + while (!iter.atEnd()) { uchar32_t c = *iter; if (endian != unicode::EUTFEE_NATIVE && getEndianness() != endian) c = unicode::swapEndian32(c); @@ -3090,37 +2906,31 @@ public: return ret; } - //! Converts the string to a wchar_t encoded string. - /** The size of a wchar_t changes depending on the platform. This function will store a - correct UTF-8, -16, or -32 encoded string depending on the size of a wchar_t. **/ + /** The size of a wchar_t changes depending on the platform. This function will + store a correct UTF-8, -16, or -32 encoded string depending on the size of a + wchar_t. **/ //! \param endian The desired endianness of the string. - //! \param addBOM If true, the proper unicode byte-order mark will be prefixed to the string. - //! \return A string containing the wchar_t encoded string. - core::string toWCHAR_s(const unicode::EUTF_ENDIAN endian = unicode::EUTFEE_NATIVE, const bool addBOM = false) const + //! \param addBOM If true, the proper unicode byte-order mark will be prefixed to + //! the string. \return A string containing the wchar_t encoded string. + core::string toWCHAR_s( + const unicode::EUTF_ENDIAN endian = unicode::EUTFEE_NATIVE, + const bool addBOM = false) const { - if (sizeof(wchar_t) == 4) - { + if (sizeof(wchar_t) == 4) { core::array a(toUTF32(endian, addBOM)); core::stringw ret(a.pointer()); return ret; - } - else if (sizeof(wchar_t) == 2) - { - if (endian == unicode::EUTFEE_NATIVE && addBOM == false) - { + } else if (sizeof(wchar_t) == 2) { + if (endian == unicode::EUTFEE_NATIVE && addBOM == false) { core::stringw ret(array); return ret; - } - else - { + } else { core::array a(toUTF16(endian, addBOM)); core::stringw ret(a.pointer()); return ret; } - } - else if (sizeof(wchar_t) == 1) - { + } else if (sizeof(wchar_t) == 1) { core::array a(toUTF8(addBOM)); core::stringw ret(a.pointer()); return ret; @@ -3130,47 +2940,47 @@ public: return core::stringw(); } - //! Converts the string to a wchar_t encoded string array. - /** The size of a wchar_t changes depending on the platform. This function will store a - correct UTF-8, -16, or -32 encoded string depending on the size of a wchar_t. **/ + /** The size of a wchar_t changes depending on the platform. This function will + store a correct UTF-8, -16, or -32 encoded string depending on the size of a + wchar_t. **/ //! \param endian The desired endianness of the string. - //! \param addBOM If true, the proper unicode byte-order mark will be prefixed to the string. - //! \return An array containing the wchar_t encoded string. - core::array toWCHAR(const unicode::EUTF_ENDIAN endian = unicode::EUTFEE_NATIVE, const bool addBOM = false) const + //! \param addBOM If true, the proper unicode byte-order mark will be prefixed to + //! the string. \return An array containing the wchar_t encoded string. + core::array toWCHAR( + const unicode::EUTF_ENDIAN endian = unicode::EUTFEE_NATIVE, + const bool addBOM = false) const { - if (sizeof(wchar_t) == 4) - { + if (sizeof(wchar_t) == 4) { core::array a(toUTF32(endian, addBOM)); core::array ret(a.size()); ret.set_used(a.size()); - memcpy((void*)ret.pointer(), (void*)a.pointer(), a.size() * sizeof(uchar32_t)); + memcpy((void *)ret.pointer(), (void *)a.pointer(), + a.size() * sizeof(uchar32_t)); return ret; } - if (sizeof(wchar_t) == 2) - { - if (endian == unicode::EUTFEE_NATIVE && addBOM == false) - { + if (sizeof(wchar_t) == 2) { + if (endian == unicode::EUTFEE_NATIVE && addBOM == false) { core::array ret(used); ret.set_used(used); - memcpy((void*)ret.pointer(), (void*)array, used * sizeof(uchar16_t)); + memcpy((void *)ret.pointer(), (void *)array, + used * sizeof(uchar16_t)); return ret; - } - else - { + } else { core::array a(toUTF16(endian, addBOM)); core::array ret(a.size()); ret.set_used(a.size()); - memcpy((void*)ret.pointer(), (void*)a.pointer(), a.size() * sizeof(uchar16_t)); + memcpy((void *)ret.pointer(), (void *)a.pointer(), + a.size() * sizeof(uchar16_t)); return ret; } } - if (sizeof(wchar_t) == 1) - { + if (sizeof(wchar_t) == 1) { core::array a(toUTF8(addBOM)); core::array ret(a.size()); ret.set_used(a.size()); - memcpy((void*)ret.pointer(), (void*)a.pointer(), a.size() * sizeof(uchar8_t)); + memcpy((void *)ret.pointer(), (void *)a.pointer(), + a.size() * sizeof(uchar8_t)); return ret; } @@ -3180,9 +2990,10 @@ public: //! Converts the string to a properly encoded io::path string. //! \param endian The desired endianness of the string. - //! \param addBOM If true, the proper unicode byte-order mark will be prefixed to the string. - //! \return An io::path string containing the properly encoded string. - io::path toPATH_s(const unicode::EUTF_ENDIAN endian = unicode::EUTFEE_NATIVE, const bool addBOM = false) const + //! \param addBOM If true, the proper unicode byte-order mark will be prefixed to + //! the string. \return An io::path string containing the properly encoded string. + io::path toPATH_s(const unicode::EUTF_ENDIAN endian = unicode::EUTFEE_NATIVE, + const bool addBOM = false) const { #if defined(_IRR_WCHAR_FILESYSTEM) return toWCHAR_s(endian, addBOM); @@ -3192,11 +3003,10 @@ public: } //! Loads an unknown stream of data. - //! Will attempt to determine if the stream is unicode data. Useful for loading from files. - //! \param data The data stream to load from. - //! \param data_size The length of the data string. - //! \return A reference to our current string. - ustring16& loadDataStream(const char* data, size_t data_size) + //! Will attempt to determine if the stream is unicode data. Useful for loading + //! from files. \param data The data stream to load from. \param data_size The + //! length of the data string. \return A reference to our current string. + ustring16 &loadDataStream(const char *data, size_t data_size) { // Clear our string. *this = ""; @@ -3204,24 +3014,23 @@ public: return *this; unicode::EUTF_ENCODE e = unicode::determineUnicodeBOM(data); - switch (e) - { - default: - case unicode::EUTFE_UTF8: - append((uchar8_t*)data, data_size); - break; + switch (e) { + default: + case unicode::EUTFE_UTF8: + append((uchar8_t *)data, data_size); + break; - case unicode::EUTFE_UTF16: - case unicode::EUTFE_UTF16_BE: - case unicode::EUTFE_UTF16_LE: - append((uchar16_t*)data, data_size / 2); - break; + case unicode::EUTFE_UTF16: + case unicode::EUTFE_UTF16_BE: + case unicode::EUTFE_UTF16_LE: + append((uchar16_t *)data, data_size / 2); + break; - case unicode::EUTFE_UTF32: - case unicode::EUTFE_UTF32_BE: - case unicode::EUTFE_UTF32_LE: - append((uchar32_t*)data, data_size / 4); - break; + case unicode::EUTFE_UTF32: + case unicode::EUTFE_UTF32_BE: + case unicode::EUTFE_UTF32_LE: + append((uchar32_t *)data, data_size / 4); + break; } return *this; @@ -3229,35 +3038,33 @@ public: //! Gets the encoding of the Unicode string this class contains. //! \return An enum describing the current encoding of this string. - const unicode::EUTF_ENCODE getEncoding() const - { - return encoding; - } + const unicode::EUTF_ENCODE getEncoding() const { return encoding; } //! Gets the endianness of the Unicode string this class contains. //! \return An enum describing the endianness of this string. const unicode::EUTF_ENDIAN getEndianness() const { if (encoding == unicode::EUTFE_UTF16_LE || - encoding == unicode::EUTFE_UTF32_LE) + encoding == unicode::EUTFE_UTF32_LE) return unicode::EUTFEE_LITTLE; - else return unicode::EUTFEE_BIG; + else + return unicode::EUTFEE_BIG; } private: - //! Reallocate the string, making it bigger or smaller. //! \param new_size The new size of the string. void reallocate(u32 new_size) { - uchar16_t* old_array = array; + uchar16_t *old_array = array; - array = allocator.allocate(new_size + 1); //new u16[new_size]; + array = allocator.allocate(new_size + 1); // new u16[new_size]; allocated = new_size + 1; - if (old_array == 0) return; + if (old_array == 0) + return; u32 amount = used < new_size ? used : new_size; - for (u32 i=0; i<=amount; ++i) + for (u32 i = 0; i <= amount; ++i) array[i] = old_array[i]; if (allocated <= used) @@ -3270,121 +3077,115 @@ private: //--- member variables - uchar16_t* array; + uchar16_t *array; unicode::EUTF_ENCODE encoding; u32 allocated; u32 used; TAlloc allocator; - //irrAllocator allocator; + // irrAllocator allocator; }; -typedef ustring16 > ustring; - +typedef ustring16> ustring; //! Appends two ustring16s. template -inline ustring16 operator+(const ustring16& left, const ustring16& right) +inline ustring16 operator+( + const ustring16 &left, const ustring16 &right) { ustring16 ret(left); ret += right; return ret; } - //! Appends a ustring16 and a null-terminated unicode string. template -inline ustring16 operator+(const ustring16& left, const B* const right) +inline ustring16 operator+(const ustring16 &left, const B *const right) { ustring16 ret(left); ret += right; return ret; } - //! Appends a ustring16 and a null-terminated unicode string. template -inline ustring16 operator+(const B* const left, const ustring16& right) +inline ustring16 operator+(const B *const left, const ustring16 &right) { ustring16 ret(left); ret += right; return ret; } - //! Appends a ustring16 and an Irrlicht string. template -inline ustring16 operator+(const ustring16& left, const string& right) +inline ustring16 operator+( + const ustring16 &left, const string &right) { ustring16 ret(left); ret += right; return ret; } - //! Appends a ustring16 and an Irrlicht string. template -inline ustring16 operator+(const string& left, const ustring16& right) +inline ustring16 operator+( + const string &left, const ustring16 &right) { ustring16 ret(left); ret += right; return ret; } - //! Appends a ustring16 and a std::basic_string. template -inline ustring16 operator+(const ustring16& left, const std::basic_string& right) +inline ustring16 operator+(const ustring16 &left, + const std::basic_string &right) { ustring16 ret(left); ret += right; return ret; } - //! Appends a ustring16 and a std::basic_string. template -inline ustring16 operator+(const std::basic_string& left, const ustring16& right) +inline ustring16 operator+(const std::basic_string &left, + const ustring16 &right) { ustring16 ret(left); ret += right; return ret; } - //! Appends a ustring16 and a char. template -inline ustring16 operator+(const ustring16& left, const char right) +inline ustring16 operator+(const ustring16 &left, const char right) { ustring16 ret(left); ret += right; return ret; } - //! Appends a ustring16 and a char. template -inline ustring16 operator+(const char left, const ustring16& right) +inline ustring16 operator+(const char left, const ustring16 &right) { ustring16 ret(left); ret += right; return ret; } - #ifdef USTRING_CPP0X_NEWLITERALS //! Appends a ustring16 and a uchar32_t. template -inline ustring16 operator+(const ustring16& left, const uchar32_t right) +inline ustring16 operator+(const ustring16 &left, const uchar32_t right) { ustring16 ret(left); ret += right; return ret; } - //! Appends a ustring16 and a uchar32_t. template -inline ustring16 operator+(const uchar32_t left, const ustring16& right) +inline ustring16 operator+(const uchar32_t left, const ustring16 &right) { ustring16 ret(left); ret += right; @@ -3392,454 +3193,417 @@ inline ustring16 operator+(const uchar32_t left, const ustring16 } #endif +//! Appends a ustring16 and a short. +template +inline ustring16 operator+(const ustring16 &left, const short right) +{ + ustring16 ret(left); + ret += core::stringc(right); + return ret; +} //! Appends a ustring16 and a short. template -inline ustring16 operator+(const ustring16& left, const short right) -{ - ustring16 ret(left); - ret += core::stringc(right); - return ret; -} - - -//! Appends a ustring16 and a short. -template -inline ustring16 operator+(const short left, const ustring16& right) +inline ustring16 operator+(const short left, const ustring16 &right) { ustring16 ret((core::stringc(left))); ret += right; return ret; } - //! Appends a ustring16 and an unsigned short. template -inline ustring16 operator+(const ustring16& left, const unsigned short right) +inline ustring16 operator+( + const ustring16 &left, const unsigned short right) { ustring16 ret(left); ret += core::stringc(right); return ret; } - //! Appends a ustring16 and an unsigned short. template -inline ustring16 operator+(const unsigned short left, const ustring16& right) +inline ustring16 operator+( + const unsigned short left, const ustring16 &right) { ustring16 ret((core::stringc(left))); ret += right; return ret; } - //! Appends a ustring16 and an int. template -inline ustring16 operator+(const ustring16& left, const int right) +inline ustring16 operator+(const ustring16 &left, const int right) { ustring16 ret(left); ret += core::stringc(right); return ret; } - //! Appends a ustring16 and an int. template -inline ustring16 operator+(const int left, const ustring16& right) +inline ustring16 operator+(const int left, const ustring16 &right) { ustring16 ret((core::stringc(left))); ret += right; return ret; } - //! Appends a ustring16 and an unsigned int. template -inline ustring16 operator+(const ustring16& left, const unsigned int right) +inline ustring16 operator+( + const ustring16 &left, const unsigned int right) { ustring16 ret(left); ret += core::stringc(right); return ret; } - //! Appends a ustring16 and an unsigned int. template -inline ustring16 operator+(const unsigned int left, const ustring16& right) +inline ustring16 operator+( + const unsigned int left, const ustring16 &right) { ustring16 ret((core::stringc(left))); ret += right; return ret; } - //! Appends a ustring16 and a long. template -inline ustring16 operator+(const ustring16& left, const long right) +inline ustring16 operator+(const ustring16 &left, const long right) { ustring16 ret(left); ret += core::stringc(right); return ret; } - //! Appends a ustring16 and a long. template -inline ustring16 operator+(const long left, const ustring16& right) +inline ustring16 operator+(const long left, const ustring16 &right) { ustring16 ret((core::stringc(left))); ret += right; return ret; } - //! Appends a ustring16 and an unsigned long. template -inline ustring16 operator+(const ustring16& left, const unsigned long right) +inline ustring16 operator+( + const ustring16 &left, const unsigned long right) { ustring16 ret(left); ret += core::stringc(right); return ret; } - //! Appends a ustring16 and an unsigned long. template -inline ustring16 operator+(const unsigned long left, const ustring16& right) +inline ustring16 operator+( + const unsigned long left, const ustring16 &right) { ustring16 ret((core::stringc(left))); ret += right; return ret; } - //! Appends a ustring16 and a float. template -inline ustring16 operator+(const ustring16& left, const float right) +inline ustring16 operator+(const ustring16 &left, const float right) { ustring16 ret(left); ret += core::stringc(right); return ret; } - //! Appends a ustring16 and a float. template -inline ustring16 operator+(const float left, const ustring16& right) +inline ustring16 operator+(const float left, const ustring16 &right) { ustring16 ret((core::stringc(left))); ret += right; return ret; } - //! Appends a ustring16 and a double. template -inline ustring16 operator+(const ustring16& left, const double right) +inline ustring16 operator+(const ustring16 &left, const double right) { ustring16 ret(left); ret += core::stringc(right); return ret; } - //! Appends a ustring16 and a double. template -inline ustring16 operator+(const double left, const ustring16& right) +inline ustring16 operator+(const double left, const ustring16 &right) { ustring16 ret((core::stringc(left))); ret += right; return ret; } - #ifdef USTRING_CPP0X //! Appends two ustring16s. template -inline ustring16&& operator+(const ustring16& left, ustring16&& right) +inline ustring16 &&operator+( + const ustring16 &left, ustring16 &&right) { - //std::cout << "MOVE operator+(&, &&)" << std::endl; + // std::cout << "MOVE operator+(&, &&)" << std::endl; right.insert(left, 0); return std::move(right); } - //! Appends two ustring16s. template -inline ustring16&& operator+(ustring16&& left, const ustring16& right) +inline ustring16 &&operator+( + ustring16 &&left, const ustring16 &right) { - //std::cout << "MOVE operator+(&&, &)" << std::endl; + // std::cout << "MOVE operator+(&&, &)" << std::endl; left.append(right); return std::move(left); } - //! Appends two ustring16s. template -inline ustring16&& operator+(ustring16&& left, ustring16&& right) +inline ustring16 &&operator+(ustring16 &&left, ustring16 &&right) { - //std::cout << "MOVE operator+(&&, &&)" << std::endl; + // std::cout << "MOVE operator+(&&, &&)" << std::endl; if ((right.size_raw() <= left.capacity() - left.size_raw()) || - (right.capacity() - right.size_raw() < left.size_raw())) - { + (right.capacity() - right.size_raw() < left.size_raw())) { left.append(right); return std::move(left); - } - else - { + } else { right.insert(left, 0); return std::move(right); } } - //! Appends a ustring16 and a null-terminated unicode string. template -inline ustring16&& operator+(ustring16&& left, const B* const right) +inline ustring16 &&operator+(ustring16 &&left, const B *const right) { - //std::cout << "MOVE operator+(&&, B*)" << std::endl; + // std::cout << "MOVE operator+(&&, B*)" << std::endl; left.append(right); return std::move(left); } - //! Appends a ustring16 and a null-terminated unicode string. template -inline ustring16&& operator+(const B* const left, ustring16&& right) +inline ustring16 &&operator+(const B *const left, ustring16 &&right) { - //std::cout << "MOVE operator+(B*, &&)" << std::endl; + // std::cout << "MOVE operator+(B*, &&)" << std::endl; right.insert(left, 0); return std::move(right); } - //! Appends a ustring16 and an Irrlicht string. template -inline ustring16&& operator+(const string& left, ustring16&& right) +inline ustring16 &&operator+( + const string &left, ustring16 &&right) { - //std::cout << "MOVE operator+(&, &&)" << std::endl; + // std::cout << "MOVE operator+(&, &&)" << std::endl; right.insert(left, 0); return std::move(right); } - //! Appends a ustring16 and an Irrlicht string. template -inline ustring16&& operator+(ustring16&& left, const string& right) +inline ustring16 &&operator+( + ustring16 &&left, const string &right) { - //std::cout << "MOVE operator+(&&, &)" << std::endl; + // std::cout << "MOVE operator+(&&, &)" << std::endl; left.append(right); return std::move(left); } - //! Appends a ustring16 and a std::basic_string. template -inline ustring16&& operator+(const std::basic_string& left, ustring16&& right) +inline ustring16 &&operator+( + const std::basic_string &left, ustring16 &&right) { - //std::cout << "MOVE operator+(&, &&)" << std::endl; + // std::cout << "MOVE operator+(&, &&)" << std::endl; right.insert(core::ustring16(left), 0); return std::move(right); } - //! Appends a ustring16 and a std::basic_string. template -inline ustring16&& operator+(ustring16&& left, const std::basic_string& right) +inline ustring16 &&operator+( + ustring16 &&left, const std::basic_string &right) { - //std::cout << "MOVE operator+(&&, &)" << std::endl; + // std::cout << "MOVE operator+(&&, &)" << std::endl; left.append(right); return std::move(left); } - //! Appends a ustring16 and a char. template -inline ustring16 operator+(ustring16&& left, const char right) +inline ustring16 operator+(ustring16 &&left, const char right) { left.append((uchar32_t)right); return std::move(left); } - //! Appends a ustring16 and a char. template -inline ustring16 operator+(const char left, ustring16&& right) +inline ustring16 operator+(const char left, ustring16 &&right) { right.insert((uchar32_t)left, 0); return std::move(right); } - #ifdef USTRING_CPP0X_NEWLITERALS //! Appends a ustring16 and a uchar32_t. template -inline ustring16 operator+(ustring16&& left, const uchar32_t right) +inline ustring16 operator+(ustring16 &&left, const uchar32_t right) { left.append(right); return std::move(left); } - //! Appends a ustring16 and a uchar32_t. template -inline ustring16 operator+(const uchar32_t left, ustring16&& right) +inline ustring16 operator+(const uchar32_t left, ustring16 &&right) { right.insert(left, 0); return std::move(right); } #endif +//! Appends a ustring16 and a short. +template +inline ustring16 operator+(ustring16 &&left, const short right) +{ + left.append(core::stringc(right)); + return std::move(left); +} //! Appends a ustring16 and a short. template -inline ustring16 operator+(ustring16&& left, const short right) -{ - left.append(core::stringc(right)); - return std::move(left); -} - - -//! Appends a ustring16 and a short. -template -inline ustring16 operator+(const short left, ustring16&& right) +inline ustring16 operator+(const short left, ustring16 &&right) { right.insert(core::stringc(left), 0); return std::move(right); } - //! Appends a ustring16 and an unsigned short. template -inline ustring16 operator+(ustring16&& left, const unsigned short right) +inline ustring16 operator+(ustring16 &&left, const unsigned short right) { left.append(core::stringc(right)); return std::move(left); } - //! Appends a ustring16 and an unsigned short. template -inline ustring16 operator+(const unsigned short left, ustring16&& right) +inline ustring16 operator+(const unsigned short left, ustring16 &&right) { right.insert(core::stringc(left), 0); return std::move(right); } - //! Appends a ustring16 and an int. template -inline ustring16 operator+(ustring16&& left, const int right) +inline ustring16 operator+(ustring16 &&left, const int right) { left.append(core::stringc(right)); return std::move(left); } - //! Appends a ustring16 and an int. template -inline ustring16 operator+(const int left, ustring16&& right) +inline ustring16 operator+(const int left, ustring16 &&right) { right.insert(core::stringc(left), 0); return std::move(right); } - //! Appends a ustring16 and an unsigned int. template -inline ustring16 operator+(ustring16&& left, const unsigned int right) +inline ustring16 operator+(ustring16 &&left, const unsigned int right) { left.append(core::stringc(right)); return std::move(left); } - //! Appends a ustring16 and an unsigned int. template -inline ustring16 operator+(const unsigned int left, ustring16&& right) +inline ustring16 operator+(const unsigned int left, ustring16 &&right) { right.insert(core::stringc(left), 0); return std::move(right); } - //! Appends a ustring16 and a long. template -inline ustring16 operator+(ustring16&& left, const long right) +inline ustring16 operator+(ustring16 &&left, const long right) { left.append(core::stringc(right)); return std::move(left); } - //! Appends a ustring16 and a long. template -inline ustring16 operator+(const long left, ustring16&& right) +inline ustring16 operator+(const long left, ustring16 &&right) { right.insert(core::stringc(left), 0); return std::move(right); } - //! Appends a ustring16 and an unsigned long. template -inline ustring16 operator+(ustring16&& left, const unsigned long right) +inline ustring16 operator+(ustring16 &&left, const unsigned long right) { left.append(core::stringc(right)); return std::move(left); } - //! Appends a ustring16 and an unsigned long. template -inline ustring16 operator+(const unsigned long left, ustring16&& right) +inline ustring16 operator+(const unsigned long left, ustring16 &&right) { right.insert(core::stringc(left), 0); return std::move(right); } - //! Appends a ustring16 and a float. template -inline ustring16 operator+(ustring16&& left, const float right) +inline ustring16 operator+(ustring16 &&left, const float right) { left.append(core::stringc(right)); return std::move(left); } - //! Appends a ustring16 and a float. template -inline ustring16 operator+(const float left, ustring16&& right) +inline ustring16 operator+(const float left, ustring16 &&right) { right.insert(core::stringc(left), 0); return std::move(right); } - //! Appends a ustring16 and a double. template -inline ustring16 operator+(ustring16&& left, const double right) +inline ustring16 operator+(ustring16 &&left, const double right) { left.append(core::stringc(right)); return std::move(left); } - //! Appends a ustring16 and a double. template -inline ustring16 operator+(const double left, ustring16&& right) +inline ustring16 operator+(const double left, ustring16 &&right) { right.insert(core::stringc(left), 0); return std::move(right); } #endif - #ifndef USTRING_NO_STL //! Writes a ustring16 to an ostream. template -inline std::ostream& operator<<(std::ostream& out, const ustring16& in) +inline std::ostream &operator<<(std::ostream &out, const ustring16 &in) { out << in.toUTF8_s().c_str(); return out; @@ -3847,14 +3611,13 @@ inline std::ostream& operator<<(std::ostream& out, const ustring16& in) //! Writes a ustring16 to a wostream. template -inline std::wostream& operator<<(std::wostream& out, const ustring16& in) +inline std::wostream &operator<<(std::wostream &out, const ustring16 &in) { out << in.toWCHAR_s().c_str(); return out; } #endif - #ifndef USTRING_NO_STL namespace unicode @@ -3864,23 +3627,22 @@ namespace unicode //! Algorithm taken from std::hash. class hash : public std::unary_function { - public: - size_t operator()(const core::ustring& s) const - { - size_t ret = 2166136261U; - size_t index = 0; - size_t stride = 1 + s.size_raw() / 10; +public: + size_t operator()(const core::ustring &s) const + { + size_t ret = 2166136261U; + size_t index = 0; + size_t stride = 1 + s.size_raw() / 10; - core::ustring::const_iterator i = s.begin(); - while (i != s.end()) - { - // TODO: Don't force u32 on an x64 OS. Make it agnostic. - ret = 16777619U * ret ^ (size_t)s[(u32)index]; - index += stride; - i += stride; - } - return (ret); + core::ustring::const_iterator i = s.begin(); + while (i != s.end()) { + // TODO: Don't force u32 on an x64 OS. Make it agnostic. + ret = 16777619U * ret ^ (size_t)s[(u32)index]; + index += stride; + i += stride; } + return (ret); + } }; } // end namespace unicode diff --git a/src/irrlicht_changes/static_text.cpp b/src/irrlicht_changes/static_text.cpp index bf61cd64e..eb1d338dc 100644 --- a/src/irrlicht_changes/static_text.cpp +++ b/src/irrlicht_changes/static_text.cpp @@ -13,7 +13,7 @@ #include #if USE_FREETYPE - #include "CGUITTFont.h" +#include "CGUITTFont.h" #endif #include "util/string.h" @@ -27,23 +27,20 @@ namespace gui { //! constructor StaticText::StaticText(const EnrichedString &text, bool border, - IGUIEnvironment* environment, IGUIElement* parent, - s32 id, const core::rect& rectangle, - bool background) -: IGUIStaticText(environment, parent, id, rectangle), - HAlign(EGUIA_UPPERLEFT), VAlign(EGUIA_UPPERLEFT), - Border(border), WordWrap(false), Background(background), - RestrainTextInside(true), RightToLeft(false), - OverrideFont(0), LastBreakFont(0) + IGUIEnvironment *environment, IGUIElement *parent, s32 id, + const core::rect &rectangle, bool background) : + IGUIStaticText(environment, parent, id, rectangle), + HAlign(EGUIA_UPPERLEFT), VAlign(EGUIA_UPPERLEFT), Border(border), + WordWrap(false), Background(background), RestrainTextInside(true), + RightToLeft(false), OverrideFont(0), LastBreakFont(0) { - #ifdef _DEBUG +#ifdef _DEBUG setDebugName("StaticText"); - #endif +#endif setText(text); } - //! destructor StaticText::~StaticText() { @@ -57,23 +54,24 @@ void StaticText::draw() if (!IsVisible) return; - IGUISkin* skin = Environment->getSkin(); + IGUISkin *skin = Environment->getSkin(); if (!skin) return; - video::IVideoDriver* driver = Environment->getVideoDriver(); + video::IVideoDriver *driver = Environment->getVideoDriver(); core::rect frameRect(AbsoluteRect); // draw background if (Background) - driver->draw2DRectangle(getBackgroundColor(), frameRect, &AbsoluteClippingRect); + driver->draw2DRectangle( + getBackgroundColor(), frameRect, &AbsoluteClippingRect); // draw the border - if (Border) - { - skin->draw3DSunkenPane(this, 0, true, false, frameRect, &AbsoluteClippingRect); + if (Border) { + skin->draw3DSunkenPane( + this, 0, true, false, frameRect, &AbsoluteClippingRect); frameRect.UpperLeftCorner.X += skin->getSize(EGDS_TEXT_DISTANCE_X); } @@ -84,52 +82,52 @@ void StaticText::draw() updateText(); core::rect r = frameRect; - s32 height_line = font->getDimension(L"A").Height + font->getKerningHeight(); + s32 height_line = font->getDimension(L"A").Height + + font->getKerningHeight(); s32 height_total = height_line * BrokenText.size(); - if (VAlign == EGUIA_CENTER && WordWrap) - { + if (VAlign == EGUIA_CENTER && WordWrap) { r.UpperLeftCorner.Y = r.getCenter().Y - (height_total / 2); - } - else if (VAlign == EGUIA_LOWERRIGHT) - { + } else if (VAlign == EGUIA_LOWERRIGHT) { r.UpperLeftCorner.Y = r.LowerRightCorner.Y - height_total; } - if (HAlign == EGUIA_LOWERRIGHT) - { - r.UpperLeftCorner.X = r.LowerRightCorner.X - - getTextWidth(); + if (HAlign == EGUIA_LOWERRIGHT) { + r.UpperLeftCorner.X = r.LowerRightCorner.X - getTextWidth(); } irr::video::SColor previous_color(255, 255, 255, 255); for (const EnrichedString &str : BrokenText) { - if (HAlign == EGUIA_LOWERRIGHT) - { - r.UpperLeftCorner.X = frameRect.LowerRightCorner.X - - font->getDimension(str.c_str()).Width; + if (HAlign == EGUIA_LOWERRIGHT) { + r.UpperLeftCorner.X = + frameRect.LowerRightCorner.X - + font->getDimension(str.c_str()).Width; } - //str = colorizeText(BrokenText[i].c_str(), colors, previous_color); - //if (!colors.empty()) - // previous_color = colors[colors.size() - 1]; + // str = colorizeText(BrokenText[i].c_str(), colors, + // previous_color); if (!colors.empty()) previous_color = + //colors[colors.size() - 1]; #if USE_FREETYPE if (font->getType() == irr::gui::EGFT_CUSTOM) { - irr::gui::CGUITTFont *tmp = static_cast(font); - tmp->draw(str, - r, previous_color, // FIXME - HAlign == EGUIA_CENTER, VAlign == EGUIA_CENTER, - (RestrainTextInside ? &AbsoluteClippingRect : NULL)); + irr::gui::CGUITTFont *tmp = + static_cast(font); + tmp->draw(str, r, previous_color, // FIXME + HAlign == EGUIA_CENTER, + VAlign == EGUIA_CENTER, + (RestrainTextInside ? &AbsoluteClippingRect + : NULL)); } else #endif { // Draw non-colored text - font->draw(str.c_str(), - r, str.getDefaultColor(), // TODO: Implement colorization - HAlign == EGUIA_CENTER, VAlign == EGUIA_CENTER, - (RestrainTextInside ? &AbsoluteClippingRect : NULL)); + font->draw(str.c_str(), r, + str.getDefaultColor(), // TODO: Implement + // colorization + HAlign == EGUIA_CENTER, + VAlign == EGUIA_CENTER, + (RestrainTextInside ? &AbsoluteClippingRect + : NULL)); } - r.LowerRightCorner.Y += height_line; r.UpperLeftCorner.Y += height_line; } @@ -138,9 +136,8 @@ void StaticText::draw() IGUIElement::draw(); } - //! Sets another skin independent font. -void StaticText::setOverrideFont(IGUIFont* font) +void StaticText::setOverrideFont(IGUIFont *font) { if (OverrideFont == font) return; @@ -157,17 +154,17 @@ void StaticText::setOverrideFont(IGUIFont* font) } //! Gets the override font (if any) -IGUIFont * StaticText::getOverrideFont() const +IGUIFont *StaticText::getOverrideFont() const { return OverrideFont; } //! Get the font which is used right now for drawing -IGUIFont* StaticText::getActiveFont() const +IGUIFont *StaticText::getActiveFont() const { - if ( OverrideFont ) + if (OverrideFont) return OverrideFont; - IGUISkin* skin = Environment->getSkin(); + IGUISkin *skin = Environment->getSkin(); if (skin) return skin->getFont(); return 0; @@ -180,7 +177,6 @@ void StaticText::setOverrideColor(video::SColor color) updateText(); } - //! Sets another color for the text. void StaticText::setBackgroundColor(video::SColor color) { @@ -188,66 +184,57 @@ void StaticText::setBackgroundColor(video::SColor color) Background = true; } - //! Sets whether to draw the background void StaticText::setDrawBackground(bool draw) { Background = draw; } - //! Gets the background color video::SColor StaticText::getBackgroundColor() const { IGUISkin *skin = Environment->getSkin(); - return (ColoredText.hasBackground() || !skin) ? - ColoredText.getBackground() : skin->getColor(gui::EGDC_3D_FACE); + return (ColoredText.hasBackground() || !skin) ? ColoredText.getBackground() + : skin->getColor(gui::EGDC_3D_FACE); } - //! Checks if background drawing is enabled bool StaticText::isDrawBackgroundEnabled() const { return Background; } - //! Sets whether to draw the border void StaticText::setDrawBorder(bool draw) { Border = draw; } - //! Checks if border drawing is enabled bool StaticText::isDrawBorderEnabled() const { return Border; } - void StaticText::setTextRestrainedInside(bool restrainTextInside) { RestrainTextInside = restrainTextInside; } - bool StaticText::isTextRestrainedInside() const { return RestrainTextInside; } - void StaticText::setTextAlignment(EGUI_ALIGNMENT horizontal, EGUI_ALIGNMENT vertical) { HAlign = horizontal; VAlign = vertical; } - #if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR <= 7 -const video::SColor& StaticText::getOverrideColor() const +const video::SColor &StaticText::getOverrideColor() const #else video::SColor StaticText::getOverrideColor() const #endif @@ -255,7 +242,6 @@ video::SColor StaticText::getOverrideColor() const return ColoredText.getDefaultColor(); } - //! Sets if the static text should use the overide color or the //! color in the gui skin. void StaticText::enableOverrideColor(bool enable) @@ -263,13 +249,11 @@ void StaticText::enableOverrideColor(bool enable) // TODO } - bool StaticText::isOverrideColorEnabled() const { return true; } - //! Enables or disables word wrap for using the static text as //! multiline text control. void StaticText::setWordWrap(bool enable) @@ -278,29 +262,24 @@ void StaticText::setWordWrap(bool enable) updateText(); } - bool StaticText::isWordWrapEnabled() const { return WordWrap; } - void StaticText::setRightToLeft(bool rtl) { - if (RightToLeft != rtl) - { + if (RightToLeft != rtl) { RightToLeft = rtl; updateText(); } } - bool StaticText::isRightToLeft() const { return RightToLeft; } - //! Breaks the single text line. // Updates the font colors void StaticText::updateText() @@ -320,8 +299,8 @@ void StaticText::updateText() // Update word wrap - IGUISkin* skin = Environment->getSkin(); - IGUIFont* font = getActiveFont(); + IGUISkin *skin = Environment->getSkin(); + IGUIFont *font = getActiveFont(); if (!font) return; @@ -334,92 +313,96 @@ void StaticText::updateText() s32 length = 0; s32 elWidth = RelativeRect.getWidth(); if (Border) - elWidth -= 2*skin->getSize(EGDS_TEXT_DISTANCE_X); + elWidth -= 2 * skin->getSize(EGDS_TEXT_DISTANCE_X); wchar_t c; - //std::vector colors; + // std::vector colors; // We have to deal with right-to-left and left-to-right differently // However, most parts of the following code is the same, it's just // some order and boundaries which change. - if (!RightToLeft) - { + if (!RightToLeft) { // regular (left-to-right) - for (s32 i=0; igetDimension(whitespace.c_str()).Width; - //const std::wstring sanitized = removeEscapes(word.c_str()); - const s32 wordlgth = font->getDimension(word.c_str()).Width; + const s32 whitelgth = + font->getDimension(whitespace.c_str()) + .Width; + // const std::wstring sanitized = + // removeEscapes(word.c_str()); + const s32 wordlgth = + font->getDimension(word.c_str()) + .Width; - if (wordlgth > elWidth) - { - // This word is too long to fit in the available space, look for - // the Unicode Soft HYphen (SHY / 00AD) character for a place to - // break the word at - int where = core::stringw(word.c_str()).findFirst( wchar_t(0x00AD) ); - if (where != -1) - { - EnrichedString first = word.substr(0, where); - EnrichedString second = word.substr(where, word.size() - where); + if (wordlgth > elWidth) { + // This word is too long to fit in the + // available space, look for the Unicode + // Soft HYphen (SHY / 00AD) character for + // a place to break the word at + int where = core::stringw(word.c_str()) + .findFirst(wchar_t( + 0x00AD)); + if (where != -1) { + EnrichedString first = word.substr( + 0, where); + EnrichedString second = word.substr( + where, + word.size() - where); first.addCharNoColor(L'-'); - BrokenText.push_back(line + first); - const s32 secondLength = font->getDimension(second.c_str()).Width; + BrokenText.push_back( + line + first); + const s32 secondLength = + font->getDimension(second.c_str()) + .Width; length = secondLength; line = second; - } - else - { - // No soft hyphen found, so there's nothing more we can do + } else { + // No soft hyphen found, so + // there's nothing more we can do // break to next line if (length) - BrokenText.push_back(line); + BrokenText.push_back( + line); length = wordlgth; line = word; } - } - else if (length && (length + wordlgth + whitelgth > elWidth)) - { + } else if (length && + (length + wordlgth + whitelgth > + elWidth)) { // break to next line BrokenText.push_back(line); length = wordlgth; line = word; - } - else - { + } else { // add word to line line += whitespace; line += word; @@ -430,14 +413,12 @@ void StaticText::updateText() whitespace.clear(); } - if ( isWhitespace && c != 0) - { + if (isWhitespace && c != 0) { whitespace.addChar(cText, i); } // compute line break - if (lineBreak) - { + if (lineBreak) { line += whitespace; line += word; BrokenText.push_back(line); @@ -452,49 +433,45 @@ void StaticText::updateText() line += whitespace; line += word; BrokenText.push_back(line); - } - else - { + } else { // right-to-left - for (s32 i=size; i>=0; --i) - { + for (s32 i = size; i >= 0; --i) { c = cText.getString()[i]; bool lineBreak = false; if (c == L'\r') // Mac or Windows breaks { lineBreak = true; - //if ((i>0) && Text[i-1] == L'\n') // Windows breaks + // if ((i>0) && Text[i-1] == L'\n') // Windows breaks //{ // Text.erase(i-1); // --size; //} c = '\0'; - } - else if (c == L'\n') // Unix breaks + } else if (c == L'\n') // Unix breaks { lineBreak = true; c = '\0'; } - if (c==L' ' || c==0 || i==0) - { - if (word.size()) - { + if (c == L' ' || c == 0 || i == 0) { + if (word.size()) { // here comes the next whitespace, look if // we must break the last word to the next line. - const s32 whitelgth = font->getDimension(whitespace.c_str()).Width; - const s32 wordlgth = font->getDimension(word.c_str()).Width; + const s32 whitelgth = + font->getDimension(whitespace.c_str()) + .Width; + const s32 wordlgth = + font->getDimension(word.c_str()) + .Width; - if (length && (length + wordlgth + whitelgth > elWidth)) - { + if (length && (length + wordlgth + whitelgth > + elWidth)) { // break to next line BrokenText.push_back(line); length = wordlgth; line = word; - } - else - { + } else { // add word to line line = whitespace + line; line = word + line; @@ -506,12 +483,12 @@ void StaticText::updateText() } if (c != 0) - // whitespace = core::stringw(&c, 1) + whitespace; - whitespace = cText.substr(i, 1) + whitespace; + // whitespace = core::stringw(&c, 1) + + //whitespace; + whitespace = cText.substr(i, 1) + whitespace; // compute line break - if (lineBreak) - { + if (lineBreak) { line = whitespace + line; line = word + line; BrokenText.push_back(line); @@ -520,11 +497,9 @@ void StaticText::updateText() whitespace.clear(); length = 0; } - } - else - { + } else { // yippee this is a word.. - //word = core::stringw(&c, 1) + word; + // word = core::stringw(&c, 1) + word; word = cText.substr(i, 1) + word; } } @@ -535,9 +510,8 @@ void StaticText::updateText() } } - //! Sets the new caption of this element. -void StaticText::setText(const wchar_t* text) +void StaticText::setText(const wchar_t *text) { setText(EnrichedString(text, getOverrideColor())); } @@ -555,11 +529,10 @@ void StaticText::updateAbsolutePosition() updateText(); } - //! Returns the height of the text in pixels when it is drawn. s32 StaticText::getTextHeight() const { - IGUIFont* font = getActiveFont(); + IGUIFont *font = getActiveFont(); if (!font) return 0; @@ -571,7 +544,6 @@ s32 StaticText::getTextHeight() const return font->getDimension(BrokenText[0].c_str()).Height; } - s32 StaticText::getTextWidth() const { IGUIFont *font = getActiveFont(); @@ -590,34 +562,34 @@ s32 StaticText::getTextWidth() const return widest; } - //! Writes attributes of the element. //! Implement this to expose the attributes of your element for //! scripting languages, editors, debuggers or xml serialization purposes. -void StaticText::serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options=0) const +void StaticText::serializeAttributes( + io::IAttributes *out, io::SAttributeReadWriteOptions *options = 0) const { - IGUIStaticText::serializeAttributes(out,options); + IGUIStaticText::serializeAttributes(out, options); - out->addBool ("Border", Border); - out->addBool ("OverrideColorEnabled",true); - out->addBool ("OverrideBGColorEnabled",ColoredText.hasBackground()); - out->addBool ("WordWrap", WordWrap); - out->addBool ("Background", Background); - out->addBool ("RightToLeft", RightToLeft); - out->addBool ("RestrainTextInside", RestrainTextInside); - out->addColor ("OverrideColor", ColoredText.getDefaultColor()); - out->addColor ("BGColor", ColoredText.getBackground()); - out->addEnum ("HTextAlign", HAlign, GUIAlignmentNames); - out->addEnum ("VTextAlign", VAlign, GUIAlignmentNames); + out->addBool("Border", Border); + out->addBool("OverrideColorEnabled", true); + out->addBool("OverrideBGColorEnabled", ColoredText.hasBackground()); + out->addBool("WordWrap", WordWrap); + out->addBool("Background", Background); + out->addBool("RightToLeft", RightToLeft); + out->addBool("RestrainTextInside", RestrainTextInside); + out->addColor("OverrideColor", ColoredText.getDefaultColor()); + out->addColor("BGColor", ColoredText.getBackground()); + out->addEnum("HTextAlign", HAlign, GUIAlignmentNames); + out->addEnum("VTextAlign", VAlign, GUIAlignmentNames); // out->addFont ("OverrideFont", OverrideFont); } - //! Reads attributes of the element -void StaticText::deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options=0) +void StaticText::deserializeAttributes( + io::IAttributes *in, io::SAttributeReadWriteOptions *options = 0) { - IGUIStaticText::deserializeAttributes(in,options); + IGUIStaticText::deserializeAttributes(in, options); Border = in->getAttributeAsBool("Border"); setWordWrap(in->getAttributeAsBool("WordWrap")); @@ -629,8 +601,10 @@ void StaticText::deserializeAttributes(io::IAttributes* in, io::SAttributeReadWr if (in->getAttributeAsBool("OverrideBGColorEnabled")) ColoredText.setBackground(in->getAttributeAsColor("BGColor")); - setTextAlignment( (EGUI_ALIGNMENT) in->getAttributeAsEnumeration("HTextAlign", GUIAlignmentNames), - (EGUI_ALIGNMENT) in->getAttributeAsEnumeration("VTextAlign", GUIAlignmentNames)); + setTextAlignment((EGUI_ALIGNMENT)in->getAttributeAsEnumeration( + "HTextAlign", GUIAlignmentNames), + (EGUI_ALIGNMENT)in->getAttributeAsEnumeration( + "VTextAlign", GUIAlignmentNames)); // OverrideFont = in->getAttributeAsFont("OverrideFont"); } @@ -641,5 +615,4 @@ void StaticText::deserializeAttributes(io::IAttributes* in, io::SAttributeReadWr } // end namespace irr - #endif // _IRR_COMPILE_WITH_GUI_ diff --git a/src/irrlicht_changes/static_text.h b/src/irrlicht_changes/static_text.h index 1f111ea56..5c4db3e95 100644 --- a/src/irrlicht_changes/static_text.h +++ b/src/irrlicht_changes/static_text.h @@ -28,207 +28,202 @@ namespace irr namespace gui { - const EGUI_ELEMENT_TYPE EGUIET_ENRICHED_STATIC_TEXT = (EGUI_ELEMENT_TYPE)(0x1000); +const EGUI_ELEMENT_TYPE EGUIET_ENRICHED_STATIC_TEXT = (EGUI_ELEMENT_TYPE)(0x1000); - class StaticText : public IGUIStaticText - { - public: - - // StaticText is translated by EnrichedString. - // No need to use translate_string() - StaticText(const EnrichedString &text, bool border, IGUIEnvironment* environment, - IGUIElement* parent, s32 id, const core::rect& rectangle, +class StaticText : public IGUIStaticText +{ +public: + // StaticText is translated by EnrichedString. + // No need to use translate_string() + StaticText(const EnrichedString &text, bool border, IGUIEnvironment *environment, + IGUIElement *parent, s32 id, const core::rect &rectangle, bool background = false); - //! destructor - virtual ~StaticText(); + //! destructor + virtual ~StaticText(); - static irr::gui::IGUIStaticText *add( - irr::gui::IGUIEnvironment *guienv, - const EnrichedString &text, - const core::rect< s32 > &rectangle, - bool border = false, - bool wordWrap = true, - irr::gui::IGUIElement *parent = NULL, - s32 id = -1, + static irr::gui::IGUIStaticText *add(irr::gui::IGUIEnvironment *guienv, + const EnrichedString &text, const core::rect &rectangle, + bool border = false, bool wordWrap = true, + irr::gui::IGUIElement *parent = NULL, s32 id = -1, bool fillBackground = false) - { - if (parent == NULL) { - // parent is NULL, so we must find one, or we need not to drop - // result, but then there will be a memory leak. - // - // What Irrlicht does is to use guienv as a parent, but the problem - // is that guienv is here only an IGUIEnvironment, while it is a - // CGUIEnvironment in Irrlicht, which inherits from both IGUIElement - // and IGUIEnvironment. - // - // A solution would be to dynamic_cast guienv to a - // IGUIElement*, but Irrlicht is shipped without rtti support - // in some distributions, causing the dymanic_cast to segfault. - // - // Thus, to find the parent, we create a dummy StaticText and ask - // for its parent, and then remove it. - irr::gui::IGUIStaticText *dummy_text = - guienv->addStaticText(L"", rectangle, border, wordWrap, - parent, id, fillBackground); - parent = dummy_text->getParent(); - dummy_text->remove(); - } - irr::gui::IGUIStaticText *result = new irr::gui::StaticText( - text, border, guienv, parent, - id, rectangle, fillBackground); - - result->setWordWrap(wordWrap); - result->drop(); - return result; + { + if (parent == NULL) { + // parent is NULL, so we must find one, or we need not to drop + // result, but then there will be a memory leak. + // + // What Irrlicht does is to use guienv as a parent, but the + // problem is that guienv is here only an IGUIEnvironment, while + // it is a CGUIEnvironment in Irrlicht, which inherits from both + // IGUIElement and IGUIEnvironment. + // + // A solution would be to dynamic_cast guienv to a + // IGUIElement*, but Irrlicht is shipped without rtti support + // in some distributions, causing the dymanic_cast to segfault. + // + // Thus, to find the parent, we create a dummy StaticText and ask + // for its parent, and then remove it. + irr::gui::IGUIStaticText *dummy_text = guienv->addStaticText(L"", + rectangle, border, wordWrap, parent, id, + fillBackground); + parent = dummy_text->getParent(); + dummy_text->remove(); } + irr::gui::IGUIStaticText *result = new irr::gui::StaticText(text, border, + guienv, parent, id, rectangle, fillBackground); - static irr::gui::IGUIStaticText *add( - irr::gui::IGUIEnvironment *guienv, - const wchar_t *text, - const core::rect< s32 > &rectangle, - bool border = false, - bool wordWrap = true, - irr::gui::IGUIElement *parent = NULL, - s32 id = -1, + result->setWordWrap(wordWrap); + result->drop(); + return result; + } + + static irr::gui::IGUIStaticText *add(irr::gui::IGUIEnvironment *guienv, + const wchar_t *text, const core::rect &rectangle, + bool border = false, bool wordWrap = true, + irr::gui::IGUIElement *parent = NULL, s32 id = -1, bool fillBackground = false) - { - return add(guienv, EnrichedString(text), rectangle, border, wordWrap, parent, - id, fillBackground); - } + { + return add(guienv, EnrichedString(text), rectangle, border, wordWrap, + parent, id, fillBackground); + } - //! draws the element and its children - virtual void draw(); + //! draws the element and its children + virtual void draw(); - //! Sets another skin independent font. - virtual void setOverrideFont(IGUIFont* font=0); + //! Sets another skin independent font. + virtual void setOverrideFont(IGUIFont *font = 0); - //! Gets the override font (if any) - virtual IGUIFont* getOverrideFont() const; + //! Gets the override font (if any) + virtual IGUIFont *getOverrideFont() const; - //! Get the font which is used right now for drawing - virtual IGUIFont* getActiveFont() const; + //! Get the font which is used right now for drawing + virtual IGUIFont *getActiveFont() const; - //! Sets another color for the text. - virtual void setOverrideColor(video::SColor color); + //! Sets another color for the text. + virtual void setOverrideColor(video::SColor color); - //! Sets another color for the background. - virtual void setBackgroundColor(video::SColor color); + //! Sets another color for the background. + virtual void setBackgroundColor(video::SColor color); - //! Sets whether to draw the background - virtual void setDrawBackground(bool draw); + //! Sets whether to draw the background + virtual void setDrawBackground(bool draw); - //! Gets the background color - virtual video::SColor getBackgroundColor() const; + //! Gets the background color + virtual video::SColor getBackgroundColor() const; - //! Checks if background drawing is enabled - virtual bool isDrawBackgroundEnabled() const; + //! Checks if background drawing is enabled + virtual bool isDrawBackgroundEnabled() const; - //! Sets whether to draw the border - virtual void setDrawBorder(bool draw); + //! Sets whether to draw the border + virtual void setDrawBorder(bool draw); - //! Checks if border drawing is enabled - virtual bool isDrawBorderEnabled() const; + //! Checks if border drawing is enabled + virtual bool isDrawBorderEnabled() const; - //! Sets alignment mode for text - virtual void setTextAlignment(EGUI_ALIGNMENT horizontal, EGUI_ALIGNMENT vertical); + //! Sets alignment mode for text + virtual void setTextAlignment(EGUI_ALIGNMENT horizontal, EGUI_ALIGNMENT vertical); - //! Gets the override color - #if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR <= 7 - virtual const video::SColor& getOverrideColor() const; - #else - virtual video::SColor getOverrideColor() const; - #endif +//! Gets the override color +#if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR <= 7 + virtual const video::SColor &getOverrideColor() const; +#else + virtual video::SColor getOverrideColor() const; +#endif - //! Sets if the static text should use the overide color or the - //! color in the gui skin. - virtual void enableOverrideColor(bool enable); + //! Sets if the static text should use the overide color or the + //! color in the gui skin. + virtual void enableOverrideColor(bool enable); - //! Checks if an override color is enabled - virtual bool isOverrideColorEnabled() const; + //! Checks if an override color is enabled + virtual bool isOverrideColorEnabled() const; - //! Set whether the text in this label should be clipped if it goes outside bounds - virtual void setTextRestrainedInside(bool restrainedInside); + //! Set whether the text in this label should be clipped if it goes outside bounds + virtual void setTextRestrainedInside(bool restrainedInside); - //! Checks if the text in this label should be clipped if it goes outside bounds - virtual bool isTextRestrainedInside() const; + //! Checks if the text in this label should be clipped if it goes outside bounds + virtual bool isTextRestrainedInside() const; - //! Enables or disables word wrap for using the static text as - //! multiline text control. - virtual void setWordWrap(bool enable); + //! Enables or disables word wrap for using the static text as + //! multiline text control. + virtual void setWordWrap(bool enable); - //! Checks if word wrap is enabled - virtual bool isWordWrapEnabled() const; + //! Checks if word wrap is enabled + virtual bool isWordWrapEnabled() const; - //! Sets the new caption of this element. - virtual void setText(const wchar_t* text); + //! Sets the new caption of this element. + virtual void setText(const wchar_t *text); - //! Returns the height of the text in pixels when it is drawn. - virtual s32 getTextHeight() const; + //! Returns the height of the text in pixels when it is drawn. + virtual s32 getTextHeight() const; - //! Returns the width of the current text, in the current font - virtual s32 getTextWidth() const; + //! Returns the width of the current text, in the current font + virtual s32 getTextWidth() const; - //! Updates the absolute position, splits text if word wrap is enabled - virtual void updateAbsolutePosition(); + //! Updates the absolute position, splits text if word wrap is enabled + virtual void updateAbsolutePosition(); - //! Set whether the string should be interpreted as right-to-left (RTL) text - /** \note This component does not implement the Unicode bidi standard, the - text of the component should be already RTL if you call this. The - main difference when RTL is enabled is that the linebreaks for multiline - elements are performed starting from the end. - */ - virtual void setRightToLeft(bool rtl); + //! Set whether the string should be interpreted as right-to-left (RTL) text + /** \note This component does not implement the Unicode bidi standard, the + text of the component should be already RTL if you call this. The + main difference when RTL is enabled is that the linebreaks for multiline + elements are performed starting from the end. + */ + virtual void setRightToLeft(bool rtl); - //! Checks if the text should be interpreted as right-to-left text - virtual bool isRightToLeft() const; + //! Checks if the text should be interpreted as right-to-left text + virtual bool isRightToLeft() const; - //! Writes attributes of the element. - virtual void serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options) const; + //! Writes attributes of the element. + virtual void serializeAttributes(io::IAttributes *out, + io::SAttributeReadWriteOptions *options) const; - //! Reads attributes of the element - virtual void deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options); + //! Reads attributes of the element + virtual void deserializeAttributes( + io::IAttributes *in, io::SAttributeReadWriteOptions *options); - virtual bool hasType(EGUI_ELEMENT_TYPE t) const { - return (t == EGUIET_ENRICHED_STATIC_TEXT) || (t == EGUIET_STATIC_TEXT); - }; - - virtual bool hasType(EGUI_ELEMENT_TYPE t) { - return (t == EGUIET_ENRICHED_STATIC_TEXT) || (t == EGUIET_STATIC_TEXT); - }; - - void setText(const EnrichedString &text); - - private: - - //! Breaks the single text line. - void updateText(); - - EGUI_ALIGNMENT HAlign, VAlign; - bool Border; - bool WordWrap; - bool Background; - bool RestrainTextInside; - bool RightToLeft; - - gui::IGUIFont* OverrideFont; - gui::IGUIFont* LastBreakFont; // stored because: if skin changes, line break must be recalculated. - - EnrichedString ColoredText; - std::vector BrokenText; + virtual bool hasType(EGUI_ELEMENT_TYPE t) const + { + return (t == EGUIET_ENRICHED_STATIC_TEXT) || (t == EGUIET_STATIC_TEXT); }; + virtual bool hasType(EGUI_ELEMENT_TYPE t) + { + return (t == EGUIET_ENRICHED_STATIC_TEXT) || (t == EGUIET_STATIC_TEXT); + }; + + void setText(const EnrichedString &text); + +private: + //! Breaks the single text line. + void updateText(); + + EGUI_ALIGNMENT HAlign, VAlign; + bool Border; + bool WordWrap; + bool Background; + bool RestrainTextInside; + bool RightToLeft; + + gui::IGUIFont *OverrideFont; + gui::IGUIFont *LastBreakFont; // stored because: if skin changes, line break must + // be recalculated. + + EnrichedString ColoredText; + std::vector BrokenText; +}; } // end namespace gui } // end namespace irr -inline void setStaticText(irr::gui::IGUIStaticText *static_text, const EnrichedString &text) +inline void setStaticText( + irr::gui::IGUIStaticText *static_text, const EnrichedString &text) { // dynamic_cast not possible due to some distributions shipped // without rtti support in irrlicht if (static_text->hasType(irr::gui::EGUIET_ENRICHED_STATIC_TEXT)) { - irr::gui::StaticText* stext = static_cast(static_text); + irr::gui::StaticText *stext = + static_cast(static_text); stext->setText(text); } else { static_text->setText(text.c_str()); @@ -245,17 +240,14 @@ namespace gui class StaticText { public: - static irr::gui::IGUIStaticText *add( - irr::gui::IGUIEnvironment *guienv, - const EnrichedString &text, - const core::rect< s32 > &rectangle, - bool border = false, - bool wordWrap = true, - irr::gui::IGUIElement *parent = NULL, - s32 id = -1, - bool fillBackground = false) + static irr::gui::IGUIStaticText *add(irr::gui::IGUIEnvironment *guienv, + const EnrichedString &text, const core::rect &rectangle, + bool border = false, bool wordWrap = true, + irr::gui::IGUIElement *parent = NULL, s32 id = -1, + bool fillBackground = false) { - return guienv->addStaticText(text.c_str(), rectangle, border, wordWrap, parent, id, fillBackground); + return guienv->addStaticText(text.c_str(), rectangle, border, wordWrap, + parent, id, fillBackground); } }; @@ -263,7 +255,8 @@ public: } // end namespace irr -inline void setStaticText(irr::gui::IGUIStaticText *static_text, const EnrichedString &text) +inline void setStaticText( + irr::gui::IGUIStaticText *static_text, const EnrichedString &text) { static_text->setText(text.c_str()); } diff --git a/src/irrlichttypes.h b/src/irrlichttypes.h index 794776b26..a00b75adf 100644 --- a/src/irrlichttypes.h +++ b/src/irrlichttypes.h @@ -27,54 +27,54 @@ with this program; if not, write to the Free Software Foundation, Inc., * regardless of the compiler. */ #ifndef _MSC_VER -# include +#include #endif #include using namespace irr; -namespace irr { +namespace irr +{ // Irrlicht 1.8+ defines 64bit unsigned symbol in irrTypes.h #if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8) #ifdef _MSC_VER - // Windows - typedef long long s64; - typedef unsigned long long u64; +// Windows +typedef long long s64; +typedef unsigned long long u64; #else - // Posix - typedef int64_t s64; - typedef uint64_t u64; +// Posix +typedef int64_t s64; +typedef uint64_t u64; #endif #endif #if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR >= 9) -namespace core { - template - inline T roundingError(); +namespace core +{ +template inline T roundingError(); - template <> - inline s16 roundingError() - { - return 0; - } +template <> inline s16 roundingError() +{ + return 0; +} } #endif } -#define S8_MIN (-0x7F - 1) +#define S8_MIN (-0x7F - 1) #define S16_MIN (-0x7FFF - 1) #define S32_MIN (-0x7FFFFFFF - 1) #define S64_MIN (-0x7FFFFFFFFFFFFFFF - 1) -#define S8_MAX 0x7F +#define S8_MAX 0x7F #define S16_MAX 0x7FFF #define S32_MAX 0x7FFFFFFF #define S64_MAX 0x7FFFFFFFFFFFFFFF -#define U8_MAX 0xFF +#define U8_MAX 0xFF #define U16_MAX 0xFFFF #define U32_MAX 0xFFFFFFFF #define U64_MAX 0xFFFFFFFFFFFFFFFF diff --git a/src/itemdef.cpp b/src/itemdef.cpp index 8e0492827..ad4eafe81 100644 --- a/src/itemdef.cpp +++ b/src/itemdef.cpp @@ -52,9 +52,9 @@ ItemDefinition::ItemDefinition(const ItemDefinition &def) *this = def; } -ItemDefinition& ItemDefinition::operator=(const ItemDefinition &def) +ItemDefinition &ItemDefinition::operator=(const ItemDefinition &def) { - if(this == &def) + if (this == &def) return *this; reset(); @@ -70,10 +70,8 @@ ItemDefinition& ItemDefinition::operator=(const ItemDefinition &def) stack_max = def.stack_max; usable = def.usable; liquids_pointable = def.liquids_pointable; - if(def.tool_capabilities) - { - tool_capabilities = new ToolCapabilities( - *def.tool_capabilities); + if (def.tool_capabilities) { + tool_capabilities = new ToolCapabilities(*def.tool_capabilities); } groups = def.groups; node_placement_prediction = def.node_placement_prediction; @@ -193,7 +191,7 @@ void ItemDefinition::deSerialize(std::istream &is) groups.clear(); u32 groups_size = readU16(is); - for(u32 i=0; i &values = m_clientcached.getValues(); + const std::vector &values = m_clientcached.getValues(); for (ClientCached *cc : values) { if (cc->wield_mesh.mesh) cc->wield_mesh.mesh->drop(); @@ -265,7 +259,7 @@ public: } m_item_definitions.clear(); } - virtual const ItemDefinition& get(const std::string &name_) const + virtual const ItemDefinition &get(const std::string &name_) const { // Convert name according to possible alias std::string name = getAlias(name_); @@ -303,11 +297,11 @@ public: } #ifndef SERVER public: - ClientCached* createClientCachedDirect(const std::string &name, - Client *client) const + ClientCached *createClientCachedDirect( + const std::string &name, Client *client) const { - infostream<<"Lazily creating item texture and mesh for \"" - <getTextureSource(); @@ -341,8 +335,7 @@ public: return cc; } - ClientCached* getClientCached(const std::string &name, - Client *client) const + ClientCached *getClientCached(const std::string &name, Client *client) const { ClientCached *cc = NULL; m_clientcached.get(name, &cc); @@ -354,62 +347,61 @@ public: } // We're gonna ask the result to be put into here - static ResultQueue result_queue; + static ResultQueue result_queue; // Throw a request in m_get_clientcached_queue.add(name, 0, 0, &result_queue); try { - while(true) { + while (true) { // Wait result for a second - GetResult - result = result_queue.pop_front(1000); + GetResult result = + result_queue.pop_front(1000); if (result.key == name) { return result.item; } } - } catch(ItemNotFoundException &e) { + } catch (ItemNotFoundException &e) { errorstream << "Waiting for clientcached " << name - << " timed out." << std::endl; + << " timed out." << std::endl; return &m_dummy_clientcached; } } // Get item inventory texture - virtual video::ITexture* getInventoryTexture(const std::string &name, - Client *client) const + virtual video::ITexture *getInventoryTexture( + const std::string &name, Client *client) const { ClientCached *cc = getClientCached(name, client); - if(!cc) + if (!cc) return NULL; return cc->inventory_texture; } // Get item wield mesh - virtual ItemMesh* getWieldMesh(const std::string &name, - Client *client) const + virtual ItemMesh *getWieldMesh(const std::string &name, Client *client) const { ClientCached *cc = getClientCached(name, client); - if(!cc) + if (!cc) return NULL; return &(cc->wield_mesh); } // Get item palette - virtual Palette* getPalette(const std::string &name, - Client *client) const + virtual Palette *getPalette(const std::string &name, Client *client) const { ClientCached *cc = getClientCached(name, client); - if(!cc) + if (!cc) return NULL; return cc->palette; } - virtual video::SColor getItemstackColor(const ItemStack &stack, - Client *client) const + virtual video::SColor getItemstackColor( + const ItemStack &stack, Client *client) const { // Look for direct color definition const std::string &colorstring = stack.metadata.getString("color", 0); video::SColor directcolor; - if (!colorstring.empty() && parseColorString(colorstring, directcolor, true)) + if (!colorstring.empty() && + parseColorString(colorstring, directcolor, true)) return directcolor; // See if there is a palette Palette *palette = getPalette(stack.name, client); @@ -423,14 +415,16 @@ public: void applyTextureOverrides(const std::vector &overrides) { infostream << "ItemDefManager::applyTextureOverrides(): Applying " - "overrides to textures" << std::endl; + "overrides to textures" + << std::endl; - for (const TextureOverride& texture_override : overrides) { - if (m_item_definitions.find(texture_override.id) == m_item_definitions.end()) { + for (const TextureOverride &texture_override : overrides) { + if (m_item_definitions.find(texture_override.id) == + m_item_definitions.end()) { continue; // Ignore unknown item } - ItemDefinition* itemdef = m_item_definitions[texture_override.id]; + ItemDefinition *itemdef = m_item_definitions[texture_override.id]; if (texture_override.hasTarget(OverrideTarget::INVENTORY)) itemdef->inventory_image = texture_override.texture; @@ -441,8 +435,7 @@ public: } void clear() { - for (auto &i : m_item_definitions) - { + for (auto &i : m_item_definitions) { delete i.second; } m_item_definitions.clear(); @@ -455,23 +448,23 @@ public: // "air" is the air node // "ignore" is the ignore node - ItemDefinition* hand_def = new ItemDefinition; + ItemDefinition *hand_def = new ItemDefinition; hand_def->name = ""; hand_def->wield_image = "wieldhand.png"; hand_def->tool_capabilities = new ToolCapabilities; m_item_definitions.insert(std::make_pair("", hand_def)); - ItemDefinition* unknown_def = new ItemDefinition; + ItemDefinition *unknown_def = new ItemDefinition; unknown_def->type = ITEM_NODE; unknown_def->name = "unknown"; m_item_definitions.insert(std::make_pair("unknown", unknown_def)); - ItemDefinition* air_def = new ItemDefinition; + ItemDefinition *air_def = new ItemDefinition; air_def->type = ITEM_NODE; air_def->name = "air"; m_item_definitions.insert(std::make_pair("air", air_def)); - ItemDefinition* ignore_def = new ItemDefinition; + ItemDefinition *ignore_def = new ItemDefinition; ignore_def->type = ITEM_NODE; ignore_def->name = "ignore"; m_item_definitions.insert(std::make_pair("ignore", ignore_def)); @@ -481,32 +474,33 @@ public: TRACESTREAM(<< "ItemDefManager: registering " << def.name << std::endl); // Ensure that the "" item (the hand) always has ToolCapabilities if (def.name.empty()) - FATAL_ERROR_IF(!def.tool_capabilities, "Hand does not have ToolCapabilities"); + FATAL_ERROR_IF(!def.tool_capabilities, + "Hand does not have ToolCapabilities"); - if(m_item_definitions.count(def.name) == 0) + if (m_item_definitions.count(def.name) == 0) m_item_definitions[def.name] = new ItemDefinition(def); else *(m_item_definitions[def.name]) = def; // Remove conflicting alias if it exists bool alias_removed = (m_aliases.erase(def.name) != 0); - if(alias_removed) - infostream<<"ItemDefManager: erased alias "< " << convert_to << std::endl); + TRACESTREAM(<< "ItemDefManager: setting alias " << name << " -> " + << convert_to << std::endl); m_aliases[name] = convert_to; } } @@ -537,21 +531,20 @@ public: clear(); // Deserialize int version = readU8(is); - if(version != 0) + if (version != 0) throw SerializationError("unsupported ItemDefManager version"); u16 count = readU16(is); - for(u16 i=0; i - request = m_get_clientcached_queue.pop(); + // NOTE this is only thread safe for ONE consumer thread! + while (!m_get_clientcached_queue.empty()) { + GetRequest request = + m_get_clientcached_queue.pop(); - m_get_clientcached_queue.pushResult(request, - createClientCachedDirect(request.key, (Client *)gamedef)); + m_get_clientcached_queue.pushResult( + request, createClientCachedDirect(request.key, + (Client *)gamedef)); } #endif } + private: // Key is name - std::map m_item_definitions; + std::map m_item_definitions; // Aliases StringMap m_aliases; #ifndef SERVER @@ -582,13 +576,14 @@ private: // A reference to this can be returned when nothing is found, to avoid NULLs mutable ClientCached m_dummy_clientcached; // Cached textures and meshes - mutable MutexedMap m_clientcached; + mutable MutexedMap m_clientcached; // Queued clientcached fetches (to be processed by the main thread) - mutable RequestQueue m_get_clientcached_queue; + mutable RequestQueue + m_get_clientcached_queue; #endif }; -IWritableItemDefManager* createItemDefManager() +IWritableItemDefManager *createItemDefManager() { return new CItemDefManager(); } diff --git a/src/itemdef.h b/src/itemdef.h index f47e6cbe7..e6170d150 100644 --- a/src/itemdef.h +++ b/src/itemdef.h @@ -54,7 +54,7 @@ struct ItemDefinition Basic item properties */ ItemType type; - std::string name; // "" = hand + std::string name; // "" = hand std::string description; // Shown in tooltip. /* @@ -64,8 +64,9 @@ struct ItemDefinition std::string inventory_overlay; // Overlay of inventory_image. std::string wield_image; // If empty, inventory_image or mesh (only nodes) is used std::string wield_overlay; // Overlay of wield_image. - std::string palette_image; // If specified, the item will be colorized based on this - video::SColor color; // The fallback color of the node. + std::string palette_image; // If specified, the item will be colorized based on + // this + video::SColor color; // The fallback color of the node. v3f wield_scale; /* @@ -91,11 +92,12 @@ struct ItemDefinition */ ItemDefinition(); ItemDefinition(const ItemDefinition &def); - ItemDefinition& operator=(const ItemDefinition &def); + ItemDefinition &operator=(const ItemDefinition &def); ~ItemDefinition(); void reset(); void serialize(std::ostream &os, u16 protocol_version) const; void deSerialize(std::istream &is); + private: void resetInitial(); }; @@ -108,30 +110,28 @@ public: virtual ~IItemDefManager() = default; // Get item definition - virtual const ItemDefinition& get(const std::string &name) const=0; + virtual const ItemDefinition &get(const std::string &name) const = 0; // Get alias definition - virtual const std::string &getAlias(const std::string &name) const=0; + virtual const std::string &getAlias(const std::string &name) const = 0; // Get set of all defined item names and aliases - virtual void getAll(std::set &result) const=0; + virtual void getAll(std::set &result) const = 0; // Check if item is known - virtual bool isKnown(const std::string &name) const=0; + virtual bool isKnown(const std::string &name) const = 0; #ifndef SERVER // Get item inventory texture - virtual video::ITexture* getInventoryTexture(const std::string &name, - Client *client) const=0; + virtual video::ITexture *getInventoryTexture( + const std::string &name, Client *client) const = 0; // Get item wield mesh - virtual ItemMesh* getWieldMesh(const std::string &name, - Client *client) const=0; + virtual ItemMesh *getWieldMesh(const std::string &name, Client *client) const = 0; // Get item palette - virtual Palette* getPalette(const std::string &name, - Client *client) const = 0; + virtual Palette *getPalette(const std::string &name, Client *client) const = 0; // Returns the base color of an item stack: the color of all // tiles that do not define their own color. - virtual video::SColor getItemstackColor(const ItemStack &stack, - Client *client) const = 0; + virtual video::SColor getItemstackColor( + const ItemStack &stack, Client *client) const = 0; #endif - virtual void serialize(std::ostream &os, u16 protocol_version)=0; + virtual void serialize(std::ostream &os, u16 protocol_version) = 0; }; class IWritableItemDefManager : public IItemDefManager @@ -142,43 +142,43 @@ public: virtual ~IWritableItemDefManager() = default; // Get item definition - virtual const ItemDefinition& get(const std::string &name) const=0; + virtual const ItemDefinition &get(const std::string &name) const = 0; // Get alias definition - virtual const std::string &getAlias(const std::string &name) const=0; + virtual const std::string &getAlias(const std::string &name) const = 0; // Get set of all defined item names and aliases - virtual void getAll(std::set &result) const=0; + virtual void getAll(std::set &result) const = 0; // Check if item is known - virtual bool isKnown(const std::string &name) const=0; + virtual bool isKnown(const std::string &name) const = 0; #ifndef SERVER // Get item inventory texture - virtual video::ITexture* getInventoryTexture(const std::string &name, - Client *client) const=0; + virtual video::ITexture *getInventoryTexture( + const std::string &name, Client *client) const = 0; // Get item wield mesh - virtual ItemMesh* getWieldMesh(const std::string &name, - Client *client) const=0; + virtual ItemMesh *getWieldMesh(const std::string &name, Client *client) const = 0; #endif // Replace the textures of registered nodes with the ones specified in // the texture pack's override.txt files - virtual void applyTextureOverrides(const std::vector &overrides)=0; + virtual void applyTextureOverrides( + const std::vector &overrides) = 0; // Remove all registered item and node definitions and aliases // Then re-add the builtin item definitions - virtual void clear()=0; + virtual void clear() = 0; // Register item definition - virtual void registerItem(const ItemDefinition &def)=0; - virtual void unregisterItem(const std::string &name)=0; + virtual void registerItem(const ItemDefinition &def) = 0; + virtual void unregisterItem(const std::string &name) = 0; // Set an alias so that items named will load as . // Alias is not set if has already been defined. // Alias will be removed if is defined at a later point of time. - virtual void registerAlias(const std::string &name, - const std::string &convert_to)=0; + virtual void registerAlias( + const std::string &name, const std::string &convert_to) = 0; - virtual void serialize(std::ostream &os, u16 protocol_version)=0; - virtual void deSerialize(std::istream &is)=0; + virtual void serialize(std::ostream &os, u16 protocol_version) = 0; + virtual void deSerialize(std::istream &is) = 0; // Do stuff asked by threads that can only be done in the main thread - virtual void processQueue(IGameDef *gamedef)=0; + virtual void processQueue(IGameDef *gamedef) = 0; }; -IWritableItemDefManager* createItemDefManager(); +IWritableItemDefManager *createItemDefManager(); diff --git a/src/itemstackmetadata.cpp b/src/itemstackmetadata.cpp index 4aa1a0903..66dd71947 100644 --- a/src/itemstackmetadata.cpp +++ b/src/itemstackmetadata.cpp @@ -17,7 +17,6 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ - #include "itemstackmetadata.h" #include "util/serialize.h" #include "util/strfnd.h" @@ -51,8 +50,8 @@ void ItemStackMetadata::serialize(std::ostream &os) const os2 << DESERIALIZE_START; for (const auto &stringvar : m_stringvars) { if (!stringvar.first.empty() || !stringvar.second.empty()) - os2 << stringvar.first << DESERIALIZE_KV_DELIM - << stringvar.second << DESERIALIZE_PAIR_DELIM; + os2 << stringvar.first << DESERIALIZE_KV_DELIM << stringvar.second + << DESERIALIZE_PAIR_DELIM; } os << serializeJsonStringIfNeeded(os2.str()); } @@ -69,7 +68,7 @@ void ItemStackMetadata::deSerialize(std::istream &is) fnd.to(1); while (!fnd.at_end()) { std::string name = fnd.next(DESERIALIZE_KV_DELIM_STR); - std::string var = fnd.next(DESERIALIZE_PAIR_DELIM_STR); + std::string var = fnd.next(DESERIALIZE_PAIR_DELIM_STR); m_stringvars[name] = var; } } else { diff --git a/src/light.cpp b/src/light.cpp index 8196fedff..af8666e5d 100644 --- a/src/light.cpp +++ b/src/light.cpp @@ -29,16 +29,15 @@ static u8 light_LUT[LIGHT_SUN + 1]; // The const ref to light_LUT is what is actually used in the code const u8 *light_decode_table = light_LUT; - -struct LightingParams { - float a, b, c; // Lighting curve polynomial coefficients +struct LightingParams +{ + float a, b, c; // Lighting curve polynomial coefficients float boost, center, sigma; // Lighting curve parametric boost - float gamma; // Lighting curve gamma correction + float gamma; // Lighting curve gamma correction }; static LightingParams params; - float decode_light_f(float x) { if (x >= 1.0f) // x is often 1.0f @@ -46,7 +45,7 @@ float decode_light_f(float x) x = std::fmax(x, 0.0f); float brightness = ((params.a * x + params.b) * x + params.c) * x; brightness += params.boost * - std::exp(-0.5f * sqr((x - params.center) / params.sigma)); + std::exp(-0.5f * sqr((x - params.center) / params.sigma)); if (brightness <= 0.0f) // May happen if parameters are extreme return 0.0f; if (brightness >= 1.0f) @@ -54,25 +53,26 @@ float decode_light_f(float x) return powf(brightness, 1.0f / params.gamma); } - // Initialize or update the light value tables using the specified gamma void set_light_table(float gamma) { -// Lighting curve bounding gradients + // Lighting curve bounding gradients const float alpha = rangelim(g_settings->getFloat("lighting_alpha"), 0.0f, 3.0f); - const float beta = rangelim(g_settings->getFloat("lighting_beta"), 0.0f, 3.0f); -// Lighting curve polynomial coefficients + const float beta = rangelim(g_settings->getFloat("lighting_beta"), 0.0f, 3.0f); + // Lighting curve polynomial coefficients params.a = alpha + beta - 2.0f; params.b = 3.0f - 2.0f * alpha - beta; params.c = alpha; -// Lighting curve parametric boost + // Lighting curve parametric boost params.boost = rangelim(g_settings->getFloat("lighting_boost"), 0.0f, 0.4f); - params.center = rangelim(g_settings->getFloat("lighting_boost_center"), 0.0f, 1.0f); - params.sigma = rangelim(g_settings->getFloat("lighting_boost_spread"), 0.0f, 0.4f); -// Lighting curve gamma correction + params.center = rangelim( + g_settings->getFloat("lighting_boost_center"), 0.0f, 1.0f); + params.sigma = rangelim( + g_settings->getFloat("lighting_boost_spread"), 0.0f, 0.4f); + // Lighting curve gamma correction params.gamma = rangelim(gamma, 0.33f, 3.0f); -// Boundary values should be fixed + // Boundary values should be fixed light_LUT[0] = 0; light_LUT[LIGHT_SUN] = 255; diff --git a/src/log.cpp b/src/log.cpp index 54442c39b..0fd4dc01c 100644 --- a/src/log.cpp +++ b/src/log.cpp @@ -37,11 +37,10 @@ with this program; if not, write to the Free Software Foundation, Inc., const int BUFFER_LENGTH = 256; -class StringBuffer : public std::streambuf { +class StringBuffer : public std::streambuf +{ public: - StringBuffer() { - buffer_index = 0; - } + StringBuffer() { buffer_index = 0; } int overflow(int c); virtual void flush(const std::string &buf) = 0; @@ -53,13 +52,10 @@ private: int buffer_index; }; - -class LogBuffer : public StringBuffer { +class LogBuffer : public StringBuffer +{ public: - LogBuffer(Logger &logger, LogLevel lev) : - logger(logger), - level(lev) - {} + LogBuffer(Logger &logger, LogLevel lev) : logger(logger), level(lev) {} void flush(const std::string &buffer); @@ -68,8 +64,8 @@ private: LogLevel level; }; - -class RawLogBuffer : public StringBuffer { +class RawLogBuffer : public StringBuffer +{ public: void flush(const std::string &buffer); }; @@ -119,33 +115,28 @@ std::ostream verbosestream(&verbose_buf); #ifdef __ANDROID__ static unsigned int g_level_to_android[] = { - ANDROID_LOG_INFO, // LL_NONE - //ANDROID_LOG_FATAL, - ANDROID_LOG_ERROR, // LL_ERROR - ANDROID_LOG_WARN, // LL_WARNING - ANDROID_LOG_WARN, // LL_ACTION - //ANDROID_LOG_INFO, - ANDROID_LOG_DEBUG, // LL_INFO - ANDROID_LOG_VERBOSE, // LL_VERBOSE + ANDROID_LOG_INFO, // LL_NONE + // ANDROID_LOG_FATAL, + ANDROID_LOG_ERROR, // LL_ERROR + ANDROID_LOG_WARN, // LL_WARNING + ANDROID_LOG_WARN, // LL_ACTION + // ANDROID_LOG_INFO, + ANDROID_LOG_DEBUG, // LL_INFO + ANDROID_LOG_VERBOSE, // LL_VERBOSE }; -class AndroidSystemLogOutput : public ICombinedLogOutput { - public: - AndroidSystemLogOutput() - { - g_logger.addOutput(this); - } - ~AndroidSystemLogOutput() - { - g_logger.removeOutput(this); - } - void logRaw(LogLevel lev, const std::string &line) - { - STATIC_ASSERT(ARRLEN(g_level_to_android) == LL_MAX, +class AndroidSystemLogOutput : public ICombinedLogOutput +{ +public: + AndroidSystemLogOutput() { g_logger.addOutput(this); } + ~AndroidSystemLogOutput() { g_logger.removeOutput(this); } + void logRaw(LogLevel lev, const std::string &line) + { + STATIC_ASSERT(ARRLEN(g_level_to_android) == LL_MAX, mismatch_between_android_and_internal_loglevels); - __android_log_print(g_level_to_android[lev], - PROJECT_NAME_C, "%s", line.c_str()); - } + __android_log_print(g_level_to_android[lev], PROJECT_NAME_C, "%s", + line.c_str()); + } }; AndroidSystemLogOutput g_android_log_output; @@ -154,7 +145,6 @@ AndroidSystemLogOutput g_android_log_output; /////////////////////////////////////////////////////////////////////////////// - //// //// Logger //// @@ -239,16 +229,15 @@ void Logger::deregisterThread() const std::string Logger::getLevelLabel(LogLevel lev) { static const std::string names[] = { - "", - "ERROR", - "WARNING", - "ACTION", - "INFO", - "VERBOSE", + "", + "ERROR", + "WARNING", + "ACTION", + "INFO", + "VERBOSE", }; assert(lev < LL_MAX && lev >= 0); - STATIC_ASSERT(ARRLEN(names) == LL_MAX, - mismatch_between_loglevel_names_and_enum); + STATIC_ASSERT(ARRLEN(names) == LL_MAX, mismatch_between_loglevel_names_and_enum); return names[lev]; } @@ -298,15 +287,14 @@ void Logger::logToOutputsRaw(LogLevel lev, const std::string &line) } void Logger::logToOutputs(LogLevel lev, const std::string &combined, - const std::string &time, const std::string &thread_name, - const std::string &payload_text) + const std::string &time, const std::string &thread_name, + const std::string &payload_text) { MutexAutoLock lock(m_mutex); for (size_t i = 0; i != m_outputs[lev].size(); i++) m_outputs[lev][i]->log(lev, combined, time, thread_name, payload_text); } - //// //// *LogOutput methods //// @@ -323,26 +311,28 @@ void FileLogOutput::setFile(const std::string &filename, s64 file_size_max) if (is_too_large) { std::string filename_secondary = filename + ".1"; - actionstream << "The log file grew too big; it is moved to " << - filename_secondary << std::endl; + actionstream << "The log file grew too big; it is moved to " + << filename_secondary << std::endl; remove(filename_secondary.c_str()); rename(filename.c_str(), filename_secondary.c_str()); } m_stream.open(filename, std::ios::app | std::ios::ate); if (!m_stream.good()) - throw FileNotGoodException("Failed to open log file " + - filename + ": " + strerror(errno)); + throw FileNotGoodException("Failed to open log file " + filename + ": " + + strerror(errno)); m_stream << "\n\n" - "-------------" << std::endl << - " Separator" << std::endl << - "-------------\n" << std::endl; + "-------------" + << std::endl + << " Separator" << std::endl + << "-------------\n" + << std::endl; } void StreamLogOutput::logRaw(LogLevel lev, const std::string &line) { bool colored_message = (Logger::color_mode == LOG_COLOR_ALWAYS) || - (Logger::color_mode == LOG_COLOR_AUTO && is_tty); + (Logger::color_mode == LOG_COLOR_AUTO && is_tty); if (colored_message) { switch (lev) { case LL_ERROR: @@ -381,7 +371,8 @@ void LogOutputBuffer::updateLogLevel() LogLevel log_level = Logger::stringToLevel(conf_loglev); if (log_level == LL_MAX) { warningstream << "Supplied unrecognized chat_log_level; " - "showing none." << std::endl; + "showing none." + << std::endl; log_level = LL_NONE; } @@ -407,7 +398,8 @@ void LogOutputBuffer::logRaw(LogLevel lev, const std::string &line) case LL_VERBOSE: // dark grey color = "\x1b(c@#888)"; break; - default: break; + default: + break; } } @@ -424,7 +416,6 @@ int StringBuffer::overflow(int c) return c; } - std::streamsize StringBuffer::xsputn(const char *s, std::streamsize n) { for (int i = 0; i < n; ++i) @@ -447,7 +438,6 @@ void StringBuffer::push_back(char c) } } - void LogBuffer::flush(const std::string &buffer) { logger.log(level, buffer); diff --git a/src/log.h b/src/log.h index 856d3479b..9fe5aa291 100644 --- a/src/log.h +++ b/src/log.h @@ -25,24 +25,26 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include #include -#if !defined(_WIN32) // POSIX - #include +#if !defined(_WIN32) // POSIX +#include #endif #include "irrlichttypes.h" class ILogOutput; -enum LogLevel { +enum LogLevel +{ LL_NONE, // Special level that is always printed LL_ERROR, LL_WARNING, - LL_ACTION, // In-game actions + LL_ACTION, // In-game actions LL_INFO, LL_VERBOSE, LL_MAX, }; -enum LogColor { +enum LogColor +{ LOG_COLOR_NEVER, LOG_COLOR_ALWAYS, LOG_COLOR_AUTO, @@ -51,7 +53,8 @@ enum LogColor { typedef u8 LogLevelMask; #define LOGLEVEL_TO_MASKLEVEL(x) (1 << x) -class Logger { +class Logger +{ public: void addOutput(ILogOutput *out); void addOutput(ILogOutput *out, LogLevel lev); @@ -77,9 +80,8 @@ public: private: void logToOutputsRaw(LogLevel, const std::string &line); - void logToOutputs(LogLevel, const std::string &combined, - const std::string &time, const std::string &thread_name, - const std::string &payload_text); + void logToOutputs(LogLevel, const std::string &combined, const std::string &time, + const std::string &thread_name, const std::string &payload_text); const std::string getThreadName(); @@ -94,28 +96,29 @@ private: bool m_trace_enabled; }; -class ILogOutput { +class ILogOutput +{ public: virtual void logRaw(LogLevel, const std::string &line) = 0; - virtual void log(LogLevel, const std::string &combined, - const std::string &time, const std::string &thread_name, - const std::string &payload_text) = 0; + virtual void log(LogLevel, const std::string &combined, const std::string &time, + const std::string &thread_name, + const std::string &payload_text) = 0; }; -class ICombinedLogOutput : public ILogOutput { +class ICombinedLogOutput : public ILogOutput +{ public: - void log(LogLevel lev, const std::string &combined, - const std::string &time, const std::string &thread_name, - const std::string &payload_text) + void log(LogLevel lev, const std::string &combined, const std::string &time, + const std::string &thread_name, const std::string &payload_text) { logRaw(lev, combined); } }; -class StreamLogOutput : public ICombinedLogOutput { +class StreamLogOutput : public ICombinedLogOutput +{ public: - StreamLogOutput(std::ostream &stream) : - m_stream(stream) + StreamLogOutput(std::ostream &stream) : m_stream(stream) { #if !defined(_WIN32) is_tty = isatty(fileno(stdout)); @@ -131,7 +134,8 @@ private: bool is_tty; }; -class FileLogOutput : public ICombinedLogOutput { +class FileLogOutput : public ICombinedLogOutput +{ public: void setFile(const std::string &filename, s64 file_size_max); @@ -144,32 +148,20 @@ private: std::ofstream m_stream; }; -class LogOutputBuffer : public ICombinedLogOutput { +class LogOutputBuffer : public ICombinedLogOutput +{ public: - LogOutputBuffer(Logger &logger) : - m_logger(logger) - { - updateLogLevel(); - }; + LogOutputBuffer(Logger &logger) : m_logger(logger) { updateLogLevel(); }; - virtual ~LogOutputBuffer() - { - m_logger.removeOutput(this); - } + virtual ~LogOutputBuffer() { m_logger.removeOutput(this); } void updateLogLevel(); void logRaw(LogLevel lev, const std::string &line); - void clear() - { - m_buffer = std::queue(); - } + void clear() { m_buffer = std::queue(); } - bool empty() const - { - return m_buffer.empty(); - } + bool empty() const { return m_buffer.empty(); } std::string get() { @@ -185,7 +177,6 @@ private: Logger &m_logger; }; - extern StreamLogOutput stdout_output; extern StreamLogOutput stderr_output; extern std::ostream null_stream; @@ -212,11 +203,12 @@ extern std::ostream infostream; extern std::ostream verbosestream; extern std::ostream dstream; -#define TRACEDO(x) do { \ - if (g_logger.getTraceEnabled()) { \ - x; \ - } \ -} while (0) +#define TRACEDO(x) \ + do { \ + if (g_logger.getTraceEnabled()) { \ + x; \ + } \ + } while (0) #define TRACESTREAM(x) TRACEDO(verbosestream x) @@ -225,5 +217,5 @@ extern std::ostream dstream; #define dout_server (*dout_server_ptr) #ifndef SERVER - #define dout_client (*dout_client_ptr) +#define dout_client (*dout_client_ptr) #endif diff --git a/src/main.cpp b/src/main.cpp index af6d307dc..d49cda59d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -39,7 +39,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "porting.h" #include "network/socket.h" #if USE_CURSES - #include "terminal_chat_console.h" +#include "terminal_chat_console.h" #endif #ifndef SERVER #include "gui/guiMainMenu.h" @@ -49,14 +49,12 @@ with this program; if not, write to the Free Software Foundation, Inc., #endif #ifdef HAVE_TOUCHSCREENGUI - #include "gui/touchscreengui.h" +#include "gui/touchscreengui.h" #endif -#if !defined(SERVER) && \ - (IRRLICHT_VERSION_MAJOR == 1) && \ - (IRRLICHT_VERSION_MINOR == 8) && \ - (IRRLICHT_VERSION_REVISION == 2) - #error "Irrlicht 1.8.2 is known to be broken - please update Irrlicht to version >= 1.8.3" +#if !defined(SERVER) && (IRRLICHT_VERSION_MAJOR == 1) && \ + (IRRLICHT_VERSION_MINOR == 8) && (IRRLICHT_VERSION_REVISION == 2) +#error "Irrlicht 1.8.2 is known to be broken - please update Irrlicht to version >= 1.8.3" #endif #define DEBUGFILE "debug.txt" @@ -74,8 +72,8 @@ static void set_allowed_options(OptionList *allowed_options); static void print_help(const OptionList &allowed_options); static void print_allowed_options(const OptionList &allowed_options); static void print_version(); -static void print_worldspecs(const std::vector &worldspecs, - std::ostream &os, bool print_name = true, bool print_path = true); +static void print_worldspecs(const std::vector &worldspecs, std::ostream &os, + bool print_name = true, bool print_path = true); static void print_modified_quicktune_values(); static void list_game_ids(); @@ -105,7 +103,6 @@ static bool migrate_map_database(const GameParams &game_params, const Settings & /**********************************************************************/ - FileLogOutput file_log_output; static OptionList allowed_options; @@ -120,9 +117,7 @@ int main(int argc, char *argv[]) Settings cmd_args; bool cmd_args_ok = get_cmdline_opts(argc, argv, &cmd_args); - if (!cmd_args_ok - || cmd_args.getFlag("help") - || cmd_args.exists("nonopt1")) { + if (!cmd_args_ok || cmd_args.getFlag("help") || cmd_args.exists("nonopt1")) { porting::attachOrCreateConsole(); print_help(allowed_options); return cmd_args_ok ? 0 : 1; @@ -172,7 +167,7 @@ int main(int argc, char *argv[]) list_worlds(true, true); } else { errorstream << "Invalid --worldlist value: " - << cmd_args.get("worldlist") << std::endl; + << cmd_args.get("worldlist") << std::endl; return 1; } return 0; @@ -191,8 +186,9 @@ int main(int argc, char *argv[]) return run_tests(); #else errorstream << "Unittest support is not enabled in this binary. " - << "If you want to enable it, compile project with BUILD_UNITTESTS=1 flag." - << std::endl; + << "If you want to enable it, compile project with " + "BUILD_UNITTESTS=1 flag." + << std::endl; #endif } #endif @@ -237,12 +233,10 @@ int main(int argc, char *argv[]) return retval; } - /***************************************************************************** * Startup / Init *****************************************************************************/ - static bool get_cmdline_opts(int argc, char *argv[], Settings *cmd_args) { set_allowed_options(&allowed_options); @@ -254,71 +248,98 @@ static void set_allowed_options(OptionList *allowed_options) { allowed_options->clear(); - allowed_options->insert(std::make_pair("help", ValueSpec(VALUETYPE_FLAG, - _("Show allowed options")))); - allowed_options->insert(std::make_pair("version", ValueSpec(VALUETYPE_FLAG, - _("Show version information")))); - allowed_options->insert(std::make_pair("config", ValueSpec(VALUETYPE_STRING, - _("Load configuration from specified file")))); - allowed_options->insert(std::make_pair("port", ValueSpec(VALUETYPE_STRING, - _("Set network port (UDP)")))); - allowed_options->insert(std::make_pair("run-unittests", ValueSpec(VALUETYPE_FLAG, - _("Run the unit tests and exit")))); - allowed_options->insert(std::make_pair("map-dir", ValueSpec(VALUETYPE_STRING, - _("Same as --world (deprecated)")))); - allowed_options->insert(std::make_pair("world", ValueSpec(VALUETYPE_STRING, - _("Set world path (implies local game if used with option --go)")))); - allowed_options->insert(std::make_pair("worldname", ValueSpec(VALUETYPE_STRING, - _("Set world by name (implies local game if used with option --go)")))); - allowed_options->insert(std::make_pair("worldlist", ValueSpec(VALUETYPE_STRING, - _("Get list of worlds ('path' lists paths, " - "'name' lists names, 'both' lists both)")))); - allowed_options->insert(std::make_pair("quiet", ValueSpec(VALUETYPE_FLAG, - _("Print to console errors only")))); - allowed_options->insert(std::make_pair("color", ValueSpec(VALUETYPE_STRING, - _("Coloured logs ('always', 'never' or 'auto'), defaults to 'auto'" - )))); - allowed_options->insert(std::make_pair("info", ValueSpec(VALUETYPE_FLAG, - _("Print more information to console")))); - allowed_options->insert(std::make_pair("verbose", ValueSpec(VALUETYPE_FLAG, - _("Print even more information to console")))); - allowed_options->insert(std::make_pair("trace", ValueSpec(VALUETYPE_FLAG, - _("Print enormous amounts of information to log and console")))); - allowed_options->insert(std::make_pair("logfile", ValueSpec(VALUETYPE_STRING, - _("Set logfile path ('' = no logging)")))); - allowed_options->insert(std::make_pair("gameid", ValueSpec(VALUETYPE_STRING, - _("Set gameid (\"--gameid list\" prints available ones)")))); - allowed_options->insert(std::make_pair("migrate", ValueSpec(VALUETYPE_STRING, - _("Migrate from current map backend to another (Only works when using minetestserver or with --server)")))); - allowed_options->insert(std::make_pair("migrate-players", ValueSpec(VALUETYPE_STRING, - _("Migrate from current players backend to another (Only works when using minetestserver or with --server)")))); - allowed_options->insert(std::make_pair("migrate-auth", ValueSpec(VALUETYPE_STRING, - _("Migrate from current auth backend to another (Only works when using minetestserver or with --server)")))); - allowed_options->insert(std::make_pair("terminal", ValueSpec(VALUETYPE_FLAG, - _("Feature an interactive terminal (Only works when using minetestserver or with --server)")))); + allowed_options->insert(std::make_pair( + "help", ValueSpec(VALUETYPE_FLAG, _("Show allowed options")))); + allowed_options->insert(std::make_pair("version", + ValueSpec(VALUETYPE_FLAG, _("Show version information")))); + allowed_options->insert(std::make_pair("config", + ValueSpec(VALUETYPE_STRING, + _("Load configuration from specified file")))); + allowed_options->insert(std::make_pair("port", + ValueSpec(VALUETYPE_STRING, _("Set network port (UDP)")))); + allowed_options->insert(std::make_pair("run-unittests", + ValueSpec(VALUETYPE_FLAG, _("Run the unit tests and exit")))); + allowed_options->insert(std::make_pair("map-dir", + ValueSpec(VALUETYPE_STRING, _("Same as --world (deprecated)")))); + allowed_options->insert(std::make_pair( + "world", ValueSpec(VALUETYPE_STRING, + _("Set world path (implies local game " + "if used with option --go)")))); + allowed_options->insert(std::make_pair("worldname", + ValueSpec(VALUETYPE_STRING, + _("Set world by name (implies local game if used " + "with option --go)")))); + allowed_options->insert(std::make_pair("worldlist", + ValueSpec(VALUETYPE_STRING, + _("Get list of worlds ('path' lists paths, " + "'name' lists names, 'both' lists both)")))); + allowed_options->insert(std::make_pair("quiet", + ValueSpec(VALUETYPE_FLAG, _("Print to console errors only")))); + allowed_options->insert(std::make_pair( + "color", ValueSpec(VALUETYPE_STRING, + _("Coloured logs ('always', 'never' or " + "'auto'), defaults to 'auto'")))); + allowed_options->insert(std::make_pair( + "info", ValueSpec(VALUETYPE_FLAG, + _("Print more information to console")))); + allowed_options->insert(std::make_pair("verbose", + ValueSpec(VALUETYPE_FLAG, + _("Print even more information to console")))); + allowed_options->insert(std::make_pair("trace", + ValueSpec(VALUETYPE_FLAG, _("Print enormous amounts of " + "information to log and console")))); + allowed_options->insert(std::make_pair("logfile", + ValueSpec(VALUETYPE_STRING, + _("Set logfile path ('' = no logging)")))); + allowed_options->insert(std::make_pair("gameid", + ValueSpec(VALUETYPE_STRING, _("Set gameid (\"--gameid list\" " + "prints available ones)")))); + allowed_options->insert(std::make_pair("migrate", + ValueSpec(VALUETYPE_STRING, + _("Migrate from current map backend to another " + "(Only works when using minetestserver or with " + "--server)")))); + allowed_options->insert(std::make_pair("migrate-players", + ValueSpec(VALUETYPE_STRING, + _("Migrate from current players backend to " + "another (Only works when using minetestserver " + "or with --server)")))); + allowed_options->insert(std::make_pair("migrate-auth", + ValueSpec(VALUETYPE_STRING, + _("Migrate from current auth backend to another " + "(Only works when using minetestserver or with " + "--server)")))); + allowed_options->insert(std::make_pair("terminal", + ValueSpec(VALUETYPE_FLAG, + _("Feature an interactive terminal (Only works " + "when using minetestserver or with " + "--server)")))); #ifndef SERVER - allowed_options->insert(std::make_pair("videomodes", ValueSpec(VALUETYPE_FLAG, - _("Show available video modes")))); - allowed_options->insert(std::make_pair("speedtests", ValueSpec(VALUETYPE_FLAG, - _("Run speed tests")))); - allowed_options->insert(std::make_pair("address", ValueSpec(VALUETYPE_STRING, - _("Address to connect to. ('' = local game)")))); - allowed_options->insert(std::make_pair("random-input", ValueSpec(VALUETYPE_FLAG, - _("Enable random user input, for testing")))); - allowed_options->insert(std::make_pair("server", ValueSpec(VALUETYPE_FLAG, - _("Run dedicated server")))); - allowed_options->insert(std::make_pair("name", ValueSpec(VALUETYPE_STRING, - _("Set player name")))); - allowed_options->insert(std::make_pair("password", ValueSpec(VALUETYPE_STRING, - _("Set password")))); - allowed_options->insert(std::make_pair("password-file", ValueSpec(VALUETYPE_STRING, - _("Set password from contents of file")))); - allowed_options->insert(std::make_pair("go", ValueSpec(VALUETYPE_FLAG, - _("Disable main menu")))); - allowed_options->insert(std::make_pair("console", ValueSpec(VALUETYPE_FLAG, - _("Starts with the console (Windows only)")))); + allowed_options->insert(std::make_pair("videomodes", + ValueSpec(VALUETYPE_FLAG, _("Show available video modes")))); + allowed_options->insert(std::make_pair( + "speedtests", ValueSpec(VALUETYPE_FLAG, _("Run speed tests")))); + allowed_options->insert(std::make_pair("address", + ValueSpec(VALUETYPE_STRING, + _("Address to connect to. ('' = local game)")))); + allowed_options->insert(std::make_pair("random-input", + ValueSpec(VALUETYPE_FLAG, + _("Enable random user input, for testing")))); + allowed_options->insert(std::make_pair( + "server", ValueSpec(VALUETYPE_FLAG, _("Run dedicated server")))); + allowed_options->insert(std::make_pair( + "name", ValueSpec(VALUETYPE_STRING, _("Set player name")))); + allowed_options->insert(std::make_pair( + "password", ValueSpec(VALUETYPE_STRING, _("Set password")))); + allowed_options->insert(std::make_pair("password-file", + ValueSpec(VALUETYPE_STRING, + _("Set password from contents of file")))); + allowed_options->insert(std::make_pair( + "go", ValueSpec(VALUETYPE_FLAG, _("Disable main menu")))); + allowed_options->insert(std::make_pair("console", + ValueSpec(VALUETYPE_FLAG, + _("Starts with the console (Windows only)")))); #endif - } static void print_help(const OptionList &allowed_options) @@ -346,8 +367,8 @@ static void print_allowed_options(const OptionList &allowed_options) static void print_version() { - std::cout << PROJECT_NAME_C " " << g_version_hash - << " (" << porting::getPlatformName() << ")" << std::endl; + std::cout << PROJECT_NAME_C " " << g_version_hash << " (" + << porting::getPlatformName() << ")" << std::endl; #ifndef SERVER std::cout << "Using Irrlicht " IRRLICHT_SDK_VERSION << std::endl; #endif @@ -358,7 +379,7 @@ static void list_game_ids() { std::set gameids = getAvailableGameIds(); for (const std::string &gameid : gameids) - std::cout << gameid < &worldspecs, - std::ostream &os, bool print_name, bool print_path) +static void print_worldspecs(const std::vector &worldspecs, std::ostream &os, + bool print_name, bool print_path) { for (const WorldSpec &worldspec : worldspecs) { std::string name = worldspec.name; @@ -437,7 +458,7 @@ static bool setup_log_params(const Settings &cmd_args) if (cmd_args.getFlag("trace")) { dstream << _("Enabling trace level debug output") << std::endl; g_logger.setTraceEnabled(true); - dout_con_ptr = &verbosestream; // This is somewhat old + dout_con_ptr = &verbosestream; // This is somewhat old socket_enable_debug_output = true; // Sockets doesn't use log.h } @@ -492,8 +513,8 @@ static bool init_common(const Settings &cmd_args, int argc, char *argv[]) // Initialize HTTP fetcher httpfetch_init(g_settings->getS32("curl_parallel_limit")); - init_gettext(porting::path_locale.c_str(), - g_settings->get("language"), argc, argv); + init_gettext(porting::path_locale.c_str(), g_settings->get("language"), argc, + argv); return true; } @@ -501,21 +522,20 @@ static bool init_common(const Settings &cmd_args, int argc, char *argv[]) static void startup_message() { infostream << PROJECT_NAME << " " << _("with") - << " SER_FMT_VER_HIGHEST_READ=" - << (int)SER_FMT_VER_HIGHEST_READ << ", " - << g_build_info << std::endl; + << " SER_FMT_VER_HIGHEST_READ=" << (int)SER_FMT_VER_HIGHEST_READ + << ", " << g_build_info << std::endl; } static bool read_config_file(const Settings &cmd_args) { // Path of configuration file in use - sanity_check(g_settings_path == ""); // Sanity check + sanity_check(g_settings_path == ""); // Sanity check if (cmd_args.exists("config")) { bool r = g_settings->readConfigFile(cmd_args.get("config").c_str()); if (!r) { errorstream << "Could not read configuration from \"" - << cmd_args.get("config") << "\"" << std::endl; + << cmd_args.get("config") << "\"" << std::endl; return false; } g_settings_path = cmd_args.get("config"); @@ -523,14 +543,14 @@ static bool read_config_file(const Settings &cmd_args) std::vector filenames; filenames.push_back(porting::path_user + DIR_DELIM + "minetest.conf"); // Legacy configuration file location - filenames.push_back(porting::path_user + - DIR_DELIM + ".." + DIR_DELIM + "minetest.conf"); + filenames.push_back(porting::path_user + DIR_DELIM + ".." + DIR_DELIM + + "minetest.conf"); #if RUN_IN_PLACE // Try also from a lower level (to aid having the same configuration // for many RUN_IN_PLACE installs) - filenames.push_back(porting::path_user + - DIR_DELIM + ".." + DIR_DELIM + ".." + DIR_DELIM + "minetest.conf"); + filenames.push_back(porting::path_user + DIR_DELIM + ".." + DIR_DELIM + + ".." + DIR_DELIM + "minetest.conf"); #endif for (const std::string &filename : filenames) { @@ -562,29 +582,32 @@ static void init_log_streams(const Settings &cmd_args) // Old integer format if (std::isdigit(conf_loglev[0])) { warningstream << "Deprecated use of debug_log_level with an " - "integer value; please update your configuration." << std::endl; - static const char *lev_name[] = - {"", "error", "action", "info", "verbose"}; + "integer value; please update your configuration." + << std::endl; + static const char *lev_name[] = { + "", "error", "action", "info", "verbose"}; int lev_i = atoi(conf_loglev.c_str()); if (lev_i < 0 || lev_i >= (int)ARRLEN(lev_name)) { warningstream << "Supplied invalid debug_log_level!" - " Assuming action level." << std::endl; + " Assuming action level." + << std::endl; lev_i = 2; } conf_loglev = lev_name[lev_i]; } - if (log_filename.empty() || conf_loglev.empty()) // No logging + if (log_filename.empty() || conf_loglev.empty()) // No logging return; LogLevel log_level = Logger::stringToLevel(conf_loglev); if (log_level == LL_MAX) { warningstream << "Supplied unrecognized debug_log_level; " - "using maximum." << std::endl; + "using maximum." + << std::endl; } - file_log_output.setFile(log_filename, - g_settings->getU64("debug_log_size_max") * 1000000); + file_log_output.setFile( + log_filename, g_settings->getU64("debug_log_size_max") * 1000000); g_logger.addOutputMaxLevel(&file_log_output, log_level); } @@ -645,8 +668,10 @@ static bool get_world_from_cmdline(GameParams *game_params, const Settings &cmd_ for (const WorldSpec &worldspec : worldspecs) { std::string name = worldspec.name; if (name == commanded_worldname) { - dstream << _("Using world specified by --worldname on the " - "command line") << std::endl; + dstream << _("Using world specified by --worldname on " + "the " + "command line") + << std::endl; commanded_world = worldspec.path; found = true; break; @@ -654,7 +679,7 @@ static bool get_world_from_cmdline(GameParams *game_params, const Settings &cmd_ } if (!found) { dstream << _("World") << " '" << commanded_worldname - << _("' not available. Available worlds:") << std::endl; + << _("' not available. Available worlds:") << std::endl; print_worldspecs(worldspecs, dstream); return false; } @@ -698,25 +723,26 @@ static bool auto_select_world(GameParams *game_params) // If there is only a single world, use it if (worldspecs.size() == 1) { world_path = worldspecs[0].path; - dstream <<_("Automatically selecting world at") << " [" - << world_path << "]" << std::endl; - // If there are multiple worlds, list them + dstream << _("Automatically selecting world at") << " [" << world_path + << "]" << std::endl; + // If there are multiple worlds, list them } else if (worldspecs.size() > 1 && game_params->is_dedicated_server) { std::cerr << _("Multiple worlds are available.") << std::endl; std::cerr << _("Please select one using --worldname " - " or --world ") << std::endl; + " or --world ") + << std::endl; print_worldspecs(worldspecs, std::cerr); return false; - // If there are no worlds, automatically create a new one + // If there are no worlds, automatically create a new one } else { // This is the ultimate default world path - world_path = porting::path_user + DIR_DELIM + "worlds" + - DIR_DELIM + "world"; - infostream << "Using default world at [" - << world_path << "]" << std::endl; + world_path = porting::path_user + DIR_DELIM + "worlds" + DIR_DELIM + + "world"; + infostream << "Using default world at [" << world_path << "]" + << std::endl; } - assert(world_path != ""); // Post-condition + assert(world_path != ""); // Post-condition game_params->world_path = world_path; return true; } @@ -726,8 +752,8 @@ static std::string get_clean_world_path(const std::string &path) const std::string worldmt = "world.mt"; std::string clean_path; - if (path.size() > worldmt.size() - && path.substr(path.size() - worldmt.size()) == worldmt) { + if (path.size() > worldmt.size() && + path.substr(path.size() - worldmt.size()) == worldmt) { dstream << _("Supplied world.mt file - stripping it off.") << std::endl; clean_path = path.substr(0, path.size() - worldmt.size()); } else { @@ -736,7 +762,6 @@ static std::string get_clean_world_path(const std::string &path) return path; } - static bool game_configure_subgame(GameParams *game_params, const Settings &cmd_args) { bool success; @@ -760,7 +785,7 @@ static bool get_game_from_cmdline(GameParams *game_params, const Settings &cmd_a return false; } dstream << _("Using game specified by --gameid on the command line") - << std::endl; + << std::endl; game_params->game_spec = commanded_gamespec; return true; } @@ -772,22 +797,24 @@ static bool determine_subgame(GameParams *game_params) { SubgameSpec gamespec; - assert(game_params->world_path != ""); // Pre-condition + assert(game_params->world_path != ""); // Pre-condition // If world doesn't exist - if (!game_params->world_path.empty() - && !getWorldExists(game_params->world_path)) { + if (!game_params->world_path.empty() && + !getWorldExists(game_params->world_path)) { // Try to take gamespec from command line if (game_params->game_spec.isValid()) { gamespec = game_params->game_spec; - infostream << "Using commanded gameid [" << gamespec.id << "]" << std::endl; + infostream << "Using commanded gameid [" << gamespec.id << "]" + << std::endl; } else { // Otherwise we will be using "minetest" gamespec = findSubgame(g_settings->get("default_game")); - infostream << "Using default gameid [" << gamespec.id << "]" << std::endl; + infostream << "Using default gameid [" << gamespec.id << "]" + << std::endl; if (!gamespec.isValid()) { errorstream << "Game specified in default_game [" - << g_settings->get("default_game") - << "] is invalid." << std::endl; + << g_settings->get("default_game") + << "] is invalid." << std::endl; return false; } } @@ -797,21 +824,23 @@ static bool determine_subgame(GameParams *game_params) if (game_params->game_spec.isValid()) { gamespec = game_params->game_spec; if (game_params->game_spec.id != world_gameid) { - warningstream << "Using commanded gameid [" - << gamespec.id << "]" << " instead of world gameid [" - << world_gameid << "]" << std::endl; + warningstream << "Using commanded gameid [" << gamespec.id + << "]" + << " instead of world gameid [" + << world_gameid << "]" << std::endl; } } else { // If world contains an embedded game, use it; // Otherwise find world from local system. gamespec = findWorldSubgame(game_params->world_path); - infostream << "Using world gameid [" << gamespec.id << "]" << std::endl; + infostream << "Using world gameid [" << gamespec.id << "]" + << std::endl; } } if (!gamespec.isValid()) { errorstream << "Game [" << gamespec.id << "] could not be found." - << std::endl; + << std::endl; return false; } @@ -819,35 +848,33 @@ static bool determine_subgame(GameParams *game_params) return true; } - /***************************************************************************** * Dedicated server *****************************************************************************/ static bool run_dedicated_server(const GameParams &game_params, const Settings &cmd_args) { - verbosestream << _("Using world path") << " [" - << game_params.world_path << "]" << std::endl; - verbosestream << _("Using gameid") << " [" - << game_params.game_spec.id << "]" << std::endl; + verbosestream << _("Using world path") << " [" << game_params.world_path << "]" + << std::endl; + verbosestream << _("Using gameid") << " [" << game_params.game_spec.id << "]" + << std::endl; // Bind address std::string bind_str = g_settings->get("bind_address"); Address bind_addr(0, 0, 0, 0, game_params.socket_port); if (g_settings->getBool("ipv6_server")) { - bind_addr.setAddress((IPv6AddressBytes*) NULL); + bind_addr.setAddress((IPv6AddressBytes *)NULL); } try { bind_addr.Resolve(bind_str.c_str()); } catch (ResolveError &e) { infostream << "Resolving bind address \"" << bind_str - << "\" failed: " << e.what() - << " -- Listening on all addresses." << std::endl; + << "\" failed: " << e.what() + << " -- Listening on all addresses." << std::endl; } if (bind_addr.isIPv6() && !g_settings->getBool("enable_ipv6")) { - errorstream << "Unable to listen on " - << bind_addr.serializeString() - << L" because IPv6 is disabled" << std::endl; + errorstream << "Unable to listen on " << bind_addr.serializeString() + << L" because IPv6 is disabled" << std::endl; return false; } @@ -872,15 +899,18 @@ static bool run_dedicated_server(const GameParams &game_params, const Settings & if (!name_ok) { if (admin_nick.empty()) { errorstream << "No name given for admin. " - << "Please check your minetest.conf that it " - << "contains a 'name = ' to your main admin account." - << std::endl; + << "Please check your minetest.conf that it " + << "contains a 'name = ' to your main admin " + "account." + << std::endl; } else { - errorstream << "Name for admin '" - << admin_nick << "' is not valid. " - << "Please check that it only contains allowed characters. " - << "Valid characters are: " << PLAYERNAME_ALLOWED_CHARS_USER_EXPL - << std::endl; + errorstream << "Name for admin '" << admin_nick + << "' is not valid. " + << "Please check that it only contains " + "allowed characters. " + << "Valid characters are: " + << PLAYERNAME_ALLOWED_CHARS_USER_EXPL + << std::endl; } return false; } @@ -918,13 +948,14 @@ static bool run_dedicated_server(const GameParams &game_params, const Settings & } else { #else errorstream << "Cmd arg --terminal passed, but " - << "compiled without ncurses. Ignoring." << std::endl; - } { + << "compiled without ncurses. Ignoring." << std::endl; + } + { #endif try { // Create server - Server server(game_params.world_path, game_params.game_spec, false, - bind_addr, true); + Server server(game_params.world_path, game_params.game_spec, + false, bind_addr, true); server.start(); // Run server @@ -955,21 +986,24 @@ static bool migrate_map_database(const GameParams &game_params, const Settings & if (!world_mt.exists("backend")) { errorstream << "Please specify your current backend in world.mt:" - << std::endl - << " backend = {sqlite3|leveldb|redis|dummy|postgresql}" - << std::endl; + << std::endl + << " backend = " + "{sqlite3|leveldb|redis|dummy|postgresql}" + << std::endl; return false; } std::string backend = world_mt.get("backend"); if (backend == migrate_to) { errorstream << "Cannot migrate: new backend is same" - << " as the old one" << std::endl; + << " as the old one" << std::endl; return false; } - MapDatabase *old_db = ServerMap::createDatabase(backend, game_params.world_path, world_mt), - *new_db = ServerMap::createDatabase(migrate_to, game_params.world_path, world_mt); + MapDatabase *old_db = ServerMap::createDatabase( + backend, game_params.world_path, world_mt), + *new_db = ServerMap::createDatabase( + migrate_to, game_params.world_path, world_mt); u32 count = 0; time_t last_update_time = 0; @@ -978,19 +1012,22 @@ static bool migrate_map_database(const GameParams &game_params, const Settings & std::vector blocks; old_db->listAllLoadableBlocks(blocks); new_db->beginSave(); - for (std::vector::const_iterator it = blocks.begin(); it != blocks.end(); ++it) { - if (kill) return false; + for (std::vector::const_iterator it = blocks.begin(); it != blocks.end(); + ++it) { + if (kill) + return false; std::string data; old_db->loadBlock(*it, &data); if (!data.empty()) { new_db->saveBlock(*it, data); } else { - errorstream << "Failed to load block " << PP(*it) << ", skipping it." << std::endl; + errorstream << "Failed to load block " << PP(*it) + << ", skipping it." << std::endl; } if (++count % 0xFF == 0 && time(NULL) - last_update_time >= 1) { std::cerr << " Migrated " << count << " blocks, " - << (100.0 * count / blocks.size()) << "% completed.\r"; + << (100.0 * count / blocks.size()) << "% completed.\r"; new_db->endSave(); new_db->beginSave(); last_update_time = time(NULL); diff --git a/src/map.cpp b/src/map.cpp index b9ab7c066..fc284dcee 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -57,15 +57,12 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "database/database-postgresql.h" #endif - /* Map */ -Map::Map(std::ostream &dout, IGameDef *gamedef): - m_dout(dout), - m_gamedef(gamedef), - m_nodedef(gamedef->ndef()) +Map::Map(std::ostream &dout, IGameDef *gamedef) : + m_dout(dout), m_gamedef(gamedef), m_nodedef(gamedef->ndef()) { } @@ -96,14 +93,14 @@ void Map::dispatchEvent(const MapEditEvent &event) } } -MapSector * Map::getSectorNoGenerateNoLock(v2s16 p) +MapSector *Map::getSectorNoGenerateNoLock(v2s16 p) { - if(m_sector_cache != NULL && p == m_sector_cache_p){ - MapSector * sector = m_sector_cache; + if (m_sector_cache != NULL && p == m_sector_cache_p) { + MapSector *sector = m_sector_cache; return sector; } - std::map::iterator n = m_sectors.find(p); + std::map::iterator n = m_sectors.find(p); if (n == m_sectors.end()) return NULL; @@ -117,25 +114,25 @@ MapSector * Map::getSectorNoGenerateNoLock(v2s16 p) return sector; } -MapSector * Map::getSectorNoGenerate(v2s16 p) +MapSector *Map::getSectorNoGenerate(v2s16 p) { return getSectorNoGenerateNoLock(p); } -MapBlock * Map::getBlockNoCreateNoEx(v3s16 p3d) +MapBlock *Map::getBlockNoCreateNoEx(v3s16 p3d) { v2s16 p2d(p3d.X, p3d.Z); - MapSector * sector = getSectorNoGenerate(p2d); - if(sector == NULL) + MapSector *sector = getSectorNoGenerate(p2d); + if (sector == NULL) return NULL; MapBlock *block = sector->getBlockNoCreateNoEx(p3d.Y); return block; } -MapBlock * Map::getBlockNoCreate(v3s16 p3d) +MapBlock *Map::getBlockNoCreate(v3s16 p3d) { MapBlock *block = getBlockNoCreateNoEx(p3d); - if(block == NULL) + if (block == NULL) throw InvalidPositionException(); return block; } @@ -165,7 +162,7 @@ MapNode Map::getNode(v3s16 p, bool *is_valid_position) return {CONTENT_IGNORE}; } - v3s16 relpos = p - blockpos*MAP_BLOCKSIZE; + v3s16 relpos = p - blockpos * MAP_BLOCKSIZE; bool is_valid_p; MapNode node = block->getNodeNoCheck(relpos, &is_valid_p); if (is_valid_position != NULL) @@ -174,26 +171,27 @@ MapNode Map::getNode(v3s16 p, bool *is_valid_position) } // throws InvalidPositionException if not found -void Map::setNode(v3s16 p, MapNode & n) +void Map::setNode(v3s16 p, MapNode &n) { v3s16 blockpos = getNodeBlockPos(p); MapBlock *block = getBlockNoCreate(blockpos); - v3s16 relpos = p - blockpos*MAP_BLOCKSIZE; + v3s16 relpos = p - blockpos * MAP_BLOCKSIZE; // Never allow placing CONTENT_IGNORE, it causes problems - if(n.getContent() == CONTENT_IGNORE){ + if (n.getContent() == CONTENT_IGNORE) { bool temp_bool; - errorstream<<"Map::setNode(): Not allowing to place CONTENT_IGNORE" - <<" while trying to replace \"" - <get(block->getNodeNoCheck(relpos, &temp_bool)).name - <<"\" at "<get(block->getNodeNoCheck(relpos, &temp_bool)) + .name + << "\" at " << PP(p) << " (block " << PP(blockpos) << ")" + << std::endl; return; } block->setNodeNoCheck(relpos, n); } void Map::addNodeAndUpdate(v3s16 p, MapNode n, - std::map &modified_blocks, - bool remove_metadata) + std::map &modified_blocks, bool remove_metadata) { // Collect old node for rollback RollbackNode rollback_oldnode(this, p, m_gamedef); @@ -213,7 +211,7 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n, setNode(p, n); // Update lighting - std::vector > oldnodes; + std::vector> oldnodes; oldnodes.emplace_back(p, oldnode); voxalgo::update_lighting_nodes(this, oldnodes, modified_blocks); @@ -222,8 +220,7 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n, } // Report for rollback - if(m_gamedef->rollback()) - { + if (m_gamedef->rollback()) { RollbackNode rollback_newnode(this, p, m_gamedef); RollbackAction action; action.setSetNode(p, rollback_oldnode, rollback_newnode); @@ -240,15 +237,13 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n, bool is_valid_position; MapNode n2 = getNode(p2, &is_valid_position); - if(is_valid_position && - (m_nodedef->get(n2).isLiquid() || - n2.getContent() == CONTENT_AIR)) + if (is_valid_position && (m_nodedef->get(n2).isLiquid() || + n2.getContent() == CONTENT_AIR)) m_transforming_liquid.push_back(p2); } } -void Map::removeNodeAndUpdate(v3s16 p, - std::map &modified_blocks) +void Map::removeNodeAndUpdate(v3s16 p, std::map &modified_blocks) { addNodeAndUpdate(p, MapNode(CONTENT_AIR), modified_blocks, true); } @@ -261,16 +256,15 @@ bool Map::addNodeWithEvent(v3s16 p, MapNode n, bool remove_metadata) event.n = n; bool succeeded = true; - try{ - std::map modified_blocks; + try { + std::map modified_blocks; addNodeAndUpdate(p, n, modified_blocks, remove_metadata); // Copy modified_blocks to event for (auto &modified_block : modified_blocks) { event.modified_blocks.insert(modified_block.first); } - } - catch(InvalidPositionException &e){ + } catch (InvalidPositionException &e) { succeeded = false; } @@ -286,16 +280,15 @@ bool Map::removeNodeWithEvent(v3s16 p) event.p = p; bool succeeded = true; - try{ - std::map modified_blocks; + try { + std::map modified_blocks; removeNodeAndUpdate(p, modified_blocks); // Copy modified_blocks to event for (auto &modified_block : modified_blocks) { event.modified_blocks.insert(modified_block.first); } - } - catch(InvalidPositionException &e){ + } catch (InvalidPositionException &e) { succeeded = false; } @@ -304,14 +297,14 @@ bool Map::removeNodeWithEvent(v3s16 p) return succeeded; } -struct TimeOrderedMapBlock { +struct TimeOrderedMapBlock +{ MapSector *sect; MapBlock *block; - TimeOrderedMapBlock(MapSector *sect, MapBlock *block) : - sect(sect), - block(block) - {} + TimeOrderedMapBlock(MapSector *sect, MapBlock *block) : sect(sect), block(block) + { + } bool operator<(const TimeOrderedMapBlock &b) const { @@ -350,14 +343,15 @@ void Map::timerUpdate(float dtime, float unload_timeout, u32 max_loaded_blocks, for (MapBlock *block : blocks) { block->incrementUsageTimer(dtime); - if (block->refGet() == 0 - && block->getUsageTimer() > unload_timeout) { + if (block->refGet() == 0 && + block->getUsageTimer() > unload_timeout) { v3s16 p = block->getPos(); // Save if modified - if (block->getModified() != MOD_STATE_CLEAN - && save_before_unloading) { - modprofiler.add(block->getModifiedReasonString(), 1); + if (block->getModified() != MOD_STATE_CLEAN && + save_before_unloading) { + modprofiler.add(block->getModifiedReasonString(), + 1); if (!saveBlock(block)) continue; saved_blocks_count++; @@ -395,8 +389,10 @@ void Map::timerUpdate(float dtime, float unload_timeout, u32 max_loaded_blocks, } block_count_all = mapblock_queue.size(); // Delete old blocks, and blocks over the limit from the memory - while (!mapblock_queue.empty() && (mapblock_queue.size() > max_loaded_blocks - || mapblock_queue.top().block->getUsageTimer() > unload_timeout)) { + while (!mapblock_queue.empty() && + (mapblock_queue.size() > max_loaded_blocks || + mapblock_queue.top().block->getUsageTimer() > + unload_timeout)) { TimeOrderedMapBlock b = mapblock_queue.top(); mapblock_queue.pop(); @@ -408,7 +404,8 @@ void Map::timerUpdate(float dtime, float unload_timeout, u32 max_loaded_blocks, v3s16 p = block->getPos(); // Save if modified - if (block->getModified() != MOD_STATE_CLEAN && save_before_unloading) { + if (block->getModified() != MOD_STATE_CLEAN && + save_before_unloading) { modprofiler.add(block->getModifiedReasonString(), 1); if (!saveBlock(block)) continue; @@ -436,18 +433,18 @@ void Map::timerUpdate(float dtime, float unload_timeout, u32 max_loaded_blocks, // Finally delete the empty sectors deleteSectors(sector_deletion_queue); - if(deleted_blocks_count != 0) - { + if (deleted_blocks_count != 0) { PrintInfo(infostream); // ServerMap/ClientMap: - infostream<<"Unloaded "< §orList) for (v2s16 j : sectorList) { MapSector *sector = m_sectors[j]; // If sector is in sector cache, remove it from there - if(m_sector_cache == sector) + if (m_sector_cache == sector) m_sector_cache = NULL; // Remove from map and delete m_sectors.erase(j); @@ -473,60 +470,56 @@ void Map::deleteSectors(std::vector §orList) void Map::PrintInfo(std::ostream &out) { - out<<"Map: "; + out << "Map: "; } #define WATER_DROP_BOOST 4 const static v3s16 liquid_6dirs[6] = { - // order: upper before same level before lower - v3s16( 0, 1, 0), - v3s16( 0, 0, 1), - v3s16( 1, 0, 0), - v3s16( 0, 0,-1), - v3s16(-1, 0, 0), - v3s16( 0,-1, 0) -}; + // order: upper before same level before lower + v3s16(0, 1, 0), v3s16(0, 0, 1), v3s16(1, 0, 0), v3s16(0, 0, -1), + v3s16(-1, 0, 0), v3s16(0, -1, 0)}; -enum NeighborType : u8 { +enum NeighborType : u8 +{ NEIGHBOR_UPPER, NEIGHBOR_SAME_LEVEL, NEIGHBOR_LOWER }; -struct NodeNeighbor { +struct NodeNeighbor +{ MapNode n; NeighborType t; v3s16 p; - NodeNeighbor() - : n(CONTENT_AIR), t(NEIGHBOR_SAME_LEVEL) - { } + NodeNeighbor() : n(CONTENT_AIR), t(NEIGHBOR_SAME_LEVEL) {} - NodeNeighbor(const MapNode &node, NeighborType n_type, const v3s16 &pos) - : n(node), - t(n_type), - p(pos) - { } + NodeNeighbor(const MapNode &node, NeighborType n_type, const v3s16 &pos) : + n(node), t(n_type), p(pos) + { + } }; -void Map::transforming_liquid_add(v3s16 p) { - m_transforming_liquid.push_back(p); +void Map::transforming_liquid_add(v3s16 p) +{ + m_transforming_liquid.push_back(p); } -void Map::transformLiquids(std::map &modified_blocks, - ServerEnvironment *env) +void Map::transformLiquids( + std::map &modified_blocks, ServerEnvironment *env) { u32 loopcount = 0; u32 initial_size = m_transforming_liquid.size(); /*if(initial_size != 0) - infostream<<"transformLiquids(): initial_size="< &modified_blocks, loop_max *= m_transforming_liquid_loop_count_multiplier; #endif - while (m_transforming_liquid.size() != 0) - { + while (m_transforming_liquid.size() != 0) { // This should be done here so that it is done when continue is used if (loopcount >= initial_size || loopcount >= loop_max) break; @@ -576,22 +568,22 @@ void Map::transformLiquids(std::map &modified_blocks, const ContentFeatures &cf = m_nodedef->get(n0); LiquidType liquid_type = cf.liquid_type; switch (liquid_type) { - case LIQUID_SOURCE: - liquid_level = LIQUID_LEVEL_SOURCE; - liquid_kind = cf.liquid_alternative_flowing_id; - break; - case LIQUID_FLOWING: - liquid_level = (n0.param2 & LIQUID_LEVEL_MASK); - liquid_kind = n0.getContent(); - break; - case LIQUID_NONE: - // if this node is 'floodable', it *could* be transformed - // into a liquid, otherwise, continue with the next node. - if (!cf.floodable) - continue; - floodable_node = n0.getContent(); - liquid_kind = CONTENT_AIR; - break; + case LIQUID_SOURCE: + liquid_level = LIQUID_LEVEL_SOURCE; + liquid_kind = cf.liquid_alternative_flowing_id; + break; + case LIQUID_FLOWING: + liquid_level = (n0.param2 & LIQUID_LEVEL_MASK); + liquid_kind = n0.getContent(); + break; + case LIQUID_NONE: + // if this node is 'floodable', it *could* be transformed + // into a liquid, otherwise, continue with the next node. + if (!cf.floodable) + continue; + floodable_node = n0.getContent(); + liquid_kind = CONTENT_AIR; + break; } /* @@ -603,78 +595,88 @@ void Map::transformLiquids(std::map &modified_blocks, int num_flows = 0; NodeNeighbor airs[6]; // surrounding air int num_airs = 0; - NodeNeighbor neutrals[6]; // nodes that are solid or another kind of liquid + NodeNeighbor neutrals[6]; // nodes that are solid or another kind of + // liquid int num_neutrals = 0; bool flowing_down = false; bool ignored_sources = false; for (u16 i = 0; i < 6; i++) { NeighborType nt = NEIGHBOR_SAME_LEVEL; switch (i) { - case 0: - nt = NEIGHBOR_UPPER; - break; - case 5: - nt = NEIGHBOR_LOWER; - break; - default: - break; + case 0: + nt = NEIGHBOR_UPPER; + break; + case 5: + nt = NEIGHBOR_LOWER; + break; + default: + break; } v3s16 npos = p0 + liquid_6dirs[i]; NodeNeighbor nb(getNode(npos), nt, npos); const ContentFeatures &cfnb = m_nodedef->get(nb.n); switch (m_nodedef->get(nb.n.getContent()).liquid_type) { - case LIQUID_NONE: - if (cfnb.floodable) { - airs[num_airs++] = nb; - // if the current node is a water source the neighbor - // should be enqueded for transformation regardless of whether the - // current node changes or not. - if (nb.t != NEIGHBOR_UPPER && liquid_type != LIQUID_NONE) - m_transforming_liquid.push_back(npos); - // if the current node happens to be a flowing node, it will start to flow down here. + case LIQUID_NONE: + if (cfnb.floodable) { + airs[num_airs++] = nb; + // if the current node is a water source the + // neighbor should be enqueded for transformation + // regardless of whether the current node changes + // or not. + if (nb.t != NEIGHBOR_UPPER && + liquid_type != LIQUID_NONE) + m_transforming_liquid.push_back(npos); + // if the current node happens to be a flowing + // node, it will start to flow down here. + if (nb.t == NEIGHBOR_LOWER) + flowing_down = true; + } else { + neutrals[num_neutrals++] = nb; + if (nb.n.getContent() == CONTENT_IGNORE) { + // If node below is ignore prevent water + // from spreading outwards and otherwise + // prevent from flowing away as ignore + // node might be the source if (nb.t == NEIGHBOR_LOWER) flowing_down = true; - } else { - neutrals[num_neutrals++] = nb; - if (nb.n.getContent() == CONTENT_IGNORE) { - // If node below is ignore prevent water from - // spreading outwards and otherwise prevent from - // flowing away as ignore node might be the source - if (nb.t == NEIGHBOR_LOWER) - flowing_down = true; - else - ignored_sources = true; - } + else + ignored_sources = true; } - break; - case LIQUID_SOURCE: - // if this node is not (yet) of a liquid type, choose the first liquid type we encounter + } + break; + case LIQUID_SOURCE: + // if this node is not (yet) of a liquid type, choose the + // first liquid type we encounter + if (liquid_kind == CONTENT_AIR) + liquid_kind = cfnb.liquid_alternative_flowing_id; + if (cfnb.liquid_alternative_flowing_id != liquid_kind) { + neutrals[num_neutrals++] = nb; + } else { + // Do not count bottom source, it will screw + // things up + if (nt != NEIGHBOR_LOWER) + sources[num_sources++] = nb; + } + break; + case LIQUID_FLOWING: + if (nb.t != NEIGHBOR_SAME_LEVEL || + (nb.n.param2 & LIQUID_FLOW_DOWN_MASK) != + LIQUID_FLOW_DOWN_MASK) { + // if this node is not (yet) of a liquid type, + // choose the first liquid type we encounter but + // exclude falling liquids on the same level, they + // cannot flow here anyway if (liquid_kind == CONTENT_AIR) liquid_kind = cfnb.liquid_alternative_flowing_id; - if (cfnb.liquid_alternative_flowing_id != liquid_kind) { - neutrals[num_neutrals++] = nb; - } else { - // Do not count bottom source, it will screw things up - if(nt != NEIGHBOR_LOWER) - sources[num_sources++] = nb; - } - break; - case LIQUID_FLOWING: - if (nb.t != NEIGHBOR_SAME_LEVEL || - (nb.n.param2 & LIQUID_FLOW_DOWN_MASK) != LIQUID_FLOW_DOWN_MASK) { - // if this node is not (yet) of a liquid type, choose the first liquid type we encounter - // but exclude falling liquids on the same level, they cannot flow here anyway - if (liquid_kind == CONTENT_AIR) - liquid_kind = cfnb.liquid_alternative_flowing_id; - } - if (cfnb.liquid_alternative_flowing_id != liquid_kind) { - neutrals[num_neutrals++] = nb; - } else { - flows[num_flows++] = nb; - if (nb.t == NEIGHBOR_LOWER) - flowing_down = true; - } - break; + } + if (cfnb.liquid_alternative_flowing_id != liquid_kind) { + neutrals[num_neutrals++] = nb; + } else { + flows[num_flows++] = nb; + if (nb.t == NEIGHBOR_LOWER) + flowing_down = true; + } + break; } } @@ -689,11 +691,15 @@ void Map::transformLiquids(std::map &modified_blocks, if (range > LIQUID_LEVEL_MAX + 1) range = LIQUID_LEVEL_MAX + 1; - if ((num_sources >= 2 && m_nodedef->get(liquid_kind).liquid_renewable) || liquid_type == LIQUID_SOURCE) { - // liquid_kind will be set to either the flowing alternative of the node (if it's a liquid) - // or the flowing alternative of the first of the surrounding sources (if it's air), so - // it's perfectly safe to use liquid_kind here to determine the new node content. - new_node_content = m_nodedef->get(liquid_kind).liquid_alternative_source_id; + if ((num_sources >= 2 && m_nodedef->get(liquid_kind).liquid_renewable) || + liquid_type == LIQUID_SOURCE) { + // liquid_kind will be set to either the flowing alternative of + // the node (if it's a liquid) or the flowing alternative of the + // first of the surrounding sources (if it's air), so it's + // perfectly safe to use liquid_kind here to determine the new + // node content. + new_node_content = m_nodedef->get(liquid_kind) + .liquid_alternative_source_id; } else if (num_sources >= 1 && sources[0].t != NEIGHBOR_LOWER) { // liquid_kind is set properly, see above max_node_level = new_node_level = LIQUID_LEVEL_MAX; @@ -707,26 +713,34 @@ void Map::transformLiquids(std::map &modified_blocks, new_node_level = liquid_level; new_node_content = liquid_kind; } else { - // no surrounding sources, so get the maximum level that can flow into this node + // no surrounding sources, so get the maximum level that can flow + // into this node for (u16 i = 0; i < num_flows; i++) { - u8 nb_liquid_level = (flows[i].n.param2 & LIQUID_LEVEL_MASK); + u8 nb_liquid_level = + (flows[i].n.param2 & LIQUID_LEVEL_MASK); switch (flows[i].t) { - case NEIGHBOR_UPPER: - if (nb_liquid_level + WATER_DROP_BOOST > max_node_level) { - max_node_level = LIQUID_LEVEL_MAX; - if (nb_liquid_level + WATER_DROP_BOOST < LIQUID_LEVEL_MAX) - max_node_level = nb_liquid_level + WATER_DROP_BOOST; - } else if (nb_liquid_level > max_node_level) { - max_node_level = nb_liquid_level; - } - break; - case NEIGHBOR_LOWER: - break; - case NEIGHBOR_SAME_LEVEL: - if ((flows[i].n.param2 & LIQUID_FLOW_DOWN_MASK) != LIQUID_FLOW_DOWN_MASK && - nb_liquid_level > 0 && nb_liquid_level - 1 > max_node_level) - max_node_level = nb_liquid_level - 1; - break; + case NEIGHBOR_UPPER: + if (nb_liquid_level + WATER_DROP_BOOST > + max_node_level) { + max_node_level = LIQUID_LEVEL_MAX; + if (nb_liquid_level + WATER_DROP_BOOST < + LIQUID_LEVEL_MAX) + max_node_level = nb_liquid_level + + WATER_DROP_BOOST; + } else if (nb_liquid_level > max_node_level) { + max_node_level = nb_liquid_level; + } + break; + case NEIGHBOR_LOWER: + break; + case NEIGHBOR_SAME_LEVEL: + if ((flows[i].n.param2 & LIQUID_FLOW_DOWN_MASK) != + LIQUID_FLOW_DOWN_MASK && + nb_liquid_level > 0 && + nb_liquid_level - 1 > + max_node_level) + max_node_level = nb_liquid_level - 1; + break; } } @@ -736,7 +750,8 @@ void Map::transformLiquids(std::map &modified_blocks, // must be at least 1 in absolute value s8 level_inc = max_node_level - liquid_level; if (level_inc < -viscosity || level_inc > viscosity) - new_node_level = liquid_level + level_inc/viscosity; + new_node_level = liquid_level + + level_inc / viscosity; else if (level_inc < 0) new_node_level = liquid_level - 1; else if (level_inc > 0) @@ -751,28 +766,32 @@ void Map::transformLiquids(std::map &modified_blocks, new_node_content = liquid_kind; else new_node_content = floodable_node; - } /* - check if anything has changed. if not, just continue with the next node. + check if anything has changed. if not, just continue with the next + node. */ if (new_node_content == n0.getContent() && - (m_nodedef->get(n0.getContent()).liquid_type != LIQUID_FLOWING || - ((n0.param2 & LIQUID_LEVEL_MASK) == (u8)new_node_level && - ((n0.param2 & LIQUID_FLOW_DOWN_MASK) == LIQUID_FLOW_DOWN_MASK) - == flowing_down))) + (m_nodedef->get(n0.getContent()).liquid_type != + LIQUID_FLOWING || + ((n0.param2 & LIQUID_LEVEL_MASK) == + (u8)new_node_level && + ((n0.param2 & LIQUID_FLOW_DOWN_MASK) == + LIQUID_FLOW_DOWN_MASK) == + flowing_down))) continue; - /* update the current node */ MapNode n00 = n0; - //bool flow_down_enabled = (flowing_down && ((n0.param2 & LIQUID_FLOW_DOWN_MASK) != LIQUID_FLOW_DOWN_MASK)); + // bool flow_down_enabled = (flowing_down && ((n0.param2 & + // LIQUID_FLOW_DOWN_MASK) != LIQUID_FLOW_DOWN_MASK)); if (m_nodedef->get(new_node_content).liquid_type == LIQUID_FLOWING) { // set level to last 3 bits, flowing down bit to 4th bit - n0.param2 = (flowing_down ? LIQUID_FLOW_DOWN_MASK : 0x00) | (new_node_level & LIQUID_LEVEL_MASK); + n0.param2 = (flowing_down ? LIQUID_FLOW_DOWN_MASK : 0x00) | + (new_node_level & LIQUID_LEVEL_MASK); } else { // set the liquid level and flow bits to 0 n0.param2 &= ~(LIQUID_LEVEL_MASK | LIQUID_FLOW_DOWN_MASK); @@ -798,7 +817,8 @@ void Map::transformLiquids(std::map &modified_blocks, if (m_gamedef->rollback() && !suspect.empty()) { // Blame suspect - RollbackScopeActor rollback_scope(m_gamedef->rollback(), suspect, true); + RollbackScopeActor rollback_scope( + m_gamedef->rollback(), suspect, true); // Get old node for rollback RollbackNode rollback_oldnode(this, p0, m_gamedef); // Set node @@ -816,7 +836,7 @@ void Map::transformLiquids(std::map &modified_blocks, v3s16 blockpos = getNodeBlockPos(p0); MapBlock *block = getBlockNoCreateNoEx(blockpos); if (block != NULL) { - modified_blocks[blockpos] = block; + modified_blocks[blockpos] = block; changed_nodes.emplace_back(p0, n00); } @@ -824,31 +844,31 @@ void Map::transformLiquids(std::map &modified_blocks, enqueue neighbors for update if neccessary */ switch (m_nodedef->get(n0.getContent()).liquid_type) { - case LIQUID_SOURCE: - case LIQUID_FLOWING: - // make sure source flows into all neighboring nodes - for (u16 i = 0; i < num_flows; i++) - if (flows[i].t != NEIGHBOR_UPPER) - m_transforming_liquid.push_back(flows[i].p); - for (u16 i = 0; i < num_airs; i++) - if (airs[i].t != NEIGHBOR_UPPER) - m_transforming_liquid.push_back(airs[i].p); - break; - case LIQUID_NONE: - // this flow has turned to air; neighboring flows might need to do the same - for (u16 i = 0; i < num_flows; i++) + case LIQUID_SOURCE: + case LIQUID_FLOWING: + // make sure source flows into all neighboring nodes + for (u16 i = 0; i < num_flows; i++) + if (flows[i].t != NEIGHBOR_UPPER) m_transforming_liquid.push_back(flows[i].p); - break; + for (u16 i = 0; i < num_airs; i++) + if (airs[i].t != NEIGHBOR_UPPER) + m_transforming_liquid.push_back(airs[i].p); + break; + case LIQUID_NONE: + // this flow has turned to air; neighboring flows might need to do + // the same + for (u16 i = 0; i < num_flows; i++) + m_transforming_liquid.push_back(flows[i].p); + break; } } - //infostream<<"Map::transformLiquids(): loopcount="< Map::findNodesWithMetadata(v3s16 p1, v3s16 p2) VoxelArea area(p1, p2); for (s16 z = bpmin.Z; z <= bpmax.Z; z++) - for (s16 y = bpmin.Y; y <= bpmax.Y; y++) - for (s16 x = bpmin.X; x <= bpmax.X; x++) { - v3s16 blockpos(x, y, z); + for (s16 y = bpmin.Y; y <= bpmax.Y; y++) + for (s16 x = bpmin.X; x <= bpmax.X; x++) { + v3s16 blockpos(x, y, z); - MapBlock *block = getBlockNoCreateNoEx(blockpos); - if (!block) { - verbosestream << "Map::getNodeMetadata(): Need to emerge " - << PP(blockpos) << std::endl; - block = emergeBlock(blockpos, false); - } - if (!block) { - infostream << "WARNING: Map::getNodeMetadata(): Block not found" - << std::endl; - continue; - } + MapBlock *block = getBlockNoCreateNoEx(blockpos); + if (!block) { + verbosestream << "Map::getNodeMetadata(): Need " + "to emerge " + << PP(blockpos) << std::endl; + block = emergeBlock(blockpos, false); + } + if (!block) { + infostream << "WARNING: Map::getNodeMetadata(): " + "Block not found" + << std::endl; + continue; + } - v3s16 p_base = blockpos * MAP_BLOCKSIZE; - std::vector keys = block->m_node_metadata.getAllKeys(); - for (size_t i = 0; i != keys.size(); i++) { - v3s16 p(keys[i] + p_base); - if (!area.contains(p)) - continue; + v3s16 p_base = blockpos * MAP_BLOCKSIZE; + std::vector keys = + block->m_node_metadata.getAllKeys(); + for (size_t i = 0; i != keys.size(); i++) { + v3s16 p(keys[i] + p_base); + if (!area.contains(p)) + continue; - positions_with_meta.push_back(p); - } - } + positions_with_meta.push_back(p); + } + } return positions_with_meta; } @@ -942,16 +966,15 @@ std::vector Map::findNodesWithMetadata(v3s16 p1, v3s16 p2) NodeMetadata *Map::getNodeMetadata(v3s16 p) { v3s16 blockpos = getNodeBlockPos(p); - v3s16 p_rel = p - blockpos*MAP_BLOCKSIZE; + v3s16 p_rel = p - blockpos * MAP_BLOCKSIZE; MapBlock *block = getBlockNoCreateNoEx(blockpos); - if(!block){ - infostream<<"Map::getNodeMetadata(): Need to emerge " - <m_node_metadata.get(p_rel); @@ -961,16 +984,15 @@ NodeMetadata *Map::getNodeMetadata(v3s16 p) bool Map::setNodeMetadata(v3s16 p, NodeMetadata *meta) { v3s16 blockpos = getNodeBlockPos(p); - v3s16 p_rel = p - blockpos*MAP_BLOCKSIZE; + v3s16 p_rel = p - blockpos * MAP_BLOCKSIZE; MapBlock *block = getBlockNoCreateNoEx(blockpos); - if(!block){ - infostream<<"Map::setNodeMetadata(): Need to emerge " - <m_node_metadata.set(p_rel, meta); @@ -980,12 +1002,11 @@ bool Map::setNodeMetadata(v3s16 p, NodeMetadata *meta) void Map::removeNodeMetadata(v3s16 p) { v3s16 blockpos = getNodeBlockPos(p); - v3s16 p_rel = p - blockpos*MAP_BLOCKSIZE; + v3s16 p_rel = p - blockpos * MAP_BLOCKSIZE; MapBlock *block = getBlockNoCreateNoEx(blockpos); - if(block == NULL) - { - warningstream<<"Map::removeNodeMetadata(): Block not found" - <m_node_metadata.remove(p_rel); @@ -994,16 +1015,15 @@ void Map::removeNodeMetadata(v3s16 p) NodeTimer Map::getNodeTimer(v3s16 p) { v3s16 blockpos = getNodeBlockPos(p); - v3s16 p_rel = p - blockpos*MAP_BLOCKSIZE; + v3s16 p_rel = p - blockpos * MAP_BLOCKSIZE; MapBlock *block = getBlockNoCreateNoEx(blockpos); - if(!block){ - infostream<<"Map::getNodeTimer(): Need to emerge " - <m_node_timers.get(p_rel); @@ -1015,16 +1035,15 @@ void Map::setNodeTimer(const NodeTimer &t) { v3s16 p = t.position; v3s16 blockpos = getNodeBlockPos(p); - v3s16 p_rel = p - blockpos*MAP_BLOCKSIZE; + v3s16 p_rel = p - blockpos * MAP_BLOCKSIZE; MapBlock *block = getBlockNoCreateNoEx(blockpos); - if(!block){ - infostream<<"Map::setNodeTimer(): Need to emerge " - <m_node_timers.remove(p_rel); } bool Map::determineAdditionalOcclusionCheck(const v3s16 &pos_camera, - const core::aabbox3d &block_bounds, v3s16 &check) + const core::aabbox3d &block_bounds, v3s16 &check) { /* This functions determines the node inside the target block that is @@ -1058,9 +1075,9 @@ bool Map::determineAdditionalOcclusionCheck(const v3s16 &pos_camera, - the closest node is a corner, corners are checked anyway. - the camera is inside the target block, it will never be occluded. */ -#define CLOSEST_EDGE(pos, bounds, axis) \ - ((pos).axis <= (bounds).MinEdge.axis) ? (bounds).MinEdge.axis : \ - (bounds).MaxEdge.axis +#define CLOSEST_EDGE(pos, bounds, axis) \ + ((pos).axis <= (bounds).MinEdge.axis) ? (bounds).MinEdge.axis \ + : (bounds).MaxEdge.axis bool x_inside = (block_bounds.MinEdge.X <= pos_camera.X) && (pos_camera.X <= block_bounds.MaxEdge.X); @@ -1109,8 +1126,8 @@ bool Map::determineAdditionalOcclusionCheck(const v3s16 &pos_camera, return false; } -bool Map::isOccluded(const v3s16 &pos_camera, const v3s16 &pos_target, - float step, float stepfac, float offset, float end_offset, u32 needed_count) +bool Map::isOccluded(const v3s16 &pos_camera, const v3s16 &pos_target, float step, + float stepfac, float offset, float end_offset, u32 needed_count) { v3f direction = intToFloat(pos_target - pos_camera, BS); float distance = direction.getLength(); @@ -1129,8 +1146,7 @@ bool Map::isOccluded(const v3s16 &pos_camera, const v3s16 &pos_target, MapNode node = getNode(pos_node, &is_valid_position); - if (is_valid_position && - !m_nodedef->get(node).light_propagates) { + if (is_valid_position && !m_nodedef->get(node).light_propagates) { // Cannot see through light-blocking nodes --> occluded count++; if (count >= needed_count) @@ -1147,15 +1163,15 @@ bool Map::isBlockOccluded(MapBlock *block, v3s16 cam_pos_nodes) // Overshoot a little for less flickering static const s16 bs2 = MAP_BLOCKSIZE / 2 + 1; static const v3s16 dir9[9] = { - v3s16( 0, 0, 0), - v3s16( 1, 1, 1) * bs2, - v3s16( 1, 1, -1) * bs2, - v3s16( 1, -1, 1) * bs2, - v3s16( 1, -1, -1) * bs2, - v3s16(-1, 1, 1) * bs2, - v3s16(-1, 1, -1) * bs2, - v3s16(-1, -1, 1) * bs2, - v3s16(-1, -1, -1) * bs2, + v3s16(0, 0, 0), + v3s16(1, 1, 1) * bs2, + v3s16(1, 1, -1) * bs2, + v3s16(1, -1, 1) * bs2, + v3s16(1, -1, -1) * bs2, + v3s16(-1, 1, 1) * bs2, + v3s16(-1, 1, -1) * bs2, + v3s16(-1, -1, 1) * bs2, + v3s16(-1, -1, -1) * bs2, }; v3s16 pos_blockcenter = block->getPosRelative() + (MAP_BLOCKSIZE / 2); @@ -1183,14 +1199,14 @@ bool Map::isBlockOccluded(MapBlock *block, v3s16 cam_pos_nodes) v3s16 check; if (determineAdditionalOcclusionCheck(cam_pos_nodes, block->getBox(), check)) { // node is always on a side facing the camera, end_offset can be lower - if (!isOccluded(cam_pos_nodes, check, step, stepfac, start_offset, - -1.0f, needed_count)) + if (!isOccluded(cam_pos_nodes, check, step, stepfac, start_offset, -1.0f, + needed_count)) return false; } for (const v3s16 &dir : dir9) { if (!isOccluded(cam_pos_nodes, pos_blockcenter + dir, step, stepfac, - start_offset, end_offset, needed_count)) + start_offset, end_offset, needed_count)) return false; } return true; @@ -1199,13 +1215,13 @@ bool Map::isBlockOccluded(MapBlock *block, v3s16 cam_pos_nodes) /* ServerMap */ -ServerMap::ServerMap(const std::string &savedir, IGameDef *gamedef, - EmergeManager *emerge, MetricsBackend *mb): - Map(dout_server, gamedef), - settings_mgr(g_settings, savedir + DIR_DELIM + "map_meta.txt"), - m_emerge(emerge) +ServerMap::ServerMap(const std::string &savedir, IGameDef *gamedef, EmergeManager *emerge, + MetricsBackend *mb) : + Map(dout_server, gamedef), + settings_mgr(g_settings, savedir + DIR_DELIM + "map_meta.txt"), + m_emerge(emerge) { - verbosestream<map_settings_mgr = &settings_mgr; @@ -1226,35 +1242,40 @@ ServerMap::ServerMap(const std::string &savedir, IGameDef *gamedef, dbase = createDatabase(backend, savedir, conf); if (conf.exists("readonly_backend")) { std::string readonly_dir = savedir + DIR_DELIM + "readonly"; - dbase_ro = createDatabase(conf.get("readonly_backend"), readonly_dir, conf); + dbase_ro = createDatabase( + conf.get("readonly_backend"), readonly_dir, conf); } if (!conf.updateConfigFile(conf_path.c_str())) - errorstream << "ServerMap::ServerMap(): Failed to update world.mt!" << std::endl; + errorstream << "ServerMap::ServerMap(): Failed to update world.mt!" + << std::endl; m_savedir = savedir; m_map_saving_enabled = false; - m_save_time_counter = mb->addCounter("minetest_core_map_save_time", "Map save time (in nanoseconds)"); + m_save_time_counter = mb->addCounter( + "minetest_core_map_save_time", "Map save time (in nanoseconds)"); try { // If directory exists, check contents and load if possible if (fs::PathExists(m_savedir)) { // If directory is empty, it is safe to save into it. if (fs::GetDirListing(m_savedir).empty()) { - infostream<<"ServerMap: Empty save directory is valid." - <mapgen_limit, 0, MAX_MAP_GENERATION_LIMIT) / - MAP_BLOCKSIZE; - return p.X < -mapgen_limit_bp || - p.X > mapgen_limit_bp || - p.Y < -mapgen_limit_bp || - p.Y > mapgen_limit_bp || - p.Z < -mapgen_limit_bp || - p.Z > mapgen_limit_bp; + const s16 mapgen_limit_bp = rangelim(getMapgenParams()->mapgen_limit, 0, + MAX_MAP_GENERATION_LIMIT) / + MAP_BLOCKSIZE; + return p.X < -mapgen_limit_bp || p.X > mapgen_limit_bp || + p.Y < -mapgen_limit_bp || p.Y > mapgen_limit_bp || + p.Z < -mapgen_limit_bp || p.Z > mapgen_limit_bp; } bool ServerMap::initBlockMake(v3s16 blockpos, BlockMakeData *data) @@ -1373,26 +1387,26 @@ bool ServerMap::initBlockMake(v3s16 blockpos, BlockMakeData *data) Create the whole area of this and the neighboring blocks */ for (s16 x = full_bpmin.X; x <= full_bpmax.X; x++) - for (s16 z = full_bpmin.Z; z <= full_bpmax.Z; z++) { - v2s16 sectorpos(x, z); - // Sector metadata is loaded from disk if not already loaded. - MapSector *sector = createSector(sectorpos); - FATAL_ERROR_IF(sector == NULL, "createSector() failed"); + for (s16 z = full_bpmin.Z; z <= full_bpmax.Z; z++) { + v2s16 sectorpos(x, z); + // Sector metadata is loaded from disk if not already loaded. + MapSector *sector = createSector(sectorpos); + FATAL_ERROR_IF(sector == NULL, "createSector() failed"); - for (s16 y = full_bpmin.Y; y <= full_bpmax.Y; y++) { - v3s16 p(x, y, z); + for (s16 y = full_bpmin.Y; y <= full_bpmax.Y; y++) { + v3s16 p(x, y, z); - MapBlock *block = emergeBlock(p, false); - if (block == NULL) { - block = createBlock(p); + MapBlock *block = emergeBlock(p, false); + if (block == NULL) { + block = createBlock(p); - // Block gets sunlight if this is true. - // Refer to the map generator heuristics. - bool ug = m_emerge->isBlockUnderground(p); - block->setIsUnderground(ug); + // Block gets sunlight if this is true. + // Refer to the map generator heuristics. + bool ug = m_emerge->isBlockUnderground(p); + block->setIsUnderground(ug); + } } } - } /* Now we have a big empty area. @@ -1427,8 +1441,8 @@ bool ServerMap::initBlockMake(v3s16 blockpos, BlockMakeData *data) return true; } -void ServerMap::finishBlockMake(BlockMakeData *data, - std::map *changed_blocks) +void ServerMap::finishBlockMake( + BlockMakeData *data, std::map *changed_blocks) { v3s16 bpmin = data->blockpos_min; v3s16 bpmax = data->blockpos_max; @@ -1445,7 +1459,7 @@ void ServerMap::finishBlockMake(BlockMakeData *data, data->vmanip->blitBackAll(changed_blocks); EMERGE_DBG_OUT("finishBlockMake: changed_blocks.size()=" - << changed_blocks->size()); + << changed_blocks->size()); /* Copy transforming liquid information @@ -1466,28 +1480,28 @@ void ServerMap::finishBlockMake(BlockMakeData *data, /* Set block as modified */ - block->raiseModified(MOD_STATE_WRITE_NEEDED, - MOD_REASON_EXPIRE_DAYNIGHTDIFF); + block->raiseModified( + MOD_STATE_WRITE_NEEDED, MOD_REASON_EXPIRE_DAYNIGHTDIFF); } /* Set central blocks as generated */ for (s16 x = bpmin.X; x <= bpmax.X; x++) - for (s16 z = bpmin.Z; z <= bpmax.Z; z++) - for (s16 y = bpmin.Y; y <= bpmax.Y; y++) { - MapBlock *block = getBlockNoCreateNoEx(v3s16(x, y, z)); - if (!block) - continue; + for (s16 z = bpmin.Z; z <= bpmax.Z; z++) + for (s16 y = bpmin.Y; y <= bpmax.Y; y++) { + MapBlock *block = getBlockNoCreateNoEx(v3s16(x, y, z)); + if (!block) + continue; - block->setGenerated(true); - } + block->setGenerated(true); + } /* Save changed parts of map NOTE: Will be saved later. */ - //save(MOD_STATE_WRITE_AT_UNLOAD); + // save(MOD_STATE_WRITE_AT_UNLOAD); } MapSector *ServerMap::createSector(v2s16 p2d) @@ -1503,11 +1517,10 @@ MapSector *ServerMap::createSector(v2s16 p2d) Do not create over max mapgen limit */ const s16 max_limit_bp = MAX_MAP_GENERATION_LIMIT / MAP_BLOCKSIZE; - if (p2d.X < -max_limit_bp || - p2d.X > max_limit_bp || - p2d.Y < -max_limit_bp || - p2d.Y > max_limit_bp) - throw InvalidPositionException("createSector(): pos. over max mapgen limit"); + if (p2d.X < -max_limit_bp || p2d.X > max_limit_bp || p2d.Y < -max_limit_bp || + p2d.Y > max_limit_bp) + throw InvalidPositionException( + "createSector(): pos. over max mapgen limit"); /* Generate blank sector @@ -1516,7 +1529,7 @@ MapSector *ServerMap::createSector(v2s16 p2d) sector = new MapSector(this, p2d, m_gamedef); // Sector position on map in nodes - //v2s16 nodepos2d = p2d * MAP_BLOCKSIZE; + // v2s16 nodepos2d = p2d * MAP_BLOCKSIZE; /* Insert to container @@ -1636,13 +1649,14 @@ MapBlock * ServerMap::generateBlock( } #endif -MapBlock * ServerMap::createBlock(v3s16 p) +MapBlock *ServerMap::createBlock(v3s16 p) { /* Do not create over max mapgen limit */ if (blockpos_over_max_limit(p)) - throw InvalidPositionException("createBlock(): pos. over max mapgen limit"); + throw InvalidPositionException( + "createBlock(): pos. over max mapgen limit"); v2s16 p2d(p.X, p.Z); s16 block_y = p.Y; @@ -1657,7 +1671,7 @@ MapBlock * ServerMap::createBlock(v3s16 p) try { sector = createSector(p2d); } catch (InvalidPositionException &e) { - infostream<<"createBlock: createSector() failed"<getBlockNoCreateNoEx(block_y); if (block) { - if(block->isDummy()) + if (block->isDummy()) block->unDummify(); return block; } @@ -1677,7 +1691,7 @@ MapBlock * ServerMap::createBlock(v3s16 p) return block; } -MapBlock * ServerMap::emergeBlock(v3s16 p, bool create_blank) +MapBlock *ServerMap::emergeBlock(v3s16 p, bool create_blank) { { MapBlock *block = getBlockNoCreateNoEx(p); @@ -1687,7 +1701,7 @@ MapBlock * ServerMap::emergeBlock(v3s16 p, bool create_blank) { MapBlock *block = loadBlock(p); - if(block) + if (block) return block; } @@ -1772,15 +1786,15 @@ plan_b: s16 level = m_emerge->getGroundLevelAtPoint(p2d); return level; - //double level = base_rock_level_2d(m_seed, p2d) + AVERAGE_MUD_AMOUNT; - //return (s16)level; + // double level = base_rock_level_2d(m_seed, p2d) + AVERAGE_MUD_AMOUNT; + // return (s16)level; } void ServerMap::createDirs(const std::string &path) { if (!fs::CreateAllDirs(path)) { - m_dout<<"ServerMap: Failed to create directory " - <<"\""<getModified() >= (u32)save_level) { + if (block->getModified() >= (u32)save_level) { // Lazy beginSave() - if(!save_started) { + if (!save_started) { beginSave(); save_started = true; } @@ -1836,20 +1850,18 @@ void ServerMap::save(ModifiedState save_level) } } - if(save_started) + if (save_started) endSave(); /* Only print if something happened or saved whole map */ - if(save_level == MOD_STATE_CLEAN - || block_count != 0) { - infostream << "ServerMap: Written: " - << block_count << " blocks" - << ", " << block_count_all << " blocks in memory." - << std::endl; + if (save_level == MOD_STATE_CLEAN || block_count != 0) { + infostream << "ServerMap: Written: " << block_count << " blocks" + << ", " << block_count_all << " blocks in memory." + << std::endl; PrintInfo(infostream); // ServerMap/ClientMap: - infostream<<"Blocks modified by: "< &dst) } MapDatabase *ServerMap::createDatabase( - const std::string &name, - const std::string &savedir, - Settings &conf) + const std::string &name, const std::string &savedir, Settings &conf) { if (name == "sqlite3") return new MapDatabaseSQLite3(savedir); if (name == "dummy") return new Database_Dummy(); - #if USE_LEVELDB +#if USE_LEVELDB if (name == "leveldb") return new Database_LevelDB(savedir); - #endif - #if USE_REDIS +#endif +#if USE_REDIS if (name == "redis") return new Database_Redis(conf); - #endif - #if USE_POSTGRESQL +#endif +#if USE_POSTGRESQL if (name == "postgresql") { std::string connect_string; conf.getNoEx("pgsql_connection", connect_string); return new MapDatabasePostgreSQL(connect_string); } - #endif +#endif throw BaseException(std::string("Database backend ") + name + " not supported."); } @@ -1928,8 +1938,8 @@ bool ServerMap::saveBlock(MapBlock *block, MapDatabase *db) // Dummy blocks are not written if (block->isDummy()) { - warningstream << "saveBlock: Not writing dummy block " - << PP(p3d) << std::endl; + warningstream << "saveBlock: Not writing dummy block " << PP(p3d) + << std::endl; return true; } @@ -1941,7 +1951,7 @@ bool ServerMap::saveBlock(MapBlock *block, MapDatabase *db) [1] data */ std::ostringstream o(std::ios_base::binary); - o.write((char*) &version, 1); + o.write((char *)&version, 1); block->serialize(o, version, true); bool ret = db->saveBlock(p3d, o.str()); @@ -1952,23 +1962,23 @@ bool ServerMap::saveBlock(MapBlock *block, MapDatabase *db) return ret; } -void ServerMap::loadBlock(std::string *blob, v3s16 p3d, MapSector *sector, bool save_after_load) +void ServerMap::loadBlock( + std::string *blob, v3s16 p3d, MapSector *sector, bool save_after_load) { try { std::istringstream is(*blob, std::ios_base::binary); u8 version = SER_FMT_VER_INVALID; - is.read((char*)&version, 1); + is.read((char *)&version, 1); - if(is.fail()) + if (is.fail()) throw SerializationError("ServerMap::loadBlock(): Failed" - " to read MapBlock version"); + " to read MapBlock version"); MapBlock *block = NULL; bool created_new = false; block = sector->getBlockNoCreateNoEx(p3d.Y); - if(block == NULL) - { + if (block == NULL) { block = sector->createBlankBlockNoInsert(p3d.Y); created_new = true; } @@ -1987,33 +1997,31 @@ void ServerMap::loadBlock(std::string *blob, v3s16 p3d, MapSector *sector, bool Save blocks loaded in old format in new format */ - //if(version < SER_FMT_VER_HIGHEST_READ || save_after_load) + // if(version < SER_FMT_VER_HIGHEST_READ || save_after_load) // Only save if asked to; no need to update version - if(save_after_load) + if (save_after_load) saveBlock(block); // We just loaded it from, so it's up-to-date. block->resetModified(); - } - catch(SerializationError &e) - { - errorstream<<"Invalid block data in database" - <<" ("<getBool("ignore_world_load_errors")){ - errorstream<<"Ignoring block load error. Duck and cover! " - <<"(ignore_world_load_errors)"<getBool("ignore_world_load_errors")) { + errorstream << "Ignoring block load error. Duck and cover! " + << "(ignore_world_load_errors)" << std::endl; } else { throw SerializationError("Invalid block data in database"); } } } -MapBlock* ServerMap::loadBlock(v3s16 blockpos) +MapBlock *ServerMap::loadBlock(v3s16 blockpos) { bool created_new = (getBlockNoCreateNoEx(blockpos) == NULL); @@ -2034,16 +2042,16 @@ MapBlock* ServerMap::loadBlock(v3s16 blockpos) MapBlock *block = getBlockNoCreateNoEx(blockpos); if (created_new && (block != NULL)) { - std::map modified_blocks; + std::map modified_blocks; // Fix lighting if necessary voxalgo::update_block_border_lighting(this, block, modified_blocks); if (!modified_blocks.empty()) { - //Modified lighting, send event + // Modified lighting, send event MapEditEvent event; event.type = MEET_OTHER; std::map::iterator it; - for (it = modified_blocks.begin(); - it != modified_blocks.end(); ++it) + for (it = modified_blocks.begin(); it != modified_blocks.end(); + ++it) event.modified_blocks.insert(it->first); dispatchEvent(event); } @@ -2070,11 +2078,11 @@ bool ServerMap::deleteBlock(v3s16 blockpos) void ServerMap::PrintInfo(std::ostream &out) { - out<<"ServerMap: "; + out << "ServerMap: "; } -bool ServerMap::repairBlockLight(v3s16 blockpos, - std::map *modified_blocks) +bool ServerMap::repairBlockLight( + v3s16 blockpos, std::map *modified_blocks) { MapBlock *block = emergeBlock(blockpos, false); if (!block || !block->isGenerated()) @@ -2083,14 +2091,12 @@ bool ServerMap::repairBlockLight(v3s16 blockpos, return true; } -MMVManip::MMVManip(Map *map): - VoxelManipulator(), - m_map(map) +MMVManip::MMVManip(Map *map) : VoxelManipulator(), m_map(map) { } -void MMVManip::initialEmerge(v3s16 blockpos_min, v3s16 blockpos_max, - bool load_if_inexistent) +void MMVManip::initialEmerge( + v3s16 blockpos_min, v3s16 blockpos_max, bool load_if_inexistent) { TimeTaker timer1("initialEmerge", &emerge_time); @@ -2098,84 +2104,92 @@ void MMVManip::initialEmerge(v3s16 blockpos_min, v3s16 blockpos_max, v3s16 p_min = blockpos_min; v3s16 p_max = blockpos_max; - VoxelArea block_area_nodes - (p_min*MAP_BLOCKSIZE, (p_max+1)*MAP_BLOCKSIZE-v3s16(1,1,1)); + VoxelArea block_area_nodes(p_min * MAP_BLOCKSIZE, + (p_max + 1) * MAP_BLOCKSIZE - v3s16(1, 1, 1)); - u32 size_MB = block_area_nodes.getVolume()*4/1000000; - if(size_MB >= 1) - { - infostream<<"initialEmerge: area: "; + u32 size_MB = block_area_nodes.getVolume() * 4 / 1000000; + if (size_MB >= 1) { + infostream << "initialEmerge: area: "; block_area_nodes.print(infostream); - infostream<<" ("<::iterator n; - n = m_loaded_blocks.find(p); - if(n != m_loaded_blocks.end()) - continue; + for (s32 z = p_min.Z; z <= p_max.Z; z++) + for (s32 y = p_min.Y; y <= p_max.Y; y++) + for (s32 x = p_min.X; x <= p_max.X; x++) { + u8 flags = 0; + MapBlock *block; + v3s16 p(x, y, z); + std::map::iterator n; + n = m_loaded_blocks.find(p); + if (n != m_loaded_blocks.end()) + continue; - bool block_data_inexistent = false; - { - TimeTaker timer2("emerge load", &emerge_load_time); - - block = m_map->getBlockNoCreateNoEx(p); - if (!block || block->isDummy()) - block_data_inexistent = true; - else - block->copyTo(*this); - } - - if(block_data_inexistent) - { - - if (load_if_inexistent && !blockpos_over_max_limit(p)) { - ServerMap *svrmap = (ServerMap *)m_map; - block = svrmap->emergeBlock(p, false); - if (block == NULL) - block = svrmap->createBlock(p); - block->copyTo(*this); - } else { - flags |= VMANIP_BLOCK_DATA_INEXIST; - - /* - Mark area inexistent - */ - VoxelArea a(p*MAP_BLOCKSIZE, (p+1)*MAP_BLOCKSIZE-v3s16(1,1,1)); - // Fill with VOXELFLAG_NO_DATA - for(s32 z=a.MinEdge.Z; z<=a.MaxEdge.Z; z++) - for(s32 y=a.MinEdge.Y; y<=a.MaxEdge.Y; y++) + bool block_data_inexistent = false; { - s32 i = m_area.index(a.MinEdge.X,y,z); - memset(&m_flags[i], VOXELFLAG_NO_DATA, MAP_BLOCKSIZE); - } - } - } - /*else if (block->getNode(0, 0, 0).getContent() == CONTENT_IGNORE) - { - // Mark that block was loaded as blank - flags |= VMANIP_BLOCK_CONTAINS_CIGNORE; - }*/ + TimeTaker timer2( + "emerge load", &emerge_load_time); - m_loaded_blocks[p] = flags; - } + block = m_map->getBlockNoCreateNoEx(p); + if (!block || block->isDummy()) + block_data_inexistent = true; + else + block->copyTo(*this); + } + + if (block_data_inexistent) { + + if (load_if_inexistent && + !blockpos_over_max_limit(p)) { + ServerMap *svrmap = (ServerMap *)m_map; + block = svrmap->emergeBlock(p, false); + if (block == NULL) + block = svrmap->createBlock(p); + block->copyTo(*this); + } else { + flags |= VMANIP_BLOCK_DATA_INEXIST; + + /* + Mark area inexistent + */ + VoxelArea a(p * MAP_BLOCKSIZE, + (p + 1) * MAP_BLOCKSIZE - + v3s16(1, 1, 1)); + // Fill with VOXELFLAG_NO_DATA + for (s32 z = a.MinEdge.Z; + z <= a.MaxEdge.Z; z++) + for (s32 y = a.MinEdge.Y; + y <= a.MaxEdge.Y; + y++) { + s32 i = m_area.index( + a.MinEdge.X, + y, z); + memset(&m_flags[i], + VOXELFLAG_NO_DATA, + MAP_BLOCKSIZE); + } + } + } + /*else if (block->getNode(0, 0, 0).getContent() == + CONTENT_IGNORE) + { + // Mark that block was loaded as blank + flags |= VMANIP_BLOCK_CONTAINS_CIGNORE; + }*/ + + m_loaded_blocks[p] = flags; + } m_is_dirty = false; } -void MMVManip::blitBackAll(std::map *modified_blocks, - bool overwrite_generated) +void MMVManip::blitBackAll( + std::map *modified_blocks, bool overwrite_generated) { - if(m_area.getExtent() == v3s16(0,0,0)) + if (m_area.getExtent() == v3s16(0, 0, 0)) return; /* @@ -2186,15 +2200,15 @@ void MMVManip::blitBackAll(std::map *modified_blocks, MapBlock *block = m_map->getBlockNoCreateNoEx(p); bool existed = !(loaded_block.second & VMANIP_BLOCK_DATA_INEXIST); if (!existed || (block == NULL) || - (!overwrite_generated && block->isGenerated())) + (!overwrite_generated && block->isGenerated())) continue; block->copyFrom(*this); block->raiseModified(MOD_STATE_WRITE_NEEDED, MOD_REASON_VMANIP); - if(modified_blocks) + if (modified_blocks) (*modified_blocks)[p] = block; } } -//END +// END diff --git a/src/map.h b/src/map.h index 4d9847063..d3df05f7f 100644 --- a/src/map.h +++ b/src/map.h @@ -58,7 +58,8 @@ struct BlockMakeData; #define MAPTYPE_SERVER 1 #define MAPTYPE_CLIENT 2 -enum MapEditEventType{ +enum MapEditEventType +{ // Node added (changed from air or something else to something) MEET_ADDNODE, // Node removed (changed to air) @@ -83,25 +84,24 @@ struct MapEditEvent VoxelArea getArea() const { - switch(type){ + switch (type) { case MEET_ADDNODE: return VoxelArea(p); case MEET_REMOVENODE: return VoxelArea(p); case MEET_SWAPNODE: return VoxelArea(p); - case MEET_BLOCK_NODE_METADATA_CHANGED: - { - v3s16 np1 = p*MAP_BLOCKSIZE; - v3s16 np2 = np1 + v3s16(1,1,1)*MAP_BLOCKSIZE - v3s16(1,1,1); + case MEET_BLOCK_NODE_METADATA_CHANGED: { + v3s16 np1 = p * MAP_BLOCKSIZE; + v3s16 np2 = np1 + v3s16(1, 1, 1) * MAP_BLOCKSIZE - v3s16(1, 1, 1); return VoxelArea(np1, np2); } - case MEET_OTHER: - { + case MEET_OTHER: { VoxelArea a; for (v3s16 p : modified_blocks) { - v3s16 np1 = p*MAP_BLOCKSIZE; - v3s16 np2 = np1 + v3s16(1,1,1)*MAP_BLOCKSIZE - v3s16(1,1,1); + v3s16 np1 = p * MAP_BLOCKSIZE; + v3s16 np2 = np1 + v3s16(1, 1, 1) * MAP_BLOCKSIZE - + v3s16(1, 1, 1); a.addPoint(np1); a.addPoint(np2); } @@ -122,23 +122,16 @@ public: class Map /*: public NodeContainer*/ { public: - Map(std::ostream &dout, IGameDef *gamedef); virtual ~Map(); DISABLE_CLASS_COPY(Map); - virtual s32 mapType() const - { - return MAPTYPE_BASE; - } + virtual s32 mapType() const { return MAPTYPE_BASE; } /* Drop (client) or delete (server) the map. */ - virtual void drop() - { - delete this; - } + virtual void drop() { delete this; } void addEventReceiver(MapEventReceiver *event_receiver); void removeEventReceiver(MapEventReceiver *event_receiver); @@ -146,28 +139,30 @@ public: void dispatchEvent(const MapEditEvent &event); // On failure returns NULL - MapSector * getSectorNoGenerateNoLock(v2s16 p2d); + MapSector *getSectorNoGenerateNoLock(v2s16 p2d); // Same as the above (there exists no lock anymore) - MapSector * getSectorNoGenerate(v2s16 p2d); + MapSector *getSectorNoGenerate(v2s16 p2d); // Gets an existing sector or creates an empty one - //MapSector * getSectorCreate(v2s16 p2d); + // MapSector * getSectorCreate(v2s16 p2d); /* This is overloaded by ClientMap and ServerMap to allow their differing fetch methods. */ - virtual MapSector * emergeSector(v2s16 p){ return NULL; } + virtual MapSector *emergeSector(v2s16 p) { return NULL; } // Returns InvalidPositionException if not found - MapBlock * getBlockNoCreate(v3s16 p); + MapBlock *getBlockNoCreate(v3s16 p); // Returns NULL if not found - MapBlock * getBlockNoCreateNoEx(v3s16 p); + MapBlock *getBlockNoCreateNoEx(v3s16 p); /* Server overrides */ - virtual MapBlock * emergeBlock(v3s16 p, bool create_blank=true) - { return getBlockNoCreateNoEx(p); } + virtual MapBlock *emergeBlock(v3s16 p, bool create_blank = true) + { + return getBlockNoCreateNoEx(p); + } - inline const NodeDefManager * getNodeDefManager() { return m_nodedef; } + inline const NodeDefManager *getNodeDefManager() { return m_nodedef; } // Returns InvalidPositionException if not found bool isNodeUnderground(v3s16 p); @@ -175,7 +170,7 @@ public: bool isValidPosition(v3s16 p); // throws InvalidPositionException if not found - void setNode(v3s16 p, MapNode & n); + void setNode(v3s16 p, MapNode &n); // Returns a CONTENT_IGNORE node if not found // If is_valid_position is not NULL then this will be set to true if the @@ -186,10 +181,9 @@ public: These handle lighting but not faces. */ void addNodeAndUpdate(v3s16 p, MapNode n, - std::map &modified_blocks, + std::map &modified_blocks, bool remove_metadata = true); - void removeNodeAndUpdate(v3s16 p, - std::map &modified_blocks); + void removeNodeAndUpdate(v3s16 p, std::map &modified_blocks); /* Wrappers for the latter ones. @@ -215,13 +209,13 @@ public: Saves modified blocks before unloading on MAPTYPE_SERVER. */ void timerUpdate(float dtime, float unload_timeout, u32 max_loaded_blocks, - std::vector *unloaded_blocks=NULL); + std::vector *unloaded_blocks = NULL); /* Unloads all blocks with a zero refCount(). Saves modified blocks before unloading on MAPTYPE_SERVER. */ - void unloadUnreferencedBlocks(std::vector *unloaded_blocks=NULL); + void unloadUnreferencedBlocks(std::vector *unloaded_blocks = NULL); // Deletes sectors and their blocks from memory // Takes cache into account @@ -231,7 +225,7 @@ public: // For debug printing. Prints "Map: ", "ServerMap: " or "ClientMap: " virtual void PrintInfo(std::ostream &out); - void transformLiquids(std::map & modified_blocks, + void transformLiquids(std::map &modified_blocks, ServerEnvironment *env); /* @@ -271,7 +265,7 @@ public: /* Misc. */ - std::map *getSectorsPtr(){return &m_sectors;} + std::map *getSectorsPtr() { return &m_sectors; } /* Variables @@ -280,6 +274,7 @@ public: void transforming_liquid_add(v3s16 p); bool isBlockOccluded(MapBlock *block, v3s16 cam_pos_nodes); + protected: friend class LuaVoxelManip; @@ -287,9 +282,9 @@ protected: IGameDef *m_gamedef; - std::set m_event_receivers; + std::set m_event_receivers; - std::map m_sectors; + std::map m_sectors; // Be sure to set this to NULL when the cached sector is deleted MapSector *m_sector_cache = nullptr; @@ -302,10 +297,10 @@ protected: const NodeDefManager *m_nodedef; bool determineAdditionalOcclusionCheck(const v3s16 &pos_camera, - const core::aabbox3d &block_bounds, v3s16 &check); - bool isOccluded(const v3s16 &pos_camera, const v3s16 &pos_target, - float step, float stepfac, float start_offset, float end_offset, - u32 needed_count); + const core::aabbox3d &block_bounds, v3s16 &check); + bool isOccluded(const v3s16 &pos_camera, const v3s16 &pos_target, float step, + float stepfac, float start_offset, float end_offset, + u32 needed_count); private: f32 m_transforming_liquid_loop_count_multiplier = 1.0f; @@ -326,13 +321,11 @@ public: /* savedir: directory to which map data should be saved */ - ServerMap(const std::string &savedir, IGameDef *gamedef, EmergeManager *emerge, MetricsBackend *mb); + ServerMap(const std::string &savedir, IGameDef *gamedef, EmergeManager *emerge, + MetricsBackend *mb); ~ServerMap(); - s32 mapType() const - { - return MAPTYPE_SERVER; - } + s32 mapType() const { return MAPTYPE_SERVER; } /* Get a sector from somewhere. @@ -347,8 +340,8 @@ public: */ bool blockpos_over_mapgen_limit(v3s16 p); bool initBlockMake(v3s16 blockpos, BlockMakeData *data); - void finishBlockMake(BlockMakeData *data, - std::map *changed_blocks); + void finishBlockMake( + BlockMakeData *data, std::map *changed_blocks); /* Get a block from somewhere. @@ -364,7 +357,7 @@ public: - Create blank filled with CONTENT_IGNORE */ - MapBlock *emergeBlock(v3s16 p, bool create_blank=true); + MapBlock *emergeBlock(v3s16 p, bool create_blank = true); /* Try to get a block. @@ -386,7 +379,8 @@ public: /* Database functions */ - static MapDatabase *createDatabase(const std::string &name, const std::string &savedir, Settings &conf); + static MapDatabase *createDatabase(const std::string &name, + const std::string &savedir, Settings &conf); // Call these before and after saving of blocks void beginSave(); @@ -400,9 +394,10 @@ public: bool saveBlock(MapBlock *block); static bool saveBlock(MapBlock *block, MapDatabase *db); - MapBlock* loadBlock(v3s16 p); + MapBlock *loadBlock(v3s16 p); // Database version - void loadBlock(std::string *blob, v3s16 p3d, MapSector *sector, bool save_after_load=false); + void loadBlock(std::string *blob, v3s16 p3d, MapSector *sector, + bool save_after_load = false); bool deleteBlock(v3s16 blockpos); @@ -411,7 +406,7 @@ public: // For debug printing virtual void PrintInfo(std::ostream &out); - bool isSavingEnabled(){ return m_map_saving_enabled; } + bool isSavingEnabled() { return m_map_saving_enabled; } u64 getSeed(); s16 getWaterLevel(); @@ -423,8 +418,8 @@ public: * Returns false if the block is not generated (so nothing * changed), true otherwise. */ - bool repairBlockLight(v3s16 blockpos, - std::map *modified_blocks); + bool repairBlockLight( + v3s16 blockpos, std::map *modified_blocks); MapSettingsManager settings_mgr; @@ -454,8 +449,7 @@ private: MetricCounterPtr m_save_time_counter; }; - -#define VMANIP_BLOCK_DATA_INEXIST 1 +#define VMANIP_BLOCK_DATA_INEXIST 1 #define VMANIP_BLOCK_CONTAINS_CIGNORE 2 class MMVManip : public VoxelManipulator @@ -471,11 +465,11 @@ public: } void initialEmerge(v3s16 blockpos_min, v3s16 blockpos_max, - bool load_if_inexistent = true); + bool load_if_inexistent = true); // This is much faster with big chunks of generated data - void blitBackAll(std::map * modified_blocks, - bool overwrite_generated = true); + void blitBackAll(std::map *modified_blocks, + bool overwrite_generated = true); bool m_is_dirty = false; diff --git a/src/map_settings_manager.cpp b/src/map_settings_manager.cpp index 4f070e910..9be9194bc 100644 --- a/src/map_settings_manager.cpp +++ b/src/map_settings_manager.cpp @@ -25,25 +25,21 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "map_settings_manager.h" -MapSettingsManager::MapSettingsManager(Settings *user_settings, - const std::string &map_meta_path): - m_map_meta_path(map_meta_path), - m_map_settings(new Settings()), - m_user_settings(user_settings) +MapSettingsManager::MapSettingsManager( + Settings *user_settings, const std::string &map_meta_path) : + m_map_meta_path(map_meta_path), + m_map_settings(new Settings()), m_user_settings(user_settings) { assert(m_user_settings != NULL); } - MapSettingsManager::~MapSettingsManager() { delete m_map_settings; delete mapgen_params; } - -bool MapSettingsManager::getMapSetting( - const std::string &name, std::string *value_out) +bool MapSettingsManager::getMapSetting(const std::string &name, std::string *value_out) { if (m_map_settings->getNoEx(name, *value_out)) return true; @@ -55,17 +51,15 @@ bool MapSettingsManager::getMapSetting( return m_user_settings->getNoEx(name, *value_out); } - bool MapSettingsManager::getMapSettingNoiseParams( - const std::string &name, NoiseParams *value_out) + const std::string &name, NoiseParams *value_out) { return m_map_settings->getNoiseParams(name, *value_out) || - m_user_settings->getNoiseParams(name, *value_out); + m_user_settings->getNoiseParams(name, *value_out); } - bool MapSettingsManager::setMapSetting( - const std::string &name, const std::string &value, bool override_meta) + const std::string &name, const std::string &value, bool override_meta) { if (mapgen_params) return false; @@ -78,9 +72,8 @@ bool MapSettingsManager::setMapSetting( return true; } - bool MapSettingsManager::setMapSettingNoiseParams( - const std::string &name, const NoiseParams *value, bool override_meta) + const std::string &name, const NoiseParams *value, bool override_meta) { if (mapgen_params) return false; @@ -89,14 +82,13 @@ bool MapSettingsManager::setMapSettingNoiseParams( return true; } - bool MapSettingsManager::loadMapMeta() { std::ifstream is(m_map_meta_path.c_str(), std::ios_base::binary); if (!is.good()) { - errorstream << "loadMapMeta: could not open " - << m_map_meta_path << std::endl; + errorstream << "loadMapMeta: could not open " << m_map_meta_path + << std::endl; return false; } @@ -108,7 +100,6 @@ bool MapSettingsManager::loadMapMeta() return true; } - bool MapSettingsManager::saveMapMeta() { // If mapgen params haven't been created yet; abort @@ -117,7 +108,7 @@ bool MapSettingsManager::saveMapMeta() if (!fs::CreateAllDirs(fs::RemoveLastPathComponent(m_map_meta_path))) { errorstream << "saveMapMeta: could not create dirs to " - << m_map_meta_path; + << m_map_meta_path; return false; } @@ -134,15 +125,14 @@ bool MapSettingsManager::saveMapMeta() oss << "[end_of_params]\n"; if (!fs::safeWriteToFile(m_map_meta_path, oss.str())) { - errorstream << "saveMapMeta: could not write " - << m_map_meta_path << std::endl; + errorstream << "saveMapMeta: could not write " << m_map_meta_path + << std::endl; return false; } return true; } - MapgenParams *MapSettingsManager::makeMapgenParams() { if (mapgen_params) @@ -163,12 +153,13 @@ MapgenParams *MapSettingsManager::makeMapgenParams() // Now, get the mapgen type so we can create the appropriate MapgenParams std::string mg_name; - MapgenType mgtype = getMapSetting("mg_name", &mg_name) ? - Mapgen::getMapgenType(mg_name) : MAPGEN_DEFAULT; + MapgenType mgtype = getMapSetting("mg_name", &mg_name) + ? Mapgen::getMapgenType(mg_name) + : MAPGEN_DEFAULT; if (mgtype == MAPGEN_INVALID) { - errorstream << "EmergeManager: mapgen '" << mg_name << - "' not valid; falling back to " << - Mapgen::getMapgenName(MAPGEN_DEFAULT) << std::endl; + errorstream << "EmergeManager: mapgen '" << mg_name + << "' not valid; falling back to " + << Mapgen::getMapgenName(MAPGEN_DEFAULT) << std::endl; mgtype = MAPGEN_DEFAULT; } diff --git a/src/map_settings_manager.h b/src/map_settings_manager.h index 5baa38455..e7383cec5 100644 --- a/src/map_settings_manager.h +++ b/src/map_settings_manager.h @@ -42,10 +42,10 @@ struct MapgenParams; to whichever Map-related objects that may require it. - Save these active settings to the metadata file when requested */ -class MapSettingsManager { +class MapSettingsManager +{ public: - MapSettingsManager(Settings *user_settings, - const std::string &map_meta_path); + MapSettingsManager(Settings *user_settings, const std::string &map_meta_path); ~MapSettingsManager(); // Finalized map generation parameters @@ -53,17 +53,16 @@ public: bool getMapSetting(const std::string &name, std::string *value_out); - bool getMapSettingNoiseParams( - const std::string &name, NoiseParams *value_out); + bool getMapSettingNoiseParams(const std::string &name, NoiseParams *value_out); // Note: Map config becomes read-only after makeMapgenParams() gets called // (i.e. mapgen_params is non-NULL). Attempts to set map config after // params have been finalized will result in failure. - bool setMapSetting(const std::string &name, - const std::string &value, bool override_meta = false); + bool setMapSetting(const std::string &name, const std::string &value, + bool override_meta = false); - bool setMapSettingNoiseParams(const std::string &name, - const NoiseParams *value, bool override_meta = false); + bool setMapSettingNoiseParams(const std::string &name, const NoiseParams *value, + bool override_meta = false); bool loadMapMeta(); bool saveMapMeta(); diff --git a/src/mapblock.cpp b/src/mapblock.cpp index 5b755b7a6..ccee71a7e 100644 --- a/src/mapblock.cpp +++ b/src/mapblock.cpp @@ -27,7 +27,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "gamedef.h" #include "log.h" #include "nameidmapping.h" -#include "content_mapnode.h" // For legacy name-id mapping +#include "content_mapnode.h" // For legacy name-id mapping #include "content_nodemeta.h" // For legacy deserialization #include "serialization.h" #ifndef SERVER @@ -39,37 +39,34 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/basic_macros.h" static const char *modified_reason_strings[] = { - "initial", - "reallocate", - "setIsUnderground", - "setLightingExpired", - "setGenerated", - "setNode", - "setNodeNoCheck", - "setTimestamp", - "NodeMetaRef::reportMetadataChange", - "clearAllObjects", - "Timestamp expired (step)", - "addActiveObjectRaw", - "removeRemovedObjects/remove", - "removeRemovedObjects/deactivate", - "Stored list cleared in activateObjects due to overflow", - "deactivateFarObjects: Static data moved in", - "deactivateFarObjects: Static data moved out", - "deactivateFarObjects: Static data changed considerably", - "finishBlockMake: expireDayNightDiff", - "unknown", + "initial", + "reallocate", + "setIsUnderground", + "setLightingExpired", + "setGenerated", + "setNode", + "setNodeNoCheck", + "setTimestamp", + "NodeMetaRef::reportMetadataChange", + "clearAllObjects", + "Timestamp expired (step)", + "addActiveObjectRaw", + "removeRemovedObjects/remove", + "removeRemovedObjects/deactivate", + "Stored list cleared in activateObjects due to overflow", + "deactivateFarObjects: Static data moved in", + "deactivateFarObjects: Static data moved out", + "deactivateFarObjects: Static data changed considerably", + "finishBlockMake: expireDayNightDiff", + "unknown", }; - /* MapBlock */ -MapBlock::MapBlock(Map *parent, v3s16 pos, IGameDef *gamedef, bool dummy): - m_parent(parent), - m_pos(pos), - m_pos_relative(pos * MAP_BLOCKSIZE), +MapBlock::MapBlock(Map *parent, v3s16 pos, IGameDef *gamedef, bool dummy) : + m_parent(parent), m_pos(pos), m_pos_relative(pos * MAP_BLOCKSIZE), m_gamedef(gamedef) { if (!dummy) @@ -117,7 +114,7 @@ std::string MapBlock::getModifiedReasonString() std::string reason; const u32 ubound = MYMIN(sizeof(m_modified_reason) * CHAR_BIT, - ARRLEN(modified_reason_strings)); + ARRLEN(modified_reason_strings)); for (u32 i = 0; i != ubound; i++) { if ((m_modified_reason & (1 << i)) == 0) @@ -133,25 +130,22 @@ std::string MapBlock::getModifiedReasonString() return reason; } - void MapBlock::copyTo(VoxelManipulator &dst) { v3s16 data_size(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE); - VoxelArea data_area(v3s16(0,0,0), data_size - v3s16(1,1,1)); + VoxelArea data_area(v3s16(0, 0, 0), data_size - v3s16(1, 1, 1)); // Copy from data to VoxelManipulator - dst.copyFrom(data, data_area, v3s16(0,0,0), - getPosRelative(), data_size); + dst.copyFrom(data, data_area, v3s16(0, 0, 0), getPosRelative(), data_size); } void MapBlock::copyFrom(VoxelManipulator &dst) { v3s16 data_size(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE); - VoxelArea data_area(v3s16(0,0,0), data_size - v3s16(1,1,1)); + VoxelArea data_area(v3s16(0, 0, 0), data_size - v3s16(1, 1, 1)); // Copy from VoxelManipulator to data - dst.copyTo(data, data_area, v3s16(0,0,0), - getPosRelative(), data_size); + dst.copyTo(data, data_area, v3s16(0, 0, 0), getPosRelative(), data_size); } void MapBlock::actuallyUpdateDayNightDiff() @@ -220,25 +214,21 @@ void MapBlock::expireDayNightDiff() s16 MapBlock::getGroundLevel(v2s16 p2d) { - if(isDummy()) + if (isDummy()) return -3; - try - { - s16 y = MAP_BLOCKSIZE-1; - for(; y>=0; y--) - { + try { + s16 y = MAP_BLOCKSIZE - 1; + for (; y >= 0; y--) { MapNode n = getNodeRef(p2d.X, y, p2d.Y); if (m_gamedef->ndef()->get(n).walkable) { - if(y == MAP_BLOCKSIZE-1) + if (y == MAP_BLOCKSIZE - 1) return -2; return y; } } return -1; - } - catch(InvalidPositionException &e) - { + } catch (InvalidPositionException &e) { return -3; } } @@ -253,8 +243,8 @@ s16 MapBlock::getGroundLevel(v2s16 p2d) // a speedup of 4 for one of the major time consuming functions on storing // mapblocks. static content_t getBlockNodeIdMapping_mapping[USHRT_MAX + 1]; -static void getBlockNodeIdMapping(NameIdMapping *nimap, MapNode *nodes, - const NodeDefManager *nodedef) +static void getBlockNodeIdMapping( + NameIdMapping *nimap, MapNode *nodes, const NodeDefManager *nodedef) { memset(getBlockNodeIdMapping_mapping, 0xFF, (USHRT_MAX + 1) * sizeof(content_t)); @@ -267,9 +257,7 @@ static void getBlockNodeIdMapping(NameIdMapping *nimap, MapNode *nodes, // Try to find an existing mapping if (getBlockNodeIdMapping_mapping[global_id] != 0xFFFF) { id = getBlockNodeIdMapping_mapping[global_id]; - } - else - { + } else { // We have to assign a new mapping id = id_counter++; getBlockNodeIdMapping_mapping[global_id] = id; @@ -287,14 +275,15 @@ static void getBlockNodeIdMapping(NameIdMapping *nimap, MapNode *nodes, } for (u16 unknown_content : unknown_contents) { errorstream << "getBlockNodeIdMapping(): IGNORING ERROR: " - << "Name for node id " << unknown_content << " not known" << std::endl; + << "Name for node id " << unknown_content << " not known" + << std::endl; } } // Correct ids in the block to match nodedef based on names. // Unknown ones are added to nodedef. // Will not update itself to match id-name pairs in nodedef. -static void correctBlockNodeIds(const NameIdMapping *nimap, MapNode *nodes, - IGameDef *gamedef) +static void correctBlockNodeIds( + const NameIdMapping *nimap, MapNode *nodes, IGameDef *gamedef) { const NodeDefManager *nodedef = gamedef->ndef(); // This means the block contains incorrect ids, and we contain @@ -310,10 +299,10 @@ static void correctBlockNodeIds(const NameIdMapping *nimap, MapNode *nodes, for (u32 i = 0; i < MapBlock::nodecount; i++) { content_t local_id = nodes[i].getContent(); - // If previous node local_id was found and same than before, don't lookup maps - // apply directly previous resolved id - // This permits to massively improve loading performance when nodes are similar - // example: default:air, default:stone are massively present + // If previous node local_id was found and same than before, don't lookup + // maps apply directly previous resolved id This permits to massively + // improve loading performance when nodes are similar example: + // default:air, default:stone are massively present if (previous_exists && local_id == previous_local_id) { nodes[i].setContent(previous_global_id); continue; @@ -343,21 +332,21 @@ static void correctBlockNodeIds(const NameIdMapping *nimap, MapNode *nodes, previous_exists = true; } - for (const content_t c: unnamed_contents) { + for (const content_t c : unnamed_contents) { errorstream << "correctBlockNodeIds(): IGNORING ERROR: " - << "Block contains id " << c - << " with no name mapping" << std::endl; + << "Block contains id " << c << " with no name mapping" + << std::endl; } - for (const std::string &node_name: unallocatable_contents) { + for (const std::string &node_name : unallocatable_contents) { errorstream << "correctBlockNodeIds(): IGNORING ERROR: " - << "Could not allocate global id for node name \"" - << node_name << "\"" << std::endl; + << "Could not allocate global id for node name \"" + << node_name << "\"" << std::endl; } } void MapBlock::serialize(std::ostream &os, u8 version, bool disk) { - if(!ser_ver_supported(version)) + if (!ser_ver_supported(version)) throw VersionMismatchException("ERROR: MapBlock format not supported"); if (!data) @@ -367,9 +356,9 @@ void MapBlock::serialize(std::ostream &os, u8 version, bool disk) // First byte u8 flags = 0; - if(is_underground) + if (is_underground) flags |= 0x01; - if(getDayNightDiff()) + if (getDayNightDiff()) flags |= 0x02; if (!m_generated) flags |= 0x08; @@ -382,10 +371,9 @@ void MapBlock::serialize(std::ostream &os, u8 version, bool disk) Bulk node data */ NameIdMapping nimap; - if(disk) - { + if (disk) { MapNode *tmp_nodes = new MapNode[nodecount]; - for(u32 i=0; indef()); @@ -393,18 +381,16 @@ void MapBlock::serialize(std::ostream &os, u8 version, bool disk) u8 params_width = 2; writeU8(os, content_width); writeU8(os, params_width); - MapNode::serializeBulk(os, version, tmp_nodes, nodecount, - content_width, params_width, true); + MapNode::serializeBulk(os, version, tmp_nodes, nodecount, content_width, + params_width, true); delete[] tmp_nodes; - } - else - { + } else { u8 content_width = 2; u8 params_width = 2; writeU8(os, content_width); writeU8(os, params_width); - MapNode::serializeBulk(os, version, data, nodecount, - content_width, params_width, true); + MapNode::serializeBulk(os, version, data, nodecount, content_width, + params_width, true); } /* @@ -417,9 +403,8 @@ void MapBlock::serialize(std::ostream &os, u8 version, bool disk) /* Data that goes to disk, but not the network */ - if(disk) - { - if(version <= 24){ + if (disk) { + if (version <= 24) { // Node timers m_node_timers.serialize(os, version); } @@ -433,7 +418,7 @@ void MapBlock::serialize(std::ostream &os, u8 version, bool disk) // Write block-specific node definition id mapping nimap.serialize(os); - if(version >= 25){ + if (version >= 25) { // Node timers m_node_timers.serialize(os, version); } @@ -451,15 +436,14 @@ void MapBlock::serializeNetworkSpecific(std::ostream &os) void MapBlock::deSerialize(std::istream &is, u8 version, bool disk) { - if(!ser_ver_supported(version)) + if (!ser_ver_supported(version)) throw VersionMismatchException("ERROR: MapBlock format not supported"); - TRACESTREAM(<<"MapBlock::deSerialize "<= 23) m_node_metadata.deSerialize(iss, m_gamedef->idef()); else - content_nodemeta_deserialize_legacy(iss, - &m_node_metadata, &m_node_timers, - m_gamedef->idef()); - } catch(SerializationError &e) { - warningstream<<"MapBlock::deSerialize(): Ignoring an error" - <<" while deserializing node metadata at (" - <idef()); + } catch (SerializationError &e) { + warningstream << "MapBlock::deSerialize(): Ignoring an error" + << " while deserializing node metadata at (" << PP(getPos()) + << ": " << e.what() << std::endl; } /* Data that is only on disk */ - if(disk) - { + if (disk) { // Node timers - if(version == 23){ + if (version == 23) { // Read unused zero readU8(is); } - if(version == 24){ - TRACESTREAM(<<"MapBlock::deSerialize "<= 25){ - TRACESTREAM(<<"MapBlock::deSerialize "<=25)"<= 25) { + TRACESTREAM(<< "MapBlock::deSerialize " << PP(getPos()) + << ": Node timers (ver>=25)" << std::endl); m_node_timers.deSerialize(is, version); } } - TRACESTREAM(<<"MapBlock::deSerialize "<= 18) + if (version >= 18) m_generated = (flags & 0x08) == 0; // Uncompress data @@ -651,14 +635,15 @@ void MapBlock::deSerialize_pre22(std::istream &is, u8 version, bool disk) decompress(is, os, version); std::string s = os.str(); if (s.size() != nodecount * 3) - throw SerializationError(std::string(FUNCTION_NAME) - + ": decompress resulted in size other than nodecount*3"); + throw SerializationError(std::string(FUNCTION_NAME) + + ": decompress resulted in size other " + "than nodecount*3"); // deserialize nodes from buffer for (u32 i = 0; i < nodecount; i++) { - databuf_nodelist[i*ser_length] = s[i]; - databuf_nodelist[i*ser_length + 1] = s[i+nodecount]; - databuf_nodelist[i*ser_length + 2] = s[i+nodecount*2]; + databuf_nodelist[i * ser_length] = s[i]; + databuf_nodelist[i * ser_length + 1] = s[i + nodecount]; + databuf_nodelist[i * ser_length + 2] = s[i + nodecount * 2]; } /* @@ -669,22 +654,26 @@ void MapBlock::deSerialize_pre22(std::istream &is, u8 version, bool disk) try { if (version <= 15) { std::string data = deSerializeString(is); - std::istringstream iss(data, std::ios_base::binary); + std::istringstream iss( + data, std::ios_base::binary); content_nodemeta_deserialize_legacy(iss, - &m_node_metadata, &m_node_timers, - m_gamedef->idef()); + &m_node_metadata, &m_node_timers, + m_gamedef->idef()); } else { - //std::string data = deSerializeLongString(is); + // std::string data = deSerializeLongString(is); std::ostringstream oss(std::ios_base::binary); decompressZlib(is, oss); - std::istringstream iss(oss.str(), std::ios_base::binary); + std::istringstream iss( + oss.str(), std::ios_base::binary); content_nodemeta_deserialize_legacy(iss, - &m_node_metadata, &m_node_timers, - m_gamedef->idef()); + &m_node_metadata, &m_node_timers, + m_gamedef->idef()); } - } catch(SerializationError &e) { - warningstream<<"MapBlock::deSerialize(): Ignoring an error" - <<" while deserializing node metadata"<= 9) { u16 count = readU16(is); // Not supported and length not known if count is not 0 - if(count != 0){ - warningstream<<"MapBlock::deSerialize_pre22(): " - <<"Ignoring stuff coming at and after MBOs"<= 21) { nimap.deSerialize(is); - // Else set the legacy mapping + // Else set the legacy mapping } else { content_mapnode_get_name_id_mapping(&nimap); } correctBlockNodeIds(&nimap, data, m_gamedef); } - // Legacy data changes // This code has to convert from pre-22 to post-22 format. const NodeDefManager *nodedef = m_gamedef->ndef(); - for(u32 i=0; iget(data[i].getContent()); // Mineral - if(nodedef->getId("default:stone") == data[i].getContent() - && data[i].getParam1() == 1) - { + if (nodedef->getId("default:stone") == data[i].getContent() && + data[i].getParam1() == 1) { data[i].setContent(nodedef->getId("default:stone_with_coal")); data[i].setParam1(0); - } - else if(nodedef->getId("default:stone") == data[i].getContent() - && data[i].getParam1() == 2) - { + } else if (nodedef->getId("default:stone") == data[i].getContent() && + data[i].getParam1() == 2) { data[i].setContent(nodedef->getId("default:stone_with_iron")); data[i].setParam1(0); } // facedir_simple - if(f.legacy_facedir_simple) - { + if (f.legacy_facedir_simple) { data[i].setParam2(data[i].getParam1()); data[i].setParam1(0); } // wall_mounted - if(f.legacy_wallmounted) - { - u8 wallmounted_new_to_old[8] = {0x04, 0x08, 0x01, 0x02, 0x10, 0x20, 0, 0}; + if (f.legacy_wallmounted) { + u8 wallmounted_new_to_old[8] = { + 0x04, 0x08, 0x01, 0x02, 0x10, 0x20, 0, 0}; u8 dir_old_format = data[i].getParam2(); u8 dir_new_format = 0; - for(u8 j=0; j<8; j++) - { - if((dir_old_format & wallmounted_new_to_old[j]) != 0) - { + for (u8 j = 0; j < 8; j++) { + if ((dir_old_format & wallmounted_new_to_old[j]) != 0) { dir_new_format = j; break; } @@ -777,7 +759,6 @@ void MapBlock::deSerialize_pre22(std::istream &is, u8 version, bool disk) data[i].setParam2(dir_new_format); } } - } /* @@ -785,7 +766,7 @@ void MapBlock::deSerialize_pre22(std::istream &is, u8 version, bool disk) */ std::string analyze_block(MapBlock *block) { - if(block == NULL) + if (block == NULL) return "NULL"; std::ostringstream desc; @@ -793,84 +774,78 @@ std::string analyze_block(MapBlock *block) v3s16 p = block->getPos(); char spos[25]; porting::mt_snprintf(spos, sizeof(spos), "(%2d,%2d,%2d), ", p.X, p.Y, p.Z); - desc<getModified()) - { + switch (block->getModified()) { case MOD_STATE_CLEAN: - desc<<"CLEAN, "; + desc << "CLEAN, "; break; case MOD_STATE_WRITE_AT_UNLOAD: - desc<<"WRITE_AT_UNLOAD, "; + desc << "WRITE_AT_UNLOAD, "; break; case MOD_STATE_WRITE_NEEDED: - desc<<"WRITE_NEEDED, "; + desc << "WRITE_NEEDED, "; break; default: - desc<<"unknown getModified()="+itos(block->getModified())+", "; + desc << "unknown getModified()=" + itos(block->getModified()) + ", "; } - if(block->isGenerated()) - desc<<"is_gen [X], "; + if (block->isGenerated()) + desc << "is_gen [X], "; else - desc<<"is_gen [ ], "; + desc << "is_gen [ ], "; - if(block->getIsUnderground()) - desc<<"is_ug [X], "; + if (block->getIsUnderground()) + desc << "is_ug [X], "; else - desc<<"is_ug [ ], "; + desc << "is_ug [ ], "; - desc<<"lighting_complete: "<getLightingComplete()<<", "; + desc << "lighting_complete: " << block->getLightingComplete() << ", "; - if(block->isDummy()) - { - desc<<"Dummy, "; - } - else - { + if (block->isDummy()) { + desc << "Dummy, "; + } else { bool full_ignore = true; bool some_ignore = false; bool full_air = true; bool some_air = false; - for(s16 z0=0; z0getNodeNoEx(p); - content_t c = n.getContent(); - if(c == CONTENT_IGNORE) - some_ignore = true; - else - full_ignore = false; - if(c == CONTENT_AIR) - some_air = true; - else - full_air = false; - } + for (s16 z0 = 0; z0 < MAP_BLOCKSIZE; z0++) + for (s16 y0 = 0; y0 < MAP_BLOCKSIZE; y0++) + for (s16 x0 = 0; x0 < MAP_BLOCKSIZE; x0++) { + v3s16 p(x0, y0, z0); + MapNode n = block->getNodeNoEx(p); + content_t c = n.getContent(); + if (c == CONTENT_IGNORE) + some_ignore = true; + else + full_ignore = false; + if (c == CONTENT_AIR) + some_air = true; + else + full_air = false; + } - desc<<"content {"; + desc << "content {"; std::ostringstream ss; - if(full_ignore) - ss<<"IGNORE (full), "; - else if(some_ignore) - ss<<"IGNORE, "; + if (full_ignore) + ss << "IGNORE (full), "; + else if (some_ignore) + ss << "IGNORE, "; - if(full_air) - ss<<"AIR (full), "; - else if(some_air) - ss<<"AIR, "; + if (full_air) + ss << "AIR (full), "; + else if (some_air) + ss << "AIR, "; - if(ss.str().size()>=2) - desc<= 2) + desc << ss.str().substr(0, ss.str().size() - 2); - desc<<"}, "; + desc << "}, "; } - return desc.str().substr(0, desc.str().size()-2); + return desc.str().substr(0, desc.str().size() - 2); } - -//END +// END diff --git a/src/mapblock.h b/src/mapblock.h index 6b5015cab..ff558c685 100644 --- a/src/mapblock.h +++ b/src/mapblock.h @@ -44,27 +44,27 @@ class VoxelManipulator; //// MapBlock modified reason flags //// -#define MOD_REASON_INITIAL (1 << 0) -#define MOD_REASON_REALLOCATE (1 << 1) -#define MOD_REASON_SET_IS_UNDERGROUND (1 << 2) -#define MOD_REASON_SET_LIGHTING_COMPLETE (1 << 3) -#define MOD_REASON_SET_GENERATED (1 << 4) -#define MOD_REASON_SET_NODE (1 << 5) -#define MOD_REASON_SET_NODE_NO_CHECK (1 << 6) -#define MOD_REASON_SET_TIMESTAMP (1 << 7) -#define MOD_REASON_REPORT_META_CHANGE (1 << 8) -#define MOD_REASON_CLEAR_ALL_OBJECTS (1 << 9) -#define MOD_REASON_BLOCK_EXPIRED (1 << 10) -#define MOD_REASON_ADD_ACTIVE_OBJECT_RAW (1 << 11) -#define MOD_REASON_REMOVE_OBJECTS_REMOVE (1 << 12) +#define MOD_REASON_INITIAL (1 << 0) +#define MOD_REASON_REALLOCATE (1 << 1) +#define MOD_REASON_SET_IS_UNDERGROUND (1 << 2) +#define MOD_REASON_SET_LIGHTING_COMPLETE (1 << 3) +#define MOD_REASON_SET_GENERATED (1 << 4) +#define MOD_REASON_SET_NODE (1 << 5) +#define MOD_REASON_SET_NODE_NO_CHECK (1 << 6) +#define MOD_REASON_SET_TIMESTAMP (1 << 7) +#define MOD_REASON_REPORT_META_CHANGE (1 << 8) +#define MOD_REASON_CLEAR_ALL_OBJECTS (1 << 9) +#define MOD_REASON_BLOCK_EXPIRED (1 << 10) +#define MOD_REASON_ADD_ACTIVE_OBJECT_RAW (1 << 11) +#define MOD_REASON_REMOVE_OBJECTS_REMOVE (1 << 12) #define MOD_REASON_REMOVE_OBJECTS_DEACTIVATE (1 << 13) -#define MOD_REASON_TOO_MANY_OBJECTS (1 << 14) -#define MOD_REASON_STATIC_DATA_ADDED (1 << 15) -#define MOD_REASON_STATIC_DATA_REMOVED (1 << 16) -#define MOD_REASON_STATIC_DATA_CHANGED (1 << 17) -#define MOD_REASON_EXPIRE_DAYNIGHTDIFF (1 << 18) -#define MOD_REASON_VMANIP (1 << 19) -#define MOD_REASON_UNKNOWN (1 << 20) +#define MOD_REASON_TOO_MANY_OBJECTS (1 << 14) +#define MOD_REASON_STATIC_DATA_ADDED (1 << 15) +#define MOD_REASON_STATIC_DATA_REMOVED (1 << 16) +#define MOD_REASON_STATIC_DATA_CHANGED (1 << 17) +#define MOD_REASON_EXPIRE_DAYNIGHTDIFF (1 << 18) +#define MOD_REASON_VMANIP (1 << 19) +#define MOD_REASON_UNKNOWN (1 << 20) //// //// MapBlock itself @@ -73,7 +73,7 @@ class VoxelManipulator; class MapBlock { public: - MapBlock(Map *parent, v3s16 pos, IGameDef *gamedef, bool dummy=false); + MapBlock(Map *parent, v3s16 pos, IGameDef *gamedef, bool dummy = false); ~MapBlock(); /*virtual u16 nodeContainerId() const @@ -81,10 +81,7 @@ public: return NODECONTAINER_ID_MAPBLOCK; }*/ - Map * getParent() - { - return m_parent; - } + Map *getParent() { return m_parent; } void reallocate() { @@ -96,15 +93,12 @@ public: raiseModified(MOD_STATE_WRITE_NEEDED, MOD_REASON_REALLOCATE); } - MapNode* getData() - { - return data; - } + MapNode *getData() { return data; } //// //// Modification tracking methods //// - void raiseModified(u32 mod, u32 reason=MOD_REASON_UNKNOWN) + void raiseModified(u32 mod, u32 reason = MOD_REASON_UNKNOWN) { if (mod > m_modified) { m_modified = mod; @@ -118,15 +112,9 @@ public: contents_cached = false; } - inline u32 getModified() - { - return m_modified; - } + inline u32 getModified() { return m_modified; } - inline u32 getModifiedReason() - { - return m_modified_reason; - } + inline u32 getModifiedReason() { return m_modified_reason; } std::string getModifiedReasonString(); @@ -140,10 +128,7 @@ public: //// Flags //// - inline bool isDummy() - { - return !data; - } + inline bool isDummy() { return !data; } inline void unDummify() { @@ -152,10 +137,7 @@ public: } // is_underground getter/setter - inline bool getIsUnderground() - { - return is_underground; - } + inline bool getIsUnderground() { return is_underground; } inline void setIsUnderground(bool a_is_underground) { @@ -167,17 +149,14 @@ public: { if (newflags != m_lighting_complete) { m_lighting_complete = newflags; - raiseModified(MOD_STATE_WRITE_NEEDED, MOD_REASON_SET_LIGHTING_COMPLETE); + raiseModified(MOD_STATE_WRITE_NEEDED, + MOD_REASON_SET_LIGHTING_COMPLETE); } } - inline u16 getLightingComplete() - { - return m_lighting_complete; - } + inline u16 getLightingComplete() { return m_lighting_complete; } - inline void setLightingComplete(LightBank bank, u8 direction, - bool is_complete) + inline void setLightingComplete(LightBank bank, u8 direction, bool is_complete) { assert(direction >= 0 && direction <= 5); if (bank == LIGHTBANK_NIGHT) { @@ -201,10 +180,7 @@ public: return (m_lighting_complete & (1 << direction)) != 0; } - inline bool isGenerated() - { - return m_generated; - } + inline bool isGenerated() { return m_generated; } inline void setGenerated(bool b) { @@ -218,22 +194,17 @@ public: //// Position stuff //// - inline v3s16 getPos() - { - return m_pos; - } + inline v3s16 getPos() { return m_pos; } - inline v3s16 getPosRelative() - { - return m_pos_relative; - } + inline v3s16 getPosRelative() { return m_pos_relative; } inline core::aabbox3d getBox() { return core::aabbox3d(getPosRelative(), - getPosRelative() - + v3s16(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE) - - v3s16(1,1,1)); + getPosRelative() + + v3s16(MAP_BLOCKSIZE, MAP_BLOCKSIZE, + MAP_BLOCKSIZE) - + v3s16(1, 1, 1)); } //// @@ -242,16 +213,11 @@ public: inline bool isValidPosition(s16 x, s16 y, s16 z) { - return data - && x >= 0 && x < MAP_BLOCKSIZE - && y >= 0 && y < MAP_BLOCKSIZE - && z >= 0 && z < MAP_BLOCKSIZE; + return data && x >= 0 && x < MAP_BLOCKSIZE && y >= 0 && + y < MAP_BLOCKSIZE && z >= 0 && z < MAP_BLOCKSIZE; } - inline bool isValidPosition(v3s16 p) - { - return isValidPosition(p.X, p.Y, p.Z); - } + inline bool isValidPosition(v3s16 p) { return isValidPosition(p.X, p.Y, p.Z); } inline MapNode getNode(s16 x, s16 y, s16 z, bool *valid_position) { @@ -274,7 +240,7 @@ public: return getNode(p.X, p.Y, p.Z, &is_valid); } - inline void setNode(s16 x, s16 y, s16 z, MapNode & n) + inline void setNode(s16 x, s16 y, s16 z, MapNode &n) { if (!isValidPosition(x, y, z)) throw InvalidPositionException(); @@ -283,10 +249,7 @@ public: raiseModified(MOD_STATE_WRITE_NEEDED, MOD_REASON_SET_NODE); } - inline void setNode(v3s16 p, MapNode & n) - { - setNode(p.X, p.Y, p.Z, n); - } + inline void setNode(v3s16 p, MapNode &n) { setNode(p.X, p.Y, p.Z, n); } //// //// Non-checking variants of the above @@ -322,7 +285,7 @@ public: return getNodeUnsafe(p.X, p.Y, p.Z); } - inline void setNodeNoCheck(s16 x, s16 y, s16 z, MapNode & n) + inline void setNodeNoCheck(s16 x, s16 y, s16 z, MapNode &n) { if (!data) throw InvalidPositionException(); @@ -331,7 +294,7 @@ public: raiseModified(MOD_STATE_WRITE_NEEDED, MOD_REASON_SET_NODE_NO_CHECK); } - inline void setNodeNoCheck(v3s16 p, MapNode & n) + inline void setNodeNoCheck(v3s16 p, MapNode &n) { setNodeNoCheck(p.X, p.Y, p.Z, n); } @@ -340,14 +303,14 @@ public: // is not valid on this MapBlock. bool isValidPositionParent(v3s16 p); MapNode getNodeParent(v3s16 p, bool *is_valid_position = NULL); - void setNodeParent(v3s16 p, MapNode & n); + void setNodeParent(v3s16 p, MapNode &n); inline void drawbox(s16 x0, s16 y0, s16 z0, s16 w, s16 h, s16 d, MapNode node) { for (u16 z = 0; z < d; z++) - for (u16 y = 0; y < h; y++) - for (u16 x = 0; x < w; x++) - setNode(x0 + x, y0 + y, z0 + z, node); + for (u16 y = 0; y < h; y++) + for (u16 x = 0; x < w; x++) + setNode(x0 + x, y0 + y, z0 + z, node); } // Copies data to VoxelManipulator to getPosRelative() @@ -398,82 +361,43 @@ public: raiseModified(MOD_STATE_WRITE_AT_UNLOAD, MOD_REASON_SET_TIMESTAMP); } - inline void setTimestampNoChangedFlag(u32 time) - { - m_timestamp = time; - } + inline void setTimestampNoChangedFlag(u32 time) { m_timestamp = time; } - inline u32 getTimestamp() - { - return m_timestamp; - } + inline u32 getTimestamp() { return m_timestamp; } - inline u32 getDiskTimestamp() - { - return m_disk_timestamp; - } + inline u32 getDiskTimestamp() { return m_disk_timestamp; } //// //// Usage timer (see m_usage_timer) //// - inline void resetUsageTimer() - { - m_usage_timer = 0; - } + inline void resetUsageTimer() { m_usage_timer = 0; } - inline void incrementUsageTimer(float dtime) - { - m_usage_timer += dtime; - } + inline void incrementUsageTimer(float dtime) { m_usage_timer += dtime; } - inline float getUsageTimer() - { - return m_usage_timer; - } + inline float getUsageTimer() { return m_usage_timer; } //// //// Reference counting (see m_refcount) //// - inline void refGrab() - { - m_refcount++; - } + inline void refGrab() { m_refcount++; } - inline void refDrop() - { - m_refcount--; - } + inline void refDrop() { m_refcount--; } - inline int refGet() - { - return m_refcount; - } + inline int refGet() { return m_refcount; } //// //// Node Timers //// - inline NodeTimer getNodeTimer(const v3s16 &p) - { - return m_node_timers.get(p); - } + inline NodeTimer getNodeTimer(const v3s16 &p) { return m_node_timers.get(p); } - inline void removeNodeTimer(const v3s16 &p) - { - m_node_timers.remove(p); - } + inline void removeNodeTimer(const v3s16 &p) { m_node_timers.remove(p); } - inline void setNodeTimer(const NodeTimer &t) - { - m_node_timers.set(t); - } + inline void setNodeTimer(const NodeTimer &t) { m_node_timers.set(t); } - inline void clearNodeTimers() - { - m_node_timers.clear(); - } + inline void clearNodeTimers() { m_node_timers.clear(); } //// //// Serialization @@ -489,6 +413,7 @@ public: void serializeNetworkSpecific(std::ostream &os); void deSerializeNetworkSpecific(std::istream &is); + private: /* Private methods @@ -508,10 +433,7 @@ private: return data[z * zstride + y * ystride + x]; } - inline MapNode &getNodeRef(v3s16 &p) - { - return getNodeRef(p.X, p.Y, p.Z); - } + inline MapNode &getNodeRef(v3s16 &p) { return getNodeRef(p.X, p.Y, p.Z); } public: /* @@ -550,11 +472,12 @@ private: v3s16 m_pos; /* This is the precalculated m_pos_relative value - * This caches the value, improving performance by removing 3 s16 multiplications - * at runtime on each getPosRelative call - * For a 5 minutes runtime with valgrind this removes 3 * 19M s16 multiplications - * The gain can be estimated in Release Build to 3 * 100M multiply operations for 5 mins - */ + * This caches the value, improving performance by removing 3 s16 multiplications + * at runtime on each getPosRelative call + * For a 5 minutes runtime with valgrind this removes 3 * 19M s16 multiplications + * The gain can be estimated in Release Build to 3 * 100M multiply operations for + * 5 mins + */ v3s16 m_pos_relative; IGameDef *m_gamedef; @@ -590,7 +513,7 @@ private: * nothing, nothing, nothing, nothing, * night X-, night Y-, night Z-, night Z+, night Y+, night X+, * day X-, day Y-, day Z-, day Z+, day Y+, day X+. - */ + */ u16 m_lighting_complete = 0xFFFF; // Whether day and night lighting differs @@ -620,28 +543,20 @@ private: int m_refcount = 0; }; -typedef std::vector MapBlockVect; +typedef std::vector MapBlockVect; inline bool objectpos_over_limit(v3f p) { const float max_limit_bs = MAX_MAP_GENERATION_LIMIT * BS; - return p.X < -max_limit_bs || - p.X > max_limit_bs || - p.Y < -max_limit_bs || - p.Y > max_limit_bs || - p.Z < -max_limit_bs || - p.Z > max_limit_bs; + return p.X < -max_limit_bs || p.X > max_limit_bs || p.Y < -max_limit_bs || + p.Y > max_limit_bs || p.Z < -max_limit_bs || p.Z > max_limit_bs; } inline bool blockpos_over_max_limit(v3s16 p) { const s16 max_limit_bp = MAX_MAP_GENERATION_LIMIT / MAP_BLOCKSIZE; - return p.X < -max_limit_bp || - p.X > max_limit_bp || - p.Y < -max_limit_bp || - p.Y > max_limit_bp || - p.Z < -max_limit_bp || - p.Z > max_limit_bp; + return p.X < -max_limit_bp || p.X > max_limit_bp || p.Y < -max_limit_bp || + p.Y > max_limit_bp || p.Z < -max_limit_bp || p.Z > max_limit_bp; } /* diff --git a/src/mapgen/cavegen.cpp b/src/mapgen/cavegen.cpp index 340079821..a51476135 100644 --- a/src/mapgen/cavegen.cpp +++ b/src/mapgen/cavegen.cpp @@ -32,14 +32,13 @@ with this program; if not, write to the Free Software Foundation, Inc., // TODO Remove this. Cave liquids are now defined and located using biome definitions static NoiseParams nparams_caveliquids(0, 1, v3f(150.0, 150.0, 150.0), 776, 3, 0.6, 2.0); - //// //// CavesNoiseIntersection //// -CavesNoiseIntersection::CavesNoiseIntersection( - const NodeDefManager *nodedef, BiomeManager *biomemgr, v3s16 chunksize, - NoiseParams *np_cave1, NoiseParams *np_cave2, s32 seed, float cave_width) +CavesNoiseIntersection::CavesNoiseIntersection(const NodeDefManager *nodedef, + BiomeManager *biomemgr, v3s16 chunksize, NoiseParams *np_cave1, + NoiseParams *np_cave2, s32 seed, float cave_width) { assert(nodedef); assert(biomemgr); @@ -50,7 +49,7 @@ CavesNoiseIntersection::CavesNoiseIntersection( m_csize = chunksize; m_cave_width = cave_width; - m_ystride = m_csize.X; + m_ystride = m_csize.X; m_zstride_1d = m_csize.X * (m_csize.Y + 1); // Noises are created using 1-down overgeneration @@ -60,16 +59,14 @@ CavesNoiseIntersection::CavesNoiseIntersection( noise_cave2 = new Noise(np_cave2, seed, m_csize.X, m_csize.Y + 1, m_csize.Z); } - CavesNoiseIntersection::~CavesNoiseIntersection() { delete noise_cave1; delete noise_cave2; } - -void CavesNoiseIntersection::generateCaves(MMVManip *vm, - v3s16 nmin, v3s16 nmax, biome_t *biomemap) +void CavesNoiseIntersection::generateCaves( + MMVManip *vm, v3s16 nmin, v3s16 nmax, biome_t *biomemap) { assert(vm); assert(biomemap); @@ -78,114 +75,120 @@ void CavesNoiseIntersection::generateCaves(MMVManip *vm, noise_cave2->perlinMap3D(nmin.X, nmin.Y - 1, nmin.Z); const v3s16 &em = vm->m_area.getExtent(); - u32 index2d = 0; // Biomemap index + u32 index2d = 0; // Biomemap index for (s16 z = nmin.Z; z <= nmax.Z; z++) - for (s16 x = nmin.X; x <= nmax.X; x++, index2d++) { - bool column_is_open = false; // Is column open to overground - bool is_under_river = false; // Is column under river water - bool is_under_tunnel = false; // Is tunnel or is under tunnel - bool is_top_filler_above = false; // Is top or filler above node - // Indexes at column top - u32 vi = vm->m_area.index(x, nmax.Y, z); - u32 index3d = (z - nmin.Z) * m_zstride_1d + m_csize.Y * m_ystride + - (x - nmin.X); // 3D noise index - // Biome of column - Biome *biome = (Biome *)m_bmgr->getRaw(biomemap[index2d]); - u16 depth_top = biome->depth_top; - u16 base_filler = depth_top + biome->depth_filler; - u16 depth_riverbed = biome->depth_riverbed; - u16 nplaced = 0; - // Don't excavate the overgenerated stone at nmax.Y + 1, - // this creates a 'roof' over the tunnel, preventing light in - // tunnels at mapchunk borders when generating mapchunks upwards. - // This 'roof' is removed when the mapchunk above is generated. - for (s16 y = nmax.Y; y >= nmin.Y - 1; y--, - index3d -= m_ystride, - VoxelArea::add_y(em, vi, -1)) { - content_t c = vm->m_data[vi].getContent(); + for (s16 x = nmin.X; x <= nmax.X; x++, index2d++) { + bool column_is_open = false; // Is column open to overground + bool is_under_river = false; // Is column under river water + bool is_under_tunnel = false; // Is tunnel or is under tunnel + bool is_top_filler_above = false; // Is top or filler above node + // Indexes at column top + u32 vi = vm->m_area.index(x, nmax.Y, z); + u32 index3d = (z - nmin.Z) * m_zstride_1d + + m_csize.Y * m_ystride + + (x - nmin.X); // 3D noise index + // Biome of column + Biome *biome = (Biome *)m_bmgr->getRaw(biomemap[index2d]); + u16 depth_top = biome->depth_top; + u16 base_filler = depth_top + biome->depth_filler; + u16 depth_riverbed = biome->depth_riverbed; + u16 nplaced = 0; + // Don't excavate the overgenerated stone at nmax.Y + 1, + // this creates a 'roof' over the tunnel, preventing light in + // tunnels at mapchunk borders when generating mapchunks upwards. + // This 'roof' is removed when the mapchunk above is generated. + for (s16 y = nmax.Y; y >= nmin.Y - 1; y--, index3d -= m_ystride, + VoxelArea::add_y(em, vi, -1)) { + content_t c = vm->m_data[vi].getContent(); - if (c == CONTENT_AIR || c == biome->c_water_top || - c == biome->c_water) { - column_is_open = true; - is_top_filler_above = false; - continue; - } + if (c == CONTENT_AIR || c == biome->c_water_top || + c == biome->c_water) { + column_is_open = true; + is_top_filler_above = false; + continue; + } - if (c == biome->c_river_water) { - column_is_open = true; - is_under_river = true; - is_top_filler_above = false; - continue; - } + if (c == biome->c_river_water) { + column_is_open = true; + is_under_river = true; + is_top_filler_above = false; + continue; + } - // Ground - float d1 = contour(noise_cave1->result[index3d]); - float d2 = contour(noise_cave2->result[index3d]); + // Ground + float d1 = contour(noise_cave1->result[index3d]); + float d2 = contour(noise_cave2->result[index3d]); - if (d1 * d2 > m_cave_width && m_ndef->get(c).is_ground_content) { - // In tunnel and ground content, excavate - vm->m_data[vi] = MapNode(CONTENT_AIR); - is_under_tunnel = true; - // If tunnel roof is top or filler, replace with stone - if (is_top_filler_above) - vm->m_data[vi + em.X] = MapNode(biome->c_stone); - is_top_filler_above = false; - } else if (column_is_open && is_under_tunnel && - (c == biome->c_stone || c == biome->c_filler)) { - // Tunnel entrance floor, place biome surface nodes - if (is_under_river) { - if (nplaced < depth_riverbed) { - vm->m_data[vi] = MapNode(biome->c_riverbed); + if (d1 * d2 > m_cave_width && + m_ndef->get(c).is_ground_content) { + // In tunnel and ground content, excavate + vm->m_data[vi] = MapNode(CONTENT_AIR); + is_under_tunnel = true; + // If tunnel roof is top or filler, replace with + // stone + if (is_top_filler_above) + vm->m_data[vi + em.X] = + MapNode(biome->c_stone); + is_top_filler_above = false; + } else if (column_is_open && is_under_tunnel && + (c == biome->c_stone || + c == biome->c_filler)) { + // Tunnel entrance floor, place biome surface + // nodes + if (is_under_river) { + if (nplaced < depth_riverbed) { + vm->m_data[vi] = MapNode( + biome->c_riverbed); + is_top_filler_above = true; + nplaced++; + } else { + // Disable top/filler placement + column_is_open = false; + is_under_river = false; + is_under_tunnel = false; + } + } else if (nplaced < depth_top) { + vm->m_data[vi] = MapNode(biome->c_top); + is_top_filler_above = true; + nplaced++; + } else if (nplaced < base_filler) { + vm->m_data[vi] = MapNode(biome->c_filler); is_top_filler_above = true; nplaced++; } else { // Disable top/filler placement column_is_open = false; - is_under_river = false; is_under_tunnel = false; } - } else if (nplaced < depth_top) { - vm->m_data[vi] = MapNode(biome->c_top); - is_top_filler_above = true; - nplaced++; - } else if (nplaced < base_filler) { - vm->m_data[vi] = MapNode(biome->c_filler); - is_top_filler_above = true; - nplaced++; } else { - // Disable top/filler placement - column_is_open = false; - is_under_tunnel = false; - } - } else { - // Not tunnel or tunnel entrance floor - // Check node for possible replacing with stone for tunnel roof - if (c == biome->c_top || c == biome->c_filler) - is_top_filler_above = true; + // Not tunnel or tunnel entrance floor + // Check node for possible replacing with stone + // for tunnel roof + if (c == biome->c_top || c == biome->c_filler) + is_top_filler_above = true; - column_is_open = false; + column_is_open = false; + } } } - } } - //// //// CavernsNoise //// -CavernsNoise::CavernsNoise( - const NodeDefManager *nodedef, v3s16 chunksize, NoiseParams *np_cavern, - s32 seed, float cavern_limit, float cavern_taper, float cavern_threshold) +CavernsNoise::CavernsNoise(const NodeDefManager *nodedef, v3s16 chunksize, + NoiseParams *np_cavern, s32 seed, float cavern_limit, float cavern_taper, + float cavern_threshold) { assert(nodedef); - m_ndef = nodedef; + m_ndef = nodedef; - m_csize = chunksize; - m_cavern_limit = cavern_limit; - m_cavern_taper = cavern_taper; + m_csize = chunksize; + m_cavern_limit = cavern_limit; + m_cavern_taper = cavern_taper; m_cavern_threshold = cavern_threshold; m_ystride = m_csize.X; @@ -205,13 +208,11 @@ CavernsNoise::CavernsNoise( c_lava_source = CONTENT_AIR; } - CavernsNoise::~CavernsNoise() { delete noise_cavern; } - bool CavernsNoise::generateCaverns(MMVManip *vm, v3s16 nmin, v3s16 nmax) { assert(vm); @@ -221,10 +222,10 @@ bool CavernsNoise::generateCaverns(MMVManip *vm, v3s16 nmin, v3s16 nmax) // Cache cavern_amp values float *cavern_amp = new float[m_csize.Y + 1]; - u8 cavern_amp_index = 0; // Index zero at column top + u8 cavern_amp_index = 0; // Index zero at column top for (s16 y = nmax.Y; y >= nmin.Y - 1; y--, cavern_amp_index++) { cavern_amp[cavern_amp_index] = - MYMIN((m_cavern_limit - y) / (float)m_cavern_taper, 1.0f); + MYMIN((m_cavern_limit - y) / (float)m_cavern_taper, 1.0f); } //// Place nodes @@ -233,65 +234,57 @@ bool CavernsNoise::generateCaverns(MMVManip *vm, v3s16 nmin, v3s16 nmax) u32 index2d = 0; for (s16 z = nmin.Z; z <= nmax.Z; z++) - for (s16 x = nmin.X; x <= nmax.X; x++, index2d++) { - // Reset cave_amp index to column top - cavern_amp_index = 0; - // Initial voxelmanip index at column top - u32 vi = vm->m_area.index(x, nmax.Y, z); - // Initial 3D noise index at column top - u32 index3d = (z - nmin.Z) * m_zstride_1d + m_csize.Y * m_ystride + - (x - nmin.X); - // Don't excavate the overgenerated stone at node_max.Y + 1, - // this creates a 'roof' over the cavern, preventing light in - // caverns at mapchunk borders when generating mapchunks upwards. - // This 'roof' is excavated when the mapchunk above is generated. - for (s16 y = nmax.Y; y >= nmin.Y - 1; y--, - index3d -= m_ystride, - VoxelArea::add_y(em, vi, -1), - cavern_amp_index++) { - content_t c = vm->m_data[vi].getContent(); - float n_absamp_cavern = std::fabs(noise_cavern->result[index3d]) * - cavern_amp[cavern_amp_index]; - // Disable CavesRandomWalk at a safe distance from caverns - // to avoid excessively spreading liquids in caverns. - if (n_absamp_cavern > m_cavern_threshold - 0.1f) { - near_cavern = true; - if (n_absamp_cavern > m_cavern_threshold && - m_ndef->get(c).is_ground_content) - vm->m_data[vi] = MapNode(CONTENT_AIR); + for (s16 x = nmin.X; x <= nmax.X; x++, index2d++) { + // Reset cave_amp index to column top + cavern_amp_index = 0; + // Initial voxelmanip index at column top + u32 vi = vm->m_area.index(x, nmax.Y, z); + // Initial 3D noise index at column top + u32 index3d = (z - nmin.Z) * m_zstride_1d + + m_csize.Y * m_ystride + (x - nmin.X); + // Don't excavate the overgenerated stone at node_max.Y + 1, + // this creates a 'roof' over the cavern, preventing light in + // caverns at mapchunk borders when generating mapchunks upwards. + // This 'roof' is excavated when the mapchunk above is generated. + for (s16 y = nmax.Y; y >= nmin.Y - 1; y--, index3d -= m_ystride, + VoxelArea::add_y(em, vi, -1), cavern_amp_index++) { + content_t c = vm->m_data[vi].getContent(); + float n_absamp_cavern = + std::fabs(noise_cavern->result[index3d]) * + cavern_amp[cavern_amp_index]; + // Disable CavesRandomWalk at a safe distance from caverns + // to avoid excessively spreading liquids in caverns. + if (n_absamp_cavern > m_cavern_threshold - 0.1f) { + near_cavern = true; + if (n_absamp_cavern > m_cavern_threshold && + m_ndef->get(c).is_ground_content) + vm->m_data[vi] = MapNode(CONTENT_AIR); + } } } - } delete[] cavern_amp; return near_cavern; } - //// //// CavesRandomWalk //// -CavesRandomWalk::CavesRandomWalk( - const NodeDefManager *ndef, - GenerateNotifier *gennotify, - s32 seed, - int water_level, - content_t water_source, - content_t lava_source, - float large_cave_flooded, - BiomeGen *biomegen) +CavesRandomWalk::CavesRandomWalk(const NodeDefManager *ndef, GenerateNotifier *gennotify, + s32 seed, int water_level, content_t water_source, content_t lava_source, + float large_cave_flooded, BiomeGen *biomegen) { assert(ndef); - this->ndef = ndef; - this->gennotify = gennotify; - this->seed = seed; - this->water_level = water_level; - this->np_caveliquids = &nparams_caveliquids; + this->ndef = ndef; + this->gennotify = gennotify; + this->seed = seed; + this->water_level = water_level; + this->np_caveliquids = &nparams_caveliquids; this->large_cave_flooded = large_cave_flooded; - this->bmgn = biomegen; + this->bmgn = biomegen; c_water_source = water_source; if (c_water_source == CONTENT_IGNORE) @@ -306,18 +299,17 @@ CavesRandomWalk::CavesRandomWalk( c_lava_source = CONTENT_AIR; } - -void CavesRandomWalk::makeCave(MMVManip *vm, v3s16 nmin, v3s16 nmax, - PseudoRandom *ps, bool is_large_cave, int max_stone_height, s16 *heightmap) +void CavesRandomWalk::makeCave(MMVManip *vm, v3s16 nmin, v3s16 nmax, PseudoRandom *ps, + bool is_large_cave, int max_stone_height, s16 *heightmap) { assert(vm); assert(ps); - this->vm = vm; - this->ps = ps; - this->node_min = nmin; - this->node_max = nmax; - this->heightmap = heightmap; + this->vm = vm; + this->ps = ps; + this->node_min = nmin; + this->node_max = nmax; + this->heightmap = heightmap; this->large_cave = is_large_cave; this->ystride = nmax.X - nmin.X + 1; @@ -333,8 +325,8 @@ void CavesRandomWalk::makeCave(MMVManip *vm, v3s16 nmin, v3s16 nmax, Biome *biome = (Biome *)bmgn->getBiomeAtPoint(midp); if (biome->c_cave_liquid[0] != CONTENT_IGNORE) { use_biome_liquid = true; - c_biome_liquid = - biome->c_cave_liquid[ps->range(0, biome->c_cave_liquid.size() - 1)]; + c_biome_liquid = biome->c_cave_liquid[ps->range( + 0, biome->c_cave_liquid.size() - 1)]; if (c_biome_liquid == CONTENT_AIR) flooded = false; } @@ -403,8 +395,8 @@ void CavesRandomWalk::makeCave(MMVManip *vm, v3s16 nmin, v3s16 nmax, // Add generation notify begin event if (gennotify) { v3s16 abs_pos(of.X + orp.X, of.Y + orp.Y, of.Z + orp.Z); - GenNotifyType notifytype = large_cave ? - GENNOTIFY_LARGECAVE_BEGIN : GENNOTIFY_CAVE_BEGIN; + GenNotifyType notifytype = large_cave ? GENNOTIFY_LARGECAVE_BEGIN + : GENNOTIFY_CAVE_BEGIN; gennotify->addEvent(notifytype, abs_pos); } @@ -415,13 +407,12 @@ void CavesRandomWalk::makeCave(MMVManip *vm, v3s16 nmin, v3s16 nmax, // Add generation notify end event if (gennotify) { v3s16 abs_pos(of.X + orp.X, of.Y + orp.Y, of.Z + orp.Z); - GenNotifyType notifytype = large_cave ? - GENNOTIFY_LARGECAVE_END : GENNOTIFY_CAVE_END; + GenNotifyType notifytype = + large_cave ? GENNOTIFY_LARGECAVE_END : GENNOTIFY_CAVE_END; gennotify->addEvent(notifytype, abs_pos); } } - void CavesRandomWalk::makeTunnel(bool dirswitch) { if (dirswitch && !large_cave) { @@ -440,17 +431,11 @@ void CavesRandomWalk::makeTunnel(bool dirswitch) v3s16 maxlen; if (large_cave) { - maxlen = v3s16( - rs_part_max_length_rs, - rs_part_max_length_rs / 2, - rs_part_max_length_rs - ); + maxlen = v3s16(rs_part_max_length_rs, rs_part_max_length_rs / 2, + rs_part_max_length_rs); } else { - maxlen = v3s16( - rs_part_max_length_rs, - ps->range(1, rs_part_max_length_rs), - rs_part_max_length_rs - ); + maxlen = v3s16(rs_part_max_length_rs, ps->range(1, rs_part_max_length_rs), + rs_part_max_length_rs); } v3f vec; @@ -506,7 +491,6 @@ void CavesRandomWalk::makeTunnel(bool dirswitch) orp = rp; } - void CavesRandomWalk::carveRoute(v3f vec, float f, bool randomize_xz) { MapNode airnode(CONTENT_AIR); @@ -528,13 +512,16 @@ void CavesRandomWalk::carveRoute(v3f vec, float f, bool randomize_xz) if (use_biome_liquid) { liquidnode = c_biome_liquid; } else { - // If cave liquid not defined by biome, fallback to old hardcoded behaviour. - // TODO 'np_caveliquids' is deprecated and should eventually be removed. - // Cave liquids are now defined and located using biome definitions. - float nval = NoisePerlin3D(np_caveliquids, startp.X, - startp.Y, startp.Z, seed); - liquidnode = (nval < 0.40f && node_max.Y < water_level - 256) ? - lavanode : waternode; + // If cave liquid not defined by biome, fallback to old hardcoded + // behaviour. + // TODO 'np_caveliquids' is deprecated and should eventually be + // removed. Cave liquids are now defined and located using biome + // definitions. + float nval = NoisePerlin3D(np_caveliquids, startp.X, startp.Y, + startp.Z, seed); + liquidnode = (nval < 0.40f && node_max.Y < water_level - 256) + ? lavanode + : waternode; } } @@ -549,7 +536,8 @@ void CavesRandomWalk::carveRoute(v3f vec, float f, bool randomize_xz) for (s16 z0 = d0; z0 <= d1; z0++) { s16 si = rs / 2 - MYMAX(0, abs(z0) - rs / 7 - 1); - for (s16 x0 = -si - ps->range(0,1); x0 <= si - 1 + ps->range(0,1); x0++) { + for (s16 x0 = -si - ps->range(0, 1); x0 <= si - 1 + ps->range(0, 1); + x0++) { s16 maxabsxz = MYMAX(abs(x0), abs(z0)); s16 si2 = rs / 2 - MYMAX(0, maxabsxz - rs / 7 - 1); @@ -580,10 +568,15 @@ void CavesRandomWalk::carveRoute(v3f vec, float f, bool randomize_xz) int full_ymin = node_min.Y - MAP_BLOCKSIZE; int full_ymax = node_max.Y + MAP_BLOCKSIZE; - if (flooded && full_ymin < water_level && full_ymax > water_level) - vm->m_data[i] = (p.Y <= water_level) ? waternode : airnode; + if (flooded && full_ymin < water_level && + full_ymax > water_level) + vm->m_data[i] = (p.Y <= water_level) + ? waternode + : airnode; else if (flooded && full_ymax < water_level) - vm->m_data[i] = (p.Y < startp.Y - 4) ? liquidnode : airnode; + vm->m_data[i] = (p.Y < startp.Y - 4) + ? liquidnode + : airnode; else vm->m_data[i] = airnode; } else { @@ -595,11 +588,9 @@ void CavesRandomWalk::carveRoute(v3f vec, float f, bool randomize_xz) } } - inline bool CavesRandomWalk::isPosAboveSurface(v3s16 p) { - if (heightmap != NULL && - p.Z >= node_min.Z && p.Z <= node_max.Z && + if (heightmap != NULL && p.Z >= node_min.Z && p.Z <= node_max.Z && p.X >= node_min.X && p.X <= node_max.X) { u32 index = (p.Z - node_min.Z) * ystride + (p.X - node_min.X); if (heightmap[index] < p.Y) @@ -611,18 +602,17 @@ inline bool CavesRandomWalk::isPosAboveSurface(v3s16 p) return false; } - //// //// CavesV6 //// -CavesV6::CavesV6(const NodeDefManager *ndef, GenerateNotifier *gennotify, - int water_level, content_t water_source, content_t lava_source) +CavesV6::CavesV6(const NodeDefManager *ndef, GenerateNotifier *gennotify, int water_level, + content_t water_source, content_t lava_source) { assert(ndef); - this->ndef = ndef; - this->gennotify = gennotify; + this->ndef = ndef; + this->gennotify = gennotify; this->water_level = water_level; c_water_source = water_source; @@ -638,21 +628,20 @@ CavesV6::CavesV6(const NodeDefManager *ndef, GenerateNotifier *gennotify, c_lava_source = CONTENT_AIR; } - -void CavesV6::makeCave(MMVManip *vm, v3s16 nmin, v3s16 nmax, - PseudoRandom *ps, PseudoRandom *ps2, - bool is_large_cave, int max_stone_height, s16 *heightmap) +void CavesV6::makeCave(MMVManip *vm, v3s16 nmin, v3s16 nmax, PseudoRandom *ps, + PseudoRandom *ps2, bool is_large_cave, int max_stone_height, + s16 *heightmap) { assert(vm); assert(ps); assert(ps2); - this->vm = vm; - this->ps = ps; - this->ps2 = ps2; - this->node_min = nmin; - this->node_max = nmax; - this->heightmap = heightmap; + this->vm = vm; + this->ps = ps; + this->ps2 = ps2; + this->node_min = nmin; + this->node_max = nmax; + this->heightmap = heightmap; this->large_cave = is_large_cave; this->ystride = nmax.X - nmin.X + 1; @@ -660,10 +649,10 @@ void CavesV6::makeCave(MMVManip *vm, v3s16 nmin, v3s16 nmax, // Set initial parameters from randomness min_tunnel_diameter = 2; max_tunnel_diameter = ps->range(2, 6); - int dswitchint = ps->range(1, 14); + int dswitchint = ps->range(1, 14); if (large_cave) { - part_max_length_rs = ps->range(2, 4); - tunnel_routepoints = ps->range(5, ps->range(15, 30)); + part_max_length_rs = ps->range(2, 4); + tunnel_routepoints = ps->range(5, ps->range(15, 30)); min_tunnel_diameter = 5; max_tunnel_diameter = ps->range(7, ps->range(8, 24)); } else { @@ -718,8 +707,8 @@ void CavesV6::makeCave(MMVManip *vm, v3s16 nmin, v3s16 nmax, // Add generation notify begin event if (gennotify != NULL) { v3s16 abs_pos(of.X + orp.X, of.Y + orp.Y, of.Z + orp.Z); - GenNotifyType notifytype = large_cave ? - GENNOTIFY_LARGECAVE_BEGIN : GENNOTIFY_CAVE_BEGIN; + GenNotifyType notifytype = large_cave ? GENNOTIFY_LARGECAVE_BEGIN + : GENNOTIFY_CAVE_BEGIN; gennotify->addEvent(notifytype, abs_pos); } @@ -730,13 +719,12 @@ void CavesV6::makeCave(MMVManip *vm, v3s16 nmin, v3s16 nmax, // Add generation notify end event if (gennotify != NULL) { v3s16 abs_pos(of.X + orp.X, of.Y + orp.Y, of.Z + orp.Z); - GenNotifyType notifytype = large_cave ? - GENNOTIFY_LARGECAVE_END : GENNOTIFY_CAVE_END; + GenNotifyType notifytype = + large_cave ? GENNOTIFY_LARGECAVE_END : GENNOTIFY_CAVE_END; gennotify->addEvent(notifytype, abs_pos); } } - void CavesV6::makeTunnel(bool dirswitch) { if (dirswitch && !large_cave) { @@ -755,17 +743,11 @@ void CavesV6::makeTunnel(bool dirswitch) v3s16 maxlen; if (large_cave) { - maxlen = v3s16( - rs_part_max_length_rs, - rs_part_max_length_rs / 2, - rs_part_max_length_rs - ); + maxlen = v3s16(rs_part_max_length_rs, rs_part_max_length_rs / 2, + rs_part_max_length_rs); } else { - maxlen = v3s16( - rs_part_max_length_rs, - ps->range(1, rs_part_max_length_rs), - rs_part_max_length_rs - ); + maxlen = v3s16(rs_part_max_length_rs, ps->range(1, rs_part_max_length_rs), + rs_part_max_length_rs); } v3f vec; @@ -789,9 +771,8 @@ void CavesV6::makeTunnel(bool dirswitch) // If startpoint and endpoint are above ground, disable placement of nodes // in carveRoute while still running all PseudoRandom calls to ensure caves // are consistent with existing worlds. - bool tunnel_above_ground = - p1.Y > getSurfaceFromHeightmap(p1) && - p2.Y > getSurfaceFromHeightmap(p2); + bool tunnel_above_ground = p1.Y > getSurfaceFromHeightmap(p1) && + p2.Y > getSurfaceFromHeightmap(p2); vec += main_direction; @@ -828,9 +809,7 @@ void CavesV6::makeTunnel(bool dirswitch) orp = rp; } - -void CavesV6::carveRoute(v3f vec, float f, bool randomize_xz, - bool tunnel_above_ground) +void CavesV6::carveRoute(v3f vec, float f, bool randomize_xz, bool tunnel_above_ground) { MapNode airnode(CONTENT_AIR); MapNode waternode(c_water_source); @@ -853,7 +832,8 @@ void CavesV6::carveRoute(v3f vec, float f, bool randomize_xz, for (s16 z0 = d0; z0 <= d1; z0++) { s16 si = rs / 2 - MYMAX(0, abs(z0) - rs / 7 - 1); - for (s16 x0 = -si - ps->range(0,1); x0 <= si - 1 + ps->range(0,1); x0++) { + for (s16 x0 = -si - ps->range(0, 1); x0 <= si - 1 + ps->range(0, 1); + x0++) { if (tunnel_above_ground) continue; @@ -881,10 +861,15 @@ void CavesV6::carveRoute(v3f vec, float f, bool randomize_xz, int full_ymin = node_min.Y - MAP_BLOCKSIZE; int full_ymax = node_max.Y + MAP_BLOCKSIZE; - if (full_ymin < water_level && full_ymax > water_level) { - vm->m_data[i] = (p.Y <= water_level) ? waternode : airnode; + if (full_ymin < water_level && + full_ymax > water_level) { + vm->m_data[i] = (p.Y <= water_level) + ? waternode + : airnode; } else if (full_ymax < water_level) { - vm->m_data[i] = (p.Y < startp.Y - 2) ? lavanode : airnode; + vm->m_data[i] = (p.Y < startp.Y - 2) + ? lavanode + : airnode; } else { vm->m_data[i] = airnode; } @@ -900,16 +885,13 @@ void CavesV6::carveRoute(v3f vec, float f, bool randomize_xz, } } - inline s16 CavesV6::getSurfaceFromHeightmap(v3s16 p) { - if (heightmap != NULL && - p.Z >= node_min.Z && p.Z <= node_max.Z && + if (heightmap != NULL && p.Z >= node_min.Z && p.Z <= node_max.Z && p.X >= node_min.X && p.X <= node_max.X) { u32 index = (p.Z - node_min.Z) * ystride + (p.X - node_min.X); return heightmap[index]; } return water_level; - } diff --git a/src/mapgen/cavegen.h b/src/mapgen/cavegen.h index d678d365b..876c45936 100644 --- a/src/mapgen/cavegen.h +++ b/src/mapgen/cavegen.h @@ -22,7 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #define VMANIP_FLAG_CAVE VOXELFLAG_CHECKED1 -typedef u16 biome_t; // copy from mg_biome.h to avoid an unnecessary include +typedef u16 biome_t; // copy from mg_biome.h to avoid an unnecessary include class GenerateNotifier; @@ -41,9 +41,9 @@ class GenerateNotifier; class CavesNoiseIntersection { public: - CavesNoiseIntersection(const NodeDefManager *nodedef, - BiomeManager *biomemgr, v3s16 chunksize, NoiseParams *np_cave1, - NoiseParams *np_cave2, s32 seed, float cave_width); + CavesNoiseIntersection(const NodeDefManager *nodedef, BiomeManager *biomemgr, + v3s16 chunksize, NoiseParams *np_cave1, NoiseParams *np_cave2, + s32 seed, float cave_width); ~CavesNoiseIntersection(); void generateCaves(MMVManip *vm, v3s16 nmin, v3s16 nmax, biome_t *biomemap); @@ -71,8 +71,8 @@ class CavernsNoise { public: CavernsNoise(const NodeDefManager *nodedef, v3s16 chunksize, - NoiseParams *np_cavern, s32 seed, float cavern_limit, - float cavern_taper, float cavern_threshold); + NoiseParams *np_cavern, s32 seed, float cavern_limit, + float cavern_taper, float cavern_threshold); ~CavernsNoise(); bool generateCaverns(MMVManip *vm, v3s16 nmin, v3s16 nmax); @@ -143,7 +143,7 @@ public: v3f orp; // starting point, relative to caved space v3s16 of; // absolute coordinates of caved space v3s16 ar; // allowed route area - s16 rs; // tunnel radius size + s16 rs; // tunnel radius size v3f main_direction; s16 route_y_min; @@ -158,10 +158,11 @@ public: // ndef is a mandatory parameter. // If gennotify is NULL, generation events are not logged. // If biomegen is NULL, cave liquids have classic behaviour. - CavesRandomWalk(const NodeDefManager *ndef, GenerateNotifier *gennotify = - NULL, s32 seed = 0, int water_level = 1, content_t water_source = - CONTENT_IGNORE, content_t lava_source = CONTENT_IGNORE, - float large_cave_flooded = 0.5f, BiomeGen *biomegen = NULL); + CavesRandomWalk(const NodeDefManager *ndef, GenerateNotifier *gennotify = NULL, + s32 seed = 0, int water_level = 1, + content_t water_source = CONTENT_IGNORE, + content_t lava_source = CONTENT_IGNORE, + float large_cave_flooded = 0.5f, BiomeGen *biomegen = NULL); // vm and ps are mandatory parameters. // If heightmap is NULL, the surface level at all points is assumed to @@ -221,7 +222,7 @@ public: v3f orp; // starting point, relative to caved space v3s16 of; // absolute coordinates of caved space v3s16 ar; // allowed route area - s16 rs; // tunnel radius size + s16 rs; // tunnel radius size v3f main_direction; s16 route_y_min; diff --git a/src/mapgen/dungeongen.cpp b/src/mapgen/dungeongen.cpp index acdb1a0f0..521eb7f7b 100644 --- a/src/mapgen/dungeongen.cpp +++ b/src/mapgen/dungeongen.cpp @@ -31,20 +31,18 @@ with this program; if not, write to the Free Software Foundation, Inc., //#define DGEN_USE_TORCHES - /////////////////////////////////////////////////////////////////////////////// - -DungeonGen::DungeonGen(const NodeDefManager *ndef, - GenerateNotifier *gennotify, DungeonParams *dparams) +DungeonGen::DungeonGen(const NodeDefManager *ndef, GenerateNotifier *gennotify, + DungeonParams *dparams) { assert(ndef); - this->ndef = ndef; + this->ndef = ndef; this->gennotify = gennotify; #ifdef DGEN_USE_TORCHES - c_torch = ndef->getId("default:torch"); + c_torch = ndef->getId("default:torch"); #endif if (dparams) { @@ -53,30 +51,29 @@ DungeonGen::DungeonGen(const NodeDefManager *ndef, // Default dungeon parameters dp.seed = 0; - dp.c_wall = ndef->getId("mapgen_cobble"); + dp.c_wall = ndef->getId("mapgen_cobble"); dp.c_alt_wall = ndef->getId("mapgen_mossycobble"); - dp.c_stair = ndef->getId("mapgen_stair_cobble"); + dp.c_stair = ndef->getId("mapgen_stair_cobble"); - dp.diagonal_dirs = false; - dp.only_in_ground = true; - dp.holesize = v3s16(1, 2, 1); - dp.corridor_len_min = 1; - dp.corridor_len_max = 13; - dp.room_size_min = v3s16(4, 4, 4); - dp.room_size_max = v3s16(8, 6, 8); + dp.diagonal_dirs = false; + dp.only_in_ground = true; + dp.holesize = v3s16(1, 2, 1); + dp.corridor_len_min = 1; + dp.corridor_len_max = 13; + dp.room_size_min = v3s16(4, 4, 4); + dp.room_size_max = v3s16(8, 6, 8); dp.room_size_large_min = v3s16(8, 8, 8); dp.room_size_large_max = v3s16(16, 16, 16); - dp.large_room_chance = 1; - dp.num_rooms = 8; - dp.num_dungeons = 1; - dp.notifytype = GENNOTIFY_DUNGEON; + dp.large_room_chance = 1; + dp.num_rooms = 8; + dp.num_dungeons = 1; + dp.notifytype = GENNOTIFY_DUNGEON; - dp.np_alt_wall = - NoiseParams(-0.4, 1.0, v3f(40.0, 40.0, 40.0), 32474, 6, 1.1, 2.0); + dp.np_alt_wall = NoiseParams( + -0.4, 1.0, v3f(40.0, 40.0, 40.0), 32474, 6, 1.1, 2.0); } } - void DungeonGen::generate(MMVManip *vm, u32 bseed, v3s16 nmin, v3s16 nmax) { if (dp.num_dungeons == 0) @@ -84,7 +81,7 @@ void DungeonGen::generate(MMVManip *vm, u32 bseed, v3s16 nmin, v3s16 nmax) assert(vm); - //TimeTaker t("gen dungeons"); + // TimeTaker t("gen dungeons"); this->vm = vm; this->blockseed = bseed; @@ -94,13 +91,13 @@ void DungeonGen::generate(MMVManip *vm, u32 bseed, v3s16 nmin, v3s16 nmax) vm->clearFlag(VMANIP_FLAG_DUNGEON_INSIDE | VMANIP_FLAG_DUNGEON_PRESERVE); if (dp.only_in_ground) { - // Set all air and liquid drawtypes to be untouchable to make dungeons generate - // in ground only. - // Set 'ignore' to be untouchable to prevent generation in ungenerated neighbor - // mapchunks, to avoid dungeon rooms generating outside ground. - // Like randomwalk caves, preserve nodes that have 'is_ground_content = false', - // to avoid dungeons that generate out beyond the edge of a mapchunk destroying - // nodes added by mods in 'register_on_generated()'. + // Set all air and liquid drawtypes to be untouchable to make dungeons + // generate in ground only. Set 'ignore' to be untouchable to prevent + // generation in ungenerated neighbor mapchunks, to avoid dungeon rooms + // generating outside ground. Like randomwalk caves, preserve nodes that + // have 'is_ground_content = false', to avoid dungeons that generate out + // beyond the edge of a mapchunk destroying nodes added by mods in + // 'register_on_generated()'. for (s16 z = nmin.Z; z <= nmax.Z; z++) { for (s16 y = nmin.Y; y <= nmax.Y; y++) { u32 i = vm->m_area.index(nmin.X, y, z); @@ -108,8 +105,10 @@ void DungeonGen::generate(MMVManip *vm, u32 bseed, v3s16 nmin, v3s16 nmax) content_t c = vm->m_data[i].getContent(); NodeDrawType dtype = ndef->get(c).drawtype; if (dtype == NDT_AIRLIKE || dtype == NDT_LIQUID || - c == CONTENT_IGNORE || !ndef->get(c).is_ground_content) - vm->m_flags[i] |= VMANIP_FLAG_DUNGEON_PRESERVE; + c == CONTENT_IGNORE || + !ndef->get(c).is_ground_content) + vm->m_flags[i] |= + VMANIP_FLAG_DUNGEON_PRESERVE; i++; } } @@ -125,21 +124,21 @@ void DungeonGen::generate(MMVManip *vm, u32 bseed, v3s16 nmin, v3s16 nmax) return; for (s16 z = nmin.Z; z <= nmax.Z; z++) - for (s16 y = nmin.Y; y <= nmax.Y; y++) { - u32 i = vm->m_area.index(nmin.X, y, z); - for (s16 x = nmin.X; x <= nmax.X; x++) { - if (vm->m_data[i].getContent() == dp.c_wall) { - if (NoisePerlin3D(&dp.np_alt_wall, x, y, z, blockseed) > 0.0f) - vm->m_data[i].setContent(dp.c_alt_wall); + for (s16 y = nmin.Y; y <= nmax.Y; y++) { + u32 i = vm->m_area.index(nmin.X, y, z); + for (s16 x = nmin.X; x <= nmax.X; x++) { + if (vm->m_data[i].getContent() == dp.c_wall) { + if (NoisePerlin3D(&dp.np_alt_wall, x, y, z, + blockseed) > 0.0f) + vm->m_data[i].setContent(dp.c_alt_wall); + } + i++; } - i++; } - } - //printf("== gen dungeons: %dms\n", t.stop()); + // printf("== gen dungeons: %dms\n", t.stop()); } - void DungeonGen::makeDungeon(v3s16 start_padding) { const v3s16 &areasize = vm->m_area.getExtent(); @@ -152,9 +151,12 @@ void DungeonGen::makeDungeon(v3s16 start_padding) bool fits = false; for (u32 i = 0; i < 100 && !fits; i++) { if (dp.large_room_chance >= 1) { - roomsize.Z = random.range(dp.room_size_large_min.Z, dp.room_size_large_max.Z); - roomsize.Y = random.range(dp.room_size_large_min.Y, dp.room_size_large_max.Y); - roomsize.X = random.range(dp.room_size_large_min.X, dp.room_size_large_max.X); + roomsize.Z = random.range(dp.room_size_large_min.Z, + dp.room_size_large_max.Z); + roomsize.Y = random.range(dp.room_size_large_min.Y, + dp.room_size_large_max.Y); + roomsize.X = random.range(dp.room_size_large_min.X, + dp.room_size_large_max.X); } else { roomsize.Z = random.range(dp.room_size_min.Z, dp.room_size_max.Z); roomsize.Y = random.range(dp.room_size_min.Y, dp.room_size_max.Y); @@ -174,16 +176,18 @@ void DungeonGen::makeDungeon(v3s16 start_padding) */ fits = true; for (s16 z = 0; z < roomsize.Z; z++) - for (s16 y = 0; y < roomsize.Y; y++) - for (s16 x = 0; x < roomsize.X; x++) { - v3s16 p = roomplace + v3s16(x, y, z); - u32 vi = vm->m_area.index(p); - if ((vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE) || - vm->m_data[vi].getContent() == CONTENT_IGNORE) { - fits = false; - break; - } - } + for (s16 y = 0; y < roomsize.Y; y++) + for (s16 x = 0; x < roomsize.X; x++) { + v3s16 p = roomplace + v3s16(x, y, z); + u32 vi = vm->m_area.index(p); + if ((vm->m_flags[vi] & + VMANIP_FLAG_DUNGEON_UNTOUCHABLE) || + vm->m_data[vi].getContent() == + CONTENT_IGNORE) { + fits = false; + break; + } + } } // No place found if (!fits) @@ -248,11 +252,15 @@ void DungeonGen::makeDungeon(v3s16 start_padding) makeCorridor(doorplace, doordir, corridor_end, corridor_end_dir); // Find a place for a random sized room - if (dp.large_room_chance > 1 && random.range(1, dp.large_room_chance) == 1) { + if (dp.large_room_chance > 1 && + random.range(1, dp.large_room_chance) == 1) { // Large room - roomsize.Z = random.range(dp.room_size_large_min.Z, dp.room_size_large_max.Z); - roomsize.Y = random.range(dp.room_size_large_min.Y, dp.room_size_large_max.Y); - roomsize.X = random.range(dp.room_size_large_min.X, dp.room_size_large_max.X); + roomsize.Z = random.range(dp.room_size_large_min.Z, + dp.room_size_large_max.Z); + roomsize.Y = random.range(dp.room_size_large_min.Y, + dp.room_size_large_max.Y); + roomsize.X = random.range(dp.room_size_large_min.X, + dp.room_size_large_max.X); } else { roomsize.Z = random.range(dp.room_size_min.Z, dp.room_size_max.Z); roomsize.Y = random.range(dp.room_size_min.Y, dp.room_size_max.Y); @@ -273,7 +281,6 @@ void DungeonGen::makeDungeon(v3s16 start_padding) } } - void DungeonGen::makeRoom(v3s16 roomsize, v3s16 roomplace) { MapNode n_wall(dp.c_wall); @@ -281,112 +288,107 @@ void DungeonGen::makeRoom(v3s16 roomsize, v3s16 roomplace) // Make +-X walls for (s16 z = 0; z < roomsize.Z; z++) - for (s16 y = 0; y < roomsize.Y; y++) { - { - v3s16 p = roomplace + v3s16(0, y, z); - if (!vm->m_area.contains(p)) - continue; - u32 vi = vm->m_area.index(p); - if (vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE) - continue; - vm->m_data[vi] = n_wall; + for (s16 y = 0; y < roomsize.Y; y++) { + { + v3s16 p = roomplace + v3s16(0, y, z); + if (!vm->m_area.contains(p)) + continue; + u32 vi = vm->m_area.index(p); + if (vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE) + continue; + vm->m_data[vi] = n_wall; + } + { + v3s16 p = roomplace + v3s16(roomsize.X - 1, y, z); + if (!vm->m_area.contains(p)) + continue; + u32 vi = vm->m_area.index(p); + if (vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE) + continue; + vm->m_data[vi] = n_wall; + } } - { - v3s16 p = roomplace + v3s16(roomsize.X - 1, y, z); - if (!vm->m_area.contains(p)) - continue; - u32 vi = vm->m_area.index(p); - if (vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE) - continue; - vm->m_data[vi] = n_wall; - } - } // Make +-Z walls for (s16 x = 0; x < roomsize.X; x++) - for (s16 y = 0; y < roomsize.Y; y++) { - { - v3s16 p = roomplace + v3s16(x, y, 0); - if (!vm->m_area.contains(p)) - continue; - u32 vi = vm->m_area.index(p); - if (vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE) - continue; - vm->m_data[vi] = n_wall; + for (s16 y = 0; y < roomsize.Y; y++) { + { + v3s16 p = roomplace + v3s16(x, y, 0); + if (!vm->m_area.contains(p)) + continue; + u32 vi = vm->m_area.index(p); + if (vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE) + continue; + vm->m_data[vi] = n_wall; + } + { + v3s16 p = roomplace + v3s16(x, y, roomsize.Z - 1); + if (!vm->m_area.contains(p)) + continue; + u32 vi = vm->m_area.index(p); + if (vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE) + continue; + vm->m_data[vi] = n_wall; + } } - { - v3s16 p = roomplace + v3s16(x, y, roomsize.Z - 1); - if (!vm->m_area.contains(p)) - continue; - u32 vi = vm->m_area.index(p); - if (vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE) - continue; - vm->m_data[vi] = n_wall; - } - } // Make +-Y walls (floor and ceiling) for (s16 z = 0; z < roomsize.Z; z++) - for (s16 x = 0; x < roomsize.X; x++) { - { - v3s16 p = roomplace + v3s16(x, 0, z); - if (!vm->m_area.contains(p)) - continue; - u32 vi = vm->m_area.index(p); - if (vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE) - continue; - vm->m_data[vi] = n_wall; + for (s16 x = 0; x < roomsize.X; x++) { + { + v3s16 p = roomplace + v3s16(x, 0, z); + if (!vm->m_area.contains(p)) + continue; + u32 vi = vm->m_area.index(p); + if (vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE) + continue; + vm->m_data[vi] = n_wall; + } + { + v3s16 p = roomplace + v3s16(x, roomsize.Y - 1, z); + if (!vm->m_area.contains(p)) + continue; + u32 vi = vm->m_area.index(p); + if (vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE) + continue; + vm->m_data[vi] = n_wall; + } } - { - v3s16 p = roomplace + v3s16(x,roomsize. Y - 1, z); - if (!vm->m_area.contains(p)) - continue; - u32 vi = vm->m_area.index(p); - if (vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE) - continue; - vm->m_data[vi] = n_wall; - } - } // Fill with air for (s16 z = 1; z < roomsize.Z - 1; z++) - for (s16 y = 1; y < roomsize.Y - 1; y++) - for (s16 x = 1; x < roomsize.X - 1; x++) { - v3s16 p = roomplace + v3s16(x, y, z); - if (!vm->m_area.contains(p)) - continue; - u32 vi = vm->m_area.index(p); - vm->m_flags[vi] |= VMANIP_FLAG_DUNGEON_UNTOUCHABLE; - vm->m_data[vi] = n_air; - } + for (s16 y = 1; y < roomsize.Y - 1; y++) + for (s16 x = 1; x < roomsize.X - 1; x++) { + v3s16 p = roomplace + v3s16(x, y, z); + if (!vm->m_area.contains(p)) + continue; + u32 vi = vm->m_area.index(p); + vm->m_flags[vi] |= VMANIP_FLAG_DUNGEON_UNTOUCHABLE; + vm->m_data[vi] = n_air; + } } - -void DungeonGen::makeFill(v3s16 place, v3s16 size, - u8 avoid_flags, MapNode n, u8 or_flags) +void DungeonGen::makeFill(v3s16 place, v3s16 size, u8 avoid_flags, MapNode n, u8 or_flags) { for (s16 z = 0; z < size.Z; z++) - for (s16 y = 0; y < size.Y; y++) - for (s16 x = 0; x < size.X; x++) { - v3s16 p = place + v3s16(x, y, z); - if (!vm->m_area.contains(p)) - continue; - u32 vi = vm->m_area.index(p); - if (vm->m_flags[vi] & avoid_flags) - continue; - vm->m_flags[vi] |= or_flags; - vm->m_data[vi] = n; - } + for (s16 y = 0; y < size.Y; y++) + for (s16 x = 0; x < size.X; x++) { + v3s16 p = place + v3s16(x, y, z); + if (!vm->m_area.contains(p)) + continue; + u32 vi = vm->m_area.index(p); + if (vm->m_flags[vi] & avoid_flags) + continue; + vm->m_flags[vi] |= or_flags; + vm->m_data[vi] = n; + } } - void DungeonGen::makeHole(v3s16 place) { - makeFill(place, dp.holesize, 0, MapNode(CONTENT_AIR), - VMANIP_FLAG_DUNGEON_INSIDE); + makeFill(place, dp.holesize, 0, MapNode(CONTENT_AIR), VMANIP_FLAG_DUNGEON_INSIDE); } - void DungeonGen::makeDoor(v3s16 doorplace, v3s16 doordir) { makeHole(doorplace); @@ -397,9 +399,8 @@ void DungeonGen::makeDoor(v3s16 doorplace, v3s16 doordir) #endif } - -void DungeonGen::makeCorridor(v3s16 doorplace, v3s16 doordir, - v3s16 &result_place, v3s16 &result_dir) +void DungeonGen::makeCorridor( + v3s16 doorplace, v3s16 doordir, v3s16 &result_place, v3s16 &result_dir) { makeHole(doorplace); v3s16 p0 = doorplace; @@ -421,14 +422,16 @@ void DungeonGen::makeCorridor(v3s16 doorplace, v3s16 doordir, if (vm->m_area.contains(p) && vm->m_area.contains(p + v3s16(0, 1, 0))) { if (make_stairs) { makeFill(p + v3s16(-1, -1, -1), - dp.holesize + v3s16(2, 3, 2), - VMANIP_FLAG_DUNGEON_UNTOUCHABLE, - MapNode(dp.c_wall), - 0); + dp.holesize + v3s16(2, 3, 2), + VMANIP_FLAG_DUNGEON_UNTOUCHABLE, + MapNode(dp.c_wall), 0); makeFill(p, dp.holesize, VMANIP_FLAG_DUNGEON_UNTOUCHABLE, - MapNode(CONTENT_AIR), VMANIP_FLAG_DUNGEON_INSIDE); - makeFill(p - dir, dp.holesize, VMANIP_FLAG_DUNGEON_UNTOUCHABLE, - MapNode(CONTENT_AIR), VMANIP_FLAG_DUNGEON_INSIDE); + MapNode(CONTENT_AIR), + VMANIP_FLAG_DUNGEON_INSIDE); + makeFill(p - dir, dp.holesize, + VMANIP_FLAG_DUNGEON_UNTOUCHABLE, + MapNode(CONTENT_AIR), + VMANIP_FLAG_DUNGEON_INSIDE); // TODO: fix stairs code so it works 100% // (quite difficult) @@ -436,30 +439,52 @@ void DungeonGen::makeCorridor(v3s16 doorplace, v3s16 doordir, // exclude stairs from the bottom step // exclude stairs from diagonal steps if (((dir.X ^ dir.Z) & 1) && - (((make_stairs == 1) && i != 0) || - ((make_stairs == -1) && i != length - 1))) { + (((make_stairs == 1) && i != 0) || + ((make_stairs == -1) && + i != length - 1))) { // rotate face 180 deg if // making stairs backwards int facedir = dir_to_facedir(dir * make_stairs); v3s16 ps = p; - u16 stair_width = (dir.Z != 0) ? dp.holesize.X : dp.holesize.Z; + u16 stair_width = (dir.Z != 0) ? dp.holesize.X + : dp.holesize.Z; // Stair width direction vector - v3s16 swv = (dir.Z != 0) ? v3s16(1, 0, 0) : v3s16(0, 0, 1); + v3s16 swv = (dir.Z != 0) ? v3s16(1, 0, 0) + : v3s16(0, 0, 1); for (u16 st = 0; st < stair_width; st++) { if (make_stairs == -1) { - u32 vi = vm->m_area.index(ps.X - dir.X, ps.Y - 1, ps.Z - dir.Z); - if (vm->m_area.contains(ps + v3s16(-dir.X, -1, -dir.Z)) && - vm->m_data[vi].getContent() == dp.c_wall) { - vm->m_flags[vi] |= VMANIP_FLAG_DUNGEON_UNTOUCHABLE; - vm->m_data[vi] = MapNode(dp.c_stair, 0, facedir); + u32 vi = vm->m_area.index( + ps.X - dir.X, + ps.Y - 1, + ps.Z - dir.Z); + if (vm->m_area.contains( + ps + + v3s16(-dir.X, -1, + -dir.Z)) && + vm->m_data[vi].getContent() == + dp.c_wall) { + vm->m_flags[vi] |= + VMANIP_FLAG_DUNGEON_UNTOUCHABLE; + vm->m_data[vi] = MapNode( + dp.c_stair, + 0, + facedir); } } else if (make_stairs == 1) { - u32 vi = vm->m_area.index(ps.X, ps.Y - 1, ps.Z); - if (vm->m_area.contains(ps + v3s16(0, -1, 0)) && - vm->m_data[vi].getContent() == dp.c_wall) { - vm->m_flags[vi] |= VMANIP_FLAG_DUNGEON_UNTOUCHABLE; - vm->m_data[vi] = MapNode(dp.c_stair, 0, facedir); + u32 vi = vm->m_area.index(ps.X, + ps.Y - 1, ps.Z); + if (vm->m_area.contains( + ps + + v3s16(0, -1, 0)) && + vm->m_data[vi].getContent() == + dp.c_wall) { + vm->m_flags[vi] |= + VMANIP_FLAG_DUNGEON_UNTOUCHABLE; + vm->m_data[vi] = MapNode( + dp.c_stair, + 0, + facedir); } } ps += swv; @@ -467,10 +492,9 @@ void DungeonGen::makeCorridor(v3s16 doorplace, v3s16 doordir, } } else { makeFill(p + v3s16(-1, -1, -1), - dp.holesize + v3s16(2, 2, 2), - VMANIP_FLAG_DUNGEON_UNTOUCHABLE, - MapNode(dp.c_wall), - 0); + dp.holesize + v3s16(2, 2, 2), + VMANIP_FLAG_DUNGEON_UNTOUCHABLE, + MapNode(dp.c_wall), 0); makeHole(p); } @@ -501,7 +525,6 @@ void DungeonGen::makeCorridor(v3s16 doorplace, v3s16 doordir, result_dir = dir; } - bool DungeonGen::findPlaceForDoor(v3s16 &result_place, v3s16 &result_dir) { for (u32 i = 0; i < 100; i++) { @@ -524,25 +547,30 @@ bool DungeonGen::findPlaceForDoor(v3s16 &result_place, v3s16 &result_dir) Determine where to move next */ // Jump one up if the actual space is there - if (vm->getNodeNoExNoEmerge(p + - v3s16(0, 0, 0)).getContent() == dp.c_wall && - vm->getNodeNoExNoEmerge(p + - v3s16(0, 1, 0)).getContent() == CONTENT_AIR && - vm->getNodeNoExNoEmerge(p + - v3s16(0, 2, 0)).getContent() == CONTENT_AIR) - p += v3s16(0,1,0); + if (vm->getNodeNoExNoEmerge(p + v3s16(0, 0, 0)).getContent() == + dp.c_wall && + vm->getNodeNoExNoEmerge(p + v3s16(0, 1, 0)) + .getContent() == + CONTENT_AIR && + vm->getNodeNoExNoEmerge(p + v3s16(0, 2, 0)) + .getContent() == + CONTENT_AIR) + p += v3s16(0, 1, 0); // Jump one down if the actual space is there - if (vm->getNodeNoExNoEmerge(p + - v3s16(0, 1, 0)).getContent() == dp.c_wall && - vm->getNodeNoExNoEmerge(p + - v3s16(0, 0, 0)).getContent() == CONTENT_AIR && - vm->getNodeNoExNoEmerge(p + - v3s16(0, -1, 0)).getContent() == CONTENT_AIR) + if (vm->getNodeNoExNoEmerge(p + v3s16(0, 1, 0)).getContent() == + dp.c_wall && + vm->getNodeNoExNoEmerge(p + v3s16(0, 0, 0)) + .getContent() == + CONTENT_AIR && + vm->getNodeNoExNoEmerge(p + v3s16(0, -1, 0)) + .getContent() == + CONTENT_AIR) p += v3s16(0, -1, 0); // Check if walking is now possible if (vm->getNodeNoExNoEmerge(p).getContent() != CONTENT_AIR || - vm->getNodeNoExNoEmerge(p + - v3s16(0, 1, 0)).getContent() != CONTENT_AIR) { + vm->getNodeNoExNoEmerge(p + v3s16(0, 1, 0)) + .getContent() != + CONTENT_AIR) { // Cannot continue walking here randomizeDir(); continue; @@ -553,9 +581,8 @@ bool DungeonGen::findPlaceForDoor(v3s16 &result_place, v3s16 &result_dir) return false; } - bool DungeonGen::findPlaceForRoomDoor(v3s16 roomsize, v3s16 &result_doorplace, - v3s16 &result_doordir, v3s16 &result_roomplace) + v3s16 &result_doordir, v3s16 &result_roomplace) { for (s16 trycount = 0; trycount < 30; trycount++) { v3s16 doorplace; @@ -567,45 +594,46 @@ bool DungeonGen::findPlaceForRoomDoor(v3s16 roomsize, v3s16 &result_doorplace, // X east, Z north, Y up if (doordir == v3s16(1, 0, 0)) // X+ roomplace = doorplace + - v3s16(0, -1, random.range(-roomsize.Z + 2, -2)); + v3s16(0, -1, random.range(-roomsize.Z + 2, -2)); if (doordir == v3s16(-1, 0, 0)) // X- roomplace = doorplace + - v3s16(-roomsize.X + 1, -1, random.range(-roomsize.Z + 2, -2)); + v3s16(-roomsize.X + 1, -1, + random.range(-roomsize.Z + 2, -2)); if (doordir == v3s16(0, 0, 1)) // Z+ roomplace = doorplace + - v3s16(random.range(-roomsize.X + 2, -2), -1, 0); + v3s16(random.range(-roomsize.X + 2, -2), -1, 0); if (doordir == v3s16(0, 0, -1)) // Z- - roomplace = doorplace + - v3s16(random.range(-roomsize.X + 2, -2), -1, -roomsize.Z + 1); + roomplace = doorplace + v3s16(random.range(-roomsize.X + 2, -2), + -1, -roomsize.Z + 1); // Check fit bool fits = true; for (s16 z = 1; z < roomsize.Z - 1; z++) - for (s16 y = 1; y < roomsize.Y - 1; y++) - for (s16 x = 1; x < roomsize.X - 1; x++) { - v3s16 p = roomplace + v3s16(x, y, z); - if (!vm->m_area.contains(p)) { - fits = false; - break; - } - if (vm->m_flags[vm->m_area.index(p)] & VMANIP_FLAG_DUNGEON_INSIDE) { - fits = false; - break; - } - } + for (s16 y = 1; y < roomsize.Y - 1; y++) + for (s16 x = 1; x < roomsize.X - 1; x++) { + v3s16 p = roomplace + v3s16(x, y, z); + if (!vm->m_area.contains(p)) { + fits = false; + break; + } + if (vm->m_flags[vm->m_area.index(p)] & + VMANIP_FLAG_DUNGEON_INSIDE) { + fits = false; + break; + } + } if (!fits) { // Find new place continue; } result_doorplace = doorplace; - result_doordir = doordir; + result_doordir = doordir; result_roomplace = roomplace; return true; } return false; } - v3s16 rand_ortho_dir(PseudoRandom &random, bool diagonal_dirs) { // Make diagonal directions somewhat rare @@ -630,7 +658,6 @@ v3s16 rand_ortho_dir(PseudoRandom &random, bool diagonal_dirs) return random.next() % 2 ? v3s16(0, 0, -1) : v3s16(0, 0, 1); } - v3s16 turn_xz(v3s16 olddir, int t) { v3s16 dir; @@ -648,7 +675,6 @@ v3s16 turn_xz(v3s16 olddir, int t) return dir; } - void random_turn(PseudoRandom &random, v3s16 &dir) { int turn = random.range(0, 2); @@ -664,7 +690,6 @@ void random_turn(PseudoRandom &random, v3s16 &dir) } } - int dir_to_facedir(v3s16 d) { if (abs(d.X) > abs(d.Z)) diff --git a/src/mapgen/dungeongen.h b/src/mapgen/dungeongen.h index 35e6beef5..f70fedb5e 100644 --- a/src/mapgen/dungeongen.h +++ b/src/mapgen/dungeongen.h @@ -26,8 +26,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #define VMANIP_FLAG_DUNGEON_INSIDE VOXELFLAG_CHECKED1 #define VMANIP_FLAG_DUNGEON_PRESERVE VOXELFLAG_CHECKED2 -#define VMANIP_FLAG_DUNGEON_UNTOUCHABLE (\ - VMANIP_FLAG_DUNGEON_INSIDE|VMANIP_FLAG_DUNGEON_PRESERVE) +#define VMANIP_FLAG_DUNGEON_UNTOUCHABLE \ + (VMANIP_FLAG_DUNGEON_INSIDE | VMANIP_FLAG_DUNGEON_PRESERVE) class MMVManip; class NodeDefManager; @@ -37,8 +37,8 @@ v3s16 turn_xz(v3s16 olddir, int t); void random_turn(PseudoRandom &random, v3s16 &dir); int dir_to_facedir(v3s16 d); - -struct DungeonParams { +struct DungeonParams +{ s32 seed; content_t c_wall; @@ -82,7 +82,8 @@ struct DungeonParams { GenNotifyType notifytype; }; -class DungeonGen { +class DungeonGen +{ public: MMVManip *vm = nullptr; const NodeDefManager *ndef; @@ -99,15 +100,15 @@ public: v3s16 m_pos; v3s16 m_dir; - DungeonGen(const NodeDefManager *ndef, - GenerateNotifier *gennotify, DungeonParams *dparams); + DungeonGen(const NodeDefManager *ndef, GenerateNotifier *gennotify, + DungeonParams *dparams); void generate(MMVManip *vm, u32 bseed, v3s16 full_node_min, v3s16 full_node_max); void makeDungeon(v3s16 start_padding); void makeRoom(v3s16 roomsize, v3s16 roomplace); - void makeCorridor(v3s16 doorplace, v3s16 doordir, - v3s16 &result_place, v3s16 &result_dir); + void makeCorridor(v3s16 doorplace, v3s16 doordir, v3s16 &result_place, + v3s16 &result_dir); void makeDoor(v3s16 doorplace, v3s16 doordir); void makeFill(v3s16 place, v3s16 size, u8 avoid_flags, MapNode n, u8 or_flags); void makeHole(v3s16 place); @@ -116,10 +117,7 @@ public: bool findPlaceForRoomDoor(v3s16 roomsize, v3s16 &result_doorplace, v3s16 &result_doordir, v3s16 &result_roomplace); - inline void randomizeDir() - { - m_dir = rand_ortho_dir(random, dp.diagonal_dirs); - } + inline void randomizeDir() { m_dir = rand_ortho_dir(random, dp.diagonal_dirs); } }; extern NoiseParams nparams_dungeon_density; diff --git a/src/mapgen/mapgen.cpp b/src/mapgen/mapgen.cpp index f57529082..84cb79400 100644 --- a/src/mapgen/mapgen.cpp +++ b/src/mapgen/mapgen.cpp @@ -52,27 +52,20 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "cavegen.h" #include "dungeongen.h" -FlagDesc flagdesc_mapgen[] = { - {"caves", MG_CAVES}, - {"dungeons", MG_DUNGEONS}, - {"light", MG_LIGHT}, - {"decorations", MG_DECORATIONS}, - {"biomes", MG_BIOMES}, - {NULL, 0} -}; +FlagDesc flagdesc_mapgen[] = {{"caves", MG_CAVES}, {"dungeons", MG_DUNGEONS}, + {"light", MG_LIGHT}, {"decorations", MG_DECORATIONS}, + {"biomes", MG_BIOMES}, {NULL, 0}}; -FlagDesc flagdesc_gennotify[] = { - {"dungeon", 1 << GENNOTIFY_DUNGEON}, - {"temple", 1 << GENNOTIFY_TEMPLE}, - {"cave_begin", 1 << GENNOTIFY_CAVE_BEGIN}, - {"cave_end", 1 << GENNOTIFY_CAVE_END}, - {"large_cave_begin", 1 << GENNOTIFY_LARGECAVE_BEGIN}, - {"large_cave_end", 1 << GENNOTIFY_LARGECAVE_END}, - {"decoration", 1 << GENNOTIFY_DECORATION}, - {NULL, 0} -}; +FlagDesc flagdesc_gennotify[] = {{"dungeon", 1 << GENNOTIFY_DUNGEON}, + {"temple", 1 << GENNOTIFY_TEMPLE}, + {"cave_begin", 1 << GENNOTIFY_CAVE_BEGIN}, + {"cave_end", 1 << GENNOTIFY_CAVE_END}, + {"large_cave_begin", 1 << GENNOTIFY_LARGECAVE_BEGIN}, + {"large_cave_end", 1 << GENNOTIFY_LARGECAVE_END}, + {"decoration", 1 << GENNOTIFY_DECORATION}, {NULL, 0}}; -struct MapgenDesc { +struct MapgenDesc +{ const char *name; bool is_user_visible; }; @@ -88,32 +81,30 @@ struct MapgenDesc { // Of the remaining, v5 last due to age, v7 first due to being the default. // The order of 'enum MapgenType' in mapgen.h must match this order. static MapgenDesc g_reg_mapgens[] = { - {"v7", true}, - {"valleys", true}, - {"carpathian", true}, - {"v5", true}, - {"flat", true}, - {"fractal", true}, - {"singlenode", true}, - {"v6", true}, + {"v7", true}, + {"valleys", true}, + {"carpathian", true}, + {"v5", true}, + {"flat", true}, + {"fractal", true}, + {"singlenode", true}, + {"v6", true}, }; -STATIC_ASSERT( - ARRLEN(g_reg_mapgens) == MAPGEN_INVALID, - registered_mapgens_is_wrong_size); +STATIC_ASSERT(ARRLEN(g_reg_mapgens) == MAPGEN_INVALID, registered_mapgens_is_wrong_size); //// //// Mapgen //// Mapgen::Mapgen(int mapgenid, MapgenParams *params, EmergeParams *emerge) : - gennotify(emerge->gen_notify_on, emerge->gen_notify_on_deco_ids) + gennotify(emerge->gen_notify_on, emerge->gen_notify_on_deco_ids) { - id = mapgenid; - water_level = params->water_level; + id = mapgenid; + water_level = params->water_level; mapgen_limit = params->mapgen_limit; - flags = params->flags; - csize = v3s16(1, 1, 1) * (params->chunksize * MAP_BLOCKSIZE); + flags = params->flags; + csize = v3s16(1, 1, 1) * (params->chunksize * MAP_BLOCKSIZE); /* We are losing half our entropy by doing this, but it is necessary to @@ -130,10 +121,9 @@ Mapgen::Mapgen(int mapgenid, MapgenParams *params, EmergeParams *emerge) : */ seed = (s32)params->seed; - ndef = emerge->ndef; + ndef = emerge->ndef; } - MapgenType Mapgen::getMapgenType(const std::string &mgname) { for (size_t i = 0; i != ARRLEN(g_reg_mapgens); i++) { @@ -144,7 +134,6 @@ MapgenType Mapgen::getMapgenType(const std::string &mgname) return MAPGEN_INVALID; } - const char *Mapgen::getMapgenName(MapgenType mgtype) { size_t index = (size_t)mgtype; @@ -154,9 +143,8 @@ const char *Mapgen::getMapgenName(MapgenType mgtype) return g_reg_mapgens[index].name; } - -Mapgen *Mapgen::createMapgen(MapgenType mgtype, MapgenParams *params, - EmergeParams *emerge) +Mapgen *Mapgen::createMapgen( + MapgenType mgtype, MapgenParams *params, EmergeParams *emerge) { switch (mgtype) { case MAPGEN_CARPATHIAN: @@ -180,7 +168,6 @@ Mapgen *Mapgen::createMapgen(MapgenType mgtype, MapgenParams *params, } } - MapgenParams *Mapgen::createMapgenParams(MapgenType mgtype) { switch (mgtype) { @@ -205,7 +192,6 @@ MapgenParams *Mapgen::createMapgenParams(MapgenType mgtype) } } - void Mapgen::getMapgenNames(std::vector *mgnames, bool include_hidden) { for (u32 i = 0; i != ARRLEN(g_reg_mapgens); i++) { @@ -217,7 +203,7 @@ void Mapgen::getMapgenNames(std::vector *mgnames, bool include_hid void Mapgen::setDefaultSettings(Settings *settings) { settings->setDefault("mg_flags", flagdesc_mapgen, - MG_CAVES | MG_DUNGEONS | MG_LIGHT | MG_DECORATIONS | MG_BIOMES); + MG_CAVES | MG_DUNGEONS | MG_LIGHT | MG_DECORATIONS | MG_BIOMES); for (int i = 0; i < (int)MAPGEN_INVALID; ++i) { MapgenParams *params = createMapgenParams((MapgenType)i); @@ -228,13 +214,9 @@ void Mapgen::setDefaultSettings(Settings *settings) u32 Mapgen::getBlockSeed(v3s16 p, s32 seed) { - return (u32)seed + - p.Z * 38134234 + - p.Y * 42123 + - p.X * 23; + return (u32)seed + p.Z * 38134234 + p.Y * 42123 + p.X * 23; } - u32 Mapgen::getBlockSeed2(v3s16 p, s32 seed) { u32 n = 1619 * p.X + 31337 * p.Y + 52591 * p.Z + 1013 * seed; @@ -242,7 +224,6 @@ u32 Mapgen::getBlockSeed2(v3s16 p, s32 seed) return (n * (n * n * 60493 + 19990303) + 1376312589); } - // Returns Y one under area minimum if not found s16 Mapgen::findGroundLevelFull(v2s16 p2d) { @@ -262,7 +243,6 @@ s16 Mapgen::findGroundLevelFull(v2s16 p2d) return (y >= y_nodes_min) ? y : y_nodes_min - 1; } - // Returns -MAX_MAP_GENERATION_LIMIT if not found s16 Mapgen::findGroundLevel(v2s16 p2d, s16 ymin, s16 ymax) { @@ -280,7 +260,6 @@ s16 Mapgen::findGroundLevel(v2s16 p2d, s16 ymin, s16 ymax) return (y >= ymin) ? y : -MAX_MAP_GENERATION_LIMIT; } - // Returns -MAX_MAP_GENERATION_LIMIT if not found or if ground is found first s16 Mapgen::findLiquidSurface(v2s16 p2d, s16 ymin, s16 ymax) { @@ -301,13 +280,12 @@ s16 Mapgen::findLiquidSurface(v2s16 p2d, s16 ymin, s16 ymax) return (y >= ymin) ? y : -MAX_MAP_GENERATION_LIMIT; } - void Mapgen::updateHeightmap(v3s16 nmin, v3s16 nmax) { if (!heightmap) return; - //TimeTaker t("Mapgen::updateHeightmap", NULL, PRECISION_MICRO); + // TimeTaker t("Mapgen::updateHeightmap", NULL, PRECISION_MICRO); int index = 0; for (s16 z = nmin.Z; z <= nmax.Z; z++) { for (s16 x = nmin.X; x <= nmax.X; x++, index++) { @@ -318,9 +296,8 @@ void Mapgen::updateHeightmap(v3s16 nmin, v3s16 nmax) } } - -void Mapgen::getSurfaces(v2s16 p2d, s16 ymin, s16 ymax, - std::vector &floors, std::vector &ceilings) +void Mapgen::getSurfaces(v2s16 p2d, s16 ymin, s16 ymax, std::vector &floors, + std::vector &ceilings) { const v3s16 &em = vm->m_area.getExtent(); @@ -345,7 +322,6 @@ void Mapgen::getSurfaces(v2s16 p2d, s16 ymin, s16 ymax, } } - inline bool Mapgen::isLiquidHorizontallyFlowable(u32 vi, v3s16 em) { u32 vi_neg_x = vi; @@ -382,55 +358,60 @@ inline bool Mapgen::isLiquidHorizontallyFlowable(u32 vi, v3s16 em) void Mapgen::updateLiquid(UniqueQueue *trans_liquid, v3s16 nmin, v3s16 nmax) { bool isignored, isliquid, wasignored, wasliquid, waschecked, waspushed; - const v3s16 &em = vm->m_area.getExtent(); + const v3s16 &em = vm->m_area.getExtent(); for (s16 z = nmin.Z + 1; z <= nmax.Z - 1; z++) - for (s16 x = nmin.X + 1; x <= nmax.X - 1; x++) { - wasignored = true; - wasliquid = false; - waschecked = false; - waspushed = false; + for (s16 x = nmin.X + 1; x <= nmax.X - 1; x++) { + wasignored = true; + wasliquid = false; + waschecked = false; + waspushed = false; - u32 vi = vm->m_area.index(x, nmax.Y, z); - for (s16 y = nmax.Y; y >= nmin.Y; y--) { - isignored = vm->m_data[vi].getContent() == CONTENT_IGNORE; - isliquid = ndef->get(vm->m_data[vi]).isLiquid(); + u32 vi = vm->m_area.index(x, nmax.Y, z); + for (s16 y = nmax.Y; y >= nmin.Y; y--) { + isignored = vm->m_data[vi].getContent() == CONTENT_IGNORE; + isliquid = ndef->get(vm->m_data[vi]).isLiquid(); - if (isignored || wasignored || isliquid == wasliquid) { - // Neither topmost node of liquid column nor topmost node below column - waschecked = false; - waspushed = false; - } else if (isliquid) { - // This is the topmost node in the column - bool ispushed = false; - if (isLiquidHorizontallyFlowable(vi, em)) { - trans_liquid->push_back(v3s16(x, y, z)); - ispushed = true; - } - // Remember waschecked and waspushed to avoid repeated - // checks/pushes in case the column consists of only this node - waschecked = true; - waspushed = ispushed; - } else { - // This is the topmost node below a liquid column - u32 vi_above = vi; - VoxelArea::add_y(em, vi_above, 1); - if (!waspushed && (ndef->get(vm->m_data[vi]).floodable || - (!waschecked && isLiquidHorizontallyFlowable(vi_above, em)))) { - // Push back the lowest node in the column which is one - // node above this one - trans_liquid->push_back(v3s16(x, y + 1, z)); + if (isignored || wasignored || isliquid == wasliquid) { + // Neither topmost node of liquid column nor + // topmost node below column + waschecked = false; + waspushed = false; + } else if (isliquid) { + // This is the topmost node in the column + bool ispushed = false; + if (isLiquidHorizontallyFlowable(vi, em)) { + trans_liquid->push_back(v3s16(x, y, z)); + ispushed = true; + } + // Remember waschecked and waspushed to avoid + // repeated checks/pushes in case the column + // consists of only this node + waschecked = true; + waspushed = ispushed; + } else { + // This is the topmost node below a liquid column + u32 vi_above = vi; + VoxelArea::add_y(em, vi_above, 1); + if (!waspushed && + (ndef->get(vm->m_data[vi]).floodable || + (!waschecked && isLiquidHorizontallyFlowable( + vi_above, + em)))) { + // Push back the lowest node in the column + // which is one node above this one + trans_liquid->push_back( + v3s16(x, y + 1, z)); + } } + + wasliquid = isliquid; + wasignored = isignored; + VoxelArea::add_y(em, vi, -1); } - - wasliquid = isliquid; - wasignored = isignored; - VoxelArea::add_y(em, vi, -1); } - } } - void Mapgen::setLighting(u8 light, v3s16 nmin, v3s16 nmax) { ScopeProfiler sp(g_profiler, "EmergeThread: update lighting", SPT_AVG); @@ -445,9 +426,8 @@ void Mapgen::setLighting(u8 light, v3s16 nmin, v3s16 nmax) } } - void Mapgen::lightSpread(VoxelArea &a, std::queue> &queue, - const v3s16 &p, u8 light) + const v3s16 &p, u8 light) { if (light <= 1 || !a.contains(p)) return; @@ -466,16 +446,14 @@ void Mapgen::lightSpread(VoxelArea &a, std::queue> &queue, // Bail out only if we have no more light from either bank to propogate, or // we hit a solid block that light cannot pass through. - if ((light_day <= (n.param1 & 0x0F) && - light_night <= (n.param1 & 0xF0)) || + if ((light_day <= (n.param1 & 0x0F) && light_night <= (n.param1 & 0xF0)) || !ndef->get(n).light_propagates) return; // Since this recursive function only terminates when there is no light from // either bank left, we need to take the max of both banks into account for // the case where spreading has stopped for one light bank but not the other. - light = MYMAX(light_day, n.param1 & 0x0F) | - MYMAX(light_night, n.param1 & 0xF0); + light = MYMAX(light_day, n.param1 & 0x0F) | MYMAX(light_night, n.param1 & 0xF0); n.param1 = light; @@ -483,23 +461,21 @@ void Mapgen::lightSpread(VoxelArea &a, std::queue> &queue, queue.emplace(p, light); } - void Mapgen::calcLighting(v3s16 nmin, v3s16 nmax, v3s16 full_nmin, v3s16 full_nmax, - bool propagate_shadow) + bool propagate_shadow) { ScopeProfiler sp(g_profiler, "EmergeThread: update lighting", SPT_AVG); - //TimeTaker t("updateLighting"); + // TimeTaker t("updateLighting"); propagateSunlight(nmin, nmax, propagate_shadow); spreadLight(full_nmin, full_nmax); - //printf("updateLighting: %dms\n", t.stop()); + // printf("updateLighting: %dms\n", t.stop()); } - void Mapgen::propagateSunlight(v3s16 nmin, v3s16 nmax, bool propagate_shadow) { - //TimeTaker t("propagateSunlight"); + // TimeTaker t("propagateSunlight"); VoxelArea a(nmin, nmax); bool block_is_underground = (water_level >= nmax.Y); const v3s16 &em = vm->m_area.getExtent(); @@ -529,13 +505,12 @@ void Mapgen::propagateSunlight(v3s16 nmin, v3s16 nmax, bool propagate_shadow) } } } - //printf("propagateSunlight: %dms\n", t.stop()); + // printf("propagateSunlight: %dms\n", t.stop()); } - void Mapgen::spreadLight(const v3s16 &nmin, const v3s16 &nmax) { - //TimeTaker t("spreadLight"); + // TimeTaker t("spreadLight"); std::queue> queue; VoxelArea a(nmin, nmax); @@ -551,8 +526,9 @@ void Mapgen::spreadLight(const v3s16 &nmin, const v3s16 &nmax) if (!cf.light_propagates) continue; - // TODO(hmmmmm): Abstract away direct param1 accesses with a - // wrapper, but something lighter than MapNode::get/setLight + // TODO(hmmmmm): Abstract away direct param1 accesses with + // a wrapper, but something lighter than + // MapNode::get/setLight u8 light_produced = cf.light_source; if (light_produced) @@ -577,19 +553,18 @@ void Mapgen::spreadLight(const v3s16 &nmin, const v3s16 &nmax) queue.pop(); } - //printf("spreadLight: %lums\n", t.stop()); + // printf("spreadLight: %lums\n", t.stop()); } - //// //// MapgenBasic //// -MapgenBasic::MapgenBasic(int mapgenid, MapgenParams *params, EmergeParams *emerge) - : Mapgen(mapgenid, params, emerge) +MapgenBasic::MapgenBasic(int mapgenid, MapgenParams *params, EmergeParams *emerge) : + Mapgen(mapgenid, params, emerge) { this->m_emerge = emerge; - this->m_bmgr = emerge->biomemgr; + this->m_bmgr = emerge->biomemgr; //// Here, 'stride' refers to the number of elements needed to skip to index //// an adjacent element for that coordinate in noise/height/biome maps @@ -618,11 +593,11 @@ MapgenBasic::MapgenBasic(int mapgenid, MapgenParams *params, EmergeParams *emerg biomemap = biomegen->biomemap; //// Look up some commonly used content - c_stone = ndef->getId("mapgen_stone"); - c_water_source = ndef->getId("mapgen_water_source"); + c_stone = ndef->getId("mapgen_stone"); + c_water_source = ndef->getId("mapgen_water_source"); c_river_water_source = ndef->getId("mapgen_river_water_source"); - c_lava_source = ndef->getId("mapgen_lava_source"); - c_cobble = ndef->getId("mapgen_cobble"); + c_lava_source = ndef->getId("mapgen_lava_source"); + c_cobble = ndef->getId("mapgen_cobble"); // Fall back to more basic content if not defined. // Lava falls back to water as both are suitable as cave liquids. @@ -630,23 +605,25 @@ MapgenBasic::MapgenBasic(int mapgenid, MapgenParams *params, EmergeParams *emerg c_lava_source = c_water_source; if (c_stone == CONTENT_IGNORE) - errorstream << "Mapgen: Mapgen alias 'mapgen_stone' is invalid!" << std::endl; + errorstream << "Mapgen: Mapgen alias 'mapgen_stone' is invalid!" + << std::endl; if (c_water_source == CONTENT_IGNORE) - errorstream << "Mapgen: Mapgen alias 'mapgen_water_source' is invalid!" << std::endl; + errorstream << "Mapgen: Mapgen alias 'mapgen_water_source' is invalid!" + << std::endl; if (c_river_water_source == CONTENT_IGNORE) - warningstream << "Mapgen: Mapgen alias 'mapgen_river_water_source' is invalid!" << std::endl; + warningstream << "Mapgen: Mapgen alias 'mapgen_river_water_source' is " + "invalid!" + << std::endl; } - MapgenBasic::~MapgenBasic() { delete biomegen; - delete []heightmap; + delete[] heightmap; delete m_emerge; // destroying EmergeParams is our responsibility } - void MapgenBasic::generateBiomes() { // can't generate biomes without a biome generator! @@ -659,131 +636,158 @@ void MapgenBasic::generateBiomes() noise_filler_depth->perlinMap2D(node_min.X, node_min.Z); for (s16 z = node_min.Z; z <= node_max.Z; z++) - for (s16 x = node_min.X; x <= node_max.X; x++, index++) { - Biome *biome = NULL; - biome_t water_biome_index = 0; - u16 depth_top = 0; - u16 base_filler = 0; - u16 depth_water_top = 0; - u16 depth_riverbed = 0; - s16 biome_y_min = -MAX_MAP_GENERATION_LIMIT; - u32 vi = vm->m_area.index(x, node_max.Y, z); + for (s16 x = node_min.X; x <= node_max.X; x++, index++) { + Biome *biome = NULL; + biome_t water_biome_index = 0; + u16 depth_top = 0; + u16 base_filler = 0; + u16 depth_water_top = 0; + u16 depth_riverbed = 0; + s16 biome_y_min = -MAX_MAP_GENERATION_LIMIT; + u32 vi = vm->m_area.index(x, node_max.Y, z); - // Check node at base of mapchunk above, either a node of a previously - // generated mapchunk or if not, a node of overgenerated base terrain. - content_t c_above = vm->m_data[vi + em.X].getContent(); - bool air_above = c_above == CONTENT_AIR; - bool river_water_above = c_above == c_river_water_source; - bool water_above = c_above == c_water_source || river_water_above; + // Check node at base of mapchunk above, either a node of a + // previously generated mapchunk or if not, a node of + // overgenerated base terrain. + content_t c_above = vm->m_data[vi + em.X].getContent(); + bool air_above = c_above == CONTENT_AIR; + bool river_water_above = c_above == c_river_water_source; + bool water_above = c_above == c_water_source || river_water_above; - biomemap[index] = BIOME_NONE; + biomemap[index] = BIOME_NONE; - // If there is air or water above enable top/filler placement, otherwise force - // nplaced to stone level by setting a number exceeding any possible filler depth. - u16 nplaced = (air_above || water_above) ? 0 : U16_MAX; + // If there is air or water above enable top/filler placement, + // otherwise force nplaced to stone level by setting a number + // exceeding any possible filler depth. + u16 nplaced = (air_above || water_above) ? 0 : U16_MAX; - for (s16 y = node_max.Y; y >= node_min.Y; y--) { - content_t c = vm->m_data[vi].getContent(); - // Biome is (re)calculated: - // 1. At the surface of stone below air or water. - // 2. At the surface of water below air. - // 3. When stone or water is detected but biome has not yet been calculated. - // 4. When stone or water is detected just below a biome's lower limit. - bool is_stone_surface = (c == c_stone) && - (air_above || water_above || !biome || y < biome_y_min); // 1, 3, 4 + for (s16 y = node_max.Y; y >= node_min.Y; y--) { + content_t c = vm->m_data[vi].getContent(); + // Biome is (re)calculated: + // 1. At the surface of stone below air or water. + // 2. At the surface of water below air. + // 3. When stone or water is detected but biome has not + // yet been calculated. + // 4. When stone or water is detected just below a biome's + // lower limit. + bool is_stone_surface = + (c == c_stone) && + (air_above || water_above || !biome || + y < biome_y_min); // 1, 3, + // 4 - bool is_water_surface = - (c == c_water_source || c == c_river_water_source) && - (air_above || !biome || y < biome_y_min); // 2, 3, 4 + bool is_water_surface = + (c == c_water_source || + c == c_river_water_source) && + (air_above || !biome || + y < biome_y_min); // 2, 3, + // 4 - if (is_stone_surface || is_water_surface) { - // (Re)calculate biome - biome = biomegen->getBiomeAtIndex(index, v3s16(x, y, z)); + if (is_stone_surface || is_water_surface) { + // (Re)calculate biome + biome = biomegen->getBiomeAtIndex( + index, v3s16(x, y, z)); - // Add biome to biomemap at first stone surface detected - if (biomemap[index] == BIOME_NONE && is_stone_surface) - biomemap[index] = biome->index; + // Add biome to biomemap at first stone surface + // detected + if (biomemap[index] == BIOME_NONE && + is_stone_surface) + biomemap[index] = biome->index; - // Store biome of first water surface detected, as a fallback - // entry for the biomemap. - if (water_biome_index == 0 && is_water_surface) - water_biome_index = biome->index; + // Store biome of first water surface detected, as + // a fallback entry for the biomemap. + if (water_biome_index == 0 && is_water_surface) + water_biome_index = biome->index; - depth_top = biome->depth_top; - base_filler = MYMAX(depth_top + - biome->depth_filler + - noise_filler_depth->result[index], 0.0f); - depth_water_top = biome->depth_water_top; - depth_riverbed = biome->depth_riverbed; - biome_y_min = biome->min_pos.Y; - } - - if (c == c_stone) { - content_t c_below = vm->m_data[vi - em.X].getContent(); - - // If the node below isn't solid, make this node stone, so that - // any top/filler nodes above are structurally supported. - // This is done by aborting the cycle of top/filler placement - // immediately by forcing nplaced to stone level. - if (c_below == CONTENT_AIR - || c_below == c_water_source - || c_below == c_river_water_source) - nplaced = U16_MAX; - - if (river_water_above) { - if (nplaced < depth_riverbed) { - vm->m_data[vi] = MapNode(biome->c_riverbed); - nplaced++; - } else { - nplaced = U16_MAX; // Disable top/filler placement - river_water_above = false; - } - } else if (nplaced < depth_top) { - vm->m_data[vi] = MapNode(biome->c_top); - nplaced++; - } else if (nplaced < base_filler) { - vm->m_data[vi] = MapNode(biome->c_filler); - nplaced++; - } else { - vm->m_data[vi] = MapNode(biome->c_stone); - nplaced = U16_MAX; // Disable top/filler placement + depth_top = biome->depth_top; + base_filler = MYMAX( + depth_top + biome->depth_filler + + noise_filler_depth->result + [index], + 0.0f); + depth_water_top = biome->depth_water_top; + depth_riverbed = biome->depth_riverbed; + biome_y_min = biome->min_pos.Y; } - air_above = false; - water_above = false; - } else if (c == c_water_source) { - vm->m_data[vi] = MapNode((y > (s32)(water_level - depth_water_top)) - ? biome->c_water_top : biome->c_water); - nplaced = 0; // Enable top/filler placement for next surface - air_above = false; - water_above = true; - } else if (c == c_river_water_source) { - vm->m_data[vi] = MapNode(biome->c_river_water); - nplaced = 0; // Enable riverbed placement for next surface - air_above = false; - water_above = true; - river_water_above = true; - } else if (c == CONTENT_AIR) { - nplaced = 0; // Enable top/filler placement for next surface - air_above = true; - water_above = false; - } else { // Possible various nodes overgenerated from neighbouring mapchunks - nplaced = U16_MAX; // Disable top/filler placement - air_above = false; - water_above = false; + if (c == c_stone) { + content_t c_below = vm->m_data[vi - em.X] + .getContent(); + + // If the node below isn't solid, make this node + // stone, so that any top/filler nodes above are + // structurally supported. This is done by + // aborting the cycle of top/filler placement + // immediately by forcing nplaced to stone level. + if (c_below == CONTENT_AIR || + c_below == c_water_source || + c_below == c_river_water_source) + nplaced = U16_MAX; + + if (river_water_above) { + if (nplaced < depth_riverbed) { + vm->m_data[vi] = MapNode( + biome->c_riverbed); + nplaced++; + } else { + nplaced = U16_MAX; // Disable + // top/filler + // placement + river_water_above = false; + } + } else if (nplaced < depth_top) { + vm->m_data[vi] = MapNode(biome->c_top); + nplaced++; + } else if (nplaced < base_filler) { + vm->m_data[vi] = MapNode(biome->c_filler); + nplaced++; + } else { + vm->m_data[vi] = MapNode(biome->c_stone); + nplaced = U16_MAX; // Disable top/filler + // placement + } + + air_above = false; + water_above = false; + } else if (c == c_water_source) { + vm->m_data[vi] = MapNode( + (y > (s32)(water_level - + depth_water_top)) + ? biome->c_water_top + : biome->c_water); + nplaced = 0; // Enable top/filler placement for + // next surface + air_above = false; + water_above = true; + } else if (c == c_river_water_source) { + vm->m_data[vi] = MapNode(biome->c_river_water); + nplaced = 0; // Enable riverbed placement for next + // surface + air_above = false; + water_above = true; + river_water_above = true; + } else if (c == CONTENT_AIR) { + nplaced = 0; // Enable top/filler placement for + // next surface + air_above = true; + water_above = false; + } else { // Possible various nodes overgenerated from + // neighbouring mapchunks + nplaced = U16_MAX; // Disable top/filler placement + air_above = false; + water_above = false; + } + + VoxelArea::add_y(em, vi, -1); } - - VoxelArea::add_y(em, vi, -1); + // If no stone surface detected in mapchunk column and a water + // surface biome fallback exists, add it to the biomemap. This + // avoids water surface decorations failing in deep water. + if (biomemap[index] == BIOME_NONE && water_biome_index != 0) + biomemap[index] = water_biome_index; } - // If no stone surface detected in mapchunk column and a water surface - // biome fallback exists, add it to the biomemap. This avoids water - // surface decorations failing in deep water. - if (biomemap[index] == BIOME_NONE && water_biome_index != 0) - biomemap[index] = water_biome_index; - } } - void MapgenBasic::dustTopNodes() { if (node_max.Y < water_level) @@ -793,61 +797,59 @@ void MapgenBasic::dustTopNodes() u32 index = 0; for (s16 z = node_min.Z; z <= node_max.Z; z++) - for (s16 x = node_min.X; x <= node_max.X; x++, index++) { - Biome *biome = (Biome *)m_bmgr->getRaw(biomemap[index]); + for (s16 x = node_min.X; x <= node_max.X; x++, index++) { + Biome *biome = (Biome *)m_bmgr->getRaw(biomemap[index]); - if (biome->c_dust == CONTENT_IGNORE) - continue; - - // Check if mapchunk above has generated, if so, drop dust from 16 nodes - // above current mapchunk top, above decorations that will extend above - // the current mapchunk. If the mapchunk above has not generated, it - // will provide this required dust when it does. - u32 vi = vm->m_area.index(x, full_node_max.Y, z); - content_t c_full_max = vm->m_data[vi].getContent(); - s16 y_start; - - if (c_full_max == CONTENT_AIR) { - y_start = full_node_max.Y - 1; - } else if (c_full_max == CONTENT_IGNORE) { - vi = vm->m_area.index(x, node_max.Y + 1, z); - content_t c_max = vm->m_data[vi].getContent(); - - if (c_max == CONTENT_AIR) - y_start = node_max.Y; - else + if (biome->c_dust == CONTENT_IGNORE) continue; - } else { - continue; - } - vi = vm->m_area.index(x, y_start, z); - for (s16 y = y_start; y >= node_min.Y - 1; y--) { - if (vm->m_data[vi].getContent() != CONTENT_AIR) - break; + // Check if mapchunk above has generated, if so, drop dust from 16 + // nodes above current mapchunk top, above decorations that will + // extend above the current mapchunk. If the mapchunk above has + // not generated, it will provide this required dust when it does. + u32 vi = vm->m_area.index(x, full_node_max.Y, z); + content_t c_full_max = vm->m_data[vi].getContent(); + s16 y_start; - VoxelArea::add_y(em, vi, -1); - } + if (c_full_max == CONTENT_AIR) { + y_start = full_node_max.Y - 1; + } else if (c_full_max == CONTENT_IGNORE) { + vi = vm->m_area.index(x, node_max.Y + 1, z); + content_t c_max = vm->m_data[vi].getContent(); - content_t c = vm->m_data[vi].getContent(); - NodeDrawType dtype = ndef->get(c).drawtype; - // Only place on cubic, walkable, non-dust nodes. - // Dust check needed due to avoid double layer of dust caused by - // dropping dust from 16 nodes above mapchunk top. - if ((dtype == NDT_NORMAL || - dtype == NDT_ALLFACES || - dtype == NDT_ALLFACES_OPTIONAL || - dtype == NDT_GLASSLIKE || - dtype == NDT_GLASSLIKE_FRAMED || - dtype == NDT_GLASSLIKE_FRAMED_OPTIONAL) && - ndef->get(c).walkable && c != biome->c_dust) { - VoxelArea::add_y(em, vi, 1); - vm->m_data[vi] = MapNode(biome->c_dust); + if (c_max == CONTENT_AIR) + y_start = node_max.Y; + else + continue; + } else { + continue; + } + + vi = vm->m_area.index(x, y_start, z); + for (s16 y = y_start; y >= node_min.Y - 1; y--) { + if (vm->m_data[vi].getContent() != CONTENT_AIR) + break; + + VoxelArea::add_y(em, vi, -1); + } + + content_t c = vm->m_data[vi].getContent(); + NodeDrawType dtype = ndef->get(c).drawtype; + // Only place on cubic, walkable, non-dust nodes. + // Dust check needed due to avoid double layer of dust caused by + // dropping dust from 16 nodes above mapchunk top. + if ((dtype == NDT_NORMAL || dtype == NDT_ALLFACES || + dtype == NDT_ALLFACES_OPTIONAL || + dtype == NDT_GLASSLIKE || + dtype == NDT_GLASSLIKE_FRAMED || + dtype == NDT_GLASSLIKE_FRAMED_OPTIONAL) && + ndef->get(c).walkable && c != biome->c_dust) { + VoxelArea::add_y(em, vi, 1); + vm->m_data[vi] = MapNode(biome->c_dust); + } } - } } - void MapgenBasic::generateCavesNoiseIntersection(s16 max_stone_y) { // cave_width >= 10 is used to disable generation and avoid the intensive @@ -855,13 +857,12 @@ void MapgenBasic::generateCavesNoiseIntersection(s16 max_stone_y) if (node_min.Y > max_stone_y || cave_width >= 10.0f) return; - CavesNoiseIntersection caves_noise(ndef, m_bmgr, csize, - &np_cave1, &np_cave2, seed, cave_width); + CavesNoiseIntersection caves_noise( + ndef, m_bmgr, csize, &np_cave1, &np_cave2, seed, cave_width); caves_noise.generateCaves(vm, node_min, node_max, biomemap); } - void MapgenBasic::generateCavesRandomWalk(s16 max_stone_y, s16 large_cave_ymax) { if (node_min.Y > max_stone_y) @@ -872,8 +873,8 @@ void MapgenBasic::generateCavesRandomWalk(s16 max_stone_y, s16 large_cave_ymax) u32 num_small_caves = ps.range(small_cave_num_min, small_cave_num_max); for (u32 i = 0; i < num_small_caves; i++) { - CavesRandomWalk cave(ndef, &gennotify, seed, water_level, - c_water_source, c_lava_source, large_cave_flooded, biomegen); + CavesRandomWalk cave(ndef, &gennotify, seed, water_level, c_water_source, + c_lava_source, large_cave_flooded, biomegen); cave.makeCave(vm, node_min, node_max, &ps, false, max_stone_y, heightmap); } @@ -886,33 +887,32 @@ void MapgenBasic::generateCavesRandomWalk(s16 max_stone_y, s16 large_cave_ymax) u32 num_large_caves = ps.range(large_cave_num_min, large_cave_num_max); for (u32 i = 0; i < num_large_caves; i++) { - CavesRandomWalk cave(ndef, &gennotify, seed, water_level, - c_water_source, c_lava_source, large_cave_flooded, biomegen); + CavesRandomWalk cave(ndef, &gennotify, seed, water_level, c_water_source, + c_lava_source, large_cave_flooded, biomegen); cave.makeCave(vm, node_min, node_max, &ps, true, max_stone_y, heightmap); } } - bool MapgenBasic::generateCavernsNoise(s16 max_stone_y) { if (node_min.Y > max_stone_y || node_min.Y > cavern_limit) return false; - CavernsNoise caverns_noise(ndef, csize, &np_cavern, - seed, cavern_limit, cavern_taper, cavern_threshold); + CavernsNoise caverns_noise(ndef, csize, &np_cavern, seed, cavern_limit, + cavern_taper, cavern_threshold); return caverns_noise.generateCaverns(vm, node_min, node_max); } - void MapgenBasic::generateDungeons(s16 max_stone_y) { if (node_min.Y > max_stone_y || node_min.Y > dungeon_ymax || node_max.Y < dungeon_ymin) return; - u16 num_dungeons = std::fmax(std::floor( - NoisePerlin3D(&np_dungeons, node_min.X, node_min.Y, node_min.Z, seed)), 0.0f); + u16 num_dungeons = std::fmax(std::floor(NoisePerlin3D(&np_dungeons, node_min.X, + node_min.Y, node_min.Z, seed)), + 0.0f); if (num_dungeons == 0) return; @@ -921,24 +921,24 @@ void MapgenBasic::generateDungeons(s16 max_stone_y) DungeonParams dp; dp.np_alt_wall = - NoiseParams(-0.4, 1.0, v3f(40.0, 40.0, 40.0), 32474, 6, 1.1, 2.0); + NoiseParams(-0.4, 1.0, v3f(40.0, 40.0, 40.0), 32474, 6, 1.1, 2.0); - dp.seed = seed; - dp.only_in_ground = true; - dp.num_dungeons = num_dungeons; - dp.notifytype = GENNOTIFY_DUNGEON; - dp.num_rooms = ps.range(2, 16); - dp.room_size_min = v3s16(5, 5, 5); - dp.room_size_max = v3s16(12, 6, 12); + dp.seed = seed; + dp.only_in_ground = true; + dp.num_dungeons = num_dungeons; + dp.notifytype = GENNOTIFY_DUNGEON; + dp.num_rooms = ps.range(2, 16); + dp.room_size_min = v3s16(5, 5, 5); + dp.room_size_max = v3s16(12, 6, 12); dp.room_size_large_min = v3s16(12, 6, 12); dp.room_size_large_max = v3s16(16, 16, 16); - dp.large_room_chance = (ps.range(1, 4) == 1) ? 8 : 0; - dp.diagonal_dirs = ps.range(1, 8) == 1; + dp.large_room_chance = (ps.range(1, 4) == 1) ? 8 : 0; + dp.diagonal_dirs = ps.range(1, 8) == 1; // Diagonal corridors must have 'hole' width >=2 to be passable - u8 holewidth = (dp.diagonal_dirs) ? 2 : ps.range(1, 2); - dp.holesize = v3s16(holewidth, 3, holewidth); - dp.corridor_len_min = 1; - dp.corridor_len_max = 13; + u8 holewidth = (dp.diagonal_dirs) ? 2 : ps.range(1, 2); + dp.holesize = v3s16(holewidth, 3, holewidth); + dp.corridor_len_min = 1; + dp.corridor_len_max = 13; // Get biome at mapchunk midpoint v3s16 chunk_mid = node_min + (node_max - node_min) / v3s16(2, 2, 2); @@ -952,102 +952,91 @@ void MapgenBasic::generateDungeons(s16 max_stone_y) // dungeongen.cpp. dp.c_alt_wall = biome->c_dungeon_alt; // Stairs fall back to 'c_dungeon' if not defined by biome - dp.c_stair = (biome->c_dungeon_stair != CONTENT_IGNORE) ? - biome->c_dungeon_stair : biome->c_dungeon; - // Fallback to using cobble mapgen alias if defined + dp.c_stair = (biome->c_dungeon_stair != CONTENT_IGNORE) + ? biome->c_dungeon_stair + : biome->c_dungeon; + // Fallback to using cobble mapgen alias if defined } else if (c_cobble != CONTENT_IGNORE) { - dp.c_wall = c_cobble; + dp.c_wall = c_cobble; dp.c_alt_wall = CONTENT_IGNORE; - dp.c_stair = c_cobble; - // Fallback to using biome-defined stone + dp.c_stair = c_cobble; + // Fallback to using biome-defined stone } else { - dp.c_wall = biome->c_stone; + dp.c_wall = biome->c_stone; dp.c_alt_wall = CONTENT_IGNORE; - dp.c_stair = biome->c_stone; + dp.c_stair = biome->c_stone; } DungeonGen dgen(ndef, &gennotify, &dp); dgen.generate(vm, blockseed, full_node_min, full_node_max); } - //// //// GenerateNotifier //// -GenerateNotifier::GenerateNotifier(u32 notify_on, - const std::set *notify_on_deco_ids) +GenerateNotifier::GenerateNotifier(u32 notify_on, const std::set *notify_on_deco_ids) { m_notify_on = notify_on; m_notify_on_deco_ids = notify_on_deco_ids; } - void GenerateNotifier::setNotifyOn(u32 notify_on) { m_notify_on = notify_on; } - -void GenerateNotifier::setNotifyOnDecoIds( - const std::set *notify_on_deco_ids) +void GenerateNotifier::setNotifyOnDecoIds(const std::set *notify_on_deco_ids) { m_notify_on_deco_ids = notify_on_deco_ids; } - bool GenerateNotifier::addEvent(GenNotifyType type, v3s16 pos, u32 id) { if (!(m_notify_on & (1 << type))) return false; if (type == GENNOTIFY_DECORATION && - m_notify_on_deco_ids->find(id) == m_notify_on_deco_ids->cend()) + m_notify_on_deco_ids->find(id) == m_notify_on_deco_ids->cend()) return false; GenNotifyEvent gne; gne.type = type; - gne.pos = pos; - gne.id = id; + gne.pos = pos; + gne.id = id; m_notify_events.push_back(gne); return true; } - -void GenerateNotifier::getEvents( - std::map > &event_map) +void GenerateNotifier::getEvents(std::map> &event_map) { std::list::iterator it; for (it = m_notify_events.begin(); it != m_notify_events.end(); ++it) { GenNotifyEvent &gn = *it; - std::string name = (gn.type == GENNOTIFY_DECORATION) ? - "decoration#"+ itos(gn.id) : - flagdesc_gennotify[gn.type].name; + std::string name = (gn.type == GENNOTIFY_DECORATION) + ? "decoration#" + itos(gn.id) + : flagdesc_gennotify[gn.type].name; event_map[name].push_back(gn.pos); } } - void GenerateNotifier::clearEvents() { m_notify_events.clear(); } - //// //// MapgenParams //// - MapgenParams::~MapgenParams() { delete bparams; } - void MapgenParams::readParams(const Settings *settings) { std::string seed_str; @@ -1080,7 +1069,6 @@ void MapgenParams::readParams(const Settings *settings) } } - void MapgenParams::writeParams(Settings *settings) const { settings->set("mg_name", Mapgen::getMapgenName(mgtype)); @@ -1094,7 +1082,6 @@ void MapgenParams::writeParams(Settings *settings) const bparams->writeParams(settings); } - // Calculate exact edges of the outermost mapchunks that are within the // set 'mapgen_limit'. void MapgenParams::calcMapgenEdges() @@ -1111,8 +1098,8 @@ void MapgenParams::calcMapgenEdges() s16 ccfmax = ccmax + MAP_BLOCKSIZE; // Effective mapgen limit, in blocks // Uses same calculation as ServerMap::blockpos_over_mapgen_limit(v3s16 p) - s16 mapgen_limit_b = rangelim(mapgen_limit, - 0, MAX_MAP_GENERATION_LIMIT) / MAP_BLOCKSIZE; + s16 mapgen_limit_b = rangelim(mapgen_limit, 0, MAX_MAP_GENERATION_LIMIT) / + MAP_BLOCKSIZE; // Effective mapgen limits, in nodes s16 mapgen_limit_min = -mapgen_limit_b * MAP_BLOCKSIZE; s16 mapgen_limit_max = (mapgen_limit_b + 1) * MAP_BLOCKSIZE - 1; @@ -1127,7 +1114,6 @@ void MapgenParams::calcMapgenEdges() m_mapgen_edges_calculated = true; } - s32 MapgenParams::getSpawnRangeMax() { if (!m_mapgen_edges_calculated) diff --git a/src/mapgen/mapgen.h b/src/mapgen/mapgen.h index a92b3b0d0..7ab797658 100644 --- a/src/mapgen/mapgen.h +++ b/src/mapgen/mapgen.h @@ -30,15 +30,15 @@ with this program; if not, write to the Free Software Foundation, Inc., #define MAPGEN_DEFAULT_NAME "v7" /////////////////// Mapgen flags -#define MG_TREES 0x01 // Obsolete. Moved into mgv6 flags -#define MG_CAVES 0x02 -#define MG_DUNGEONS 0x04 -#define MG_FLAT 0x08 // Obsolete. Moved into mgv6 flags -#define MG_LIGHT 0x10 +#define MG_TREES 0x01 // Obsolete. Moved into mgv6 flags +#define MG_CAVES 0x02 +#define MG_DUNGEONS 0x04 +#define MG_FLAT 0x08 // Obsolete. Moved into mgv6 flags +#define MG_LIGHT 0x10 #define MG_DECORATIONS 0x20 -#define MG_BIOMES 0x40 +#define MG_BIOMES 0x40 -typedef u16 biome_t; // copy from mg_biome.h to avoid an unnecessary include +typedef u16 biome_t; // copy from mg_biome.h to avoid an unnecessary include class Settings; class MMVManip; @@ -59,7 +59,8 @@ struct BlockMakeData; class VoxelArea; class Map; -enum MapgenObject { +enum MapgenObject +{ MGOBJ_VMANIP, MGOBJ_HEIGHTMAP, MGOBJ_BIOMEMAP, @@ -68,7 +69,8 @@ enum MapgenObject { MGOBJ_GENNOTIFY }; -enum GenNotifyType { +enum GenNotifyType +{ GENNOTIFY_DUNGEON, GENNOTIFY_TEMPLE, GENNOTIFY_CAVE_BEGIN, @@ -79,13 +81,15 @@ enum GenNotifyType { NUM_GENNOTIFY_TYPES }; -struct GenNotifyEvent { +struct GenNotifyEvent +{ GenNotifyType type; v3s16 pos; u32 id; }; -class GenerateNotifier { +class GenerateNotifier +{ public: GenerateNotifier() = default; GenerateNotifier(u32 notify_on, const std::set *notify_on_deco_ids); @@ -93,8 +97,8 @@ public: void setNotifyOn(u32 notify_on); void setNotifyOnDecoIds(const std::set *notify_on_deco_ids); - bool addEvent(GenNotifyType type, v3s16 pos, u32 id=0); - void getEvents(std::map > &event_map); + bool addEvent(GenNotifyType type, v3s16 pos, u32 id = 0); + void getEvents(std::map> &event_map); void clearEvents(); private: @@ -104,7 +108,8 @@ private: }; // Order must match the order of 'static MapgenDesc g_reg_mapgens[]' in mapgen.cpp -enum MapgenType { +enum MapgenType +{ MAPGEN_V7, MAPGEN_VALLEYS, MAPGEN_CARPATHIAN, @@ -116,7 +121,8 @@ enum MapgenType { MAPGEN_INVALID, }; -struct MapgenParams { +struct MapgenParams +{ MapgenParams() = default; virtual ~MapgenParams(); @@ -137,7 +143,7 @@ struct MapgenParams { virtual void readParams(const Settings *settings); virtual void writeParams(Settings *settings) const; // Default settings for g_settings such as flags - virtual void setDefaultSettings(Settings *settings) {}; + virtual void setDefaultSettings(Settings *settings){}; s32 getSpawnRangeMax(); @@ -146,7 +152,6 @@ private: bool m_mapgen_edges_calculated = false; }; - /* Generic interface for map generators. All mapgens must inherit this class. If a feature exposed by a public member pointer is not supported by a @@ -156,7 +161,8 @@ private: methods can be used by constructing a Mapgen base class and setting the appropriate public members (e.g. vm, ndef, and so on). */ -class Mapgen { +class Mapgen +{ public: s32 seed = 0; int water_level = 0; @@ -189,16 +195,16 @@ public: s16 findGroundLevel(v2s16 p2d, s16 ymin, s16 ymax); s16 findLiquidSurface(v2s16 p2d, s16 ymin, s16 ymax); void updateHeightmap(v3s16 nmin, v3s16 nmax); - void getSurfaces(v2s16 p2d, s16 ymin, s16 ymax, - std::vector &floors, std::vector &ceilings); + void getSurfaces(v2s16 p2d, s16 ymin, s16 ymax, std::vector &floors, + std::vector &ceilings); void updateLiquid(UniqueQueue *trans_liquid, v3s16 nmin, v3s16 nmax); void setLighting(u8 light, v3s16 nmin, v3s16 nmax); void lightSpread(VoxelArea &a, std::queue> &queue, - const v3s16 &p, u8 light); + const v3s16 &p, u8 light); void calcLighting(v3s16 nmin, v3s16 nmax, v3s16 full_nmin, v3s16 full_nmax, - bool propagate_shadow = true); + bool propagate_shadow = true); void propagateSunlight(v3s16 nmin, v3s16 nmax, bool propagate_shadow); void spreadLight(const v3s16 &nmin, const v3s16 &nmax); @@ -215,10 +221,11 @@ public: // Mapgen management functions static MapgenType getMapgenType(const std::string &mgname); static const char *getMapgenName(MapgenType mgtype); - static Mapgen *createMapgen(MapgenType mgtype, MapgenParams *params, - EmergeParams *emerge); + static Mapgen *createMapgen( + MapgenType mgtype, MapgenParams *params, EmergeParams *emerge); static MapgenParams *createMapgenParams(MapgenType mgtype); - static void getMapgenNames(std::vector *mgnames, bool include_hidden); + static void getMapgenNames( + std::vector *mgnames, bool include_hidden); static void setDefaultSettings(Settings *settings); private: @@ -242,7 +249,8 @@ private: Note that you must still create your own generateTerrain implementation when inheriting MapgenBasic. */ -class MapgenBasic : public Mapgen { +class MapgenBasic : public Mapgen +{ public: MapgenBasic(int mapgenid, MapgenParams *params, EmergeParams *emerge); virtual ~MapgenBasic(); diff --git a/src/mapgen/mapgen_carpathian.cpp b/src/mapgen/mapgen_carpathian.cpp index feb9b428c..47e33bde9 100644 --- a/src/mapgen/mapgen_carpathian.cpp +++ b/src/mapgen/mapgen_carpathian.cpp @@ -18,7 +18,6 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ - #include #include "mapgen.h" #include "voxel.h" @@ -38,68 +37,64 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "mg_decoration.h" #include "mapgen_carpathian.h" - -FlagDesc flagdesc_mapgen_carpathian[] = { - {"caverns", MGCARPATHIAN_CAVERNS}, - {"rivers", MGCARPATHIAN_RIVERS}, - {NULL, 0} -}; - +FlagDesc flagdesc_mapgen_carpathian[] = {{"caverns", MGCARPATHIAN_CAVERNS}, + {"rivers", MGCARPATHIAN_RIVERS}, {NULL, 0}}; /////////////////////////////////////////////////////////////////////////////// - -MapgenCarpathian::MapgenCarpathian(MapgenCarpathianParams *params, EmergeParams *emerge) - : MapgenBasic(MAPGEN_CARPATHIAN, params, emerge) +MapgenCarpathian::MapgenCarpathian(MapgenCarpathianParams *params, EmergeParams *emerge) : + MapgenBasic(MAPGEN_CARPATHIAN, params, emerge) { - base_level = params->base_level; - river_width = params->river_width; - river_depth = params->river_depth; - valley_width = params->valley_width; + base_level = params->base_level; + river_width = params->river_width; + river_depth = params->river_depth; + valley_width = params->valley_width; - spflags = params->spflags; - cave_width = params->cave_width; - large_cave_depth = params->large_cave_depth; + spflags = params->spflags; + cave_width = params->cave_width; + large_cave_depth = params->large_cave_depth; small_cave_num_min = params->small_cave_num_min; small_cave_num_max = params->small_cave_num_max; large_cave_num_min = params->large_cave_num_min; large_cave_num_max = params->large_cave_num_max; large_cave_flooded = params->large_cave_flooded; - cavern_limit = params->cavern_limit; - cavern_taper = params->cavern_taper; - cavern_threshold = params->cavern_threshold; - dungeon_ymin = params->dungeon_ymin; - dungeon_ymax = params->dungeon_ymax; + cavern_limit = params->cavern_limit; + cavern_taper = params->cavern_taper; + cavern_threshold = params->cavern_threshold; + dungeon_ymin = params->dungeon_ymin; + dungeon_ymax = params->dungeon_ymax; grad_wl = 1 - water_level; //// 2D Terrain noise - noise_filler_depth = new Noise(¶ms->np_filler_depth, seed, csize.X, csize.Z); - noise_height1 = new Noise(¶ms->np_height1, seed, csize.X, csize.Z); - noise_height2 = new Noise(¶ms->np_height2, seed, csize.X, csize.Z); - noise_height3 = new Noise(¶ms->np_height3, seed, csize.X, csize.Z); - noise_height4 = new Noise(¶ms->np_height4, seed, csize.X, csize.Z); - noise_hills_terrain = new Noise(¶ms->np_hills_terrain, seed, csize.X, csize.Z); - noise_ridge_terrain = new Noise(¶ms->np_ridge_terrain, seed, csize.X, csize.Z); - noise_step_terrain = new Noise(¶ms->np_step_terrain, seed, csize.X, csize.Z); - noise_hills = new Noise(¶ms->np_hills, seed, csize.X, csize.Z); - noise_ridge_mnt = new Noise(¶ms->np_ridge_mnt, seed, csize.X, csize.Z); - noise_step_mnt = new Noise(¶ms->np_step_mnt, seed, csize.X, csize.Z); + noise_filler_depth = new Noise(¶ms->np_filler_depth, seed, csize.X, csize.Z); + noise_height1 = new Noise(¶ms->np_height1, seed, csize.X, csize.Z); + noise_height2 = new Noise(¶ms->np_height2, seed, csize.X, csize.Z); + noise_height3 = new Noise(¶ms->np_height3, seed, csize.X, csize.Z); + noise_height4 = new Noise(¶ms->np_height4, seed, csize.X, csize.Z); + noise_hills_terrain = + new Noise(¶ms->np_hills_terrain, seed, csize.X, csize.Z); + noise_ridge_terrain = + new Noise(¶ms->np_ridge_terrain, seed, csize.X, csize.Z); + noise_step_terrain = new Noise(¶ms->np_step_terrain, seed, csize.X, csize.Z); + noise_hills = new Noise(¶ms->np_hills, seed, csize.X, csize.Z); + noise_ridge_mnt = new Noise(¶ms->np_ridge_mnt, seed, csize.X, csize.Z); + noise_step_mnt = new Noise(¶ms->np_step_mnt, seed, csize.X, csize.Z); if (spflags & MGCARPATHIAN_RIVERS) - noise_rivers = new Noise(¶ms->np_rivers, seed, csize.X, csize.Z); + noise_rivers = new Noise(¶ms->np_rivers, seed, csize.X, csize.Z); //// 3D terrain noise // 1 up 1 down overgeneration - noise_mnt_var = new Noise(¶ms->np_mnt_var, seed, csize.X, csize.Y + 2, csize.Z); + noise_mnt_var = new Noise( + ¶ms->np_mnt_var, seed, csize.X, csize.Y + 2, csize.Z); //// Cave noise - MapgenBasic::np_cave1 = params->np_cave1; - MapgenBasic::np_cave2 = params->np_cave2; + MapgenBasic::np_cave1 = params->np_cave1; + MapgenBasic::np_cave2 = params->np_cave2; MapgenBasic::np_cavern = params->np_cavern; MapgenBasic::np_dungeons = params->np_dungeons; } - MapgenCarpathian::~MapgenCarpathian() { delete noise_filler_depth; @@ -119,122 +114,118 @@ MapgenCarpathian::~MapgenCarpathian() delete noise_mnt_var; } - -MapgenCarpathianParams::MapgenCarpathianParams(): - np_filler_depth (0, 1, v3f(128, 128, 128), 261, 3, 0.7, 2.0), - np_height1 (0, 5, v3f(251, 251, 251), 9613, 5, 0.5, 2.0), - np_height2 (0, 5, v3f(383, 383, 383), 1949, 5, 0.5, 2.0), - np_height3 (0, 5, v3f(509, 509, 509), 3211, 5, 0.5, 2.0), - np_height4 (0, 5, v3f(631, 631, 631), 1583, 5, 0.5, 2.0), - np_hills_terrain (1, 1, v3f(1301, 1301, 1301), 1692, 5, 0.5, 2.0), - np_ridge_terrain (1, 1, v3f(1889, 1889, 1889), 3568, 5, 0.5, 2.0), - np_step_terrain (1, 1, v3f(1889, 1889, 1889), 4157, 5, 0.5, 2.0), - np_hills (0, 3, v3f(257, 257, 257), 6604, 6, 0.5, 2.0), - np_ridge_mnt (0, 12, v3f(743, 743, 743), 5520, 6, 0.7, 2.0), - np_step_mnt (0, 8, v3f(509, 509, 509), 2590, 6, 0.6, 2.0), - np_rivers (0, 1, v3f(1000, 1000, 1000), 85039, 5, 0.6, 2.0), - np_mnt_var (0, 1, v3f(499, 499, 499), 2490, 5, 0.55, 2.0), - np_cave1 (0, 12, v3f(61, 61, 61), 52534, 3, 0.5, 2.0), - np_cave2 (0, 12, v3f(67, 67, 67), 10325, 3, 0.5, 2.0), - np_cavern (0, 1, v3f(384, 128, 384), 723, 5, 0.63, 2.0), - np_dungeons (0.9, 0.5, v3f(500, 500, 500), 0, 2, 0.8, 2.0) +MapgenCarpathianParams::MapgenCarpathianParams() : + np_filler_depth(0, 1, v3f(128, 128, 128), 261, 3, 0.7, 2.0), + np_height1(0, 5, v3f(251, 251, 251), 9613, 5, 0.5, 2.0), + np_height2(0, 5, v3f(383, 383, 383), 1949, 5, 0.5, 2.0), + np_height3(0, 5, v3f(509, 509, 509), 3211, 5, 0.5, 2.0), + np_height4(0, 5, v3f(631, 631, 631), 1583, 5, 0.5, 2.0), + np_hills_terrain(1, 1, v3f(1301, 1301, 1301), 1692, 5, 0.5, 2.0), + np_ridge_terrain(1, 1, v3f(1889, 1889, 1889), 3568, 5, 0.5, 2.0), + np_step_terrain(1, 1, v3f(1889, 1889, 1889), 4157, 5, 0.5, 2.0), + np_hills(0, 3, v3f(257, 257, 257), 6604, 6, 0.5, 2.0), + np_ridge_mnt(0, 12, v3f(743, 743, 743), 5520, 6, 0.7, 2.0), + np_step_mnt(0, 8, v3f(509, 509, 509), 2590, 6, 0.6, 2.0), + np_rivers(0, 1, v3f(1000, 1000, 1000), 85039, 5, 0.6, 2.0), + np_mnt_var(0, 1, v3f(499, 499, 499), 2490, 5, 0.55, 2.0), + np_cave1(0, 12, v3f(61, 61, 61), 52534, 3, 0.5, 2.0), + np_cave2(0, 12, v3f(67, 67, 67), 10325, 3, 0.5, 2.0), + np_cavern(0, 1, v3f(384, 128, 384), 723, 5, 0.63, 2.0), + np_dungeons(0.9, 0.5, v3f(500, 500, 500), 0, 2, 0.8, 2.0) { } - void MapgenCarpathianParams::readParams(const Settings *settings) { - settings->getFlagStrNoEx("mgcarpathian_spflags", spflags, flagdesc_mapgen_carpathian); + settings->getFlagStrNoEx( + "mgcarpathian_spflags", spflags, flagdesc_mapgen_carpathian); - settings->getFloatNoEx("mgcarpathian_base_level", base_level); - settings->getFloatNoEx("mgcarpathian_river_width", river_width); - settings->getFloatNoEx("mgcarpathian_river_depth", river_depth); + settings->getFloatNoEx("mgcarpathian_base_level", base_level); + settings->getFloatNoEx("mgcarpathian_river_width", river_width); + settings->getFloatNoEx("mgcarpathian_river_depth", river_depth); settings->getFloatNoEx("mgcarpathian_valley_width", valley_width); - settings->getFloatNoEx("mgcarpathian_cave_width", cave_width); - settings->getS16NoEx("mgcarpathian_large_cave_depth", large_cave_depth); - settings->getU16NoEx("mgcarpathian_small_cave_num_min", small_cave_num_min); - settings->getU16NoEx("mgcarpathian_small_cave_num_max", small_cave_num_max); - settings->getU16NoEx("mgcarpathian_large_cave_num_min", large_cave_num_min); - settings->getU16NoEx("mgcarpathian_large_cave_num_max", large_cave_num_max); + settings->getFloatNoEx("mgcarpathian_cave_width", cave_width); + settings->getS16NoEx("mgcarpathian_large_cave_depth", large_cave_depth); + settings->getU16NoEx("mgcarpathian_small_cave_num_min", small_cave_num_min); + settings->getU16NoEx("mgcarpathian_small_cave_num_max", small_cave_num_max); + settings->getU16NoEx("mgcarpathian_large_cave_num_min", large_cave_num_min); + settings->getU16NoEx("mgcarpathian_large_cave_num_max", large_cave_num_max); settings->getFloatNoEx("mgcarpathian_large_cave_flooded", large_cave_flooded); - settings->getS16NoEx("mgcarpathian_cavern_limit", cavern_limit); - settings->getS16NoEx("mgcarpathian_cavern_taper", cavern_taper); - settings->getFloatNoEx("mgcarpathian_cavern_threshold", cavern_threshold); - settings->getS16NoEx("mgcarpathian_dungeon_ymin", dungeon_ymin); - settings->getS16NoEx("mgcarpathian_dungeon_ymax", dungeon_ymax); + settings->getS16NoEx("mgcarpathian_cavern_limit", cavern_limit); + settings->getS16NoEx("mgcarpathian_cavern_taper", cavern_taper); + settings->getFloatNoEx("mgcarpathian_cavern_threshold", cavern_threshold); + settings->getS16NoEx("mgcarpathian_dungeon_ymin", dungeon_ymin); + settings->getS16NoEx("mgcarpathian_dungeon_ymax", dungeon_ymax); - settings->getNoiseParams("mgcarpathian_np_filler_depth", np_filler_depth); - settings->getNoiseParams("mgcarpathian_np_height1", np_height1); - settings->getNoiseParams("mgcarpathian_np_height2", np_height2); - settings->getNoiseParams("mgcarpathian_np_height3", np_height3); - settings->getNoiseParams("mgcarpathian_np_height4", np_height4); + settings->getNoiseParams("mgcarpathian_np_filler_depth", np_filler_depth); + settings->getNoiseParams("mgcarpathian_np_height1", np_height1); + settings->getNoiseParams("mgcarpathian_np_height2", np_height2); + settings->getNoiseParams("mgcarpathian_np_height3", np_height3); + settings->getNoiseParams("mgcarpathian_np_height4", np_height4); settings->getNoiseParams("mgcarpathian_np_hills_terrain", np_hills_terrain); settings->getNoiseParams("mgcarpathian_np_ridge_terrain", np_ridge_terrain); - settings->getNoiseParams("mgcarpathian_np_step_terrain", np_step_terrain); - settings->getNoiseParams("mgcarpathian_np_hills", np_hills); - settings->getNoiseParams("mgcarpathian_np_ridge_mnt", np_ridge_mnt); - settings->getNoiseParams("mgcarpathian_np_step_mnt", np_step_mnt); - settings->getNoiseParams("mgcarpathian_np_rivers", np_rivers); - settings->getNoiseParams("mgcarpathian_np_mnt_var", np_mnt_var); - settings->getNoiseParams("mgcarpathian_np_cave1", np_cave1); - settings->getNoiseParams("mgcarpathian_np_cave2", np_cave2); - settings->getNoiseParams("mgcarpathian_np_cavern", np_cavern); - settings->getNoiseParams("mgcarpathian_np_dungeons", np_dungeons); + settings->getNoiseParams("mgcarpathian_np_step_terrain", np_step_terrain); + settings->getNoiseParams("mgcarpathian_np_hills", np_hills); + settings->getNoiseParams("mgcarpathian_np_ridge_mnt", np_ridge_mnt); + settings->getNoiseParams("mgcarpathian_np_step_mnt", np_step_mnt); + settings->getNoiseParams("mgcarpathian_np_rivers", np_rivers); + settings->getNoiseParams("mgcarpathian_np_mnt_var", np_mnt_var); + settings->getNoiseParams("mgcarpathian_np_cave1", np_cave1); + settings->getNoiseParams("mgcarpathian_np_cave2", np_cave2); + settings->getNoiseParams("mgcarpathian_np_cavern", np_cavern); + settings->getNoiseParams("mgcarpathian_np_dungeons", np_dungeons); } - void MapgenCarpathianParams::writeParams(Settings *settings) const { settings->setFlagStr("mgcarpathian_spflags", spflags, flagdesc_mapgen_carpathian); - settings->setFloat("mgcarpathian_base_level", base_level); - settings->setFloat("mgcarpathian_river_width", river_width); - settings->setFloat("mgcarpathian_river_depth", river_depth); + settings->setFloat("mgcarpathian_base_level", base_level); + settings->setFloat("mgcarpathian_river_width", river_width); + settings->setFloat("mgcarpathian_river_depth", river_depth); settings->setFloat("mgcarpathian_valley_width", valley_width); - settings->setFloat("mgcarpathian_cave_width", cave_width); - settings->setS16("mgcarpathian_large_cave_depth", large_cave_depth); - settings->setU16("mgcarpathian_small_cave_num_min", small_cave_num_min); - settings->setU16("mgcarpathian_small_cave_num_max", small_cave_num_max); - settings->setU16("mgcarpathian_large_cave_num_min", large_cave_num_min); - settings->setU16("mgcarpathian_large_cave_num_max", large_cave_num_max); + settings->setFloat("mgcarpathian_cave_width", cave_width); + settings->setS16("mgcarpathian_large_cave_depth", large_cave_depth); + settings->setU16("mgcarpathian_small_cave_num_min", small_cave_num_min); + settings->setU16("mgcarpathian_small_cave_num_max", small_cave_num_max); + settings->setU16("mgcarpathian_large_cave_num_min", large_cave_num_min); + settings->setU16("mgcarpathian_large_cave_num_max", large_cave_num_max); settings->setFloat("mgcarpathian_large_cave_flooded", large_cave_flooded); - settings->setS16("mgcarpathian_cavern_limit", cavern_limit); - settings->setS16("mgcarpathian_cavern_taper", cavern_taper); - settings->setFloat("mgcarpathian_cavern_threshold", cavern_threshold); - settings->setS16("mgcarpathian_dungeon_ymin", dungeon_ymin); - settings->setS16("mgcarpathian_dungeon_ymax", dungeon_ymax); + settings->setS16("mgcarpathian_cavern_limit", cavern_limit); + settings->setS16("mgcarpathian_cavern_taper", cavern_taper); + settings->setFloat("mgcarpathian_cavern_threshold", cavern_threshold); + settings->setS16("mgcarpathian_dungeon_ymin", dungeon_ymin); + settings->setS16("mgcarpathian_dungeon_ymax", dungeon_ymax); - settings->setNoiseParams("mgcarpathian_np_filler_depth", np_filler_depth); - settings->setNoiseParams("mgcarpathian_np_height1", np_height1); - settings->setNoiseParams("mgcarpathian_np_height2", np_height2); - settings->setNoiseParams("mgcarpathian_np_height3", np_height3); - settings->setNoiseParams("mgcarpathian_np_height4", np_height4); + settings->setNoiseParams("mgcarpathian_np_filler_depth", np_filler_depth); + settings->setNoiseParams("mgcarpathian_np_height1", np_height1); + settings->setNoiseParams("mgcarpathian_np_height2", np_height2); + settings->setNoiseParams("mgcarpathian_np_height3", np_height3); + settings->setNoiseParams("mgcarpathian_np_height4", np_height4); settings->setNoiseParams("mgcarpathian_np_hills_terrain", np_hills_terrain); settings->setNoiseParams("mgcarpathian_np_ridge_terrain", np_ridge_terrain); - settings->setNoiseParams("mgcarpathian_np_step_terrain", np_step_terrain); - settings->setNoiseParams("mgcarpathian_np_hills", np_hills); - settings->setNoiseParams("mgcarpathian_np_ridge_mnt", np_ridge_mnt); - settings->setNoiseParams("mgcarpathian_np_step_mnt", np_step_mnt); - settings->setNoiseParams("mgcarpathian_np_rivers", np_rivers); - settings->setNoiseParams("mgcarpathian_np_mnt_var", np_mnt_var); - settings->setNoiseParams("mgcarpathian_np_cave1", np_cave1); - settings->setNoiseParams("mgcarpathian_np_cave2", np_cave2); - settings->setNoiseParams("mgcarpathian_np_cavern", np_cavern); - settings->setNoiseParams("mgcarpathian_np_dungeons", np_dungeons); + settings->setNoiseParams("mgcarpathian_np_step_terrain", np_step_terrain); + settings->setNoiseParams("mgcarpathian_np_hills", np_hills); + settings->setNoiseParams("mgcarpathian_np_ridge_mnt", np_ridge_mnt); + settings->setNoiseParams("mgcarpathian_np_step_mnt", np_step_mnt); + settings->setNoiseParams("mgcarpathian_np_rivers", np_rivers); + settings->setNoiseParams("mgcarpathian_np_mnt_var", np_mnt_var); + settings->setNoiseParams("mgcarpathian_np_cave1", np_cave1); + settings->setNoiseParams("mgcarpathian_np_cave2", np_cave2); + settings->setNoiseParams("mgcarpathian_np_cavern", np_cavern); + settings->setNoiseParams("mgcarpathian_np_dungeons", np_dungeons); } - void MapgenCarpathianParams::setDefaultSettings(Settings *settings) { settings->setDefault("mgcarpathian_spflags", flagdesc_mapgen_carpathian, - MGCARPATHIAN_CAVERNS); + MGCARPATHIAN_CAVERNS); } //////////////////////////////////////////////////////////////////////////////// - // Lerp function inline float MapgenCarpathian::getLerp(float noise1, float noise2, float mod) { @@ -251,10 +242,8 @@ float MapgenCarpathian::getSteps(float noise) return (k + s) * w; } - //////////////////////////////////////////////////////////////////////////////// - void MapgenCarpathian::makeChunk(BlockMakeData *data) { // Pre-conditions @@ -308,8 +297,8 @@ void MapgenCarpathian::makeChunk(BlockMakeData *data) // Disable large randomwalk caves in this mapchunk by setting // 'large cave depth' to world base. Avoids excessive liquid in // large caverns and floating blobs of overgenerated liquid. - generateCavesRandomWalk(stone_surface_max_y, - -MAX_MAP_GENERATION_LIMIT); + generateCavesRandomWalk( + stone_surface_max_y, -MAX_MAP_GENERATION_LIMIT); else generateCavesRandomWalk(stone_surface_max_y, large_cave_depth); } @@ -341,16 +330,15 @@ void MapgenCarpathian::makeChunk(BlockMakeData *data) this->generating = false; } - //////////////////////////////////////////////////////////////////////////////// - int MapgenCarpathian::getSpawnLevelAtPoint(v2s16 p) { // If rivers are enabled, first check if in a river channel if (spflags & MGCARPATHIAN_RIVERS) { - float river = std::fabs(NoisePerlin2D(&noise_rivers->np, p.X, p.Y, seed)) - - river_width; + float river = std::fabs(NoisePerlin2D( + &noise_rivers->np, p.X, p.Y, seed)) - + river_width; if (river < 0.0f) return MAX_MAP_GENERATION_LIMIT; // Unsuitable spawn point } @@ -360,11 +348,13 @@ int MapgenCarpathian::getSpawnLevelAtPoint(v2s16 p) float height3 = NoisePerlin2D(&noise_height3->np, p.X, p.Y, seed); float height4 = NoisePerlin2D(&noise_height4->np, p.X, p.Y, seed); - float hterabs = std::fabs(NoisePerlin2D(&noise_hills_terrain->np, p.X, p.Y, seed)); + float hterabs = std::fabs( + NoisePerlin2D(&noise_hills_terrain->np, p.X, p.Y, seed)); float n_hills = NoisePerlin2D(&noise_hills->np, p.X, p.Y, seed); float hill_mnt = hterabs * hterabs * hterabs * n_hills * n_hills; - float rterabs = std::fabs(NoisePerlin2D(&noise_ridge_terrain->np, p.X, p.Y, seed)); + float rterabs = std::fabs( + NoisePerlin2D(&noise_ridge_terrain->np, p.X, p.Y, seed)); float n_ridge_mnt = NoisePerlin2D(&noise_ridge_mnt->np, p.X, p.Y, seed); float ridge_mnt = rterabs * rterabs * rterabs * (1.0f - std::fabs(n_ridge_mnt)); @@ -376,7 +366,8 @@ int MapgenCarpathian::getSpawnLevelAtPoint(v2s16 p) float river = 0.0f; if ((spflags & MGCARPATHIAN_RIVERS) && node_max.Y >= water_level - 16) { - river = std::fabs(NoisePerlin2D(&noise_rivers->np, p.X, p.Y, seed)) - river_width; + river = std::fabs(NoisePerlin2D(&noise_rivers->np, p.X, p.Y, seed)) - + river_width; if (river <= valley_width) { // Within river valley if (river < 0.0f) { @@ -402,7 +393,8 @@ int MapgenCarpathian::getSpawnLevelAtPoint(v2s16 p) float hill3 = getLerp(height3, height2, mnt_var); float hill4 = getLerp(height1, height4, mnt_var); - float hilliness = std::fmax(std::fmin(hill1, hill2), std::fmin(hill3, hill4)); + float hilliness = std::fmax( + std::fmin(hill1, hill2), std::fmin(hill3, hill4)); float hills = hill_mnt * hilliness; float ridged_mountains = ridge_mnt * hilliness; float step_mountains = step_mnt * hilliness; @@ -416,14 +408,16 @@ int MapgenCarpathian::getSpawnLevelAtPoint(v2s16 p) if (valley < 0.0f) { // River channel surface_level = std::fmin(surface_level, - water_level - std::sqrt(-valley) * river_depth); + water_level - std::sqrt(-valley) * + river_depth); } else if (surface_level > water_level) { // Valley slopes - surface_level = water_level + (surface_level - water_level) * valley; + surface_level = water_level + + (surface_level - water_level) * valley; } } - if (y < surface_level) { //TODO '<=' fix from generateTerrain() + if (y < surface_level) { // TODO '<=' fix from generateTerrain() // solid node solid_below = true; cons_non_solid = 0; @@ -438,10 +432,8 @@ int MapgenCarpathian::getSpawnLevelAtPoint(v2s16 p) return MAX_MAP_GENERATION_LIMIT; // No suitable spawn point found } - //////////////////////////////////////////////////////////////////////////////// - int MapgenCarpathian::generateTerrain() { MapNode mn_air(CONTENT_AIR); @@ -470,108 +462,117 @@ int MapgenCarpathian::generateTerrain() u32 index2d = 0; for (s16 z = node_min.Z; z <= node_max.Z; z++) - for (s16 x = node_min.X; x <= node_max.X; x++, index2d++) { - // Hill/Mountain height (hilliness) - float height1 = noise_height1->result[index2d]; - float height2 = noise_height2->result[index2d]; - float height3 = noise_height3->result[index2d]; - float height4 = noise_height4->result[index2d]; + for (s16 x = node_min.X; x <= node_max.X; x++, index2d++) { + // Hill/Mountain height (hilliness) + float height1 = noise_height1->result[index2d]; + float height2 = noise_height2->result[index2d]; + float height3 = noise_height3->result[index2d]; + float height4 = noise_height4->result[index2d]; - // Rolling hills - float hterabs = std::fabs(noise_hills_terrain->result[index2d]); - float n_hills = noise_hills->result[index2d]; - float hill_mnt = hterabs * hterabs * hterabs * n_hills * n_hills; + // Rolling hills + float hterabs = std::fabs(noise_hills_terrain->result[index2d]); + float n_hills = noise_hills->result[index2d]; + float hill_mnt = hterabs * hterabs * hterabs * n_hills * n_hills; - // Ridged mountains - float rterabs = std::fabs(noise_ridge_terrain->result[index2d]); - float n_ridge_mnt = noise_ridge_mnt->result[index2d]; - float ridge_mnt = rterabs * rterabs * rterabs * - (1.0f - std::fabs(n_ridge_mnt)); + // Ridged mountains + float rterabs = std::fabs(noise_ridge_terrain->result[index2d]); + float n_ridge_mnt = noise_ridge_mnt->result[index2d]; + float ridge_mnt = rterabs * rterabs * rterabs * + (1.0f - std::fabs(n_ridge_mnt)); - // Step (terraced) mountains - float sterabs = std::fabs(noise_step_terrain->result[index2d]); - float n_step_mnt = noise_step_mnt->result[index2d]; - float step_mnt = sterabs * sterabs * sterabs * getSteps(n_step_mnt); - - // Rivers - float valley = 1.0f; - float river = 0.0f; - - if ((spflags & MGCARPATHIAN_RIVERS) && node_max.Y >= water_level - 16) { - river = std::fabs(noise_rivers->result[index2d]) - river_width; - if (river <= valley_width) { - // Within river valley - if (river < 0.0f) { - // River channel - valley = river; - } else { - // Valley slopes. - // 0 at river edge, 1 at valley edge. - float riversc = river / valley_width; - // Smoothstep - valley = riversc * riversc * (3.0f - 2.0f * riversc); - } - } - } - - // Initialise 3D noise index and voxelmanip index to column base - u32 index3d = (z - node_min.Z) * zstride_1u1d + (x - node_min.X); - u32 vi = vm->m_area.index(x, node_min.Y - 1, z); - - for (s16 y = node_min.Y - 1; y <= node_max.Y + 1; - y++, - index3d += ystride, - VoxelArea::add_y(em, vi, 1)) { - if (vm->m_data[vi].getContent() != CONTENT_IGNORE) - continue; - - // Combine height noises and apply 3D variation - float mnt_var = noise_mnt_var->result[index3d]; - float hill1 = getLerp(height1, height2, mnt_var); - float hill2 = getLerp(height3, height4, mnt_var); - float hill3 = getLerp(height3, height2, mnt_var); - float hill4 = getLerp(height1, height4, mnt_var); - - // 'hilliness' determines whether hills/mountains are - // small or large - float hilliness = - std::fmax(std::fmin(hill1, hill2), std::fmin(hill3, hill4)); - float hills = hill_mnt * hilliness; - float ridged_mountains = ridge_mnt * hilliness; - float step_mountains = step_mnt * hilliness; - - // Gradient & shallow seabed - s32 grad = (y < water_level) ? grad_wl + (water_level - y) * 3 : - 1 - y; - - // Final terrain level - float mountains = hills + ridged_mountains + step_mountains; - float surface_level = base_level + mountains + grad; + // Step (terraced) mountains + float sterabs = std::fabs(noise_step_terrain->result[index2d]); + float n_step_mnt = noise_step_mnt->result[index2d]; + float step_mnt = sterabs * sterabs * sterabs * + getSteps(n_step_mnt); // Rivers - if ((spflags & MGCARPATHIAN_RIVERS) && node_max.Y >= water_level - 16 && - river <= valley_width) { - if (valley < 0.0f) { - // River channel - surface_level = std::fmin(surface_level, - water_level - std::sqrt(-valley) * river_depth); - } else if (surface_level > water_level) { - // Valley slopes - surface_level = water_level + (surface_level - water_level) * valley; + float valley = 1.0f; + float river = 0.0f; + + if ((spflags & MGCARPATHIAN_RIVERS) && + node_max.Y >= water_level - 16) { + river = std::fabs(noise_rivers->result[index2d]) - + river_width; + if (river <= valley_width) { + // Within river valley + if (river < 0.0f) { + // River channel + valley = river; + } else { + // Valley slopes. + // 0 at river edge, 1 at valley edge. + float riversc = river / valley_width; + // Smoothstep + valley = riversc * riversc * + (3.0f - 2.0f * riversc); + } } } - if (y < surface_level) { //TODO '<=' - vm->m_data[vi] = mn_stone; // Stone - if (y > stone_surface_max_y) - stone_surface_max_y = y; - } else if (y <= water_level) { - vm->m_data[vi] = mn_water; // Sea water - } else { - vm->m_data[vi] = mn_air; // Air + // Initialise 3D noise index and voxelmanip index to column base + u32 index3d = (z - node_min.Z) * zstride_1u1d + (x - node_min.X); + u32 vi = vm->m_area.index(x, node_min.Y - 1, z); + + for (s16 y = node_min.Y - 1; y <= node_max.Y + 1; y++, + index3d += ystride, VoxelArea::add_y(em, vi, 1)) { + if (vm->m_data[vi].getContent() != CONTENT_IGNORE) + continue; + + // Combine height noises and apply 3D variation + float mnt_var = noise_mnt_var->result[index3d]; + float hill1 = getLerp(height1, height2, mnt_var); + float hill2 = getLerp(height3, height4, mnt_var); + float hill3 = getLerp(height3, height2, mnt_var); + float hill4 = getLerp(height1, height4, mnt_var); + + // 'hilliness' determines whether hills/mountains are + // small or large + float hilliness = std::fmax(std::fmin(hill1, hill2), + std::fmin(hill3, hill4)); + float hills = hill_mnt * hilliness; + float ridged_mountains = ridge_mnt * hilliness; + float step_mountains = step_mnt * hilliness; + + // Gradient & shallow seabed + s32 grad = (y < water_level) + ? grad_wl + (water_level - y) * 3 + : 1 - y; + + // Final terrain level + float mountains = + hills + ridged_mountains + step_mountains; + float surface_level = base_level + mountains + grad; + + // Rivers + if ((spflags & MGCARPATHIAN_RIVERS) && + node_max.Y >= water_level - 16 && + river <= valley_width) { + if (valley < 0.0f) { + // River channel + surface_level = std::fmin(surface_level, + water_level - std::sqrt(-valley) * + river_depth); + } else if (surface_level > water_level) { + // Valley slopes + surface_level = water_level + + (surface_level - + water_level) * + valley; + } + } + + if (y < surface_level) { // TODO '<=' + vm->m_data[vi] = mn_stone; // Stone + if (y > stone_surface_max_y) + stone_surface_max_y = y; + } else if (y <= water_level) { + vm->m_data[vi] = mn_water; // Sea water + } else { + vm->m_data[vi] = mn_air; // Air + } } } - } return stone_surface_max_y; } diff --git a/src/mapgen/mapgen_carpathian.h b/src/mapgen/mapgen_carpathian.h index 31b2b91d8..5c44c16e3 100644 --- a/src/mapgen/mapgen_carpathian.h +++ b/src/mapgen/mapgen_carpathian.h @@ -23,32 +23,31 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "mapgen.h" #define MGCARPATHIAN_CAVERNS 0x01 -#define MGCARPATHIAN_RIVERS 0x02 +#define MGCARPATHIAN_RIVERS 0x02 class BiomeManager; extern FlagDesc flagdesc_mapgen_carpathian[]; - struct MapgenCarpathianParams : public MapgenParams { - float base_level = 12.0f; - float river_width = 0.05f; - float river_depth = 24.0f; - float valley_width = 0.25f; + float base_level = 12.0f; + float river_width = 0.05f; + float river_depth = 24.0f; + float valley_width = 0.25f; - float cave_width = 0.09f; - s16 large_cave_depth = -33; - u16 small_cave_num_min = 0; - u16 small_cave_num_max = 0; - u16 large_cave_num_min = 0; - u16 large_cave_num_max = 2; + float cave_width = 0.09f; + s16 large_cave_depth = -33; + u16 small_cave_num_min = 0; + u16 small_cave_num_max = 0; + u16 large_cave_num_min = 0; + u16 large_cave_num_max = 2; float large_cave_flooded = 0.5f; - s16 cavern_limit = -256; - s16 cavern_taper = 256; - float cavern_threshold = 0.7f; - s16 dungeon_ymin = -31000; - s16 dungeon_ymax = 31000; + s16 cavern_limit = -256; + s16 cavern_taper = 256; + float cavern_threshold = 0.7f; + s16 dungeon_ymin = -31000; + s16 dungeon_ymax = 31000; NoiseParams np_filler_depth; NoiseParams np_height1; diff --git a/src/mapgen/mapgen_flat.cpp b/src/mapgen/mapgen_flat.cpp index 369777ad2..e24f1e4a1 100644 --- a/src/mapgen/mapgen_flat.cpp +++ b/src/mapgen/mapgen_flat.cpp @@ -18,7 +18,6 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ - #include "mapgen.h" #include "voxel.h" #include "noise.h" @@ -37,34 +36,29 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "mg_decoration.h" #include "mapgen_flat.h" - FlagDesc flagdesc_mapgen_flat[] = { - {"lakes", MGFLAT_LAKES}, - {"hills", MGFLAT_HILLS}, - {NULL, 0} -}; + {"lakes", MGFLAT_LAKES}, {"hills", MGFLAT_HILLS}, {NULL, 0}}; /////////////////////////////////////////////////////////////////////////////////////// - -MapgenFlat::MapgenFlat(MapgenFlatParams *params, EmergeParams *emerge) - : MapgenBasic(MAPGEN_FLAT, params, emerge) +MapgenFlat::MapgenFlat(MapgenFlatParams *params, EmergeParams *emerge) : + MapgenBasic(MAPGEN_FLAT, params, emerge) { - spflags = params->spflags; - ground_level = params->ground_level; - large_cave_depth = params->large_cave_depth; + spflags = params->spflags; + ground_level = params->ground_level; + large_cave_depth = params->large_cave_depth; small_cave_num_min = params->small_cave_num_min; small_cave_num_max = params->small_cave_num_max; large_cave_num_min = params->large_cave_num_min; large_cave_num_max = params->large_cave_num_max; large_cave_flooded = params->large_cave_flooded; - cave_width = params->cave_width; - lake_threshold = params->lake_threshold; - lake_steepness = params->lake_steepness; - hill_threshold = params->hill_threshold; - hill_steepness = params->hill_steepness; - dungeon_ymin = params->dungeon_ymin; - dungeon_ymax = params->dungeon_ymax; + cave_width = params->cave_width; + lake_threshold = params->lake_threshold; + lake_steepness = params->lake_steepness; + hill_threshold = params->hill_threshold; + hill_steepness = params->hill_steepness; + dungeon_ymin = params->dungeon_ymin; + dungeon_ymax = params->dungeon_ymax; // 2D noise noise_filler_depth = new Noise(¶ms->np_filler_depth, seed, csize.X, csize.Z); @@ -72,12 +66,11 @@ MapgenFlat::MapgenFlat(MapgenFlatParams *params, EmergeParams *emerge) if ((spflags & MGFLAT_LAKES) || (spflags & MGFLAT_HILLS)) noise_terrain = new Noise(¶ms->np_terrain, seed, csize.X, csize.Z); // 3D noise - MapgenBasic::np_cave1 = params->np_cave1; - MapgenBasic::np_cave2 = params->np_cave2; + MapgenBasic::np_cave1 = params->np_cave1; + MapgenBasic::np_cave2 = params->np_cave2; MapgenBasic::np_dungeons = params->np_dungeons; } - MapgenFlat::~MapgenFlat() { delete noise_filler_depth; @@ -86,92 +79,86 @@ MapgenFlat::~MapgenFlat() delete noise_terrain; } - -MapgenFlatParams::MapgenFlatParams(): - np_terrain (0, 1, v3f(600, 600, 600), 7244, 5, 0.6, 2.0), - np_filler_depth (0, 1.2, v3f(150, 150, 150), 261, 3, 0.7, 2.0), - np_cave1 (0, 12, v3f(61, 61, 61), 52534, 3, 0.5, 2.0), - np_cave2 (0, 12, v3f(67, 67, 67), 10325, 3, 0.5, 2.0), - np_dungeons (0.9, 0.5, v3f(500, 500, 500), 0, 2, 0.8, 2.0) +MapgenFlatParams::MapgenFlatParams() : + np_terrain(0, 1, v3f(600, 600, 600), 7244, 5, 0.6, 2.0), + np_filler_depth(0, 1.2, v3f(150, 150, 150), 261, 3, 0.7, 2.0), + np_cave1(0, 12, v3f(61, 61, 61), 52534, 3, 0.5, 2.0), + np_cave2(0, 12, v3f(67, 67, 67), 10325, 3, 0.5, 2.0), + np_dungeons(0.9, 0.5, v3f(500, 500, 500), 0, 2, 0.8, 2.0) { } - void MapgenFlatParams::readParams(const Settings *settings) { settings->getFlagStrNoEx("mgflat_spflags", spflags, flagdesc_mapgen_flat); - settings->getS16NoEx("mgflat_ground_level", ground_level); - settings->getS16NoEx("mgflat_large_cave_depth", large_cave_depth); - settings->getU16NoEx("mgflat_small_cave_num_min", small_cave_num_min); - settings->getU16NoEx("mgflat_small_cave_num_max", small_cave_num_max); - settings->getU16NoEx("mgflat_large_cave_num_min", large_cave_num_min); - settings->getU16NoEx("mgflat_large_cave_num_max", large_cave_num_max); + settings->getS16NoEx("mgflat_ground_level", ground_level); + settings->getS16NoEx("mgflat_large_cave_depth", large_cave_depth); + settings->getU16NoEx("mgflat_small_cave_num_min", small_cave_num_min); + settings->getU16NoEx("mgflat_small_cave_num_max", small_cave_num_max); + settings->getU16NoEx("mgflat_large_cave_num_min", large_cave_num_min); + settings->getU16NoEx("mgflat_large_cave_num_max", large_cave_num_max); settings->getFloatNoEx("mgflat_large_cave_flooded", large_cave_flooded); - settings->getFloatNoEx("mgflat_cave_width", cave_width); - settings->getFloatNoEx("mgflat_lake_threshold", lake_threshold); - settings->getFloatNoEx("mgflat_lake_steepness", lake_steepness); - settings->getFloatNoEx("mgflat_hill_threshold", hill_threshold); - settings->getFloatNoEx("mgflat_hill_steepness", hill_steepness); - settings->getS16NoEx("mgflat_dungeon_ymin", dungeon_ymin); - settings->getS16NoEx("mgflat_dungeon_ymax", dungeon_ymax); + settings->getFloatNoEx("mgflat_cave_width", cave_width); + settings->getFloatNoEx("mgflat_lake_threshold", lake_threshold); + settings->getFloatNoEx("mgflat_lake_steepness", lake_steepness); + settings->getFloatNoEx("mgflat_hill_threshold", hill_threshold); + settings->getFloatNoEx("mgflat_hill_steepness", hill_steepness); + settings->getS16NoEx("mgflat_dungeon_ymin", dungeon_ymin); + settings->getS16NoEx("mgflat_dungeon_ymax", dungeon_ymax); - settings->getNoiseParams("mgflat_np_terrain", np_terrain); + settings->getNoiseParams("mgflat_np_terrain", np_terrain); settings->getNoiseParams("mgflat_np_filler_depth", np_filler_depth); - settings->getNoiseParams("mgflat_np_cave1", np_cave1); - settings->getNoiseParams("mgflat_np_cave2", np_cave2); - settings->getNoiseParams("mgflat_np_dungeons", np_dungeons); + settings->getNoiseParams("mgflat_np_cave1", np_cave1); + settings->getNoiseParams("mgflat_np_cave2", np_cave2); + settings->getNoiseParams("mgflat_np_dungeons", np_dungeons); } - void MapgenFlatParams::writeParams(Settings *settings) const { settings->setFlagStr("mgflat_spflags", spflags, flagdesc_mapgen_flat); - settings->setS16("mgflat_ground_level", ground_level); - settings->setS16("mgflat_large_cave_depth", large_cave_depth); - settings->setU16("mgflat_small_cave_num_min", small_cave_num_min); - settings->setU16("mgflat_small_cave_num_max", small_cave_num_max); - settings->setU16("mgflat_large_cave_num_min", large_cave_num_min); - settings->setU16("mgflat_large_cave_num_max", large_cave_num_max); + settings->setS16("mgflat_ground_level", ground_level); + settings->setS16("mgflat_large_cave_depth", large_cave_depth); + settings->setU16("mgflat_small_cave_num_min", small_cave_num_min); + settings->setU16("mgflat_small_cave_num_max", small_cave_num_max); + settings->setU16("mgflat_large_cave_num_min", large_cave_num_min); + settings->setU16("mgflat_large_cave_num_max", large_cave_num_max); settings->setFloat("mgflat_large_cave_flooded", large_cave_flooded); - settings->setFloat("mgflat_cave_width", cave_width); - settings->setFloat("mgflat_lake_threshold", lake_threshold); - settings->setFloat("mgflat_lake_steepness", lake_steepness); - settings->setFloat("mgflat_hill_threshold", hill_threshold); - settings->setFloat("mgflat_hill_steepness", hill_steepness); - settings->setS16("mgflat_dungeon_ymin", dungeon_ymin); - settings->setS16("mgflat_dungeon_ymax", dungeon_ymax); + settings->setFloat("mgflat_cave_width", cave_width); + settings->setFloat("mgflat_lake_threshold", lake_threshold); + settings->setFloat("mgflat_lake_steepness", lake_steepness); + settings->setFloat("mgflat_hill_threshold", hill_threshold); + settings->setFloat("mgflat_hill_steepness", hill_steepness); + settings->setS16("mgflat_dungeon_ymin", dungeon_ymin); + settings->setS16("mgflat_dungeon_ymax", dungeon_ymax); - settings->setNoiseParams("mgflat_np_terrain", np_terrain); + settings->setNoiseParams("mgflat_np_terrain", np_terrain); settings->setNoiseParams("mgflat_np_filler_depth", np_filler_depth); - settings->setNoiseParams("mgflat_np_cave1", np_cave1); - settings->setNoiseParams("mgflat_np_cave2", np_cave2); - settings->setNoiseParams("mgflat_np_dungeons", np_dungeons); + settings->setNoiseParams("mgflat_np_cave1", np_cave1); + settings->setNoiseParams("mgflat_np_cave2", np_cave2); + settings->setNoiseParams("mgflat_np_dungeons", np_dungeons); } - void MapgenFlatParams::setDefaultSettings(Settings *settings) { settings->setDefault("mgflat_spflags", flagdesc_mapgen_flat, 0); } - ///////////////////////////////////////////////////////////////// - int MapgenFlat::getSpawnLevelAtPoint(v2s16 p) { s16 stone_level = ground_level; - float n_terrain = - ((spflags & MGFLAT_LAKES) || (spflags & MGFLAT_HILLS)) ? - NoisePerlin2D(&noise_terrain->np, p.X, p.Y, seed) : - 0.0f; + float n_terrain = ((spflags & MGFLAT_LAKES) || (spflags & MGFLAT_HILLS)) + ? NoisePerlin2D(&noise_terrain->np, p.X, p.Y, + seed) + : 0.0f; if ((spflags & MGFLAT_LAKES) && n_terrain < lake_threshold) { s16 depress = (lake_threshold - n_terrain) * lake_steepness; stone_level = ground_level - depress; } else if ((spflags & MGFLAT_HILLS) && n_terrain > hill_threshold) { s16 rise = (n_terrain - hill_threshold) * hill_steepness; - stone_level = ground_level + rise; + stone_level = ground_level + rise; } if (ground_level < water_level) @@ -187,23 +174,22 @@ int MapgenFlat::getSpawnLevelAtPoint(v2s16 p) return MAX_MAP_GENERATION_LIMIT; } - void MapgenFlat::makeChunk(BlockMakeData *data) { // Pre-conditions assert(data->vmanip); assert(data->nodedef); assert(data->blockpos_requested.X >= data->blockpos_min.X && - data->blockpos_requested.Y >= data->blockpos_min.Y && - data->blockpos_requested.Z >= data->blockpos_min.Z); + data->blockpos_requested.Y >= data->blockpos_min.Y && + data->blockpos_requested.Z >= data->blockpos_min.Z); assert(data->blockpos_requested.X <= data->blockpos_max.X && - data->blockpos_requested.Y <= data->blockpos_max.Y && - data->blockpos_requested.Z <= data->blockpos_max.Z); + data->blockpos_requested.Y <= data->blockpos_max.Y && + data->blockpos_requested.Z <= data->blockpos_max.Z); this->generating = true; - this->vm = data->vmanip; + this->vm = data->vmanip; this->ndef = data->nodedef; - //TimeTaker t("makeChunk"); + // TimeTaker t("makeChunk"); v3s16 blockpos_min = data->blockpos_min; v3s16 blockpos_max = data->blockpos_max; @@ -247,21 +233,20 @@ void MapgenFlat::makeChunk(BlockMakeData *data) if (flags & MG_BIOMES) dustTopNodes(); - //printf("makeChunk: %dms\n", t.stop()); + // printf("makeChunk: %dms\n", t.stop()); updateLiquid(&data->transforming_liquid, full_node_min, full_node_max); if (flags & MG_LIGHT) calcLighting(node_min - v3s16(0, 1, 0), node_max + v3s16(0, 1, 0), - full_node_min, full_node_max); + full_node_min, full_node_max); - //setLighting(node_min - v3s16(1, 0, 1) * MAP_BLOCKSIZE, + // setLighting(node_min - v3s16(1, 0, 1) * MAP_BLOCKSIZE, // node_max + v3s16(1, 0, 1) * MAP_BLOCKSIZE, 0xFF); this->generating = false; } - s16 MapgenFlat::generateTerrain() { MapNode n_air(CONTENT_AIR); @@ -277,34 +262,36 @@ s16 MapgenFlat::generateTerrain() noise_terrain->perlinMap2D(node_min.X, node_min.Z); for (s16 z = node_min.Z; z <= node_max.Z; z++) - for (s16 x = node_min.X; x <= node_max.X; x++, ni2d++) { - s16 stone_level = ground_level; - float n_terrain = use_noise ? noise_terrain->result[ni2d] : 0.0f; + for (s16 x = node_min.X; x <= node_max.X; x++, ni2d++) { + s16 stone_level = ground_level; + float n_terrain = use_noise ? noise_terrain->result[ni2d] : 0.0f; - if ((spflags & MGFLAT_LAKES) && n_terrain < lake_threshold) { - s16 depress = (lake_threshold - n_terrain) * lake_steepness; - stone_level = ground_level - depress; - } else if ((spflags & MGFLAT_HILLS) && n_terrain > hill_threshold) { - s16 rise = (n_terrain - hill_threshold) * hill_steepness; - stone_level = ground_level + rise; - } - - u32 vi = vm->m_area.index(x, node_min.Y - 1, z); - for (s16 y = node_min.Y - 1; y <= node_max.Y + 1; y++) { - if (vm->m_data[vi].getContent() == CONTENT_IGNORE) { - if (y <= stone_level) { - vm->m_data[vi] = n_stone; - if (y > stone_surface_max_y) - stone_surface_max_y = y; - } else if (y <= water_level) { - vm->m_data[vi] = n_water; - } else { - vm->m_data[vi] = n_air; - } + if ((spflags & MGFLAT_LAKES) && n_terrain < lake_threshold) { + s16 depress = (lake_threshold - n_terrain) * + lake_steepness; + stone_level = ground_level - depress; + } else if ((spflags & MGFLAT_HILLS) && + n_terrain > hill_threshold) { + s16 rise = (n_terrain - hill_threshold) * hill_steepness; + stone_level = ground_level + rise; + } + + u32 vi = vm->m_area.index(x, node_min.Y - 1, z); + for (s16 y = node_min.Y - 1; y <= node_max.Y + 1; y++) { + if (vm->m_data[vi].getContent() == CONTENT_IGNORE) { + if (y <= stone_level) { + vm->m_data[vi] = n_stone; + if (y > stone_surface_max_y) + stone_surface_max_y = y; + } else if (y <= water_level) { + vm->m_data[vi] = n_water; + } else { + vm->m_data[vi] = n_air; + } + } + VoxelArea::add_y(em, vi, 1); } - VoxelArea::add_y(em, vi, 1); } - } return stone_surface_max_y; } diff --git a/src/mapgen/mapgen_fractal.cpp b/src/mapgen/mapgen_fractal.cpp index cb55bc288..fca8c9196 100644 --- a/src/mapgen/mapgen_fractal.cpp +++ b/src/mapgen/mapgen_fractal.cpp @@ -18,7 +18,6 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ - #include "mapgen.h" #include #include "voxel.h" @@ -38,37 +37,32 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "mg_decoration.h" #include "mapgen_fractal.h" - -FlagDesc flagdesc_mapgen_fractal[] = { - {"terrain", MGFRACTAL_TERRAIN}, - {NULL, 0} -}; +FlagDesc flagdesc_mapgen_fractal[] = {{"terrain", MGFRACTAL_TERRAIN}, {NULL, 0}}; /////////////////////////////////////////////////////////////////////////////////////// - -MapgenFractal::MapgenFractal(MapgenFractalParams *params, EmergeParams *emerge) - : MapgenBasic(MAPGEN_FRACTAL, params, emerge) +MapgenFractal::MapgenFractal(MapgenFractalParams *params, EmergeParams *emerge) : + MapgenBasic(MAPGEN_FRACTAL, params, emerge) { - spflags = params->spflags; - cave_width = params->cave_width; - large_cave_depth = params->large_cave_depth; + spflags = params->spflags; + cave_width = params->cave_width; + large_cave_depth = params->large_cave_depth; small_cave_num_min = params->small_cave_num_min; small_cave_num_max = params->small_cave_num_max; large_cave_num_min = params->large_cave_num_min; large_cave_num_max = params->large_cave_num_max; large_cave_flooded = params->large_cave_flooded; - dungeon_ymin = params->dungeon_ymin; - dungeon_ymax = params->dungeon_ymax; - fractal = params->fractal; - iterations = params->iterations; - scale = params->scale; - offset = params->offset; - slice_w = params->slice_w; - julia_x = params->julia_x; - julia_y = params->julia_y; - julia_z = params->julia_z; - julia_w = params->julia_w; + dungeon_ymin = params->dungeon_ymin; + dungeon_ymax = params->dungeon_ymax; + fractal = params->fractal; + iterations = params->iterations; + scale = params->scale; + offset = params->offset; + slice_w = params->slice_w; + julia_x = params->julia_x; + julia_y = params->julia_y; + julia_z = params->julia_z; + julia_w = params->julia_w; //// 2D noise if (spflags & MGFRACTAL_TERRAIN) @@ -79,106 +73,99 @@ MapgenFractal::MapgenFractal(MapgenFractalParams *params, EmergeParams *emerge) //// 3D noise MapgenBasic::np_dungeons = params->np_dungeons; // Overgeneration to node_min.Y - 1 - MapgenBasic::np_cave1 = params->np_cave1; - MapgenBasic::np_cave2 = params->np_cave2; + MapgenBasic::np_cave1 = params->np_cave1; + MapgenBasic::np_cave2 = params->np_cave2; formula = fractal / 2 + fractal % 2; - julia = fractal % 2 == 0; + julia = fractal % 2 == 0; } - MapgenFractal::~MapgenFractal() { delete noise_seabed; delete noise_filler_depth; } - -MapgenFractalParams::MapgenFractalParams(): - np_seabed (-14, 9, v3f(600, 600, 600), 41900, 5, 0.6, 2.0), - np_filler_depth (0, 1.2, v3f(150, 150, 150), 261, 3, 0.7, 2.0), - np_cave1 (0, 12, v3f(61, 61, 61), 52534, 3, 0.5, 2.0), - np_cave2 (0, 12, v3f(67, 67, 67), 10325, 3, 0.5, 2.0), - np_dungeons (0.9, 0.5, v3f(500, 500, 500), 0, 2, 0.8, 2.0) +MapgenFractalParams::MapgenFractalParams() : + np_seabed(-14, 9, v3f(600, 600, 600), 41900, 5, 0.6, 2.0), + np_filler_depth(0, 1.2, v3f(150, 150, 150), 261, 3, 0.7, 2.0), + np_cave1(0, 12, v3f(61, 61, 61), 52534, 3, 0.5, 2.0), + np_cave2(0, 12, v3f(67, 67, 67), 10325, 3, 0.5, 2.0), + np_dungeons(0.9, 0.5, v3f(500, 500, 500), 0, 2, 0.8, 2.0) { } - void MapgenFractalParams::readParams(const Settings *settings) { settings->getFlagStrNoEx("mgfractal_spflags", spflags, flagdesc_mapgen_fractal); - settings->getFloatNoEx("mgfractal_cave_width", cave_width); - settings->getS16NoEx("mgfractal_large_cave_depth", large_cave_depth); - settings->getU16NoEx("mgfractal_small_cave_num_min", small_cave_num_min); - settings->getU16NoEx("mgfractal_small_cave_num_max", small_cave_num_max); - settings->getU16NoEx("mgfractal_large_cave_num_min", large_cave_num_min); - settings->getU16NoEx("mgfractal_large_cave_num_max", large_cave_num_max); + settings->getFloatNoEx("mgfractal_cave_width", cave_width); + settings->getS16NoEx("mgfractal_large_cave_depth", large_cave_depth); + settings->getU16NoEx("mgfractal_small_cave_num_min", small_cave_num_min); + settings->getU16NoEx("mgfractal_small_cave_num_max", small_cave_num_max); + settings->getU16NoEx("mgfractal_large_cave_num_min", large_cave_num_min); + settings->getU16NoEx("mgfractal_large_cave_num_max", large_cave_num_max); settings->getFloatNoEx("mgfractal_large_cave_flooded", large_cave_flooded); - settings->getS16NoEx("mgfractal_dungeon_ymin", dungeon_ymin); - settings->getS16NoEx("mgfractal_dungeon_ymax", dungeon_ymax); - settings->getU16NoEx("mgfractal_fractal", fractal); - settings->getU16NoEx("mgfractal_iterations", iterations); - settings->getV3FNoEx("mgfractal_scale", scale); - settings->getV3FNoEx("mgfractal_offset", offset); - settings->getFloatNoEx("mgfractal_slice_w", slice_w); - settings->getFloatNoEx("mgfractal_julia_x", julia_x); - settings->getFloatNoEx("mgfractal_julia_y", julia_y); - settings->getFloatNoEx("mgfractal_julia_z", julia_z); - settings->getFloatNoEx("mgfractal_julia_w", julia_w); + settings->getS16NoEx("mgfractal_dungeon_ymin", dungeon_ymin); + settings->getS16NoEx("mgfractal_dungeon_ymax", dungeon_ymax); + settings->getU16NoEx("mgfractal_fractal", fractal); + settings->getU16NoEx("mgfractal_iterations", iterations); + settings->getV3FNoEx("mgfractal_scale", scale); + settings->getV3FNoEx("mgfractal_offset", offset); + settings->getFloatNoEx("mgfractal_slice_w", slice_w); + settings->getFloatNoEx("mgfractal_julia_x", julia_x); + settings->getFloatNoEx("mgfractal_julia_y", julia_y); + settings->getFloatNoEx("mgfractal_julia_z", julia_z); + settings->getFloatNoEx("mgfractal_julia_w", julia_w); - settings->getNoiseParams("mgfractal_np_seabed", np_seabed); + settings->getNoiseParams("mgfractal_np_seabed", np_seabed); settings->getNoiseParams("mgfractal_np_filler_depth", np_filler_depth); - settings->getNoiseParams("mgfractal_np_cave1", np_cave1); - settings->getNoiseParams("mgfractal_np_cave2", np_cave2); - settings->getNoiseParams("mgfractal_np_dungeons", np_dungeons); + settings->getNoiseParams("mgfractal_np_cave1", np_cave1); + settings->getNoiseParams("mgfractal_np_cave2", np_cave2); + settings->getNoiseParams("mgfractal_np_dungeons", np_dungeons); } - void MapgenFractalParams::writeParams(Settings *settings) const { settings->setFlagStr("mgfractal_spflags", spflags, flagdesc_mapgen_fractal); - settings->setFloat("mgfractal_cave_width", cave_width); - settings->setS16("mgfractal_large_cave_depth", large_cave_depth); - settings->setU16("mgfractal_small_cave_num_min", small_cave_num_min); - settings->setU16("mgfractal_small_cave_num_max", small_cave_num_max); - settings->setU16("mgfractal_large_cave_num_min", large_cave_num_min); - settings->setU16("mgfractal_large_cave_num_max", large_cave_num_max); + settings->setFloat("mgfractal_cave_width", cave_width); + settings->setS16("mgfractal_large_cave_depth", large_cave_depth); + settings->setU16("mgfractal_small_cave_num_min", small_cave_num_min); + settings->setU16("mgfractal_small_cave_num_max", small_cave_num_max); + settings->setU16("mgfractal_large_cave_num_min", large_cave_num_min); + settings->setU16("mgfractal_large_cave_num_max", large_cave_num_max); settings->setFloat("mgfractal_large_cave_flooded", large_cave_flooded); - settings->setS16("mgfractal_dungeon_ymin", dungeon_ymin); - settings->setS16("mgfractal_dungeon_ymax", dungeon_ymax); - settings->setU16("mgfractal_fractal", fractal); - settings->setU16("mgfractal_iterations", iterations); - settings->setV3F("mgfractal_scale", scale); - settings->setV3F("mgfractal_offset", offset); - settings->setFloat("mgfractal_slice_w", slice_w); - settings->setFloat("mgfractal_julia_x", julia_x); - settings->setFloat("mgfractal_julia_y", julia_y); - settings->setFloat("mgfractal_julia_z", julia_z); - settings->setFloat("mgfractal_julia_w", julia_w); + settings->setS16("mgfractal_dungeon_ymin", dungeon_ymin); + settings->setS16("mgfractal_dungeon_ymax", dungeon_ymax); + settings->setU16("mgfractal_fractal", fractal); + settings->setU16("mgfractal_iterations", iterations); + settings->setV3F("mgfractal_scale", scale); + settings->setV3F("mgfractal_offset", offset); + settings->setFloat("mgfractal_slice_w", slice_w); + settings->setFloat("mgfractal_julia_x", julia_x); + settings->setFloat("mgfractal_julia_y", julia_y); + settings->setFloat("mgfractal_julia_z", julia_z); + settings->setFloat("mgfractal_julia_w", julia_w); - settings->setNoiseParams("mgfractal_np_seabed", np_seabed); + settings->setNoiseParams("mgfractal_np_seabed", np_seabed); settings->setNoiseParams("mgfractal_np_filler_depth", np_filler_depth); - settings->setNoiseParams("mgfractal_np_cave1", np_cave1); - settings->setNoiseParams("mgfractal_np_cave2", np_cave2); - settings->setNoiseParams("mgfractal_np_dungeons", np_dungeons); + settings->setNoiseParams("mgfractal_np_cave1", np_cave1); + settings->setNoiseParams("mgfractal_np_cave2", np_cave2); + settings->setNoiseParams("mgfractal_np_dungeons", np_dungeons); } - void MapgenFractalParams::setDefaultSettings(Settings *settings) { - settings->setDefault("mgfractal_spflags", flagdesc_mapgen_fractal, - MGFRACTAL_TERRAIN); + settings->setDefault( + "mgfractal_spflags", flagdesc_mapgen_fractal, MGFRACTAL_TERRAIN); } - ///////////////////////////////////////////////////////////////// - int MapgenFractal::getSpawnLevelAtPoint(v2s16 p) { bool solid_below = false; // Fractal node is present below to spawn on - u8 air_count = 0; // Consecutive air nodes above a fractal node - s16 search_start = 0; // No terrain search start + u8 air_count = 0; // Consecutive air nodes above a fractal node + s16 search_start = 0; // No terrain search start // If terrain present, don't start search below terrain or water level if (noise_seabed) { @@ -200,23 +187,22 @@ int MapgenFractal::getSpawnLevelAtPoint(v2s16 p) } } - return MAX_MAP_GENERATION_LIMIT; // Unsuitable spawn point + return MAX_MAP_GENERATION_LIMIT; // Unsuitable spawn point } - void MapgenFractal::makeChunk(BlockMakeData *data) { // Pre-conditions assert(data->vmanip); assert(data->nodedef); assert(data->blockpos_requested.X >= data->blockpos_min.X && - data->blockpos_requested.Y >= data->blockpos_min.Y && - data->blockpos_requested.Z >= data->blockpos_min.Z); + data->blockpos_requested.Y >= data->blockpos_min.Y && + data->blockpos_requested.Z >= data->blockpos_min.Z); assert(data->blockpos_requested.X <= data->blockpos_max.X && - data->blockpos_requested.Y <= data->blockpos_max.Y && - data->blockpos_requested.Z <= data->blockpos_max.Z); + data->blockpos_requested.Y <= data->blockpos_max.Y && + data->blockpos_requested.Z <= data->blockpos_max.Z); - //TimeTaker t("makeChunk"); + // TimeTaker t("makeChunk"); this->generating = true; this->vm = data->vmanip; @@ -271,19 +257,18 @@ void MapgenFractal::makeChunk(BlockMakeData *data) // Calculate lighting if (flags & MG_LIGHT) calcLighting(node_min - v3s16(0, 1, 0), node_max + v3s16(0, 1, 0), - full_node_min, full_node_max); + full_node_min, full_node_max); this->generating = false; - //printf("makeChunk: %lums\n", t.stop()); + // printf("makeChunk: %lums\n", t.stop()); } - bool MapgenFractal::getFractalAtPoint(s16 x, s16 y, s16 z) { float cx, cy, cz, cw, ox, oy, oz, ow; - if (julia) { // Julia set + if (julia) { // Julia set cx = julia_x; cy = julia_y; cz = julia_z; @@ -292,7 +277,7 @@ bool MapgenFractal::getFractalAtPoint(s16 x, s16 y, s16 z) oy = (float)y / scale.Y - offset.Y; oz = (float)z / scale.Z - offset.Z; ow = slice_w; - } else { // Mandelbrot set + } else { // Mandelbrot set cx = (float)x / scale.X - offset.X; cy = (float)y / scale.Y - offset.Y; cz = (float)z / scale.Z - offset.Z; @@ -341,7 +326,8 @@ bool MapgenFractal::getFractalAtPoint(s16 x, s16 y, s16 z) nz = -2.0f * ox * oz + cz; break; case 6: // 3D "Christmas Tree" - // Altering the formula here is necessary to avoid division by zero + // Altering the formula here is necessary to avoid division by + // zero if (std::fabs(oz) < 0.000000001f) { nx = ox * ox - oy * oy - oz * oz + cx; ny = 2.0f * oy * ox + cy; @@ -380,7 +366,8 @@ bool MapgenFractal::getFractalAtPoint(s16 x, s16 y, s16 z) case 9: // 4D "Mandelbulb" float rxy = std::sqrt(ox * ox + oy * oy); float rxyz = std::sqrt(ox * ox + oy * oy + oz * oz); - if (std::fabs(ow) < 0.000000001f && std::fabs(oz) < 0.000000001f) { + if (std::fabs(ow) < 0.000000001f && + std::fabs(oz) < 0.000000001f) { nx = (ox * ox - oy * oy) + cx; ny = 2.0f * ox * oy + cy; nz = -2.0f * rxy * oz + cz; @@ -408,7 +395,6 @@ bool MapgenFractal::getFractalAtPoint(s16 x, s16 y, s16 z) return true; } - s16 MapgenFractal::generateTerrain() { MapNode n_air(CONTENT_AIR); @@ -432,12 +418,14 @@ s16 MapgenFractal::generateTerrain() if (noise_seabed) seabed_height = noise_seabed->result[index2d]; - if (((spflags & MGFRACTAL_TERRAIN) && y <= seabed_height) || + if (((spflags & MGFRACTAL_TERRAIN) && + y <= seabed_height) || getFractalAtPoint(x, y, z)) { vm->m_data[vi] = n_stone; if (y > stone_surface_max_y) stone_surface_max_y = y; - } else if ((spflags & MGFRACTAL_TERRAIN) && y <= water_level) { + } else if ((spflags & MGFRACTAL_TERRAIN) && + y <= water_level) { vm->m_data[vi] = n_water; } else { vm->m_data[vi] = n_air; diff --git a/src/mapgen/mapgen_fractal.h b/src/mapgen/mapgen_fractal.h index 23af925bc..a1d8b890c 100644 --- a/src/mapgen/mapgen_fractal.h +++ b/src/mapgen/mapgen_fractal.h @@ -26,13 +26,12 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "mapgen.h" ///////////// Mapgen Fractal flags -#define MGFRACTAL_TERRAIN 0x01 +#define MGFRACTAL_TERRAIN 0x01 class BiomeManager; extern FlagDesc flagdesc_mapgen_fractal[]; - struct MapgenFractalParams : public MapgenParams { float cave_width = 0.09f; @@ -68,7 +67,6 @@ struct MapgenFractalParams : public MapgenParams void setDefaultSettings(Settings *settings); }; - class MapgenFractal : public MapgenBasic { public: diff --git a/src/mapgen/mapgen_singlenode.cpp b/src/mapgen/mapgen_singlenode.cpp index cade9e7a8..6724e5fe7 100644 --- a/src/mapgen/mapgen_singlenode.cpp +++ b/src/mapgen/mapgen_singlenode.cpp @@ -28,9 +28,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "voxelalgorithms.h" #include "emerge.h" - -MapgenSinglenode::MapgenSinglenode(MapgenParams *params, EmergeParams *emerge) - : Mapgen(MAPGEN_SINGLENODE, params, emerge) +MapgenSinglenode::MapgenSinglenode(MapgenParams *params, EmergeParams *emerge) : + Mapgen(MAPGEN_SINGLENODE, params, emerge) { const NodeDefManager *ndef = emerge->ndef; @@ -42,7 +41,6 @@ MapgenSinglenode::MapgenSinglenode(MapgenParams *params, EmergeParams *emerge) set_light = (ndef->get(n_node).sunlight_propagates) ? LIGHT_SUN : 0x00; } - //////////////////////// Map generator void MapgenSinglenode::makeChunk(BlockMakeData *data) @@ -51,14 +49,14 @@ void MapgenSinglenode::makeChunk(BlockMakeData *data) assert(data->vmanip); assert(data->nodedef); assert(data->blockpos_requested.X >= data->blockpos_min.X && - data->blockpos_requested.Y >= data->blockpos_min.Y && - data->blockpos_requested.Z >= data->blockpos_min.Z); + data->blockpos_requested.Y >= data->blockpos_min.Y && + data->blockpos_requested.Z >= data->blockpos_min.Z); assert(data->blockpos_requested.X <= data->blockpos_max.X && - data->blockpos_requested.Y <= data->blockpos_max.Y && - data->blockpos_requested.Z <= data->blockpos_max.Z); + data->blockpos_requested.Y <= data->blockpos_max.Y && + data->blockpos_requested.Z <= data->blockpos_max.Z); this->generating = true; - this->vm = data->vmanip; + this->vm = data->vmanip; this->ndef = data->nodedef; v3s16 blockpos_min = data->blockpos_min; @@ -73,14 +71,14 @@ void MapgenSinglenode::makeChunk(BlockMakeData *data) MapNode n_node(c_node); for (s16 z = node_min.Z; z <= node_max.Z; z++) - for (s16 y = node_min.Y; y <= node_max.Y; y++) { - u32 i = vm->m_area.index(node_min.X, y, z); - for (s16 x = node_min.X; x <= node_max.X; x++) { - if (vm->m_data[i].getContent() == CONTENT_IGNORE) - vm->m_data[i] = n_node; - i++; + for (s16 y = node_min.Y; y <= node_max.Y; y++) { + u32 i = vm->m_area.index(node_min.X, y, z); + for (s16 x = node_min.X; x <= node_max.X; x++) { + if (vm->m_data[i].getContent() == CONTENT_IGNORE) + vm->m_data[i] = n_node; + i++; + } } - } // Add top and bottom side of water to transforming_liquid queue updateLiquid(&data->transforming_liquid, node_min, node_max); @@ -92,7 +90,6 @@ void MapgenSinglenode::makeChunk(BlockMakeData *data) this->generating = false; } - int MapgenSinglenode::getSpawnLevelAtPoint(v2s16 p) { return 0; diff --git a/src/mapgen/mapgen_v5.cpp b/src/mapgen/mapgen_v5.cpp index 124667e5d..950e23f86 100644 --- a/src/mapgen/mapgen_v5.cpp +++ b/src/mapgen/mapgen_v5.cpp @@ -18,7 +18,6 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ - #include "mapgen.h" #include "voxel.h" #include "noise.h" @@ -37,46 +36,40 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "mg_decoration.h" #include "mapgen_v5.h" +FlagDesc flagdesc_mapgen_v5[] = {{"caverns", MGV5_CAVERNS}, {NULL, 0}}; -FlagDesc flagdesc_mapgen_v5[] = { - {"caverns", MGV5_CAVERNS}, - {NULL, 0} -}; - - -MapgenV5::MapgenV5(MapgenV5Params *params, EmergeParams *emerge) - : MapgenBasic(MAPGEN_V5, params, emerge) +MapgenV5::MapgenV5(MapgenV5Params *params, EmergeParams *emerge) : + MapgenBasic(MAPGEN_V5, params, emerge) { - spflags = params->spflags; - cave_width = params->cave_width; - large_cave_depth = params->large_cave_depth; + spflags = params->spflags; + cave_width = params->cave_width; + large_cave_depth = params->large_cave_depth; small_cave_num_min = params->small_cave_num_min; small_cave_num_max = params->small_cave_num_max; large_cave_num_min = params->large_cave_num_min; large_cave_num_max = params->large_cave_num_max; large_cave_flooded = params->large_cave_flooded; - cavern_limit = params->cavern_limit; - cavern_taper = params->cavern_taper; - cavern_threshold = params->cavern_threshold; - dungeon_ymin = params->dungeon_ymin; - dungeon_ymax = params->dungeon_ymax; + cavern_limit = params->cavern_limit; + cavern_taper = params->cavern_taper; + cavern_threshold = params->cavern_threshold; + dungeon_ymin = params->dungeon_ymin; + dungeon_ymax = params->dungeon_ymax; // Terrain noise noise_filler_depth = new Noise(¶ms->np_filler_depth, seed, csize.X, csize.Z); - noise_factor = new Noise(¶ms->np_factor, seed, csize.X, csize.Z); - noise_height = new Noise(¶ms->np_height, seed, csize.X, csize.Z); + noise_factor = new Noise(¶ms->np_factor, seed, csize.X, csize.Z); + noise_height = new Noise(¶ms->np_height, seed, csize.X, csize.Z); // 3D terrain noise // 1-up 1-down overgeneration noise_ground = new Noise(¶ms->np_ground, seed, csize.X, csize.Y + 2, csize.Z); // 1 down overgeneration - MapgenBasic::np_cave1 = params->np_cave1; - MapgenBasic::np_cave2 = params->np_cave2; - MapgenBasic::np_cavern = params->np_cavern; + MapgenBasic::np_cave1 = params->np_cave1; + MapgenBasic::np_cave2 = params->np_cave2; + MapgenBasic::np_cavern = params->np_cavern; MapgenBasic::np_dungeons = params->np_dungeons; } - MapgenV5::~MapgenV5() { delete noise_filler_depth; @@ -85,83 +78,77 @@ MapgenV5::~MapgenV5() delete noise_ground; } - -MapgenV5Params::MapgenV5Params(): - np_filler_depth (0, 1, v3f(150, 150, 150), 261, 4, 0.7, 2.0), - np_factor (0, 1, v3f(250, 250, 250), 920381, 3, 0.45, 2.0), - np_height (0, 10, v3f(250, 250, 250), 84174, 4, 0.5, 2.0), - np_ground (0, 40, v3f(80, 80, 80), 983240, 4, 0.55, 2.0, NOISE_FLAG_EASED), - np_cave1 (0, 12, v3f(61, 61, 61), 52534, 3, 0.5, 2.0), - np_cave2 (0, 12, v3f(67, 67, 67), 10325, 3, 0.5, 2.0), - np_cavern (0, 1, v3f(384, 128, 384), 723, 5, 0.63, 2.0), - np_dungeons (0.9, 0.5, v3f(500, 500, 500), 0, 2, 0.8, 2.0) +MapgenV5Params::MapgenV5Params() : + np_filler_depth(0, 1, v3f(150, 150, 150), 261, 4, 0.7, 2.0), + np_factor(0, 1, v3f(250, 250, 250), 920381, 3, 0.45, 2.0), + np_height(0, 10, v3f(250, 250, 250), 84174, 4, 0.5, 2.0), + np_ground(0, 40, v3f(80, 80, 80), 983240, 4, 0.55, 2.0, NOISE_FLAG_EASED), + np_cave1(0, 12, v3f(61, 61, 61), 52534, 3, 0.5, 2.0), + np_cave2(0, 12, v3f(67, 67, 67), 10325, 3, 0.5, 2.0), + np_cavern(0, 1, v3f(384, 128, 384), 723, 5, 0.63, 2.0), + np_dungeons(0.9, 0.5, v3f(500, 500, 500), 0, 2, 0.8, 2.0) { } - void MapgenV5Params::readParams(const Settings *settings) { settings->getFlagStrNoEx("mgv5_spflags", spflags, flagdesc_mapgen_v5); - settings->getFloatNoEx("mgv5_cave_width", cave_width); - settings->getS16NoEx("mgv5_large_cave_depth", large_cave_depth); - settings->getU16NoEx("mgv5_small_cave_num_min", small_cave_num_min); - settings->getU16NoEx("mgv5_small_cave_num_max", small_cave_num_max); - settings->getU16NoEx("mgv5_large_cave_num_min", large_cave_num_min); - settings->getU16NoEx("mgv5_large_cave_num_max", large_cave_num_max); + settings->getFloatNoEx("mgv5_cave_width", cave_width); + settings->getS16NoEx("mgv5_large_cave_depth", large_cave_depth); + settings->getU16NoEx("mgv5_small_cave_num_min", small_cave_num_min); + settings->getU16NoEx("mgv5_small_cave_num_max", small_cave_num_max); + settings->getU16NoEx("mgv5_large_cave_num_min", large_cave_num_min); + settings->getU16NoEx("mgv5_large_cave_num_max", large_cave_num_max); settings->getFloatNoEx("mgv5_large_cave_flooded", large_cave_flooded); - settings->getS16NoEx("mgv5_cavern_limit", cavern_limit); - settings->getS16NoEx("mgv5_cavern_taper", cavern_taper); - settings->getFloatNoEx("mgv5_cavern_threshold", cavern_threshold); - settings->getS16NoEx("mgv5_dungeon_ymin", dungeon_ymin); - settings->getS16NoEx("mgv5_dungeon_ymax", dungeon_ymax); + settings->getS16NoEx("mgv5_cavern_limit", cavern_limit); + settings->getS16NoEx("mgv5_cavern_taper", cavern_taper); + settings->getFloatNoEx("mgv5_cavern_threshold", cavern_threshold); + settings->getS16NoEx("mgv5_dungeon_ymin", dungeon_ymin); + settings->getS16NoEx("mgv5_dungeon_ymax", dungeon_ymax); settings->getNoiseParams("mgv5_np_filler_depth", np_filler_depth); - settings->getNoiseParams("mgv5_np_factor", np_factor); - settings->getNoiseParams("mgv5_np_height", np_height); - settings->getNoiseParams("mgv5_np_ground", np_ground); - settings->getNoiseParams("mgv5_np_cave1", np_cave1); - settings->getNoiseParams("mgv5_np_cave2", np_cave2); - settings->getNoiseParams("mgv5_np_cavern", np_cavern); - settings->getNoiseParams("mgv5_np_dungeons", np_dungeons); + settings->getNoiseParams("mgv5_np_factor", np_factor); + settings->getNoiseParams("mgv5_np_height", np_height); + settings->getNoiseParams("mgv5_np_ground", np_ground); + settings->getNoiseParams("mgv5_np_cave1", np_cave1); + settings->getNoiseParams("mgv5_np_cave2", np_cave2); + settings->getNoiseParams("mgv5_np_cavern", np_cavern); + settings->getNoiseParams("mgv5_np_dungeons", np_dungeons); } - void MapgenV5Params::writeParams(Settings *settings) const { settings->setFlagStr("mgv5_spflags", spflags, flagdesc_mapgen_v5); - settings->setFloat("mgv5_cave_width", cave_width); - settings->setS16("mgv5_large_cave_depth", large_cave_depth); - settings->setU16("mgv5_small_cave_num_min", small_cave_num_min); - settings->setU16("mgv5_small_cave_num_max", small_cave_num_max); - settings->setU16("mgv5_large_cave_num_min", large_cave_num_min); - settings->setU16("mgv5_large_cave_num_max", large_cave_num_max); + settings->setFloat("mgv5_cave_width", cave_width); + settings->setS16("mgv5_large_cave_depth", large_cave_depth); + settings->setU16("mgv5_small_cave_num_min", small_cave_num_min); + settings->setU16("mgv5_small_cave_num_max", small_cave_num_max); + settings->setU16("mgv5_large_cave_num_min", large_cave_num_min); + settings->setU16("mgv5_large_cave_num_max", large_cave_num_max); settings->setFloat("mgv5_large_cave_flooded", large_cave_flooded); - settings->setS16("mgv5_cavern_limit", cavern_limit); - settings->setS16("mgv5_cavern_taper", cavern_taper); - settings->setFloat("mgv5_cavern_threshold", cavern_threshold); - settings->setS16("mgv5_dungeon_ymin", dungeon_ymin); - settings->setS16("mgv5_dungeon_ymax", dungeon_ymax); + settings->setS16("mgv5_cavern_limit", cavern_limit); + settings->setS16("mgv5_cavern_taper", cavern_taper); + settings->setFloat("mgv5_cavern_threshold", cavern_threshold); + settings->setS16("mgv5_dungeon_ymin", dungeon_ymin); + settings->setS16("mgv5_dungeon_ymax", dungeon_ymax); settings->setNoiseParams("mgv5_np_filler_depth", np_filler_depth); - settings->setNoiseParams("mgv5_np_factor", np_factor); - settings->setNoiseParams("mgv5_np_height", np_height); - settings->setNoiseParams("mgv5_np_ground", np_ground); - settings->setNoiseParams("mgv5_np_cave1", np_cave1); - settings->setNoiseParams("mgv5_np_cave2", np_cave2); - settings->setNoiseParams("mgv5_np_cavern", np_cavern); - settings->setNoiseParams("mgv5_np_dungeons", np_dungeons); + settings->setNoiseParams("mgv5_np_factor", np_factor); + settings->setNoiseParams("mgv5_np_height", np_height); + settings->setNoiseParams("mgv5_np_ground", np_ground); + settings->setNoiseParams("mgv5_np_cave1", np_cave1); + settings->setNoiseParams("mgv5_np_cave2", np_cave2); + settings->setNoiseParams("mgv5_np_cavern", np_cavern); + settings->setNoiseParams("mgv5_np_dungeons", np_dungeons); } - void MapgenV5Params::setDefaultSettings(Settings *settings) { settings->setDefault("mgv5_spflags", flagdesc_mapgen_v5, MGV5_CAVERNS); } - ///////////////////////////////////////////////////////////////// - int MapgenV5::getSpawnLevelAtPoint(v2s16 p) { @@ -183,9 +170,9 @@ int MapgenV5::getSpawnLevelAtPoint(v2s16 p) for (s16 y = max_spawn_y + 128; y >= water_level; y--) { float n_ground = NoisePerlin3D(&noise_ground->np, p.X, y, p.Y, seed); - if (n_ground * f > y - h) { // If solid + if (n_ground * f > y - h) { // If solid if (y < water_level || y > max_spawn_y) - return MAX_MAP_GENERATION_LIMIT; // Unsuitable spawn point + return MAX_MAP_GENERATION_LIMIT; // Unsuitable spawn point // y + 2 because y is surface and due to biome 'dust' nodes. return y + 2; @@ -195,23 +182,22 @@ int MapgenV5::getSpawnLevelAtPoint(v2s16 p) return MAX_MAP_GENERATION_LIMIT; } - void MapgenV5::makeChunk(BlockMakeData *data) { // Pre-conditions assert(data->vmanip); assert(data->nodedef); assert(data->blockpos_requested.X >= data->blockpos_min.X && - data->blockpos_requested.Y >= data->blockpos_min.Y && - data->blockpos_requested.Z >= data->blockpos_min.Z); + data->blockpos_requested.Y >= data->blockpos_min.Y && + data->blockpos_requested.Z >= data->blockpos_min.Z); assert(data->blockpos_requested.X <= data->blockpos_max.X && - data->blockpos_requested.Y <= data->blockpos_max.Y && - data->blockpos_requested.Z <= data->blockpos_max.Z); + data->blockpos_requested.Y <= data->blockpos_max.Y && + data->blockpos_requested.Z <= data->blockpos_max.Z); this->generating = true; - this->vm = data->vmanip; + this->vm = data->vmanip; this->ndef = data->nodedef; - //TimeTaker t("makeChunk"); + // TimeTaker t("makeChunk"); v3s16 blockpos_min = data->blockpos_min; v3s16 blockpos_max = data->blockpos_max; @@ -250,8 +236,8 @@ void MapgenV5::makeChunk(BlockMakeData *data) // Disable large randomwalk caves in this mapchunk by setting // 'large cave depth' to world base. Avoids excessive liquid in // large caverns and floating blobs of overgenerated liquid. - generateCavesRandomWalk(stone_surface_max_y, - -MAX_MAP_GENERATION_LIMIT); + generateCavesRandomWalk( + stone_surface_max_y, -MAX_MAP_GENERATION_LIMIT); else generateCavesRandomWalk(stone_surface_max_y, large_cave_depth); } @@ -271,7 +257,7 @@ void MapgenV5::makeChunk(BlockMakeData *data) if (flags & MG_BIOMES) dustTopNodes(); - //printf("makeChunk: %dms\n", t.stop()); + // printf("makeChunk: %dms\n", t.stop()); // Add top and bottom side of water to transforming_liquid queue updateLiquid(&data->transforming_liquid, full_node_min, full_node_max); @@ -279,13 +265,12 @@ void MapgenV5::makeChunk(BlockMakeData *data) // Calculate lighting if (flags & MG_LIGHT) { calcLighting(node_min - v3s16(0, 1, 0), node_max + v3s16(0, 1, 0), - full_node_min, full_node_max); + full_node_min, full_node_max); } this->generating = false; } - int MapgenV5::generateBaseTerrain() { u32 index = 0; @@ -296,10 +281,11 @@ int MapgenV5::generateBaseTerrain() noise_height->perlinMap2D(node_min.X, node_min.Z); noise_ground->perlinMap3D(node_min.X, node_min.Y - 1, node_min.Z); - for (s16 z=node_min.Z; z<=node_max.Z; z++) { - for (s16 y=node_min.Y - 1; y<=node_max.Y + 1; y++) { + for (s16 z = node_min.Z; z <= node_max.Z; z++) { + for (s16 y = node_min.Y - 1; y <= node_max.Y + 1; y++) { u32 vi = vm->m_area.index(node_min.X, y, z); - for (s16 x=node_min.X; x<=node_max.X; x++, vi++, index++, index2d++) { + for (s16 x = node_min.X; x <= node_max.X; + x++, vi++, index++, index2d++) { if (vm->m_data[vi].getContent() != CONTENT_IGNORE) continue; diff --git a/src/mapgen/mapgen_v6.cpp b/src/mapgen/mapgen_v6.cpp index e9692246c..8a0f01409 100644 --- a/src/mapgen/mapgen_v6.cpp +++ b/src/mapgen/mapgen_v6.cpp @@ -19,7 +19,6 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ - #include #include "mapgen.h" #include "voxel.h" @@ -39,70 +38,64 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "mg_decoration.h" #include "mapgen_v6.h" - -FlagDesc flagdesc_mapgen_v6[] = { - {"jungles", MGV6_JUNGLES}, - {"biomeblend", MGV6_BIOMEBLEND}, - {"mudflow", MGV6_MUDFLOW}, - {"snowbiomes", MGV6_SNOWBIOMES}, - {"flat", MGV6_FLAT}, - {"trees", MGV6_TREES}, - {NULL, 0} -}; - +FlagDesc flagdesc_mapgen_v6[] = {{"jungles", MGV6_JUNGLES}, + {"biomeblend", MGV6_BIOMEBLEND}, {"mudflow", MGV6_MUDFLOW}, + {"snowbiomes", MGV6_SNOWBIOMES}, {"flat", MGV6_FLAT}, + {"trees", MGV6_TREES}, {NULL, 0}}; ///////////////////////////////////////////////////////////////////////////// - -MapgenV6::MapgenV6(MapgenV6Params *params, EmergeParams *emerge) - : Mapgen(MAPGEN_V6, params, emerge) +MapgenV6::MapgenV6(MapgenV6Params *params, EmergeParams *emerge) : + Mapgen(MAPGEN_V6, params, emerge) { m_emerge = emerge; ystride = csize.X; heightmap = new s16[csize.X * csize.Z]; - spflags = params->spflags; - freq_desert = params->freq_desert; - freq_beach = params->freq_beach; + spflags = params->spflags; + freq_desert = params->freq_desert; + freq_beach = params->freq_beach; dungeon_ymin = params->dungeon_ymin; dungeon_ymax = params->dungeon_ymax; - np_cave = ¶ms->np_cave; - np_humidity = ¶ms->np_humidity; - np_trees = ¶ms->np_trees; + np_cave = ¶ms->np_cave; + np_humidity = ¶ms->np_humidity; + np_trees = ¶ms->np_trees; np_apple_trees = ¶ms->np_apple_trees; np_dungeons = NoiseParams(0.9, 0.5, v3f(500.0, 500.0, 500.0), 0, 2, 0.8, 2.0); //// Create noise objects - noise_terrain_base = new Noise(¶ms->np_terrain_base, seed, csize.X, csize.Y); - noise_terrain_higher = new Noise(¶ms->np_terrain_higher, seed, csize.X, csize.Y); - noise_steepness = new Noise(¶ms->np_steepness, seed, csize.X, csize.Y); - noise_height_select = new Noise(¶ms->np_height_select, seed, csize.X, csize.Y); - noise_mud = new Noise(¶ms->np_mud, seed, csize.X, csize.Y); - noise_beach = new Noise(¶ms->np_beach, seed, csize.X, csize.Y); - noise_biome = new Noise(¶ms->np_biome, seed, - csize.X + 2 * MAP_BLOCKSIZE, csize.Y + 2 * MAP_BLOCKSIZE); - noise_humidity = new Noise(¶ms->np_humidity, seed, + noise_terrain_base = new Noise(¶ms->np_terrain_base, seed, csize.X, csize.Y); + noise_terrain_higher = + new Noise(¶ms->np_terrain_higher, seed, csize.X, csize.Y); + noise_steepness = new Noise(¶ms->np_steepness, seed, csize.X, csize.Y); + noise_height_select = + new Noise(¶ms->np_height_select, seed, csize.X, csize.Y); + noise_mud = new Noise(¶ms->np_mud, seed, csize.X, csize.Y); + noise_beach = new Noise(¶ms->np_beach, seed, csize.X, csize.Y); + noise_biome = new Noise(¶ms->np_biome, seed, csize.X + 2 * MAP_BLOCKSIZE, + csize.Y + 2 * MAP_BLOCKSIZE); + noise_humidity = new Noise(¶ms->np_humidity, seed, csize.X + 2 * MAP_BLOCKSIZE, csize.Y + 2 * MAP_BLOCKSIZE); //// Resolve nodes to be used const NodeDefManager *ndef = emerge->ndef; - c_stone = ndef->getId("mapgen_stone"); - c_dirt = ndef->getId("mapgen_dirt"); + c_stone = ndef->getId("mapgen_stone"); + c_dirt = ndef->getId("mapgen_dirt"); c_dirt_with_grass = ndef->getId("mapgen_dirt_with_grass"); - c_sand = ndef->getId("mapgen_sand"); - c_water_source = ndef->getId("mapgen_water_source"); - c_lava_source = ndef->getId("mapgen_lava_source"); - c_gravel = ndef->getId("mapgen_gravel"); - c_desert_stone = ndef->getId("mapgen_desert_stone"); - c_desert_sand = ndef->getId("mapgen_desert_sand"); - c_dirt_with_snow = ndef->getId("mapgen_dirt_with_snow"); - c_snow = ndef->getId("mapgen_snow"); - c_snowblock = ndef->getId("mapgen_snowblock"); - c_ice = ndef->getId("mapgen_ice"); + c_sand = ndef->getId("mapgen_sand"); + c_water_source = ndef->getId("mapgen_water_source"); + c_lava_source = ndef->getId("mapgen_lava_source"); + c_gravel = ndef->getId("mapgen_gravel"); + c_desert_stone = ndef->getId("mapgen_desert_stone"); + c_desert_sand = ndef->getId("mapgen_desert_sand"); + c_dirt_with_snow = ndef->getId("mapgen_dirt_with_snow"); + c_snow = ndef->getId("mapgen_snow"); + c_snowblock = ndef->getId("mapgen_snowblock"); + c_ice = ndef->getId("mapgen_ice"); if (c_gravel == CONTENT_IGNORE) c_gravel = c_stone; @@ -119,9 +112,9 @@ MapgenV6::MapgenV6(MapgenV6Params *params, EmergeParams *emerge) if (c_ice == CONTENT_IGNORE) c_ice = c_water_source; - c_cobble = ndef->getId("mapgen_cobble"); - c_mossycobble = ndef->getId("mapgen_mossycobble"); - c_stair_cobble = ndef->getId("mapgen_stair_cobble"); + c_cobble = ndef->getId("mapgen_cobble"); + c_mossycobble = ndef->getId("mapgen_mossycobble"); + c_stair_cobble = ndef->getId("mapgen_stair_cobble"); c_stair_desert_stone = ndef->getId("mapgen_stair_desert_stone"); if (c_mossycobble == CONTENT_IGNORE) @@ -132,22 +125,29 @@ MapgenV6::MapgenV6(MapgenV6Params *params, EmergeParams *emerge) c_stair_desert_stone = c_desert_stone; if (c_stone == CONTENT_IGNORE) - errorstream << "Mapgen v6: Mapgen alias 'mapgen_stone' is invalid!" << std::endl; + errorstream << "Mapgen v6: Mapgen alias 'mapgen_stone' is invalid!" + << std::endl; if (c_dirt == CONTENT_IGNORE) - errorstream << "Mapgen v6: Mapgen alias 'mapgen_dirt' is invalid!" << std::endl; + errorstream << "Mapgen v6: Mapgen alias 'mapgen_dirt' is invalid!" + << std::endl; if (c_dirt_with_grass == CONTENT_IGNORE) - errorstream << "Mapgen v6: Mapgen alias 'mapgen_dirt_with_grass' is invalid!" << std::endl; + errorstream << "Mapgen v6: Mapgen alias 'mapgen_dirt_with_grass' is " + "invalid!" + << std::endl; if (c_sand == CONTENT_IGNORE) - errorstream << "Mapgen v6: Mapgen alias 'mapgen_sand' is invalid!" << std::endl; + errorstream << "Mapgen v6: Mapgen alias 'mapgen_sand' is invalid!" + << std::endl; if (c_water_source == CONTENT_IGNORE) - errorstream << "Mapgen v6: Mapgen alias 'mapgen_water_source' is invalid!" << std::endl; + errorstream << "Mapgen v6: Mapgen alias 'mapgen_water_source' is invalid!" + << std::endl; if (c_lava_source == CONTENT_IGNORE) - errorstream << "Mapgen v6: Mapgen alias 'mapgen_lava_source' is invalid!" << std::endl; + errorstream << "Mapgen v6: Mapgen alias 'mapgen_lava_source' is invalid!" + << std::endl; if (c_cobble == CONTENT_IGNORE) - errorstream << "Mapgen v6: Mapgen alias 'mapgen_cobble' is invalid!" << std::endl; + errorstream << "Mapgen v6: Mapgen alias 'mapgen_cobble' is invalid!" + << std::endl; } - MapgenV6::~MapgenV6() { delete noise_terrain_base; @@ -164,77 +164,72 @@ MapgenV6::~MapgenV6() delete m_emerge; // our responsibility } - -MapgenV6Params::MapgenV6Params(): - np_terrain_base (-4, 20.0, v3f(250.0, 250.0, 250.0), 82341, 5, 0.6, 2.0), - np_terrain_higher (20, 16.0, v3f(500.0, 500.0, 500.0), 85039, 5, 0.6, 2.0), - np_steepness (0.85, 0.5, v3f(125.0, 125.0, 125.0), -932, 5, 0.7, 2.0), - np_height_select (0, 1.0, v3f(250.0, 250.0, 250.0), 4213, 5, 0.69, 2.0), - np_mud (4, 2.0, v3f(200.0, 200.0, 200.0), 91013, 3, 0.55, 2.0), - np_beach (0, 1.0, v3f(250.0, 250.0, 250.0), 59420, 3, 0.50, 2.0), - np_biome (0, 1.0, v3f(500.0, 500.0, 500.0), 9130, 3, 0.50, 2.0), - np_cave (6, 6.0, v3f(250.0, 250.0, 250.0), 34329, 3, 0.50, 2.0), - np_humidity (0.5, 0.5, v3f(500.0, 500.0, 500.0), 72384, 3, 0.50, 2.0), - np_trees (0, 1.0, v3f(125.0, 125.0, 125.0), 2, 4, 0.66, 2.0), - np_apple_trees (0, 1.0, v3f(100.0, 100.0, 100.0), 342902, 3, 0.45, 2.0) +MapgenV6Params::MapgenV6Params() : + np_terrain_base(-4, 20.0, v3f(250.0, 250.0, 250.0), 82341, 5, 0.6, 2.0), + np_terrain_higher(20, 16.0, v3f(500.0, 500.0, 500.0), 85039, 5, 0.6, 2.0), + np_steepness(0.85, 0.5, v3f(125.0, 125.0, 125.0), -932, 5, 0.7, 2.0), + np_height_select(0, 1.0, v3f(250.0, 250.0, 250.0), 4213, 5, 0.69, 2.0), + np_mud(4, 2.0, v3f(200.0, 200.0, 200.0), 91013, 3, 0.55, 2.0), + np_beach(0, 1.0, v3f(250.0, 250.0, 250.0), 59420, 3, 0.50, 2.0), + np_biome(0, 1.0, v3f(500.0, 500.0, 500.0), 9130, 3, 0.50, 2.0), + np_cave(6, 6.0, v3f(250.0, 250.0, 250.0), 34329, 3, 0.50, 2.0), + np_humidity(0.5, 0.5, v3f(500.0, 500.0, 500.0), 72384, 3, 0.50, 2.0), + np_trees(0, 1.0, v3f(125.0, 125.0, 125.0), 2, 4, 0.66, 2.0), + np_apple_trees(0, 1.0, v3f(100.0, 100.0, 100.0), 342902, 3, 0.45, 2.0) { } - void MapgenV6Params::readParams(const Settings *settings) { settings->getFlagStrNoEx("mgv6_spflags", spflags, flagdesc_mapgen_v6); settings->getFloatNoEx("mgv6_freq_desert", freq_desert); - settings->getFloatNoEx("mgv6_freq_beach", freq_beach); - settings->getS16NoEx("mgv6_dungeon_ymin", dungeon_ymin); - settings->getS16NoEx("mgv6_dungeon_ymax", dungeon_ymax); + settings->getFloatNoEx("mgv6_freq_beach", freq_beach); + settings->getS16NoEx("mgv6_dungeon_ymin", dungeon_ymin); + settings->getS16NoEx("mgv6_dungeon_ymax", dungeon_ymax); - settings->getNoiseParams("mgv6_np_terrain_base", np_terrain_base); + settings->getNoiseParams("mgv6_np_terrain_base", np_terrain_base); settings->getNoiseParams("mgv6_np_terrain_higher", np_terrain_higher); - settings->getNoiseParams("mgv6_np_steepness", np_steepness); - settings->getNoiseParams("mgv6_np_height_select", np_height_select); - settings->getNoiseParams("mgv6_np_mud", np_mud); - settings->getNoiseParams("mgv6_np_beach", np_beach); - settings->getNoiseParams("mgv6_np_biome", np_biome); - settings->getNoiseParams("mgv6_np_cave", np_cave); - settings->getNoiseParams("mgv6_np_humidity", np_humidity); - settings->getNoiseParams("mgv6_np_trees", np_trees); - settings->getNoiseParams("mgv6_np_apple_trees", np_apple_trees); + settings->getNoiseParams("mgv6_np_steepness", np_steepness); + settings->getNoiseParams("mgv6_np_height_select", np_height_select); + settings->getNoiseParams("mgv6_np_mud", np_mud); + settings->getNoiseParams("mgv6_np_beach", np_beach); + settings->getNoiseParams("mgv6_np_biome", np_biome); + settings->getNoiseParams("mgv6_np_cave", np_cave); + settings->getNoiseParams("mgv6_np_humidity", np_humidity); + settings->getNoiseParams("mgv6_np_trees", np_trees); + settings->getNoiseParams("mgv6_np_apple_trees", np_apple_trees); } - void MapgenV6Params::writeParams(Settings *settings) const { settings->setFlagStr("mgv6_spflags", spflags, flagdesc_mapgen_v6); settings->setFloat("mgv6_freq_desert", freq_desert); - settings->setFloat("mgv6_freq_beach", freq_beach); - settings->setS16("mgv6_dungeon_ymin", dungeon_ymin); - settings->setS16("mgv6_dungeon_ymax", dungeon_ymax); + settings->setFloat("mgv6_freq_beach", freq_beach); + settings->setS16("mgv6_dungeon_ymin", dungeon_ymin); + settings->setS16("mgv6_dungeon_ymax", dungeon_ymax); - settings->setNoiseParams("mgv6_np_terrain_base", np_terrain_base); + settings->setNoiseParams("mgv6_np_terrain_base", np_terrain_base); settings->setNoiseParams("mgv6_np_terrain_higher", np_terrain_higher); - settings->setNoiseParams("mgv6_np_steepness", np_steepness); - settings->setNoiseParams("mgv6_np_height_select", np_height_select); - settings->setNoiseParams("mgv6_np_mud", np_mud); - settings->setNoiseParams("mgv6_np_beach", np_beach); - settings->setNoiseParams("mgv6_np_biome", np_biome); - settings->setNoiseParams("mgv6_np_cave", np_cave); - settings->setNoiseParams("mgv6_np_humidity", np_humidity); - settings->setNoiseParams("mgv6_np_trees", np_trees); - settings->setNoiseParams("mgv6_np_apple_trees", np_apple_trees); + settings->setNoiseParams("mgv6_np_steepness", np_steepness); + settings->setNoiseParams("mgv6_np_height_select", np_height_select); + settings->setNoiseParams("mgv6_np_mud", np_mud); + settings->setNoiseParams("mgv6_np_beach", np_beach); + settings->setNoiseParams("mgv6_np_biome", np_biome); + settings->setNoiseParams("mgv6_np_cave", np_cave); + settings->setNoiseParams("mgv6_np_humidity", np_humidity); + settings->setNoiseParams("mgv6_np_trees", np_trees); + settings->setNoiseParams("mgv6_np_apple_trees", np_apple_trees); } - void MapgenV6Params::setDefaultSettings(Settings *settings) { - settings->setDefault("mgv6_spflags", flagdesc_mapgen_v6, MGV6_JUNGLES | - MGV6_SNOWBIOMES | MGV6_TREES | MGV6_BIOMEBLEND | MGV6_MUDFLOW); + settings->setDefault("mgv6_spflags", flagdesc_mapgen_v6, + MGV6_JUNGLES | MGV6_SNOWBIOMES | MGV6_TREES | MGV6_BIOMEBLEND | + MGV6_MUDFLOW); } - //////////////////////// Some helper functions for the map generator - // Returns Y one under area minimum if not found s16 MapgenV6::find_stone_level(v2s16 p2d) { @@ -254,7 +249,6 @@ s16 MapgenV6::find_stone_level(v2s16 p2d) return (y >= y_nodes_min) ? y : y_nodes_min - 1; } - // Required by mapgen.h bool MapgenV6::block_is_underground(u64 seed, v3s16 blockpos) { @@ -263,23 +257,22 @@ bool MapgenV6::block_is_underground(u64 seed, v3s16 blockpos) // Nah, this is just a heuristic, just return something s16 minimum_groundlevel = water_level; - if(blockpos.Y * MAP_BLOCKSIZE + MAP_BLOCKSIZE <= minimum_groundlevel) + if (blockpos.Y * MAP_BLOCKSIZE + MAP_BLOCKSIZE <= minimum_groundlevel) return true; return false; } - //////////////////////// Base terrain height functions float MapgenV6::baseTerrainLevel(float terrain_base, float terrain_higher, - float steepness, float height_select) + float steepness, float height_select) { - float base = 1 + terrain_base; + float base = 1 + terrain_base; float higher = 1 + terrain_higher; // Limit higher ground level to at least base - if(higher < base) + if (higher < base) higher = base; // Steepness factor of cliffs @@ -299,71 +292,61 @@ float MapgenV6::baseTerrainLevel(float terrain_base, float terrain_higher, return base * (1.0 - a) + higher * a; } - float MapgenV6::baseTerrainLevelFromNoise(v2s16 p) { if (spflags & MGV6_FLAT) return water_level; - float terrain_base = NoisePerlin2D_PO(&noise_terrain_base->np, - p.X, 0.5, p.Y, 0.5, seed); - float terrain_higher = NoisePerlin2D_PO(&noise_terrain_higher->np, - p.X, 0.5, p.Y, 0.5, seed); - float steepness = NoisePerlin2D_PO(&noise_steepness->np, - p.X, 0.5, p.Y, 0.5, seed); - float height_select = NoisePerlin2D_PO(&noise_height_select->np, - p.X, 0.5, p.Y, 0.5, seed); + float terrain_base = NoisePerlin2D_PO( + &noise_terrain_base->np, p.X, 0.5, p.Y, 0.5, seed); + float terrain_higher = NoisePerlin2D_PO( + &noise_terrain_higher->np, p.X, 0.5, p.Y, 0.5, seed); + float steepness = + NoisePerlin2D_PO(&noise_steepness->np, p.X, 0.5, p.Y, 0.5, seed); + float height_select = NoisePerlin2D_PO( + &noise_height_select->np, p.X, 0.5, p.Y, 0.5, seed); - return baseTerrainLevel(terrain_base, terrain_higher, - steepness, height_select); + return baseTerrainLevel(terrain_base, terrain_higher, steepness, height_select); } - float MapgenV6::baseTerrainLevelFromMap(v2s16 p) { int index = (p.Y - node_min.Z) * ystride + (p.X - node_min.X); return baseTerrainLevelFromMap(index); } - float MapgenV6::baseTerrainLevelFromMap(int index) { if (spflags & MGV6_FLAT) return water_level; - float terrain_base = noise_terrain_base->result[index]; + float terrain_base = noise_terrain_base->result[index]; float terrain_higher = noise_terrain_higher->result[index]; - float steepness = noise_steepness->result[index]; - float height_select = noise_height_select->result[index]; + float steepness = noise_steepness->result[index]; + float height_select = noise_height_select->result[index]; - return baseTerrainLevel(terrain_base, terrain_higher, - steepness, height_select); + return baseTerrainLevel(terrain_base, terrain_higher, steepness, height_select); } - s16 MapgenV6::find_ground_level_from_noise(u64 seed, v2s16 p2d, s16 precision) { return baseTerrainLevelFromNoise(p2d) + MGV6_AVERAGE_MUD_AMOUNT; } - int MapgenV6::getGroundLevelAtPoint(v2s16 p) { return baseTerrainLevelFromNoise(p) + MGV6_AVERAGE_MUD_AMOUNT; } - int MapgenV6::getSpawnLevelAtPoint(v2s16 p) { s16 level_at_point = baseTerrainLevelFromNoise(p) + MGV6_AVERAGE_MUD_AMOUNT; - if (level_at_point <= water_level || - level_at_point > water_level + 16) - return MAX_MAP_GENERATION_LIMIT; // Unsuitable spawn point + if (level_at_point <= water_level || level_at_point > water_level + 16) + return MAX_MAP_GENERATION_LIMIT; // Unsuitable spawn point return level_at_point; } - //////////////////////// Noise functions float MapgenV6::getMudAmount(v2s16 p) @@ -372,22 +355,19 @@ float MapgenV6::getMudAmount(v2s16 p) return getMudAmount(index); } - bool MapgenV6::getHaveBeach(v2s16 p) { int index = (p.Y - node_min.Z) * ystride + (p.X - node_min.X); return getHaveBeach(index); } - BiomeV6Type MapgenV6::getBiome(v2s16 p) { - int index = (p.Y - full_node_min.Z) * (ystride + 2 * MAP_BLOCKSIZE) - + (p.X - full_node_min.X); + int index = (p.Y - full_node_min.Z) * (ystride + 2 * MAP_BLOCKSIZE) + + (p.X - full_node_min.X); return getBiome(index, p); } - float MapgenV6::getHumidity(v2s16 p) { /*double noise = noise2d_perlin( @@ -395,8 +375,8 @@ float MapgenV6::getHumidity(v2s16 p) seed+72384, 4, 0.66); noise = (noise + 1.0)/2.0;*/ - int index = (p.Y - full_node_min.Z) * (ystride + 2 * MAP_BLOCKSIZE) - + (p.X - full_node_min.X); + int index = (p.Y - full_node_min.Z) * (ystride + 2 * MAP_BLOCKSIZE) + + (p.X - full_node_min.X); float noise = noise_humidity->result[index]; if (noise < 0.0) @@ -406,7 +386,6 @@ float MapgenV6::getHumidity(v2s16 p) return noise; } - float MapgenV6::getTreeAmount(v2s16 p) { /*double noise = noise2d_perlin( @@ -421,7 +400,6 @@ float MapgenV6::getTreeAmount(v2s16 p) return 0.04 * (noise - zeroval) / (1.0 - zeroval); } - bool MapgenV6::getHaveAppleTree(v2s16 p) { /*is_apple_tree = noise2d_perlin( @@ -433,7 +411,6 @@ bool MapgenV6::getHaveAppleTree(v2s16 p) return noise > 0.2; } - float MapgenV6::getMudAmount(int index) { if (spflags & MGV6_FLAT) @@ -446,7 +423,6 @@ float MapgenV6::getMudAmount(int index) return noise_mud->result[index]; } - bool MapgenV6::getHaveBeach(int index) { // Determine whether to have sand here @@ -458,7 +434,6 @@ bool MapgenV6::getHaveBeach(int index) return (sandnoise > freq_beach); } - BiomeV6Type MapgenV6::getBiome(int index, v2s16 p) { // Just do something very simple as for now @@ -470,7 +445,8 @@ BiomeV6Type MapgenV6::getBiome(int index, v2s16 p) float h = noise_humidity->result[index]; if (spflags & MGV6_SNOWBIOMES) { - float blend = (spflags & MGV6_BIOMEBLEND) ? noise2d(p.X, p.Y, seed) / 40 : 0; + float blend = (spflags & MGV6_BIOMEBLEND) ? noise2d(p.X, p.Y, seed) / 40 + : 0; if (d > MGV6_FREQ_HOT + blend) { if (h > MGV6_FREQ_JUNGLE + blend) @@ -500,17 +476,14 @@ BiomeV6Type MapgenV6::getBiome(int index, v2s16 p) return BT_JUNGLE; return BT_NORMAL; - } - u32 MapgenV6::get_blockseed(u64 seed, v3s16 p) { s32 x = p.X, y = p.Y, z = p.Z; return (u32)(seed % 0x100000000ULL) + z * 38134234 + y * 42123 + x * 23; } - //////////////////////// Map generator void MapgenV6::makeChunk(BlockMakeData *data) @@ -519,14 +492,14 @@ void MapgenV6::makeChunk(BlockMakeData *data) assert(data->vmanip); assert(data->nodedef); assert(data->blockpos_requested.X >= data->blockpos_min.X && - data->blockpos_requested.Y >= data->blockpos_min.Y && - data->blockpos_requested.Z >= data->blockpos_min.Z); + data->blockpos_requested.Y >= data->blockpos_min.Y && + data->blockpos_requested.Z >= data->blockpos_min.Z); assert(data->blockpos_requested.X <= data->blockpos_max.X && - data->blockpos_requested.Y <= data->blockpos_max.Y && - data->blockpos_requested.Z <= data->blockpos_max.Z); + data->blockpos_requested.Y <= data->blockpos_max.Y && + data->blockpos_requested.Z <= data->blockpos_max.Z); this->generating = true; - this->vm = data->vmanip; + this->vm = data->vmanip; this->ndef = data->nodedef; // Hack: use minimum block coords for old code that assumes a single block @@ -578,7 +551,6 @@ void MapgenV6::makeChunk(BlockMakeData *data) // Flow mud away from steep edges if (spflags & MGV6_MUDFLOW) flowMud(mudflow_minpos, mudflow_maxpos); - } // Update heightmap after mudflow @@ -586,50 +558,53 @@ void MapgenV6::makeChunk(BlockMakeData *data) // Add dungeons if ((flags & MG_DUNGEONS) && stone_surface_max_y >= node_min.Y && - full_node_min.Y >= dungeon_ymin && full_node_max.Y <= dungeon_ymax) { - u16 num_dungeons = std::fmax(std::floor( - NoisePerlin3D(&np_dungeons, node_min.X, node_min.Y, node_min.Z, seed)), 0.0f); + full_node_min.Y >= dungeon_ymin && + full_node_max.Y <= dungeon_ymax) { + u16 num_dungeons = std::fmax( + std::floor(NoisePerlin3D(&np_dungeons, node_min.X, + node_min.Y, node_min.Z, seed)), + 0.0f); if (num_dungeons >= 1) { PseudoRandom ps(blockseed + 4713); DungeonParams dp; - dp.seed = seed; - dp.num_dungeons = num_dungeons; - dp.only_in_ground = true; - dp.corridor_len_min = 1; - dp.corridor_len_max = 13; - dp.num_rooms = ps.range(2, 16); + dp.seed = seed; + dp.num_dungeons = num_dungeons; + dp.only_in_ground = true; + dp.corridor_len_min = 1; + dp.corridor_len_max = 13; + dp.num_rooms = ps.range(2, 16); dp.large_room_chance = (ps.range(1, 4) == 1) ? 1 : 0; - dp.np_alt_wall - = NoiseParams(-0.4, 1.0, v3f(40.0, 40.0, 40.0), 32474, 6, 1.1, 2.0); + dp.np_alt_wall = NoiseParams(-0.4, 1.0, v3f(40.0, 40.0, 40.0), + 32474, 6, 1.1, 2.0); if (getBiome(0, v2s16(node_min.X, node_min.Z)) == BT_DESERT) { - dp.c_wall = c_desert_stone; - dp.c_alt_wall = CONTENT_IGNORE; - dp.c_stair = c_stair_desert_stone; + dp.c_wall = c_desert_stone; + dp.c_alt_wall = CONTENT_IGNORE; + dp.c_stair = c_stair_desert_stone; - dp.diagonal_dirs = true; - dp.holesize = v3s16(2, 3, 2); - dp.room_size_min = v3s16(6, 9, 6); - dp.room_size_max = v3s16(10, 11, 10); + dp.diagonal_dirs = true; + dp.holesize = v3s16(2, 3, 2); + dp.room_size_min = v3s16(6, 9, 6); + dp.room_size_max = v3s16(10, 11, 10); dp.room_size_large_min = v3s16(10, 13, 10); dp.room_size_large_max = v3s16(18, 21, 18); - dp.notifytype = GENNOTIFY_TEMPLE; + dp.notifytype = GENNOTIFY_TEMPLE; } else { - dp.c_wall = c_cobble; - dp.c_alt_wall = c_mossycobble; - dp.c_stair = c_stair_cobble; + dp.c_wall = c_cobble; + dp.c_alt_wall = c_mossycobble; + dp.c_stair = c_stair_cobble; - dp.diagonal_dirs = false; - dp.holesize = v3s16(1, 2, 1); - dp.room_size_min = v3s16(4, 4, 4); - dp.room_size_max = v3s16(8, 6, 8); + dp.diagonal_dirs = false; + dp.holesize = v3s16(1, 2, 1); + dp.room_size_min = v3s16(4, 4, 4); + dp.room_size_max = v3s16(8, 6, 8); dp.room_size_large_min = v3s16(8, 8, 8); dp.room_size_large_max = v3s16(16, 16, 16); - dp.notifytype = GENNOTIFY_DUNGEON; + dp.notifytype = GENNOTIFY_DUNGEON; } DungeonGen dgen(ndef, &gennotify, &dp); @@ -657,13 +632,12 @@ void MapgenV6::makeChunk(BlockMakeData *data) // Calculate lighting if (flags & MG_LIGHT) calcLighting(node_min - v3s16(1, 1, 1) * MAP_BLOCKSIZE, - node_max + v3s16(1, 0, 1) * MAP_BLOCKSIZE, - full_node_min, full_node_max); + node_max + v3s16(1, 0, 1) * MAP_BLOCKSIZE, full_node_min, + full_node_max); this->generating = false; } - void MapgenV6::calculateNoise() { int x = node_min.X; @@ -687,10 +661,9 @@ void MapgenV6::calculateNoise() // only humidity at point does } - int MapgenV6::generateGround() { - //TimeTaker timer1("Generating ground level"); + // TimeTaker timer1("Generating ground level"); MapNode n_air(CONTENT_AIR), n_water_source(c_water_source); MapNode n_stone(c_stone), n_desert_stone(c_desert_stone); MapNode n_ice(c_ice); @@ -698,218 +671,242 @@ int MapgenV6::generateGround() u32 index = 0; for (s16 z = node_min.Z; z <= node_max.Z; z++) - for (s16 x = node_min.X; x <= node_max.X; x++, index++) { - // Surface height - s16 surface_y = (s16)baseTerrainLevelFromMap(index); + for (s16 x = node_min.X; x <= node_max.X; x++, index++) { + // Surface height + s16 surface_y = (s16)baseTerrainLevelFromMap(index); - // Log it - if (surface_y > stone_surface_max_y) - stone_surface_max_y = surface_y; + // Log it + if (surface_y > stone_surface_max_y) + stone_surface_max_y = surface_y; - BiomeV6Type bt = getBiome(v2s16(x, z)); + BiomeV6Type bt = getBiome(v2s16(x, z)); - // Fill ground with stone - const v3s16 &em = vm->m_area.getExtent(); - u32 i = vm->m_area.index(x, node_min.Y, z); - for (s16 y = node_min.Y; y <= node_max.Y; y++) { - if (vm->m_data[i].getContent() == CONTENT_IGNORE) { - if (y <= surface_y) { - vm->m_data[i] = (y >= MGV6_DESERT_STONE_BASE - && bt == BT_DESERT) ? - n_desert_stone : n_stone; - } else if (y <= water_level) { - vm->m_data[i] = (y >= MGV6_ICE_BASE - && bt == BT_TUNDRA) ? - n_ice : n_water_source; - } else { - vm->m_data[i] = n_air; + // Fill ground with stone + const v3s16 &em = vm->m_area.getExtent(); + u32 i = vm->m_area.index(x, node_min.Y, z); + for (s16 y = node_min.Y; y <= node_max.Y; y++) { + if (vm->m_data[i].getContent() == CONTENT_IGNORE) { + if (y <= surface_y) { + vm->m_data[i] = (y >= MGV6_DESERT_STONE_BASE && + bt == BT_DESERT) + ? n_desert_stone + : n_stone; + } else if (y <= water_level) { + vm->m_data[i] = (y >= MGV6_ICE_BASE && + bt == BT_TUNDRA) + ? n_ice + : n_water_source; + } else { + vm->m_data[i] = n_air; + } } + VoxelArea::add_y(em, i, 1); } - VoxelArea::add_y(em, i, 1); } - } return stone_surface_max_y; } - void MapgenV6::addMud() { // 15ms @cs=8 - //TimeTaker timer1("add mud"); + // TimeTaker timer1("add mud"); MapNode n_dirt(c_dirt), n_gravel(c_gravel); MapNode n_sand(c_sand), n_desert_sand(c_desert_sand); MapNode addnode; u32 index = 0; for (s16 z = node_min.Z; z <= node_max.Z; z++) - for (s16 x = node_min.X; x <= node_max.X; x++, index++) { - // Randomize mud amount - s16 mud_add_amount = getMudAmount(index) / 2.0 + 0.5; + for (s16 x = node_min.X; x <= node_max.X; x++, index++) { + // Randomize mud amount + s16 mud_add_amount = getMudAmount(index) / 2.0 + 0.5; - // Find ground level - s16 surface_y = find_stone_level(v2s16(x, z)); /////////////////optimize this! + // Find ground level + s16 surface_y = find_stone_level( + v2s16(x, z)); /////////////////optimize this! - // Handle area not found - if (surface_y == vm->m_area.MinEdge.Y - 1) - continue; + // Handle area not found + if (surface_y == vm->m_area.MinEdge.Y - 1) + continue; - BiomeV6Type bt = getBiome(v2s16(x, z)); - addnode = (bt == BT_DESERT) ? n_desert_sand : n_dirt; + BiomeV6Type bt = getBiome(v2s16(x, z)); + addnode = (bt == BT_DESERT) ? n_desert_sand : n_dirt; - if (bt == BT_DESERT && surface_y + mud_add_amount <= water_level + 1) { - addnode = n_sand; - } else if (mud_add_amount <= 0) { - mud_add_amount = 1 - mud_add_amount; - addnode = n_gravel; - } else if (bt != BT_DESERT && getHaveBeach(index) && - surface_y + mud_add_amount <= water_level + 2) { - addnode = n_sand; + if (bt == BT_DESERT && + surface_y + mud_add_amount <= water_level + 1) { + addnode = n_sand; + } else if (mud_add_amount <= 0) { + mud_add_amount = 1 - mud_add_amount; + addnode = n_gravel; + } else if (bt != BT_DESERT && getHaveBeach(index) && + surface_y + mud_add_amount <= water_level + 2) { + addnode = n_sand; + } + + if ((bt == BT_DESERT || bt == BT_TUNDRA) && surface_y > 20) + mud_add_amount = MYMAX( + 0, mud_add_amount - (surface_y - 20) / 5); + + /* If topmost node is grass, change it to mud. It might be if it + was + // flown to there from a neighboring chunk and then converted. + u32 i = vm->m_area.index(x, surface_y, z); + if (vm->m_data[i].getContent() == c_dirt_with_grass) + vm->m_data[i] = n_dirt;*/ + + // Add mud on ground + s16 mudcount = 0; + const v3s16 &em = vm->m_area.getExtent(); + s16 y_start = surface_y + 1; + u32 i = vm->m_area.index(x, y_start, z); + for (s16 y = y_start; y <= node_max.Y; y++) { + if (mudcount >= mud_add_amount) + break; + + vm->m_data[i] = addnode; + mudcount++; + + VoxelArea::add_y(em, i, 1); + } } - - if ((bt == BT_DESERT || bt == BT_TUNDRA) && surface_y > 20) - mud_add_amount = MYMAX(0, mud_add_amount - (surface_y - 20) / 5); - - /* If topmost node is grass, change it to mud. It might be if it was - // flown to there from a neighboring chunk and then converted. - u32 i = vm->m_area.index(x, surface_y, z); - if (vm->m_data[i].getContent() == c_dirt_with_grass) - vm->m_data[i] = n_dirt;*/ - - // Add mud on ground - s16 mudcount = 0; - const v3s16 &em = vm->m_area.getExtent(); - s16 y_start = surface_y + 1; - u32 i = vm->m_area.index(x, y_start, z); - for (s16 y = y_start; y <= node_max.Y; y++) { - if (mudcount >= mud_add_amount) - break; - - vm->m_data[i] = addnode; - mudcount++; - - VoxelArea::add_y(em, i, 1); - } - } } - void MapgenV6::flowMud(s16 &mudflow_minpos, s16 &mudflow_maxpos) { const v3s16 &em = vm->m_area.getExtent(); static const v3s16 dirs4[4] = { - v3s16(0, 0, 1), // Back - v3s16(1, 0, 0), // Right - v3s16(0, 0, -1), // Front - v3s16(-1, 0, 0), // Left + v3s16(0, 0, 1), // Back + v3s16(1, 0, 0), // Right + v3s16(0, 0, -1), // Front + v3s16(-1, 0, 0), // Left }; - + // Iterate twice for (s16 k = 0; k < 2; k++) { for (s16 z = mudflow_minpos; z <= mudflow_maxpos; z++) - for (s16 x = mudflow_minpos; x <= mudflow_maxpos; x++) { - // Node column position - v2s16 p2d; - // Invert coordinates on second iteration to process columns in - // opposite order, to avoid a directional bias. - if (k == 1) - p2d = v2s16(node_max.X, node_max.Z) - v2s16(x, z); - else - p2d = v2s16(node_min.X, node_min.Z) + v2s16(x, z); + for (s16 x = mudflow_minpos; x <= mudflow_maxpos; x++) { + // Node column position + v2s16 p2d; + // Invert coordinates on second iteration to process + // columns in opposite order, to avoid a directional bias. + if (k == 1) + p2d = v2s16(node_max.X, node_max.Z) - v2s16(x, z); + else + p2d = v2s16(node_min.X, node_min.Z) + v2s16(x, z); - s16 y = node_max.Y; + s16 y = node_max.Y; - while (y >= node_min.Y) { - for (;; y--) { - u32 i = vm->m_area.index(p2d.X, y, p2d.Y); - MapNode *n = nullptr; + while (y >= node_min.Y) { + for (;; y--) { + u32 i = vm->m_area.index(p2d.X, y, p2d.Y); + MapNode *n = nullptr; - // Find next mud node in mapchunk column - for (; y >= node_min.Y; y--) { - n = &vm->m_data[i]; - if (n->getContent() == c_dirt || - n->getContent() == c_dirt_with_grass || - n->getContent() == c_gravel) + // Find next mud node in mapchunk column + for (; y >= node_min.Y; y--) { + n = &vm->m_data[i]; + if (n->getContent() == c_dirt || + n->getContent() == + c_dirt_with_grass || + n->getContent() == + c_gravel) + break; + + VoxelArea::add_y(em, i, -1); + } + if (y < node_min.Y) + // No mud found in mapchunk + // column, process the next column break; - VoxelArea::add_y(em, i, -1); - } - if (y < node_min.Y) - // No mud found in mapchunk column, process the next column - break; + if (n->getContent() == c_dirt || + n->getContent() == + c_dirt_with_grass) { + // Convert dirt_with_grass to dirt + n->setContent(c_dirt); + // Don't flow mud if the stuff + // under it is not mud, to leave + // at least 1 node of mud. + u32 i2 = i; + VoxelArea::add_y(em, i2, -1); + MapNode *n2 = &vm->m_data[i2]; + if (n2->getContent() != c_dirt && + n2->getContent() != + c_dirt_with_grass) + // Find next mud node in + // column + continue; + } - if (n->getContent() == c_dirt || n->getContent() == c_dirt_with_grass) { - // Convert dirt_with_grass to dirt - n->setContent(c_dirt); - // Don't flow mud if the stuff under it is not mud, - // to leave at least 1 node of mud. - u32 i2 = i; - VoxelArea::add_y(em, i2, -1); - MapNode *n2 = &vm->m_data[i2]; - if (n2->getContent() != c_dirt && - n2->getContent() != c_dirt_with_grass) + // Check if node above is walkable. If so, + // cancel flowing as if node above keeps + // it in place. + u32 i3 = i; + VoxelArea::add_y(em, i3, 1); + MapNode *n3 = &vm->m_data[i3]; + if (ndef->get(*n3).walkable) // Find next mud node in column continue; - } - // Check if node above is walkable. If so, cancel - // flowing as if node above keeps it in place. - u32 i3 = i; - VoxelArea::add_y(em, i3, 1); - MapNode *n3 = &vm->m_data[i3]; - if (ndef->get(*n3).walkable) - // Find next mud node in column - continue; + // Drop mud on one side + for (const v3s16 &dirp : dirs4) { + u32 i2 = i; + // Move to side + VoxelArea::add_p(em, i2, dirp); + // Check that side is air + MapNode *n2 = &vm->m_data[i2]; + if (ndef->get(*n2).walkable) + continue; - // Drop mud on one side - for (const v3s16 &dirp : dirs4) { - u32 i2 = i; - // Move to side - VoxelArea::add_p(em, i2, dirp); - // Check that side is air - MapNode *n2 = &vm->m_data[i2]; - if (ndef->get(*n2).walkable) - continue; - - // Check that under side is air - VoxelArea::add_y(em, i2, -1); - n2 = &vm->m_data[i2]; - if (ndef->get(*n2).walkable) - continue; - - // Loop further down until not air - s16 y2 = y - 1; // y of i2 - bool dropped_to_unknown = false; - do { - y2--; + // Check that under side is air VoxelArea::add_y(em, i2, -1); n2 = &vm->m_data[i2]; - // If out of area or in ungenerated world - if (y2 < full_node_min.Y || n2->getContent() == CONTENT_IGNORE) { - dropped_to_unknown = true; - break; - } - } while (!ndef->get(*n2).walkable); + if (ndef->get(*n2).walkable) + continue; - if (!dropped_to_unknown) { - // Move up one so that we're in air - VoxelArea::add_y(em, i2, 1); - // Move mud to new place, and if outside mapchunk remove - // any decorations above removed or placed mud. - moveMud(i, i2, i3, p2d, em); + // Loop further down until not air + s16 y2 = y - 1; // y of i2 + bool dropped_to_unknown = false; + do { + y2--; + VoxelArea::add_y(em, i2, + -1); + n2 = &vm->m_data[i2]; + // If out of area or in + // ungenerated world + if (y2 < full_node_min.Y || + n2->getContent() == + CONTENT_IGNORE) { + dropped_to_unknown = + true; + break; + } + } while (!ndef->get(*n2).walkable); + + if (!dropped_to_unknown) { + // Move up one so that + // we're in air + VoxelArea::add_y(em, i2, + 1); + // Move mud to new place, + // and if outside mapchunk + // remove any decorations + // above removed or placed + // mud. + moveMud(i, i2, i3, p2d, + em); + } + // Done, find next mud node in + // column + break; } - // Done, find next mud node in column - break; } } } - } } } - -void MapgenV6::moveMud(u32 remove_index, u32 place_index, - u32 above_remove_index, v2s16 pos, v3s16 em) +void MapgenV6::moveMud(u32 remove_index, u32 place_index, u32 above_remove_index, + v2s16 pos, v3s16 em) { MapNode n_air(CONTENT_AIR); // Copy mud from old place to new place @@ -919,17 +916,20 @@ void MapgenV6::moveMud(u32 remove_index, u32 place_index, // Outside the mapchunk decorations may need to be removed if above removed // mud or if half-buried in placed mud. Placed mud is to the side of pos so // use 'pos.X >= node_max.X' etc. - if (pos.X >= node_max.X || pos.X <= node_min.X || - pos.Y >= node_max.Z || pos.Y <= node_min.Z) { + if (pos.X >= node_max.X || pos.X <= node_min.X || pos.Y >= node_max.Z || + pos.Y <= node_min.Z) { // 'above remove' node is above removed mud. If it is not air, water or // 'ignore' it is a decoration that needs removing. Also search upwards // to remove a possible stacked decoration. // Check for 'ignore' because stacked decorations can penetrate into // 'ignore' nodes above the mapchunk. while (vm->m_area.contains(above_remove_index) && - vm->m_data[above_remove_index].getContent() != CONTENT_AIR && - vm->m_data[above_remove_index].getContent() != c_water_source && - vm->m_data[above_remove_index].getContent() != CONTENT_IGNORE) { + vm->m_data[above_remove_index].getContent() != + CONTENT_AIR && + vm->m_data[above_remove_index].getContent() != + c_water_source && + vm->m_data[above_remove_index].getContent() != + CONTENT_IGNORE) { vm->m_data[above_remove_index] = n_air; VoxelArea::add_y(em, above_remove_index, 1); } @@ -946,10 +946,9 @@ void MapgenV6::moveMud(u32 remove_index, u32 place_index, } } - void MapgenV6::placeTreesAndJungleGrass() { - //TimeTaker t("placeTrees"); + // TimeTaker t("placeTrees"); if (node_max.Y < water_level) return; @@ -969,98 +968,96 @@ void MapgenV6::placeTreesAndJungleGrass() // N.B. We must add jungle grass first, since tree leaves will // obstruct the ground, giving us a false ground level for (s16 z0 = 0; z0 < div; z0++) - for (s16 x0 = 0; x0 < div; x0++) { - // Center position of part of division - v2s16 p2d_center( - node_min.X + sidelen / 2 + sidelen * x0, - node_min.Z + sidelen / 2 + sidelen * z0 - ); - // Minimum edge of part of division - v2s16 p2d_min( - node_min.X + sidelen * x0, - node_min.Z + sidelen * z0 - ); - // Maximum edge of part of division - v2s16 p2d_max( - node_min.X + sidelen + sidelen * x0 - 1, - node_min.Z + sidelen + sidelen * z0 - 1 - ); + for (s16 x0 = 0; x0 < div; x0++) { + // Center position of part of division + v2s16 p2d_center(node_min.X + sidelen / 2 + sidelen * x0, + node_min.Z + sidelen / 2 + sidelen * z0); + // Minimum edge of part of division + v2s16 p2d_min(node_min.X + sidelen * x0, + node_min.Z + sidelen * z0); + // Maximum edge of part of division + v2s16 p2d_max(node_min.X + sidelen + sidelen * x0 - 1, + node_min.Z + sidelen + sidelen * z0 - 1); - // Get biome at center position of part of division - BiomeV6Type bt = getBiome(p2d_center); + // Get biome at center position of part of division + BiomeV6Type bt = getBiome(p2d_center); - // Amount of trees - u32 tree_count; - if (bt == BT_JUNGLE || bt == BT_TAIGA || bt == BT_NORMAL) { - tree_count = area * getTreeAmount(p2d_center); - if (bt == BT_JUNGLE) - tree_count *= 4; - } else { - tree_count = 0; - } + // Amount of trees + u32 tree_count; + if (bt == BT_JUNGLE || bt == BT_TAIGA || bt == BT_NORMAL) { + tree_count = area * getTreeAmount(p2d_center); + if (bt == BT_JUNGLE) + tree_count *= 4; + } else { + tree_count = 0; + } - // Add jungle grass - if (bt == BT_JUNGLE) { - float humidity = getHumidity(p2d_center); - u32 grass_count = 5 * humidity * tree_count; - for (u32 i = 0; i < grass_count; i++) { - s16 x = grassrandom.range(p2d_min.X, p2d_max.X); - s16 z = grassrandom.range(p2d_min.Y, p2d_max.Y); - int mapindex = central_area_size.X * (z - node_min.Z) - + (x - node_min.X); + // Add jungle grass + if (bt == BT_JUNGLE) { + float humidity = getHumidity(p2d_center); + u32 grass_count = 5 * humidity * tree_count; + for (u32 i = 0; i < grass_count; i++) { + s16 x = grassrandom.range(p2d_min.X, p2d_max.X); + s16 z = grassrandom.range(p2d_min.Y, p2d_max.Y); + int mapindex = central_area_size.X * + (z - node_min.Z) + + (x - node_min.X); + s16 y = heightmap[mapindex]; + if (y < water_level) + continue; + + u32 vi = vm->m_area.index(x, y, z); + // place on dirt_with_grass, since we know it is + // exposed to sunlight + if (vm->m_data[vi].getContent() == + c_dirt_with_grass) { + VoxelArea::add_y(em, vi, 1); + vm->m_data[vi] = n_junglegrass; + } + } + } + + // Put trees in random places on part of division + for (u32 i = 0; i < tree_count; i++) { + s16 x = myrand_range(p2d_min.X, p2d_max.X); + s16 z = myrand_range(p2d_min.Y, p2d_max.Y); + int mapindex = central_area_size.X * (z - node_min.Z) + + (x - node_min.X); s16 y = heightmap[mapindex]; - if (y < water_level) + // Don't make a tree under water level + // Don't make a tree so high that it doesn't fit + if (y < water_level || y > node_max.Y - 6) continue; - u32 vi = vm->m_area.index(x, y, z); - // place on dirt_with_grass, since we know it is exposed to sunlight - if (vm->m_data[vi].getContent() == c_dirt_with_grass) { - VoxelArea::add_y(em, vi, 1); - vm->m_data[vi] = n_junglegrass; + v3s16 p(x, y, z); + // Trees grow only on mud and grass + { + u32 i = vm->m_area.index(p); + content_t c = vm->m_data[i].getContent(); + if (c != c_dirt && c != c_dirt_with_grass && + c != c_dirt_with_snow) + continue; + } + p.Y++; + + // Make a tree + if (bt == BT_JUNGLE) { + treegen::make_jungletree(*vm, p, ndef, myrand()); + } else if (bt == BT_TAIGA) { + treegen::make_pine_tree(*vm, p - v3s16(0, 1, 0), + ndef, myrand()); + } else if (bt == BT_NORMAL) { + bool is_apple_tree = + (myrand_range(0, 3) == 0) && + getHaveAppleTree(v2s16(x, z)); + treegen::make_tree(*vm, p, is_apple_tree, ndef, + myrand()); } } } - - // Put trees in random places on part of division - for (u32 i = 0; i < tree_count; i++) { - s16 x = myrand_range(p2d_min.X, p2d_max.X); - s16 z = myrand_range(p2d_min.Y, p2d_max.Y); - int mapindex = central_area_size.X * (z - node_min.Z) - + (x - node_min.X); - s16 y = heightmap[mapindex]; - // Don't make a tree under water level - // Don't make a tree so high that it doesn't fit - if (y < water_level || y > node_max.Y - 6) - continue; - - v3s16 p(x, y, z); - // Trees grow only on mud and grass - { - u32 i = vm->m_area.index(p); - content_t c = vm->m_data[i].getContent(); - if (c != c_dirt && - c != c_dirt_with_grass && - c != c_dirt_with_snow) - continue; - } - p.Y++; - - // Make a tree - if (bt == BT_JUNGLE) { - treegen::make_jungletree(*vm, p, ndef, myrand()); - } else if (bt == BT_TAIGA) { - treegen::make_pine_tree(*vm, p - v3s16(0, 1, 0), ndef, myrand()); - } else if (bt == BT_NORMAL) { - bool is_apple_tree = (myrand_range(0, 3) == 0) && - getHaveAppleTree(v2s16(x, z)); - treegen::make_tree(*vm, p, is_apple_tree, ndef, myrand()); - } - } - } - //printf("placeTreesAndJungleGrass: %dms\n", t.stop()); + // printf("placeTreesAndJungleGrass: %dms\n", t.stop()); } - void MapgenV6::growGrass() // Add surface nodes { MapNode n_dirt_with_grass(c_dirt_with_grass); @@ -1071,53 +1068,54 @@ void MapgenV6::growGrass() // Add surface nodes u32 index = 0; for (s16 z = full_node_min.Z; z <= full_node_max.Z; z++) - for (s16 x = full_node_min.X; x <= full_node_max.X; x++, index++) { - // Find the lowest surface to which enough light ends up to make - // grass grow. Basically just wait until not air and not leaves. - s16 surface_y = 0; - { - u32 i = vm->m_area.index(x, node_max.Y, z); - s16 y; - // Go to ground level - for (y = node_max.Y; y >= full_node_min.Y; y--) { - MapNode &n = vm->m_data[i]; - if (ndef->get(n).param_type != CPT_LIGHT || - ndef->get(n).liquid_type != LIQUID_NONE || - n.getContent() == c_ice) - break; - VoxelArea::add_y(em, i, -1); - } - surface_y = (y >= full_node_min.Y) ? y : full_node_min.Y; - } - - BiomeV6Type bt = getBiome(index, v2s16(x, z)); - u32 i = vm->m_area.index(x, surface_y, z); - content_t c = vm->m_data[i].getContent(); - if (surface_y >= water_level - 20) { - if (bt == BT_TAIGA && c == c_dirt) { - vm->m_data[i] = n_dirt_with_snow; - } else if (bt == BT_TUNDRA) { - if (c == c_dirt) { - vm->m_data[i] = n_snowblock; + for (s16 x = full_node_min.X; x <= full_node_max.X; x++, index++) { + // Find the lowest surface to which enough light ends up to make + // grass grow. Basically just wait until not air and not leaves. + s16 surface_y = 0; + { + u32 i = vm->m_area.index(x, node_max.Y, z); + s16 y; + // Go to ground level + for (y = node_max.Y; y >= full_node_min.Y; y--) { + MapNode &n = vm->m_data[i]; + if (ndef->get(n).param_type != CPT_LIGHT || + ndef->get(n).liquid_type != + LIQUID_NONE || + n.getContent() == c_ice) + break; VoxelArea::add_y(em, i, -1); - vm->m_data[i] = n_dirt_with_snow; - } else if (c == c_stone && surface_y < node_max.Y) { - VoxelArea::add_y(em, i, 1); - vm->m_data[i] = n_snowblock; } - } else if (c == c_dirt) { - vm->m_data[i] = n_dirt_with_grass; + surface_y = (y >= full_node_min.Y) ? y : full_node_min.Y; + } + + BiomeV6Type bt = getBiome(index, v2s16(x, z)); + u32 i = vm->m_area.index(x, surface_y, z); + content_t c = vm->m_data[i].getContent(); + if (surface_y >= water_level - 20) { + if (bt == BT_TAIGA && c == c_dirt) { + vm->m_data[i] = n_dirt_with_snow; + } else if (bt == BT_TUNDRA) { + if (c == c_dirt) { + vm->m_data[i] = n_snowblock; + VoxelArea::add_y(em, i, -1); + vm->m_data[i] = n_dirt_with_snow; + } else if (c == c_stone && + surface_y < node_max.Y) { + VoxelArea::add_y(em, i, 1); + vm->m_data[i] = n_snowblock; + } + } else if (c == c_dirt) { + vm->m_data[i] = n_dirt_with_grass; + } } } - } } - void MapgenV6::generateCaves(int max_stone_y) { float cave_amount = NoisePerlin2D(np_cave, node_min.X, node_min.Y, seed); - int volume_nodes = (node_max.X - node_min.X + 1) * - (node_max.Y - node_min.Y + 1) * MAP_BLOCKSIZE; + int volume_nodes = (node_max.X - node_min.X + 1) * (node_max.Y - node_min.Y + 1) * + MAP_BLOCKSIZE; cave_amount = MYMAX(0.0, cave_amount); u32 caves_count = cave_amount * volume_nodes / 50000; u32 bruises_count = 1; @@ -1128,15 +1126,16 @@ void MapgenV6::generateCaves(int max_stone_y) bruises_count = ps.range(0, ps.range(0, 2)); if (getBiome(v2s16(node_min.X, node_min.Z)) == BT_DESERT) { - caves_count /= 3; + caves_count /= 3; bruises_count /= 3; } for (u32 i = 0; i < caves_count + bruises_count; i++) { - CavesV6 cave(ndef, &gennotify, water_level, c_water_source, c_lava_source); + CavesV6 cave(ndef, &gennotify, water_level, c_water_source, + c_lava_source); bool large_cave = (i >= caves_count); - cave.makeCave(vm, node_min, node_max, &ps, &ps2, - large_cave, max_stone_y, heightmap); + cave.makeCave(vm, node_min, node_max, &ps, &ps2, large_cave, max_stone_y, + heightmap); } } diff --git a/src/mapgen/mapgen_v6.h b/src/mapgen/mapgen_v6.h index ff565edec..a0abbb010 100644 --- a/src/mapgen/mapgen_v6.h +++ b/src/mapgen/mapgen_v6.h @@ -33,17 +33,15 @@ with this program; if not, write to the Free Software Foundation, Inc., #define MGV6_FREQ_JUNGLE 0.5 //////////// Mapgen V6 flags -#define MGV6_JUNGLES 0x01 +#define MGV6_JUNGLES 0x01 #define MGV6_BIOMEBLEND 0x02 -#define MGV6_MUDFLOW 0x04 +#define MGV6_MUDFLOW 0x04 #define MGV6_SNOWBIOMES 0x08 -#define MGV6_FLAT 0x10 -#define MGV6_TREES 0x20 - +#define MGV6_FLAT 0x10 +#define MGV6_TREES 0x20 extern FlagDesc flagdesc_mapgen_v6[]; - enum BiomeV6Type { BT_NORMAL, @@ -53,8 +51,8 @@ enum BiomeV6Type BT_TAIGA, }; - -struct MapgenV6Params : public MapgenParams { +struct MapgenV6Params : public MapgenParams +{ float freq_desert = 0.45f; float freq_beach = 0.15f; s16 dungeon_ymin = -31000; @@ -80,8 +78,8 @@ struct MapgenV6Params : public MapgenParams { void setDefaultSettings(Settings *settings); }; - -class MapgenV6 : public Mapgen { +class MapgenV6 : public Mapgen +{ public: EmergeParams *m_emerge; @@ -142,8 +140,8 @@ public: int getGroundLevelAtPoint(v2s16 p); int getSpawnLevelAtPoint(v2s16 p); - float baseTerrainLevel(float terrain_base, float terrain_higher, - float steepness, float height_select); + float baseTerrainLevel(float terrain_base, float terrain_higher, float steepness, + float height_select); virtual float baseTerrainLevelFromNoise(v2s16 p); virtual float baseTerrainLevelFromMap(v2s16 p); virtual float baseTerrainLevelFromMap(int index); @@ -168,8 +166,8 @@ public: int generateGround(); void addMud(); void flowMud(s16 &mudflow_minpos, s16 &mudflow_maxpos); - void moveMud(u32 remove_index, u32 place_index, - u32 above_remove_index, v2s16 pos, v3s16 em); + void moveMud(u32 remove_index, u32 place_index, u32 above_remove_index, v2s16 pos, + v3s16 em); void growGrass(); void placeTreesAndJungleGrass(); virtual void generateCaves(int max_stone_y); diff --git a/src/mapgen/mapgen_v7.cpp b/src/mapgen/mapgen_v7.cpp index e93dc9140..c1ce55348 100644 --- a/src/mapgen/mapgen_v7.cpp +++ b/src/mapgen/mapgen_v7.cpp @@ -18,7 +18,6 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ - #include "mapgen.h" #include #include "voxel.h" @@ -38,92 +37,80 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "mg_decoration.h" #include "mapgen_v7.h" - -FlagDesc flagdesc_mapgen_v7[] = { - {"mountains", MGV7_MOUNTAINS}, - {"ridges", MGV7_RIDGES}, - {"floatlands", MGV7_FLOATLANDS}, - {"caverns", MGV7_CAVERNS}, - {NULL, 0} -}; - +FlagDesc flagdesc_mapgen_v7[] = {{"mountains", MGV7_MOUNTAINS}, {"ridges", MGV7_RIDGES}, + {"floatlands", MGV7_FLOATLANDS}, {"caverns", MGV7_CAVERNS}, {NULL, 0}}; //////////////////////////////////////////////////////////////////////////////// - -MapgenV7::MapgenV7(MapgenV7Params *params, EmergeParams *emerge) - : MapgenBasic(MAPGEN_V7, params, emerge) +MapgenV7::MapgenV7(MapgenV7Params *params, EmergeParams *emerge) : + MapgenBasic(MAPGEN_V7, params, emerge) { - spflags = params->spflags; - mount_zero_level = params->mount_zero_level; - floatland_ymin = params->floatland_ymin; - floatland_ymax = params->floatland_ymax; - floatland_taper = params->floatland_taper; - float_taper_exp = params->float_taper_exp; - floatland_density = params->floatland_density; - floatland_ywater = params->floatland_ywater; + spflags = params->spflags; + mount_zero_level = params->mount_zero_level; + floatland_ymin = params->floatland_ymin; + floatland_ymax = params->floatland_ymax; + floatland_taper = params->floatland_taper; + float_taper_exp = params->float_taper_exp; + floatland_density = params->floatland_density; + floatland_ywater = params->floatland_ywater; - cave_width = params->cave_width; - large_cave_depth = params->large_cave_depth; + cave_width = params->cave_width; + large_cave_depth = params->large_cave_depth; small_cave_num_min = params->small_cave_num_min; small_cave_num_max = params->small_cave_num_max; large_cave_num_min = params->large_cave_num_min; large_cave_num_max = params->large_cave_num_max; large_cave_flooded = params->large_cave_flooded; - cavern_limit = params->cavern_limit; - cavern_taper = params->cavern_taper; - cavern_threshold = params->cavern_threshold; - dungeon_ymin = params->dungeon_ymin; - dungeon_ymax = params->dungeon_ymax; + cavern_limit = params->cavern_limit; + cavern_taper = params->cavern_taper; + cavern_threshold = params->cavern_threshold; + dungeon_ymin = params->dungeon_ymin; + dungeon_ymax = params->dungeon_ymax; // Allocate floatland noise offset cache this->float_offset_cache = new float[csize.Y + 2]; // 2D noise - noise_terrain_base = - new Noise(¶ms->np_terrain_base, seed, csize.X, csize.Z); - noise_terrain_alt = - new Noise(¶ms->np_terrain_alt, seed, csize.X, csize.Z); + noise_terrain_base = new Noise(¶ms->np_terrain_base, seed, csize.X, csize.Z); + noise_terrain_alt = new Noise(¶ms->np_terrain_alt, seed, csize.X, csize.Z); noise_terrain_persist = - new Noise(¶ms->np_terrain_persist, seed, csize.X, csize.Z); + new Noise(¶ms->np_terrain_persist, seed, csize.X, csize.Z); noise_height_select = - new Noise(¶ms->np_height_select, seed, csize.X, csize.Z); - noise_filler_depth = - new Noise(¶ms->np_filler_depth, seed, csize.X, csize.Z); + new Noise(¶ms->np_height_select, seed, csize.X, csize.Z); + noise_filler_depth = new Noise(¶ms->np_filler_depth, seed, csize.X, csize.Z); if (spflags & MGV7_MOUNTAINS) { // 2D noise - noise_mount_height = - new Noise(¶ms->np_mount_height, seed, csize.X, csize.Z); + noise_mount_height = new Noise( + ¶ms->np_mount_height, seed, csize.X, csize.Z); // 3D noise, 1 up, 1 down overgeneration - noise_mountain = - new Noise(¶ms->np_mountain, seed, csize.X, csize.Y + 2, csize.Z); + noise_mountain = new Noise(¶ms->np_mountain, seed, csize.X, + csize.Y + 2, csize.Z); } if (spflags & MGV7_RIDGES) { // 2D noise - noise_ridge_uwater = - new Noise(¶ms->np_ridge_uwater, seed, csize.X, csize.Z); + noise_ridge_uwater = new Noise( + ¶ms->np_ridge_uwater, seed, csize.X, csize.Z); // 3D noise, 1 up, 1 down overgeneration - noise_ridge = - new Noise(¶ms->np_ridge, seed, csize.X, csize.Y + 2, csize.Z); + noise_ridge = new Noise( + ¶ms->np_ridge, seed, csize.X, csize.Y + 2, csize.Z); } if (spflags & MGV7_FLOATLANDS) { // 3D noise, 1 up, 1 down overgeneration - noise_floatland = - new Noise(¶ms->np_floatland, seed, csize.X, csize.Y + 2, csize.Z); + noise_floatland = new Noise(¶ms->np_floatland, seed, csize.X, + csize.Y + 2, csize.Z); } // 3D noise, 1 down overgeneration - MapgenBasic::np_cave1 = params->np_cave1; - MapgenBasic::np_cave2 = params->np_cave2; - MapgenBasic::np_cavern = params->np_cavern; + MapgenBasic::np_cave1 = params->np_cave1; + MapgenBasic::np_cave2 = params->np_cave2; + MapgenBasic::np_cavern = params->np_cavern; // 3D noise MapgenBasic::np_dungeons = params->np_dungeons; } - MapgenV7::~MapgenV7() { delete noise_terrain_base; @@ -146,128 +133,122 @@ MapgenV7::~MapgenV7() delete noise_floatland; } - delete []float_offset_cache; + delete[] float_offset_cache; } - -MapgenV7Params::MapgenV7Params(): - np_terrain_base (4.0, 70.0, v3f(600, 600, 600), 82341, 5, 0.6, 2.0), - np_terrain_alt (4.0, 25.0, v3f(600, 600, 600), 5934, 5, 0.6, 2.0), - np_terrain_persist (0.6, 0.1, v3f(2000, 2000, 2000), 539, 3, 0.6, 2.0), - np_height_select (-8.0, 16.0, v3f(500, 500, 500), 4213, 6, 0.7, 2.0), - np_filler_depth (0.0, 1.2, v3f(150, 150, 150), 261, 3, 0.7, 2.0), - np_mount_height (256.0, 112.0, v3f(1000, 1000, 1000), 72449, 3, 0.6, 2.0), - np_ridge_uwater (0.0, 1.0, v3f(1000, 1000, 1000), 85039, 5, 0.6, 2.0), - np_mountain (-0.6, 1.0, v3f(250, 350, 250), 5333, 5, 0.63, 2.0), - np_ridge (0.0, 1.0, v3f(100, 100, 100), 6467, 4, 0.75, 2.0), - np_floatland (0.0, 0.7, v3f(384, 96, 384), 1009, 4, 0.75, 1.618), - np_cavern (0.0, 1.0, v3f(384, 128, 384), 723, 5, 0.63, 2.0), - np_cave1 (0.0, 12.0, v3f(61, 61, 61), 52534, 3, 0.5, 2.0), - np_cave2 (0.0, 12.0, v3f(67, 67, 67), 10325, 3, 0.5, 2.0), - np_dungeons (0.9, 0.5, v3f(500, 500, 500), 0, 2, 0.8, 2.0) +MapgenV7Params::MapgenV7Params() : + np_terrain_base(4.0, 70.0, v3f(600, 600, 600), 82341, 5, 0.6, 2.0), + np_terrain_alt(4.0, 25.0, v3f(600, 600, 600), 5934, 5, 0.6, 2.0), + np_terrain_persist(0.6, 0.1, v3f(2000, 2000, 2000), 539, 3, 0.6, 2.0), + np_height_select(-8.0, 16.0, v3f(500, 500, 500), 4213, 6, 0.7, 2.0), + np_filler_depth(0.0, 1.2, v3f(150, 150, 150), 261, 3, 0.7, 2.0), + np_mount_height(256.0, 112.0, v3f(1000, 1000, 1000), 72449, 3, 0.6, 2.0), + np_ridge_uwater(0.0, 1.0, v3f(1000, 1000, 1000), 85039, 5, 0.6, 2.0), + np_mountain(-0.6, 1.0, v3f(250, 350, 250), 5333, 5, 0.63, 2.0), + np_ridge(0.0, 1.0, v3f(100, 100, 100), 6467, 4, 0.75, 2.0), + np_floatland(0.0, 0.7, v3f(384, 96, 384), 1009, 4, 0.75, 1.618), + np_cavern(0.0, 1.0, v3f(384, 128, 384), 723, 5, 0.63, 2.0), + np_cave1(0.0, 12.0, v3f(61, 61, 61), 52534, 3, 0.5, 2.0), + np_cave2(0.0, 12.0, v3f(67, 67, 67), 10325, 3, 0.5, 2.0), + np_dungeons(0.9, 0.5, v3f(500, 500, 500), 0, 2, 0.8, 2.0) { } - void MapgenV7Params::readParams(const Settings *settings) { settings->getFlagStrNoEx("mgv7_spflags", spflags, flagdesc_mapgen_v7); - settings->getS16NoEx("mgv7_mount_zero_level", mount_zero_level); - settings->getS16NoEx("mgv7_floatland_ymin", floatland_ymin); - settings->getS16NoEx("mgv7_floatland_ymax", floatland_ymax); - settings->getS16NoEx("mgv7_floatland_taper", floatland_taper); - settings->getFloatNoEx("mgv7_float_taper_exp", float_taper_exp); - settings->getFloatNoEx("mgv7_floatland_density", floatland_density); - settings->getS16NoEx("mgv7_floatland_ywater", floatland_ywater); + settings->getS16NoEx("mgv7_mount_zero_level", mount_zero_level); + settings->getS16NoEx("mgv7_floatland_ymin", floatland_ymin); + settings->getS16NoEx("mgv7_floatland_ymax", floatland_ymax); + settings->getS16NoEx("mgv7_floatland_taper", floatland_taper); + settings->getFloatNoEx("mgv7_float_taper_exp", float_taper_exp); + settings->getFloatNoEx("mgv7_floatland_density", floatland_density); + settings->getS16NoEx("mgv7_floatland_ywater", floatland_ywater); - settings->getFloatNoEx("mgv7_cave_width", cave_width); - settings->getS16NoEx("mgv7_large_cave_depth", large_cave_depth); - settings->getU16NoEx("mgv7_small_cave_num_min", small_cave_num_min); - settings->getU16NoEx("mgv7_small_cave_num_max", small_cave_num_max); - settings->getU16NoEx("mgv7_large_cave_num_min", large_cave_num_min); - settings->getU16NoEx("mgv7_large_cave_num_max", large_cave_num_max); - settings->getFloatNoEx("mgv7_large_cave_flooded", large_cave_flooded); - settings->getS16NoEx("mgv7_cavern_limit", cavern_limit); - settings->getS16NoEx("mgv7_cavern_taper", cavern_taper); - settings->getFloatNoEx("mgv7_cavern_threshold", cavern_threshold); - settings->getS16NoEx("mgv7_dungeon_ymin", dungeon_ymin); - settings->getS16NoEx("mgv7_dungeon_ymax", dungeon_ymax); + settings->getFloatNoEx("mgv7_cave_width", cave_width); + settings->getS16NoEx("mgv7_large_cave_depth", large_cave_depth); + settings->getU16NoEx("mgv7_small_cave_num_min", small_cave_num_min); + settings->getU16NoEx("mgv7_small_cave_num_max", small_cave_num_max); + settings->getU16NoEx("mgv7_large_cave_num_min", large_cave_num_min); + settings->getU16NoEx("mgv7_large_cave_num_max", large_cave_num_max); + settings->getFloatNoEx("mgv7_large_cave_flooded", large_cave_flooded); + settings->getS16NoEx("mgv7_cavern_limit", cavern_limit); + settings->getS16NoEx("mgv7_cavern_taper", cavern_taper); + settings->getFloatNoEx("mgv7_cavern_threshold", cavern_threshold); + settings->getS16NoEx("mgv7_dungeon_ymin", dungeon_ymin); + settings->getS16NoEx("mgv7_dungeon_ymax", dungeon_ymax); - settings->getNoiseParams("mgv7_np_terrain_base", np_terrain_base); - settings->getNoiseParams("mgv7_np_terrain_alt", np_terrain_alt); + settings->getNoiseParams("mgv7_np_terrain_base", np_terrain_base); + settings->getNoiseParams("mgv7_np_terrain_alt", np_terrain_alt); settings->getNoiseParams("mgv7_np_terrain_persist", np_terrain_persist); - settings->getNoiseParams("mgv7_np_height_select", np_height_select); - settings->getNoiseParams("mgv7_np_filler_depth", np_filler_depth); - settings->getNoiseParams("mgv7_np_mount_height", np_mount_height); - settings->getNoiseParams("mgv7_np_ridge_uwater", np_ridge_uwater); - settings->getNoiseParams("mgv7_np_mountain", np_mountain); - settings->getNoiseParams("mgv7_np_ridge", np_ridge); - settings->getNoiseParams("mgv7_np_floatland", np_floatland); - settings->getNoiseParams("mgv7_np_cavern", np_cavern); - settings->getNoiseParams("mgv7_np_cave1", np_cave1); - settings->getNoiseParams("mgv7_np_cave2", np_cave2); - settings->getNoiseParams("mgv7_np_dungeons", np_dungeons); + settings->getNoiseParams("mgv7_np_height_select", np_height_select); + settings->getNoiseParams("mgv7_np_filler_depth", np_filler_depth); + settings->getNoiseParams("mgv7_np_mount_height", np_mount_height); + settings->getNoiseParams("mgv7_np_ridge_uwater", np_ridge_uwater); + settings->getNoiseParams("mgv7_np_mountain", np_mountain); + settings->getNoiseParams("mgv7_np_ridge", np_ridge); + settings->getNoiseParams("mgv7_np_floatland", np_floatland); + settings->getNoiseParams("mgv7_np_cavern", np_cavern); + settings->getNoiseParams("mgv7_np_cave1", np_cave1); + settings->getNoiseParams("mgv7_np_cave2", np_cave2); + settings->getNoiseParams("mgv7_np_dungeons", np_dungeons); } - void MapgenV7Params::writeParams(Settings *settings) const { settings->setFlagStr("mgv7_spflags", spflags, flagdesc_mapgen_v7); - settings->setS16("mgv7_mount_zero_level", mount_zero_level); - settings->setS16("mgv7_floatland_ymin", floatland_ymin); - settings->setS16("mgv7_floatland_ymax", floatland_ymax); - settings->setS16("mgv7_floatland_taper", floatland_taper); - settings->setFloat("mgv7_float_taper_exp", float_taper_exp); - settings->setFloat("mgv7_floatland_density", floatland_density); - settings->setS16("mgv7_floatland_ywater", floatland_ywater); + settings->setS16("mgv7_mount_zero_level", mount_zero_level); + settings->setS16("mgv7_floatland_ymin", floatland_ymin); + settings->setS16("mgv7_floatland_ymax", floatland_ymax); + settings->setS16("mgv7_floatland_taper", floatland_taper); + settings->setFloat("mgv7_float_taper_exp", float_taper_exp); + settings->setFloat("mgv7_floatland_density", floatland_density); + settings->setS16("mgv7_floatland_ywater", floatland_ywater); - settings->setFloat("mgv7_cave_width", cave_width); - settings->setS16("mgv7_large_cave_depth", large_cave_depth); - settings->setU16("mgv7_small_cave_num_min", small_cave_num_min); - settings->setU16("mgv7_small_cave_num_max", small_cave_num_max); - settings->setU16("mgv7_large_cave_num_min", large_cave_num_min); - settings->setU16("mgv7_large_cave_num_max", large_cave_num_max); - settings->setFloat("mgv7_large_cave_flooded", large_cave_flooded); - settings->setS16("mgv7_cavern_limit", cavern_limit); - settings->setS16("mgv7_cavern_taper", cavern_taper); - settings->setFloat("mgv7_cavern_threshold", cavern_threshold); - settings->setS16("mgv7_dungeon_ymin", dungeon_ymin); - settings->setS16("mgv7_dungeon_ymax", dungeon_ymax); + settings->setFloat("mgv7_cave_width", cave_width); + settings->setS16("mgv7_large_cave_depth", large_cave_depth); + settings->setU16("mgv7_small_cave_num_min", small_cave_num_min); + settings->setU16("mgv7_small_cave_num_max", small_cave_num_max); + settings->setU16("mgv7_large_cave_num_min", large_cave_num_min); + settings->setU16("mgv7_large_cave_num_max", large_cave_num_max); + settings->setFloat("mgv7_large_cave_flooded", large_cave_flooded); + settings->setS16("mgv7_cavern_limit", cavern_limit); + settings->setS16("mgv7_cavern_taper", cavern_taper); + settings->setFloat("mgv7_cavern_threshold", cavern_threshold); + settings->setS16("mgv7_dungeon_ymin", dungeon_ymin); + settings->setS16("mgv7_dungeon_ymax", dungeon_ymax); - settings->setNoiseParams("mgv7_np_terrain_base", np_terrain_base); - settings->setNoiseParams("mgv7_np_terrain_alt", np_terrain_alt); + settings->setNoiseParams("mgv7_np_terrain_base", np_terrain_base); + settings->setNoiseParams("mgv7_np_terrain_alt", np_terrain_alt); settings->setNoiseParams("mgv7_np_terrain_persist", np_terrain_persist); - settings->setNoiseParams("mgv7_np_height_select", np_height_select); - settings->setNoiseParams("mgv7_np_filler_depth", np_filler_depth); - settings->setNoiseParams("mgv7_np_mount_height", np_mount_height); - settings->setNoiseParams("mgv7_np_ridge_uwater", np_ridge_uwater); - settings->setNoiseParams("mgv7_np_mountain", np_mountain); - settings->setNoiseParams("mgv7_np_ridge", np_ridge); - settings->setNoiseParams("mgv7_np_floatland", np_floatland); - settings->setNoiseParams("mgv7_np_cavern", np_cavern); - settings->setNoiseParams("mgv7_np_cave1", np_cave1); - settings->setNoiseParams("mgv7_np_cave2", np_cave2); - settings->setNoiseParams("mgv7_np_dungeons", np_dungeons); + settings->setNoiseParams("mgv7_np_height_select", np_height_select); + settings->setNoiseParams("mgv7_np_filler_depth", np_filler_depth); + settings->setNoiseParams("mgv7_np_mount_height", np_mount_height); + settings->setNoiseParams("mgv7_np_ridge_uwater", np_ridge_uwater); + settings->setNoiseParams("mgv7_np_mountain", np_mountain); + settings->setNoiseParams("mgv7_np_ridge", np_ridge); + settings->setNoiseParams("mgv7_np_floatland", np_floatland); + settings->setNoiseParams("mgv7_np_cavern", np_cavern); + settings->setNoiseParams("mgv7_np_cave1", np_cave1); + settings->setNoiseParams("mgv7_np_cave2", np_cave2); + settings->setNoiseParams("mgv7_np_dungeons", np_dungeons); } - void MapgenV7Params::setDefaultSettings(Settings *settings) { settings->setDefault("mgv7_spflags", flagdesc_mapgen_v7, - MGV7_MOUNTAINS | MGV7_RIDGES | MGV7_CAVERNS); + MGV7_MOUNTAINS | MGV7_RIDGES | MGV7_CAVERNS); } - //////////////////////////////////////////////////////////////////////////////// - int MapgenV7::getSpawnLevelAtPoint(v2s16 p) { // If rivers are enabled, first check if in a river if (spflags & MGV7_RIDGES) { float width = 0.2f; float uwatern = NoisePerlin2D(&noise_ridge_uwater->np, p.X, p.Y, seed) * - 2.0f; + 2.0f; if (std::fabs(uwatern) <= width) return MAX_MAP_GENERATION_LIMIT; // Unsuitable spawn point } @@ -278,7 +259,7 @@ int MapgenV7::getSpawnLevelAtPoint(v2s16 p) // Raising the maximum spawn level above 'water_level + 16' is necessary // for when terrain 'offset's are set much higher than water_level. s16 max_spawn_y = std::fmax(std::fmax(noise_terrain_alt->np.offset, - noise_terrain_base->np.offset), + noise_terrain_base->np.offset), water_level + 16); // Base terrain calculation s16 y = baseTerrainLevelAtPoint(p.X, p.Y); @@ -311,20 +292,19 @@ int MapgenV7::getSpawnLevelAtPoint(v2s16 p) return MAX_MAP_GENERATION_LIMIT; } - void MapgenV7::makeChunk(BlockMakeData *data) { // Pre-conditions assert(data->vmanip); assert(data->nodedef); assert(data->blockpos_requested.X >= data->blockpos_min.X && - data->blockpos_requested.Y >= data->blockpos_min.Y && - data->blockpos_requested.Z >= data->blockpos_min.Z); + data->blockpos_requested.Y >= data->blockpos_min.Y && + data->blockpos_requested.Z >= data->blockpos_min.Z); assert(data->blockpos_requested.X <= data->blockpos_max.X && - data->blockpos_requested.Y <= data->blockpos_max.Y && - data->blockpos_requested.Z <= data->blockpos_max.Z); + data->blockpos_requested.Y <= data->blockpos_max.Y && + data->blockpos_requested.Z <= data->blockpos_max.Z); - //TimeTaker t("makeChunk"); + // TimeTaker t("makeChunk"); this->generating = true; this->vm = data->vmanip; @@ -370,8 +350,8 @@ void MapgenV7::makeChunk(BlockMakeData *data) // Disable large randomwalk caves in this mapchunk by setting // 'large cave depth' to world base. Avoids excessive liquid in // large caverns and floating blobs of overgenerated liquid. - generateCavesRandomWalk(stone_surface_max_y, - -MAX_MAP_GENERATION_LIMIT); + generateCavesRandomWalk( + stone_surface_max_y, -MAX_MAP_GENERATION_LIMIT); else generateCavesRandomWalk(stone_surface_max_y, large_cave_depth); } @@ -397,21 +377,20 @@ void MapgenV7::makeChunk(BlockMakeData *data) // Calculate lighting // Limit floatland shadows bool propagate_shadow = !((spflags & MGV7_FLOATLANDS) && - node_max.Y >= floatland_ymin - csize.Y * 2 && node_min.Y <= floatland_ymax); + node_max.Y >= floatland_ymin - csize.Y * 2 && + node_min.Y <= floatland_ymax); if (flags & MG_LIGHT) calcLighting(node_min - v3s16(0, 1, 0), node_max + v3s16(0, 1, 0), - full_node_min, full_node_max, propagate_shadow); + full_node_min, full_node_max, propagate_shadow); this->generating = false; - //printf("makeChunk: %lums\n", t.stop()); + // printf("makeChunk: %lums\n", t.stop()); } - //////////////////////////////////////////////////////////////////////////////// - float MapgenV7::baseTerrainLevelAtPoint(s16 x, s16 z) { float hselect = NoisePerlin2D(&noise_height_select->np, x, z, seed); @@ -431,7 +410,6 @@ float MapgenV7::baseTerrainLevelAtPoint(s16 x, s16 z) return (height_base * hselect) + (height_alt * (1.0f - hselect)); } - float MapgenV7::baseTerrainLevelFromMap(int index) { float hselect = rangelim(noise_height_select->result[index], 0.0f, 1.0f); @@ -444,18 +422,16 @@ float MapgenV7::baseTerrainLevelFromMap(int index) return (height_base * hselect) + (height_alt * (1.0f - hselect)); } - bool MapgenV7::getMountainTerrainAtPoint(s16 x, s16 y, s16 z) { - float mnt_h_n = - std::fmax(NoisePerlin2D(&noise_mount_height->np, x, z, seed), 1.0f); + float mnt_h_n = std::fmax( + NoisePerlin2D(&noise_mount_height->np, x, z, seed), 1.0f); float density_gradient = -((float)(y - mount_zero_level) / mnt_h_n); float mnt_n = NoisePerlin3D(&noise_mountain->np, x, y, z, seed); return mnt_n + density_gradient >= 0.0f; } - bool MapgenV7::getMountainTerrainFromMap(int idx_xyz, int idx_xz, s16 y) { float mounthn = std::fmax(noise_mount_height->result[idx_xz], 1.0f); @@ -465,13 +441,12 @@ bool MapgenV7::getMountainTerrainFromMap(int idx_xyz, int idx_xz, s16 y) return mountn + density_gradient >= 0.0f; } - bool MapgenV7::getFloatlandTerrainFromMap(int idx_xyz, float float_offset) { - return noise_floatland->result[idx_xyz] + floatland_density - float_offset >= 0.0f; + return noise_floatland->result[idx_xyz] + floatland_density - float_offset >= + 0.0f; } - int MapgenV7::generateTerrain() { MapNode n_air(CONTENT_AIR); @@ -500,8 +475,8 @@ int MapgenV7::generateTerrain() s16 float_taper_ymax = floatland_ymax - floatland_taper; s16 float_taper_ymin = floatland_ymin + floatland_taper; - if ((spflags & MGV7_FLOATLANDS) && - node_max.Y >= floatland_ymin && node_min.Y <= floatland_ymax) { + if ((spflags & MGV7_FLOATLANDS) && node_max.Y >= floatland_ymin && + node_min.Y <= floatland_ymax) { gen_floatlands = true; // Calculate noise for floatland generation noise_floatland->perlinMap3D(node_min.X, node_min.Y - 1, node_min.Z); @@ -510,11 +485,15 @@ int MapgenV7::generateTerrain() for (s16 y = node_min.Y - 1; y <= node_max.Y + 1; y++, cache_index++) { float float_offset = 0.0f; if (y > float_taper_ymax) { - float_offset = std::pow((y - float_taper_ymax) / (float)floatland_taper, - float_taper_exp) * 4.0f; + float_offset = std::pow((y - float_taper_ymax) / + (float)floatland_taper, + float_taper_exp) * + 4.0f; } else if (y < float_taper_ymin) { - float_offset = std::pow((float_taper_ymin - y) / (float)floatland_taper, - float_taper_exp) * 4.0f; + float_offset = std::pow((float_taper_ymin - + y) / (float)floatland_taper, + float_taper_exp) * + 4.0f; } float_offset_cache[cache_index] = float_offset; } @@ -526,50 +505,51 @@ int MapgenV7::generateTerrain() u32 index2d = 0; for (s16 z = node_min.Z; z <= node_max.Z; z++) - for (s16 x = node_min.X; x <= node_max.X; x++, index2d++) { - s16 surface_y = baseTerrainLevelFromMap(index2d); - if (surface_y > stone_surface_max_y) - stone_surface_max_y = surface_y; + for (s16 x = node_min.X; x <= node_max.X; x++, index2d++) { + s16 surface_y = baseTerrainLevelFromMap(index2d); + if (surface_y > stone_surface_max_y) + stone_surface_max_y = surface_y; - cache_index = 0; - u32 vi = vm->m_area.index(x, node_min.Y - 1, z); - u32 index3d = (z - node_min.Z) * zstride_1u1d + (x - node_min.X); + cache_index = 0; + u32 vi = vm->m_area.index(x, node_min.Y - 1, z); + u32 index3d = (z - node_min.Z) * zstride_1u1d + (x - node_min.X); - for (s16 y = node_min.Y - 1; y <= node_max.Y + 1; - y++, - index3d += ystride, - VoxelArea::add_y(em, vi, 1), - cache_index++) { - if (vm->m_data[vi].getContent() != CONTENT_IGNORE) - continue; + for (s16 y = node_min.Y - 1; y <= node_max.Y + 1; y++, + index3d += ystride, VoxelArea::add_y(em, vi, 1), + cache_index++) { + if (vm->m_data[vi].getContent() != CONTENT_IGNORE) + continue; - if (y <= surface_y) { - vm->m_data[vi] = n_stone; // Base terrain - } else if ((spflags & MGV7_MOUNTAINS) && - getMountainTerrainFromMap(index3d, index2d, y)) { - vm->m_data[vi] = n_stone; // Mountain terrain - if (y > stone_surface_max_y) - stone_surface_max_y = y; - } else if (gen_floatlands && - getFloatlandTerrainFromMap(index3d, - float_offset_cache[cache_index])) { - vm->m_data[vi] = n_stone; // Floatland terrain - if (y > stone_surface_max_y) - stone_surface_max_y = y; - } else if (y <= water_level) { // Surface water - vm->m_data[vi] = n_water; - } else if (gen_floatlands && y >= float_taper_ymax && y <= floatland_ywater) { - vm->m_data[vi] = n_water; // Water for solid floatland layer only - } else { - vm->m_data[vi] = n_air; // Air + if (y <= surface_y) { + vm->m_data[vi] = n_stone; // Base terrain + } else if ((spflags & MGV7_MOUNTAINS) && + getMountainTerrainFromMap( + index3d, index2d, y)) { + vm->m_data[vi] = n_stone; // Mountain terrain + if (y > stone_surface_max_y) + stone_surface_max_y = y; + } else if (gen_floatlands && + getFloatlandTerrainFromMap(index3d, + float_offset_cache + [cache_index])) { + vm->m_data[vi] = n_stone; // Floatland terrain + if (y > stone_surface_max_y) + stone_surface_max_y = y; + } else if (y <= water_level) { // Surface water + vm->m_data[vi] = n_water; + } else if (gen_floatlands && y >= float_taper_ymax && + y <= floatland_ywater) { + vm->m_data[vi] = n_water; // Water for solid + // floatland layer only + } else { + vm->m_data[vi] = n_air; // Air + } } } - } return stone_surface_max_y; } - void MapgenV7::generateRidgeTerrain() { if (node_max.Y < water_level - 16 || @@ -585,28 +565,31 @@ void MapgenV7::generateRidgeTerrain() float width = 0.2f; for (s16 z = node_min.Z; z <= node_max.Z; z++) - for (s16 y = node_min.Y - 1; y <= node_max.Y + 1; y++) { - u32 vi = vm->m_area.index(node_min.X, y, z); - for (s16 x = node_min.X; x <= node_max.X; x++, index3d++, vi++) { - u32 index2d = (z - node_min.Z) * csize.X + (x - node_min.X); - float uwatern = noise_ridge_uwater->result[index2d] * 2.0f; - if (std::fabs(uwatern) > width) - continue; - // Optimises, but also avoids removing nodes placed by mods in - // 'on-generated', when generating outside mapchunk. - content_t c = vm->m_data[vi].getContent(); - if (c != c_stone) - continue; + for (s16 y = node_min.Y - 1; y <= node_max.Y + 1; y++) { + u32 vi = vm->m_area.index(node_min.X, y, z); + for (s16 x = node_min.X; x <= node_max.X; x++, index3d++, vi++) { + u32 index2d = (z - node_min.Z) * csize.X + + (x - node_min.X); + float uwatern = noise_ridge_uwater->result[index2d] * + 2.0f; + if (std::fabs(uwatern) > width) + continue; + // Optimises, but also avoids removing nodes placed by + // mods in 'on-generated', when generating outside + // mapchunk. + content_t c = vm->m_data[vi].getContent(); + if (c != c_stone) + continue; - float altitude = y - water_level; - float height_mod = (altitude + 17.0f) / 2.5f; - float width_mod = width - std::fabs(uwatern); - float nridge = noise_ridge->result[index3d] * - std::fmax(altitude, 0.0f) / 7.0f; - if (nridge + width_mod * height_mod < 0.6f) - continue; + float altitude = y - water_level; + float height_mod = (altitude + 17.0f) / 2.5f; + float width_mod = width - std::fabs(uwatern); + float nridge = noise_ridge->result[index3d] * + std::fmax(altitude, 0.0f) / 7.0f; + if (nridge + width_mod * height_mod < 0.6f) + continue; - vm->m_data[vi] = (y > water_level) ? n_air : n_water; + vm->m_data[vi] = (y > water_level) ? n_air : n_water; + } } - } } diff --git a/src/mapgen/mapgen_v7.h b/src/mapgen/mapgen_v7.h index 4020cd935..b51ff10ce 100644 --- a/src/mapgen/mapgen_v7.h +++ b/src/mapgen/mapgen_v7.h @@ -23,18 +23,18 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "mapgen.h" ///////////// Mapgen V7 flags -#define MGV7_MOUNTAINS 0x01 -#define MGV7_RIDGES 0x02 -#define MGV7_FLOATLANDS 0x04 -#define MGV7_CAVERNS 0x08 +#define MGV7_MOUNTAINS 0x01 +#define MGV7_RIDGES 0x02 +#define MGV7_FLOATLANDS 0x04 +#define MGV7_CAVERNS 0x08 #define MGV7_BIOMEREPEAT 0x10 // Now unused class BiomeManager; extern FlagDesc flagdesc_mapgen_v7[]; - -struct MapgenV7Params : public MapgenParams { +struct MapgenV7Params : public MapgenParams +{ s16 mount_zero_level = 0; s16 floatland_ymin = 1024; s16 floatland_ymax = 4096; @@ -79,8 +79,8 @@ struct MapgenV7Params : public MapgenParams { void setDefaultSettings(Settings *settings); }; - -class MapgenV7 : public MapgenBasic { +class MapgenV7 : public MapgenBasic +{ public: MapgenV7(MapgenV7Params *params, EmergeParams *emerge); ~MapgenV7(); diff --git a/src/mapgen/mapgen_valleys.cpp b/src/mapgen/mapgen_valleys.cpp index efcc8ee85..d06b05990 100644 --- a/src/mapgen/mapgen_valleys.cpp +++ b/src/mapgen/mapgen_valleys.cpp @@ -24,7 +24,6 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ - #include "mapgen.h" #include "voxel.h" #include "noise.h" @@ -44,60 +43,57 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "cavegen.h" #include +FlagDesc flagdesc_mapgen_valleys[] = {{"altitude_chill", MGVALLEYS_ALT_CHILL}, + {"humid_rivers", MGVALLEYS_HUMID_RIVERS}, + {"vary_river_depth", MGVALLEYS_VARY_RIVER_DEPTH}, + {"altitude_dry", MGVALLEYS_ALT_DRY}, {NULL, 0}}; -FlagDesc flagdesc_mapgen_valleys[] = { - {"altitude_chill", MGVALLEYS_ALT_CHILL}, - {"humid_rivers", MGVALLEYS_HUMID_RIVERS}, - {"vary_river_depth", MGVALLEYS_VARY_RIVER_DEPTH}, - {"altitude_dry", MGVALLEYS_ALT_DRY}, - {NULL, 0} -}; - - -MapgenValleys::MapgenValleys(MapgenValleysParams *params, EmergeParams *emerge) - : MapgenBasic(MAPGEN_VALLEYS, params, emerge) +MapgenValleys::MapgenValleys(MapgenValleysParams *params, EmergeParams *emerge) : + MapgenBasic(MAPGEN_VALLEYS, params, emerge) { // NOTE: MapgenValleys has a hard dependency on BiomeGenOriginal m_bgen = (BiomeGenOriginal *)biomegen; - spflags = params->spflags; - altitude_chill = params->altitude_chill; - river_depth_bed = params->river_depth + 1.0f; - river_size_factor = params->river_size / 100.0f; + spflags = params->spflags; + altitude_chill = params->altitude_chill; + river_depth_bed = params->river_depth + 1.0f; + river_size_factor = params->river_size / 100.0f; - cave_width = params->cave_width; - large_cave_depth = params->large_cave_depth; + cave_width = params->cave_width; + large_cave_depth = params->large_cave_depth; small_cave_num_min = params->small_cave_num_min; small_cave_num_max = params->small_cave_num_max; large_cave_num_min = params->large_cave_num_min; large_cave_num_max = params->large_cave_num_max; large_cave_flooded = params->large_cave_flooded; - cavern_limit = params->cavern_limit; - cavern_taper = params->cavern_taper; - cavern_threshold = params->cavern_threshold; - dungeon_ymin = params->dungeon_ymin; - dungeon_ymax = params->dungeon_ymax; + cavern_limit = params->cavern_limit; + cavern_taper = params->cavern_taper; + cavern_threshold = params->cavern_threshold; + dungeon_ymin = params->dungeon_ymin; + dungeon_ymax = params->dungeon_ymax; //// 2D Terrain noise - noise_filler_depth = new Noise(¶ms->np_filler_depth, seed, csize.X, csize.Z); - noise_inter_valley_slope = new Noise(¶ms->np_inter_valley_slope, seed, csize.X, csize.Z); - noise_rivers = new Noise(¶ms->np_rivers, seed, csize.X, csize.Z); - noise_terrain_height = new Noise(¶ms->np_terrain_height, seed, csize.X, csize.Z); - noise_valley_depth = new Noise(¶ms->np_valley_depth, seed, csize.X, csize.Z); - noise_valley_profile = new Noise(¶ms->np_valley_profile, seed, csize.X, csize.Z); + noise_filler_depth = new Noise(¶ms->np_filler_depth, seed, csize.X, csize.Z); + noise_inter_valley_slope = + new Noise(¶ms->np_inter_valley_slope, seed, csize.X, csize.Z); + noise_rivers = new Noise(¶ms->np_rivers, seed, csize.X, csize.Z); + noise_terrain_height = + new Noise(¶ms->np_terrain_height, seed, csize.X, csize.Z); + noise_valley_depth = new Noise(¶ms->np_valley_depth, seed, csize.X, csize.Z); + noise_valley_profile = + new Noise(¶ms->np_valley_profile, seed, csize.X, csize.Z); //// 3D Terrain noise // 1-up 1-down overgeneration - noise_inter_valley_fill = new Noise(¶ms->np_inter_valley_fill, - seed, csize.X, csize.Y + 2, csize.Z); + noise_inter_valley_fill = new Noise(¶ms->np_inter_valley_fill, seed, csize.X, + csize.Y + 2, csize.Z); // 1-down overgeneraion - MapgenBasic::np_cave1 = params->np_cave1; - MapgenBasic::np_cave2 = params->np_cave2; - MapgenBasic::np_cavern = params->np_cavern; + MapgenBasic::np_cave1 = params->np_cave1; + MapgenBasic::np_cave2 = params->np_cave2; + MapgenBasic::np_cavern = params->np_cavern; MapgenBasic::np_dungeons = params->np_dungeons; } - MapgenValleys::~MapgenValleys() { delete noise_filler_depth; @@ -109,115 +105,111 @@ MapgenValleys::~MapgenValleys() delete noise_valley_profile; } - -MapgenValleysParams::MapgenValleysParams(): - np_filler_depth (0.0, 1.2, v3f(256, 256, 256), 1605, 3, 0.5, 2.0), - np_inter_valley_fill (0.0, 1.0, v3f(256, 512, 256), 1993, 6, 0.8, 2.0), - np_inter_valley_slope (0.5, 0.5, v3f(128, 128, 128), 746, 1, 1.0, 2.0), - np_rivers (0.0, 1.0, v3f(256, 256, 256), -6050, 5, 0.6, 2.0), - np_terrain_height (-10.0, 50.0, v3f(1024, 1024, 1024), 5202, 6, 0.4, 2.0), - np_valley_depth (5.0, 4.0, v3f(512, 512, 512), -1914, 1, 1.0, 2.0), - np_valley_profile (0.6, 0.50, v3f(512, 512, 512), 777, 1, 1.0, 2.0), - np_cave1 (0.0, 12.0, v3f(61, 61, 61), 52534, 3, 0.5, 2.0), - np_cave2 (0.0, 12.0, v3f(67, 67, 67), 10325, 3, 0.5, 2.0), - np_cavern (0.0, 1.0, v3f(768, 256, 768), 59033, 6, 0.63, 2.0), - np_dungeons (0.9, 0.5, v3f(500, 500, 500), 0, 2, 0.8, 2.0) +MapgenValleysParams::MapgenValleysParams() : + np_filler_depth(0.0, 1.2, v3f(256, 256, 256), 1605, 3, 0.5, 2.0), + np_inter_valley_fill(0.0, 1.0, v3f(256, 512, 256), 1993, 6, 0.8, 2.0), + np_inter_valley_slope(0.5, 0.5, v3f(128, 128, 128), 746, 1, 1.0, 2.0), + np_rivers(0.0, 1.0, v3f(256, 256, 256), -6050, 5, 0.6, 2.0), + np_terrain_height(-10.0, 50.0, v3f(1024, 1024, 1024), 5202, 6, 0.4, 2.0), + np_valley_depth(5.0, 4.0, v3f(512, 512, 512), -1914, 1, 1.0, 2.0), + np_valley_profile(0.6, 0.50, v3f(512, 512, 512), 777, 1, 1.0, 2.0), + np_cave1(0.0, 12.0, v3f(61, 61, 61), 52534, 3, 0.5, 2.0), + np_cave2(0.0, 12.0, v3f(67, 67, 67), 10325, 3, 0.5, 2.0), + np_cavern(0.0, 1.0, v3f(768, 256, 768), 59033, 6, 0.63, 2.0), + np_dungeons(0.9, 0.5, v3f(500, 500, 500), 0, 2, 0.8, 2.0) { } - void MapgenValleysParams::readParams(const Settings *settings) { settings->getFlagStrNoEx("mgvalleys_spflags", spflags, flagdesc_mapgen_valleys); - settings->getU16NoEx("mgvalleys_altitude_chill", altitude_chill); - settings->getS16NoEx("mgvalleys_large_cave_depth", large_cave_depth); - settings->getU16NoEx("mgvalleys_small_cave_num_min", small_cave_num_min); - settings->getU16NoEx("mgvalleys_small_cave_num_max", small_cave_num_max); - settings->getU16NoEx("mgvalleys_large_cave_num_min", large_cave_num_min); - settings->getU16NoEx("mgvalleys_large_cave_num_max", large_cave_num_max); + settings->getU16NoEx("mgvalleys_altitude_chill", altitude_chill); + settings->getS16NoEx("mgvalleys_large_cave_depth", large_cave_depth); + settings->getU16NoEx("mgvalleys_small_cave_num_min", small_cave_num_min); + settings->getU16NoEx("mgvalleys_small_cave_num_max", small_cave_num_max); + settings->getU16NoEx("mgvalleys_large_cave_num_min", large_cave_num_min); + settings->getU16NoEx("mgvalleys_large_cave_num_max", large_cave_num_max); settings->getFloatNoEx("mgvalleys_large_cave_flooded", large_cave_flooded); - settings->getU16NoEx("mgvalleys_river_depth", river_depth); - settings->getU16NoEx("mgvalleys_river_size", river_size); - settings->getFloatNoEx("mgvalleys_cave_width", cave_width); - settings->getS16NoEx("mgvalleys_cavern_limit", cavern_limit); - settings->getS16NoEx("mgvalleys_cavern_taper", cavern_taper); - settings->getFloatNoEx("mgvalleys_cavern_threshold", cavern_threshold); - settings->getS16NoEx("mgvalleys_dungeon_ymin", dungeon_ymin); - settings->getS16NoEx("mgvalleys_dungeon_ymax", dungeon_ymax); + settings->getU16NoEx("mgvalleys_river_depth", river_depth); + settings->getU16NoEx("mgvalleys_river_size", river_size); + settings->getFloatNoEx("mgvalleys_cave_width", cave_width); + settings->getS16NoEx("mgvalleys_cavern_limit", cavern_limit); + settings->getS16NoEx("mgvalleys_cavern_taper", cavern_taper); + settings->getFloatNoEx("mgvalleys_cavern_threshold", cavern_threshold); + settings->getS16NoEx("mgvalleys_dungeon_ymin", dungeon_ymin); + settings->getS16NoEx("mgvalleys_dungeon_ymax", dungeon_ymax); - settings->getNoiseParams("mgvalleys_np_filler_depth", np_filler_depth); - settings->getNoiseParams("mgvalleys_np_inter_valley_fill", np_inter_valley_fill); - settings->getNoiseParams("mgvalleys_np_inter_valley_slope", np_inter_valley_slope); - settings->getNoiseParams("mgvalleys_np_rivers", np_rivers); - settings->getNoiseParams("mgvalleys_np_terrain_height", np_terrain_height); - settings->getNoiseParams("mgvalleys_np_valley_depth", np_valley_depth); - settings->getNoiseParams("mgvalleys_np_valley_profile", np_valley_profile); + settings->getNoiseParams("mgvalleys_np_filler_depth", np_filler_depth); + settings->getNoiseParams("mgvalleys_np_inter_valley_fill", np_inter_valley_fill); + settings->getNoiseParams( + "mgvalleys_np_inter_valley_slope", np_inter_valley_slope); + settings->getNoiseParams("mgvalleys_np_rivers", np_rivers); + settings->getNoiseParams("mgvalleys_np_terrain_height", np_terrain_height); + settings->getNoiseParams("mgvalleys_np_valley_depth", np_valley_depth); + settings->getNoiseParams("mgvalleys_np_valley_profile", np_valley_profile); - settings->getNoiseParams("mgvalleys_np_cave1", np_cave1); - settings->getNoiseParams("mgvalleys_np_cave2", np_cave2); - settings->getNoiseParams("mgvalleys_np_cavern", np_cavern); - settings->getNoiseParams("mgvalleys_np_dungeons", np_dungeons); + settings->getNoiseParams("mgvalleys_np_cave1", np_cave1); + settings->getNoiseParams("mgvalleys_np_cave2", np_cave2); + settings->getNoiseParams("mgvalleys_np_cavern", np_cavern); + settings->getNoiseParams("mgvalleys_np_dungeons", np_dungeons); } - void MapgenValleysParams::writeParams(Settings *settings) const { settings->setFlagStr("mgvalleys_spflags", spflags, flagdesc_mapgen_valleys); - settings->setU16("mgvalleys_altitude_chill", altitude_chill); - settings->setS16("mgvalleys_large_cave_depth", large_cave_depth); - settings->setU16("mgvalleys_small_cave_num_min", small_cave_num_min); - settings->setU16("mgvalleys_small_cave_num_max", small_cave_num_max); - settings->setU16("mgvalleys_large_cave_num_min", large_cave_num_min); - settings->setU16("mgvalleys_large_cave_num_max", large_cave_num_max); + settings->setU16("mgvalleys_altitude_chill", altitude_chill); + settings->setS16("mgvalleys_large_cave_depth", large_cave_depth); + settings->setU16("mgvalleys_small_cave_num_min", small_cave_num_min); + settings->setU16("mgvalleys_small_cave_num_max", small_cave_num_max); + settings->setU16("mgvalleys_large_cave_num_min", large_cave_num_min); + settings->setU16("mgvalleys_large_cave_num_max", large_cave_num_max); settings->setFloat("mgvalleys_large_cave_flooded", large_cave_flooded); - settings->setU16("mgvalleys_river_depth", river_depth); - settings->setU16("mgvalleys_river_size", river_size); - settings->setFloat("mgvalleys_cave_width", cave_width); - settings->setS16("mgvalleys_cavern_limit", cavern_limit); - settings->setS16("mgvalleys_cavern_taper", cavern_taper); - settings->setFloat("mgvalleys_cavern_threshold", cavern_threshold); - settings->setS16("mgvalleys_dungeon_ymin", dungeon_ymin); - settings->setS16("mgvalleys_dungeon_ymax", dungeon_ymax); + settings->setU16("mgvalleys_river_depth", river_depth); + settings->setU16("mgvalleys_river_size", river_size); + settings->setFloat("mgvalleys_cave_width", cave_width); + settings->setS16("mgvalleys_cavern_limit", cavern_limit); + settings->setS16("mgvalleys_cavern_taper", cavern_taper); + settings->setFloat("mgvalleys_cavern_threshold", cavern_threshold); + settings->setS16("mgvalleys_dungeon_ymin", dungeon_ymin); + settings->setS16("mgvalleys_dungeon_ymax", dungeon_ymax); - settings->setNoiseParams("mgvalleys_np_filler_depth", np_filler_depth); - settings->setNoiseParams("mgvalleys_np_inter_valley_fill", np_inter_valley_fill); - settings->setNoiseParams("mgvalleys_np_inter_valley_slope", np_inter_valley_slope); - settings->setNoiseParams("mgvalleys_np_rivers", np_rivers); - settings->setNoiseParams("mgvalleys_np_terrain_height", np_terrain_height); - settings->setNoiseParams("mgvalleys_np_valley_depth", np_valley_depth); - settings->setNoiseParams("mgvalleys_np_valley_profile", np_valley_profile); + settings->setNoiseParams("mgvalleys_np_filler_depth", np_filler_depth); + settings->setNoiseParams("mgvalleys_np_inter_valley_fill", np_inter_valley_fill); + settings->setNoiseParams( + "mgvalleys_np_inter_valley_slope", np_inter_valley_slope); + settings->setNoiseParams("mgvalleys_np_rivers", np_rivers); + settings->setNoiseParams("mgvalleys_np_terrain_height", np_terrain_height); + settings->setNoiseParams("mgvalleys_np_valley_depth", np_valley_depth); + settings->setNoiseParams("mgvalleys_np_valley_profile", np_valley_profile); - settings->setNoiseParams("mgvalleys_np_cave1", np_cave1); - settings->setNoiseParams("mgvalleys_np_cave2", np_cave2); - settings->setNoiseParams("mgvalleys_np_cavern", np_cavern); - settings->setNoiseParams("mgvalleys_np_dungeons", np_dungeons); + settings->setNoiseParams("mgvalleys_np_cave1", np_cave1); + settings->setNoiseParams("mgvalleys_np_cave2", np_cave2); + settings->setNoiseParams("mgvalleys_np_cavern", np_cavern); + settings->setNoiseParams("mgvalleys_np_dungeons", np_dungeons); } - void MapgenValleysParams::setDefaultSettings(Settings *settings) { settings->setDefault("mgvalleys_spflags", flagdesc_mapgen_valleys, - MGVALLEYS_ALT_CHILL | MGVALLEYS_HUMID_RIVERS | - MGVALLEYS_VARY_RIVER_DEPTH | MGVALLEYS_ALT_DRY); + MGVALLEYS_ALT_CHILL | MGVALLEYS_HUMID_RIVERS | + MGVALLEYS_VARY_RIVER_DEPTH | MGVALLEYS_ALT_DRY); } - ///////////////////////////////////////////////////////////////// - void MapgenValleys::makeChunk(BlockMakeData *data) { // Pre-conditions assert(data->vmanip); assert(data->nodedef); assert(data->blockpos_requested.X >= data->blockpos_min.X && - data->blockpos_requested.Y >= data->blockpos_min.Y && - data->blockpos_requested.Z >= data->blockpos_min.Z); + data->blockpos_requested.Y >= data->blockpos_min.Y && + data->blockpos_requested.Z >= data->blockpos_min.Z); assert(data->blockpos_requested.X <= data->blockpos_max.X && - data->blockpos_requested.Y <= data->blockpos_max.Y && - data->blockpos_requested.Z <= data->blockpos_max.Z); + data->blockpos_requested.Y <= data->blockpos_max.Y && + data->blockpos_requested.Z <= data->blockpos_max.Z); - //TimeTaker t("makeChunk"); + // TimeTaker t("makeChunk"); this->generating = true; this->vm = data->vmanip; @@ -261,8 +253,8 @@ void MapgenValleys::makeChunk(BlockMakeData *data) // Disable large randomwalk caves in this mapchunk by setting // 'large cave depth' to world base. Avoids excessive liquid in // large caverns and floating blobs of overgenerated liquid. - generateCavesRandomWalk(stone_surface_max_y, - -MAX_MAP_GENERATION_LIMIT); + generateCavesRandomWalk( + stone_surface_max_y, -MAX_MAP_GENERATION_LIMIT); else generateCavesRandomWalk(stone_surface_max_y, large_cave_depth); } @@ -286,14 +278,13 @@ void MapgenValleys::makeChunk(BlockMakeData *data) if (flags & MG_LIGHT) calcLighting(node_min - v3s16(0, 1, 0), node_max + v3s16(0, 1, 0), - full_node_min, full_node_max); + full_node_min, full_node_max); this->generating = false; - //printf("makeChunk: %lums\n", t.stop()); + // printf("makeChunk: %lums\n", t.stop()); } - int MapgenValleys::getSpawnLevelAtPoint(v2s16 p) { // Check if in a river channel @@ -302,9 +293,9 @@ int MapgenValleys::getSpawnLevelAtPoint(v2s16 p) // Unsuitable spawn point return MAX_MAP_GENERATION_LIMIT; - float n_slope = NoisePerlin2D(&noise_inter_valley_slope->np, p.X, p.Y, seed); + float n_slope = NoisePerlin2D(&noise_inter_valley_slope->np, p.X, p.Y, seed); float n_terrain_height = NoisePerlin2D(&noise_terrain_height->np, p.X, p.Y, seed); - float n_valley = NoisePerlin2D(&noise_valley_depth->np, p.X, p.Y, seed); + float n_valley = NoisePerlin2D(&noise_valley_depth->np, p.X, p.Y, seed); float n_valley_profile = NoisePerlin2D(&noise_valley_profile->np, p.X, p.Y, seed); float valley_d = n_valley * n_valley; @@ -316,23 +307,25 @@ int MapgenValleys::getSpawnLevelAtPoint(v2s16 p) float slope = n_slope * valley_h; float river_y = base - 1.0f; - // Raising the maximum spawn level above 'water_level + 16' is necessary for custom - // parameters that set average terrain level much higher than water_level. + // Raising the maximum spawn level above 'water_level + 16' is necessary for + // custom parameters that set average terrain level much higher than water_level. s16 max_spawn_y = std::fmax( - noise_terrain_height->np.offset + - noise_valley_depth->np.offset * noise_valley_depth->np.offset, - water_level + 16); + noise_terrain_height->np.offset + + noise_valley_depth->np.offset * + noise_valley_depth->np.offset, + water_level + 16); // Starting spawn search at max_spawn_y + 128 ensures 128 nodes of open // space above spawn position. Avoids spawning in possibly sealed voids. for (s16 y = max_spawn_y + 128; y >= water_level; y--) { - float n_fill = NoisePerlin3D(&noise_inter_valley_fill->np, p.X, y, p.Y, seed); + float n_fill = NoisePerlin3D( + &noise_inter_valley_fill->np, p.X, y, p.Y, seed); float surface_delta = (float)y - surface_y; float density = slope * n_fill - surface_delta; - if (density > 0.0f) { // If solid - // Sometimes surface level is below river water level in places that are not - // river channels. + if (density > 0.0f) { // If solid + // Sometimes surface level is below river water level in places + // that are not river channels. if (y < water_level || y > max_spawn_y || y < (s16)river_y) // Unsuitable spawn point return MAX_MAP_GENERATION_LIMIT; @@ -345,7 +338,6 @@ int MapgenValleys::getSpawnLevelAtPoint(v2s16 p) return MAX_MAP_GENERATION_LIMIT; } - int MapgenValleys::generateTerrain() { MapNode n_air(CONTENT_AIR); @@ -366,122 +358,142 @@ int MapgenValleys::generateTerrain() u32 index_2d = 0; for (s16 z = node_min.Z; z <= node_max.Z; z++) - for (s16 x = node_min.X; x <= node_max.X; x++, index_2d++) { - float n_slope = noise_inter_valley_slope->result[index_2d]; - float n_rivers = noise_rivers->result[index_2d]; - float n_terrain_height = noise_terrain_height->result[index_2d]; - float n_valley = noise_valley_depth->result[index_2d]; - float n_valley_profile = noise_valley_profile->result[index_2d]; + for (s16 x = node_min.X; x <= node_max.X; x++, index_2d++) { + float n_slope = noise_inter_valley_slope->result[index_2d]; + float n_rivers = noise_rivers->result[index_2d]; + float n_terrain_height = noise_terrain_height->result[index_2d]; + float n_valley = noise_valley_depth->result[index_2d]; + float n_valley_profile = noise_valley_profile->result[index_2d]; - float valley_d = n_valley * n_valley; - // 'base' represents the level of the river banks - float base = n_terrain_height + valley_d; - // 'river' represents the distance from the river edge - float river = std::fabs(n_rivers) - river_size_factor; - // Use the curve of the function 1-exp(-(x/a)^2) to model valleys. - // 'valley_h' represents the height of the terrain, from the rivers. - float tv = std::fmax(river / n_valley_profile, 0.0f); - float valley_h = valley_d * (1.0f - std::exp(-tv * tv)); - // Approximate height of the terrain - float surface_y = base + valley_h; - float slope = n_slope * valley_h; - // River water surface is 1 node below river banks - float river_y = base - 1.0f; + float valley_d = n_valley * n_valley; + // 'base' represents the level of the river banks + float base = n_terrain_height + valley_d; + // 'river' represents the distance from the river edge + float river = std::fabs(n_rivers) - river_size_factor; + // Use the curve of the function 1-exp(-(x/a)^2) to model valleys. + // 'valley_h' represents the height of the terrain, from the + // rivers. + float tv = std::fmax(river / n_valley_profile, 0.0f); + float valley_h = valley_d * (1.0f - std::exp(-tv * tv)); + // Approximate height of the terrain + float surface_y = base + valley_h; + float slope = n_slope * valley_h; + // River water surface is 1 node below river banks + float river_y = base - 1.0f; - // Rivers are placed where 'river' is negative - if (river < 0.0f) { - // Use the function -sqrt(1-x^2) which models a circle - float tr = river / river_size_factor + 1.0f; - float depth = (river_depth_bed * - std::sqrt(std::fmax(0.0f, 1.0f - tr * tr))); - // There is no logical equivalent to this using rangelim - surface_y = std::fmin( - std::fmax(base - depth, (float)(water_level - 3)), - surface_y); - slope = 0.0f; - } - - // Optionally vary river depth according to heat and humidity - if (spflags & MGVALLEYS_VARY_RIVER_DEPTH) { - float t_heat = m_bgen->heatmap[index_2d]; - float heat = (spflags & MGVALLEYS_ALT_CHILL) ? - // Match heat value calculated below in - // 'Optionally decrease heat with altitude'. - // In rivers, 'ground height ignoring riverbeds' is 'base'. - // As this only affects river water we can assume y > water_level. - t_heat + 5.0f - (base - water_level) * 20.0f / altitude_chill : - t_heat; - float delta = m_bgen->humidmap[index_2d] - 50.0f; - if (delta < 0.0f) { - float t_evap = (heat - 32.0f) / 300.0f; - river_y += delta * std::fmax(t_evap, 0.08f); + // Rivers are placed where 'river' is negative + if (river < 0.0f) { + // Use the function -sqrt(1-x^2) which models a circle + float tr = river / river_size_factor + 1.0f; + float depth = (river_depth_bed * + std::sqrt(std::fmax( + 0.0f, 1.0f - tr * tr))); + // There is no logical equivalent to this using rangelim + surface_y = std::fmin( + std::fmax(base - depth, + (float)(water_level - 3)), + surface_y); + slope = 0.0f; } - } - // Highest solid node in column - s16 column_max_y = surface_y; - u32 index_3d = (z - node_min.Z) * zstride_1u1d + (x - node_min.X); - u32 index_data = vm->m_area.index(x, node_min.Y - 1, z); - - for (s16 y = node_min.Y - 1; y <= node_max.Y + 1; y++) { - if (vm->m_data[index_data].getContent() == CONTENT_IGNORE) { - float n_fill = noise_inter_valley_fill->result[index_3d]; - float surface_delta = (float)y - surface_y; - // Density = density noise + density gradient - float density = slope * n_fill - surface_delta; - - if (density > 0.0f) { - vm->m_data[index_data] = n_stone; // Stone - if (y > surface_max_y) - surface_max_y = y; - if (y > column_max_y) - column_max_y = y; - } else if (y <= water_level) { - vm->m_data[index_data] = n_water; // Water - } else if (y <= (s16)river_y) { - vm->m_data[index_data] = n_river_water; // River water - } else { - vm->m_data[index_data] = n_air; // Air + // Optionally vary river depth according to heat and humidity + if (spflags & MGVALLEYS_VARY_RIVER_DEPTH) { + float t_heat = m_bgen->heatmap[index_2d]; + float heat = (spflags & MGVALLEYS_ALT_CHILL) + ? + // Match heat value + // calculated below in + // 'Optionally decrease heat + // with altitude'. In rivers, + // 'ground height ignoring + // riverbeds' is 'base'. As + // this only affects river + // water we can assume y > + // water_level. + t_heat + 5.0f - + (base - water_level) * + 20.0f / + altitude_chill + : t_heat; + float delta = m_bgen->humidmap[index_2d] - 50.0f; + if (delta < 0.0f) { + float t_evap = (heat - 32.0f) / 300.0f; + river_y += delta * std::fmax(t_evap, 0.08f); } } - VoxelArea::add_y(em, index_data, 1); - index_3d += ystride; - } + // Highest solid node in column + s16 column_max_y = surface_y; + u32 index_3d = (z - node_min.Z) * zstride_1u1d + (x - node_min.X); + u32 index_data = vm->m_area.index(x, node_min.Y - 1, z); - // Optionally increase humidity around rivers - if (spflags & MGVALLEYS_HUMID_RIVERS) { - // Compensate to avoid increasing average humidity - m_bgen->humidmap[index_2d] *= 0.8f; - // Ground height ignoring riverbeds - float t_alt = std::fmax(base, (float)column_max_y); - float water_depth = (t_alt - base) / 4.0f; - m_bgen->humidmap[index_2d] *= - 1.0f + std::pow(0.5f, std::fmax(water_depth, 1.0f)); - } + for (s16 y = node_min.Y - 1; y <= node_max.Y + 1; y++) { + if (vm->m_data[index_data].getContent() == + CONTENT_IGNORE) { + float n_fill = noise_inter_valley_fill + ->result[index_3d]; + float surface_delta = (float)y - surface_y; + // Density = density noise + density gradient + float density = slope * n_fill - surface_delta; - // Optionally decrease humidity with altitude - if (spflags & MGVALLEYS_ALT_DRY) { - // Ground height ignoring riverbeds - float t_alt = std::fmax(base, (float)column_max_y); - // Only decrease above water_level - if (t_alt > water_level) - m_bgen->humidmap[index_2d] -= - (t_alt - water_level) * 10.0f / altitude_chill; - } + if (density > 0.0f) { + vm->m_data[index_data] = n_stone; // Stone + if (y > surface_max_y) + surface_max_y = y; + if (y > column_max_y) + column_max_y = y; + } else if (y <= water_level) { + vm->m_data[index_data] = n_water; // Water + } else if (y <= (s16)river_y) { + vm->m_data[index_data] = + n_river_water; // River + // water + } else { + vm->m_data[index_data] = n_air; // Air + } + } - // Optionally decrease heat with altitude - if (spflags & MGVALLEYS_ALT_CHILL) { - // Compensate to avoid reducing the average heat - m_bgen->heatmap[index_2d] += 5.0f; - // Ground height ignoring riverbeds - float t_alt = std::fmax(base, (float)column_max_y); - // Only decrease above water_level - if (t_alt > water_level) - m_bgen->heatmap[index_2d] -= - (t_alt - water_level) * 20.0f / altitude_chill; + VoxelArea::add_y(em, index_data, 1); + index_3d += ystride; + } + + // Optionally increase humidity around rivers + if (spflags & MGVALLEYS_HUMID_RIVERS) { + // Compensate to avoid increasing average humidity + m_bgen->humidmap[index_2d] *= 0.8f; + // Ground height ignoring riverbeds + float t_alt = std::fmax(base, (float)column_max_y); + float water_depth = (t_alt - base) / 4.0f; + m_bgen->humidmap[index_2d] *= + 1.0f + + std::pow(0.5f, std::fmax(water_depth, + 1.0f)); + } + + // Optionally decrease humidity with altitude + if (spflags & MGVALLEYS_ALT_DRY) { + // Ground height ignoring riverbeds + float t_alt = std::fmax(base, (float)column_max_y); + // Only decrease above water_level + if (t_alt > water_level) + m_bgen->humidmap[index_2d] -= + (t_alt - water_level) * 10.0f / + altitude_chill; + } + + // Optionally decrease heat with altitude + if (spflags & MGVALLEYS_ALT_CHILL) { + // Compensate to avoid reducing the average heat + m_bgen->heatmap[index_2d] += 5.0f; + // Ground height ignoring riverbeds + float t_alt = std::fmax(base, (float)column_max_y); + // Only decrease above water_level + if (t_alt > water_level) + m_bgen->heatmap[index_2d] -= + (t_alt - water_level) * 20.0f / + altitude_chill; + } } - } return surface_max_y; } diff --git a/src/mapgen/mapgen_valleys.h b/src/mapgen/mapgen_valleys.h index 34a923dfa..fb544e51b 100644 --- a/src/mapgen/mapgen_valleys.h +++ b/src/mapgen/mapgen_valleys.h @@ -24,23 +24,22 @@ 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 "mapgen.h" -#define MGVALLEYS_ALT_CHILL 0x01 -#define MGVALLEYS_HUMID_RIVERS 0x02 +#define MGVALLEYS_ALT_CHILL 0x01 +#define MGVALLEYS_HUMID_RIVERS 0x02 #define MGVALLEYS_VARY_RIVER_DEPTH 0x04 -#define MGVALLEYS_ALT_DRY 0x08 +#define MGVALLEYS_ALT_DRY 0x08 class BiomeManager; class BiomeGenOriginal; extern FlagDesc flagdesc_mapgen_valleys[]; - -struct MapgenValleysParams : public MapgenParams { +struct MapgenValleysParams : public MapgenParams +{ u16 altitude_chill = 90; u16 river_depth = 4; u16 river_size = 5; @@ -79,12 +78,10 @@ struct MapgenValleysParams : public MapgenParams { void setDefaultSettings(Settings *settings); }; - -class MapgenValleys : public MapgenBasic { +class MapgenValleys : public MapgenBasic +{ public: - - MapgenValleys(MapgenValleysParams *params, - EmergeParams *emerge); + MapgenValleys(MapgenValleysParams *params, EmergeParams *emerge); ~MapgenValleys(); virtual MapgenType getType() const { return MAPGEN_VALLEYS; } diff --git a/src/mapgen/mg_biome.cpp b/src/mapgen/mg_biome.cpp index 610c38594..ea7c24721 100644 --- a/src/mapgen/mg_biome.cpp +++ b/src/mapgen/mg_biome.cpp @@ -28,31 +28,28 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "porting.h" #include "settings.h" - /////////////////////////////////////////////////////////////////////////////// - -BiomeManager::BiomeManager(Server *server) : - ObjDefManager(server, OBJDEF_BIOME) +BiomeManager::BiomeManager(Server *server) : ObjDefManager(server, OBJDEF_BIOME) { m_server = server; // Create default biome to be used in case none exist Biome *b = new Biome; - b->name = "default"; - b->flags = 0; - b->depth_top = 0; - b->depth_filler = -MAX_MAP_GENERATION_LIMIT; + b->name = "default"; + b->flags = 0; + b->depth_top = 0; + b->depth_filler = -MAX_MAP_GENERATION_LIMIT; b->depth_water_top = 0; - b->depth_riverbed = 0; - b->min_pos = v3s16(-MAX_MAP_GENERATION_LIMIT, - -MAX_MAP_GENERATION_LIMIT, -MAX_MAP_GENERATION_LIMIT); - b->max_pos = v3s16(MAX_MAP_GENERATION_LIMIT, - MAX_MAP_GENERATION_LIMIT, MAX_MAP_GENERATION_LIMIT); - b->heat_point = 0.0; - b->humidity_point = 0.0; - b->vertical_blend = 0; + b->depth_riverbed = 0; + b->min_pos = v3s16(-MAX_MAP_GENERATION_LIMIT, -MAX_MAP_GENERATION_LIMIT, + -MAX_MAP_GENERATION_LIMIT); + b->max_pos = v3s16(MAX_MAP_GENERATION_LIMIT, MAX_MAP_GENERATION_LIMIT, + MAX_MAP_GENERATION_LIMIT); + b->heat_point = 0.0; + b->humidity_point = 0.0; + b->vertical_blend = 0; b->m_nodenames.emplace_back("mapgen_stone"); b->m_nodenames.emplace_back("mapgen_stone"); @@ -72,7 +69,6 @@ BiomeManager::BiomeManager(Server *server) : add(b); } - void BiomeManager::clear() { EmergeManager *emerge = m_server->getEmergeManager(); @@ -91,7 +87,6 @@ void BiomeManager::clear() m_objects.resize(1); } - BiomeManager *BiomeManager::clone() const { auto mgr = new BiomeManager(); @@ -101,30 +96,25 @@ BiomeManager *BiomeManager::clone() const return mgr; } - // For BiomeGen type 'BiomeGenOriginal' float BiomeManager::getHeatAtPosOriginal(v3s16 pos, NoiseParams &np_heat, - NoiseParams &np_heat_blend, u64 seed) const + NoiseParams &np_heat_blend, u64 seed) const { - return - NoisePerlin2D(&np_heat, pos.X, pos.Z, seed) + - NoisePerlin2D(&np_heat_blend, pos.X, pos.Z, seed); + return NoisePerlin2D(&np_heat, pos.X, pos.Z, seed) + + NoisePerlin2D(&np_heat_blend, pos.X, pos.Z, seed); } - // For BiomeGen type 'BiomeGenOriginal' float BiomeManager::getHumidityAtPosOriginal(v3s16 pos, NoiseParams &np_humidity, - NoiseParams &np_humidity_blend, u64 seed) const + NoiseParams &np_humidity_blend, u64 seed) const { - return - NoisePerlin2D(&np_humidity, pos.X, pos.Z, seed) + - NoisePerlin2D(&np_humidity_blend, pos.X, pos.Z, seed); + return NoisePerlin2D(&np_humidity, pos.X, pos.Z, seed) + + NoisePerlin2D(&np_humidity_blend, pos.X, pos.Z, seed); } - // For BiomeGen type 'BiomeGenOriginal' -const Biome *BiomeManager::getBiomeFromNoiseOriginal(float heat, - float humidity, v3s16 pos) const +const Biome *BiomeManager::getBiomeFromNoiseOriginal( + float heat, float humidity, v3s16 pos) const { Biome *biome_closest = nullptr; Biome *biome_closest_blend = nullptr; @@ -133,8 +123,8 @@ const Biome *BiomeManager::getBiomeFromNoiseOriginal(float heat, for (size_t i = 1; i < getNumObjects(); i++) { Biome *b = (Biome *)getRaw(i); - if (!b || - pos.Y < b->min_pos.Y || pos.Y > b->max_pos.Y + b->vertical_blend || + if (!b || pos.Y < b->min_pos.Y || + pos.Y > b->max_pos.Y + b->vertical_blend || pos.X < b->min_pos.X || pos.X > b->max_pos.X || pos.Z < b->min_pos.Z || pos.Z > b->max_pos.Z) continue; @@ -159,52 +149,48 @@ const Biome *BiomeManager::getBiomeFromNoiseOriginal(float heat, if (biome_closest_blend && dist_min_blend <= dist_min && rng.range(0, biome_closest_blend->vertical_blend) >= - pos.Y - biome_closest_blend->max_pos.Y) + pos.Y - biome_closest_blend->max_pos.Y) return biome_closest_blend; return (biome_closest) ? biome_closest : (Biome *)getRaw(BIOME_NONE); } - //////////////////////////////////////////////////////////////////////////////// void BiomeParamsOriginal::readParams(const Settings *settings) { - settings->getNoiseParams("mg_biome_np_heat", np_heat); - settings->getNoiseParams("mg_biome_np_heat_blend", np_heat_blend); - settings->getNoiseParams("mg_biome_np_humidity", np_humidity); + settings->getNoiseParams("mg_biome_np_heat", np_heat); + settings->getNoiseParams("mg_biome_np_heat_blend", np_heat_blend); + settings->getNoiseParams("mg_biome_np_humidity", np_humidity); settings->getNoiseParams("mg_biome_np_humidity_blend", np_humidity_blend); } - void BiomeParamsOriginal::writeParams(Settings *settings) const { - settings->setNoiseParams("mg_biome_np_heat", np_heat); - settings->setNoiseParams("mg_biome_np_heat_blend", np_heat_blend); - settings->setNoiseParams("mg_biome_np_humidity", np_humidity); + settings->setNoiseParams("mg_biome_np_heat", np_heat); + settings->setNoiseParams("mg_biome_np_heat_blend", np_heat_blend); + settings->setNoiseParams("mg_biome_np_humidity", np_humidity); settings->setNoiseParams("mg_biome_np_humidity_blend", np_humidity_blend); } - //////////////////////////////////////////////////////////////////////////////// -BiomeGenOriginal::BiomeGenOriginal(BiomeManager *biomemgr, - BiomeParamsOriginal *params, v3s16 chunksize) +BiomeGenOriginal::BiomeGenOriginal( + BiomeManager *biomemgr, BiomeParamsOriginal *params, v3s16 chunksize) { - m_bmgr = biomemgr; + m_bmgr = biomemgr; m_params = params; - m_csize = chunksize; + m_csize = chunksize; - noise_heat = new Noise(¶ms->np_heat, - params->seed, m_csize.X, m_csize.Z); - noise_humidity = new Noise(¶ms->np_humidity, - params->seed, m_csize.X, m_csize.Z); - noise_heat_blend = new Noise(¶ms->np_heat_blend, - params->seed, m_csize.X, m_csize.Z); - noise_humidity_blend = new Noise(¶ms->np_humidity_blend, - params->seed, m_csize.X, m_csize.Z); + noise_heat = new Noise(¶ms->np_heat, params->seed, m_csize.X, m_csize.Z); + noise_humidity = new Noise( + ¶ms->np_humidity, params->seed, m_csize.X, m_csize.Z); + noise_heat_blend = new Noise( + ¶ms->np_heat_blend, params->seed, m_csize.X, m_csize.Z); + noise_humidity_blend = new Noise( + ¶ms->np_humidity_blend, params->seed, m_csize.X, m_csize.Z); - heatmap = noise_heat->result; + heatmap = noise_heat->result; humidmap = noise_humidity->result; biomemap = new biome_t[m_csize.X * m_csize.Z]; @@ -216,7 +202,7 @@ BiomeGenOriginal::BiomeGenOriginal(BiomeManager *biomemgr, BiomeGenOriginal::~BiomeGenOriginal() { - delete []biomemap; + delete[] biomemap; delete noise_heat; delete noise_humidity; @@ -227,17 +213,17 @@ BiomeGenOriginal::~BiomeGenOriginal() // Only usable in a mapgen thread Biome *BiomeGenOriginal::calcBiomeAtPoint(v3s16 pos) const { - float heat = - NoisePerlin2D(&m_params->np_heat, pos.X, pos.Z, m_params->seed) + - NoisePerlin2D(&m_params->np_heat_blend, pos.X, pos.Z, m_params->seed); - float humidity = - NoisePerlin2D(&m_params->np_humidity, pos.X, pos.Z, m_params->seed) + - NoisePerlin2D(&m_params->np_humidity_blend, pos.X, pos.Z, m_params->seed); + float heat = NoisePerlin2D(&m_params->np_heat, pos.X, pos.Z, m_params->seed) + + NoisePerlin2D(&m_params->np_heat_blend, pos.X, pos.Z, + m_params->seed); + float humidity = NoisePerlin2D(&m_params->np_humidity, pos.X, pos.Z, + m_params->seed) + + NoisePerlin2D(&m_params->np_humidity_blend, pos.X, pos.Z, + m_params->seed); return calcBiomeFromNoise(heat, humidity, pos); } - void BiomeGenOriginal::calcBiomeNoise(v3s16 pmin) { m_pmin = pmin; @@ -248,46 +234,37 @@ void BiomeGenOriginal::calcBiomeNoise(v3s16 pmin) noise_humidity_blend->perlinMap2D(pmin.X, pmin.Z); for (s32 i = 0; i < m_csize.X * m_csize.Z; i++) { - noise_heat->result[i] += noise_heat_blend->result[i]; + noise_heat->result[i] += noise_heat_blend->result[i]; noise_humidity->result[i] += noise_humidity_blend->result[i]; } } - biome_t *BiomeGenOriginal::getBiomes(s16 *heightmap, v3s16 pmin) { for (s16 zr = 0; zr < m_csize.Z; zr++) - for (s16 xr = 0; xr < m_csize.X; xr++) { - s32 i = zr * m_csize.X + xr; - Biome *biome = calcBiomeFromNoise( - noise_heat->result[i], - noise_humidity->result[i], - v3s16(pmin.X + xr, heightmap[i], pmin.Z + zr)); + for (s16 xr = 0; xr < m_csize.X; xr++) { + s32 i = zr * m_csize.X + xr; + Biome *biome = calcBiomeFromNoise(noise_heat->result[i], + noise_humidity->result[i], + v3s16(pmin.X + xr, heightmap[i], pmin.Z + zr)); - biomemap[i] = biome->index; - } + biomemap[i] = biome->index; + } return biomemap; } - Biome *BiomeGenOriginal::getBiomeAtPoint(v3s16 pos) const { - return getBiomeAtIndex( - (pos.Z - m_pmin.Z) * m_csize.X + (pos.X - m_pmin.X), - pos); + return getBiomeAtIndex((pos.Z - m_pmin.Z) * m_csize.X + (pos.X - m_pmin.X), pos); } - Biome *BiomeGenOriginal::getBiomeAtIndex(size_t index, v3s16 pos) const { return calcBiomeFromNoise( - noise_heat->result[index], - noise_humidity->result[index], - pos); + noise_heat->result[index], noise_humidity->result[index], pos); } - Biome *BiomeGenOriginal::calcBiomeFromNoise(float heat, float humidity, v3s16 pos) const { Biome *biome_closest = nullptr; @@ -297,8 +274,8 @@ Biome *BiomeGenOriginal::calcBiomeFromNoise(float heat, float humidity, v3s16 po for (size_t i = 1; i < m_bmgr->getNumObjects(); i++) { Biome *b = (Biome *)m_bmgr->getRaw(i); - if (!b || - pos.Y < b->min_pos.Y || pos.Y > b->max_pos.Y + b->vertical_blend || + if (!b || pos.Y < b->min_pos.Y || + pos.Y > b->max_pos.Y + b->vertical_blend || pos.X < b->min_pos.X || pos.X > b->max_pos.X || pos.Z < b->min_pos.Z || pos.Z > b->max_pos.Z) continue; @@ -326,13 +303,12 @@ Biome *BiomeGenOriginal::calcBiomeFromNoise(float heat, float humidity, v3s16 po if (biome_closest_blend && dist_min_blend <= dist_min && rng.range(0, biome_closest_blend->vertical_blend) >= - pos.Y - biome_closest_blend->max_pos.Y) + pos.Y - biome_closest_blend->max_pos.Y) return biome_closest_blend; - return (biome_closest) ? biome_closest : (Biome *)m_bmgr->getRaw(BIOME_NONE); + return (biome_closest) ? biome_closest : (Biome *)m_bmgr->getRaw(BIOME_NONE); } - //////////////////////////////////////////////////////////////////////////////// ObjDef *Biome::clone() const @@ -372,16 +348,17 @@ ObjDef *Biome::clone() const void Biome::resolveNodeNames() { - getIdFromNrBacklog(&c_top, "mapgen_stone", CONTENT_AIR, false); - getIdFromNrBacklog(&c_filler, "mapgen_stone", CONTENT_AIR, false); - getIdFromNrBacklog(&c_stone, "mapgen_stone", CONTENT_AIR, false); - getIdFromNrBacklog(&c_water_top, "mapgen_water_source", CONTENT_AIR, false); - getIdFromNrBacklog(&c_water, "mapgen_water_source", CONTENT_AIR, false); - getIdFromNrBacklog(&c_river_water, "mapgen_river_water_source", CONTENT_AIR, false); - getIdFromNrBacklog(&c_riverbed, "mapgen_stone", CONTENT_AIR, false); - getIdFromNrBacklog(&c_dust, "ignore", CONTENT_IGNORE, false); + getIdFromNrBacklog(&c_top, "mapgen_stone", CONTENT_AIR, false); + getIdFromNrBacklog(&c_filler, "mapgen_stone", CONTENT_AIR, false); + getIdFromNrBacklog(&c_stone, "mapgen_stone", CONTENT_AIR, false); + getIdFromNrBacklog(&c_water_top, "mapgen_water_source", CONTENT_AIR, false); + getIdFromNrBacklog(&c_water, "mapgen_water_source", CONTENT_AIR, false); + getIdFromNrBacklog( + &c_river_water, "mapgen_river_water_source", CONTENT_AIR, false); + getIdFromNrBacklog(&c_riverbed, "mapgen_stone", CONTENT_AIR, false); + getIdFromNrBacklog(&c_dust, "ignore", CONTENT_IGNORE, false); getIdsFromNrBacklog(&c_cave_liquid); - getIdFromNrBacklog(&c_dungeon, "ignore", CONTENT_IGNORE, false); - getIdFromNrBacklog(&c_dungeon_alt, "ignore", CONTENT_IGNORE, false); - getIdFromNrBacklog(&c_dungeon_stair, "ignore", CONTENT_IGNORE, false); + getIdFromNrBacklog(&c_dungeon, "ignore", CONTENT_IGNORE, false); + getIdFromNrBacklog(&c_dungeon_alt, "ignore", CONTENT_IGNORE, false); + getIdFromNrBacklog(&c_dungeon_stair, "ignore", CONTENT_IGNORE, false); } diff --git a/src/mapgen/mg_biome.h b/src/mapgen/mg_biome.h index be4cfea4d..ba3319976 100644 --- a/src/mapgen/mg_biome.h +++ b/src/mapgen/mg_biome.h @@ -36,11 +36,13 @@ typedef u16 biome_t; #define BIOME_NONE ((biome_t)0) -enum BiomeType { +enum BiomeType +{ BIOMETYPE_NORMAL, }; -class Biome : public ObjDef, public NodeResolver { +class Biome : public ObjDef, public NodeResolver +{ public: ObjDef *clone() const; @@ -73,16 +75,17 @@ public: virtual void resolveNodeNames(); }; - //// //// BiomeGen //// -enum BiomeGenType { +enum BiomeGenType +{ BIOMEGEN_ORIGINAL, }; -struct BiomeParams { +struct BiomeParams +{ virtual void readParams(const Settings *settings) = 0; virtual void writeParams(Settings *settings) const = 0; virtual ~BiomeParams() = default; @@ -91,7 +94,8 @@ struct BiomeParams { }; // WARNING: this class is not thread-safe -class BiomeGen { +class BiomeGen +{ public: virtual ~BiomeGen() = default; @@ -128,7 +132,6 @@ protected: v3s16 m_csize; }; - //// //// BiomeGen implementations //// @@ -137,12 +140,14 @@ protected: // Original biome algorithm (Whittaker's classification + surface height) // -struct BiomeParamsOriginal : public BiomeParams { +struct BiomeParamsOriginal : public BiomeParams +{ BiomeParamsOriginal() : - np_heat(50, 50, v3f(1000.0, 1000.0, 1000.0), 5349, 3, 0.5, 2.0), - np_humidity(50, 50, v3f(1000.0, 1000.0, 1000.0), 842, 3, 0.5, 2.0), - np_heat_blend(0, 1.5, v3f(8.0, 8.0, 8.0), 13, 2, 1.0, 2.0), - np_humidity_blend(0, 1.5, v3f(8.0, 8.0, 8.0), 90003, 2, 1.0, 2.0) + np_heat(50, 50, v3f(1000.0, 1000.0, 1000.0), 5349, 3, 0.5, 2.0), + np_humidity(50, 50, v3f(1000.0, 1000.0, 1000.0), 842, 3, 0.5, + 2.0), + np_heat_blend(0, 1.5, v3f(8.0, 8.0, 8.0), 13, 2, 1.0, 2.0), + np_humidity_blend(0, 1.5, v3f(8.0, 8.0, 8.0), 90003, 2, 1.0, 2.0) { } @@ -155,10 +160,11 @@ struct BiomeParamsOriginal : public BiomeParams { NoiseParams np_humidity_blend; }; -class BiomeGenOriginal : public BiomeGen { +class BiomeGenOriginal : public BiomeGen +{ public: - BiomeGenOriginal(BiomeManager *biomemgr, - BiomeParamsOriginal *params, v3s16 chunksize); + BiomeGenOriginal(BiomeManager *biomemgr, BiomeParamsOriginal *params, + v3s16 chunksize); virtual ~BiomeGenOriginal(); BiomeGenType getType() const { return BIOMEGEN_ORIGINAL; } @@ -184,34 +190,28 @@ private: Noise *noise_humidity_blend; }; - //// //// BiomeManager //// -class BiomeManager : public ObjDefManager { +class BiomeManager : public ObjDefManager +{ public: BiomeManager(Server *server); virtual ~BiomeManager() = default; BiomeManager *clone() const; - const char *getObjectTitle() const - { - return "biome"; - } + const char *getObjectTitle() const { return "biome"; } - static Biome *create(BiomeType type) - { - return new Biome; - } + static Biome *create(BiomeType type) { return new Biome; } BiomeGen *createBiomeGen(BiomeGenType type, BiomeParams *params, v3s16 chunksize) { switch (type) { case BIOMEGEN_ORIGINAL: - return new BiomeGenOriginal(this, - (BiomeParamsOriginal *)params, chunksize); + return new BiomeGenOriginal( + this, (BiomeParamsOriginal *)params, chunksize); default: return NULL; } @@ -231,15 +231,14 @@ public: // For BiomeGen type 'BiomeGenOriginal' float getHeatAtPosOriginal(v3s16 pos, NoiseParams &np_heat, - NoiseParams &np_heat_blend, u64 seed) const; + NoiseParams &np_heat_blend, u64 seed) const; float getHumidityAtPosOriginal(v3s16 pos, NoiseParams &np_humidity, - NoiseParams &np_humidity_blend, u64 seed) const; - const Biome *getBiomeFromNoiseOriginal(float heat, float humidity, - v3s16 pos) const; + NoiseParams &np_humidity_blend, u64 seed) const; + const Biome *getBiomeFromNoiseOriginal( + float heat, float humidity, v3s16 pos) const; private: - BiomeManager() {}; + BiomeManager(){}; Server *m_server; - }; diff --git a/src/mapgen/mg_decoration.cpp b/src/mapgen/mg_decoration.cpp index a4cada396..6aa003a3f 100644 --- a/src/mapgen/mg_decoration.cpp +++ b/src/mapgen/mg_decoration.cpp @@ -28,30 +28,21 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include - -FlagDesc flagdesc_deco[] = { - {"place_center_x", DECO_PLACE_CENTER_X}, - {"place_center_y", DECO_PLACE_CENTER_Y}, - {"place_center_z", DECO_PLACE_CENTER_Z}, - {"force_placement", DECO_FORCE_PLACEMENT}, - {"liquid_surface", DECO_LIQUID_SURFACE}, - {"all_floors", DECO_ALL_FLOORS}, - {"all_ceilings", DECO_ALL_CEILINGS}, - {NULL, 0} -}; - +FlagDesc flagdesc_deco[] = {{"place_center_x", DECO_PLACE_CENTER_X}, + {"place_center_y", DECO_PLACE_CENTER_Y}, + {"place_center_z", DECO_PLACE_CENTER_Z}, + {"force_placement", DECO_FORCE_PLACEMENT}, + {"liquid_surface", DECO_LIQUID_SURFACE}, {"all_floors", DECO_ALL_FLOORS}, + {"all_ceilings", DECO_ALL_CEILINGS}, {NULL, 0}}; /////////////////////////////////////////////////////////////////////////////// - DecorationManager::DecorationManager(IGameDef *gamedef) : - ObjDefManager(gamedef, OBJDEF_DECORATION) + ObjDefManager(gamedef, OBJDEF_DECORATION) { } - -size_t DecorationManager::placeAllDecos(Mapgen *mg, u32 blockseed, - v3s16 nmin, v3s16 nmax) +size_t DecorationManager::placeAllDecos(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax) { size_t nplaced = 0; @@ -74,17 +65,14 @@ DecorationManager *DecorationManager::clone() const return mgr; } - /////////////////////////////////////////////////////////////////////////////// - void Decoration::resolveNodeNames() { getIdsFromNrBacklog(&c_place_on); getIdsFromNrBacklog(&c_spawnby); } - bool Decoration::canPlaceDecoration(MMVManip *vm, v3s16 p) { // Check if the decoration can be placed on this node @@ -97,25 +85,13 @@ bool Decoration::canPlaceDecoration(MMVManip *vm, v3s16 p) return true; int nneighs = 0; - static const v3s16 dirs[16] = { - v3s16( 0, 0, 1), - v3s16( 0, 0, -1), - v3s16( 1, 0, 0), - v3s16(-1, 0, 0), - v3s16( 1, 0, 1), - v3s16(-1, 0, 1), - v3s16(-1, 0, -1), - v3s16( 1, 0, -1), + static const v3s16 dirs[16] = {v3s16(0, 0, 1), v3s16(0, 0, -1), v3s16(1, 0, 0), + v3s16(-1, 0, 0), v3s16(1, 0, 1), v3s16(-1, 0, 1), + v3s16(-1, 0, -1), v3s16(1, 0, -1), - v3s16( 0, 1, 1), - v3s16( 0, 1, -1), - v3s16( 1, 1, 0), - v3s16(-1, 1, 0), - v3s16( 1, 1, 1), - v3s16(-1, 1, 1), - v3s16(-1, 1, -1), - v3s16( 1, 1, -1) - }; + v3s16(0, 1, 1), v3s16(0, 1, -1), v3s16(1, 1, 0), v3s16(-1, 1, 0), + v3s16(1, 1, 1), v3s16(-1, 1, 1), v3s16(-1, 1, -1), + v3s16(1, 1, -1)}; // Check these 16 neighbouring nodes for enough spawnby nodes for (size_t i = 0; i != ARRLEN(dirs); i++) { @@ -133,7 +109,6 @@ bool Decoration::canPlaceDecoration(MMVManip *vm, v3s16 p) return true; } - size_t Decoration::placeDeco(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax) { PcgRandom ps(blockseed + 53); @@ -148,132 +123,144 @@ size_t Decoration::placeDeco(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax) int area = sidelen * sidelen; for (s16 z0 = 0; z0 < divlen; z0++) - for (s16 x0 = 0; x0 < divlen; x0++) { - v2s16 p2d_center( // Center position of part of division - nmin.X + sidelen / 2 + sidelen * x0, - nmin.Z + sidelen / 2 + sidelen * z0 - ); - v2s16 p2d_min( // Minimum edge of part of division - nmin.X + sidelen * x0, - nmin.Z + sidelen * z0 - ); - v2s16 p2d_max( // Maximum edge of part of division - nmin.X + sidelen + sidelen * x0 - 1, - nmin.Z + sidelen + sidelen * z0 - 1 - ); + for (s16 x0 = 0; x0 < divlen; x0++) { + v2s16 p2d_center( // Center position of part of division + nmin.X + sidelen / 2 + sidelen * x0, + nmin.Z + sidelen / 2 + sidelen * z0); + v2s16 p2d_min( // Minimum edge of part of division + nmin.X + sidelen * x0, nmin.Z + sidelen * z0); + v2s16 p2d_max( // Maximum edge of part of division + nmin.X + sidelen + sidelen * x0 - 1, + nmin.Z + sidelen + sidelen * z0 - 1); - bool cover = false; - // Amount of decorations - float nval = (flags & DECO_USE_NOISE) ? - NoisePerlin2D(&np, p2d_center.X, p2d_center.Y, mapseed) : - fill_ratio; - u32 deco_count = 0; + bool cover = false; + // Amount of decorations + float nval = (flags & DECO_USE_NOISE) + ? NoisePerlin2D(&np, p2d_center.X, + p2d_center.Y, + mapseed) + : fill_ratio; + u32 deco_count = 0; - if (nval >= 10.0f) { - // Complete coverage. Disable random placement to avoid - // redundant multiple placements at one position. - cover = true; - deco_count = area; - } else { - float deco_count_f = (float)area * nval; - if (deco_count_f >= 1.0f) { - deco_count = deco_count_f; - } else if (deco_count_f > 0.0f) { - // For very low density calculate a chance for 1 decoration - if (ps.range(1000) <= deco_count_f * 1000.0f) - deco_count = 1; - } - } - - s16 x = p2d_min.X - 1; - s16 z = p2d_min.Y; - - for (u32 i = 0; i < deco_count; i++) { - if (!cover) { - x = ps.range(p2d_min.X, p2d_max.X); - z = ps.range(p2d_min.Y, p2d_max.Y); + if (nval >= 10.0f) { + // Complete coverage. Disable random placement to avoid + // redundant multiple placements at one position. + cover = true; + deco_count = area; } else { - x++; - if (x == p2d_max.X + 1) { - z++; - x = p2d_min.X; + float deco_count_f = (float)area * nval; + if (deco_count_f >= 1.0f) { + deco_count = deco_count_f; + } else if (deco_count_f > 0.0f) { + // For very low density calculate a chance for 1 + // decoration + if (ps.range(1000) <= deco_count_f * 1000.0f) + deco_count = 1; } } - int mapindex = carea_size * (z - nmin.Z) + (x - nmin.X); - if ((flags & DECO_ALL_FLOORS) || - (flags & DECO_ALL_CEILINGS)) { - // All-surfaces decorations - // Check biome of column - if (mg->biomemap && !biomes.empty()) { - auto iter = biomes.find(mg->biomemap[mapindex]); - if (iter == biomes.end()) - continue; - } + s16 x = p2d_min.X - 1; + s16 z = p2d_min.Y; - // Get all floors and ceilings in node column - u16 size = (nmax.Y - nmin.Y + 1) / 2; - std::vector floors; - std::vector ceilings; - floors.reserve(size); - ceilings.reserve(size); - - mg->getSurfaces(v2s16(x, z), nmin.Y, nmax.Y, floors, ceilings); - - if (flags & DECO_ALL_FLOORS) { - // Floor decorations - for (const s16 y : floors) { - if (y < y_min || y > y_max) - continue; - - v3s16 pos(x, y, z); - if (generate(mg->vm, &ps, pos, false)) - mg->gennotify.addEvent( - GENNOTIFY_DECORATION, pos, index); + for (u32 i = 0; i < deco_count; i++) { + if (!cover) { + x = ps.range(p2d_min.X, p2d_max.X); + z = ps.range(p2d_min.Y, p2d_max.Y); + } else { + x++; + if (x == p2d_max.X + 1) { + z++; + x = p2d_min.X; } } + int mapindex = carea_size * (z - nmin.Z) + (x - nmin.X); - if (flags & DECO_ALL_CEILINGS) { - // Ceiling decorations - for (const s16 y : ceilings) { - if (y < y_min || y > y_max) + if ((flags & DECO_ALL_FLOORS) || + (flags & DECO_ALL_CEILINGS)) { + // All-surfaces decorations + // Check biome of column + if (mg->biomemap && !biomes.empty()) { + auto iter = biomes.find( + mg->biomemap[mapindex]); + if (iter == biomes.end()) continue; - - v3s16 pos(x, y, z); - if (generate(mg->vm, &ps, pos, true)) - mg->gennotify.addEvent( - GENNOTIFY_DECORATION, pos, index); } - } - } else { // Heightmap decorations - s16 y = -MAX_MAP_GENERATION_LIMIT; - if (flags & DECO_LIQUID_SURFACE) - y = mg->findLiquidSurface(v2s16(x, z), nmin.Y, nmax.Y); - else if (mg->heightmap) - y = mg->heightmap[mapindex]; - else - y = mg->findGroundLevel(v2s16(x, z), nmin.Y, nmax.Y); - if (y < y_min || y > y_max || y < nmin.Y || y > nmax.Y) - continue; + // Get all floors and ceilings in node column + u16 size = (nmax.Y - nmin.Y + 1) / 2; + std::vector floors; + std::vector ceilings; + floors.reserve(size); + ceilings.reserve(size); - if (mg->biomemap && !biomes.empty()) { - auto iter = biomes.find(mg->biomemap[mapindex]); - if (iter == biomes.end()) + mg->getSurfaces(v2s16(x, z), nmin.Y, nmax.Y, + floors, ceilings); + + if (flags & DECO_ALL_FLOORS) { + // Floor decorations + for (const s16 y : floors) { + if (y < y_min || y > y_max) + continue; + + v3s16 pos(x, y, z); + if (generate(mg->vm, &ps, pos, + false)) + mg->gennotify.addEvent( + GENNOTIFY_DECORATION, + pos, + index); + } + } + + if (flags & DECO_ALL_CEILINGS) { + // Ceiling decorations + for (const s16 y : ceilings) { + if (y < y_min || y > y_max) + continue; + + v3s16 pos(x, y, z); + if (generate(mg->vm, &ps, pos, + true)) + mg->gennotify.addEvent( + GENNOTIFY_DECORATION, + pos, + index); + } + } + } else { // Heightmap decorations + s16 y = -MAX_MAP_GENERATION_LIMIT; + if (flags & DECO_LIQUID_SURFACE) + y = mg->findLiquidSurface(v2s16(x, z), + nmin.Y, nmax.Y); + else if (mg->heightmap) + y = mg->heightmap[mapindex]; + else + y = mg->findGroundLevel(v2s16(x, z), + nmin.Y, nmax.Y); + + if (y < y_min || y > y_max || y < nmin.Y || + y > nmax.Y) continue; - } - v3s16 pos(x, y, z); - if (generate(mg->vm, &ps, pos, false)) - mg->gennotify.addEvent(GENNOTIFY_DECORATION, pos, index); + if (mg->biomemap && !biomes.empty()) { + auto iter = biomes.find( + mg->biomemap[mapindex]); + if (iter == biomes.end()) + continue; + } + + v3s16 pos(x, y, z); + if (generate(mg->vm, &ps, pos, false)) + mg->gennotify.addEvent( + GENNOTIFY_DECORATION, pos, + index); + } } } - } return 0; } - void Decoration::cloneTo(Decoration *def) const { ObjDef::cloneTo(def); @@ -291,10 +278,8 @@ void Decoration::cloneTo(Decoration *def) const def->biomes = biomes; } - /////////////////////////////////////////////////////////////////////////////// - ObjDef *DecoSimple::clone() const { auto def = new DecoSimple(); @@ -309,14 +294,12 @@ ObjDef *DecoSimple::clone() const return def; } - void DecoSimple::resolveNodeNames() { Decoration::resolveNodeNames(); getIdsFromNrBacklog(&c_decos); } - size_t DecoSimple::generate(MMVManip *vm, PcgRandom *pr, v3s16 p, bool ceiling) { // Don't bother if there aren't any decorations to place @@ -347,10 +330,10 @@ size_t DecoSimple::generate(MMVManip *vm, PcgRandom *pr, v3s16 p, bool ceiling) } content_t c_place = c_decos[pr->range(0, c_decos.size() - 1)]; - s16 height = (deco_height_max > 0) ? - pr->range(deco_height, deco_height_max) : deco_height; - u8 param2 = (deco_param2_max > 0) ? - pr->range(deco_param2, deco_param2_max) : deco_param2; + s16 height = (deco_height_max > 0) ? pr->range(deco_height, deco_height_max) + : deco_height; + u8 param2 = (deco_param2_max > 0) ? pr->range(deco_param2, deco_param2_max) + : deco_param2; bool force_placement = (flags & DECO_FORCE_PLACEMENT); const v3s16 &em = vm->m_area.getExtent(); @@ -385,17 +368,14 @@ size_t DecoSimple::generate(MMVManip *vm, PcgRandom *pr, v3s16 p, bool ceiling) return 1; } - /////////////////////////////////////////////////////////////////////////////// - DecoSchematic::~DecoSchematic() { if (was_cloned) delete schematic; } - ObjDef *DecoSchematic::clone() const { auto def = new DecoSchematic(); @@ -407,13 +387,12 @@ ObjDef *DecoSchematic::clone() const * and not a handle. We are left with no option but to clone it ourselves. * This is a waste of memory and should be replaced with an alternative * approach sometime. */ - def->schematic = dynamic_cast(schematic->clone()); + def->schematic = dynamic_cast(schematic->clone()); def->was_cloned = true; return def; } - size_t DecoSchematic::generate(MMVManip *vm, PcgRandom *pr, v3s16 p, bool ceiling) { // Schematic could have been unloaded but not the decoration @@ -443,8 +422,9 @@ size_t DecoSchematic::generate(MMVManip *vm, PcgRandom *pr, v3s16 p, bool ceilin if (p.Y < vm->m_area.MinEdge.Y) return 0; - Rotation rot = (rotation == ROTATE_RAND) ? - (Rotation)pr->range(ROTATE_0, ROTATE_270) : rotation; + Rotation rot = (rotation == ROTATE_RAND) + ? (Rotation)pr->range(ROTATE_0, ROTATE_270) + : rotation; if (flags & DECO_PLACE_CENTER_X) { if (rot == ROTATE_0 || rot == ROTATE_180) diff --git a/src/mapgen/mg_decoration.h b/src/mapgen/mg_decoration.h index 1ea02a527..c0cd4af9f 100644 --- a/src/mapgen/mg_decoration.h +++ b/src/mapgen/mg_decoration.h @@ -25,32 +25,33 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "noise.h" #include "nodedef.h" -typedef u16 biome_t; // copy from mg_biome.h to avoid an unnecessary include +typedef u16 biome_t; // copy from mg_biome.h to avoid an unnecessary include class Mapgen; class MMVManip; class PcgRandom; class Schematic; -enum DecorationType { +enum DecorationType +{ DECO_SIMPLE, DECO_SCHEMATIC, DECO_LSYSTEM }; -#define DECO_PLACE_CENTER_X 0x01 -#define DECO_PLACE_CENTER_Y 0x02 -#define DECO_PLACE_CENTER_Z 0x04 -#define DECO_USE_NOISE 0x08 +#define DECO_PLACE_CENTER_X 0x01 +#define DECO_PLACE_CENTER_Y 0x02 +#define DECO_PLACE_CENTER_Z 0x04 +#define DECO_USE_NOISE 0x08 #define DECO_FORCE_PLACEMENT 0x10 -#define DECO_LIQUID_SURFACE 0x20 -#define DECO_ALL_FLOORS 0x40 -#define DECO_ALL_CEILINGS 0x80 +#define DECO_LIQUID_SURFACE 0x20 +#define DECO_ALL_FLOORS 0x40 +#define DECO_ALL_CEILINGS 0x80 extern FlagDesc flagdesc_deco[]; - -class Decoration : public ObjDef, public NodeResolver { +class Decoration : public ObjDef, public NodeResolver +{ public: Decoration() = default; virtual ~Decoration() = default; @@ -80,8 +81,8 @@ protected: void cloneTo(Decoration *def) const; }; - -class DecoSimple : public Decoration { +class DecoSimple : public Decoration +{ public: ObjDef *clone() const; @@ -95,8 +96,8 @@ public: u8 deco_param2_max; }; - -class DecoSchematic : public Decoration { +class DecoSchematic : public Decoration +{ public: ObjDef *clone() const; @@ -110,7 +111,6 @@ public: bool was_cloned = false; // see FIXME inside DecoSchemtic::clone() }; - /* class DecoLSystem : public Decoration { public: @@ -118,18 +118,15 @@ public: }; */ - -class DecorationManager : public ObjDefManager { +class DecorationManager : public ObjDefManager +{ public: DecorationManager(IGameDef *gamedef); virtual ~DecorationManager() = default; DecorationManager *clone() const; - const char *getObjectTitle() const - { - return "decoration"; - } + const char *getObjectTitle() const { return "decoration"; } static Decoration *create(DecorationType type) { @@ -138,7 +135,7 @@ public: return new DecoSimple; case DECO_SCHEMATIC: return new DecoSchematic; - //case DECO_LSYSTEM: + // case DECO_LSYSTEM: // return new DecoLSystem; default: return NULL; @@ -148,5 +145,5 @@ public: size_t placeAllDecos(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax); private: - DecorationManager() {}; + DecorationManager(){}; }; diff --git a/src/mapgen/mg_ore.cpp b/src/mapgen/mg_ore.cpp index b50ed6a32..5af8e31e4 100644 --- a/src/mapgen/mg_ore.cpp +++ b/src/mapgen/mg_ore.cpp @@ -27,24 +27,16 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include - -FlagDesc flagdesc_ore[] = { - {"absheight", OREFLAG_ABSHEIGHT}, // Non-functional - {"puff_cliffs", OREFLAG_PUFF_CLIFFS}, - {"puff_additive_composition", OREFLAG_PUFF_ADDITIVE}, - {NULL, 0} -}; - +FlagDesc flagdesc_ore[] = {{"absheight", OREFLAG_ABSHEIGHT}, // Non-functional + {"puff_cliffs", OREFLAG_PUFF_CLIFFS}, + {"puff_additive_composition", OREFLAG_PUFF_ADDITIVE}, {NULL, 0}}; /////////////////////////////////////////////////////////////////////////////// - -OreManager::OreManager(IGameDef *gamedef) : - ObjDefManager(gamedef, OBJDEF_ORE) +OreManager::OreManager(IGameDef *gamedef) : ObjDefManager(gamedef, OBJDEF_ORE) { } - size_t OreManager::placeAllOres(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax) { size_t nplaced = 0; @@ -61,17 +53,15 @@ size_t OreManager::placeAllOres(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nma return nplaced; } - void OreManager::clear() { for (ObjDef *object : m_objects) { - Ore *ore = (Ore *) object; + Ore *ore = (Ore *)object; delete ore; } m_objects.clear(); } - OreManager *OreManager::clone() const { auto mgr = new OreManager(); @@ -79,23 +69,19 @@ OreManager *OreManager::clone() const return mgr; } - /////////////////////////////////////////////////////////////////////////////// - Ore::~Ore() { delete noise; } - void Ore::resolveNodeNames() { getIdFromNrBacklog(&c_ore, "", CONTENT_AIR); getIdsFromNrBacklog(&c_wherein); } - size_t Ore::placeOre(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax) { if (nmin.Y > y_max || nmax.Y < y_min) @@ -113,7 +99,6 @@ size_t Ore::placeOre(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax) return 1; } - void Ore::cloneTo(Ore *def) const { ObjDef::cloneTo(def); @@ -133,10 +118,8 @@ void Ore::cloneTo(Ore *def) const def->biomes = biomes; } - /////////////////////////////////////////////////////////////////////////////// - ObjDef *OreScatter::clone() const { auto def = new OreScatter(); @@ -144,19 +127,17 @@ ObjDef *OreScatter::clone() const return def; } - -void OreScatter::generate(MMVManip *vm, int mapseed, u32 blockseed, - v3s16 nmin, v3s16 nmax, biome_t *biomemap) +void OreScatter::generate(MMVManip *vm, int mapseed, u32 blockseed, v3s16 nmin, + v3s16 nmax, biome_t *biomemap) { PcgRandom pr(blockseed); MapNode n_ore(c_ore, 0, ore_param2); - u32 sizex = (nmax.X - nmin.X + 1); - u32 volume = (nmax.X - nmin.X + 1) * - (nmax.Y - nmin.Y + 1) * - (nmax.Z - nmin.Z + 1); - u32 csize = clust_size; - u32 cvolume = csize * csize * csize; + u32 sizex = (nmax.X - nmin.X + 1); + u32 volume = (nmax.X - nmin.X + 1) * (nmax.Y - nmin.Y + 1) * + (nmax.Z - nmin.Z + 1); + u32 csize = clust_size; + u32 cvolume = csize * csize * csize; u32 nclusters = volume / clust_scarcity; for (u32 i = 0; i != nclusters; i++) { @@ -165,7 +146,7 @@ void OreScatter::generate(MMVManip *vm, int mapseed, u32 blockseed, int z0 = pr.range(nmin.Z, nmax.Z - csize + 1); if ((flags & OREFLAG_USE_NOISE) && - (NoisePerlin3D(&np, x0, y0, z0, mapseed) < nthresh)) + (NoisePerlin3D(&np, x0, y0, z0, mapseed) < nthresh)) continue; if (biomemap && !biomes.empty()) { @@ -176,24 +157,24 @@ void OreScatter::generate(MMVManip *vm, int mapseed, u32 blockseed, } for (u32 z1 = 0; z1 != csize; z1++) - for (u32 y1 = 0; y1 != csize; y1++) - for (u32 x1 = 0; x1 != csize; x1++) { - if (pr.range(1, cvolume) > clust_num_ores) - continue; + for (u32 y1 = 0; y1 != csize; y1++) + for (u32 x1 = 0; x1 != csize; x1++) { + if (pr.range(1, cvolume) > clust_num_ores) + continue; - u32 i = vm->m_area.index(x0 + x1, y0 + y1, z0 + z1); - if (!CONTAINS(c_wherein, vm->m_data[i].getContent())) - continue; + u32 i = vm->m_area.index( + x0 + x1, y0 + y1, z0 + z1); + if (!CONTAINS(c_wherein, + vm->m_data[i].getContent())) + continue; - vm->m_data[i] = n_ore; - } + vm->m_data[i] = n_ore; + } } } - /////////////////////////////////////////////////////////////////////////////// - ObjDef *OreSheet::clone() const { auto def = new OreSheet(); @@ -206,9 +187,8 @@ ObjDef *OreSheet::clone() const return def; } - -void OreSheet::generate(MMVManip *vm, int mapseed, u32 blockseed, - v3s16 nmin, v3s16 nmax, biome_t *biomemap) +void OreSheet::generate(MMVManip *vm, int mapseed, u32 blockseed, v3s16 nmin, v3s16 nmax, + biome_t *biomemap) { PcgRandom pr(blockseed + 4234); MapNode n_ore(c_ore, 0, ore_param2); @@ -217,9 +197,8 @@ void OreSheet::generate(MMVManip *vm, int mapseed, u32 blockseed, int y_start_min = nmin.Y + max_height; int y_start_max = nmax.Y - max_height; - int y_start = y_start_min < y_start_max ? - pr.range(y_start_min, y_start_max) : - (y_start_min + y_start_max) / 2; + int y_start = y_start_min < y_start_max ? pr.range(y_start_min, y_start_max) + : (y_start_min + y_start_max) / 2; if (!noise) { int sx = nmax.X - nmin.X + 1; @@ -231,45 +210,43 @@ void OreSheet::generate(MMVManip *vm, int mapseed, u32 blockseed, size_t index = 0; for (int z = nmin.Z; z <= nmax.Z; z++) - for (int x = nmin.X; x <= nmax.X; x++, index++) { - float noiseval = noise->result[index]; - if (noiseval < nthresh) - continue; - - if (biomemap && !biomes.empty()) { - auto it = biomes.find(biomemap[index]); - if (it == biomes.end()) + for (int x = nmin.X; x <= nmax.X; x++, index++) { + float noiseval = noise->result[index]; + if (noiseval < nthresh) continue; + + if (biomemap && !biomes.empty()) { + auto it = biomes.find(biomemap[index]); + if (it == biomes.end()) + continue; + } + + u16 height = pr.range(column_height_min, column_height_max); + int ymidpoint = y_start + noiseval; + int y0 = MYMAX(nmin.Y, + ymidpoint - height * (1 - column_midpoint_factor)); + int y1 = MYMIN(nmax.Y, y0 + height - 1); + + for (int y = y0; y <= y1; y++) { + u32 i = vm->m_area.index(x, y, z); + if (!vm->m_area.contains(i)) + continue; + if (!CONTAINS(c_wherein, vm->m_data[i].getContent())) + continue; + + vm->m_data[i] = n_ore; + } } - - u16 height = pr.range(column_height_min, column_height_max); - int ymidpoint = y_start + noiseval; - int y0 = MYMAX(nmin.Y, ymidpoint - height * (1 - column_midpoint_factor)); - int y1 = MYMIN(nmax.Y, y0 + height - 1); - - for (int y = y0; y <= y1; y++) { - u32 i = vm->m_area.index(x, y, z); - if (!vm->m_area.contains(i)) - continue; - if (!CONTAINS(c_wherein, vm->m_data[i].getContent())) - continue; - - vm->m_data[i] = n_ore; - } - } } - /////////////////////////////////////////////////////////////////////////////// - OrePuff::~OrePuff() { delete noise_puff_top; delete noise_puff_bottom; } - ObjDef *OrePuff::clone() const { auto def = new OrePuff(); @@ -283,9 +260,8 @@ ObjDef *OrePuff::clone() const return def; } - -void OrePuff::generate(MMVManip *vm, int mapseed, u32 blockseed, - v3s16 nmin, v3s16 nmax, biome_t *biomemap) +void OrePuff::generate(MMVManip *vm, int mapseed, u32 blockseed, v3s16 nmin, v3s16 nmax, + biome_t *biomemap) { PcgRandom pr(blockseed + 4234); MapNode n_ore(c_ore, 0, ore_param2); @@ -306,57 +282,55 @@ void OrePuff::generate(MMVManip *vm, int mapseed, u32 blockseed, size_t index = 0; for (int z = nmin.Z; z <= nmax.Z; z++) - for (int x = nmin.X; x <= nmax.X; x++, index++) { - float noiseval = noise->result[index]; - if (noiseval < nthresh) - continue; - - if (biomemap && !biomes.empty()) { - auto it = biomes.find(biomemap[index]); - if (it == biomes.end()) + for (int x = nmin.X; x <= nmax.X; x++, index++) { + float noiseval = noise->result[index]; + if (noiseval < nthresh) continue; - } - if (!noise_generated) { - noise_generated = true; - noise_puff_top->perlinMap2D(nmin.X, nmin.Z); - noise_puff_bottom->perlinMap2D(nmin.X, nmin.Z); - } + if (biomemap && !biomes.empty()) { + auto it = biomes.find(biomemap[index]); + if (it == biomes.end()) + continue; + } - float ntop = noise_puff_top->result[index]; - float nbottom = noise_puff_bottom->result[index]; + if (!noise_generated) { + noise_generated = true; + noise_puff_top->perlinMap2D(nmin.X, nmin.Z); + noise_puff_bottom->perlinMap2D(nmin.X, nmin.Z); + } - if (!(flags & OREFLAG_PUFF_CLIFFS)) { - float ndiff = noiseval - nthresh; - if (ndiff < 1.0f) { - ntop *= ndiff; - nbottom *= ndiff; + float ntop = noise_puff_top->result[index]; + float nbottom = noise_puff_bottom->result[index]; + + if (!(flags & OREFLAG_PUFF_CLIFFS)) { + float ndiff = noiseval - nthresh; + if (ndiff < 1.0f) { + ntop *= ndiff; + nbottom *= ndiff; + } + } + + int ymid = y_start; + int y0 = ymid - nbottom; + int y1 = ymid + ntop; + + if ((flags & OREFLAG_PUFF_ADDITIVE) && (y0 > y1)) + SWAP(int, y0, y1); + + for (int y = y0; y <= y1; y++) { + u32 i = vm->m_area.index(x, y, z); + if (!vm->m_area.contains(i)) + continue; + if (!CONTAINS(c_wherein, vm->m_data[i].getContent())) + continue; + + vm->m_data[i] = n_ore; } } - - int ymid = y_start; - int y0 = ymid - nbottom; - int y1 = ymid + ntop; - - if ((flags & OREFLAG_PUFF_ADDITIVE) && (y0 > y1)) - SWAP(int, y0, y1); - - for (int y = y0; y <= y1; y++) { - u32 i = vm->m_area.index(x, y, z); - if (!vm->m_area.contains(i)) - continue; - if (!CONTAINS(c_wherein, vm->m_data[i].getContent())) - continue; - - vm->m_data[i] = n_ore; - } - } } - /////////////////////////////////////////////////////////////////////////////// - ObjDef *OreBlob::clone() const { auto def = new OreBlob(); @@ -364,18 +338,16 @@ ObjDef *OreBlob::clone() const return def; } - -void OreBlob::generate(MMVManip *vm, int mapseed, u32 blockseed, - v3s16 nmin, v3s16 nmax, biome_t *biomemap) +void OreBlob::generate(MMVManip *vm, int mapseed, u32 blockseed, v3s16 nmin, v3s16 nmax, + biome_t *biomemap) { PcgRandom pr(blockseed + 2404); MapNode n_ore(c_ore, 0, ore_param2); - u32 sizex = (nmax.X - nmin.X + 1); - u32 volume = (nmax.X - nmin.X + 1) * - (nmax.Y - nmin.Y + 1) * - (nmax.Z - nmin.Z + 1); - u32 csize = clust_size; + u32 sizex = (nmax.X - nmin.X + 1); + u32 volume = (nmax.X - nmin.X + 1) * (nmax.Y - nmin.Y + 1) * + (nmax.Z - nmin.Z + 1); + u32 csize = clust_size; u32 nblobs = volume / clust_scarcity; if (!noise) @@ -398,45 +370,48 @@ void OreBlob::generate(MMVManip *vm, int mapseed, u32 blockseed, size_t index = 0; for (u32 z1 = 0; z1 != csize; z1++) - for (u32 y1 = 0; y1 != csize; y1++) - for (u32 x1 = 0; x1 != csize; x1++, index++) { - u32 i = vm->m_area.index(x0 + x1, y0 + y1, z0 + z1); - if (!CONTAINS(c_wherein, vm->m_data[i].getContent())) - continue; + for (u32 y1 = 0; y1 != csize; y1++) + for (u32 x1 = 0; x1 != csize; x1++, index++) { + u32 i = vm->m_area.index( + x0 + x1, y0 + y1, z0 + z1); + if (!CONTAINS(c_wherein, + vm->m_data[i].getContent())) + continue; - // Lazily generate noise only if there's a chance of ore being placed - // This simple optimization makes calls 6x faster on average - if (!noise_generated) { - noise_generated = true; - noise->perlinMap3D(x0, y0, z0); - } + // Lazily generate noise only if there's a chance + // of ore being placed This simple optimization + // makes calls 6x faster on average + if (!noise_generated) { + noise_generated = true; + noise->perlinMap3D(x0, y0, z0); + } - float noiseval = noise->result[index]; + float noiseval = noise->result[index]; - float xdist = (s32)x1 - (s32)csize / 2; - float ydist = (s32)y1 - (s32)csize / 2; - float zdist = (s32)z1 - (s32)csize / 2; + float xdist = (s32)x1 - (s32)csize / 2; + float ydist = (s32)y1 - (s32)csize / 2; + float zdist = (s32)z1 - (s32)csize / 2; - noiseval -= std::sqrt(xdist * xdist + ydist * ydist + zdist * zdist) / csize; + noiseval -= std::sqrt(xdist * xdist + + ydist * ydist + + zdist * zdist) / + csize; - if (noiseval < nthresh) - continue; + if (noiseval < nthresh) + continue; - vm->m_data[i] = n_ore; - } + vm->m_data[i] = n_ore; + } } } - /////////////////////////////////////////////////////////////////////////////// - OreVein::~OreVein() { delete noise2; } - ObjDef *OreVein::clone() const { auto def = new OreVein(); @@ -449,9 +424,8 @@ ObjDef *OreVein::clone() const return def; } - -void OreVein::generate(MMVManip *vm, int mapseed, u32 blockseed, - v3s16 nmin, v3s16 nmax, biome_t *biomemap) +void OreVein::generate(MMVManip *vm, int mapseed, u32 blockseed, v3s16 nmin, v3s16 nmax, + biome_t *biomemap) { PcgRandom pr(blockseed + 520); MapNode n_ore(c_ore, 0, ore_param2); @@ -467,7 +441,7 @@ void OreVein::generate(MMVManip *vm, int mapseed, u32 blockseed, delete noise; delete noise2; int sizez = nmax.Z - nmin.Z + 1; - noise = new Noise(&np, mapseed, sizex, sizey, sizez); + noise = new Noise(&np, mapseed, sizex, sizey, sizez); noise2 = new Noise(&np, mapseed + 436, sizex, sizey, sizez); sizey_prev = sizey; } @@ -475,49 +449,48 @@ void OreVein::generate(MMVManip *vm, int mapseed, u32 blockseed, bool noise_generated = false; size_t index = 0; for (int z = nmin.Z; z <= nmax.Z; z++) - for (int y = nmin.Y; y <= nmax.Y; y++) - for (int x = nmin.X; x <= nmax.X; x++, index++) { - u32 i = vm->m_area.index(x, y, z); - if (!vm->m_area.contains(i)) - continue; - if (!CONTAINS(c_wherein, vm->m_data[i].getContent())) - continue; + for (int y = nmin.Y; y <= nmax.Y; y++) + for (int x = nmin.X; x <= nmax.X; x++, index++) { + u32 i = vm->m_area.index(x, y, z); + if (!vm->m_area.contains(i)) + continue; + if (!CONTAINS(c_wherein, vm->m_data[i].getContent())) + continue; - if (biomemap && !biomes.empty()) { - u32 bmapidx = sizex * (z - nmin.Z) + (x - nmin.X); - auto it = biomes.find(biomemap[bmapidx]); - if (it == biomes.end()) - continue; - } + if (biomemap && !biomes.empty()) { + u32 bmapidx = sizex * (z - nmin.Z) + (x - nmin.X); + auto it = biomes.find(biomemap[bmapidx]); + if (it == biomes.end()) + continue; + } - // Same lazy generation optimization as in OreBlob - if (!noise_generated) { - noise_generated = true; - noise->perlinMap3D(nmin.X, nmin.Y, nmin.Z); - noise2->perlinMap3D(nmin.X, nmin.Y, nmin.Z); - } + // Same lazy generation optimization as in OreBlob + if (!noise_generated) { + noise_generated = true; + noise->perlinMap3D(nmin.X, nmin.Y, nmin.Z); + noise2->perlinMap3D(nmin.X, nmin.Y, nmin.Z); + } - // randval ranges from -1..1 - float randval = (float)pr.next() / (pr.RANDOM_RANGE / 2) - 1.f; - float noiseval = contour(noise->result[index]); - float noiseval2 = contour(noise2->result[index]); - if (noiseval * noiseval2 + randval * random_factor < nthresh) - continue; + // randval ranges from -1..1 + float randval = (float)pr.next() / (pr.RANDOM_RANGE / 2) - + 1.f; + float noiseval = contour(noise->result[index]); + float noiseval2 = contour(noise2->result[index]); + if (noiseval * noiseval2 + randval * random_factor < + nthresh) + continue; - vm->m_data[i] = n_ore; - } + vm->m_data[i] = n_ore; + } } - /////////////////////////////////////////////////////////////////////////////// - OreStratum::~OreStratum() { delete noise_stratum_thickness; } - ObjDef *OreStratum::clone() const { auto def = new OreStratum(); @@ -530,9 +503,8 @@ ObjDef *OreStratum::clone() const return def; } - -void OreStratum::generate(MMVManip *vm, int mapseed, u32 blockseed, - v3s16 nmin, v3s16 nmax, biome_t *biomemap) +void OreStratum::generate(MMVManip *vm, int mapseed, u32 blockseed, v3s16 nmin, + v3s16 nmax, biome_t *biomemap) { PcgRandom pr(blockseed + 4234); MapNode n_ore(c_ore, 0, ore_param2); @@ -550,7 +522,8 @@ void OreStratum::generate(MMVManip *vm, int mapseed, u32 blockseed, if (!noise_stratum_thickness) { int sx = nmax.X - nmin.X + 1; int sz = nmax.Z - nmin.Z + 1; - noise_stratum_thickness = new Noise(&np_stratum_thickness, 0, sx, sz); + noise_stratum_thickness = + new Noise(&np_stratum_thickness, 0, sx, sz); } noise_stratum_thickness->perlinMap2D(nmin.X, nmin.Z); } @@ -558,39 +531,41 @@ void OreStratum::generate(MMVManip *vm, int mapseed, u32 blockseed, size_t index = 0; for (int z = nmin.Z; z <= nmax.Z; z++) - for (int x = nmin.X; x <= nmax.X; x++, index++) { - if (biomemap && !biomes.empty()) { - auto it = biomes.find(biomemap[index]); - if (it == biomes.end()) - continue; + for (int x = nmin.X; x <= nmax.X; x++, index++) { + if (biomemap && !biomes.empty()) { + auto it = biomes.find(biomemap[index]); + if (it == biomes.end()) + continue; + } + + int y0; + int y1; + + if (flags & OREFLAG_USE_NOISE) { + float nhalfthick = + ((flags & OREFLAG_USE_NOISE2) ? noise_stratum_thickness + ->result[index] + : (float)stratum_thickness) / + 2.0f; + float nmid = noise->result[index]; + y0 = MYMAX(nmin.Y, std::ceil(nmid - nhalfthick)); + y1 = MYMIN(nmax.Y, nmid + nhalfthick); + } else { // Simple horizontal stratum + y0 = nmin.Y; + y1 = nmax.Y; + } + + for (int y = y0; y <= y1; y++) { + if (pr.range(1, clust_scarcity) != 1) + continue; + + u32 i = vm->m_area.index(x, y, z); + if (!vm->m_area.contains(i)) + continue; + if (!CONTAINS(c_wherein, vm->m_data[i].getContent())) + continue; + + vm->m_data[i] = n_ore; + } } - - int y0; - int y1; - - if (flags & OREFLAG_USE_NOISE) { - float nhalfthick = ((flags & OREFLAG_USE_NOISE2) ? - noise_stratum_thickness->result[index] : (float)stratum_thickness) / - 2.0f; - float nmid = noise->result[index]; - y0 = MYMAX(nmin.Y, std::ceil(nmid - nhalfthick)); - y1 = MYMIN(nmax.Y, nmid + nhalfthick); - } else { // Simple horizontal stratum - y0 = nmin.Y; - y1 = nmax.Y; - } - - for (int y = y0; y <= y1; y++) { - if (pr.range(1, clust_scarcity) != 1) - continue; - - u32 i = vm->m_area.index(x, y, z); - if (!vm->m_area.contains(i)) - continue; - if (!CONTAINS(c_wherein, vm->m_data[i].getContent())) - continue; - - vm->m_data[i] = n_ore; - } - } } diff --git a/src/mapgen/mg_ore.h b/src/mapgen/mg_ore.h index 76420fab4..e2053a274 100644 --- a/src/mapgen/mg_ore.h +++ b/src/mapgen/mg_ore.h @@ -25,7 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "noise.h" #include "nodedef.h" -typedef u16 biome_t; // copy from mg_biome.h to avoid an unnecessary include +typedef u16 biome_t; // copy from mg_biome.h to avoid an unnecessary include class Noise; class Mapgen; @@ -33,13 +33,14 @@ class MMVManip; /////////////////// Ore generation flags -#define OREFLAG_ABSHEIGHT 0x01 // Non-functional but kept to not break flags -#define OREFLAG_PUFF_CLIFFS 0x02 +#define OREFLAG_ABSHEIGHT 0x01 // Non-functional but kept to not break flags +#define OREFLAG_PUFF_CLIFFS 0x02 #define OREFLAG_PUFF_ADDITIVE 0x04 -#define OREFLAG_USE_NOISE 0x08 -#define OREFLAG_USE_NOISE2 0x10 +#define OREFLAG_USE_NOISE 0x08 +#define OREFLAG_USE_NOISE2 0x10 -enum OreType { +enum OreType +{ ORE_SCATTER, ORE_SHEET, ORE_PUFF, @@ -50,48 +51,54 @@ enum OreType { extern FlagDesc flagdesc_ore[]; -class Ore : public ObjDef, public NodeResolver { +class Ore : public ObjDef, public NodeResolver +{ public: static const bool NEEDS_NOISE = false; - content_t c_ore; // the node to place + content_t c_ore; // the node to place std::vector c_wherein; // the nodes to be placed in - u32 clust_scarcity; // ore cluster has a 1-in-clust_scarcity chance of appearing at a node + u32 clust_scarcity; // ore cluster has a 1-in-clust_scarcity chance of appearing + // at a node s16 clust_num_ores; // how many ore nodes are in a chunk - s16 clust_size; // how large (in nodes) a chunk of ore is + s16 clust_size; // how large (in nodes) a chunk of ore is s16 y_min; s16 y_max; - u8 ore_param2; // to set node-specific attributes - u32 flags = 0; // attributes for this ore - float nthresh; // threshold for noise at which an ore is placed - NoiseParams np; // noise for distribution of clusters (NULL for uniform scattering) + u8 ore_param2; // to set node-specific attributes + u32 flags = 0; // attributes for this ore + float nthresh; // threshold for noise at which an ore is placed + NoiseParams np; // noise for distribution of clusters (NULL for uniform + // scattering) Noise *noise = nullptr; std::unordered_set biomes; - Ore() = default;; + Ore() = default; + ; virtual ~Ore(); virtual void resolveNodeNames(); size_t placeOre(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax); - virtual void generate(MMVManip *vm, int mapseed, u32 blockseed, - v3s16 nmin, v3s16 nmax, biome_t *biomemap) = 0; + virtual void generate(MMVManip *vm, int mapseed, u32 blockseed, v3s16 nmin, + v3s16 nmax, biome_t *biomemap) = 0; protected: void cloneTo(Ore *def) const; }; -class OreScatter : public Ore { +class OreScatter : public Ore +{ public: static const bool NEEDS_NOISE = false; ObjDef *clone() const; - virtual void generate(MMVManip *vm, int mapseed, u32 blockseed, - v3s16 nmin, v3s16 nmax, biome_t *biomemap); + virtual void generate(MMVManip *vm, int mapseed, u32 blockseed, v3s16 nmin, + v3s16 nmax, biome_t *biomemap); }; -class OreSheet : public Ore { +class OreSheet : public Ore +{ public: static const bool NEEDS_NOISE = true; @@ -101,11 +108,12 @@ public: u16 column_height_max; float column_midpoint_factor; - virtual void generate(MMVManip *vm, int mapseed, u32 blockseed, - v3s16 nmin, v3s16 nmax, biome_t *biomemap); + virtual void generate(MMVManip *vm, int mapseed, u32 blockseed, v3s16 nmin, + v3s16 nmax, biome_t *biomemap); }; -class OrePuff : public Ore { +class OrePuff : public Ore +{ public: static const bool NEEDS_NOISE = true; @@ -119,21 +127,23 @@ public: OrePuff() = default; virtual ~OrePuff(); - virtual void generate(MMVManip *vm, int mapseed, u32 blockseed, - v3s16 nmin, v3s16 nmax, biome_t *biomemap); + virtual void generate(MMVManip *vm, int mapseed, u32 blockseed, v3s16 nmin, + v3s16 nmax, biome_t *biomemap); }; -class OreBlob : public Ore { +class OreBlob : public Ore +{ public: static const bool NEEDS_NOISE = true; ObjDef *clone() const; - virtual void generate(MMVManip *vm, int mapseed, u32 blockseed, - v3s16 nmin, v3s16 nmax, biome_t *biomemap); + virtual void generate(MMVManip *vm, int mapseed, u32 blockseed, v3s16 nmin, + v3s16 nmax, biome_t *biomemap); }; -class OreVein : public Ore { +class OreVein : public Ore +{ public: static const bool NEEDS_NOISE = true; @@ -146,11 +156,12 @@ public: OreVein() = default; virtual ~OreVein(); - virtual void generate(MMVManip *vm, int mapseed, u32 blockseed, - v3s16 nmin, v3s16 nmax, biome_t *biomemap); + virtual void generate(MMVManip *vm, int mapseed, u32 blockseed, v3s16 nmin, + v3s16 nmax, biome_t *biomemap); }; -class OreStratum : public Ore { +class OreStratum : public Ore +{ public: static const bool NEEDS_NOISE = false; @@ -163,21 +174,19 @@ public: OreStratum() = default; virtual ~OreStratum(); - virtual void generate(MMVManip *vm, int mapseed, u32 blockseed, - v3s16 nmin, v3s16 nmax, biome_t *biomemap); + virtual void generate(MMVManip *vm, int mapseed, u32 blockseed, v3s16 nmin, + v3s16 nmax, biome_t *biomemap); }; -class OreManager : public ObjDefManager { +class OreManager : public ObjDefManager +{ public: OreManager(IGameDef *gamedef); virtual ~OreManager() = default; OreManager *clone() const; - const char *getObjectTitle() const - { - return "ore"; - } + const char *getObjectTitle() const { return "ore"; } static Ore *create(OreType type) { @@ -204,5 +213,5 @@ public: size_t placeAllOres(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax); private: - OreManager() {}; + OreManager(){}; }; diff --git a/src/mapgen/mg_schematic.cpp b/src/mapgen/mg_schematic.cpp index ba102d997..598c044d6 100644 --- a/src/mapgen/mg_schematic.cpp +++ b/src/mapgen/mg_schematic.cpp @@ -35,14 +35,11 @@ with this program; if not, write to the Free Software Foundation, Inc., /////////////////////////////////////////////////////////////////////////////// - SchematicManager::SchematicManager(Server *server) : - ObjDefManager(server, OBJDEF_SCHEMATIC), - m_server(server) + ObjDefManager(server, OBJDEF_SCHEMATIC), m_server(server) { } - SchematicManager *SchematicManager::clone() const { auto mgr = new SchematicManager(); @@ -51,7 +48,6 @@ SchematicManager *SchematicManager::clone() const return mgr; } - void SchematicManager::clear() { EmergeManager *emerge = m_server->getEmergeManager(); @@ -72,18 +68,14 @@ void SchematicManager::clear() ObjDefManager::clear(); } - /////////////////////////////////////////////////////////////////////////////// - -Schematic::Schematic() -= default; - +Schematic::Schematic() = default; Schematic::~Schematic() { - delete []schemdata; - delete []slice_probs; + delete[] schemdata; + delete[] slice_probs; } ObjDef *Schematic::clone() const @@ -105,7 +97,6 @@ ObjDef *Schematic::clone() const return def; } - void Schematic::resolveNodeNames() { getIdsFromNrBacklog(&c_nodes, true, CONTENT_AIR); @@ -118,7 +109,6 @@ void Schematic::resolveNodeNames() } } - void Schematic::blitToVManip(MMVManip *vm, v3s16 p, Rotation rot, bool force_place) { assert(schemdata && slice_probs); @@ -134,33 +124,33 @@ void Schematic::blitToVManip(MMVManip *vm, v3s16 p, Rotation rot, bool force_pla int i_start, i_step_x, i_step_z; switch (rot) { - case ROTATE_90: - i_start = sx - 1; - i_step_x = zstride; - i_step_z = -xstride; - SWAP(s16, sx, sz); - break; - case ROTATE_180: - i_start = zstride * (sz - 1) + sx - 1; - i_step_x = -xstride; - i_step_z = -zstride; - break; - case ROTATE_270: - i_start = zstride * (sz - 1); - i_step_x = -zstride; - i_step_z = xstride; - SWAP(s16, sx, sz); - break; - default: - i_start = 0; - i_step_x = xstride; - i_step_z = zstride; + case ROTATE_90: + i_start = sx - 1; + i_step_x = zstride; + i_step_z = -xstride; + SWAP(s16, sx, sz); + break; + case ROTATE_180: + i_start = zstride * (sz - 1) + sx - 1; + i_step_x = -xstride; + i_step_z = -zstride; + break; + case ROTATE_270: + i_start = zstride * (sz - 1); + i_step_x = -zstride; + i_step_z = xstride; + SWAP(s16, sx, sz); + break; + default: + i_start = 0; + i_step_x = xstride; + i_step_z = zstride; } s16 y_map = p.Y; for (s16 y = 0; y != sy; y++) { if ((slice_probs[y] != MTSCHEM_PROB_ALWAYS) && - (slice_probs[y] <= myrand_range(1, MTSCHEM_PROB_ALWAYS))) + (slice_probs[y] <= myrand_range(1, MTSCHEM_PROB_ALWAYS))) continue; for (s16 z = 0; z != sz; z++) { @@ -173,8 +163,10 @@ void Schematic::blitToVManip(MMVManip *vm, v3s16 p, Rotation rot, bool force_pla if (schemdata[i].getContent() == CONTENT_IGNORE) continue; - u8 placement_prob = schemdata[i].param1 & MTSCHEM_PROB_MASK; - bool force_place_node = schemdata[i].param1 & MTSCHEM_FORCE_PLACE; + u8 placement_prob = + schemdata[i].param1 & MTSCHEM_PROB_MASK; + bool force_place_node = + schemdata[i].param1 & MTSCHEM_FORCE_PLACE; if (placement_prob == MTSCHEM_PROB_NEVER) continue; @@ -187,7 +179,8 @@ void Schematic::blitToVManip(MMVManip *vm, v3s16 p, Rotation rot, bool force_pla } if ((placement_prob != MTSCHEM_PROB_ALWAYS) && - (placement_prob <= myrand_range(1, MTSCHEM_PROB_ALWAYS))) + (placement_prob <= + myrand_range(1, MTSCHEM_PROB_ALWAYS))) continue; vm->m_data[vi] = schemdata[i]; @@ -201,9 +194,8 @@ void Schematic::blitToVManip(MMVManip *vm, v3s16 p, Rotation rot, bool force_pla } } - -bool Schematic::placeOnVManip(MMVManip *vm, v3s16 p, u32 flags, - Rotation rot, bool force_place) +bool Schematic::placeOnVManip( + MMVManip *vm, v3s16 p, u32 flags, Rotation rot, bool force_place) { assert(vm != NULL); assert(schemdata && slice_probs); @@ -213,8 +205,8 @@ bool Schematic::placeOnVManip(MMVManip *vm, v3s16 p, u32 flags, if (rot == ROTATE_RAND) rot = (Rotation)myrand_range(ROTATE_0, ROTATE_270); - v3s16 s = (rot == ROTATE_90 || rot == ROTATE_270) ? - v3s16(size.Z, size.Y, size.X) : size; + v3s16 s = (rot == ROTATE_90 || rot == ROTATE_270) ? v3s16(size.Z, size.Y, size.X) + : size; //// Adjust placement position if necessary if (flags & DECO_PLACE_CENTER_X) @@ -229,8 +221,8 @@ bool Schematic::placeOnVManip(MMVManip *vm, v3s16 p, u32 flags, return vm->m_area.contains(VoxelArea(p, p + s - v3s16(1, 1, 1))); } -void Schematic::placeOnMap(ServerMap *map, v3s16 p, u32 flags, - Rotation rot, bool force_place) +void Schematic::placeOnMap( + ServerMap *map, v3s16 p, u32 flags, Rotation rot, bool force_place) { std::map lighting_modified_blocks; std::map modified_blocks; @@ -244,8 +236,8 @@ void Schematic::placeOnMap(ServerMap *map, v3s16 p, u32 flags, if (rot == ROTATE_RAND) rot = (Rotation)myrand_range(ROTATE_0, ROTATE_270); - v3s16 s = (rot == ROTATE_90 || rot == ROTATE_270) ? - v3s16(size.Z, size.Y, size.X) : size; + v3s16 s = (rot == ROTATE_90 || rot == ROTATE_270) ? v3s16(size.Z, size.Y, size.X) + : size; //// Adjust placement position if necessary if (flags & DECO_PLACE_CENTER_X) @@ -278,9 +270,7 @@ void Schematic::placeOnMap(ServerMap *map, v3s16 p, u32 flags, map->dispatchEvent(event); } - -bool Schematic::deserializeFromMts(std::istream *is, - std::vector *names) +bool Schematic::deserializeFromMts(std::istream *is, std::vector *names) { std::istream &ss = *is; content_t cignore = CONTENT_IGNORE; @@ -289,16 +279,20 @@ bool Schematic::deserializeFromMts(std::istream *is, //// Read signature u32 signature = readU32(ss); if (signature != MTSCHEM_FILE_SIGNATURE) { - errorstream << __FUNCTION__ << ": invalid schematic " - "file" << std::endl; + errorstream << __FUNCTION__ + << ": invalid schematic " + "file" + << std::endl; return false; } //// Read version u16 version = readU16(ss); if (version > MTSCHEM_FILE_VER_HIGHEST_READ) { - errorstream << __FUNCTION__ << ": unsupported schematic " - "file version" << std::endl; + errorstream << __FUNCTION__ + << ": unsupported schematic " + "file version" + << std::endl; return false; } @@ -306,7 +300,7 @@ bool Schematic::deserializeFromMts(std::istream *is, size = readV3S16(ss); //// Read Y-slice probability values - delete []slice_probs; + delete[] slice_probs; slice_probs = new u8[size.Y]; for (int y = 0; y != size.Y; y++) slice_probs[y] = (version >= 3) ? readU8(ss) : MTSCHEM_PROB_ALWAYS_OLD; @@ -330,11 +324,11 @@ bool Schematic::deserializeFromMts(std::istream *is, //// Read node data size_t nodecount = size.X * size.Y * size.Z; - delete []schemdata; + delete[] schemdata; schemdata = new MapNode[nodecount]; - MapNode::deSerializeBulk(ss, SER_FMT_VER_HIGHEST_READ, schemdata, - nodecount, 2, 2, true); + MapNode::deSerializeBulk( + ss, SER_FMT_VER_HIGHEST_READ, schemdata, nodecount, 2, 2, true); // Fix probability values for nodes that were ignore; removed in v2 if (version < 2) { @@ -357,17 +351,16 @@ bool Schematic::deserializeFromMts(std::istream *is, return true; } - -bool Schematic::serializeToMts(std::ostream *os, - const std::vector &names) const +bool Schematic::serializeToMts( + std::ostream *os, const std::vector &names) const { std::ostream &ss = *os; - writeU32(ss, MTSCHEM_FILE_SIGNATURE); // signature + writeU32(ss, MTSCHEM_FILE_SIGNATURE); // signature writeU16(ss, MTSCHEM_FILE_VER_HIGHEST_WRITE); // version - writeV3S16(ss, size); // schematic size + writeV3S16(ss, size); // schematic size - for (int y = 0; y != size.Y; y++) // Y slice probabilities + for (int y = 0; y != size.Y; y++) // Y slice probabilities writeU8(ss, slice_probs[y]); writeU16(ss, names.size()); // name count @@ -375,16 +368,14 @@ bool Schematic::serializeToMts(std::ostream *os, ss << serializeString(names[i]); // node names // compressed bulk node data - MapNode::serializeBulk(ss, SER_FMT_VER_HIGHEST_WRITE, - schemdata, size.X * size.Y * size.Z, 2, 2, true); + MapNode::serializeBulk(ss, SER_FMT_VER_HIGHEST_WRITE, schemdata, + size.X * size.Y * size.Z, 2, 2, true); return true; } - -bool Schematic::serializeToLua(std::ostream *os, - const std::vector &names, bool use_comments, - u32 indent_spaces) const +bool Schematic::serializeToLua(std::ostream *os, const std::vector &names, + bool use_comments, u32 indent_spaces) const { std::ostream &ss = *os; @@ -396,10 +387,8 @@ bool Schematic::serializeToLua(std::ostream *os, { ss << "schematic = {" << std::endl; ss << indent << "size = " - << "{x=" << size.X - << ", y=" << size.Y - << ", z=" << size.Z - << "}," << std::endl; + << "{x=" << size.X << ", y=" << size.Y << ", z=" << size.Z << "}," + << std::endl; } //// Write y-slice probabilities @@ -410,9 +399,8 @@ bool Schematic::serializeToLua(std::ostream *os, u8 probability = slice_probs[y] & MTSCHEM_PROB_MASK; ss << indent << indent << "{" - << "ypos=" << y - << ", prob=" << (u16)probability * 2 - << "}," << std::endl; + << "ypos=" << y << ", prob=" << (u16)probability * 2 << "}," + << std::endl; } ss << indent << "}," << std::endl; @@ -424,30 +412,32 @@ bool Schematic::serializeToLua(std::ostream *os, u32 i = 0; for (u16 z = 0; z != size.Z; z++) - for (u16 y = 0; y != size.Y; y++) { - if (use_comments) { - ss << std::endl - << indent << indent - << "-- z=" << z - << ", y=" << y << std::endl; + for (u16 y = 0; y != size.Y; y++) { + if (use_comments) { + ss << std::endl + << indent << indent << "-- z=" << z + << ", y=" << y << std::endl; + } + + for (u16 x = 0; x != size.X; x++, i++) { + u8 probability = schemdata[i].param1 & + MTSCHEM_PROB_MASK; + bool force_place = schemdata[i].param1 & + MTSCHEM_FORCE_PLACE; + + ss << indent << indent << "{" + << "name=\"" + << names[schemdata[i].getContent()] + << "\", prob=" << (u16)probability * 2 + << ", param2=" << (u16)schemdata[i].param2; + + if (force_place) + ss << ", force_place=true"; + + ss << "}," << std::endl; + } } - for (u16 x = 0; x != size.X; x++, i++) { - u8 probability = schemdata[i].param1 & MTSCHEM_PROB_MASK; - bool force_place = schemdata[i].param1 & MTSCHEM_FORCE_PLACE; - - ss << indent << indent << "{" - << "name=\"" << names[schemdata[i].getContent()] - << "\", prob=" << (u16)probability * 2 - << ", param2=" << (u16)schemdata[i].param2; - - if (force_place) - ss << ", force_place=true"; - - ss << "}," << std::endl; - } - } - ss << indent << "}," << std::endl; } @@ -456,14 +446,13 @@ bool Schematic::serializeToLua(std::ostream *os, return true; } - bool Schematic::loadSchematicFromFile(const std::string &filename, - const NodeDefManager *ndef, StringMap *replace_names) + const NodeDefManager *ndef, StringMap *replace_names) { std::ifstream is(filename.c_str(), std::ios_base::binary); if (!is.good()) { - errorstream << __FUNCTION__ << ": unable to open file '" - << filename << "'" << std::endl; + errorstream << __FUNCTION__ << ": unable to open file '" << filename + << "'" << std::endl; return false; } @@ -490,9 +479,8 @@ bool Schematic::loadSchematicFromFile(const std::string &filename, return true; } - -bool Schematic::saveSchematicToFile(const std::string &filename, - const NodeDefManager *ndef) +bool Schematic::saveSchematicToFile( + const std::string &filename, const NodeDefManager *ndef) { MapNode *orig_schemdata = schemdata; std::vector ndef_nodenames; @@ -518,7 +506,7 @@ bool Schematic::saveSchematicToFile(const std::string &filename, bool status = serializeToMts(&os, *names); if (ndef) { - delete []schemdata; + delete[] schemdata; schemdata = orig_schemdata; } @@ -528,7 +516,6 @@ bool Schematic::saveSchematicToFile(const std::string &filename, return fs::safeWriteToFile(filename, os.str()); } - bool Schematic::getSchematicFromMap(Map *map, v3s16 p1, v3s16 p2) { MMVManip *vm = new MMVManip(map); @@ -547,22 +534,20 @@ bool Schematic::getSchematicFromMap(Map *map, v3s16 p1, v3s16 p2) u32 i = 0; for (s16 z = p1.Z; z <= p2.Z; z++) - for (s16 y = p1.Y; y <= p2.Y; y++) { - u32 vi = vm->m_area.index(p1.X, y, z); - for (s16 x = p1.X; x <= p2.X; x++, i++, vi++) { - schemdata[i] = vm->m_data[vi]; - schemdata[i].param1 = MTSCHEM_PROB_ALWAYS; + for (s16 y = p1.Y; y <= p2.Y; y++) { + u32 vi = vm->m_area.index(p1.X, y, z); + for (s16 x = p1.X; x <= p2.X; x++, i++, vi++) { + schemdata[i] = vm->m_data[vi]; + schemdata[i].param1 = MTSCHEM_PROB_ALWAYS; + } } - } delete vm; return true; } - -void Schematic::applyProbabilities(v3s16 p0, - std::vector > *plist, - std::vector > *splist) +void Schematic::applyProbabilities(v3s16 p0, std::vector> *plist, + std::vector> *splist) { for (size_t i = 0; i != plist->size(); i++) { v3s16 p = (*plist)[i].first - p0; @@ -583,9 +568,8 @@ void Schematic::applyProbabilities(v3s16 p0, } } - void generate_nodelist_and_update_ids(MapNode *nodes, size_t nodecount, - std::vector *usednodes, const NodeDefManager *ndef) + std::vector *usednodes, const NodeDefManager *ndef) { std::unordered_map nodeidmap; content_t numids = 0; @@ -594,7 +578,8 @@ void generate_nodelist_and_update_ids(MapNode *nodes, size_t nodecount, content_t id; content_t c = nodes[i].getContent(); - std::unordered_map::const_iterator it = nodeidmap.find(c); + std::unordered_map::const_iterator it = + nodeidmap.find(c); if (it == nodeidmap.end()) { id = numids; numids++; diff --git a/src/mapgen/mg_schematic.h b/src/mapgen/mg_schematic.h index 6b31251b6..9ba96b4a1 100644 --- a/src/mapgen/mg_schematic.h +++ b/src/mapgen/mg_schematic.h @@ -68,29 +68,31 @@ class Server; //// Schematic constants #define MTSCHEM_FILE_SIGNATURE 0x4d54534d // 'MTSM' -#define MTSCHEM_FILE_VER_HIGHEST_READ 4 +#define MTSCHEM_FILE_VER_HIGHEST_READ 4 #define MTSCHEM_FILE_VER_HIGHEST_WRITE 4 -#define MTSCHEM_PROB_MASK 0x7F +#define MTSCHEM_PROB_MASK 0x7F -#define MTSCHEM_PROB_NEVER 0x00 -#define MTSCHEM_PROB_ALWAYS 0x7F +#define MTSCHEM_PROB_NEVER 0x00 +#define MTSCHEM_PROB_ALWAYS 0x7F #define MTSCHEM_PROB_ALWAYS_OLD 0xFF -#define MTSCHEM_FORCE_PLACE 0x80 +#define MTSCHEM_FORCE_PLACE 0x80 enum SchematicType { SCHEMATIC_NORMAL, }; -enum SchematicFormatType { +enum SchematicFormatType +{ SCHEM_FMT_HANDLE, SCHEM_FMT_MTS, SCHEM_FMT_LUA, }; -class Schematic : public ObjDef, public NodeResolver { +class Schematic : public ObjDef, public NodeResolver +{ public: Schematic(); virtual ~Schematic(); @@ -100,24 +102,24 @@ public: virtual void resolveNodeNames(); bool loadSchematicFromFile(const std::string &filename, - const NodeDefManager *ndef, StringMap *replace_names = NULL); - bool saveSchematicToFile(const std::string &filename, - const NodeDefManager *ndef); + const NodeDefManager *ndef, StringMap *replace_names = NULL); + bool saveSchematicToFile(const std::string &filename, const NodeDefManager *ndef); bool getSchematicFromMap(Map *map, v3s16 p1, v3s16 p2); bool deserializeFromMts(std::istream *is, std::vector *names); - bool serializeToMts(std::ostream *os, - const std::vector &names) const; + bool serializeToMts( + std::ostream *os, const std::vector &names) const; bool serializeToLua(std::ostream *os, const std::vector &names, - bool use_comments, u32 indent_spaces) const; + bool use_comments, u32 indent_spaces) const; void blitToVManip(MMVManip *vm, v3s16 p, Rotation rot, bool force_place); - bool placeOnVManip(MMVManip *vm, v3s16 p, u32 flags, Rotation rot, bool force_place); - void placeOnMap(ServerMap *map, v3s16 p, u32 flags, Rotation rot, bool force_place); + bool placeOnVManip( + MMVManip *vm, v3s16 p, u32 flags, Rotation rot, bool force_place); + void placeOnMap(ServerMap *map, v3s16 p, u32 flags, Rotation rot, + bool force_place); - void applyProbabilities(v3s16 p0, - std::vector > *plist, - std::vector > *splist); + void applyProbabilities(v3s16 p0, std::vector> *plist, + std::vector> *splist); std::vector c_nodes; u32 flags = 0; @@ -126,7 +128,8 @@ public: u8 *slice_probs = nullptr; }; -class SchematicManager : public ObjDefManager { +class SchematicManager : public ObjDefManager +{ public: SchematicManager(Server *server); virtual ~SchematicManager() = default; @@ -135,21 +138,15 @@ public: virtual void clear(); - const char *getObjectTitle() const - { - return "schematic"; - } + const char *getObjectTitle() const { return "schematic"; } - static Schematic *create(SchematicType type) - { - return new Schematic; - } + static Schematic *create(SchematicType type) { return new Schematic; } private: - SchematicManager() {}; + SchematicManager(){}; Server *m_server; }; void generate_nodelist_and_update_ids(MapNode *nodes, size_t nodecount, - std::vector *usednodes, const NodeDefManager *ndef); + std::vector *usednodes, const NodeDefManager *ndef); diff --git a/src/mapgen/treegen.cpp b/src/mapgen/treegen.cpp index e7e30c880..ddf518290 100644 --- a/src/mapgen/treegen.cpp +++ b/src/mapgen/treegen.cpp @@ -32,8 +32,8 @@ with this program; if not, write to the Free Software Foundation, Inc., namespace treegen { -void make_tree(MMVManip &vmanip, v3s16 p0, bool is_apple_tree, - const NodeDefManager *ndef, s32 seed) +void make_tree(MMVManip &vmanip, v3s16 p0, bool is_apple_tree, const NodeDefManager *ndef, + s32 seed) { /* NOTE: Tree-placing code is currently duplicated in the engine @@ -44,11 +44,14 @@ void make_tree(MMVManip &vmanip, v3s16 p0, bool is_apple_tree, MapNode leavesnode(ndef->getId("mapgen_leaves")); MapNode applenode(ndef->getId("mapgen_apple")); if (treenode == CONTENT_IGNORE) - errorstream << "Treegen: Mapgen alias 'mapgen_tree' is invalid!" << std::endl; + errorstream << "Treegen: Mapgen alias 'mapgen_tree' is invalid!" + << std::endl; if (leavesnode == CONTENT_IGNORE) - errorstream << "Treegen: Mapgen alias 'mapgen_leaves' is invalid!" << std::endl; + errorstream << "Treegen: Mapgen alias 'mapgen_leaves' is invalid!" + << std::endl; if (applenode == CONTENT_IGNORE) - errorstream << "Treegen: Mapgen alias 'mapgen_apple' is invalid!" << std::endl; + errorstream << "Treegen: Mapgen alias 'mapgen_apple' is invalid!" + << std::endl; PseudoRandom pr(seed); s16 trunk_h = pr.range(4, 5); @@ -72,57 +75,56 @@ void make_tree(MMVManip &vmanip, v3s16 p0, bool is_apple_tree, // Force leaves at near the end of the trunk s16 d = 1; for (s16 z = -d; z <= d; z++) - for (s16 y = -d; y <= d; y++) - for (s16 x = -d; x <= d; x++) { - leaves_d[leaves_a.index(v3s16(x, y, z))] = 1; - } + for (s16 y = -d; y <= d; y++) + for (s16 x = -d; x <= d; x++) { + leaves_d[leaves_a.index(v3s16(x, y, z))] = 1; + } // Add leaves randomly for (u32 iii = 0; iii < 7; iii++) { - v3s16 p( - pr.range(leaves_a.MinEdge.X, leaves_a.MaxEdge.X - d), - pr.range(leaves_a.MinEdge.Y, leaves_a.MaxEdge.Y - d), - pr.range(leaves_a.MinEdge.Z, leaves_a.MaxEdge.Z - d) - ); + v3s16 p(pr.range(leaves_a.MinEdge.X, leaves_a.MaxEdge.X - d), + pr.range(leaves_a.MinEdge.Y, leaves_a.MaxEdge.Y - d), + pr.range(leaves_a.MinEdge.Z, leaves_a.MaxEdge.Z - d)); for (s16 z = 0; z <= d; z++) - for (s16 y = 0; y <= d; y++) - for (s16 x = 0; x <= d; x++) { - leaves_d[leaves_a.index(p + v3s16(x, y, z))] = 1; - } + for (s16 y = 0; y <= d; y++) + for (s16 x = 0; x <= d; x++) { + leaves_d[leaves_a.index(p + v3s16(x, y, z))] = 1; + } } // Blit leaves to vmanip for (s16 z = leaves_a.MinEdge.Z; z <= leaves_a.MaxEdge.Z; z++) - for (s16 y = leaves_a.MinEdge.Y; y <= leaves_a.MaxEdge.Y; y++) { - v3s16 pmin(leaves_a.MinEdge.X, y, z); - u32 i = leaves_a.index(pmin); - u32 vi = vmanip.m_area.index(pmin + p1); - for (s16 x = leaves_a.MinEdge.X; x <= leaves_a.MaxEdge.X; x++) { - v3s16 p(x, y, z); - if (vmanip.m_area.contains(p + p1) && - (vmanip.m_data[vi].getContent() == CONTENT_AIR || - vmanip.m_data[vi].getContent() == CONTENT_IGNORE)) { - if (leaves_d[i] == 1) { - bool is_apple = pr.range(0, 99) < 10; - if (is_apple_tree && is_apple) - vmanip.m_data[vi] = applenode; - else - vmanip.m_data[vi] = leavesnode; + for (s16 y = leaves_a.MinEdge.Y; y <= leaves_a.MaxEdge.Y; y++) { + v3s16 pmin(leaves_a.MinEdge.X, y, z); + u32 i = leaves_a.index(pmin); + u32 vi = vmanip.m_area.index(pmin + p1); + for (s16 x = leaves_a.MinEdge.X; x <= leaves_a.MaxEdge.X; x++) { + v3s16 p(x, y, z); + if (vmanip.m_area.contains(p + p1) && + (vmanip.m_data[vi].getContent() == + CONTENT_AIR || + vmanip.m_data[vi].getContent() == + CONTENT_IGNORE)) { + if (leaves_d[i] == 1) { + bool is_apple = pr.range(0, 99) < 10; + if (is_apple_tree && is_apple) + vmanip.m_data[vi] = applenode; + else + vmanip.m_data[vi] = leavesnode; + } } + vi++; + i++; } - vi++; - i++; } - } } - // L-System tree LUA spawner -treegen::error spawn_ltree(ServerMap *map, v3s16 p0, - const NodeDefManager *ndef, const TreeDef &tree_definition) +treegen::error spawn_ltree(ServerMap *map, v3s16 p0, const NodeDefManager *ndef, + const TreeDef &tree_definition) { - std::map modified_blocks; + std::map modified_blocks; MMVManip vmanip(map); v3s16 tree_blockp = getNodeBlockPos(p0); treegen::error e; @@ -143,16 +145,15 @@ treegen::error spawn_ltree(ServerMap *map, v3s16 p0, return SUCCESS; } - -//L-System tree generator -treegen::error make_ltree(MMVManip &vmanip, v3s16 p0, - const NodeDefManager *ndef, TreeDef tree_definition) +// L-System tree generator +treegen::error make_ltree(MMVManip &vmanip, v3s16 p0, const NodeDefManager *ndef, + TreeDef tree_definition) { s32 seed; if (tree_definition.explicit_seed) seed = tree_definition.seed + 14002; else - seed = p0.X * 2 + p0.Y * 4 + p0.Z; // use the tree position to seed PRNG + seed = p0.X * 2 + p0.Y * 4 + p0.Z; // use the tree position to seed PRNG PseudoRandom ps(seed); // chance of inserting abcd rules @@ -161,7 +162,7 @@ treegen::error make_ltree(MMVManip &vmanip, v3s16 p0, double prop_c = 7; double prop_d = 6; - //randomize tree growth level, minimum=2 + // randomize tree growth level, minimum=2 s16 iterations = tree_definition.iterations; if (tree_definition.iterations_random_level > 0) iterations -= ps.range(0, tree_definition.iterations_random_level); @@ -170,19 +171,20 @@ treegen::error make_ltree(MMVManip &vmanip, v3s16 p0, s16 MAX_ANGLE_OFFSET = 5; double angle_in_radians = (double)tree_definition.angle * M_PI / 180; - double angleOffset_in_radians = (s16)(ps.range(0, 1) % MAX_ANGLE_OFFSET) * M_PI / 180; + double angleOffset_in_radians = + (s16)(ps.range(0, 1) % MAX_ANGLE_OFFSET) * M_PI / 180; - //initialize rotation matrix, position and stacks for branches + // initialize rotation matrix, position and stacks for branches core::matrix4 rotation; rotation = setRotationAxisRadians(rotation, M_PI / 2, v3f(0, 0, 1)); v3f position; position.X = p0.X; position.Y = p0.Y; position.Z = p0.Z; - std::stack stack_orientation; - std::stack stack_position; + std::stack stack_orientation; + std::stack stack_position; - //generate axiom + // generate axiom std::string axiom = tree_definition.initial_axiom; for (s16 i = 0; i < iterations; i++) { std::string temp; @@ -227,42 +229,28 @@ treegen::error make_ltree(MMVManip &vmanip, v3s16 p0, // Add trunk nodes below a wide trunk to avoid gaps when tree is on sloping ground if (tree_definition.trunk_type == "double") { - tree_trunk_placement( - vmanip, - v3f(position.X + 1, position.Y - 1, position.Z), - tree_definition - ); - tree_trunk_placement( - vmanip, - v3f(position.X, position.Y - 1, position.Z + 1), - tree_definition - ); - tree_trunk_placement( - vmanip, - v3f(position.X + 1, position.Y - 1, position.Z + 1), - tree_definition - ); + tree_trunk_placement(vmanip, + v3f(position.X + 1, position.Y - 1, position.Z), + tree_definition); + tree_trunk_placement(vmanip, + v3f(position.X, position.Y - 1, position.Z + 1), + tree_definition); + tree_trunk_placement(vmanip, + v3f(position.X + 1, position.Y - 1, position.Z + 1), + tree_definition); } else if (tree_definition.trunk_type == "crossed") { - tree_trunk_placement( - vmanip, - v3f(position.X + 1, position.Y - 1, position.Z), - tree_definition - ); - tree_trunk_placement( - vmanip, - v3f(position.X - 1, position.Y - 1, position.Z), - tree_definition - ); - tree_trunk_placement( - vmanip, - v3f(position.X, position.Y - 1, position.Z + 1), - tree_definition - ); - tree_trunk_placement( - vmanip, - v3f(position.X, position.Y - 1, position.Z - 1), - tree_definition - ); + tree_trunk_placement(vmanip, + v3f(position.X + 1, position.Y - 1, position.Z), + tree_definition); + tree_trunk_placement(vmanip, + v3f(position.X - 1, position.Y - 1, position.Z), + tree_definition); + tree_trunk_placement(vmanip, + v3f(position.X, position.Y - 1, position.Z + 1), + tree_definition); + tree_trunk_placement(vmanip, + v3f(position.X, position.Y - 1, position.Z - 1), + tree_definition); } /* build tree out of generated axiom @@ -293,7 +281,7 @@ treegen::error make_ltree(MMVManip &vmanip, v3s16 p0, */ - s16 x,y,z; + s16 x, y, z; for (s16 i = 0; i < (s16)axiom.size(); i++) { char axiom_char = axiom.at(i); core::matrix4 temp_rotation; @@ -306,164 +294,149 @@ treegen::error make_ltree(MMVManip &vmanip, v3s16 p0, position += dir; break; case 'T': - tree_trunk_placement( - vmanip, - v3f(position.X, position.Y, position.Z), - tree_definition - ); + tree_trunk_placement(vmanip, + v3f(position.X, position.Y, position.Z), + tree_definition); if (tree_definition.trunk_type == "double" && !tree_definition.thin_branches) { - tree_trunk_placement( - vmanip, - v3f(position.X + 1, position.Y, position.Z), - tree_definition - ); - tree_trunk_placement( - vmanip, - v3f(position.X, position.Y, position.Z + 1), - tree_definition - ); - tree_trunk_placement( - vmanip, - v3f(position.X + 1, position.Y, position.Z + 1), - tree_definition - ); + tree_trunk_placement(vmanip, + v3f(position.X + 1, position.Y, + position.Z), + tree_definition); + tree_trunk_placement(vmanip, + v3f(position.X, position.Y, + position.Z + 1), + tree_definition); + tree_trunk_placement(vmanip, + v3f(position.X + 1, position.Y, + position.Z + 1), + tree_definition); } else if (tree_definition.trunk_type == "crossed" && !tree_definition.thin_branches) { - tree_trunk_placement( - vmanip, - v3f(position.X + 1, position.Y, position.Z), - tree_definition - ); - tree_trunk_placement( - vmanip, - v3f(position.X - 1, position.Y, position.Z), - tree_definition - ); - tree_trunk_placement( - vmanip, - v3f(position.X, position.Y, position.Z + 1), - tree_definition - ); - tree_trunk_placement( - vmanip, - v3f(position.X, position.Y, position.Z - 1), - tree_definition - ); + tree_trunk_placement(vmanip, + v3f(position.X + 1, position.Y, + position.Z), + tree_definition); + tree_trunk_placement(vmanip, + v3f(position.X - 1, position.Y, + position.Z), + tree_definition); + tree_trunk_placement(vmanip, + v3f(position.X, position.Y, + position.Z + 1), + tree_definition); + tree_trunk_placement(vmanip, + v3f(position.X, position.Y, + position.Z - 1), + tree_definition); } dir = v3f(1, 0, 0); dir = transposeMatrix(rotation, dir); position += dir; break; case 'F': - tree_trunk_placement( - vmanip, - v3f(position.X, position.Y, position.Z), - tree_definition - ); + tree_trunk_placement(vmanip, + v3f(position.X, position.Y, position.Z), + tree_definition); if ((stack_orientation.empty() && - tree_definition.trunk_type == "double") || + tree_definition.trunk_type == "double") || (!stack_orientation.empty() && - tree_definition.trunk_type == "double" && - !tree_definition.thin_branches)) { - tree_trunk_placement( - vmanip, - v3f(position.X + 1, position.Y, position.Z), - tree_definition - ); - tree_trunk_placement( - vmanip, - v3f(position.X, position.Y, position.Z + 1), - tree_definition - ); - tree_trunk_placement( - vmanip, - v3f(position.X + 1, position.Y, position.Z + 1), - tree_definition - ); + tree_definition.trunk_type == + "double" && + !tree_definition.thin_branches)) { + tree_trunk_placement(vmanip, + v3f(position.X + 1, position.Y, + position.Z), + tree_definition); + tree_trunk_placement(vmanip, + v3f(position.X, position.Y, + position.Z + 1), + tree_definition); + tree_trunk_placement(vmanip, + v3f(position.X + 1, position.Y, + position.Z + 1), + tree_definition); } else if ((stack_orientation.empty() && - tree_definition.trunk_type == "crossed") || + tree_definition.trunk_type == + "crossed") || (!stack_orientation.empty() && - tree_definition.trunk_type == "crossed" && - !tree_definition.thin_branches)) { - tree_trunk_placement( - vmanip, - v3f(position.X + 1, position.Y, position.Z), - tree_definition - ); - tree_trunk_placement( - vmanip, - v3f(position.X - 1, position.Y, position.Z), - tree_definition - ); - tree_trunk_placement( - vmanip, - v3f(position.X, position.Y, position.Z + 1), - tree_definition - ); - tree_trunk_placement( - vmanip, - v3f(position.X, position.Y, position.Z - 1), - tree_definition - ); - } if (!stack_orientation.empty()) { + tree_definition.trunk_type == + "crossed" && + !tree_definition.thin_branches)) { + tree_trunk_placement(vmanip, + v3f(position.X + 1, position.Y, + position.Z), + tree_definition); + tree_trunk_placement(vmanip, + v3f(position.X - 1, position.Y, + position.Z), + tree_definition); + tree_trunk_placement(vmanip, + v3f(position.X, position.Y, + position.Z + 1), + tree_definition); + tree_trunk_placement(vmanip, + v3f(position.X, position.Y, + position.Z - 1), + tree_definition); + } + if (!stack_orientation.empty()) { s16 size = 1; for (x = -size; x <= size; x++) - for (y = -size; y <= size; y++) - for (z = -size; z <= size; z++) { - if (abs(x) == size && - abs(y) == size && - abs(z) == size) { - tree_leaves_placement( - vmanip, - v3f(position.X + x + 1, position.Y + y, - position.Z + z), - ps.next(), - tree_definition - ); - tree_leaves_placement( - vmanip, - v3f(position.X + x - 1, position.Y + y, - position.Z + z), - ps.next(), - tree_definition - ); - tree_leaves_placement( - vmanip,v3f(position.X + x, position.Y + y, - position.Z + z + 1), - ps.next(), - tree_definition - ); - tree_leaves_placement( - vmanip,v3f(position.X + x, position.Y + y, - position.Z + z - 1), - ps.next(), - tree_definition - ); - } - } + for (y = -size; y <= size; y++) + for (z = -size; z <= size; z++) { + if (abs(x) == size && + abs(y) == size && + abs(z) == size) { + tree_leaves_placement( + vmanip, + v3f(position.X + x + 1, + position.Y + y, + position.Z + z), + ps.next(), + tree_definition); + tree_leaves_placement( + vmanip, + v3f(position.X + x - 1, + position.Y + y, + position.Z + z), + ps.next(), + tree_definition); + tree_leaves_placement( + vmanip, + v3f(position.X + x, + position.Y + y, + position.Z + z + + 1), + ps.next(), + tree_definition); + tree_leaves_placement( + vmanip, + v3f(position.X + x, + position.Y + y, + position.Z + z - + 1), + ps.next(), + tree_definition); + } + } } dir = v3f(1, 0, 0); dir = transposeMatrix(rotation, dir); position += dir; break; case 'f': - tree_single_leaves_placement( - vmanip, - v3f(position.X, position.Y, position.Z), - ps.next(), - tree_definition - ); + tree_single_leaves_placement(vmanip, + v3f(position.X, position.Y, position.Z), + ps.next(), tree_definition); dir = v3f(1, 0, 0); dir = transposeMatrix(rotation, dir); position += dir; break; case 'R': - tree_fruit_placement( - vmanip, - v3f(position.X, position.Y, position.Z), - tree_definition - ); + tree_fruit_placement(vmanip, + v3f(position.X, position.Y, position.Z), + tree_definition); dir = v3f(1, 0, 0); dir = transposeMatrix(rotation, dir); position += dir; @@ -485,37 +458,41 @@ treegen::error make_ltree(MMVManip &vmanip, v3s16 p0, case '+': temp_rotation.makeIdentity(); temp_rotation = setRotationAxisRadians(temp_rotation, - angle_in_radians + angleOffset_in_radians, v3f(0, 0, 1)); + angle_in_radians + angleOffset_in_radians, + v3f(0, 0, 1)); rotation *= temp_rotation; break; case '-': temp_rotation.makeIdentity(); temp_rotation = setRotationAxisRadians(temp_rotation, - angle_in_radians + angleOffset_in_radians, v3f(0, 0, -1)); + angle_in_radians + angleOffset_in_radians, + v3f(0, 0, -1)); rotation *= temp_rotation; break; case '&': temp_rotation.makeIdentity(); temp_rotation = setRotationAxisRadians(temp_rotation, - angle_in_radians + angleOffset_in_radians, v3f(0, 1, 0)); + angle_in_radians + angleOffset_in_radians, + v3f(0, 1, 0)); rotation *= temp_rotation; break; case '^': temp_rotation.makeIdentity(); temp_rotation = setRotationAxisRadians(temp_rotation, - angle_in_radians + angleOffset_in_radians, v3f(0, -1, 0)); + angle_in_radians + angleOffset_in_radians, + v3f(0, -1, 0)); rotation *= temp_rotation; break; case '*': temp_rotation.makeIdentity(); - temp_rotation = setRotationAxisRadians(temp_rotation, - angle_in_radians, v3f(1, 0, 0)); + temp_rotation = setRotationAxisRadians( + temp_rotation, angle_in_radians, v3f(1, 0, 0)); rotation *= temp_rotation; break; case '/': temp_rotation.makeIdentity(); - temp_rotation = setRotationAxisRadians(temp_rotation, - angle_in_radians, v3f(-1, 0, 0)); + temp_rotation = setRotationAxisRadians( + temp_rotation, angle_in_radians, v3f(-1, 0, 0)); rotation *= temp_rotation; break; default: @@ -526,20 +503,18 @@ treegen::error make_ltree(MMVManip &vmanip, v3s16 p0, return SUCCESS; } - void tree_node_placement(MMVManip &vmanip, v3f p0, MapNode node) { v3s16 p1 = v3s16(myround(p0.X), myround(p0.Y), myround(p0.Z)); if (!vmanip.m_area.contains(p1)) return; u32 vi = vmanip.m_area.index(p1); - if (vmanip.m_data[vi].getContent() != CONTENT_AIR - && vmanip.m_data[vi].getContent() != CONTENT_IGNORE) + if (vmanip.m_data[vi].getContent() != CONTENT_AIR && + vmanip.m_data[vi].getContent() != CONTENT_IGNORE) return; vmanip.m_data[vmanip.m_area.index(p1)] = node; } - void tree_trunk_placement(MMVManip &vmanip, v3f p0, TreeDef &tree_definition) { v3s16 p1 = v3s16(myround(p0.X), myround(p0.Y), myround(p0.Z)); @@ -547,17 +522,16 @@ void tree_trunk_placement(MMVManip &vmanip, v3f p0, TreeDef &tree_definition) return; u32 vi = vmanip.m_area.index(p1); content_t current_node = vmanip.m_data[vi].getContent(); - if (current_node != CONTENT_AIR && current_node != CONTENT_IGNORE - && current_node != tree_definition.leavesnode.getContent() - && current_node != tree_definition.leaves2node.getContent() - && current_node != tree_definition.fruitnode.getContent()) + if (current_node != CONTENT_AIR && current_node != CONTENT_IGNORE && + current_node != tree_definition.leavesnode.getContent() && + current_node != tree_definition.leaves2node.getContent() && + current_node != tree_definition.fruitnode.getContent()) return; vmanip.m_data[vi] = tree_definition.trunknode; } - -void tree_leaves_placement(MMVManip &vmanip, v3f p0, - PseudoRandom ps, TreeDef &tree_definition) +void tree_leaves_placement( + MMVManip &vmanip, v3f p0, PseudoRandom ps, TreeDef &tree_definition) { MapNode leavesnode = tree_definition.leavesnode; if (ps.range(1, 100) > 100 - tree_definition.leaves2_chance) @@ -566,12 +540,13 @@ void tree_leaves_placement(MMVManip &vmanip, v3f p0, if (!vmanip.m_area.contains(p1)) return; u32 vi = vmanip.m_area.index(p1); - if (vmanip.m_data[vi].getContent() != CONTENT_AIR - && vmanip.m_data[vi].getContent() != CONTENT_IGNORE) + if (vmanip.m_data[vi].getContent() != CONTENT_AIR && + vmanip.m_data[vi].getContent() != CONTENT_IGNORE) return; if (tree_definition.fruit_chance > 0) { if (ps.range(1, 100) > 100 - tree_definition.fruit_chance) - vmanip.m_data[vmanip.m_area.index(p1)] = tree_definition.fruitnode; + vmanip.m_data[vmanip.m_area.index(p1)] = + tree_definition.fruitnode; else vmanip.m_data[vmanip.m_area.index(p1)] = leavesnode; } else if (ps.range(1, 100) > 20) { @@ -579,9 +554,8 @@ void tree_leaves_placement(MMVManip &vmanip, v3f p0, } } - -void tree_single_leaves_placement(MMVManip &vmanip, v3f p0, - PseudoRandom ps, TreeDef &tree_definition) +void tree_single_leaves_placement( + MMVManip &vmanip, v3f p0, PseudoRandom ps, TreeDef &tree_definition) { MapNode leavesnode = tree_definition.leavesnode; if (ps.range(1, 100) > 100 - tree_definition.leaves2_chance) @@ -590,38 +564,36 @@ void tree_single_leaves_placement(MMVManip &vmanip, v3f p0, if (!vmanip.m_area.contains(p1)) return; u32 vi = vmanip.m_area.index(p1); - if (vmanip.m_data[vi].getContent() != CONTENT_AIR - && vmanip.m_data[vi].getContent() != CONTENT_IGNORE) + if (vmanip.m_data[vi].getContent() != CONTENT_AIR && + vmanip.m_data[vi].getContent() != CONTENT_IGNORE) return; vmanip.m_data[vmanip.m_area.index(p1)] = leavesnode; } - void tree_fruit_placement(MMVManip &vmanip, v3f p0, TreeDef &tree_definition) { v3s16 p1 = v3s16(myround(p0.X), myround(p0.Y), myround(p0.Z)); if (!vmanip.m_area.contains(p1)) return; u32 vi = vmanip.m_area.index(p1); - if (vmanip.m_data[vi].getContent() != CONTENT_AIR - && vmanip.m_data[vi].getContent() != CONTENT_IGNORE) + if (vmanip.m_data[vi].getContent() != CONTENT_AIR && + vmanip.m_data[vi].getContent() != CONTENT_IGNORE) return; vmanip.m_data[vmanip.m_area.index(p1)] = tree_definition.fruitnode; } - irr::core::matrix4 setRotationAxisRadians(irr::core::matrix4 M, double angle, v3f axis) { double c = cos(angle); double s = sin(angle); double t = 1.0 - c; - double tx = t * axis.X; - double ty = t * axis.Y; - double tz = t * axis.Z; - double sx = s * axis.X; - double sy = s * axis.Y; - double sz = s * axis.Z; + double tx = t * axis.X; + double ty = t * axis.Y; + double tz = t * axis.Z; + double sx = s * axis.X; + double sy = s * axis.Y; + double sz = s * axis.Z; M[0] = tx * axis.X + c; M[1] = tx * axis.Y + sz; @@ -631,65 +603,64 @@ irr::core::matrix4 setRotationAxisRadians(irr::core::matrix4 M, double angle, v3 M[5] = ty * axis.Y + c; M[6] = ty * axis.Z + sx; - M[8] = tz * axis.X + sy; - M[9] = tz * axis.Y - sx; + M[8] = tz * axis.X + sy; + M[9] = tz * axis.Y - sx; M[10] = tz * axis.Z + c; return M; } - v3f transposeMatrix(irr::core::matrix4 M, v3f v) { v3f translated; - double x = M[0] * v.X + M[4] * v.Y + M[8] * v.Z +M[12]; - double y = M[1] * v.X + M[5] * v.Y + M[9] * v.Z +M[13]; - double z = M[2] * v.X + M[6] * v.Y + M[10] * v.Z +M[14]; + double x = M[0] * v.X + M[4] * v.Y + M[8] * v.Z + M[12]; + double y = M[1] * v.X + M[5] * v.Y + M[9] * v.Z + M[13]; + double z = M[2] * v.X + M[6] * v.Y + M[10] * v.Z + M[14]; translated.X = x; translated.Y = y; translated.Z = z; return translated; } - -void make_jungletree(MMVManip &vmanip, v3s16 p0, const NodeDefManager *ndef, - s32 seed) +void make_jungletree(MMVManip &vmanip, v3s16 p0, const NodeDefManager *ndef, s32 seed) { /* NOTE: Tree-placing code is currently duplicated in the engine and in games that have saplings; both are deprecated but not replaced yet */ - content_t c_tree = ndef->getId("mapgen_jungletree"); + content_t c_tree = ndef->getId("mapgen_jungletree"); content_t c_leaves = ndef->getId("mapgen_jungleleaves"); if (c_tree == CONTENT_IGNORE) c_tree = ndef->getId("mapgen_tree"); if (c_leaves == CONTENT_IGNORE) c_leaves = ndef->getId("mapgen_leaves"); if (c_tree == CONTENT_IGNORE) - errorstream << "Treegen: Mapgen alias 'mapgen_jungletree' is invalid!" << std::endl; + errorstream << "Treegen: Mapgen alias 'mapgen_jungletree' is invalid!" + << std::endl; if (c_leaves == CONTENT_IGNORE) - errorstream << "Treegen: Mapgen alias 'mapgen_jungleleaves' is invalid!" << std::endl; + errorstream << "Treegen: Mapgen alias 'mapgen_jungleleaves' is invalid!" + << std::endl; MapNode treenode(c_tree); MapNode leavesnode(c_leaves); PseudoRandom pr(seed); - for (s16 x= -1; x <= 1; x++) - for (s16 z= -1; z <= 1; z++) { - if (pr.range(0, 2) == 0) - continue; - v3s16 p1 = p0 + v3s16(x, 0, z); - v3s16 p2 = p0 + v3s16(x, -1, z); - u32 vi1 = vmanip.m_area.index(p1); - u32 vi2 = vmanip.m_area.index(p2); + for (s16 x = -1; x <= 1; x++) + for (s16 z = -1; z <= 1; z++) { + if (pr.range(0, 2) == 0) + continue; + v3s16 p1 = p0 + v3s16(x, 0, z); + v3s16 p2 = p0 + v3s16(x, -1, z); + u32 vi1 = vmanip.m_area.index(p1); + u32 vi2 = vmanip.m_area.index(p2); - if (vmanip.m_area.contains(p2) && - vmanip.m_data[vi2].getContent() == CONTENT_AIR) - vmanip.m_data[vi2] = treenode; - else if (vmanip.m_area.contains(p1) && - vmanip.m_data[vi1].getContent() == CONTENT_AIR) - vmanip.m_data[vi1] = treenode; - } + if (vmanip.m_area.contains(p2) && + vmanip.m_data[vi2].getContent() == CONTENT_AIR) + vmanip.m_data[vi2] = treenode; + else if (vmanip.m_area.contains(p1) && + vmanip.m_data[vi1].getContent() == CONTENT_AIR) + vmanip.m_data[vi1] = treenode; + } vmanip.m_data[vmanip.m_area.index(p0)] = treenode; s16 trunk_h = pr.range(8, 12); @@ -706,7 +677,7 @@ void make_jungletree(MMVManip &vmanip, v3s16 p0, const NodeDefManager *ndef, p1.Y -= 1; VoxelArea leaves_a(v3s16(-3, -2, -3), v3s16(3, 2, 3)); - //SharedPtr leaves_d(new u8[leaves_a.getVolume()]); + // SharedPtr leaves_d(new u8[leaves_a.getVolume()]); Buffer leaves_d(leaves_a.getVolume()); for (s32 i = 0; i < leaves_a.getVolume(); i++) leaves_d[i] = 0; @@ -714,56 +685,54 @@ void make_jungletree(MMVManip &vmanip, v3s16 p0, const NodeDefManager *ndef, // Force leaves at near the end of the trunk s16 d = 1; for (s16 z = -d; z <= d; z++) - for (s16 y = -d; y <= d; y++) - for (s16 x = -d; x <= d; x++) { - leaves_d[leaves_a.index(v3s16(x,y,z))] = 1; - } + for (s16 y = -d; y <= d; y++) + for (s16 x = -d; x <= d; x++) { + leaves_d[leaves_a.index(v3s16(x, y, z))] = 1; + } // Add leaves randomly for (u32 iii = 0; iii < 30; iii++) { - v3s16 p( - pr.range(leaves_a.MinEdge.X, leaves_a.MaxEdge.X - d), - pr.range(leaves_a.MinEdge.Y, leaves_a.MaxEdge.Y - d), - pr.range(leaves_a.MinEdge.Z, leaves_a.MaxEdge.Z - d) - ); + v3s16 p(pr.range(leaves_a.MinEdge.X, leaves_a.MaxEdge.X - d), + pr.range(leaves_a.MinEdge.Y, leaves_a.MaxEdge.Y - d), + pr.range(leaves_a.MinEdge.Z, leaves_a.MaxEdge.Z - d)); for (s16 z = 0; z <= d; z++) - for (s16 y = 0; y <= d; y++) - for (s16 x = 0; x <= d; x++) { - leaves_d[leaves_a.index(p + v3s16(x, y, z))] = 1; - } + for (s16 y = 0; y <= d; y++) + for (s16 x = 0; x <= d; x++) { + leaves_d[leaves_a.index(p + v3s16(x, y, z))] = 1; + } } // Blit leaves to vmanip for (s16 z = leaves_a.MinEdge.Z; z <= leaves_a.MaxEdge.Z; z++) - for (s16 y = leaves_a.MinEdge.Y; y <= leaves_a.MaxEdge.Y; y++) { - v3s16 pmin(leaves_a.MinEdge.X, y, z); - u32 i = leaves_a.index(pmin); - u32 vi = vmanip.m_area.index(pmin + p1); - for (s16 x = leaves_a.MinEdge.X; x <= leaves_a.MaxEdge.X; x++) { - v3s16 p(x, y, z); - if (vmanip.m_area.contains(p + p1) && - (vmanip.m_data[vi].getContent() == CONTENT_AIR || - vmanip.m_data[vi].getContent() == CONTENT_IGNORE)) { - if (leaves_d[i] == 1) - vmanip.m_data[vi] = leavesnode; + for (s16 y = leaves_a.MinEdge.Y; y <= leaves_a.MaxEdge.Y; y++) { + v3s16 pmin(leaves_a.MinEdge.X, y, z); + u32 i = leaves_a.index(pmin); + u32 vi = vmanip.m_area.index(pmin + p1); + for (s16 x = leaves_a.MinEdge.X; x <= leaves_a.MaxEdge.X; x++) { + v3s16 p(x, y, z); + if (vmanip.m_area.contains(p + p1) && + (vmanip.m_data[vi].getContent() == + CONTENT_AIR || + vmanip.m_data[vi].getContent() == + CONTENT_IGNORE)) { + if (leaves_d[i] == 1) + vmanip.m_data[vi] = leavesnode; + } + vi++; + i++; } - vi++; - i++; } - } } - -void make_pine_tree(MMVManip &vmanip, v3s16 p0, const NodeDefManager *ndef, - s32 seed) +void make_pine_tree(MMVManip &vmanip, v3s16 p0, const NodeDefManager *ndef, s32 seed) { /* NOTE: Tree-placing code is currently duplicated in the engine and in games that have saplings; both are deprecated but not replaced yet */ - content_t c_tree = ndef->getId("mapgen_pine_tree"); + content_t c_tree = ndef->getId("mapgen_pine_tree"); content_t c_leaves = ndef->getId("mapgen_pine_needles"); content_t c_snow = ndef->getId("mapgen_snow"); if (c_tree == CONTENT_IGNORE) @@ -773,9 +742,11 @@ void make_pine_tree(MMVManip &vmanip, v3s16 p0, const NodeDefManager *ndef, if (c_snow == CONTENT_IGNORE) c_snow = CONTENT_AIR; if (c_tree == CONTENT_IGNORE) - errorstream << "Treegen: Mapgen alias 'mapgen_pine_tree' is invalid!" << std::endl; + errorstream << "Treegen: Mapgen alias 'mapgen_pine_tree' is invalid!" + << std::endl; if (c_leaves == CONTENT_IGNORE) - errorstream << "Treegen: Mapgen alias 'mapgen_pine_needles' is invalid!" << std::endl; + errorstream << "Treegen: Mapgen alias 'mapgen_pine_needles' is invalid!" + << std::endl; MapNode treenode(c_tree); MapNode leavesnode(c_leaves); @@ -805,7 +776,7 @@ void make_pine_tree(MMVManip &vmanip, v3s16 p0, const NodeDefManager *ndef, for (s16 yy = -1; yy <= 1; yy++) { for (s16 zz = -dev; zz <= dev; zz++) { u32 i = leaves_a.index(v3s16(-dev, yy, zz)); - u32 ia = leaves_a.index(v3s16(-dev, yy+1, zz)); + u32 ia = leaves_a.index(v3s16(-dev, yy + 1, zz)); for (s16 xx = -dev; xx <= dev; xx++) { if (pr.range(0, 20) <= 19 - dev) { leaves_d[i] = 1; @@ -863,25 +834,28 @@ void make_pine_tree(MMVManip &vmanip, v3s16 p0, const NodeDefManager *ndef, // Blit leaves to vmanip for (s16 z = leaves_a.MinEdge.Z; z <= leaves_a.MaxEdge.Z; z++) - for (s16 y = leaves_a.MinEdge.Y; y <= leaves_a.MaxEdge.Y; y++) { - v3s16 pmin(leaves_a.MinEdge.X, y, z); - u32 i = leaves_a.index(pmin); - u32 vi = vmanip.m_area.index(pmin + p1); - for (s16 x = leaves_a.MinEdge.X; x <= leaves_a.MaxEdge.X; x++) { - v3s16 p(x, y, z); - if (vmanip.m_area.contains(p + p1) && - (vmanip.m_data[vi].getContent() == CONTENT_AIR || - vmanip.m_data[vi].getContent() == CONTENT_IGNORE || - vmanip.m_data[vi] == snownode)) { - if (leaves_d[i] == 1) - vmanip.m_data[vi] = leavesnode; - else if (leaves_d[i] == 2) - vmanip.m_data[vi] = snownode; + for (s16 y = leaves_a.MinEdge.Y; y <= leaves_a.MaxEdge.Y; y++) { + v3s16 pmin(leaves_a.MinEdge.X, y, z); + u32 i = leaves_a.index(pmin); + u32 vi = vmanip.m_area.index(pmin + p1); + for (s16 x = leaves_a.MinEdge.X; x <= leaves_a.MaxEdge.X; x++) { + v3s16 p(x, y, z); + if (vmanip.m_area.contains(p + p1) && + (vmanip.m_data[vi].getContent() == + CONTENT_AIR || + vmanip.m_data[vi].getContent() == + CONTENT_IGNORE || + vmanip.m_data[vi] == + snownode)) { + if (leaves_d[i] == 1) + vmanip.m_data[vi] = leavesnode; + else if (leaves_d[i] == 2) + vmanip.m_data[vi] = snownode; + } + vi++; + i++; } - vi++; - i++; } - } } }; // namespace treegen diff --git a/src/mapgen/treegen.h b/src/mapgen/treegen.h index 447baabb3..f2ee36a4e 100644 --- a/src/mapgen/treegen.h +++ b/src/mapgen/treegen.h @@ -28,66 +28,64 @@ class MMVManip; class NodeDefManager; class ServerMap; -namespace treegen { +namespace treegen +{ - enum error { - SUCCESS, - UNBALANCED_BRACKETS - }; +enum error +{ + SUCCESS, + UNBALANCED_BRACKETS +}; - struct TreeDef { - std::string initial_axiom; - std::string rules_a; - std::string rules_b; - std::string rules_c; - std::string rules_d; +struct TreeDef +{ + std::string initial_axiom; + std::string rules_a; + std::string rules_b; + std::string rules_c; + std::string rules_d; - MapNode trunknode; - MapNode leavesnode; - MapNode leaves2node; + MapNode trunknode; + MapNode leavesnode; + MapNode leaves2node; - int leaves2_chance; - int angle; - int iterations; - int iterations_random_level; - std::string trunk_type; - bool thin_branches; - MapNode fruitnode; - int fruit_chance; - s32 seed; - bool explicit_seed; - }; + int leaves2_chance; + int angle; + int iterations; + int iterations_random_level; + std::string trunk_type; + bool thin_branches; + MapNode fruitnode; + int fruit_chance; + s32 seed; + bool explicit_seed; +}; - // Add default tree - void make_tree(MMVManip &vmanip, v3s16 p0, - bool is_apple_tree, const NodeDefManager *ndef, s32 seed); - // Add jungle tree - void make_jungletree(MMVManip &vmanip, v3s16 p0, - const NodeDefManager *ndef, s32 seed); - // Add pine tree - void make_pine_tree(MMVManip &vmanip, v3s16 p0, - const NodeDefManager *ndef, s32 seed); +// Add default tree +void make_tree(MMVManip &vmanip, v3s16 p0, bool is_apple_tree, const NodeDefManager *ndef, + s32 seed); +// Add jungle tree +void make_jungletree(MMVManip &vmanip, v3s16 p0, const NodeDefManager *ndef, s32 seed); +// Add pine tree +void make_pine_tree(MMVManip &vmanip, v3s16 p0, const NodeDefManager *ndef, s32 seed); - // Add L-Systems tree (used by engine) - treegen::error make_ltree(MMVManip &vmanip, v3s16 p0, - const NodeDefManager *ndef, TreeDef tree_definition); - // Spawn L-systems tree from LUA - treegen::error spawn_ltree (ServerMap *map, v3s16 p0, - const NodeDefManager *ndef, const TreeDef &tree_definition); +// Add L-Systems tree (used by engine) +treegen::error make_ltree(MMVManip &vmanip, v3s16 p0, const NodeDefManager *ndef, + TreeDef tree_definition); +// Spawn L-systems tree from LUA +treegen::error spawn_ltree(ServerMap *map, v3s16 p0, const NodeDefManager *ndef, + const TreeDef &tree_definition); - // L-System tree gen helper functions - void tree_node_placement(MMVManip &vmanip, v3f p0, - MapNode node); - void tree_trunk_placement(MMVManip &vmanip, v3f p0, - TreeDef &tree_definition); - void tree_leaves_placement(MMVManip &vmanip, v3f p0, - PseudoRandom ps, TreeDef &tree_definition); - void tree_single_leaves_placement(MMVManip &vmanip, v3f p0, - PseudoRandom ps, TreeDef &tree_definition); - void tree_fruit_placement(MMVManip &vmanip, v3f p0, - TreeDef &tree_definition); - irr::core::matrix4 setRotationAxisRadians(irr::core::matrix4 M, double angle, v3f axis); +// L-System tree gen helper functions +void tree_node_placement(MMVManip &vmanip, v3f p0, MapNode node); +void tree_trunk_placement(MMVManip &vmanip, v3f p0, TreeDef &tree_definition); +void tree_leaves_placement( + MMVManip &vmanip, v3f p0, PseudoRandom ps, TreeDef &tree_definition); +void tree_single_leaves_placement( + MMVManip &vmanip, v3f p0, PseudoRandom ps, TreeDef &tree_definition); +void tree_fruit_placement(MMVManip &vmanip, v3f p0, TreeDef &tree_definition); +irr::core::matrix4 setRotationAxisRadians(irr::core::matrix4 M, double angle, v3f axis); - v3f transposeMatrix(irr::core::matrix4 M ,v3f v); +v3f transposeMatrix(irr::core::matrix4 M, v3f v); }; // namespace treegen diff --git a/src/mapnode.cpp b/src/mapnode.cpp index dcf1f6d6e..fb56f3ef4 100644 --- a/src/mapnode.cpp +++ b/src/mapnode.cpp @@ -23,7 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "nodedef.h" #include "map.h" #include "content_mapnode.h" // For mapnode_translate_*_internal -#include "serialization.h" // For ser_ver_supported +#include "serialization.h" // For ser_ver_supported #include "util/serialize.h" #include "log.h" #include "util/directiontables.h" @@ -32,13 +32,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #include static const Rotation wallmounted_to_rot[] = { - ROTATE_0, ROTATE_180, ROTATE_90, ROTATE_270 -}; - -static const u8 rot_to_wallmounted[] = { - 2, 4, 3, 5 -}; + ROTATE_0, ROTATE_180, ROTATE_90, ROTATE_270}; +static const u8 rot_to_wallmounted[] = {2, 4, 3, 5}; /* MapNode @@ -56,19 +52,15 @@ void MapNode::getColor(const ContentFeatures &f, video::SColor *color) const void MapNode::setLight(LightBank bank, u8 a_light, const ContentFeatures &f) noexcept { // If node doesn't contain light data, ignore this - if(f.param_type != CPT_LIGHT) + if (f.param_type != CPT_LIGHT) return; - if(bank == LIGHTBANK_DAY) - { + if (bank == LIGHTBANK_DAY) { param1 &= 0xf0; param1 |= a_light & 0x0f; - } - else if(bank == LIGHTBANK_NIGHT) - { + } else if (bank == LIGHTBANK_NIGHT) { param1 &= 0x0f; - param1 |= (a_light & 0x0f)<<4; - } - else + param1 |= (a_light & 0x0f) << 4; + } else assert("Invalid light bank" == NULL); } @@ -83,7 +75,7 @@ bool MapNode::isLightDayNightEq(const NodeDefManager *nodemgr) const bool isEqual; if (f.param_type == CPT_LIGHT) { - u8 day = MYMAX(f.light_source, param1 & 0x0f); + u8 day = MYMAX(f.light_source, param1 & 0x0f); u8 night = MYMAX(f.light_source, (param1 >> 4) & 0x0f); isEqual = day == night; } else { @@ -99,7 +91,7 @@ u8 MapNode::getLight(LightBank bank, const NodeDefManager *nodemgr) const const ContentFeatures &f = nodemgr->get(*this); u8 light; - if(f.param_type == CPT_LIGHT) + if (f.param_type == CPT_LIGHT) light = bank == LIGHTBANK_DAY ? param1 & 0x0f : (param1 >> 4) & 0x0f; else light = 0; @@ -109,7 +101,7 @@ u8 MapNode::getLight(LightBank bank, const NodeDefManager *nodemgr) const u8 MapNode::getLightRaw(LightBank bank, const ContentFeatures &f) const noexcept { - if(f.param_type == CPT_LIGHT) + if (f.param_type == CPT_LIGHT) return bank == LIGHTBANK_DAY ? param1 & 0x0f : (param1 >> 4) & 0x0f; return 0; } @@ -117,40 +109,36 @@ u8 MapNode::getLightRaw(LightBank bank, const ContentFeatures &f) const noexcept u8 MapNode::getLightNoChecks(LightBank bank, const ContentFeatures *f) const noexcept { return MYMAX(f->light_source, - bank == LIGHTBANK_DAY ? param1 & 0x0f : (param1 >> 4) & 0x0f); + bank == LIGHTBANK_DAY ? param1 & 0x0f : (param1 >> 4) & 0x0f); } -bool MapNode::getLightBanks(u8 &lightday, u8 &lightnight, - const NodeDefManager *nodemgr) const +bool MapNode::getLightBanks( + u8 &lightday, u8 &lightnight, const NodeDefManager *nodemgr) const { // Select the brightest of [light source, propagated light] const ContentFeatures &f = nodemgr->get(*this); - if(f.param_type == CPT_LIGHT) - { + if (f.param_type == CPT_LIGHT) { lightday = param1 & 0x0f; - lightnight = (param1>>4)&0x0f; - } - else - { + lightnight = (param1 >> 4) & 0x0f; + } else { lightday = 0; lightnight = 0; } - if(f.light_source > lightday) + if (f.light_source > lightday) lightday = f.light_source; - if(f.light_source > lightnight) + if (f.light_source > lightnight) lightnight = f.light_source; return f.param_type == CPT_LIGHT || f.light_source != 0; } -u8 MapNode::getFaceDir(const NodeDefManager *nodemgr, - bool allow_wallmounted) const +u8 MapNode::getFaceDir(const NodeDefManager *nodemgr, bool allow_wallmounted) const { const ContentFeatures &f = nodemgr->get(*this); - if (f.param_type_2 == CPT2_FACEDIR || - f.param_type_2 == CPT2_COLORED_FACEDIR) + if (f.param_type_2 == CPT2_FACEDIR || f.param_type_2 == CPT2_COLORED_FACEDIR) return (getParam2() & 0x1F) % 24; - if (allow_wallmounted && (f.param_type_2 == CPT2_WALLMOUNTED || - f.param_type_2 == CPT2_COLORED_WALLMOUNTED)) + if (allow_wallmounted && + (f.param_type_2 == CPT2_WALLMOUNTED || + f.param_type_2 == CPT2_COLORED_WALLMOUNTED)) return wallmounted_to_facedir[getParam2() & 0x07]; return 0; } @@ -166,14 +154,20 @@ u8 MapNode::getWallMounted(const NodeDefManager *nodemgr) const v3s16 MapNode::getWallMountedDir(const NodeDefManager *nodemgr) const { - switch(getWallMounted(nodemgr)) - { - case 0: default: return v3s16(0,1,0); - case 1: return v3s16(0,-1,0); - case 2: return v3s16(1,0,0); - case 3: return v3s16(-1,0,0); - case 4: return v3s16(0,0,1); - case 5: return v3s16(0,0,-1); + switch (getWallMounted(nodemgr)) { + case 0: + default: + return v3s16(0, 1, 0); + case 1: + return v3s16(0, -1, 0); + case 2: + return v3s16(1, 0, 0); + case 3: + return v3s16(-1, 0, 0); + case 4: + return v3s16(0, 0, 1); + case 5: + return v3s16(0, 0, -1); } } @@ -182,47 +176,33 @@ void MapNode::rotateAlongYAxis(const NodeDefManager *nodemgr, Rotation rot) ContentParamType2 cpt2 = nodemgr->get(*this).param_type_2; if (cpt2 == CPT2_FACEDIR || cpt2 == CPT2_COLORED_FACEDIR) { - static const u8 rotate_facedir[24 * 4] = { - // Table value = rotated facedir - // Columns: 0, 90, 180, 270 degrees rotation around vertical axis - // Rotation is anticlockwise as seen from above (+Y) + static const u8 rotate_facedir[24 * 4] = {// Table value = rotated facedir + // Columns: 0, 90, 180, 270 degrees rotation around + // vertical axis + // Rotation is anticlockwise as seen from above (+Y) - 0, 1, 2, 3, // Initial facedir 0 to 3 - 1, 2, 3, 0, - 2, 3, 0, 1, - 3, 0, 1, 2, + 0, 1, 2, 3, // Initial facedir 0 to 3 + 1, 2, 3, 0, 2, 3, 0, 1, 3, 0, 1, 2, - 4, 13, 10, 19, // 4 to 7 - 5, 14, 11, 16, - 6, 15, 8, 17, - 7, 12, 9, 18, + 4, 13, 10, 19, // 4 to 7 + 5, 14, 11, 16, 6, 15, 8, 17, 7, 12, 9, 18, - 8, 17, 6, 15, // 8 to 11 - 9, 18, 7, 12, - 10, 19, 4, 13, - 11, 16, 5, 14, + 8, 17, 6, 15, // 8 to 11 + 9, 18, 7, 12, 10, 19, 4, 13, 11, 16, 5, 14, - 12, 9, 18, 7, // 12 to 15 - 13, 10, 19, 4, - 14, 11, 16, 5, - 15, 8, 17, 6, + 12, 9, 18, 7, // 12 to 15 + 13, 10, 19, 4, 14, 11, 16, 5, 15, 8, 17, 6, - 16, 5, 14, 11, // 16 to 19 - 17, 6, 15, 8, - 18, 7, 12, 9, - 19, 4, 13, 10, + 16, 5, 14, 11, // 16 to 19 + 17, 6, 15, 8, 18, 7, 12, 9, 19, 4, 13, 10, - 20, 23, 22, 21, // 20 to 23 - 21, 20, 23, 22, - 22, 21, 20, 23, - 23, 22, 21, 20 - }; + 20, 23, 22, 21, // 20 to 23 + 21, 20, 23, 22, 22, 21, 20, 23, 23, 22, 21, 20}; u8 facedir = (param2 & 31) % 24; u8 index = facedir * 4 + rot; param2 &= ~31; param2 |= rotate_facedir[index]; - } else if (cpt2 == CPT2_WALLMOUNTED || - cpt2 == CPT2_COLORED_WALLMOUNTED) { + } else if (cpt2 == CPT2_WALLMOUNTED || cpt2 == CPT2_COLORED_WALLMOUNTED) { u8 wmountface = (param2 & 7); if (wmountface <= 1) return; @@ -234,34 +214,30 @@ void MapNode::rotateAlongYAxis(const NodeDefManager *nodemgr, Rotation rot) } void transformNodeBox(const MapNode &n, const NodeBox &nodebox, - const NodeDefManager *nodemgr, std::vector *p_boxes, - u8 neighbors = 0) + const NodeDefManager *nodemgr, std::vector *p_boxes, + u8 neighbors = 0) { std::vector &boxes = *p_boxes; if (nodebox.type == NODEBOX_FIXED || nodebox.type == NODEBOX_LEVELED) { const std::vector &fixed = nodebox.fixed; int facedir = n.getFaceDir(nodemgr, true); - u8 axisdir = facedir>>2; - facedir&=0x03; + u8 axisdir = facedir >> 2; + facedir &= 0x03; for (aabb3f box : fixed) { if (nodebox.type == NODEBOX_LEVELED) - box.MaxEdge.Y = (-0.5f + n.getLevel(nodemgr) / 64.0f) * BS; + box.MaxEdge.Y = (-0.5f + n.getLevel(nodemgr) / 64.0f) * + BS; switch (axisdir) { case 0: - if(facedir == 1) - { + if (facedir == 1) { box.MinEdge.rotateXZBy(-90); box.MaxEdge.rotateXZBy(-90); - } - else if(facedir == 2) - { + } else if (facedir == 2) { box.MinEdge.rotateXZBy(180); box.MaxEdge.rotateXZBy(180); - } - else if(facedir == 3) - { + } else if (facedir == 3) { box.MinEdge.rotateXZBy(90); box.MaxEdge.rotateXZBy(90); } @@ -269,75 +245,55 @@ void transformNodeBox(const MapNode &n, const NodeBox &nodebox, case 1: // z+ box.MinEdge.rotateYZBy(90); box.MaxEdge.rotateYZBy(90); - if(facedir == 1) - { + if (facedir == 1) { box.MinEdge.rotateXYBy(90); box.MaxEdge.rotateXYBy(90); - } - else if(facedir == 2) - { + } else if (facedir == 2) { box.MinEdge.rotateXYBy(180); box.MaxEdge.rotateXYBy(180); - } - else if(facedir == 3) - { + } else if (facedir == 3) { box.MinEdge.rotateXYBy(-90); box.MaxEdge.rotateXYBy(-90); } break; - case 2: //z- + case 2: // z- box.MinEdge.rotateYZBy(-90); box.MaxEdge.rotateYZBy(-90); - if(facedir == 1) - { + if (facedir == 1) { box.MinEdge.rotateXYBy(-90); box.MaxEdge.rotateXYBy(-90); - } - else if(facedir == 2) - { + } else if (facedir == 2) { box.MinEdge.rotateXYBy(180); box.MaxEdge.rotateXYBy(180); - } - else if(facedir == 3) - { + } else if (facedir == 3) { box.MinEdge.rotateXYBy(90); box.MaxEdge.rotateXYBy(90); } break; - case 3: //x+ + case 3: // x+ box.MinEdge.rotateXYBy(-90); box.MaxEdge.rotateXYBy(-90); - if(facedir == 1) - { + if (facedir == 1) { box.MinEdge.rotateYZBy(90); box.MaxEdge.rotateYZBy(90); - } - else if(facedir == 2) - { + } else if (facedir == 2) { box.MinEdge.rotateYZBy(180); box.MaxEdge.rotateYZBy(180); - } - else if(facedir == 3) - { + } else if (facedir == 3) { box.MinEdge.rotateYZBy(-90); box.MaxEdge.rotateYZBy(-90); } break; - case 4: //x- + case 4: // x- box.MinEdge.rotateXYBy(90); box.MaxEdge.rotateXYBy(90); - if(facedir == 1) - { + if (facedir == 1) { box.MinEdge.rotateYZBy(-90); box.MaxEdge.rotateYZBy(-90); - } - else if(facedir == 2) - { + } else if (facedir == 2) { box.MinEdge.rotateYZBy(180); box.MaxEdge.rotateYZBy(180); - } - else if(facedir == 3) - { + } else if (facedir == 3) { box.MinEdge.rotateYZBy(90); box.MaxEdge.rotateYZBy(90); } @@ -345,18 +301,13 @@ void transformNodeBox(const MapNode &n, const NodeBox &nodebox, case 5: box.MinEdge.rotateXYBy(-180); box.MaxEdge.rotateXYBy(-180); - if(facedir == 1) - { + if (facedir == 1) { box.MinEdge.rotateXZBy(90); box.MaxEdge.rotateXZBy(90); - } - else if(facedir == 2) - { + } else if (facedir == 2) { box.MinEdge.rotateXZBy(180); box.MaxEdge.rotateXZBy(180); - } - else if(facedir == 3) - { + } else if (facedir == 3) { box.MinEdge.rotateXZBy(-90); box.MaxEdge.rotateXZBy(-90); } @@ -367,38 +318,30 @@ void transformNodeBox(const MapNode &n, const NodeBox &nodebox, box.repair(); boxes.push_back(box); } - } - else if(nodebox.type == NODEBOX_WALLMOUNTED) - { + } else if (nodebox.type == NODEBOX_WALLMOUNTED) { v3s16 dir = n.getWallMountedDir(nodemgr); // top - if(dir == v3s16(0,1,0)) - { + if (dir == v3s16(0, 1, 0)) { boxes.push_back(nodebox.wall_top); } // bottom - else if(dir == v3s16(0,-1,0)) - { + else if (dir == v3s16(0, -1, 0)) { boxes.push_back(nodebox.wall_bottom); } // side - else - { - v3f vertices[2] = - { - nodebox.wall_side.MinEdge, - nodebox.wall_side.MaxEdge - }; + else { + v3f vertices[2] = {nodebox.wall_side.MinEdge, + nodebox.wall_side.MaxEdge}; for (v3f &vertex : vertices) { - if(dir == v3s16(-1,0,0)) + if (dir == v3s16(-1, 0, 0)) vertex.rotateXZBy(0); - if(dir == v3s16(1,0,0)) + if (dir == v3s16(1, 0, 0)) vertex.rotateXZBy(180); - if(dir == v3s16(0,0,-1)) + if (dir == v3s16(0, 0, -1)) vertex.rotateXZBy(90); - if(dir == v3s16(0,0,1)) + if (dir == v3s16(0, 0, 1)) vertex.rotateXZBy(-90); } @@ -406,9 +349,7 @@ void transformNodeBox(const MapNode &n, const NodeBox &nodebox, box.addInternalPoint(vertices[1]); boxes.push_back(box); } - } - else if (nodebox.type == NODEBOX_CONNECTED) - { + } else if (nodebox.type == NODEBOX_CONNECTED) { size_t boxes_size = boxes.size(); boxes_size += nodebox.fixed.size(); if (neighbors & 1) @@ -449,11 +390,10 @@ void transformNodeBox(const MapNode &n, const NodeBox &nodebox, boxes.reserve(boxes_size); -#define BOXESPUSHBACK(c) \ - for (std::vector::const_iterator \ - it = (c).begin(); \ - it != (c).end(); ++it) \ - (boxes).push_back(*it); +#define BOXESPUSHBACK(c) \ + for (std::vector::const_iterator it = (c).begin(); it != (c).end(); \ + ++it) \ + (boxes).push_back(*it); BOXESPUSHBACK(nodebox.fixed); @@ -501,16 +441,15 @@ void transformNodeBox(const MapNode &n, const NodeBox &nodebox, BOXESPUSHBACK(nodebox.disconnected_sides); } - } - else // NODEBOX_REGULAR + } else // NODEBOX_REGULAR { - boxes.emplace_back(-BS/2,-BS/2,-BS/2,BS/2,BS/2,BS/2); + boxes.emplace_back(-BS / 2, -BS / 2, -BS / 2, BS / 2, BS / 2, BS / 2); } } -static inline void getNeighborConnectingFace( - const v3s16 &p, const NodeDefManager *nodedef, - Map *map, MapNode n, u8 bitmask, u8 *neighbors) +static inline void getNeighborConnectingFace(const v3s16 &p, + const NodeDefManager *nodedef, Map *map, MapNode n, u8 bitmask, + u8 *neighbors) { MapNode n2 = map->getNode(p); if (nodedef->nodeboxConnects(n, n2, bitmask)) @@ -553,15 +492,15 @@ u8 MapNode::getNeighbors(v3s16 p, Map *map) const return neighbors; } -void MapNode::getNodeBoxes(const NodeDefManager *nodemgr, - std::vector *boxes, u8 neighbors) const +void MapNode::getNodeBoxes(const NodeDefManager *nodemgr, std::vector *boxes, + u8 neighbors) const { const ContentFeatures &f = nodemgr->get(*this); transformNodeBox(*this, f.node_box, nodemgr, boxes, neighbors); } -void MapNode::getCollisionBoxes(const NodeDefManager *nodemgr, - std::vector *boxes, u8 neighbors) const +void MapNode::getCollisionBoxes(const NodeDefManager *nodemgr, std::vector *boxes, + u8 neighbors) const { const ContentFeatures &f = nodemgr->get(*this); if (f.collision_box.fixed.empty()) @@ -570,8 +509,8 @@ void MapNode::getCollisionBoxes(const NodeDefManager *nodemgr, transformNodeBox(*this, f.collision_box, nodemgr, boxes, neighbors); } -void MapNode::getSelectionBoxes(const NodeDefManager *nodemgr, - std::vector *boxes, u8 neighbors) const +void MapNode::getSelectionBoxes(const NodeDefManager *nodemgr, std::vector *boxes, + u8 neighbors) const { const ContentFeatures &f = nodemgr->get(*this); transformNodeBox(*this, f.selection_box, nodemgr, boxes, neighbors); @@ -581,9 +520,9 @@ u8 MapNode::getMaxLevel(const NodeDefManager *nodemgr) const { const ContentFeatures &f = nodemgr->get(*this); // todo: after update in all games leave only if (f.param_type_2 == - if( f.liquid_type == LIQUID_FLOWING || f.param_type_2 == CPT2_FLOWINGLIQUID) + if (f.liquid_type == LIQUID_FLOWING || f.param_type_2 == CPT2_FLOWINGLIQUID) return LIQUID_LEVEL_MAX; - if(f.leveled || f.param_type_2 == CPT2_LEVELED) + if (f.leveled || f.param_type_2 == CPT2_LEVELED) return f.leveled_max; return 0; } @@ -592,11 +531,11 @@ u8 MapNode::getLevel(const NodeDefManager *nodemgr) const { const ContentFeatures &f = nodemgr->get(*this); // todo: after update in all games leave only if (f.param_type_2 == - if(f.liquid_type == LIQUID_SOURCE) + if (f.liquid_type == LIQUID_SOURCE) return LIQUID_LEVEL_SOURCE; if (f.param_type_2 == CPT2_FLOWINGLIQUID) return getParam2() & LIQUID_LEVEL_MASK; - if(f.liquid_type == LIQUID_FLOWING) // can remove if all param_type_2 setted + if (f.liquid_type == LIQUID_FLOWING) // can remove if all param_type_2 setted return getParam2() & LIQUID_LEVEL_MASK; if (f.param_type_2 == CPT2_LEVELED) { u8 level = getParam2() & LEVELED_MASK; @@ -613,9 +552,8 @@ s8 MapNode::setLevel(const NodeDefManager *nodemgr, s16 level) { s8 rest = 0; const ContentFeatures &f = nodemgr->get(*this); - if (f.param_type_2 == CPT2_FLOWINGLIQUID - || f.liquid_type == LIQUID_FLOWING - || f.liquid_type == LIQUID_SOURCE) { + if (f.param_type_2 == CPT2_FLOWINGLIQUID || f.liquid_type == LIQUID_FLOWING || + f.liquid_type == LIQUID_SOURCE) { if (level <= 0) { // liquid can’t exist with zero level setContent(CONTENT_AIR); return 0; @@ -626,7 +564,8 @@ s8 MapNode::setLevel(const NodeDefManager *nodemgr, s16 level) setParam2(0); } else { setContent(f.liquid_alternative_flowing_id); - setParam2((level & LIQUID_LEVEL_MASK) | (getParam2() & ~LIQUID_LEVEL_MASK)); + setParam2((level & LIQUID_LEVEL_MASK) | + (getParam2() & ~LIQUID_LEVEL_MASK)); } } else if (f.param_type_2 == CPT2_LEVELED) { if (level < 0) { // zero means default for a leveled nodebox @@ -650,7 +589,7 @@ s8 MapNode::addLevel(const NodeDefManager *nodemgr, s16 add) u32 MapNode::serializedLength(u8 version) { - if(!ser_ver_supported(version)) + if (!ser_ver_supported(version)) throw VersionMismatchException("ERROR: MapNode format not supported"); if (version == 0) @@ -666,47 +605,45 @@ u32 MapNode::serializedLength(u8 version) } void MapNode::serialize(u8 *dest, u8 version) const { - if(!ser_ver_supported(version)) + if (!ser_ver_supported(version)) throw VersionMismatchException("ERROR: MapNode format not supported"); // Can't do this anymore; we have 16-bit dynamically allocated node IDs // in memory; conversion just won't work in this direction. - if(version < 24) + if (version < 24) throw SerializationError("MapNode::serialize: serialization to " - "version < 24 not possible"); + "version < 24 not possible"); - writeU16(dest+0, param0); - writeU8(dest+2, param1); - writeU8(dest+3, param2); + writeU16(dest + 0, param0); + writeU8(dest + 2, param1); + writeU8(dest + 3, param2); } void MapNode::deSerialize(u8 *source, u8 version) { - if(!ser_ver_supported(version)) + if (!ser_ver_supported(version)) throw VersionMismatchException("ERROR: MapNode format not supported"); - if(version <= 21) - { + if (version <= 21) { deSerialize_pre22(source, version); return; } - if(version >= 24){ - param0 = readU16(source+0); - param1 = readU8(source+2); - param2 = readU8(source+3); - }else{ - param0 = readU8(source+0); - param1 = readU8(source+1); - param2 = readU8(source+2); - if(param0 > 0x7F){ - param0 |= ((param2&0xF0)<<4); + if (version >= 24) { + param0 = readU16(source + 0); + param1 = readU8(source + 2); + param2 = readU8(source + 3); + } else { + param0 = readU8(source + 0); + param1 = readU8(source + 1); + param2 = readU8(source + 2); + if (param0 > 0x7F) { + param0 |= ((param2 & 0xF0) << 4); param2 &= 0x0F; } } } -void MapNode::serializeBulk(std::ostream &os, int version, - const MapNode *nodes, u32 nodecount, - u8 content_width, u8 params_width, bool compressed) +void MapNode::serializeBulk(std::ostream &os, int version, const MapNode *nodes, + u32 nodecount, u8 content_width, u8 params_width, bool compressed) { if (!ser_ver_supported(version)) throw VersionMismatchException("ERROR: MapNode format not supported"); @@ -718,7 +655,7 @@ void MapNode::serializeBulk(std::ostream &os, int version, // in memory; conversion just won't work in this direction. if (version < 24) throw SerializationError("MapNode::serializeBulk: serialization to " - "version < 24 not possible"); + "version < 24 not possible"); size_t databuf_size = nodecount * (content_width + params_width); u8 *databuf = new u8[databuf_size]; @@ -740,78 +677,67 @@ void MapNode::serializeBulk(std::ostream &os, int version, if (compressed) compressZlib(databuf, databuf_size, os); else - os.write((const char*) &databuf[0], databuf_size); + os.write((const char *)&databuf[0], databuf_size); - delete [] databuf; + delete[] databuf; } // Deserialize bulk node data -void MapNode::deSerializeBulk(std::istream &is, int version, - MapNode *nodes, u32 nodecount, - u8 content_width, u8 params_width, bool compressed) +void MapNode::deSerializeBulk(std::istream &is, int version, MapNode *nodes, + u32 nodecount, u8 content_width, u8 params_width, bool compressed) { - if(!ser_ver_supported(version)) + if (!ser_ver_supported(version)) throw VersionMismatchException("ERROR: MapNode format not supported"); - if (version < 22 - || (content_width != 1 && content_width != 2) - || params_width != 2) + if (version < 22 || (content_width != 1 && content_width != 2) || + params_width != 2) FATAL_ERROR("Deserialize bulk node data error"); // Uncompress or read data u32 len = nodecount * (content_width + params_width); SharedBuffer databuf(len); - if(compressed) - { + if (compressed) { std::ostringstream os(std::ios_base::binary); decompressZlib(is, os); std::string s = os.str(); - if(s.size() != len) + if (s.size() != len) throw SerializationError("deSerializeBulkNodes: " - "decompress resulted in invalid size"); + "decompress resulted in invalid size"); memcpy(&databuf[0], s.c_str(), len); - } - else - { - is.read((char*) &databuf[0], len); - if(is.eof() || is.fail()) + } else { + is.read((char *)&databuf[0], len); + if (is.eof() || is.fail()) throw SerializationError("deSerializeBulkNodes: " - "failed to read bulk node data"); + "failed to read bulk node data"); } // Deserialize content - if(content_width == 1) - { - for(u32 i=0; i 0x7F){ + if (nodes[i].param0 > 0x7F) { nodes[i].param0 <<= 4; - nodes[i].param0 |= (nodes[i].param2&0xF0)>>4; + nodes[i].param0 |= (nodes[i].param2 & 0xF0) >> 4; nodes[i].param2 &= 0x0F; } } - } - else if(content_width == 2) - { - for(u32 i=0; i 0x7f){ + if (param0 > 0x7f) { param0 <<= 4; - param0 |= (param2&0xf0)>>4; + param0 |= (param2 & 0xf0) >> 4; param2 &= 0x0f; } } // Convert special values from old version to new - if(version <= 19) - { + if (version <= 19) { // In these versions, CONTENT_IGNORE and CONTENT_AIR // are 255 and 254 // Version 19 is messed up with sometimes the old values and sometimes not - if(param0 == 255) + if (param0 == 255) param0 = CONTENT_IGNORE; - else if(param0 == 254) + else if (param0 == 254) param0 = CONTENT_AIR; } diff --git a/src/mapnode.h b/src/mapnode.h index 32ac1b4f6..eaf37b01f 100644 --- a/src/mapnode.h +++ b/src/mapnode.h @@ -79,7 +79,8 @@ enum LightBank /* Simple rotation enum. */ -enum Rotation { +enum Rotation +{ ROTATE_0, ROTATE_90, ROTATE_180, @@ -98,22 +99,20 @@ enum Rotation { /* maximum amount of liquid in a block */ #define LIQUID_LEVEL_MAX LIQUID_LEVEL_MASK -#define LIQUID_LEVEL_SOURCE (LIQUID_LEVEL_MAX+1) +#define LIQUID_LEVEL_SOURCE (LIQUID_LEVEL_MAX + 1) -#define LIQUID_INFINITY_MASK 0x80 //0b10000000 +#define LIQUID_INFINITY_MASK 0x80 // 0b10000000 // mask for leveled nodebox param2 #define LEVELED_MASK 0x7F #define LEVELED_MAX LEVELED_MASK - struct ContentFeatures; /* This is the stuff what the whole world consists of. */ - struct MapNode { /* @@ -139,44 +138,24 @@ struct MapNode MapNode() = default; - MapNode(content_t content, u8 a_param1=0, u8 a_param2=0) noexcept - : param0(content), - param1(a_param1), - param2(a_param2) - { } + MapNode(content_t content, u8 a_param1 = 0, u8 a_param2 = 0) noexcept : + param0(content), param1(a_param1), param2(a_param2) + { + } bool operator==(const MapNode &other) const noexcept { - return (param0 == other.param0 - && param1 == other.param1 - && param2 == other.param2); + return (param0 == other.param0 && param1 == other.param1 && + param2 == other.param2); } // To be used everywhere - content_t getContent() const noexcept - { - return param0; - } - void setContent(content_t c) noexcept - { - param0 = c; - } - u8 getParam1() const noexcept - { - return param1; - } - void setParam1(u8 p) noexcept - { - param1 = p; - } - u8 getParam2() const noexcept - { - return param2; - } - void setParam2(u8 p) noexcept - { - param2 = p; - } + content_t getContent() const noexcept { return param0; } + void setContent(content_t c) noexcept { param0 = c; } + u8 getParam1() const noexcept { return param1; } + void setParam1(u8 p) noexcept { param1 = p; } + u8 getParam2() const noexcept { return param2; } + void setParam2(u8 p) noexcept { param2 = p; } /*! * Returns the color of the node. @@ -224,7 +203,7 @@ struct MapNode u8 getLightNoChecks(LightBank bank, const ContentFeatures *f) const noexcept; bool getLightBanks(u8 &lightday, u8 &lightnight, - const NodeDefManager *nodemgr) const; + const NodeDefManager *nodemgr) const; // 0 <= daylight_factor <= 1000 // 0 <= return value <= LIGHT_SUN @@ -236,7 +215,8 @@ struct MapNode return blend_light(daylight_factor, lightday, lightnight); } - u8 getFaceDir(const NodeDefManager *nodemgr, bool allow_wallmounted = false) const; + u8 getFaceDir(const NodeDefManager *nodemgr, + bool allow_wallmounted = false) const; u8 getWallMounted(const NodeDefManager *nodemgr) const; v3s16 getWallMountedDir(const NodeDefManager *nodemgr) const; @@ -253,19 +233,19 @@ struct MapNode Gets list of node boxes (used for rendering (NDT_NODEBOX)) */ void getNodeBoxes(const NodeDefManager *nodemgr, std::vector *boxes, - u8 neighbors = 0) const; + u8 neighbors = 0) const; /* Gets list of selection boxes */ - void getSelectionBoxes(const NodeDefManager *nodemg, - std::vector *boxes, u8 neighbors = 0) const; + void getSelectionBoxes(const NodeDefManager *nodemg, std::vector *boxes, + u8 neighbors = 0) const; /* Gets list of collision boxes */ - void getCollisionBoxes(const NodeDefManager *nodemgr, - std::vector *boxes, u8 neighbors = 0) const; + void getCollisionBoxes(const NodeDefManager *nodemgr, std::vector *boxes, + u8 neighbors = 0) const; /* Liquid/leveled helpers @@ -290,12 +270,12 @@ struct MapNode // content_width = the number of bytes of content per node // params_width = the number of bytes of params per node // compressed = true to zlib-compress output - static void serializeBulk(std::ostream &os, int version, - const MapNode *nodes, u32 nodecount, - u8 content_width, u8 params_width, bool compressed); - static void deSerializeBulk(std::istream &is, int version, - MapNode *nodes, u32 nodecount, - u8 content_width, u8 params_width, bool compressed); + static void serializeBulk(std::ostream &os, int version, const MapNode *nodes, + u32 nodecount, u8 content_width, u8 params_width, + bool compressed); + static void deSerializeBulk(std::istream &is, int version, MapNode *nodes, + u32 nodecount, u8 content_width, u8 params_width, + bool compressed); private: // Deprecated serialization methods diff --git a/src/mapsector.cpp b/src/mapsector.cpp index 3eefa5410..b004b9013 100644 --- a/src/mapsector.cpp +++ b/src/mapsector.cpp @@ -22,10 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "mapblock.h" #include "serialization.h" -MapSector::MapSector(Map *parent, v2s16 pos, IGameDef *gamedef): - m_parent(parent), - m_pos(pos), - m_gamedef(gamedef) +MapSector::MapSector(Map *parent, v2s16 pos, IGameDef *gamedef) : + m_parent(parent), m_pos(pos), m_gamedef(gamedef) { } @@ -48,7 +46,7 @@ void MapSector::deleteBlocks() m_blocks.clear(); } -MapBlock * MapSector::getBlockBuffered(s16 y) +MapBlock *MapSector::getBlockBuffered(s16 y) { MapBlock *block; @@ -57,7 +55,7 @@ MapBlock * MapSector::getBlockBuffered(s16 y) } // If block doesn't exist, return NULL - std::unordered_map::const_iterator n = m_blocks.find(y); + std::unordered_map::const_iterator n = m_blocks.find(y); block = (n != m_blocks.end() ? n->second : nullptr); // Cache the last result @@ -67,14 +65,14 @@ MapBlock * MapSector::getBlockBuffered(s16 y) return block; } -MapBlock * MapSector::getBlockNoCreateNoEx(s16 y) +MapBlock *MapSector::getBlockNoCreateNoEx(s16 y) { return getBlockBuffered(y); } -MapBlock * MapSector::createBlankBlockNoInsert(s16 y) +MapBlock *MapSector::createBlankBlockNoInsert(s16 y) { - assert(getBlockBuffered(y) == NULL); // Pre-condition + assert(getBlockBuffered(y) == NULL); // Pre-condition v3s16 blockpos_map(m_pos.X, y, m_pos.Y); @@ -83,7 +81,7 @@ MapBlock * MapSector::createBlankBlockNoInsert(s16 y) return block; } -MapBlock * MapSector::createBlankBlock(s16 y) +MapBlock *MapSector::createBlankBlock(s16 y) { MapBlock *block = createBlankBlockNoInsert(y); diff --git a/src/mapsector.h b/src/mapsector.h index dede364f6..ff96be498 100644 --- a/src/mapsector.h +++ b/src/mapsector.h @@ -39,20 +39,16 @@ class IGameDef; class MapSector { public: - MapSector(Map *parent, v2s16 pos, IGameDef *gamedef); virtual ~MapSector(); void deleteBlocks(); - v2s16 getPos() - { - return m_pos; - } + v2s16 getPos() { return m_pos; } - MapBlock * getBlockNoCreateNoEx(s16 y); - MapBlock * createBlankBlockNoInsert(s16 y); - MapBlock * createBlankBlock(s16 y); + MapBlock *getBlockNoCreateNoEx(s16 y); + MapBlock *createBlankBlockNoInsert(s16 y); + MapBlock *createBlankBlock(s16 y); void insertBlock(MapBlock *block); @@ -63,9 +59,8 @@ public: bool empty() const { return m_blocks.empty(); } protected: - // The pile of MapBlocks - std::unordered_map m_blocks; + std::unordered_map m_blocks; Map *m_parent; // Position on parent (in MapBlock widths) @@ -82,5 +77,4 @@ protected: Private methods */ MapBlock *getBlockBuffered(s16 y); - }; diff --git a/src/metadata.h b/src/metadata.h index 5333f8a9d..68b5c8f3c 100644 --- a/src/metadata.h +++ b/src/metadata.h @@ -27,6 +27,7 @@ with this program; if not, write to the Free Software Foundation, Inc., class Metadata { bool m_modified = false; + public: virtual ~Metadata() = default; @@ -34,10 +35,7 @@ public: virtual bool empty() const; bool operator==(const Metadata &other) const; - inline bool operator!=(const Metadata &other) const - { - return !(*this == other); - } + inline bool operator!=(const Metadata &other) const { return !(*this == other); } // // Key-value related @@ -46,18 +44,17 @@ public: size_t size() const; bool contains(const std::string &name) const; const std::string &getString(const std::string &name, u16 recursion = 0) const; - bool getStringToRef(const std::string &name, std::string &str, u16 recursion = 0) const; + bool getStringToRef(const std::string &name, std::string &str, + u16 recursion = 0) const; virtual bool setString(const std::string &name, const std::string &var); inline bool removeString(const std::string &name) { return setString(name, ""); } - const StringMap &getStrings() const - { - return m_stringvars; - } + const StringMap &getStrings() const { return m_stringvars; } // Add support for variable names in values const std::string &resolveString(const std::string &str, u16 recursion = 0) const; - inline bool isModified() const { return m_modified; } + inline bool isModified() const { return m_modified; } inline void setModified(bool v) { m_modified = v; } + protected: StringMap m_stringvars; }; diff --git a/src/network/address.cpp b/src/network/address.cpp index 05678aa62..d24a56027 100644 --- a/src/network/address.cpp +++ b/src/network/address.cpp @@ -283,7 +283,7 @@ bool Address::isLocalhost() const auto addr = m_address.ipv6.sin6_addr.s6_addr; return memcmp(addr, localhost_bytes, 16) == 0 || - memcmp(addr, mapped_ipv4_localhost, 13) == 0; + memcmp(addr, mapped_ipv4_localhost, 13) == 0; } return (m_address.ipv4.sin_addr.s_addr & 0xFF) == 0x7f; diff --git a/src/network/clientopcodes.cpp b/src/network/clientopcodes.cpp index f812a08a1..b7dd78f65 100644 --- a/src/network/clientopcodes.cpp +++ b/src/network/clientopcodes.cpp @@ -20,111 +20,152 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "clientopcodes.h" -const static ToClientCommandHandler null_command_handler = {"TOCLIENT_NULL", TOCLIENT_STATE_ALL, &Client::handleCommand_Null}; +const static ToClientCommandHandler null_command_handler = { + "TOCLIENT_NULL", TOCLIENT_STATE_ALL, &Client::handleCommand_Null}; -const ToClientCommandHandler toClientCommandTable[TOCLIENT_NUM_MSG_TYPES] = -{ - null_command_handler, // 0x00 (never use this) - null_command_handler, // 0x01 - { "TOCLIENT_HELLO", TOCLIENT_STATE_NOT_CONNECTED, &Client::handleCommand_Hello }, // 0x02 - { "TOCLIENT_AUTH_ACCEPT", TOCLIENT_STATE_NOT_CONNECTED, &Client::handleCommand_AuthAccept }, // 0x03 - { "TOCLIENT_ACCEPT_SUDO_MODE", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_AcceptSudoMode}, // 0x04 - { "TOCLIENT_DENY_SUDO_MODE", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_DenySudoMode}, // 0x05 - null_command_handler, // 0x06 - null_command_handler, // 0x07 - null_command_handler, // 0x08 - null_command_handler, // 0x09 - { "TOCLIENT_ACCESS_DENIED", TOCLIENT_STATE_NOT_CONNECTED, &Client::handleCommand_AccessDenied }, // 0x0A - null_command_handler, // 0x0B - null_command_handler, // 0x0C - null_command_handler, // 0x0D - null_command_handler, // 0x0E - null_command_handler, // 0x0F - null_command_handler, // 0x10 - null_command_handler, - null_command_handler, - null_command_handler, - null_command_handler, - null_command_handler, - null_command_handler, - null_command_handler, - null_command_handler, - null_command_handler, - null_command_handler, - null_command_handler, - null_command_handler, - null_command_handler, - null_command_handler, - null_command_handler, - { "TOCLIENT_BLOCKDATA", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_BlockData }, // 0x20 - { "TOCLIENT_ADDNODE", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_AddNode }, // 0x21 - { "TOCLIENT_REMOVENODE", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_RemoveNode }, // 0x22 - null_command_handler, - null_command_handler, - null_command_handler, - null_command_handler, - { "TOCLIENT_INVENTORY", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_Inventory }, // 0x27 - null_command_handler, - { "TOCLIENT_TIME_OF_DAY", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_TimeOfDay }, // 0x29 - { "TOCLIENT_CSM_RESTRICTION_FLAGS", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_CSMRestrictionFlags }, // 0x2A - { "TOCLIENT_PLAYER_SPEED", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_PlayerSpeed }, // 0x2B - { "TOCLIENT_MEDIA_PUSH", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_MediaPush }, // 0x2C - null_command_handler, - null_command_handler, - { "TOCLIENT_CHAT_MESSAGE", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_ChatMessage }, // 0x2F - null_command_handler, // 0x30 - { "TOCLIENT_ACTIVE_OBJECT_REMOVE_ADD", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_ActiveObjectRemoveAdd }, // 0x31 - { "TOCLIENT_ACTIVE_OBJECT_MESSAGES", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_ActiveObjectMessages }, // 0x32 - { "TOCLIENT_HP", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_HP }, // 0x33 - { "TOCLIENT_MOVE_PLAYER", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_MovePlayer }, // 0x34 - { "TOCLIENT_ACCESS_DENIED_LEGACY", TOCLIENT_STATE_NOT_CONNECTED, &Client::handleCommand_AccessDenied }, // 0x35 - { "TOCLIENT_FOV", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_Fov }, // 0x36 - { "TOCLIENT_DEATHSCREEN", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_DeathScreen }, // 0x37 - { "TOCLIENT_MEDIA", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_Media }, // 0x38 - null_command_handler, - { "TOCLIENT_NODEDEF", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_NodeDef }, // 0x3a - null_command_handler, - { "TOCLIENT_ANNOUNCE_MEDIA", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_AnnounceMedia }, // 0x3c - { "TOCLIENT_ITEMDEF", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_ItemDef }, // 0x3d - null_command_handler, - { "TOCLIENT_PLAY_SOUND", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_PlaySound }, // 0x3f - { "TOCLIENT_STOP_SOUND", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_StopSound }, // 0x40 - { "TOCLIENT_PRIVILEGES", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_Privileges }, // 0x41 - { "TOCLIENT_INVENTORY_FORMSPEC", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_InventoryFormSpec }, // 0x42 - { "TOCLIENT_DETACHED_INVENTORY", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_DetachedInventory }, // 0x43 - { "TOCLIENT_SHOW_FORMSPEC", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_ShowFormSpec }, // 0x44 - { "TOCLIENT_MOVEMENT", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_Movement }, // 0x45 - { "TOCLIENT_SPAWN_PARTICLE", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_SpawnParticle }, // 0x46 - { "TOCLIENT_ADD_PARTICLESPAWNER", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_AddParticleSpawner }, // 0x47 - null_command_handler, - { "TOCLIENT_HUDADD", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_HudAdd }, // 0x49 - { "TOCLIENT_HUDRM", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_HudRemove }, // 0x4a - { "TOCLIENT_HUDCHANGE", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_HudChange }, // 0x4b - { "TOCLIENT_HUD_SET_FLAGS", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_HudSetFlags }, // 0x4c - { "TOCLIENT_HUD_SET_PARAM", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_HudSetParam }, // 0x4d - { "TOCLIENT_BREATH", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_Breath }, // 0x4e - { "TOCLIENT_SET_SKY", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_HudSetSky }, // 0x4f - { "TOCLIENT_OVERRIDE_DAY_NIGHT_RATIO", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_OverrideDayNightRatio }, // 0x50 - { "TOCLIENT_LOCAL_PLAYER_ANIMATIONS", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_LocalPlayerAnimations }, // 0x51 - { "TOCLIENT_EYE_OFFSET", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_EyeOffset }, // 0x52 - { "TOCLIENT_DELETE_PARTICLESPAWNER", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_DeleteParticleSpawner }, // 0x53 - { "TOCLIENT_CLOUD_PARAMS", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_CloudParams }, // 0x54 - { "TOCLIENT_FADE_SOUND", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_FadeSound }, // 0x55 - { "TOCLIENT_UPDATE_PLAYER_LIST", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_UpdatePlayerList }, // 0x56 - { "TOCLIENT_MODCHANNEL_MSG", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_ModChannelMsg }, // 0x57 - { "TOCLIENT_MODCHANNEL_SIGNAL", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_ModChannelSignal }, // 0x58 - { "TOCLIENT_NODEMETA_CHANGED", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_NodemetaChanged }, // 0x59 - { "TOCLIENT_SET_SUN", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_HudSetSun }, // 0x5a - { "TOCLIENT_SET_MOON", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_HudSetMoon }, // 0x5b - { "TOCLIENT_SET_STARS", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_HudSetStars }, // 0x5c - null_command_handler, - null_command_handler, - null_command_handler, - { "TOCLIENT_SRP_BYTES_S_B", TOCLIENT_STATE_NOT_CONNECTED, &Client::handleCommand_SrpBytesSandB }, // 0x60 - { "TOCLIENT_FORMSPEC_PREPEND", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_FormspecPrepend }, // 0x61, +const ToClientCommandHandler toClientCommandTable[TOCLIENT_NUM_MSG_TYPES] = { + null_command_handler, // 0x00 (never use this) + null_command_handler, // 0x01 + {"TOCLIENT_HELLO", TOCLIENT_STATE_NOT_CONNECTED, + &Client::handleCommand_Hello}, // 0x02 + {"TOCLIENT_AUTH_ACCEPT", TOCLIENT_STATE_NOT_CONNECTED, + &Client::handleCommand_AuthAccept}, // 0x03 + {"TOCLIENT_ACCEPT_SUDO_MODE", TOCLIENT_STATE_CONNECTED, + &Client::handleCommand_AcceptSudoMode}, // 0x04 + {"TOCLIENT_DENY_SUDO_MODE", TOCLIENT_STATE_CONNECTED, + &Client::handleCommand_DenySudoMode}, // 0x05 + null_command_handler, // 0x06 + null_command_handler, // 0x07 + null_command_handler, // 0x08 + null_command_handler, // 0x09 + {"TOCLIENT_ACCESS_DENIED", TOCLIENT_STATE_NOT_CONNECTED, + &Client::handleCommand_AccessDenied}, // 0x0A + null_command_handler, // 0x0B + null_command_handler, // 0x0C + null_command_handler, // 0x0D + null_command_handler, // 0x0E + null_command_handler, // 0x0F + null_command_handler, // 0x10 + null_command_handler, null_command_handler, null_command_handler, + null_command_handler, null_command_handler, null_command_handler, + null_command_handler, null_command_handler, null_command_handler, + null_command_handler, null_command_handler, null_command_handler, + null_command_handler, null_command_handler, null_command_handler, + {"TOCLIENT_BLOCKDATA", TOCLIENT_STATE_CONNECTED, + &Client::handleCommand_BlockData}, // 0x20 + {"TOCLIENT_ADDNODE", TOCLIENT_STATE_CONNECTED, + &Client::handleCommand_AddNode}, // 0x21 + {"TOCLIENT_REMOVENODE", TOCLIENT_STATE_CONNECTED, + &Client::handleCommand_RemoveNode}, // 0x22 + null_command_handler, null_command_handler, null_command_handler, + null_command_handler, + {"TOCLIENT_INVENTORY", TOCLIENT_STATE_CONNECTED, + &Client::handleCommand_Inventory}, // 0x27 + null_command_handler, + {"TOCLIENT_TIME_OF_DAY", TOCLIENT_STATE_CONNECTED, + &Client::handleCommand_TimeOfDay}, // 0x29 + {"TOCLIENT_CSM_RESTRICTION_FLAGS", TOCLIENT_STATE_CONNECTED, + &Client::handleCommand_CSMRestrictionFlags}, // 0x2A + {"TOCLIENT_PLAYER_SPEED", TOCLIENT_STATE_CONNECTED, + &Client::handleCommand_PlayerSpeed}, // 0x2B + {"TOCLIENT_MEDIA_PUSH", TOCLIENT_STATE_CONNECTED, + &Client::handleCommand_MediaPush}, // 0x2C + null_command_handler, null_command_handler, + {"TOCLIENT_CHAT_MESSAGE", TOCLIENT_STATE_CONNECTED, + &Client::handleCommand_ChatMessage}, // 0x2F + null_command_handler, // 0x30 + {"TOCLIENT_ACTIVE_OBJECT_REMOVE_ADD", TOCLIENT_STATE_CONNECTED, + &Client::handleCommand_ActiveObjectRemoveAdd}, // 0x31 + {"TOCLIENT_ACTIVE_OBJECT_MESSAGES", TOCLIENT_STATE_CONNECTED, + &Client::handleCommand_ActiveObjectMessages}, // 0x32 + {"TOCLIENT_HP", TOCLIENT_STATE_CONNECTED, + &Client::handleCommand_HP}, // 0x33 + {"TOCLIENT_MOVE_PLAYER", TOCLIENT_STATE_CONNECTED, + &Client::handleCommand_MovePlayer}, // 0x34 + {"TOCLIENT_ACCESS_DENIED_LEGACY", TOCLIENT_STATE_NOT_CONNECTED, + &Client::handleCommand_AccessDenied}, // 0x35 + {"TOCLIENT_FOV", TOCLIENT_STATE_CONNECTED, + &Client::handleCommand_Fov}, // 0x36 + {"TOCLIENT_DEATHSCREEN", TOCLIENT_STATE_CONNECTED, + &Client::handleCommand_DeathScreen}, // 0x37 + {"TOCLIENT_MEDIA", TOCLIENT_STATE_CONNECTED, + &Client::handleCommand_Media}, // 0x38 + null_command_handler, + {"TOCLIENT_NODEDEF", TOCLIENT_STATE_CONNECTED, + &Client::handleCommand_NodeDef}, // 0x3a + null_command_handler, + {"TOCLIENT_ANNOUNCE_MEDIA", TOCLIENT_STATE_CONNECTED, + &Client::handleCommand_AnnounceMedia}, // 0x3c + {"TOCLIENT_ITEMDEF", TOCLIENT_STATE_CONNECTED, + &Client::handleCommand_ItemDef}, // 0x3d + null_command_handler, + {"TOCLIENT_PLAY_SOUND", TOCLIENT_STATE_CONNECTED, + &Client::handleCommand_PlaySound}, // 0x3f + {"TOCLIENT_STOP_SOUND", TOCLIENT_STATE_CONNECTED, + &Client::handleCommand_StopSound}, // 0x40 + {"TOCLIENT_PRIVILEGES", TOCLIENT_STATE_CONNECTED, + &Client::handleCommand_Privileges}, // 0x41 + {"TOCLIENT_INVENTORY_FORMSPEC", TOCLIENT_STATE_CONNECTED, + &Client::handleCommand_InventoryFormSpec}, // 0x42 + {"TOCLIENT_DETACHED_INVENTORY", TOCLIENT_STATE_CONNECTED, + &Client::handleCommand_DetachedInventory}, // 0x43 + {"TOCLIENT_SHOW_FORMSPEC", TOCLIENT_STATE_CONNECTED, + &Client::handleCommand_ShowFormSpec}, // 0x44 + {"TOCLIENT_MOVEMENT", TOCLIENT_STATE_CONNECTED, + &Client::handleCommand_Movement}, // 0x45 + {"TOCLIENT_SPAWN_PARTICLE", TOCLIENT_STATE_CONNECTED, + &Client::handleCommand_SpawnParticle}, // 0x46 + {"TOCLIENT_ADD_PARTICLESPAWNER", TOCLIENT_STATE_CONNECTED, + &Client::handleCommand_AddParticleSpawner}, // 0x47 + null_command_handler, + {"TOCLIENT_HUDADD", TOCLIENT_STATE_CONNECTED, + &Client::handleCommand_HudAdd}, // 0x49 + {"TOCLIENT_HUDRM", TOCLIENT_STATE_CONNECTED, + &Client::handleCommand_HudRemove}, // 0x4a + {"TOCLIENT_HUDCHANGE", TOCLIENT_STATE_CONNECTED, + &Client::handleCommand_HudChange}, // 0x4b + {"TOCLIENT_HUD_SET_FLAGS", TOCLIENT_STATE_CONNECTED, + &Client::handleCommand_HudSetFlags}, // 0x4c + {"TOCLIENT_HUD_SET_PARAM", TOCLIENT_STATE_CONNECTED, + &Client::handleCommand_HudSetParam}, // 0x4d + {"TOCLIENT_BREATH", TOCLIENT_STATE_CONNECTED, + &Client::handleCommand_Breath}, // 0x4e + {"TOCLIENT_SET_SKY", TOCLIENT_STATE_CONNECTED, + &Client::handleCommand_HudSetSky}, // 0x4f + {"TOCLIENT_OVERRIDE_DAY_NIGHT_RATIO", TOCLIENT_STATE_CONNECTED, + &Client::handleCommand_OverrideDayNightRatio}, // 0x50 + {"TOCLIENT_LOCAL_PLAYER_ANIMATIONS", TOCLIENT_STATE_CONNECTED, + &Client::handleCommand_LocalPlayerAnimations}, // 0x51 + {"TOCLIENT_EYE_OFFSET", TOCLIENT_STATE_CONNECTED, + &Client::handleCommand_EyeOffset}, // 0x52 + {"TOCLIENT_DELETE_PARTICLESPAWNER", TOCLIENT_STATE_CONNECTED, + &Client::handleCommand_DeleteParticleSpawner}, // 0x53 + {"TOCLIENT_CLOUD_PARAMS", TOCLIENT_STATE_CONNECTED, + &Client::handleCommand_CloudParams}, // 0x54 + {"TOCLIENT_FADE_SOUND", TOCLIENT_STATE_CONNECTED, + &Client::handleCommand_FadeSound}, // 0x55 + {"TOCLIENT_UPDATE_PLAYER_LIST", TOCLIENT_STATE_CONNECTED, + &Client::handleCommand_UpdatePlayerList}, // 0x56 + {"TOCLIENT_MODCHANNEL_MSG", TOCLIENT_STATE_CONNECTED, + &Client::handleCommand_ModChannelMsg}, // 0x57 + {"TOCLIENT_MODCHANNEL_SIGNAL", TOCLIENT_STATE_CONNECTED, + &Client::handleCommand_ModChannelSignal}, // 0x58 + {"TOCLIENT_NODEMETA_CHANGED", TOCLIENT_STATE_CONNECTED, + &Client::handleCommand_NodemetaChanged}, // 0x59 + {"TOCLIENT_SET_SUN", TOCLIENT_STATE_CONNECTED, + &Client::handleCommand_HudSetSun}, // 0x5a + {"TOCLIENT_SET_MOON", TOCLIENT_STATE_CONNECTED, + &Client::handleCommand_HudSetMoon}, // 0x5b + {"TOCLIENT_SET_STARS", TOCLIENT_STATE_CONNECTED, + &Client::handleCommand_HudSetStars}, // 0x5c + null_command_handler, null_command_handler, null_command_handler, + {"TOCLIENT_SRP_BYTES_S_B", TOCLIENT_STATE_NOT_CONNECTED, + &Client::handleCommand_SrpBytesSandB}, // 0x60 + {"TOCLIENT_FORMSPEC_PREPEND", TOCLIENT_STATE_CONNECTED, + &Client::handleCommand_FormspecPrepend}, // 0x61, }; -const static ServerCommandFactory null_command_factory = { "TOSERVER_NULL", 0, false }; +const static ServerCommandFactory null_command_factory = {"TOSERVER_NULL", 0, false}; /* Channels used for Client -> Server communication @@ -136,89 +177,88 @@ const static ServerCommandFactory null_command_factory = { "TOSERVER_NULL", 0, f the same objects are *required* to be in the same channel. */ -const ServerCommandFactory serverCommandFactoryTable[TOSERVER_NUM_MSG_TYPES] = -{ - null_command_factory, // 0x00 - null_command_factory, // 0x01 - { "TOSERVER_INIT", 1, false }, // 0x02 - null_command_factory, // 0x03 - null_command_factory, // 0x04 - null_command_factory, // 0x05 - null_command_factory, // 0x06 - null_command_factory, // 0x07 - null_command_factory, // 0x08 - null_command_factory, // 0x09 - null_command_factory, // 0x0a - null_command_factory, // 0x0b - null_command_factory, // 0x0c - null_command_factory, // 0x0d - null_command_factory, // 0x0e - null_command_factory, // 0x0f - null_command_factory, // 0x10 - { "TOSERVER_INIT2", 1, true }, // 0x11 - null_command_factory, // 0x12 - null_command_factory, // 0x13 - null_command_factory, // 0x14 - null_command_factory, // 0x15 - null_command_factory, // 0x16 - { "TOSERVER_MODCHANNEL_JOIN", 0, true }, // 0x17 - { "TOSERVER_MODCHANNEL_LEAVE", 0, true }, // 0x18 - { "TOSERVER_MODCHANNEL_MSG", 0, true }, // 0x19 - null_command_factory, // 0x1a - null_command_factory, // 0x1b - null_command_factory, // 0x1c - null_command_factory, // 0x1d - null_command_factory, // 0x1e - null_command_factory, // 0x1f - null_command_factory, // 0x20 - null_command_factory, // 0x21 - null_command_factory, // 0x22 - { "TOSERVER_PLAYERPOS", 0, false }, // 0x23 - { "TOSERVER_GOTBLOCKS", 2, true }, // 0x24 - { "TOSERVER_DELETEDBLOCKS", 2, true }, // 0x25 - null_command_factory, // 0x26 - null_command_factory, // 0x27 - null_command_factory, // 0x28 - null_command_factory, // 0x29 - null_command_factory, // 0x2a - null_command_factory, // 0x2b - null_command_factory, // 0x2c - null_command_factory, // 0x2d - null_command_factory, // 0x2e - null_command_factory, // 0x2f - null_command_factory, // 0x30 - { "TOSERVER_INVENTORY_ACTION", 0, true }, // 0x31 - { "TOSERVER_CHAT_MESSAGE", 0, true }, // 0x32 - null_command_factory, // 0x33 - null_command_factory, // 0x34 - { "TOSERVER_DAMAGE", 0, true }, // 0x35 - null_command_factory, // 0x36 - { "TOSERVER_PLAYERITEM", 0, true }, // 0x37 - { "TOSERVER_RESPAWN", 0, true }, // 0x38 - { "TOSERVER_INTERACT", 0, true }, // 0x39 - { "TOSERVER_REMOVED_SOUNDS", 2, true }, // 0x3a - { "TOSERVER_NODEMETA_FIELDS", 0, true }, // 0x3b - { "TOSERVER_INVENTORY_FIELDS", 0, true }, // 0x3c - null_command_factory, // 0x3d - null_command_factory, // 0x3e - null_command_factory, // 0x3f - { "TOSERVER_REQUEST_MEDIA", 1, true }, // 0x40 - null_command_factory, // 0x41 - null_command_factory, // 0x42 - { "TOSERVER_CLIENT_READY", 1, true }, // 0x43 - null_command_factory, // 0x44 - null_command_factory, // 0x45 - null_command_factory, // 0x46 - null_command_factory, // 0x47 - null_command_factory, // 0x48 - null_command_factory, // 0x49 - null_command_factory, // 0x4a - null_command_factory, // 0x4b - null_command_factory, // 0x4c - null_command_factory, // 0x4d - null_command_factory, // 0x4e - null_command_factory, // 0x4f - { "TOSERVER_FIRST_SRP", 1, true }, // 0x50 - { "TOSERVER_SRP_BYTES_A", 1, true }, // 0x51 - { "TOSERVER_SRP_BYTES_M", 1, true }, // 0x52 +const ServerCommandFactory serverCommandFactoryTable[TOSERVER_NUM_MSG_TYPES] = { + null_command_factory, // 0x00 + null_command_factory, // 0x01 + {"TOSERVER_INIT", 1, false}, // 0x02 + null_command_factory, // 0x03 + null_command_factory, // 0x04 + null_command_factory, // 0x05 + null_command_factory, // 0x06 + null_command_factory, // 0x07 + null_command_factory, // 0x08 + null_command_factory, // 0x09 + null_command_factory, // 0x0a + null_command_factory, // 0x0b + null_command_factory, // 0x0c + null_command_factory, // 0x0d + null_command_factory, // 0x0e + null_command_factory, // 0x0f + null_command_factory, // 0x10 + {"TOSERVER_INIT2", 1, true}, // 0x11 + null_command_factory, // 0x12 + null_command_factory, // 0x13 + null_command_factory, // 0x14 + null_command_factory, // 0x15 + null_command_factory, // 0x16 + {"TOSERVER_MODCHANNEL_JOIN", 0, true}, // 0x17 + {"TOSERVER_MODCHANNEL_LEAVE", 0, true}, // 0x18 + {"TOSERVER_MODCHANNEL_MSG", 0, true}, // 0x19 + null_command_factory, // 0x1a + null_command_factory, // 0x1b + null_command_factory, // 0x1c + null_command_factory, // 0x1d + null_command_factory, // 0x1e + null_command_factory, // 0x1f + null_command_factory, // 0x20 + null_command_factory, // 0x21 + null_command_factory, // 0x22 + {"TOSERVER_PLAYERPOS", 0, false}, // 0x23 + {"TOSERVER_GOTBLOCKS", 2, true}, // 0x24 + {"TOSERVER_DELETEDBLOCKS", 2, true}, // 0x25 + null_command_factory, // 0x26 + null_command_factory, // 0x27 + null_command_factory, // 0x28 + null_command_factory, // 0x29 + null_command_factory, // 0x2a + null_command_factory, // 0x2b + null_command_factory, // 0x2c + null_command_factory, // 0x2d + null_command_factory, // 0x2e + null_command_factory, // 0x2f + null_command_factory, // 0x30 + {"TOSERVER_INVENTORY_ACTION", 0, true}, // 0x31 + {"TOSERVER_CHAT_MESSAGE", 0, true}, // 0x32 + null_command_factory, // 0x33 + null_command_factory, // 0x34 + {"TOSERVER_DAMAGE", 0, true}, // 0x35 + null_command_factory, // 0x36 + {"TOSERVER_PLAYERITEM", 0, true}, // 0x37 + {"TOSERVER_RESPAWN", 0, true}, // 0x38 + {"TOSERVER_INTERACT", 0, true}, // 0x39 + {"TOSERVER_REMOVED_SOUNDS", 2, true}, // 0x3a + {"TOSERVER_NODEMETA_FIELDS", 0, true}, // 0x3b + {"TOSERVER_INVENTORY_FIELDS", 0, true}, // 0x3c + null_command_factory, // 0x3d + null_command_factory, // 0x3e + null_command_factory, // 0x3f + {"TOSERVER_REQUEST_MEDIA", 1, true}, // 0x40 + null_command_factory, // 0x41 + null_command_factory, // 0x42 + {"TOSERVER_CLIENT_READY", 1, true}, // 0x43 + null_command_factory, // 0x44 + null_command_factory, // 0x45 + null_command_factory, // 0x46 + null_command_factory, // 0x47 + null_command_factory, // 0x48 + null_command_factory, // 0x49 + null_command_factory, // 0x4a + null_command_factory, // 0x4b + null_command_factory, // 0x4c + null_command_factory, // 0x4d + null_command_factory, // 0x4e + null_command_factory, // 0x4f + {"TOSERVER_FIRST_SRP", 1, true}, // 0x50 + {"TOSERVER_SRP_BYTES_A", 1, true}, // 0x51 + {"TOSERVER_SRP_BYTES_M", 1, true}, // 0x52 }; diff --git a/src/network/clientopcodes.h b/src/network/clientopcodes.h index d03dc457d..d984d0cb7 100644 --- a/src/network/clientopcodes.h +++ b/src/network/clientopcodes.h @@ -25,7 +25,8 @@ with this program; if not, write to the Free Software Foundation, Inc., class NetworkPacket; -enum ToClientConnectionState { +enum ToClientConnectionState +{ TOCLIENT_STATE_NOT_CONNECTED, TOCLIENT_STATE_CONNECTED, TOCLIENT_STATE_ALL, @@ -33,14 +34,14 @@ enum ToClientConnectionState { struct ToClientCommandHandler { - const char* name; - ToClientConnectionState state; - void (Client::*handler)(NetworkPacket* pkt); + const char *name; + ToClientConnectionState state; + void (Client::*handler)(NetworkPacket *pkt); }; struct ServerCommandFactory { - const char* name; + const char *name; u8 channel; bool reliable; }; diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index f0fb09fad..7e72bc549 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -46,14 +46,14 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "gettext.h" #include "skyparams.h" -void Client::handleCommand_Deprecated(NetworkPacket* pkt) +void Client::handleCommand_Deprecated(NetworkPacket *pkt) { infostream << "Got deprecated command " - << toClientCommandTable[pkt->getCommand()].name << " from peer " - << pkt->getPeerId() << "!" << std::endl; + << toClientCommandTable[pkt->getCommand()].name << " from peer " + << pkt->getPeerId() << "!" << std::endl; } -void Client::handleCommand_Hello(NetworkPacket* pkt) +void Client::handleCommand_Hello(NetworkPacket *pkt) { if (pkt->getSize() < 1) return; @@ -63,39 +63,38 @@ void Client::handleCommand_Hello(NetworkPacket* pkt) u16 compression_mode; u32 auth_mechs; std::string username_legacy; // for case insensitivity - *pkt >> serialization_ver >> compression_mode >> proto_ver - >> auth_mechs >> username_legacy; + *pkt >> serialization_ver >> compression_mode >> proto_ver >> auth_mechs >> + username_legacy; // Chose an auth method we support AuthMechanism chosen_auth_mechanism = choseAuthMech(auth_mechs); infostream << "Client: TOCLIENT_HELLO received with " - << "serialization_ver=" << (u32)serialization_ver - << ", auth_mechs=" << auth_mechs - << ", proto_ver=" << proto_ver - << ", compression_mode=" << compression_mode - << ". Doing auth with mech " << chosen_auth_mechanism << std::endl; + << "serialization_ver=" << (u32)serialization_ver + << ", auth_mechs=" << auth_mechs << ", proto_ver=" << proto_ver + << ", compression_mode=" << compression_mode + << ". Doing auth with mech " << chosen_auth_mechanism << std::endl; if (!ser_ver_supported(serialization_ver)) { infostream << "Client: TOCLIENT_HELLO: Server sent " - << "unsupported ser_fmt_ver"<< std::endl; + << "unsupported ser_fmt_ver" << std::endl; return; } m_server_ser_ver = serialization_ver; m_proto_ver = proto_ver; - //TODO verify that username_legacy matches sent username, only + // TODO verify that username_legacy matches sent username, only // differs in casing (make both uppercase and compare) // This is only neccessary though when we actually want to add casing support if (m_chosen_auth_mech != AUTH_MECHANISM_NONE) { // we recieved a TOCLIENT_HELLO while auth was already going on errorstream << "Client: TOCLIENT_HELLO while auth was already going on" - << "(chosen_mech=" << m_chosen_auth_mech << ")." << std::endl; + << "(chosen_mech=" << m_chosen_auth_mech << ")." << std::endl; if (m_chosen_auth_mech == AUTH_MECHANISM_SRP || m_chosen_auth_mech == AUTH_MECHANISM_LEGACY_PASSWORD) { - srp_user_delete((SRPUser *) m_auth_data); + srp_user_delete((SRPUser *)m_auth_data); m_auth_data = 0; } } @@ -116,16 +115,15 @@ void Client::handleCommand_Hello(NetworkPacket* pkt) m_access_denied_reason = "Unknown"; m_con->Disconnect(); } - } -void Client::handleCommand_AuthAccept(NetworkPacket* pkt) +void Client::handleCommand_AuthAccept(NetworkPacket *pkt) { deleteAuthData(); v3f playerpos; - *pkt >> playerpos >> m_map_seed >> m_recommended_send_interval - >> m_sudo_auth_methods; + *pkt >> playerpos >> m_map_seed >> m_recommended_send_interval >> + m_sudo_auth_methods; playerpos -= v3f(0, BS / 2, 0); @@ -136,7 +134,7 @@ void Client::handleCommand_AuthAccept(NetworkPacket* pkt) infostream << "Client: received map seed: " << m_map_seed << std::endl; infostream << "Client: received recommended send interval " - << m_recommended_send_interval<getSize() < 6) return; @@ -237,7 +235,7 @@ void Client::handleCommand_RemoveNode(NetworkPacket* pkt) removeNode(p); } -void Client::handleCommand_AddNode(NetworkPacket* pkt) +void Client::handleCommand_AddNode(NetworkPacket *pkt) { if (pkt->getSize() < 6 + MapNode::serializedLength(m_server_ser_ver)) return; @@ -274,8 +272,7 @@ void Client::handleCommand_NodemetaChanged(NetworkPacket *pkt) i != meta_updates_list.end(); ++i) { v3s16 pos = i->first; - if (map.isValidPosition(pos) && - map.setNodeMetadata(pos, i->second)) + if (map.isValidPosition(pos) && map.setNodeMetadata(pos, i->second)) continue; // Prevent from deleting metadata // Meta couldn't be set, unused metadata @@ -283,7 +280,7 @@ void Client::handleCommand_NodemetaChanged(NetworkPacket *pkt) } } -void Client::handleCommand_BlockData(NetworkPacket* pkt) +void Client::handleCommand_BlockData(NetworkPacket *pkt) { // Ignore too small packet if (pkt->getSize() < 6) @@ -310,8 +307,7 @@ void Client::handleCommand_BlockData(NetworkPacket* pkt) */ block->deSerialize(istr, m_server_ser_ver, false); block->deSerializeNetworkSpecific(istr); - } - else { + } else { /* Create a new block */ @@ -331,7 +327,7 @@ void Client::handleCommand_BlockData(NetworkPacket* pkt) addUpdateMeshTaskWithEdge(p, true); } -void Client::handleCommand_Inventory(NetworkPacket* pkt) +void Client::handleCommand_Inventory(NetworkPacket *pkt) { if (pkt->getSize() < 1) return; @@ -351,7 +347,7 @@ void Client::handleCommand_Inventory(NetworkPacket* pkt) m_inventory_from_server_age = 0.0; } -void Client::handleCommand_TimeOfDay(NetworkPacket* pkt) +void Client::handleCommand_TimeOfDay(NetworkPacket *pkt) { if (pkt->getSize() < 2) return; @@ -360,13 +356,12 @@ void Client::handleCommand_TimeOfDay(NetworkPacket* pkt) *pkt >> time_of_day; - time_of_day = time_of_day % 24000; + time_of_day = time_of_day % 24000; float time_speed = 0; if (pkt->getSize() >= 2 + 4) { *pkt >> time_speed; - } - else { + } else { // Old message; try to approximate speed of time by ourselves float time_of_day_f = (float)time_of_day / 24000.0f; float tod_diff_f = 0; @@ -376,15 +371,15 @@ void Client::handleCommand_TimeOfDay(NetworkPacket* pkt) else tod_diff_f = time_of_day_f - m_last_time_of_day_f; - m_last_time_of_day_f = time_of_day_f; - float time_diff = m_time_of_day_update_timer; + m_last_time_of_day_f = time_of_day_f; + float time_diff = m_time_of_day_update_timer; m_time_of_day_update_timer = 0; if (m_time_of_day_set) { time_speed = (3600.0f * 24.0f) * tod_diff_f / time_diff; infostream << "Client: Measured time_of_day speed (old format): " - << time_speed << " tod_diff_f=" << tod_diff_f - << " time_diff=" << time_diff << std::endl; + << time_speed << " tod_diff_f=" << tod_diff_f + << " time_diff=" << time_diff << std::endl; } } @@ -393,8 +388,8 @@ void Client::handleCommand_TimeOfDay(NetworkPacket* pkt) m_env.setTimeOfDaySpeed(time_speed); m_time_of_day_set = true; - //u32 dr = m_env.getDayNightRatio(); - //infostream << "Client: time_of_day=" << time_of_day + // u32 dr = m_env.getDayNightRatio(); + // infostream << "Client: time_of_day=" << time_of_day // << " time_speed=" << time_speed // << " dr=" << dr << std::endl; } @@ -423,11 +418,11 @@ void Client::handleCommand_ChatMessage(NetworkPacket *pkt) *pkt >> chatMessage->sender >> chatMessage->message >> timestamp; chatMessage->timestamp = static_cast(timestamp); - chatMessage->type = (ChatMessageType) message_type; + chatMessage->type = (ChatMessageType)message_type; // @TODO send this to CSM using ChatMessage object if (modsLoaded() && m_script->on_receiving_message( - wide_to_utf8(chatMessage->message))) { + wide_to_utf8(chatMessage->message))) { // Message was consumed by CSM and should not be handled by client delete chatMessage; } else { @@ -435,7 +430,7 @@ void Client::handleCommand_ChatMessage(NetworkPacket *pkt) } } -void Client::handleCommand_ActiveObjectRemoveAdd(NetworkPacket* pkt) +void Client::handleCommand_ActiveObjectRemoveAdd(NetworkPacket *pkt) { /* u16 count of removed objects @@ -450,10 +445,10 @@ void Client::handleCommand_ActiveObjectRemoveAdd(NetworkPacket* pkt) string initialization data } */ - + LocalPlayer *player = m_env.getLocalPlayer(); - bool try_reattach = player && player->isWaitingForReattach(); - + bool try_reattach = player && player->isWaitingForReattach(); + try { u8 type; u16 removed_count, added_count, id; @@ -477,7 +472,7 @@ void Client::handleCommand_ActiveObjectRemoveAdd(NetworkPacket* pkt) } } catch (PacketError &e) { infostream << "handleCommand_ActiveObjectRemoveAdd: " << e.what() - << ". The packet is unreliable, ignoring" << std::endl; + << ". The packet is unreliable, ignoring" << std::endl; } // m_activeobjects_received is false before the first @@ -485,7 +480,7 @@ void Client::handleCommand_ActiveObjectRemoveAdd(NetworkPacket* pkt) m_activeobjects_received = true; } -void Client::handleCommand_ActiveObjectMessages(NetworkPacket* pkt) +void Client::handleCommand_ActiveObjectMessages(NetworkPacket *pkt) { /* for all objects @@ -511,32 +506,32 @@ void Client::handleCommand_ActiveObjectMessages(NetworkPacket* pkt) } } catch (SerializationError &e) { errorstream << "Client::handleCommand_ActiveObjectMessages: " - << "caught SerializationError: " << e.what() << std::endl; + << "caught SerializationError: " << e.what() << std::endl; } } -void Client::handleCommand_Movement(NetworkPacket* pkt) +void Client::handleCommand_Movement(NetworkPacket *pkt) { LocalPlayer *player = m_env.getLocalPlayer(); assert(player != NULL); float mad, maa, maf, msw, mscr, msf, mscl, msj, lf, lfs, ls, g; - *pkt >> mad >> maa >> maf >> msw >> mscr >> msf >> mscl >> msj - >> lf >> lfs >> ls >> g; + *pkt >> mad >> maa >> maf >> msw >> mscr >> msf >> mscl >> msj >> lf >> lfs >> + ls >> g; - player->movement_acceleration_default = mad * BS; - player->movement_acceleration_air = maa * BS; - player->movement_acceleration_fast = maf * BS; - player->movement_speed_walk = msw * BS; - player->movement_speed_crouch = mscr * BS; - player->movement_speed_fast = msf * BS; - player->movement_speed_climb = mscl * BS; - player->movement_speed_jump = msj * BS; - player->movement_liquid_fluidity = lf * BS; + player->movement_acceleration_default = mad * BS; + player->movement_acceleration_air = maa * BS; + player->movement_acceleration_fast = maf * BS; + player->movement_speed_walk = msw * BS; + player->movement_speed_crouch = mscr * BS; + player->movement_speed_fast = msf * BS; + player->movement_speed_climb = mscl * BS; + player->movement_speed_jump = msj * BS; + player->movement_liquid_fluidity = lf * BS; player->movement_liquid_fluidity_smooth = lfs * BS; - player->movement_liquid_sink = ls * BS; - player->movement_gravity = g * BS; + player->movement_liquid_sink = ls * BS; + player->movement_gravity = g * BS; } void Client::handleCommand_Fov(NetworkPacket *pkt) @@ -551,11 +546,12 @@ void Client::handleCommand_Fov(NetworkPacket *pkt) // try-catch to preserve backwards compat try { *pkt >> transition_time; - } catch (PacketError &e) {}; + } catch (PacketError &e) { + }; LocalPlayer *player = m_env.getLocalPlayer(); assert(player); - player->setFov({ fov, is_multiplier, transition_time }); + player->setFov({fov, is_multiplier, transition_time}); m_camera->notifyFovChange(); } @@ -583,7 +579,7 @@ void Client::handleCommand_HP(NetworkPacket *pkt) } } -void Client::handleCommand_Breath(NetworkPacket* pkt) +void Client::handleCommand_Breath(NetworkPacket *pkt) { LocalPlayer *player = m_env.getLocalPlayer(); assert(player != NULL); @@ -595,14 +591,15 @@ void Client::handleCommand_Breath(NetworkPacket* pkt) player->setBreath(breath); } -void Client::handleCommand_MovePlayer(NetworkPacket* pkt) -{ +void Client::handleCommand_MovePlayer(NetworkPacket *pkt) +{ LocalPlayer *player = m_env.getLocalPlayer(); assert(player != NULL); - if ((player->getCAO() && player->getCAO()->getParentId()) || player->isWaitingForReattach()) + if ((player->getCAO() && player->getCAO()->getParentId()) || + player->isWaitingForReattach()) return; - + v3f pos; f32 pitch, yaw; @@ -611,10 +608,8 @@ void Client::handleCommand_MovePlayer(NetworkPacket* pkt) player->setLegitPosition(pos); infostream << "Client got TOCLIENT_MOVE_PLAYER" - << " pos=(" << pos.X << "," << pos.Y << "," << pos.Z << ")" - << " pitch=" << pitch - << " yaw=" << yaw - << std::endl; + << " pos=(" << pos.X << "," << pos.Y << "," << pos.Z << ")" + << " pitch=" << pitch << " yaw=" << yaw << std::endl; /* Add to ClientEvent queue. @@ -622,10 +617,10 @@ void Client::handleCommand_MovePlayer(NetworkPacket* pkt) it would just force the pitch and yaw values to whatever the camera points to. */ - + if (g_settings->getBool("no_force_rotate")) return; - + ClientEvent *event = new ClientEvent(); event->type = CE_PLAYER_FORCE_MOVE; event->player_force_move.pitch = pitch; @@ -633,7 +628,7 @@ void Client::handleCommand_MovePlayer(NetworkPacket* pkt) m_client_event_queue.push(event); } -void Client::handleCommand_DeathScreen(NetworkPacket* pkt) +void Client::handleCommand_DeathScreen(NetworkPacket *pkt) { bool set_camera_point_target; v3f camera_point_target; @@ -642,32 +637,32 @@ void Client::handleCommand_DeathScreen(NetworkPacket* pkt) *pkt >> camera_point_target; ClientEvent *event = new ClientEvent(); - event->type = CE_DEATHSCREEN; + event->type = CE_DEATHSCREEN; event->deathscreen.set_camera_point_target = set_camera_point_target; - event->deathscreen.camera_point_target_x = camera_point_target.X; - event->deathscreen.camera_point_target_y = camera_point_target.Y; - event->deathscreen.camera_point_target_z = camera_point_target.Z; + event->deathscreen.camera_point_target_x = camera_point_target.X; + event->deathscreen.camera_point_target_y = camera_point_target.Y; + event->deathscreen.camera_point_target_z = camera_point_target.Z; m_client_event_queue.push(event); } -void Client::handleCommand_AnnounceMedia(NetworkPacket* pkt) +void Client::handleCommand_AnnounceMedia(NetworkPacket *pkt) { u16 num_files; *pkt >> num_files; infostream << "Client: Received media announcement: packet size: " - << pkt->getSize() << std::endl; + << pkt->getSize() << std::endl; - if (m_media_downloader == NULL || - m_media_downloader->isStarted()) { - const char *problem = m_media_downloader ? - "we already saw another announcement" : - "all media has been received already"; - errorstream << "Client: Received media announcement but " - << problem << "! " - << " files=" << num_files - << " size=" << pkt->getSize() << std::endl; + if (m_media_downloader == NULL || m_media_downloader->isStarted()) { + const char *problem = + m_media_downloader + ? "we already saw another announcement" + : "all media has been received already"; + errorstream << "Client: Received media announcement but " << problem + << "! " + << " files=" << num_files << " size=" << pkt->getSize() + << std::endl; return; } @@ -690,20 +685,19 @@ void Client::handleCommand_AnnounceMedia(NetworkPacket* pkt) *pkt >> str; Strfnd sf(str); - while(!sf.at_end()) { + while (!sf.at_end()) { std::string baseurl = trim(sf.next(",")); if (!baseurl.empty()) m_media_downloader->addRemoteServer(baseurl); } - } - catch(SerializationError& e) { + } catch (SerializationError &e) { // not supported by server or turned off } m_media_downloader->step(this); } -void Client::handleCommand_Media(NetworkPacket* pkt) +void Client::handleCommand_Media(NetworkPacket *pkt) { /* u16 command @@ -723,22 +717,21 @@ void Client::handleCommand_Media(NetworkPacket* pkt) *pkt >> num_bunches >> bunch_i >> num_files; - infostream << "Client: Received files: bunch " << bunch_i << "/" - << num_bunches << " files=" << num_files - << " size=" << pkt->getSize() << std::endl; + infostream << "Client: Received files: bunch " << bunch_i << "/" << num_bunches + << " files=" << num_files << " size=" << pkt->getSize() << std::endl; if (num_files == 0) return; if (!m_media_downloader || !m_media_downloader->isStarted()) { - const char *problem = m_media_downloader ? - "media has not been requested" : - "all media has been received already"; - errorstream << "Client: Received media but " - << problem << "! " - << " bunch " << bunch_i << "/" << num_bunches - << " files=" << num_files - << " size=" << pkt->getSize() << std::endl; + const char *problem = + m_media_downloader + ? "media has not been requested" + : "all media has been received already"; + errorstream << "Client: Received media but " << problem << "! " + << " bunch " << bunch_i << "/" << num_bunches + << " files=" << num_files << " size=" << pkt->getSize() + << std::endl; return; } @@ -746,22 +739,21 @@ void Client::handleCommand_Media(NetworkPacket* pkt) // updating content definitions sanity_check(!m_mesh_update_thread.isRunning()); - for (u32 i=0; i < num_files; i++) { + for (u32 i = 0; i < num_files; i++) { std::string name; *pkt >> name; std::string data = pkt->readLongString(); - m_media_downloader->conventionalTransferDone( - name, data, this); + m_media_downloader->conventionalTransferDone(name, data, this); } } -void Client::handleCommand_NodeDef(NetworkPacket* pkt) +void Client::handleCommand_NodeDef(NetworkPacket *pkt) { - infostream << "Client: Received node definitions: packet size: " - << pkt->getSize() << std::endl; + infostream << "Client: Received node definitions: packet size: " << pkt->getSize() + << std::endl; // Mesh update thread must be stopped while // updating content definitions @@ -778,10 +770,10 @@ void Client::handleCommand_NodeDef(NetworkPacket* pkt) m_nodedef_received = true; } -void Client::handleCommand_ItemDef(NetworkPacket* pkt) +void Client::handleCommand_ItemDef(NetworkPacket *pkt) { - infostream << "Client: Received item definitions: packet size: " - << pkt->getSize() << std::endl; + infostream << "Client: Received item definitions: packet size: " << pkt->getSize() + << std::endl; // Mesh update thread must be stopped while // updating content definitions @@ -798,7 +790,7 @@ void Client::handleCommand_ItemDef(NetworkPacket* pkt) m_itemdef_received = true; } -void Client::handleCommand_PlaySound(NetworkPacket* pkt) +void Client::handleCommand_PlaySound(NetworkPacket *pkt) { /* [0] u32 server_id @@ -832,27 +824,27 @@ void Client::handleCommand_PlaySound(NetworkPacket* pkt) *pkt >> fade; *pkt >> pitch; *pkt >> ephemeral; - } catch (PacketError &e) {}; + } catch (PacketError &e) { + }; // Start playing int client_id = -1; - switch(type) { - case 0: // local - client_id = m_sound->playSound(name, loop, gain, fade, pitch); - break; - case 1: // positional - client_id = m_sound->playSoundAt(name, loop, gain, pos, pitch); - break; - case 2: - { // object - ClientActiveObject *cao = m_env.getActiveObject(object_id); - if (cao) - pos = cao->getPosition(); - client_id = m_sound->playSoundAt(name, loop, gain, pos, pitch); - break; - } - default: - break; + switch (type) { + case 0: // local + client_id = m_sound->playSound(name, loop, gain, fade, pitch); + break; + case 1: // positional + client_id = m_sound->playSoundAt(name, loop, gain, pos, pitch); + break; + case 2: { // object + ClientActiveObject *cao = m_env.getActiveObject(object_id); + if (cao) + pos = cao->getPosition(); + client_id = m_sound->playSoundAt(name, loop, gain, pos, pitch); + break; + } + default: + break; } if (client_id != -1) { @@ -866,13 +858,14 @@ void Client::handleCommand_PlaySound(NetworkPacket* pkt) } } -void Client::handleCommand_StopSound(NetworkPacket* pkt) +void Client::handleCommand_StopSound(NetworkPacket *pkt) { s32 server_id; *pkt >> server_id; - std::unordered_map::iterator i = m_sounds_server_to_client.find(server_id); + std::unordered_map::iterator i = + m_sounds_server_to_client.find(server_id); if (i != m_sounds_server_to_client.end()) { int client_id = i->second; m_sound->stopSound(client_id); @@ -894,7 +887,7 @@ void Client::handleCommand_FadeSound(NetworkPacket *pkt) m_sound->fadeSound(i->second, step, gain); } -void Client::handleCommand_Privileges(NetworkPacket* pkt) +void Client::handleCommand_Privileges(NetworkPacket *pkt) { m_privileges.clear(); infostream << "Client: Privileges updated: "; @@ -913,7 +906,7 @@ void Client::handleCommand_Privileges(NetworkPacket* pkt) infostream << std::endl; } -void Client::handleCommand_InventoryFormSpec(NetworkPacket* pkt) +void Client::handleCommand_InventoryFormSpec(NetworkPacket *pkt) { LocalPlayer *player = m_env.getLocalPlayer(); assert(player != NULL); @@ -922,14 +915,14 @@ void Client::handleCommand_InventoryFormSpec(NetworkPacket* pkt) player->inventory_formspec = pkt->readLongString(); } -void Client::handleCommand_DetachedInventory(NetworkPacket* pkt) +void Client::handleCommand_DetachedInventory(NetworkPacket *pkt) { std::string name; bool keep_inv = true; *pkt >> name >> keep_inv; infostream << "Client: Detached inventory update: \"" << name - << "\", mode=" << (keep_inv ? "update" : "remove") << std::endl; + << "\", mode=" << (keep_inv ? "update" : "remove") << std::endl; const auto &inv_it = m_detached_inventories.find(name); if (!keep_inv) { @@ -955,7 +948,7 @@ void Client::handleCommand_DetachedInventory(NetworkPacket* pkt) inv->deSerialize(is); } -void Client::handleCommand_ShowFormSpec(NetworkPacket* pkt) +void Client::handleCommand_ShowFormSpec(NetworkPacket *pkt) { std::string formspec = pkt->readLongString(); std::string formname; @@ -971,7 +964,7 @@ void Client::handleCommand_ShowFormSpec(NetworkPacket* pkt) m_client_event_queue.push(event); } -void Client::handleCommand_SpawnParticle(NetworkPacket* pkt) +void Client::handleCommand_SpawnParticle(NetworkPacket *pkt) { std::string datastring(pkt->getString(0), pkt->getSize()); std::istringstream is(datastring, std::ios_base::binary); @@ -980,17 +973,17 @@ void Client::handleCommand_SpawnParticle(NetworkPacket* pkt) p.deSerialize(is, m_proto_ver); ClientEvent *event = new ClientEvent(); - event->type = CE_SPAWN_PARTICLE; + event->type = CE_SPAWN_PARTICLE; event->spawn_particle = new ParticleParameters(p); - + if (g_settings->getBool("log_particles")) { std::cout << p.pos.X << " " << p.pos.Y << " " << p.pos.Z << std::endl; } - + m_client_event_queue.push(event); } -void Client::handleCommand_AddParticleSpawner(NetworkPacket* pkt) +void Client::handleCommand_AddParticleSpawner(NetworkPacket *pkt) { std::string datastring(pkt->getString(0), pkt->getSize()); std::istringstream is(datastring, std::ios_base::binary); @@ -999,20 +992,20 @@ void Client::handleCommand_AddParticleSpawner(NetworkPacket* pkt) u32 server_id; u16 attached_id = 0; - p.amount = readU16(is); - p.time = readF32(is); - p.minpos = readV3F32(is); - p.maxpos = readV3F32(is); - p.minvel = readV3F32(is); - p.maxvel = readV3F32(is); - p.minacc = readV3F32(is); - p.maxacc = readV3F32(is); - p.minexptime = readF32(is); - p.maxexptime = readF32(is); - p.minsize = readF32(is); - p.maxsize = readF32(is); + p.amount = readU16(is); + p.time = readF32(is); + p.minpos = readV3F32(is); + p.maxpos = readV3F32(is); + p.minvel = readV3F32(is); + p.maxvel = readV3F32(is); + p.minacc = readV3F32(is); + p.maxacc = readV3F32(is); + p.minexptime = readF32(is); + p.maxexptime = readF32(is); + p.minsize = readF32(is); + p.maxsize = readF32(is); p.collisiondetection = readU8(is); - p.texture = deSerializeLongString(is); + p.texture = deSerializeLongString(is); server_id = readU32(is); @@ -1032,20 +1025,19 @@ void Client::handleCommand_AddParticleSpawner(NetworkPacket* pkt) break; p.node.param0 = tmp_param0; p.node.param2 = readU8(is); - p.node_tile = readU8(is); + p.node_tile = readU8(is); } while (0); auto event = new ClientEvent(); - event->type = CE_ADD_PARTICLESPAWNER; - event->add_particlespawner.p = new ParticleSpawnerParameters(p); + event->type = CE_ADD_PARTICLESPAWNER; + event->add_particlespawner.p = new ParticleSpawnerParameters(p); event->add_particlespawner.attached_id = attached_id; - event->add_particlespawner.id = server_id; + event->add_particlespawner.id = server_id; m_client_event_queue.push(event); } - -void Client::handleCommand_DeleteParticleSpawner(NetworkPacket* pkt) +void Client::handleCommand_DeleteParticleSpawner(NetworkPacket *pkt) { u32 server_id; *pkt >> server_id; @@ -1057,7 +1049,7 @@ void Client::handleCommand_DeleteParticleSpawner(NetworkPacket* pkt) m_client_event_queue.push(event); } -void Client::handleCommand_HudAdd(NetworkPacket* pkt) +void Client::handleCommand_HudAdd(NetworkPacket *pkt) { std::string datastring(pkt->getString(0), pkt->getSize()); std::istringstream is(datastring, std::ios_base::binary); @@ -1078,36 +1070,37 @@ void Client::handleCommand_HudAdd(NetworkPacket* pkt) s16 z_index = 0; std::string text2; - *pkt >> server_id >> type >> pos >> name >> scale >> text >> number >> item - >> dir >> align >> offset; + *pkt >> server_id >> type >> pos >> name >> scale >> text >> number >> item >> + dir >> align >> offset; try { *pkt >> world_pos; *pkt >> size; *pkt >> z_index; *pkt >> text2; - } catch(PacketError &e) {}; + } catch (PacketError &e) { + }; ClientEvent *event = new ClientEvent(); - event->type = CE_HUDADD; + event->type = CE_HUDADD; event->hudadd.server_id = server_id; - event->hudadd.type = type; - event->hudadd.pos = new v2f(pos); - event->hudadd.name = new std::string(name); - event->hudadd.scale = new v2f(scale); - event->hudadd.text = new std::string(text); - event->hudadd.number = number; - event->hudadd.item = item; - event->hudadd.dir = dir; - event->hudadd.align = new v2f(align); - event->hudadd.offset = new v2f(offset); + event->hudadd.type = type; + event->hudadd.pos = new v2f(pos); + event->hudadd.name = new std::string(name); + event->hudadd.scale = new v2f(scale); + event->hudadd.text = new std::string(text); + event->hudadd.number = number; + event->hudadd.item = item; + event->hudadd.dir = dir; + event->hudadd.align = new v2f(align); + event->hudadd.offset = new v2f(offset); event->hudadd.world_pos = new v3f(world_pos); - event->hudadd.size = new v2s32(size); - event->hudadd.z_index = z_index; - event->hudadd.text2 = new std::string(text2); + event->hudadd.size = new v2s32(size); + event->hudadd.z_index = z_index; + event->hudadd.text2 = new std::string(text2); m_client_event_queue.push(event); } -void Client::handleCommand_HudRemove(NetworkPacket* pkt) +void Client::handleCommand_HudRemove(NetworkPacket *pkt) { u32 server_id; @@ -1119,13 +1112,13 @@ void Client::handleCommand_HudRemove(NetworkPacket* pkt) m_hud_server_to_client.erase(i); ClientEvent *event = new ClientEvent(); - event->type = CE_HUDRM; + event->type = CE_HUDRM; event->hudrm.id = client_id; m_client_event_queue.push(event); } } -void Client::handleCommand_HudChange(NetworkPacket* pkt) +void Client::handleCommand_HudChange(NetworkPacket *pkt) { std::string sdata; v2f v2fdata; @@ -1137,34 +1130,35 @@ void Client::handleCommand_HudChange(NetworkPacket* pkt) *pkt >> server_id >> stat; - if (stat == HUD_STAT_POS || stat == HUD_STAT_SCALE || - stat == HUD_STAT_ALIGN || stat == HUD_STAT_OFFSET) + if (stat == HUD_STAT_POS || stat == HUD_STAT_SCALE || stat == HUD_STAT_ALIGN || + stat == HUD_STAT_OFFSET) *pkt >> v2fdata; else if (stat == HUD_STAT_NAME || stat == HUD_STAT_TEXT || stat == HUD_STAT_TEXT2) *pkt >> sdata; else if (stat == HUD_STAT_WORLD_POS) *pkt >> v3fdata; - else if (stat == HUD_STAT_SIZE ) + else if (stat == HUD_STAT_SIZE) *pkt >> v2s32data; else *pkt >> intdata; - std::unordered_map::const_iterator i = m_hud_server_to_client.find(server_id); + std::unordered_map::const_iterator i = + m_hud_server_to_client.find(server_id); if (i != m_hud_server_to_client.end()) { ClientEvent *event = new ClientEvent(); - event->type = CE_HUDCHANGE; - event->hudchange.id = i->second; - event->hudchange.stat = (HudElementStat)stat; + event->type = CE_HUDCHANGE; + event->hudchange.id = i->second; + event->hudchange.stat = (HudElementStat)stat; event->hudchange.v2fdata = new v2f(v2fdata); event->hudchange.v3fdata = new v3f(v3fdata); - event->hudchange.sdata = new std::string(sdata); - event->hudchange.data = intdata; + event->hudchange.sdata = new std::string(sdata); + event->hudchange.data = intdata; event->hudchange.v2s32data = new v2s32(v2s32data); m_client_event_queue.push(event); } } -void Client::handleCommand_HudSetFlags(NetworkPacket* pkt) +void Client::handleCommand_HudSetFlags(NetworkPacket *pkt) { u32 flags, mask; @@ -1174,20 +1168,22 @@ void Client::handleCommand_HudSetFlags(NetworkPacket* pkt) assert(player != NULL); bool was_minimap_visible = player->hud_flags & HUD_FLAG_MINIMAP_VISIBLE; - bool was_minimap_radar_visible = player->hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE; + bool was_minimap_radar_visible = + player->hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE; player->hud_flags &= ~mask; player->hud_flags |= flags; if (g_settings->getBool("hud_flags_bypass")) - player->hud_flags = HUD_FLAG_HOTBAR_VISIBLE | HUD_FLAG_HEALTHBAR_VISIBLE | - HUD_FLAG_CROSSHAIR_VISIBLE | HUD_FLAG_WIELDITEM_VISIBLE | - HUD_FLAG_BREATHBAR_VISIBLE | HUD_FLAG_MINIMAP_VISIBLE | - HUD_FLAG_MINIMAP_RADAR_VISIBLE; - + player->hud_flags = + HUD_FLAG_HOTBAR_VISIBLE | HUD_FLAG_HEALTHBAR_VISIBLE | + HUD_FLAG_CROSSHAIR_VISIBLE | HUD_FLAG_WIELDITEM_VISIBLE | + HUD_FLAG_BREATHBAR_VISIBLE | HUD_FLAG_MINIMAP_VISIBLE | + HUD_FLAG_MINIMAP_RADAR_VISIBLE; m_minimap_disabled_by_server = !(player->hud_flags & HUD_FLAG_MINIMAP_VISIBLE); - bool m_minimap_radar_disabled_by_server = !(player->hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE); + bool m_minimap_radar_disabled_by_server = + !(player->hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE); // Hide minimap if it has been disabled by the server if (m_minimap && m_minimap_disabled_by_server && was_minimap_visible) @@ -1198,13 +1194,12 @@ void Client::handleCommand_HudSetFlags(NetworkPacket* pkt) // Switch to surface mode if radar disabled by server if (m_minimap && m_minimap_radar_disabled_by_server && was_minimap_radar_visible) m_minimap->setMinimapMode(MINIMAP_MODE_SURFACEx1); - - } -void Client::handleCommand_HudSetParam(NetworkPacket* pkt) +void Client::handleCommand_HudSetParam(NetworkPacket *pkt) { - u16 param; std::string value; + u16 param; + std::string value; *pkt >> param >> value; @@ -1212,19 +1207,17 @@ void Client::handleCommand_HudSetParam(NetworkPacket* pkt) assert(player != NULL); if (param == HUD_PARAM_HOTBAR_ITEMCOUNT && value.size() == 4) { - s32 hotbar_itemcount = readS32((u8*) value.c_str()); + s32 hotbar_itemcount = readS32((u8 *)value.c_str()); if (hotbar_itemcount > 0 && hotbar_itemcount <= HUD_HOTBAR_ITEMCOUNT_MAX) player->hud_hotbar_itemcount = hotbar_itemcount; - } - else if (param == HUD_PARAM_HOTBAR_IMAGE) { + } else if (param == HUD_PARAM_HOTBAR_IMAGE) { player->hotbar_image = value; - } - else if (param == HUD_PARAM_HOTBAR_SELECTED_IMAGE) { + } else if (param == HUD_PARAM_HOTBAR_SELECTED_IMAGE) { player->hotbar_selected_image = value; } } -void Client::handleCommand_HudSetSky(NetworkPacket* pkt) +void Client::handleCommand_HudSetSky(NetworkPacket *pkt) { if (m_proto_ver < 39) { // Handle Protocol 38 and below servers with old set_sky, @@ -1243,7 +1236,8 @@ void Client::handleCommand_HudSetSky(NetworkPacket* pkt) skybox.clouds = true; try { skybox.clouds = readU8(is); - } catch (...) {} + } catch (...) { + } // Use default skybox settings: SkyboxDefaults sky_defaults; @@ -1257,8 +1251,7 @@ void Client::handleCommand_HudSetSky(NetworkPacket* pkt) skybox.fog_tint_type = "default"; skybox.fog_moon_tint = video::SColor(255, 255, 255, 255); skybox.fog_sun_tint = video::SColor(255, 255, 255, 255); - } - else { + } else { sun.visible = false; sun.sunrise_visible = false; moon.visible = false; @@ -1291,7 +1284,8 @@ void Client::handleCommand_HudSetSky(NetworkPacket* pkt) std::string texture; *pkt >> skybox.bgcolor >> skybox.type >> skybox.clouds >> - skybox.fog_sun_tint >> skybox.fog_moon_tint >> skybox.fog_tint_type; + skybox.fog_sun_tint >> skybox.fog_moon_tint >> + skybox.fog_tint_type; if (skybox.type == "skybox") { *pkt >> texture_count; @@ -1299,12 +1293,14 @@ void Client::handleCommand_HudSetSky(NetworkPacket* pkt) *pkt >> texture; skybox.textures.emplace_back(texture); } - } - else if (skybox.type == "regular") { - *pkt >> skybox.sky_color.day_sky >> skybox.sky_color.day_horizon - >> skybox.sky_color.dawn_sky >> skybox.sky_color.dawn_horizon - >> skybox.sky_color.night_sky >> skybox.sky_color.night_horizon - >> skybox.sky_color.indoors; + } else if (skybox.type == "regular") { + *pkt >> skybox.sky_color.day_sky >> + skybox.sky_color.day_horizon >> + skybox.sky_color.dawn_sky >> + skybox.sky_color.dawn_horizon >> + skybox.sky_color.night_sky >> + skybox.sky_color.night_horizon >> + skybox.sky_color.indoors; } ClientEvent *event = new ClientEvent(); @@ -1318,12 +1314,12 @@ void Client::handleCommand_HudSetSun(NetworkPacket *pkt) { SunParams sun; - *pkt >> sun.visible >> sun.texture>> sun.tonemap - >> sun.sunrise >> sun.sunrise_visible >> sun.scale; + *pkt >> sun.visible >> sun.texture >> sun.tonemap >> sun.sunrise >> + sun.sunrise_visible >> sun.scale; ClientEvent *event = new ClientEvent(); - event->type = CE_SET_SUN; - event->sun_params = new SunParams(sun); + event->type = CE_SET_SUN; + event->sun_params = new SunParams(sun); m_client_event_queue.push(event); } @@ -1331,11 +1327,10 @@ void Client::handleCommand_HudSetMoon(NetworkPacket *pkt) { MoonParams moon; - *pkt >> moon.visible >> moon.texture - >> moon.tonemap >> moon.scale; + *pkt >> moon.visible >> moon.texture >> moon.tonemap >> moon.scale; ClientEvent *event = new ClientEvent(); - event->type = CE_SET_MOON; + event->type = CE_SET_MOON; event->moon_params = new MoonParams(moon); m_client_event_queue.push(event); } @@ -1344,17 +1339,16 @@ void Client::handleCommand_HudSetStars(NetworkPacket *pkt) { StarParams stars; - *pkt >> stars.visible >> stars.count - >> stars.starcolor >> stars.scale; + *pkt >> stars.visible >> stars.count >> stars.starcolor >> stars.scale; ClientEvent *event = new ClientEvent(); - event->type = CE_SET_STARS; + event->type = CE_SET_STARS; event->star_params = new StarParams(stars); m_client_event_queue.push(event); } -void Client::handleCommand_CloudParams(NetworkPacket* pkt) +void Client::handleCommand_CloudParams(NetworkPacket *pkt) { f32 density; video::SColor color_bright; @@ -1363,26 +1357,25 @@ void Client::handleCommand_CloudParams(NetworkPacket* pkt) f32 thickness; v2f speed; - *pkt >> density >> color_bright >> color_ambient - >> height >> thickness >> speed; + *pkt >> density >> color_bright >> color_ambient >> height >> thickness >> speed; ClientEvent *event = new ClientEvent(); - event->type = CE_CLOUD_PARAMS; - event->cloud_params.density = density; + event->type = CE_CLOUD_PARAMS; + event->cloud_params.density = density; // use the underlying u32 representation, because we can't // use struct members with constructors here, and this way // we avoid using new() and delete() for no good reason - event->cloud_params.color_bright = color_bright.color; + event->cloud_params.color_bright = color_bright.color; event->cloud_params.color_ambient = color_ambient.color; - event->cloud_params.height = height; - event->cloud_params.thickness = thickness; + event->cloud_params.height = height; + event->cloud_params.thickness = thickness; // same here: deconstruct to skip constructor - event->cloud_params.speed_x = speed.X; - event->cloud_params.speed_y = speed.Y; + event->cloud_params.speed_x = speed.X; + event->cloud_params.speed_y = speed.Y; m_client_event_queue.push(event); } -void Client::handleCommand_OverrideDayNightRatio(NetworkPacket* pkt) +void Client::handleCommand_OverrideDayNightRatio(NetworkPacket *pkt) { bool do_override; u16 day_night_ratio_u; @@ -1392,13 +1385,13 @@ void Client::handleCommand_OverrideDayNightRatio(NetworkPacket* pkt) float day_night_ratio_f = (float)day_night_ratio_u / 65536; ClientEvent *event = new ClientEvent(); - event->type = CE_OVERRIDE_DAY_NIGHT_RATIO; + event->type = CE_OVERRIDE_DAY_NIGHT_RATIO; event->override_day_night_ratio.do_override = do_override; - event->override_day_night_ratio.ratio_f = day_night_ratio_f; + event->override_day_night_ratio.ratio_f = day_night_ratio_f; m_client_event_queue.push(event); } -void Client::handleCommand_LocalPlayerAnimations(NetworkPacket* pkt) +void Client::handleCommand_LocalPlayerAnimations(NetworkPacket *pkt) { LocalPlayer *player = m_env.getLocalPlayer(); assert(player != NULL); @@ -1410,7 +1403,7 @@ void Client::handleCommand_LocalPlayerAnimations(NetworkPacket* pkt) *pkt >> player->local_animation_speed; } -void Client::handleCommand_EyeOffset(NetworkPacket* pkt) +void Client::handleCommand_EyeOffset(NetworkPacket *pkt) { LocalPlayer *player = m_env.getLocalPlayer(); assert(player != NULL); @@ -1418,12 +1411,12 @@ void Client::handleCommand_EyeOffset(NetworkPacket* pkt) *pkt >> player->eye_offset_first >> player->eye_offset_third; } -void Client::handleCommand_UpdatePlayerList(NetworkPacket* pkt) +void Client::handleCommand_UpdatePlayerList(NetworkPacket *pkt) { u8 type; u16 num_players; *pkt >> type >> num_players; - PlayerListModifer notice_type = (PlayerListModifer) type; + PlayerListModifer notice_type = (PlayerListModifer)type; for (u16 i = 0; i < num_players; i++) { std::string name; @@ -1440,30 +1433,30 @@ void Client::handleCommand_UpdatePlayerList(NetworkPacket* pkt) } } -void Client::handleCommand_SrpBytesSandB(NetworkPacket* pkt) +void Client::handleCommand_SrpBytesSandB(NetworkPacket *pkt) { if (m_chosen_auth_mech != AUTH_MECHANISM_SRP && m_chosen_auth_mech != AUTH_MECHANISM_LEGACY_PASSWORD) { errorstream << "Client: Received SRP S_B login message," - << " but wasn't supposed to (chosen_mech=" - << m_chosen_auth_mech << ")." << std::endl; + << " but wasn't supposed to (chosen_mech=" + << m_chosen_auth_mech << ")." << std::endl; return; } char *bytes_M = 0; size_t len_M = 0; - SRPUser *usr = (SRPUser *) m_auth_data; + SRPUser *usr = (SRPUser *)m_auth_data; std::string s; std::string B; *pkt >> s >> B; infostream << "Client: Received TOCLIENT_SRP_BYTES_S_B." << std::endl; - srp_user_process_challenge(usr, (const unsigned char *) s.c_str(), s.size(), - (const unsigned char *) B.c_str(), B.size(), - (unsigned char **) &bytes_M, &len_M); + srp_user_process_challenge(usr, (const unsigned char *)s.c_str(), s.size(), + (const unsigned char *)B.c_str(), B.size(), + (unsigned char **)&bytes_M, &len_M); - if ( !bytes_M ) { + if (!bytes_M) { errorstream << "Client: SRP-6a S_B safety check violation!" << std::endl; return; } @@ -1518,8 +1511,8 @@ void Client::handleCommand_MediaPush(NetworkPacket *pkt) } verbosestream << "Server pushes media file \"" << filename << "\" with " - << filedata.size() << " bytes of data (cached=" << cached - << ")" << std::endl; + << filedata.size() << " bytes of data (cached=" << cached << ")" + << std::endl; if (m_media_pushed_files.count(filename) != 0) { // Silently ignore for synchronization purposes @@ -1532,7 +1525,7 @@ void Client::handleCommand_MediaPush(NetworkPacket *pkt) SHA1 ctx; ctx.addBytes(filedata.c_str(), filedata.size()); unsigned char *buf = ctx.getDigest(); - computed_hash.assign((char*) buf, 20); + computed_hash.assign((char *)buf, 20); free(buf); } if (raw_hash != computed_hash) { @@ -1559,12 +1552,12 @@ void Client::handleCommand_ModChannelMsg(NetworkPacket *pkt) *pkt >> channel_name >> sender >> channel_msg; verbosestream << "Mod channel message received from server " << pkt->getPeerId() - << " on channel " << channel_name << ". sender: `" << sender << "`, message: " - << channel_msg << std::endl; + << " on channel " << channel_name << ". sender: `" << sender + << "`, message: " << channel_msg << std::endl; if (!m_modchannel_mgr->channelRegistered(channel_name)) { verbosestream << "Server sent us messages on unregistered channel " - << channel_name << ", ignoring." << std::endl; + << channel_name << ", ignoring." << std::endl; return; } @@ -1584,56 +1577,57 @@ void Client::handleCommand_ModChannelSignal(NetworkPacket *pkt) bool valid_signal = true; // @TODO: send Signal to Lua API switch (signal) { - case MODCHANNEL_SIGNAL_JOIN_OK: - m_modchannel_mgr->setChannelState(channel, MODCHANNEL_STATE_READ_WRITE); - infostream << "Server ack our mod channel join on channel `" << channel - << "`, joining." << std::endl; - break; - case MODCHANNEL_SIGNAL_JOIN_FAILURE: - // Unable to join, remove channel - m_modchannel_mgr->leaveChannel(channel, 0); - infostream << "Server refused our mod channel join on channel `" << channel - << "`" << std::endl; - break; - case MODCHANNEL_SIGNAL_LEAVE_OK: + case MODCHANNEL_SIGNAL_JOIN_OK: + m_modchannel_mgr->setChannelState(channel, MODCHANNEL_STATE_READ_WRITE); + infostream << "Server ack our mod channel join on channel `" << channel + << "`, joining." << std::endl; + break; + case MODCHANNEL_SIGNAL_JOIN_FAILURE: + // Unable to join, remove channel + m_modchannel_mgr->leaveChannel(channel, 0); + infostream << "Server refused our mod channel join on channel `" + << channel << "`" << std::endl; + break; + case MODCHANNEL_SIGNAL_LEAVE_OK: #ifndef NDEBUG - infostream << "Server ack our mod channel leave on channel " << channel - << "`, leaving." << std::endl; + infostream << "Server ack our mod channel leave on channel " << channel + << "`, leaving." << std::endl; #endif - break; - case MODCHANNEL_SIGNAL_LEAVE_FAILURE: - infostream << "Server refused our mod channel leave on channel `" << channel - << "`" << std::endl; - break; - case MODCHANNEL_SIGNAL_CHANNEL_NOT_REGISTERED: + break; + case MODCHANNEL_SIGNAL_LEAVE_FAILURE: + infostream << "Server refused our mod channel leave on channel `" + << channel << "`" << std::endl; + break; + case MODCHANNEL_SIGNAL_CHANNEL_NOT_REGISTERED: #ifndef NDEBUG - // Generally unused, but ensure we don't do an implementation error - infostream << "Server tells us we sent a message on channel `" << channel - << "` but we are not registered. Message was dropped." << std::endl; + // Generally unused, but ensure we don't do an implementation error + infostream << "Server tells us we sent a message on channel `" << channel + << "` but we are not registered. Message was dropped." + << std::endl; #endif - break; - case MODCHANNEL_SIGNAL_SET_STATE: { - u8 state; - *pkt >> state; + break; + case MODCHANNEL_SIGNAL_SET_STATE: { + u8 state; + *pkt >> state; - if (state == MODCHANNEL_STATE_INIT || state >= MODCHANNEL_STATE_MAX) { - infostream << "Received wrong channel state " << state - << ", ignoring." << std::endl; - return; - } - - m_modchannel_mgr->setChannelState(channel, (ModChannelState) state); - infostream << "Server sets mod channel `" << channel - << "` in read-only mode." << std::endl; - break; + if (state == MODCHANNEL_STATE_INIT || state >= MODCHANNEL_STATE_MAX) { + infostream << "Received wrong channel state " << state + << ", ignoring." << std::endl; + return; } - default: + + m_modchannel_mgr->setChannelState(channel, (ModChannelState)state); + infostream << "Server sets mod channel `" << channel + << "` in read-only mode." << std::endl; + break; + } + default: #ifndef NDEBUG - warningstream << "Received unhandled mod channel signal ID " - << signal << ", ignoring." << std::endl; + warningstream << "Received unhandled mod channel signal ID " << signal + << ", ignoring." << std::endl; #endif - valid_signal = false; - break; + valid_signal = false; + break; } // If signal is valid, forward it to client side mods diff --git a/src/network/connection.cpp b/src/network/connection.cpp index 3692e45a9..dbcece99d 100644 --- a/src/network/connection.cpp +++ b/src/network/connection.cpp @@ -41,29 +41,29 @@ namespace con /* defines used for debugging and profiling */ /******************************************************************************/ #ifdef NDEBUG - #define LOG(a) a - #define PROFILE(a) +#define LOG(a) a +#define PROFILE(a) #else - #if 0 +#if 0 /* this mutex is used to achieve log message consistency */ std::mutex log_message_mutex; - #define LOG(a) \ - { \ - MutexAutoLock loglock(log_message_mutex); \ - a; \ - } - #else - // Prevent deadlocks until a solution is found after 5.2.0 (TODO) - #define LOG(a) a - #endif +#define LOG(a) \ + { \ + MutexAutoLock loglock(log_message_mutex); \ + a; \ + } +#else +// Prevent deadlocks until a solution is found after 5.2.0 (TODO) +#define LOG(a) a +#endif - #define PROFILE(a) a +#define PROFILE(a) a #endif #define PING_TIMEOUT 5.0 -BufferedPacket makePacket(Address &address, const SharedBuffer &data, - u32 protocol_id, session_t sender_peer_id, u8 channel) +BufferedPacket makePacket(Address &address, const SharedBuffer &data, u32 protocol_id, + session_t sender_peer_id, u8 channel) { u32 packet_size = data.getSize() + BASE_HEADER_SIZE; BufferedPacket p(packet_size); @@ -123,8 +123,7 @@ void makeSplitPacket(const SharedBuffer &data, u32 chunksize_max, u16 seqnum start = end + 1; chunk_num++; - } - while (end != data.getSize() - 1); + } while (end != data.getSize() - 1); for (SharedBuffer &chunk : *chunks) { // Write chunk_count @@ -167,11 +166,11 @@ SharedBuffer makeReliablePacket(const SharedBuffer &data, u16 seqnum) void ReliablePacketBuffer::print() { MutexAutoLock listlock(m_list_mutex); - LOG(dout_con<<"Dump of ReliablePacketBuffer:" << std::endl); + LOG(dout_con << "Dump of ReliablePacketBuffer:" << std::endl); unsigned int index = 0; for (BufferedPacket &bufferedPacket : m_list) { - u16 s = readU16(&(bufferedPacket.data[BASE_HEADER_SIZE+1])); - LOG(dout_con<::iterator i = m_list.begin(); - for(; i != m_list.end(); ++i) - { - u16 s = readU16(&(i->data[BASE_HEADER_SIZE+1])); + for (; i != m_list.end(); ++i) { + u16 s = readU16(&(i->data[BASE_HEADER_SIZE + 1])); if (s == seqnum) break; } @@ -205,7 +203,7 @@ RPBSearchResult ReliablePacketBuffer::notFound() return m_list.end(); } -bool ReliablePacketBuffer::getFirstSeqnum(u16& result) +bool ReliablePacketBuffer::getFirstSeqnum(u16 &result) { MutexAutoLock listlock(m_list_mutex); if (m_list.empty()) @@ -237,17 +235,16 @@ BufferedPacket ReliablePacketBuffer::popSeqnum(u16 seqnum) MutexAutoLock listlock(m_list_mutex); RPBSearchResult r = findPacket(seqnum); if (r == notFound()) { - LOG(dout_con<<"Sequence number: " << seqnum - << " not found in reliable buffer"<data[BASE_HEADER_SIZE+1])); + u16 s = readU16(&(next->data[BASE_HEADER_SIZE + 1])); m_oldest_non_answered_ack = s; } @@ -267,25 +264,27 @@ void ReliablePacketBuffer::insert(BufferedPacket &p, u16 next_expected) MutexAutoLock listlock(m_list_mutex); if (p.data.getSize() < BASE_HEADER_SIZE + 3) { errorstream << "ReliablePacketBuffer::insert(): Invalid data size for " - "reliable packet" << std::endl; + "reliable packet" + << std::endl; return; } u8 type = readU8(&p.data[BASE_HEADER_SIZE + 0]); if (type != PACKET_TYPE_RELIABLE) { errorstream << "ReliablePacketBuffer::insert(): type is not reliable" - << std::endl; + << std::endl; return; } u16 seqnum = readU16(&p.data[BASE_HEADER_SIZE + 1]); if (!seqnum_in_window(seqnum, next_expected, MAX_RELIABLE_WINDOW_SIZE)) { errorstream << "ReliablePacketBuffer::insert(): seqnum is outside of " - "expected window " << std::endl; + "expected window " + << std::endl; return; } if (seqnum == next_expected) { errorstream << "ReliablePacketBuffer::insert(): seqnum is next expected" - << std::endl; + << std::endl; return; } @@ -293,8 +292,7 @@ void ReliablePacketBuffer::insert(BufferedPacket &p, u16 next_expected) // Find the right place for the packet and insert it there // If list is empty, just add it - if (m_list.empty()) - { + if (m_list.empty()) { m_list.push_back(p); m_oldest_non_answered_ack = seqnum; // Done. @@ -304,47 +302,47 @@ void ReliablePacketBuffer::insert(BufferedPacket &p, u16 next_expected) // Otherwise find the right place std::list::iterator i = m_list.begin(); // Find the first packet in the list which has a higher seqnum - u16 s = readU16(&(i->data[BASE_HEADER_SIZE+1])); + u16 s = readU16(&(i->data[BASE_HEADER_SIZE + 1])); /* case seqnum is smaller then next_expected seqnum */ /* this is true e.g. on wrap around */ if (seqnum < next_expected) { - while(((s < seqnum) || (s >= next_expected)) && (i != m_list.end())) { + while (((s < seqnum) || (s >= next_expected)) && (i != m_list.end())) { ++i; if (i != m_list.end()) - s = readU16(&(i->data[BASE_HEADER_SIZE+1])); + s = readU16(&(i->data[BASE_HEADER_SIZE + 1])); } } /* non wrap around case (at least for incoming and next_expected */ - else - { - while(((s < seqnum) && (s >= next_expected)) && (i != m_list.end())) { + else { + while (((s < seqnum) && (s >= next_expected)) && (i != m_list.end())) { ++i; if (i != m_list.end()) - s = readU16(&(i->data[BASE_HEADER_SIZE+1])); + s = readU16(&(i->data[BASE_HEADER_SIZE + 1])); } } if (s == seqnum) { /* nothing to do this seems to be a resent packet */ /* for paranoia reason data should be compared */ - if ( - (readU16(&(i->data[BASE_HEADER_SIZE+1])) != seqnum) || - (i->data.getSize() != p.data.getSize()) || - (i->address != p.address) - ) - { + if ((readU16(&(i->data[BASE_HEADER_SIZE + 1])) != seqnum) || + (i->data.getSize() != p.data.getSize()) || + (i->address != p.address)) { /* if this happens your maximum transfer window may be to big */ fprintf(stderr, - "Duplicated seqnum %d non matching packet detected:\n", + "Duplicated seqnum %d non matching packet " + "detected:\n", seqnum); fprintf(stderr, "Old: seqnum: %05d size: %04d, address: %s\n", - readU16(&(i->data[BASE_HEADER_SIZE+1])),i->data.getSize(), + readU16(&(i->data[BASE_HEADER_SIZE + 1])), + i->data.getSize(), i->address.serializeString().c_str()); fprintf(stderr, "New: seqnum: %05d size: %04u, address: %s\n", - readU16(&(p.data[BASE_HEADER_SIZE+1])),p.data.getSize(), + readU16(&(p.data[BASE_HEADER_SIZE + 1])), + p.data.getSize(), p.address.serializeString().c_str()); - throw IncomingDataCorruption("duplicated packet isn't same as original one"); + throw IncomingDataCorruption( + "duplicated packet isn't same as original one"); } } /* insert or push back */ @@ -355,7 +353,8 @@ void ReliablePacketBuffer::insert(BufferedPacket &p, u16 next_expected) } /* update last packet number */ - m_oldest_non_answered_ack = readU16(&(*m_list.begin()).data[BASE_HEADER_SIZE+1]); + m_oldest_non_answered_ack = + readU16(&(*m_list.begin()).data[BASE_HEADER_SIZE + 1]); } void ReliablePacketBuffer::incrementTimeouts(float dtime) @@ -367,8 +366,8 @@ void ReliablePacketBuffer::incrementTimeouts(float dtime) } } -std::list ReliablePacketBuffer::getTimedOuts(float timeout, - unsigned int max_packets) +std::list ReliablePacketBuffer::getTimedOuts( + float timeout, unsigned int max_packets) { MutexAutoLock listlock(m_list_mutex); std::list timed_outs; @@ -376,7 +375,7 @@ std::list ReliablePacketBuffer::getTimedOuts(float timeout, if (bufferedPacket.time >= timeout) { timed_outs.push_back(bufferedPacket); - //this packet will be sent right afterwards reset timeout here + // this packet will be sent right afterwards reset timeout here bufferedPacket.time = 0.0f; if (timed_outs.size() >= max_packets) break; @@ -447,19 +446,19 @@ SharedBuffer IncomingSplitBuffer::insert(const BufferedPacket &p, bool relia errorstream << "Invalid data size for split packet" << std::endl; return SharedBuffer(); } - u8 type = readU8(&p.data[BASE_HEADER_SIZE+0]); - u16 seqnum = readU16(&p.data[BASE_HEADER_SIZE+1]); - u16 chunk_count = readU16(&p.data[BASE_HEADER_SIZE+3]); - u16 chunk_num = readU16(&p.data[BASE_HEADER_SIZE+5]); + u8 type = readU8(&p.data[BASE_HEADER_SIZE + 0]); + u16 seqnum = readU16(&p.data[BASE_HEADER_SIZE + 1]); + u16 chunk_count = readU16(&p.data[BASE_HEADER_SIZE + 3]); + u16 chunk_num = readU16(&p.data[BASE_HEADER_SIZE + 5]); if (type != PACKET_TYPE_SPLIT) { errorstream << "IncomingSplitBuffer::insert(): type is not split" - << std::endl; + << std::endl; return SharedBuffer(); } if (chunk_num >= chunk_count) { errorstream << "IncomingSplitBuffer::insert(): chunk_num=" << chunk_num - << " >= chunk_count=" << chunk_count << std::endl; + << " >= chunk_count=" << chunk_count << std::endl; return SharedBuffer(); } @@ -474,14 +473,13 @@ SharedBuffer IncomingSplitBuffer::insert(const BufferedPacket &p, bool relia if (chunk_count != sp->chunk_count) { errorstream << "IncomingSplitBuffer::insert(): chunk_count=" - << chunk_count << " != sp->chunk_count=" << sp->chunk_count - << std::endl; + << chunk_count << " != sp->chunk_count=" << sp->chunk_count + << std::endl; return SharedBuffer(); } if (reliable != sp->reliable) - LOG(derr_con<<"Connection: WARNING: reliable="<reliable="<reliable - <reliable=" << sp->reliable << std::endl); // Cut chunk data out of packet u32 chunkdatasize = p.data.getSize() - headersize; @@ -521,7 +519,8 @@ void IncomingSplitBuffer::removeUnreliableTimedOuts(float dtime, float timeout) } for (u16 j : remove_queue) { MutexAutoLock listlock(m_map_mutex); - LOG(dout_con<<"NOTE: Removing timed out unreliable split packet"< window_size) { + // know about difference of two unsigned may be negative in + // general but we already made sure it won't happen in this case + if (((u16)(next_outgoing_seqnum - lowest_unacked_seqnumber)) > + window_size) { successful = false; return 0; } - } - else { + } else { // ugly cast but this one is required in order to tell compiler we - // know about difference of two unsigned may be negative in general - // but we already made sure it won't happen in this case - if ((next_outgoing_seqnum + (u16)(SEQNUM_MAX - lowest_unacked_seqnumber)) > - window_size) { + // know about difference of two unsigned may be negative in + // general but we already made sure it won't happen in this case + if ((next_outgoing_seqnum + + (u16)(SEQNUM_MAX - + lowest_unacked_seqnumber)) > + window_size) { successful = false; return 0; } @@ -618,7 +617,7 @@ u16 Channel::readOutgoingSequenceNumber() bool Channel::putBackSequenceNumber(u16 seqnum) { - if (((seqnum + 1) % (SEQNUM_MAX+1)) == next_outgoing_seqnum) { + if (((seqnum + 1) % (SEQNUM_MAX + 1)) == next_outgoing_seqnum) { next_outgoing_seqnum = seqnum; return true; @@ -633,7 +632,8 @@ void Channel::UpdateBytesSent(unsigned int bytes, unsigned int packets) current_packet_successful += packets; } -void Channel::UpdateBytesReceived(unsigned int bytes) { +void Channel::UpdateBytesReceived(unsigned int bytes) +{ MutexAutoLock internal(m_internal_mutex); current_bytes_received += bytes; } @@ -644,7 +644,6 @@ void Channel::UpdateBytesLost(unsigned int bytes) current_bytes_lost += bytes; } - void Channel::UpdatePacketLossCounter(unsigned int count) { MutexAutoLock internal(m_internal_mutex); @@ -665,19 +664,21 @@ void Channel::UpdateTimers(float dtime) if (packet_loss_counter > 1.0f) { packet_loss_counter -= 1.0f; - unsigned int packet_loss = 11; /* use a neutral value for initialization */ + unsigned int packet_loss = + 11; /* use a neutral value for initialization */ unsigned int packets_successful = 0; - //unsigned int packet_too_late = 0; + // unsigned int packet_too_late = 0; bool reasonable_amount_of_data_transmitted = false; { MutexAutoLock internal(m_internal_mutex); packet_loss = current_packet_loss; - //packet_too_late = current_packet_too_late; + // packet_too_late = current_packet_too_late; packets_successful = current_packet_successful; - if (current_bytes_transfered > (unsigned int) (window_size*512/2)) { + if (current_bytes_transfered > + (unsigned int)(window_size * 512 / 2)) { reasonable_amount_of_data_transmitted = true; } current_packet_loss = 0; @@ -690,38 +691,33 @@ void Channel::UpdateTimers(float dtime) bool done = false; if (packets_successful > 0) { - successful_to_lost_ratio = packet_loss/packets_successful; + successful_to_lost_ratio = packet_loss / packets_successful; } else if (packet_loss > 0) { window_size = std::max( - (window_size - 10), - MIN_RELIABLE_WINDOW_SIZE); + (window_size - 10), MIN_RELIABLE_WINDOW_SIZE); done = true; } if (!done) { if ((successful_to_lost_ratio < 0.01f) && - (window_size < MAX_RELIABLE_WINDOW_SIZE)) { + (window_size < MAX_RELIABLE_WINDOW_SIZE)) { /* don't even think about increasing if we didn't even * use major parts of our window */ if (reasonable_amount_of_data_transmitted) - window_size = std::min( - (window_size + 100), + window_size = std::min((window_size + 100), MAX_RELIABLE_WINDOW_SIZE); } else if ((successful_to_lost_ratio < 0.05f) && (window_size < MAX_RELIABLE_WINDOW_SIZE)) { /* don't even think about increasing if we didn't even * use major parts of our window */ if (reasonable_amount_of_data_transmitted) - window_size = std::min( - (window_size + 50), + window_size = std::min((window_size + 50), MAX_RELIABLE_WINDOW_SIZE); } else if (successful_to_lost_ratio > 0.15f) { - window_size = std::max( - (window_size - 100), + window_size = std::max((window_size - 100), MIN_RELIABLE_WINDOW_SIZE); } else if (successful_to_lost_ratio > 0.1f) { - window_size = std::max( - (window_size - 50), + window_size = std::max((window_size - 50), MIN_RELIABLE_WINDOW_SIZE); } } @@ -730,16 +726,17 @@ void Channel::UpdateTimers(float dtime) if (bpm_counter > 10.0f) { { MutexAutoLock internal(m_internal_mutex); - cur_kbps = - (((float) current_bytes_transfered)/bpm_counter)/1024.0f; + cur_kbps = (((float)current_bytes_transfered) / bpm_counter) / + 1024.0f; current_bytes_transfered = 0; - cur_kbps_lost = - (((float) current_bytes_lost)/bpm_counter)/1024.0f; - current_bytes_lost = 0; - cur_incoming_kbps = - (((float) current_bytes_received)/bpm_counter)/1024.0f; - current_bytes_received = 0; - bpm_counter = 0.0f; + cur_kbps_lost = (((float)current_bytes_lost) / bpm_counter) / + 1024.0f; + current_bytes_lost = 0; + cur_incoming_kbps = + (((float)current_bytes_received) / bpm_counter) / + 1024.0f; + current_bytes_received = 0; + bpm_counter = 0.0f; } if (cur_kbps > max_kbps) { @@ -754,24 +751,21 @@ void Channel::UpdateTimers(float dtime) max_incoming_kbps = cur_incoming_kbps; } - rate_samples = MYMIN(rate_samples+1,10); - float old_fraction = ((float) (rate_samples-1) )/( (float) rate_samples); - avg_kbps = avg_kbps * old_fraction + - cur_kbps * (1.0 - old_fraction); - avg_kbps_lost = avg_kbps_lost * old_fraction + + rate_samples = MYMIN(rate_samples + 1, 10); + float old_fraction = ((float)(rate_samples - 1)) / ((float)rate_samples); + avg_kbps = avg_kbps * old_fraction + cur_kbps * (1.0 - old_fraction); + avg_kbps_lost = avg_kbps_lost * old_fraction + cur_kbps_lost * (1.0 - old_fraction); - avg_incoming_kbps = avg_incoming_kbps * old_fraction + - cur_incoming_kbps * (1.0 - old_fraction); + avg_incoming_kbps = avg_incoming_kbps * old_fraction + + cur_incoming_kbps * (1.0 - old_fraction); } } - /* Peer */ -PeerHelper::PeerHelper(Peer* peer) : - m_peer(peer) +PeerHelper::PeerHelper(Peer *peer) : m_peer(peer) { if (peer && !peer->IncUseCount()) m_peer = nullptr; @@ -785,7 +779,7 @@ PeerHelper::~PeerHelper() m_peer = nullptr; } -PeerHelper& PeerHelper::operator=(Peer* peer) +PeerHelper &PeerHelper::operator=(Peer *peer) { m_peer = peer; if (peer && !peer->IncUseCount()) @@ -793,24 +787,24 @@ PeerHelper& PeerHelper::operator=(Peer* peer) return *this; } -Peer* PeerHelper::operator->() const +Peer *PeerHelper::operator->() const { return m_peer; } -Peer* PeerHelper::operator&() const +Peer *PeerHelper::operator&() const { return m_peer; } bool PeerHelper::operator!() { - return ! m_peer; + return !m_peer; } -bool PeerHelper::operator!=(void* ptr) +bool PeerHelper::operator!=(void *ptr) { - return ((void*) m_peer != ptr); + return ((void *)m_peer != ptr); } bool Peer::IncUseCount() @@ -838,8 +832,9 @@ void Peer::DecUseCount() delete this; } -void Peer::RTTStatistics(float rtt, const std::string &profiler_id, - unsigned int num_samples) { +void Peer::RTTStatistics( + float rtt, const std::string &profiler_id, unsigned int num_samples) +{ if (m_last_rtt > 0) { /* set min max values */ @@ -850,18 +845,18 @@ void Peer::RTTStatistics(float rtt, const std::string &profiler_id, /* do average calculation */ if (m_rtt.avg_rtt < 0.0) - m_rtt.avg_rtt = rtt; + m_rtt.avg_rtt = rtt; else - m_rtt.avg_rtt = m_rtt.avg_rtt * (num_samples/(num_samples-1)) + - rtt * (1/num_samples); + m_rtt.avg_rtt = m_rtt.avg_rtt * (num_samples / (num_samples - 1)) + + rtt * (1 / num_samples); /* do jitter calculation */ - //just use some neutral value at beginning + // just use some neutral value at beginning float jitter = m_rtt.jitter_min; if (rtt > m_last_rtt) - jitter = rtt-m_last_rtt; + jitter = rtt - m_last_rtt; if (rtt <= m_last_rtt) jitter = m_last_rtt - rtt; @@ -872,14 +867,17 @@ void Peer::RTTStatistics(float rtt, const std::string &profiler_id, m_rtt.jitter_max = jitter; if (m_rtt.jitter_avg < 0.0) - m_rtt.jitter_avg = jitter; + m_rtt.jitter_avg = jitter; else - m_rtt.jitter_avg = m_rtt.jitter_avg * (num_samples/(num_samples-1)) + - jitter * (1/num_samples); + m_rtt.jitter_avg = + m_rtt.jitter_avg * + (num_samples / (num_samples - 1)) + + jitter * (1 / num_samples); if (!profiler_id.empty()) { g_profiler->graphAdd(profiler_id + " RTT [ms]", rtt * 1000.f); - g_profiler->graphAdd(profiler_id + " jitter [ms]", jitter * 1000.f); + g_profiler->graphAdd( + profiler_id + " jitter [ms]", jitter * 1000.f); } } /* save values required for next loop */ @@ -891,7 +889,7 @@ bool Peer::isTimedOut(float timeout) MutexAutoLock lock(m_exclusive_access_mutex); u64 current_time = porting::getTimeMs(); - float dtime = CALC_DTIME(m_last_timeout_check,current_time); + float dtime = CALC_DTIME(m_last_timeout_check, current_time); m_last_timeout_check = current_time; m_timeout_counter += dtime; @@ -909,28 +907,28 @@ void Peer::Drop() } PROFILE(std::stringstream peerIdentifier1); - PROFILE(peerIdentifier1 << "runTimeouts[" << m_connection->getDesc() - << ";" << id << ";RELIABLE]"); + PROFILE(peerIdentifier1 << "runTimeouts[" << m_connection->getDesc() << ";" << id + << ";RELIABLE]"); PROFILE(g_profiler->remove(peerIdentifier1.str())); PROFILE(std::stringstream peerIdentifier2); - PROFILE(peerIdentifier2 << "sendPackets[" << m_connection->getDesc() - << ";" << id << ";RELIABLE]"); + PROFILE(peerIdentifier2 << "sendPackets[" << m_connection->getDesc() << ";" << id + << ";RELIABLE]"); PROFILE(ScopeProfiler peerprofiler(g_profiler, peerIdentifier2.str(), SPT_AVG)); delete this; } -UDPPeer::UDPPeer(u16 a_id, Address a_address, Connection* connection) : - Peer(a_address,a_id,connection) +UDPPeer::UDPPeer(u16 a_id, Address a_address, Connection *connection) : + Peer(a_address, a_id, connection) { for (Channel &channel : channels) channel.setWindowSize(START_RELIABLE_WINDOW_SIZE); } -bool UDPPeer::getAddress(MTProtocols type,Address& toset) +bool UDPPeer::getAddress(MTProtocols type, Address &toset) { - if ((type == MTP_UDP) || (type == MTP_MINETEST_RELIABLE_UDP) || (type == MTP_PRIMARY)) - { + if ((type == MTP_UDP) || (type == MTP_MINETEST_RELIABLE_UDP) || + (type == MTP_PRIMARY)) { toset = address; return true; } @@ -943,7 +941,7 @@ void UDPPeer::reportRTT(float rtt) if (rtt < 0.0) { return; } - RTTStatistics(rtt,"rudp",MAX_RELIABLE_WINDOW_SIZE*10); + RTTStatistics(rtt, "rudp", MAX_RELIABLE_WINDOW_SIZE * 10); float timeout = getStat(AVG_RTT) * RESEND_TIMEOUT_FACTOR; if (timeout < RESEND_TIMEOUT_MIN) @@ -955,11 +953,10 @@ void UDPPeer::reportRTT(float rtt) resend_timeout = timeout; } -bool UDPPeer::Ping(float dtime,SharedBuffer& data) +bool UDPPeer::Ping(float dtime, SharedBuffer &data) { m_ping_timer += dtime; - if (m_ping_timer >= PING_TIMEOUT) - { + if (m_ping_timer >= PING_TIMEOUT) { // Create and send PING packet writeU8(&data[0], PACKET_TYPE_CONTROL); writeU8(&data[1], CONTROLTYPE_PING); @@ -969,8 +966,7 @@ bool UDPPeer::Ping(float dtime,SharedBuffer& data) return false; } -void UDPPeer::PutReliableSendCommand(ConnectionCommand &c, - unsigned int max_packet_size) +void UDPPeer::PutReliableSendCommand(ConnectionCommand &c, unsigned int max_packet_size) { if (m_pending_disconnect) return; @@ -980,41 +976,37 @@ void UDPPeer::PutReliableSendCommand(ConnectionCommand &c, if (chan.queued_commands.empty() && /* don't queue more packets then window size */ (chan.queued_reliables.size() < chan.getWindowSize() / 2)) { - LOG(dout_con<getDesc() - <<" processing reliable command for peer id: " << c.peer_id - <<" data size: " << c.data.getSize() << std::endl); - if (!processReliableSendCommand(c,max_packet_size)) { + LOG(dout_con << m_connection->getDesc() + << " processing reliable command for peer id: " << c.peer_id + << " data size: " << c.data.getSize() << std::endl); + if (!processReliableSendCommand(c, max_packet_size)) { chan.queued_commands.push_back(c); } - } - else { - LOG(dout_con<getDesc() - <<" Queueing reliable command for peer id: " << c.peer_id - <<" data size: " << c.data.getSize() <getDesc() + << " Queueing reliable command for peer id: " << c.peer_id + << " data size: " << c.data.getSize() << std::endl); chan.queued_commands.push_back(c); if (chan.queued_commands.size() >= chan.getWindowSize() / 2) { LOG(derr_con << m_connection->getDesc() - << "Possible packet stall to peer id: " << c.peer_id - << " queued_commands=" << chan.queued_commands.size() - << std::endl); + << "Possible packet stall to peer id: " << c.peer_id + << " queued_commands=" << chan.queued_commands.size() + << std::endl); } } } bool UDPPeer::processReliableSendCommand( - ConnectionCommand &c, - unsigned int max_packet_size) + ConnectionCommand &c, unsigned int max_packet_size) { if (m_pending_disconnect) return true; Channel &chan = channels[c.channelnum]; - u32 chunksize_max = max_packet_size - - BASE_HEADER_SIZE - - RELIABLE_HEADER_SIZE; + u32 chunksize_max = max_packet_size - BASE_HEADER_SIZE - RELIABLE_HEADER_SIZE; - sanity_check(c.data.getSize() < MAX_RELIABLE_WINDOW_SIZE*512); + sanity_check(c.data.getSize() < MAX_RELIABLE_WINDOW_SIZE * 512); std::list> originals; u16 split_sequence_number = chan.readNextSplitSeqNum(); @@ -1022,7 +1014,8 @@ bool UDPPeer::processReliableSendCommand( if (c.raw) { originals.emplace_back(c.data); } else { - makeAutoSplitPacket(c.data, chunksize_max,split_sequence_number, &originals); + makeAutoSplitPacket( + c.data, chunksize_max, split_sequence_number, &originals); chan.setNextSplitSeqNum(split_sequence_number); } @@ -1038,8 +1031,7 @@ bool UDPPeer::processReliableSendCommand( if (!have_sequence_number) break; - if (!have_initial_sequence_number) - { + if (!have_initial_sequence_number) { initial_sequence_number = seqnum; have_initial_sequence_number = true; } @@ -1059,11 +1051,14 @@ bool UDPPeer::processReliableSendCommand( while (!toadd.empty()) { BufferedPacket p = toadd.front(); toadd.pop(); -// LOG(dout_con<getDesc() -// << " queuing reliable packet for peer_id: " << c.peer_id -// << " channel: " << (c.channelnum&0xFF) -// << " seqnum: " << readU16(&p.data[BASE_HEADER_SIZE+1]) -// << std::endl) + // LOG(dout_con<getDesc() + // << " queuing reliable packet for + //peer_id: " << c.peer_id + // << " channel: " << + //(c.channelnum&0xFF) + // << " seqnum: " << + //readU16(&p.data[BASE_HEADER_SIZE+1]) + // << std::endl) chan.queued_reliables.push(p); pcount++; } @@ -1081,9 +1076,9 @@ bool UDPPeer::processReliableSendCommand( /* remove packet */ toadd.pop(); - bool successfully_put_back_sequence_number - = chan.putBackSequenceNumber( - (initial_sequence_number+toadd.size() % (SEQNUM_MAX+1))); + bool successfully_put_back_sequence_number = chan.putBackSequenceNumber( + (initial_sequence_number + + toadd.size() % (SEQNUM_MAX + 1))); FATAL_ERROR_IF(!successfully_put_back_sequence_number, "error"); } @@ -1092,24 +1087,20 @@ bool UDPPeer::processReliableSendCommand( // 'log_message_mutex' and 'm_list_mutex'. u32 n_queued = chan.outgoing_reliables_sent.size(); - LOG(dout_con<getDesc() - << " Windowsize exceeded on reliable sending " - << c.data.getSize() << " bytes" - << std::endl << "\t\tinitial_sequence_number: " - << initial_sequence_number - << std::endl << "\t\tgot at most : " - << packets_available << " packets" - << std::endl << "\t\tpackets queued : " - << n_queued - << std::endl); + LOG(dout_con << m_connection->getDesc() + << " Windowsize exceeded on reliable sending " << c.data.getSize() + << " bytes" << std::endl + << "\t\tinitial_sequence_number: " << initial_sequence_number + << std::endl + << "\t\tgot at most : " << packets_available << " packets" + << std::endl + << "\t\tpackets queued : " << n_queued << std::endl); return false; } -void UDPPeer::RunCommandQueues( - unsigned int max_packet_size, - unsigned int maxcommands, - unsigned int maxtransfer) +void UDPPeer::RunCommandQueues(unsigned int max_packet_size, unsigned int maxcommands, + unsigned int maxtransfer) { for (Channel &channel : channels) { @@ -1122,19 +1113,22 @@ void UDPPeer::RunCommandQueues( ConnectionCommand c = channel.queued_commands.front(); LOG(dout_con << m_connection->getDesc() - << " processing queued reliable command " << std::endl); + << " processing queued reliable command " + << std::endl); // Packet is processed, remove it from queue - if (processReliableSendCommand(c,max_packet_size)) { + if (processReliableSendCommand(c, max_packet_size)) { channel.queued_commands.pop_front(); } else { LOG(dout_con << m_connection->getDesc() - << " Failed to queue packets for peer_id: " << c.peer_id - << ", delaying sending of " << c.data.getSize() - << " bytes" << std::endl); + << " Failed to queue packets for " + "peer_id: " + << c.peer_id + << ", delaying sending of " + << c.data.getSize() << " bytes" + << std::endl); } - } - catch (ItemNotFoundException &e) { + } catch (ItemNotFoundException &e) { // intentionally empty } } @@ -1153,8 +1147,8 @@ void UDPPeer::setNextSplitSequenceNumber(u8 channel, u16 seqnum) channels[channel].setNextSplitSeqNum(seqnum); } -SharedBuffer UDPPeer::addSplitPacket(u8 channel, const BufferedPacket &toadd, - bool reliable) +SharedBuffer UDPPeer::addSplitPacket( + u8 channel, const BufferedPacket &toadd, bool reliable) { assert(channel < CHANNEL_COUNT); // Pre-condition return channels[channel].incoming_splits.insert(toadd, reliable); @@ -1164,13 +1158,13 @@ SharedBuffer UDPPeer::addSplitPacket(u8 channel, const BufferedPacket &toadd Connection */ -Connection::Connection(u32 protocol_id, u32 max_packet_size, float timeout, - bool ipv6, PeerHandler *peerhandler) : - m_udpSocket(ipv6), - m_protocol_id(protocol_id), - m_sendThread(new ConnectionSendThread(max_packet_size, timeout)), - m_receiveThread(new ConnectionReceiveThread(max_packet_size)), - m_bc_peerhandler(peerhandler) +Connection::Connection(u32 protocol_id, u32 max_packet_size, float timeout, bool ipv6, + PeerHandler *peerhandler) : + m_udpSocket(ipv6), + m_protocol_id(protocol_id), + m_sendThread(new ConnectionSendThread(max_packet_size, timeout)), + m_receiveThread(new ConnectionReceiveThread(max_packet_size)), + m_bc_peerhandler(peerhandler) { /* Amount of time Receive() will wait for data, this is entirely different @@ -1184,7 +1178,6 @@ Connection::Connection(u32 protocol_id, u32 max_packet_size, float timeout, m_receiveThread->start(); } - Connection::~Connection() { m_shutting_down = true; @@ -1192,7 +1185,7 @@ Connection::~Connection() m_sendThread->stop(); m_receiveThread->stop(); - //TODO for some unkonwn reason send/receive threads do not exit as they're + // TODO for some unkonwn reason send/receive threads do not exit as they're // supposed to be but wait on peer timeout. To speed up shutdown we reduce // timeout to half a second. m_sendThread->setPeerTimeout(0.5); @@ -1235,20 +1228,20 @@ PeerHelper Connection::getPeerNoEx(session_t peer_id) } /* find peer_id for address */ -u16 Connection::lookupPeer(Address& sender) +u16 Connection::lookupPeer(Address &sender) { MutexAutoLock peerlock(m_peers_mutex); - std::map::iterator j; + std::map::iterator j; j = m_peers.begin(); - for(; j != m_peers.end(); ++j) - { + for (; j != m_peers.end(); ++j) { Peer *peer = j->second; if (peer->isPendingDeletion()) continue; Address tocheck; - if ((peer->getAddress(MTP_MINETEST_RELIABLE_UDP, tocheck)) && (tocheck == sender)) + if ((peer->getAddress(MTP_MINETEST_RELIABLE_UDP, tocheck)) && + (tocheck == sender)) return peer->id; if ((peer->getAddress(MTP_UDP, tocheck)) && (tocheck == sender)) @@ -1273,14 +1266,13 @@ bool Connection::deletePeer(session_t peer_id, bool timeout) } Address peer_address; - //any peer has a primary address this never fails! + // any peer has a primary address this never fails! peer->getAddress(MTP_PRIMARY, peer_address); // Create event ConnectionEvent e; e.peerRemoved(peer_id, timeout, peer_address); putEvent(e); - peer->Drop(); return true; } @@ -1291,7 +1283,7 @@ ConnectionEvent Connection::waitEvent(u32 timeout_ms) { try { return m_event_queue.pop_front(timeout_ms); - } catch(ItemNotFoundException &ex) { + } catch (ItemNotFoundException &ex) { ConnectionEvent e; e.type = CONNEVENT_NONE; return e; @@ -1351,12 +1343,12 @@ bool Connection::Receive(NetworkPacket *pkt, u32 timeout) events keep happening before the timeout expires. This is not considered to be a problem (is it?) */ - for(;;) { + for (;;) { ConnectionEvent e = waitEvent(timeout); if (e.type != CONNEVENT_NONE) LOG(dout_con << getDesc() << ": Receive: got event: " - << e.describe() << std::endl); - switch(e.type) { + << e.describe() << std::endl); + switch (e.type) { case CONNEVENT_NONE: return false; case CONNEVENT_DATA_RECEIVED: @@ -1381,7 +1373,7 @@ bool Connection::Receive(NetworkPacket *pkt, u32 timeout) } case CONNEVENT_BIND_FAILED: throw ConnectionBindFailed("Failed to bind socket " - "(port already in use?)"); + "(port already in use?)"); } } return false; @@ -1399,8 +1391,7 @@ bool Connection::TryReceive(NetworkPacket *pkt) return Receive(pkt, 0); } -void Connection::Send(session_t peer_id, u8 channelnum, - NetworkPacket *pkt, bool reliable) +void Connection::Send(session_t peer_id, u8 channelnum, NetworkPacket *pkt, bool reliable) { assert(channelnum < CHANNEL_COUNT); // Pre-condition @@ -1424,7 +1415,8 @@ Address Connection::GetPeerAddress(session_t peer_id) float Connection::getPeerStat(session_t peer_id, rtt_stat_type type) { PeerHelper peer = getPeerNoEx(peer_id); - if (!peer) return -1; + if (!peer) + return -1; return peer->getStat(type); } @@ -1432,30 +1424,31 @@ float Connection::getLocalStat(rate_stat_type type) { PeerHelper peer = getPeerNoEx(PEER_ID_SERVER); - FATAL_ERROR_IF(!peer, "Connection::getLocalStat we couldn't get our own peer? are you serious???"); + FATAL_ERROR_IF(!peer, "Connection::getLocalStat we couldn't get our own peer? " + "are you serious???"); float retval = 0.0; for (Channel &channel : dynamic_cast(&peer)->channels) { - switch(type) { - case CUR_DL_RATE: - retval += channel.getCurrentDownloadRateKB(); - break; - case AVG_DL_RATE: - retval += channel.getAvgDownloadRateKB(); - break; - case CUR_INC_RATE: - retval += channel.getCurrentIncomingRateKB(); - break; - case AVG_INC_RATE: - retval += channel.getAvgIncomingRateKB(); - break; - case AVG_LOSS_RATE: - retval += channel.getAvgLossRateKB(); - break; - case CUR_LOSS_RATE: - retval += channel.getCurrentLossRateKB(); - break; + switch (type) { + case CUR_DL_RATE: + retval += channel.getCurrentDownloadRateKB(); + break; + case AVG_DL_RATE: + retval += channel.getAvgDownloadRateKB(); + break; + case CUR_INC_RATE: + retval += channel.getCurrentIncomingRateKB(); + break; + case AVG_INC_RATE: + retval += channel.getAvgIncomingRateKB(); + break; + case AVG_LOSS_RATE: + retval += channel.getAvgLossRateKB(); + break; + case CUR_LOSS_RATE: + retval += channel.getCurrentLossRateKB(); + break; default: FATAL_ERROR("Connection::getLocalStat Invalid stat type"); } @@ -1463,20 +1456,20 @@ float Connection::getLocalStat(rate_stat_type type) return retval; } -u16 Connection::createPeer(Address& sender, MTProtocols protocol, int fd) +u16 Connection::createPeer(Address &sender, MTProtocols protocol, int fd) { // Somebody wants to make a new connection // Get a unique peer id (2 or higher) session_t peer_id_new = m_next_remote_peer_id; - u16 overflow = MAX_UDP_PEERS; + u16 overflow = MAX_UDP_PEERS; /* Find an unused peer id */ MutexAutoLock lock(m_peers_mutex); bool out_of_ids = false; - for(;;) { + for (;;) { // Check if exists if (m_peers.find(peer_id_new) == m_peers.end()) @@ -1501,17 +1494,17 @@ u16 Connection::createPeer(Address& sender, MTProtocols protocol, int fd) m_peers[peer->id] = peer; m_peer_ids.push_back(peer->id); - m_next_remote_peer_id = (peer_id_new +1 ) % MAX_UDP_PEERS; + m_next_remote_peer_id = (peer_id_new + 1) % MAX_UDP_PEERS; - LOG(dout_con << getDesc() - << "createPeer(): giving peer_id=" << peer_id_new << std::endl); + LOG(dout_con << getDesc() << "createPeer(): giving peer_id=" << peer_id_new + << std::endl); ConnectionCommand cmd; SharedBuffer reply(4); writeU8(&reply[0], PACKET_TYPE_CONTROL); writeU8(&reply[1], CONTROLTYPE_SET_PEER_ID); writeU16(&reply[2], peer_id_new); - cmd.createPeer(peer_id_new,reply); + cmd.createPeer(peer_id_new, reply); putCommand(cmd); // Create peer addition event @@ -1526,14 +1519,14 @@ u16 Connection::createPeer(Address& sender, MTProtocols protocol, int fd) void Connection::PrintInfo(std::ostream &out) { m_info_mutex.lock(); - out< ack(4); @@ -1563,10 +1555,9 @@ void Connection::sendAck(session_t peer_id, u8 channelnum, u16 seqnum) m_sendThread->Trigger(); } -UDPPeer* Connection::createServerPeer(Address& address) +UDPPeer *Connection::createServerPeer(Address &address) { - if (getPeerNoEx(PEER_ID_SERVER) != 0) - { + if (getPeerNoEx(PEER_ID_SERVER) != 0) { throw ConnectionException("Already connected to a server"); } diff --git a/src/network/connection.h b/src/network/connection.h index 47b0805ce..9febfb2b7 100644 --- a/src/network/connection.h +++ b/src/network/connection.h @@ -41,7 +41,8 @@ namespace con class ConnectionReceiveThread; class ConnectionSendThread; -typedef enum MTProtocols { +typedef enum MTProtocols +{ MTP_PRIMARY, MTP_UDP, MTP_MINETEST_RELIABLE_UDP @@ -53,49 +54,43 @@ typedef enum MTProtocols { inline bool seqnum_higher(u16 totest, u16 base) { - if (totest > base) - { - if ((totest - base) > (SEQNUM_MAX/2)) + if (totest > base) { + if ((totest - base) > (SEQNUM_MAX / 2)) return false; return true; } - if ((base - totest) > (SEQNUM_MAX/2)) + if ((base - totest) > (SEQNUM_MAX / 2)) return true; return false; } -inline bool seqnum_in_window(u16 seqnum, u16 next,u16 window_size) +inline bool seqnum_in_window(u16 seqnum, u16 next, u16 window_size) { u16 window_start = next; - u16 window_end = ( next + window_size ) % (SEQNUM_MAX+1); + u16 window_end = (next + window_size) % (SEQNUM_MAX + 1); if (window_start < window_end) { return ((seqnum >= window_start) && (seqnum < window_end)); } - return ((seqnum < window_end) || (seqnum >= window_start)); } static inline float CALC_DTIME(u64 lasttime, u64 curtime) { - float value = ( curtime - lasttime) / 1000.0; - return MYMAX(MYMIN(value,0.1),0.0); + float value = (curtime - lasttime) / 1000.0; + return MYMAX(MYMIN(value, 0.1), 0.0); } struct BufferedPacket { - BufferedPacket(u8 *a_data, u32 a_size): - data(a_data, a_size) - {} - BufferedPacket(u32 a_size): - data(a_size) - {} - Buffer data; // Data of the packet, including headers - float time = 0.0f; // Seconds from buffering the packet or re-sending + BufferedPacket(u8 *a_data, u32 a_size) : data(a_data, a_size) {} + BufferedPacket(u32 a_size) : data(a_size) {} + Buffer data; // Data of the packet, including headers + float time = 0.0f; // Seconds from buffering the packet or re-sending float totaltime = 0.0f; // Seconds from buffering the packet u64 absolute_send_time = -1; Address address; // Sender or destination @@ -103,8 +98,8 @@ struct BufferedPacket }; // This adds the base headers to the data and makes a packet out of it -BufferedPacket makePacket(Address &address, const SharedBuffer &data, - u32 protocol_id, session_t sender_peer_id, u8 channel); +BufferedPacket makePacket(Address &address, const SharedBuffer &data, u32 protocol_id, + session_t sender_peer_id, u8 channel); // Depending on size, make a TYPE_ORIGINAL or TYPE_SPLIT packet // Increments split_seqnum if a split packet is made @@ -116,8 +111,7 @@ SharedBuffer makeReliablePacket(const SharedBuffer &data, u16 seqnum); struct IncomingSplitPacket { - IncomingSplitPacket(u32 cc, bool r): - chunk_count(cc), reliable(r) {} + IncomingSplitPacket(u32 cc, bool r) : chunk_count(cc), reliable(r) {} IncomingSplitPacket() = delete; @@ -125,10 +119,7 @@ struct IncomingSplitPacket u32 chunk_count; bool reliable; // If true, isn't deleted on timeout - bool allReceived() const - { - return (chunks.size() == chunk_count); - } + bool allReceived() const { return (chunks.size() == chunk_count); } bool insert(u32 chunk_num, SharedBuffer &chunkdata); SharedBuffer reassemble(); @@ -219,7 +210,8 @@ with a buffer in the receiving and transmitting end. #define RELIABLE_HEADER_SIZE 3 #define SEQNUM_INITIAL 65500 -enum PacketType: u8 { +enum PacketType : u8 +{ PACKET_TYPE_CONTROL = 0, PACKET_TYPE_ORIGINAL = 1, PACKET_TYPE_SPLIT = 2, @@ -238,22 +230,20 @@ class ReliablePacketBuffer public: ReliablePacketBuffer() = default; - bool getFirstSeqnum(u16& result); + bool getFirstSeqnum(u16 &result); BufferedPacket popFirst(); BufferedPacket popSeqnum(u16 seqnum); void insert(BufferedPacket &p, u16 next_expected); void incrementTimeouts(float dtime); - std::list getTimedOuts(float timeout, - unsigned int max_packets); + std::list getTimedOuts(float timeout, unsigned int max_packets); void print(); bool empty(); RPBSearchResult notFound(); u32 size(); - private: RPBSearchResult findPacket(u16 seqnum); // does not perform locking @@ -282,7 +272,7 @@ public: private: // Key is seqnum - std::map m_buf; + std::map m_buf; std::mutex m_map_mutex; }; @@ -296,17 +286,16 @@ struct OutgoingPacket bool ack; OutgoingPacket(session_t peer_id_, u8 channelnum_, const SharedBuffer &data_, - bool reliable_,bool ack_=false): - peer_id(peer_id_), - channelnum(channelnum_), - data(data_), - reliable(reliable_), - ack(ack_) + bool reliable_, bool ack_ = false) : + peer_id(peer_id_), + channelnum(channelnum_), data(data_), reliable(reliable_), + ack(ack_) { } }; -enum ConnectionCommandType{ +enum ConnectionCommandType +{ CONNCMD_NONE, CONNCMD_SERVE, CONNCMD_CONNECT, @@ -352,10 +341,7 @@ struct ConnectionCommand type = CONNCMD_CONNECT; address = address_; } - void disconnect() - { - type = CONNCMD_DISCONNECT; - } + void disconnect() { type = CONNCMD_DISCONNECT; } void disconnect_peer(session_t peer_id_) { type = CONNCMD_DISCONNECT_PEER; @@ -401,7 +387,7 @@ public: u16 readNextIncomingSeqNum(); u16 incNextIncomingSeqNum(); - u16 getOutgoingSequenceNumber(bool& successfull); + u16 getOutgoingSequenceNumber(bool &successfull); u16 readOutgoingSequenceNumber(); bool putBackSequenceNumber(u16); @@ -415,10 +401,10 @@ public: // re-send them if no ACK is received ReliablePacketBuffer outgoing_reliables_sent; - //queued reliable packets + // queued reliable packets std::queue queued_reliables; - //queue commands prior splitting to packets + // queue commands prior splitting to packets std::deque queued_commands; IncomingSplitBuffer incoming_splits; @@ -428,37 +414,65 @@ public: void UpdatePacketLossCounter(unsigned int count); void UpdatePacketTooLateCounter(); - void UpdateBytesSent(unsigned int bytes,unsigned int packages=1); + void UpdateBytesSent(unsigned int bytes, unsigned int packages = 1); void UpdateBytesLost(unsigned int bytes); void UpdateBytesReceived(unsigned int bytes); void UpdateTimers(float dtime); const float getCurrentDownloadRateKB() - { MutexAutoLock lock(m_internal_mutex); return cur_kbps; }; + { + MutexAutoLock lock(m_internal_mutex); + return cur_kbps; + }; const float getMaxDownloadRateKB() - { MutexAutoLock lock(m_internal_mutex); return max_kbps; }; + { + MutexAutoLock lock(m_internal_mutex); + return max_kbps; + }; const float getCurrentLossRateKB() - { MutexAutoLock lock(m_internal_mutex); return cur_kbps_lost; }; + { + MutexAutoLock lock(m_internal_mutex); + return cur_kbps_lost; + }; const float getMaxLossRateKB() - { MutexAutoLock lock(m_internal_mutex); return max_kbps_lost; }; + { + MutexAutoLock lock(m_internal_mutex); + return max_kbps_lost; + }; const float getCurrentIncomingRateKB() - { MutexAutoLock lock(m_internal_mutex); return cur_incoming_kbps; }; + { + MutexAutoLock lock(m_internal_mutex); + return cur_incoming_kbps; + }; const float getMaxIncomingRateKB() - { MutexAutoLock lock(m_internal_mutex); return max_incoming_kbps; }; + { + MutexAutoLock lock(m_internal_mutex); + return max_incoming_kbps; + }; const float getAvgDownloadRateKB() - { MutexAutoLock lock(m_internal_mutex); return avg_kbps; }; + { + MutexAutoLock lock(m_internal_mutex); + return avg_kbps; + }; const float getAvgLossRateKB() - { MutexAutoLock lock(m_internal_mutex); return avg_kbps_lost; }; + { + MutexAutoLock lock(m_internal_mutex); + return avg_kbps_lost; + }; const float getAvgIncomingRateKB() - { MutexAutoLock lock(m_internal_mutex); return avg_incoming_kbps; }; + { + MutexAutoLock lock(m_internal_mutex); + return avg_incoming_kbps; + }; const unsigned int getWindowSize() const { return window_size; }; void setWindowSize(unsigned int size) { window_size = size; }; + private: std::mutex m_internal_mutex; int window_size = MIN_RELIABLE_WINDOW_SIZE; @@ -496,14 +510,14 @@ class PeerHelper { public: PeerHelper() = default; - PeerHelper(Peer* peer); + PeerHelper(Peer *peer); ~PeerHelper(); - PeerHelper& operator=(Peer* peer); - Peer* operator->() const; - bool operator!(); - Peer* operator&() const; - bool operator!=(void* ptr); + PeerHelper &operator=(Peer *peer); + Peer *operator->() const; + bool operator!(); + Peer *operator&() const; + bool operator!=(void *ptr); private: Peer *m_peer = nullptr; @@ -511,7 +525,8 @@ private: class Connection; -typedef enum { +typedef enum +{ CUR_DL_RATE, AVG_DL_RATE, CUR_INC_RATE, @@ -520,140 +535,144 @@ typedef enum { AVG_LOSS_RATE, } rate_stat_type; -class Peer { - public: - friend class PeerHelper; +class Peer +{ +public: + friend class PeerHelper; - Peer(Address address_,u16 id_,Connection* connection) : - id(id_), - m_connection(connection), - address(address_), - m_last_timeout_check(porting::getTimeMs()) - { - }; + Peer(Address address_, u16 id_, Connection *connection) : + id(id_), m_connection(connection), address(address_), + m_last_timeout_check(porting::getTimeMs()){}; - virtual ~Peer() { - MutexAutoLock usage_lock(m_exclusive_access_mutex); - FATAL_ERROR_IF(m_usage != 0, "Reference counting failure"); - }; + virtual ~Peer() + { + MutexAutoLock usage_lock(m_exclusive_access_mutex); + FATAL_ERROR_IF(m_usage != 0, "Reference counting failure"); + }; - // Unique id of the peer - u16 id; + // Unique id of the peer + u16 id; - void Drop(); + void Drop(); - virtual void PutReliableSendCommand(ConnectionCommand &c, - unsigned int max_packet_size) {}; + virtual void PutReliableSendCommand( + ConnectionCommand &c, unsigned int max_packet_size){}; - virtual bool getAddress(MTProtocols type, Address& toset) = 0; + virtual bool getAddress(MTProtocols type, Address &toset) = 0; - bool isPendingDeletion() - { MutexAutoLock lock(m_exclusive_access_mutex); return m_pending_deletion; }; + bool isPendingDeletion() + { + MutexAutoLock lock(m_exclusive_access_mutex); + return m_pending_deletion; + }; - void ResetTimeout() - {MutexAutoLock lock(m_exclusive_access_mutex); m_timeout_counter = 0.0; }; + void ResetTimeout() + { + MutexAutoLock lock(m_exclusive_access_mutex); + m_timeout_counter = 0.0; + }; - bool isTimedOut(float timeout); + bool isTimedOut(float timeout); - unsigned int m_increment_packets_remaining = 0; + unsigned int m_increment_packets_remaining = 0; - virtual u16 getNextSplitSequenceNumber(u8 channel) { return 0; }; - virtual void setNextSplitSequenceNumber(u8 channel, u16 seqnum) {}; - virtual SharedBuffer addSplitPacket(u8 channel, const BufferedPacket &toadd, - bool reliable) - { - errorstream << "Peer::addSplitPacket called," - << " this is supposed to be never called!" << std::endl; - return SharedBuffer(0); - }; + virtual u16 getNextSplitSequenceNumber(u8 channel) { return 0; }; + virtual void setNextSplitSequenceNumber(u8 channel, u16 seqnum){}; + virtual SharedBuffer addSplitPacket( + u8 channel, const BufferedPacket &toadd, bool reliable) + { + errorstream << "Peer::addSplitPacket called," + << " this is supposed to be never called!" << std::endl; + return SharedBuffer(0); + }; - virtual bool Ping(float dtime, SharedBuffer& data) { return false; }; + virtual bool Ping(float dtime, SharedBuffer &data) { return false; }; - virtual float getStat(rtt_stat_type type) const { - switch (type) { - case MIN_RTT: - return m_rtt.min_rtt; - case MAX_RTT: - return m_rtt.max_rtt; - case AVG_RTT: - return m_rtt.avg_rtt; - case MIN_JITTER: - return m_rtt.jitter_min; - case MAX_JITTER: - return m_rtt.jitter_max; - case AVG_JITTER: - return m_rtt.jitter_avg; - } - return -1; + virtual float getStat(rtt_stat_type type) const + { + switch (type) { + case MIN_RTT: + return m_rtt.min_rtt; + case MAX_RTT: + return m_rtt.max_rtt; + case AVG_RTT: + return m_rtt.avg_rtt; + case MIN_JITTER: + return m_rtt.jitter_min; + case MAX_JITTER: + return m_rtt.jitter_max; + case AVG_JITTER: + return m_rtt.jitter_avg; } - protected: - virtual void reportRTT(float rtt) {}; + return -1; + } - void RTTStatistics(float rtt, - const std::string &profiler_id = "", - unsigned int num_samples = 1000); +protected: + virtual void reportRTT(float rtt){}; - bool IncUseCount(); - void DecUseCount(); + void RTTStatistics(float rtt, const std::string &profiler_id = "", + unsigned int num_samples = 1000); - std::mutex m_exclusive_access_mutex; + bool IncUseCount(); + void DecUseCount(); - bool m_pending_deletion = false; + std::mutex m_exclusive_access_mutex; - Connection* m_connection; + bool m_pending_deletion = false; - // Address of the peer - Address address; + Connection *m_connection; - // Ping timer - float m_ping_timer = 0.0f; - private: + // Address of the peer + Address address; - struct rttstats { - float jitter_min = FLT_MAX; - float jitter_max = 0.0f; - float jitter_avg = -1.0f; - float min_rtt = FLT_MAX; - float max_rtt = 0.0f; - float avg_rtt = -1.0f; + // Ping timer + float m_ping_timer = 0.0f; - rttstats() = default; - }; +private: + struct rttstats + { + float jitter_min = FLT_MAX; + float jitter_max = 0.0f; + float jitter_avg = -1.0f; + float min_rtt = FLT_MAX; + float max_rtt = 0.0f; + float avg_rtt = -1.0f; - rttstats m_rtt; - float m_last_rtt = -1.0f; + rttstats() = default; + }; - // current usage count - unsigned int m_usage = 0; + rttstats m_rtt; + float m_last_rtt = -1.0f; - // Seconds from last receive - float m_timeout_counter = 0.0f; + // current usage count + unsigned int m_usage = 0; - u64 m_last_timeout_check; + // Seconds from last receive + float m_timeout_counter = 0.0f; + + u64 m_last_timeout_check; }; class UDPPeer : public Peer { public: - friend class PeerHelper; friend class ConnectionReceiveThread; friend class ConnectionSendThread; friend class Connection; - UDPPeer(u16 a_id, Address a_address, Connection* connection); + UDPPeer(u16 a_id, Address a_address, Connection *connection); virtual ~UDPPeer() = default; - void PutReliableSendCommand(ConnectionCommand &c, - unsigned int max_packet_size); + void PutReliableSendCommand(ConnectionCommand &c, unsigned int max_packet_size); - bool getAddress(MTProtocols type, Address& toset); + bool getAddress(MTProtocols type, Address &toset); u16 getNextSplitSequenceNumber(u8 channel); void setNextSplitSequenceNumber(u8 channel, u16 seqnum); - SharedBuffer addSplitPacket(u8 channel, const BufferedPacket &toadd, - bool reliable); + SharedBuffer addSplitPacket( + u8 channel, const BufferedPacket &toadd, bool reliable); protected: /* @@ -662,34 +681,39 @@ protected: */ void reportRTT(float rtt); - void RunCommandQueues( - unsigned int max_packet_size, - unsigned int maxcommands, - unsigned int maxtransfer); + void RunCommandQueues(unsigned int max_packet_size, unsigned int maxcommands, + unsigned int maxtransfer); float getResendTimeout() - { MutexAutoLock lock(m_exclusive_access_mutex); return resend_timeout; } + { + MutexAutoLock lock(m_exclusive_access_mutex); + return resend_timeout; + } void setResendTimeout(float timeout) - { MutexAutoLock lock(m_exclusive_access_mutex); resend_timeout = timeout; } - bool Ping(float dtime,SharedBuffer& data); + { + MutexAutoLock lock(m_exclusive_access_mutex); + resend_timeout = timeout; + } + bool Ping(float dtime, SharedBuffer &data); Channel channels[CHANNEL_COUNT]; bool m_pending_disconnect = false; + private: // This is changed dynamically float resend_timeout = 0.5; bool processReliableSendCommand( - ConnectionCommand &c, - unsigned int max_packet_size); + ConnectionCommand &c, unsigned int max_packet_size); }; /* Connection */ -enum ConnectionEventType{ +enum ConnectionEventType +{ CONNEVENT_NONE, CONNEVENT_DATA_RECEIVED, CONNEVENT_PEER_ADDED, @@ -709,7 +733,7 @@ struct ConnectionEvent std::string describe() { - switch(type) { + switch (type) { case CONNEVENT_NONE: return "CONNEVENT_NONE"; case CONNEVENT_DATA_RECEIVED: @@ -743,10 +767,7 @@ struct ConnectionEvent timeout = timeout_; address = address_; } - void bindFailed() - { - type = CONNEVENT_BIND_FAILED; - } + void bindFailed() { type = CONNEVENT_BIND_FAILED; } }; class PeerHandler; @@ -770,7 +791,7 @@ public: void Connect(Address address); bool Connected(); void Disconnect(); - void Receive(NetworkPacket* pkt); + void Receive(NetworkPacket *pkt); bool TryReceive(NetworkPacket *pkt); void Send(session_t peer_id, u8 channelnum, NetworkPacket *pkt, bool reliable); session_t GetPeerID() const { return m_peer_id; } @@ -783,10 +804,10 @@ public: protected: PeerHelper getPeerNoEx(session_t peer_id); - u16 lookupPeer(Address& sender); + u16 lookupPeer(Address &sender); - u16 createPeer(Address& sender, MTProtocols protocol, int fd); - UDPPeer* createServerPeer(Address& sender); + u16 createPeer(Address &sender, MTProtocols protocol, int fd); + UDPPeer *createServerPeer(Address &sender); bool deletePeer(session_t peer_id, bool timeout); void SetPeerID(session_t id) { m_peer_id = id; } @@ -809,6 +830,7 @@ protected: void putEvent(ConnectionEvent &e); void TriggerSend(); + private: MutexedQueue m_event_queue; diff --git a/src/network/connectionthreads.cpp b/src/network/connectionthreads.cpp index 9a6617a1c..4d47b776c 100644 --- a/src/network/connectionthreads.cpp +++ b/src/network/connectionthreads.cpp @@ -38,10 +38,10 @@ namespace con #else /* this mutex is used to achieve log message consistency */ std::mutex log_conthread_mutex; -#define LOG(a) \ - { \ - MutexAutoLock loglock(log_conthread_mutex); \ - a; \ +#define LOG(a) \ + { \ + MutexAutoLock loglock(log_conthread_mutex); \ + a; \ } #define PROFILE(a) a //#define DEBUG_CONNECTION_KBPS @@ -66,12 +66,10 @@ static u8 readChannel(u8 *packetdata) /* Connection Threads */ /******************************************************************************/ -ConnectionSendThread::ConnectionSendThread(unsigned int max_packet_size, - float timeout) : - Thread("ConnectionSend"), - m_max_packet_size(max_packet_size), - m_timeout(timeout), - m_max_data_packets_per_iteration(g_settings->getU16("max_packets_per_iteration")) +ConnectionSendThread::ConnectionSendThread(unsigned int max_packet_size, float timeout) : + Thread("ConnectionSend"), m_max_packet_size(max_packet_size), + m_timeout(timeout), m_max_data_packets_per_iteration(g_settings->getU16( + "max_packets_per_iteration")) { SANITY_CHECK(m_max_data_packets_per_iteration > 1); } @@ -80,14 +78,15 @@ void *ConnectionSendThread::run() { assert(m_connection); - LOG(dout_con << m_connection->getDesc() - << "ConnectionSend thread started" << std::endl); + LOG(dout_con << m_connection->getDesc() << "ConnectionSend thread started" + << std::endl); u64 curtime = porting::getTimeMs(); u64 lasttime = curtime; PROFILE(std::stringstream ThreadIdentifier); - PROFILE(ThreadIdentifier << "ConnectionSend: [" << m_connection->getDesc() << "]"); + PROFILE(ThreadIdentifier << "ConnectionSend: [" << m_connection->getDesc() + << "]"); /* if stop is requested don't stop immediately but try to send all */ /* packets first */ @@ -112,8 +111,10 @@ void *ConnectionSendThread::run() runTimeouts(dtime); if (m_iteration_packets_avaialble == 0) { LOG(warningstream << m_connection->getDesc() - << " Packet quota used up after re-sending packets, " - << "max=" << m_max_data_packets_per_iteration << std::endl); + << " Packet quota used up after re-sending " + "packets, " + << "max=" << m_max_data_packets_per_iteration + << std::endl); } /* translate commands to packets */ @@ -165,7 +166,6 @@ bool ConnectionSendThread::packetsQueued() } } - return false; } @@ -185,21 +185,20 @@ void ConnectionSendThread::runTimeouts(float dtime) continue; PROFILE(std::stringstream peerIdentifier); - PROFILE(peerIdentifier << "runTimeouts[" << m_connection->getDesc() - << ";" << peerId << ";RELIABLE]"); - PROFILE(ScopeProfiler - peerprofiler(g_profiler, peerIdentifier.str(), SPT_AVG)); + PROFILE(peerIdentifier << "runTimeouts[" << m_connection->getDesc() << ";" + << peerId << ";RELIABLE]"); + PROFILE(ScopeProfiler peerprofiler( + g_profiler, peerIdentifier.str(), SPT_AVG)); - SharedBuffer data(2); // data for sending ping, required here because of goto + SharedBuffer data(2); // data for sending ping, required here because + // of goto /* Check peer timeout */ if (peer->isTimedOut(m_timeout)) { - infostream << m_connection->getDesc() - << "RunTimeouts(): Peer " << peer->id - << " has timed out." - << std::endl; + infostream << m_connection->getDesc() << "RunTimeouts(): Peer " + << peer->id << " has timed out." << std::endl; // Add peer to the list timeouted_peers.push_back(peer->id); // Don't bother going through the buffers of this one @@ -212,7 +211,8 @@ void ConnectionSendThread::runTimeouts(float dtime) std::list timed_outs; // Remove timed out incomplete unreliable split packets - channel.incoming_splits.removeUnreliableTimedOuts(dtime, m_timeout); + channel.incoming_splits.removeUnreliableTimedOuts( + dtime, m_timeout); // Increment reliable packet times channel.outgoing_reliables_sent.incrementTimeouts(dtime); @@ -223,8 +223,9 @@ void ConnectionSendThread::runTimeouts(float dtime) return; // Re-send timed out outgoing reliables - timed_outs = channel.outgoing_reliables_sent.getTimedOuts(resend_timeout, - (m_max_data_packets_per_iteration / numpeers)); + timed_outs = channel.outgoing_reliables_sent.getTimedOuts( + resend_timeout, + (m_max_data_packets_per_iteration / numpeers)); channel.UpdatePacketLossCounter(timed_outs.size()); g_profiler->graphAdd("packets_lost", timed_outs.size()); @@ -232,7 +233,7 @@ void ConnectionSendThread::runTimeouts(float dtime) m_iteration_packets_avaialble -= timed_outs.size(); for (std::list::iterator k = timed_outs.begin(); - k != timed_outs.end(); ++k) { + k != timed_outs.end(); ++k) { session_t peer_id = readPeerId(*(k->data)); u8 channelnum = readChannel(*(k->data)); u16 seqnum = readU16(&(k->data[BASE_HEADER_SIZE + 1])); @@ -243,27 +244,28 @@ void ConnectionSendThread::runTimeouts(float dtime) if (k->resend_count > MAX_RELIABLE_RETRY) { retry_count_exceeded = true; timeouted_peers.push_back(peer->id); - /* no need to check additional packets if a single one did timeout*/ + /* no need to check additional packets if a single + * one did timeout*/ break; } LOG(derr_con << m_connection->getDesc() - << "RE-SENDING timed-out RELIABLE to " - << k->address.serializeString() - << "(t/o=" << resend_timeout << "): " - << "from_peer_id=" << peer_id - << ", channel=" << ((int) channelnum & 0xff) - << ", seqnum=" << seqnum - << std::endl); + << "RE-SENDING timed-out RELIABLE to " + << k->address.serializeString() + << "(t/o=" << resend_timeout << "): " + << "from_peer_id=" << peer_id + << ", channel=" << ((int)channelnum & 0xff) + << ", seqnum=" << seqnum << std::endl); rawSend(*k); - // do not handle rtt here as we can't decide if this packet was - // lost or really takes more time to transmit + // do not handle rtt here as we can't decide if this + // packet was lost or really takes more time to transmit } if (retry_count_exceeded) { - break; /* no need to check other channels if we already did timeout */ + break; /* no need to check other channels if we already + did timeout */ } channel.UpdateTimers(dtime); @@ -276,23 +278,23 @@ void ConnectionSendThread::runTimeouts(float dtime) /* send ping if necessary */ if (udpPeer->Ping(dtime, data)) { LOG(dout_con << m_connection->getDesc() - << "Sending ping for peer_id: " << udpPeer->id << std::endl); + << "Sending ping for peer_id: " << udpPeer->id + << std::endl); /* this may fail if there ain't a sequence number left */ if (!rawSendAsPacket(udpPeer->id, 0, data, true)) { - //retrigger with reduced ping interval + // retrigger with reduced ping interval udpPeer->Ping(4.0, data); } } - udpPeer->RunCommandQueues(m_max_packet_size, - m_max_commands_per_iteration, - m_max_packets_requeued); + udpPeer->RunCommandQueues(m_max_packet_size, m_max_commands_per_iteration, + m_max_packets_requeued); } // Remove timed out peers for (u16 timeouted_peer : timeouted_peers) { - LOG(dout_con << m_connection->getDesc() - << "RunTimeouts(): Removing peer " << timeouted_peer << std::endl); + LOG(dout_con << m_connection->getDesc() << "RunTimeouts(): Removing peer " + << timeouted_peer << std::endl); m_connection->deletePeer(timeouted_peer, true); } } @@ -300,15 +302,14 @@ void ConnectionSendThread::runTimeouts(float dtime) void ConnectionSendThread::rawSend(const BufferedPacket &packet) { try { - m_connection->m_udpSocket.Send(packet.address, *packet.data, - packet.data.getSize()); - LOG(dout_con << m_connection->getDesc() - << " rawSend: " << packet.data.getSize() - << " bytes sent" << std::endl); + m_connection->m_udpSocket.Send( + packet.address, *packet.data, packet.data.getSize()); + LOG(dout_con << m_connection->getDesc() << " rawSend: " + << packet.data.getSize() << " bytes sent" << std::endl); } catch (SendFailedException &e) { LOG(derr_con << m_connection->getDesc() - << "Connection::rawSend(): SendFailedException: " - << packet.address.serializeString() << std::endl); + << "Connection::rawSend(): SendFailedException: " + << packet.address.serializeString() << std::endl); } } @@ -317,14 +318,14 @@ void ConnectionSendThread::sendAsPacketReliable(BufferedPacket &p, Channel *chan try { p.absolute_send_time = porting::getTimeMs(); // Buffer the packet - channel->outgoing_reliables_sent.insert(p, - (channel->readOutgoingSequenceNumber() - MAX_RELIABLE_WINDOW_SIZE) - % (MAX_RELIABLE_WINDOW_SIZE + 1)); - } - catch (AlreadyExistsException &e) { + channel->outgoing_reliables_sent.insert( + p, (channel->readOutgoingSequenceNumber() - + MAX_RELIABLE_WINDOW_SIZE) % + (MAX_RELIABLE_WINDOW_SIZE + 1)); + } catch (AlreadyExistsException &e) { LOG(derr_con << m_connection->getDesc() - << "WARNING: Going to send a reliable packet" - << " in outgoing buffer" << std::endl); + << "WARNING: Going to send a reliable packet" + << " in outgoing buffer" << std::endl); } // Send the packet @@ -332,21 +333,22 @@ void ConnectionSendThread::sendAsPacketReliable(BufferedPacket &p, Channel *chan } bool ConnectionSendThread::rawSendAsPacket(session_t peer_id, u8 channelnum, - const SharedBuffer &data, bool reliable) + const SharedBuffer &data, bool reliable) { PeerHelper peer = m_connection->getPeerNoEx(peer_id); if (!peer) { - LOG(errorstream << m_connection->getDesc() - << " dropped " << (reliable ? "reliable " : "") - << "packet for non existent peer_id: " << peer_id << std::endl); + LOG(errorstream << m_connection->getDesc() << " dropped " + << (reliable ? "reliable " : "") + << "packet for non existent peer_id: " << peer_id + << std::endl); return false; } Channel *channel = &(dynamic_cast(&peer)->channels[channelnum]); if (reliable) { bool have_sequence_number_for_raw_packet = true; - u16 seqnum = - channel->getOutgoingSequenceNumber(have_sequence_number_for_raw_packet); + u16 seqnum = channel->getOutgoingSequenceNumber( + have_sequence_number_for_raw_packet); if (!have_sequence_number_for_raw_packet) return false; @@ -357,24 +359,23 @@ bool ConnectionSendThread::rawSendAsPacket(session_t peer_id, u8 channelnum, // Add base headers and make a packet BufferedPacket p = con::makePacket(peer_address, reliable, - m_connection->GetProtocolID(), m_connection->GetPeerID(), - channelnum); + m_connection->GetProtocolID(), m_connection->GetPeerID(), + channelnum); // first check if our send window is already maxed out - if (channel->outgoing_reliables_sent.size() - < channel->getWindowSize()) { + if (channel->outgoing_reliables_sent.size() < channel->getWindowSize()) { LOG(dout_con << m_connection->getDesc() - << " INFO: sending a reliable packet to peer_id " << peer_id - << " channel: " << (u32)channelnum - << " seqnum: " << seqnum << std::endl); + << " INFO: sending a reliable packet to peer_id " + << peer_id << " channel: " << (u32)channelnum + << " seqnum: " << seqnum << std::endl); sendAsPacketReliable(p, channel); return true; } LOG(dout_con << m_connection->getDesc() - << " INFO: queueing reliable packet for peer_id: " << peer_id - << " channel: " << (u32)channelnum - << " seqnum: " << seqnum << std::endl); + << " INFO: queueing reliable packet for peer_id: " << peer_id + << " channel: " << (u32)channelnum << " seqnum: " << seqnum + << std::endl); channel->queued_reliables.push(p); return false; } @@ -383,8 +384,8 @@ bool ConnectionSendThread::rawSendAsPacket(session_t peer_id, u8 channelnum, if (peer->getAddress(MTP_UDP, peer_address)) { // Add base headers and make a packet BufferedPacket p = con::makePacket(peer_address, data, - m_connection->GetProtocolID(), m_connection->GetPeerID(), - channelnum); + m_connection->GetProtocolID(), m_connection->GetPeerID(), + channelnum); // Send the packet rawSend(p); @@ -392,116 +393,116 @@ bool ConnectionSendThread::rawSendAsPacket(session_t peer_id, u8 channelnum, } LOG(dout_con << m_connection->getDesc() - << " INFO: dropped unreliable packet for peer_id: " << peer_id - << " because of (yet) missing udp address" << std::endl); + << " INFO: dropped unreliable packet for peer_id: " << peer_id + << " because of (yet) missing udp address" << std::endl); return false; } void ConnectionSendThread::processReliableCommand(ConnectionCommand &c) { - assert(c.reliable); // Pre-condition + assert(c.reliable); // Pre-condition switch (c.type) { - case CONNCMD_NONE: - LOG(dout_con << m_connection->getDesc() - << "UDP processing reliable CONNCMD_NONE" << std::endl); - return; + case CONNCMD_NONE: + LOG(dout_con << m_connection->getDesc() + << "UDP processing reliable CONNCMD_NONE" << std::endl); + return; - case CONNCMD_SEND: - LOG(dout_con << m_connection->getDesc() - << "UDP processing reliable CONNCMD_SEND" << std::endl); + case CONNCMD_SEND: + LOG(dout_con << m_connection->getDesc() + << "UDP processing reliable CONNCMD_SEND" << std::endl); + sendReliable(c); + return; + + case CONNCMD_SEND_TO_ALL: + LOG(dout_con << m_connection->getDesc() + << "UDP processing CONNCMD_SEND_TO_ALL" << std::endl); + sendToAllReliable(c); + return; + + case CONCMD_CREATE_PEER: + LOG(dout_con << m_connection->getDesc() + << "UDP processing reliable CONCMD_CREATE_PEER" + << std::endl); + if (!rawSendAsPacket(c.peer_id, c.channelnum, c.data, c.reliable)) { + /* put to queue if we couldn't send it immediately */ sendReliable(c); - return; + } + return; - case CONNCMD_SEND_TO_ALL: - LOG(dout_con << m_connection->getDesc() - << "UDP processing CONNCMD_SEND_TO_ALL" << std::endl); - sendToAllReliable(c); - return; - - case CONCMD_CREATE_PEER: - LOG(dout_con << m_connection->getDesc() - << "UDP processing reliable CONCMD_CREATE_PEER" << std::endl); - if (!rawSendAsPacket(c.peer_id, c.channelnum, c.data, c.reliable)) { - /* put to queue if we couldn't send it immediately */ - sendReliable(c); - } - return; - - case CONNCMD_SERVE: - case CONNCMD_CONNECT: - case CONNCMD_DISCONNECT: - case CONCMD_ACK: - FATAL_ERROR("Got command that shouldn't be reliable as reliable command"); - default: - LOG(dout_con << m_connection->getDesc() - << " Invalid reliable command type: " << c.type << std::endl); + case CONNCMD_SERVE: + case CONNCMD_CONNECT: + case CONNCMD_DISCONNECT: + case CONCMD_ACK: + FATAL_ERROR("Got command that shouldn't be reliable as reliable command"); + default: + LOG(dout_con << m_connection->getDesc() + << " Invalid reliable command type: " << c.type + << std::endl); } } - void ConnectionSendThread::processNonReliableCommand(ConnectionCommand &c) { assert(!c.reliable); // Pre-condition switch (c.type) { - case CONNCMD_NONE: - LOG(dout_con << m_connection->getDesc() - << " UDP processing CONNCMD_NONE" << std::endl); - return; - case CONNCMD_SERVE: - LOG(dout_con << m_connection->getDesc() - << " UDP processing CONNCMD_SERVE port=" - << c.address.serializeString() << std::endl); - serve(c.address); - return; - case CONNCMD_CONNECT: - LOG(dout_con << m_connection->getDesc() - << " UDP processing CONNCMD_CONNECT" << std::endl); - connect(c.address); - return; - case CONNCMD_DISCONNECT: - LOG(dout_con << m_connection->getDesc() - << " UDP processing CONNCMD_DISCONNECT" << std::endl); - disconnect(); - return; - case CONNCMD_DISCONNECT_PEER: - LOG(dout_con << m_connection->getDesc() - << " UDP processing CONNCMD_DISCONNECT_PEER" << std::endl); - disconnect_peer(c.peer_id); - return; - case CONNCMD_SEND: - LOG(dout_con << m_connection->getDesc() - << " UDP processing CONNCMD_SEND" << std::endl); - send(c.peer_id, c.channelnum, c.data); - return; - case CONNCMD_SEND_TO_ALL: - LOG(dout_con << m_connection->getDesc() - << " UDP processing CONNCMD_SEND_TO_ALL" << std::endl); - sendToAll(c.channelnum, c.data); - return; - case CONCMD_ACK: - LOG(dout_con << m_connection->getDesc() - << " UDP processing CONCMD_ACK" << std::endl); - sendAsPacket(c.peer_id, c.channelnum, c.data, true); - return; - case CONCMD_CREATE_PEER: - FATAL_ERROR("Got command that should be reliable as unreliable command"); - default: - LOG(dout_con << m_connection->getDesc() - << " Invalid command type: " << c.type << std::endl); + case CONNCMD_NONE: + LOG(dout_con << m_connection->getDesc() << " UDP processing CONNCMD_NONE" + << std::endl); + return; + case CONNCMD_SERVE: + LOG(dout_con << m_connection->getDesc() + << " UDP processing CONNCMD_SERVE port=" + << c.address.serializeString() << std::endl); + serve(c.address); + return; + case CONNCMD_CONNECT: + LOG(dout_con << m_connection->getDesc() + << " UDP processing CONNCMD_CONNECT" << std::endl); + connect(c.address); + return; + case CONNCMD_DISCONNECT: + LOG(dout_con << m_connection->getDesc() + << " UDP processing CONNCMD_DISCONNECT" << std::endl); + disconnect(); + return; + case CONNCMD_DISCONNECT_PEER: + LOG(dout_con << m_connection->getDesc() + << " UDP processing CONNCMD_DISCONNECT_PEER" << std::endl); + disconnect_peer(c.peer_id); + return; + case CONNCMD_SEND: + LOG(dout_con << m_connection->getDesc() << " UDP processing CONNCMD_SEND" + << std::endl); + send(c.peer_id, c.channelnum, c.data); + return; + case CONNCMD_SEND_TO_ALL: + LOG(dout_con << m_connection->getDesc() + << " UDP processing CONNCMD_SEND_TO_ALL" << std::endl); + sendToAll(c.channelnum, c.data); + return; + case CONCMD_ACK: + LOG(dout_con << m_connection->getDesc() << " UDP processing CONCMD_ACK" + << std::endl); + sendAsPacket(c.peer_id, c.channelnum, c.data, true); + return; + case CONCMD_CREATE_PEER: + FATAL_ERROR("Got command that should be reliable as unreliable command"); + default: + LOG(dout_con << m_connection->getDesc() + << " Invalid command type: " << c.type << std::endl); } } void ConnectionSendThread::serve(Address bind_address) { - LOG(dout_con << m_connection->getDesc() - << "UDP serving at port " << bind_address.serializeString() << std::endl); + LOG(dout_con << m_connection->getDesc() << "UDP serving at port " + << bind_address.serializeString() << std::endl); try { m_connection->m_udpSocket.Bind(bind_address); m_connection->SetPeerID(PEER_ID_SERVER); - } - catch (SocketException &e) { + } catch (SocketException &e) { // Create event ConnectionEvent ce; ce.bindFailed(); @@ -512,8 +513,8 @@ void ConnectionSendThread::serve(Address bind_address) void ConnectionSendThread::connect(Address address) { LOG(dout_con << m_connection->getDesc() << " connecting to " - << address.serializeString() - << ":" << address.getPort() << std::endl); + << address.serializeString() << ":" << address.getPort() + << std::endl); UDPPeer *peer = m_connection->createServerPeer(address); @@ -525,7 +526,7 @@ void ConnectionSendThread::connect(Address address) Address bind_addr; if (address.isIPv6()) - bind_addr.setAddress((IPv6AddressBytes *) NULL); + bind_addr.setAddress((IPv6AddressBytes *)NULL); else bind_addr.setAddress(0, 0, 0, 0); @@ -546,7 +547,6 @@ void ConnectionSendThread::disconnect() writeU8(&data[0], PACKET_TYPE_CONTROL); writeU8(&data[1], CONTROLTYPE_DISCO); - // Send to all std::list peerids = m_connection->getPeerIDs(); @@ -577,23 +577,23 @@ void ConnectionSendThread::disconnect_peer(session_t peer_id) dynamic_cast(&peer)->m_pending_disconnect = true; } -void ConnectionSendThread::send(session_t peer_id, u8 channelnum, - const SharedBuffer &data) +void ConnectionSendThread::send( + session_t peer_id, u8 channelnum, const SharedBuffer &data) { assert(channelnum < CHANNEL_COUNT); // Pre-condition PeerHelper peer = m_connection->getPeerNoEx(peer_id); if (!peer) { LOG(dout_con << m_connection->getDesc() << " peer: peer_id=" << peer_id - << ">>>NOT<<< found on sending packet" - << ", channel " << (channelnum % 0xFF) - << ", size: " << data.getSize() << std::endl); + << ">>>NOT<<< found on sending packet" + << ", channel " << (channelnum % 0xFF) + << ", size: " << data.getSize() << std::endl); return; } LOG(dout_con << m_connection->getDesc() << " sending to peer_id=" << peer_id - << ", channel " << (channelnum % 0xFF) - << ", size: " << data.getSize() << std::endl); + << ", channel " << (channelnum % 0xFF) + << ", size: " << data.getSize() << std::endl); u16 split_sequence_number = peer->getNextSplitSequenceNumber(channelnum); @@ -647,16 +647,16 @@ void ConnectionSendThread::sendPackets(float dtime) std::list pendingDisconnect; std::map pending_unreliable; - const unsigned int peer_packet_quota = m_iteration_packets_avaialble - / MYMAX(peerIds.size(), 1); + const unsigned int peer_packet_quota = + m_iteration_packets_avaialble / MYMAX(peerIds.size(), 1); for (session_t peerId : peerIds) { PeerHelper peer = m_connection->getPeerNoEx(peerId); - //peer may have been removed + // peer may have been removed if (!peer) { - LOG(dout_con << m_connection->getDesc() << " Peer not found: peer_id=" - << peerId - << std::endl); + LOG(dout_con << m_connection->getDesc() + << " Peer not found: peer_id=" << peerId + << std::endl); continue; } peer->m_increment_packets_remaining = peer_packet_quota; @@ -671,17 +671,16 @@ void ConnectionSendThread::sendPackets(float dtime) pendingDisconnect.push_back(peerId); } - PROFILE(std::stringstream - peerIdentifier); - PROFILE( - peerIdentifier << "sendPackets[" << m_connection->getDesc() << ";" << peerId - << ";RELIABLE]"); - PROFILE(ScopeProfiler - peerprofiler(g_profiler, peerIdentifier.str(), SPT_AVG)); + PROFILE(std::stringstream peerIdentifier); + PROFILE(peerIdentifier << "sendPackets[" << m_connection->getDesc() << ";" + << peerId << ";RELIABLE]"); + PROFILE(ScopeProfiler peerprofiler( + g_profiler, peerIdentifier.str(), SPT_AVG)); LOG(dout_con << m_connection->getDesc() - << " Handle per peer queues: peer_id=" << peerId - << " packet quota: " << peer->m_increment_packets_remaining << std::endl); + << " Handle per peer queues: peer_id=" << peerId + << " packet quota: " << peer->m_increment_packets_remaining + << std::endl); // first send queued reliable packets for all peers (if possible) for (unsigned int i = 0; i < CHANNEL_COUNT; i++) { @@ -692,38 +691,34 @@ void ConnectionSendThread::sendPackets(float dtime) u16 next_to_receive = 0; channel.incoming_reliables.getFirstSeqnum(next_to_receive); - LOG(dout_con << m_connection->getDesc() << "\t channel: " - << i << ", peer quota:" - << peer->m_increment_packets_remaining - << std::endl - << "\t\t\treliables on wire: " - << channel.outgoing_reliables_sent.size() - << ", waiting for ack for " << next_to_ack - << std::endl - << "\t\t\tincoming_reliables: " - << channel.incoming_reliables.size() - << ", next reliable packet: " - << channel.readNextIncomingSeqNum() - << ", next queued: " << next_to_receive - << std::endl - << "\t\t\treliables queued : " - << channel.queued_reliables.size() - << std::endl - << "\t\t\tqueued commands : " - << channel.queued_commands.size() - << std::endl); + LOG(dout_con << m_connection->getDesc() << "\t channel: " << i + << ", peer quota:" + << peer->m_increment_packets_remaining << std::endl + << "\t\t\treliables on wire: " + << channel.outgoing_reliables_sent.size() + << ", waiting for ack for " << next_to_ack + << std::endl + << "\t\t\tincoming_reliables: " + << channel.incoming_reliables.size() + << ", next reliable packet: " + << channel.readNextIncomingSeqNum() + << ", next queued: " << next_to_receive << std::endl + << "\t\t\treliables queued : " + << channel.queued_reliables.size() << std::endl + << "\t\t\tqueued commands : " + << channel.queued_commands.size() << std::endl); while (!channel.queued_reliables.empty() && - channel.outgoing_reliables_sent.size() - < channel.getWindowSize() && + channel.outgoing_reliables_sent.size() < + channel.getWindowSize() && peer->m_increment_packets_remaining > 0) { BufferedPacket p = channel.queued_reliables.front(); channel.queued_reliables.pop(); LOG(dout_con << m_connection->getDesc() - << " INFO: sending a queued reliable packet " - << " channel: " << i - << ", seqnum: " << readU16(&p.data[BASE_HEADER_SIZE + 1]) - << std::endl); + << " INFO: sending a queued reliable packet " + << " channel: " << i << ", seqnum: " + << readU16(&p.data[BASE_HEADER_SIZE + 1]) + << std::endl); sendAsPacketReliable(p, &channel); peer->m_increment_packets_remaining--; } @@ -731,9 +726,8 @@ void ConnectionSendThread::sendPackets(float dtime) } if (!m_outgoing_queue.empty()) { - LOG(dout_con << m_connection->getDesc() - << " Handle non reliable queue (" - << m_outgoing_queue.size() << " pkts)" << std::endl); + LOG(dout_con << m_connection->getDesc() << " Handle non reliable queue (" + << m_outgoing_queue.size() << " pkts)" << std::endl); } unsigned int initial_queuesize = m_outgoing_queue.size(); @@ -748,17 +742,18 @@ void ConnectionSendThread::sendPackets(float dtime) PeerHelper peer = m_connection->getPeerNoEx(packet.peer_id); if (!peer) { LOG(dout_con << m_connection->getDesc() - << " Outgoing queue: peer_id=" << packet.peer_id - << ">>>NOT<<< found on sending packet" - << ", channel " << (packet.channelnum % 0xFF) - << ", size: " << packet.data.getSize() << std::endl); + << " Outgoing queue: peer_id=" << packet.peer_id + << ">>>NOT<<< found on sending packet" + << ", channel " << (packet.channelnum % 0xFF) + << ", size: " << packet.data.getSize() << std::endl); continue; } /* send acks immediately */ - if (packet.ack || peer->m_increment_packets_remaining > 0 || stopRequested()) { - rawSendAsPacket(packet.peer_id, packet.channelnum, - packet.data, packet.reliable); + if (packet.ack || peer->m_increment_packets_remaining > 0 || + stopRequested()) { + rawSendAsPacket(packet.peer_id, packet.channelnum, packet.data, + packet.reliable); if (peer->m_increment_packets_remaining > 0) peer->m_increment_packets_remaining--; } else { @@ -774,8 +769,10 @@ void ConnectionSendThread::sendPackets(float dtime) continue; if (peer->m_increment_packets_remaining == 0) { LOG(warningstream << m_connection->getDesc() - << " Packet quota used up for peer_id=" << peerId - << ", was " << peer_packet_quota << " pkts" << std::endl); + << " Packet quota used up for peer_id=" + << peerId << ", was " + << peer_packet_quota << " pkts" + << std::endl); } } } @@ -787,15 +784,15 @@ void ConnectionSendThread::sendPackets(float dtime) } } -void ConnectionSendThread::sendAsPacket(session_t peer_id, u8 channelnum, - const SharedBuffer &data, bool ack) +void ConnectionSendThread::sendAsPacket( + session_t peer_id, u8 channelnum, const SharedBuffer &data, bool ack) { OutgoingPacket packet(peer_id, channelnum, data, false, ack); m_outgoing_queue.push(packet); } ConnectionReceiveThread::ConnectionReceiveThread(unsigned int max_packet_size) : - Thread("ConnectionReceive") + Thread("ConnectionReceive") { } @@ -803,12 +800,12 @@ void *ConnectionReceiveThread::run() { assert(m_connection); - LOG(dout_con << m_connection->getDesc() - << "ConnectionReceive thread started" << std::endl); + LOG(dout_con << m_connection->getDesc() << "ConnectionReceive thread started" + << std::endl); - PROFILE(std::stringstream - ThreadIdentifier); - PROFILE(ThreadIdentifier << "ConnectionReceive: [" << m_connection->getDesc() << "]"); + PROFILE(std::stringstream ThreadIdentifier); + PROFILE(ThreadIdentifier << "ConnectionReceive: [" << m_connection->getDesc() + << "]"); // use IPv6 minimum allowed MTU as receive buffer size as this is // theoretical reliable upper boundary of a udp packet for all IPv6 enabled @@ -826,13 +823,12 @@ void *ConnectionReceiveThread::run() while (!stopRequested()) { BEGIN_DEBUG_EXCEPTION_HANDLER - PROFILE(ScopeProfiler - sp(g_profiler, ThreadIdentifier.str(), SPT_AVG)); + PROFILE(ScopeProfiler sp(g_profiler, ThreadIdentifier.str(), SPT_AVG)); #ifdef DEBUG_CONNECTION_KBPS lasttime = curtime; curtime = porting::getTimeMs(); - float dtime = CALC_DTIME(lasttime,curtime); + float dtime = CALC_DTIME(lasttime, curtime); #endif /* receive packets */ @@ -846,9 +842,7 @@ void *ConnectionReceiveThread::run() std::list peerids = m_connection->getPeerIDs(); for (std::list::iterator i = peerids.begin(); - i != peerids.end(); - i++) - { + i != peerids.end(); i++) { PeerHelper peer = m_connection->getPeerNoEx(*i); if (!peer) continue; @@ -858,35 +852,52 @@ void *ConnectionReceiveThread::run() float avg_rate = 0.0; float avg_loss = 0.0; - for(u16 j=0; jchannels[j].getCurrentDownloadRateKB(); - peer_loss += peer->channels[j].getCurrentLossRateKB(); - avg_rate += peer->channels[j].getAvgDownloadRateKB(); + for (u16 j = 0; j < CHANNEL_COUNT; j++) { + peer_current += peer->channels[j] + .getCurrentDownloadRateKB(); + peer_loss += peer->channels[j] + .getCurrentLossRateKB(); + avg_rate += peer->channels[j] + .getAvgDownloadRateKB(); avg_loss += peer->channels[j].getAvgLossRateKB(); } std::stringstream output; output << std::fixed << std::setprecision(1); - output << "OUT to Peer " << *i << " RATES (good / loss) " << std::endl; - output << "\tcurrent (sum): " << peer_current << "kb/s "<< peer_loss << "kb/s" << std::endl; - output << "\taverage (sum): " << avg_rate << "kb/s "<< avg_loss << "kb/s" << std::endl; + output << "OUT to Peer " << *i << " RATES (good / loss) " + << std::endl; + output << "\tcurrent (sum): " << peer_current << "kb/s " + << peer_loss << "kb/s" << std::endl; + output << "\taverage (sum): " << avg_rate << "kb/s " + << avg_loss << "kb/s" << std::endl; output << std::setfill(' '); - for(u16 j=0; jchannels[j].getCurrentDownloadRateKB() <<"kb/s" - << " AVG: " << std::setw(6) << peer->channels[j].getAvgDownloadRateKB() <<"kb/s" - << " MAX: " << std::setw(6) << peer->channels[j].getMaxDownloadRateKB() <<"kb/s" - << " /" - << " CUR: " << std::setw(6) << peer->channels[j].getCurrentLossRateKB() <<"kb/s" - << " AVG: " << std::setw(6) << peer->channels[j].getAvgLossRateKB() <<"kb/s" - << " MAX: " << std::setw(6) << peer->channels[j].getMaxLossRateKB() <<"kb/s" - << " / WS: " << peer->channels[j].getWindowSize() - << std::endl; + << " CUR: " << std::setw(6) + << peer->channels[j].getCurrentDownloadRateKB() + << "kb/s" + << " AVG: " << std::setw(6) + << peer->channels[j].getAvgDownloadRateKB() + << "kb/s" + << " MAX: " << std::setw(6) + << peer->channels[j].getMaxDownloadRateKB() + << "kb/s" + << " /" + << " CUR: " << std::setw(6) + << peer->channels[j].getCurrentLossRateKB() + << "kb/s" + << " AVG: " << std::setw(6) + << peer->channels[j].getAvgLossRateKB() + << "kb/s" + << " MAX: " << std::setw(6) + << peer->channels[j].getMaxLossRateKB() + << "kb/s" + << " / WS: " + << peer->channels[j].getWindowSize() + << std::endl; } - fprintf(stderr,"%s\n",output.str().c_str()); + fprintf(stderr, "%s\n", output.str().c_str()); } } #endif @@ -898,8 +909,7 @@ void *ConnectionReceiveThread::run() } // Receive packets from the network and buffers and create ConnectionEvents -void ConnectionReceiveThread::receive(SharedBuffer &packetdata, - bool &packet_queued) +void ConnectionReceiveThread::receive(SharedBuffer &packetdata, bool &packet_queued) { try { // First, see if there any buffered packets we can process now @@ -915,8 +925,7 @@ void ConnectionReceiveThread::receive(SharedBuffer &packetdata, e.dataReceived(peer_id, resultdata); m_connection->putEvent(e); } - } - catch (ProcessedSilentlyException &e) { + } catch (ProcessedSilentlyException &e) { /* try reading again */ } } @@ -925,19 +934,20 @@ void ConnectionReceiveThread::receive(SharedBuffer &packetdata, // Call Receive() to wait for incoming data Address sender; - s32 received_size = m_connection->m_udpSocket.Receive(sender, - *packetdata, packetdata.getSize()); + s32 received_size = m_connection->m_udpSocket.Receive( + sender, *packetdata, packetdata.getSize()); if (received_size < 0) return; if ((received_size < BASE_HEADER_SIZE) || - (readU32(&packetdata[0]) != m_connection->GetProtocolID())) { + (readU32(&packetdata[0]) != + m_connection->GetProtocolID())) { LOG(derr_con << m_connection->getDesc() - << "Receive(): Invalid incoming packet, " - << "size: " << received_size - << ", protocol: " - << ((received_size >= 4) ? readU32(&packetdata[0]) : -1) - << std::endl); + << "Receive(): Invalid incoming packet, " + << "size: " << received_size << ", protocol: " + << ((received_size >= 4) ? readU32(&packetdata[0]) + : -1) + << std::endl); return; } @@ -946,7 +956,8 @@ void ConnectionReceiveThread::receive(SharedBuffer &packetdata, if (channelnum > CHANNEL_COUNT - 1) { LOG(derr_con << m_connection->getDesc() - << "Receive(): Invalid channel " << (u32)channelnum << std::endl); + << "Receive(): Invalid channel " << (u32)channelnum + << std::endl); return; } @@ -960,14 +971,15 @@ void ConnectionReceiveThread::receive(SharedBuffer &packetdata, /* The peer was not found in our lists. Add it. */ if (peer_id == PEER_ID_INEXISTENT) { - peer_id = m_connection->createPeer(sender, MTP_MINETEST_RELIABLE_UDP, 0); + peer_id = m_connection->createPeer( + sender, MTP_MINETEST_RELIABLE_UDP, 0); } PeerHelper peer = m_connection->getPeerNoEx(peer_id); if (!peer) { LOG(dout_con << m_connection->getDesc() - << " got packet from unknown peer_id: " - << peer_id << " Ignoring." << std::endl); + << " got packet from unknown peer_id: " << peer_id + << " Ignoring." << std::endl); return; } @@ -976,15 +988,18 @@ void ConnectionReceiveThread::receive(SharedBuffer &packetdata, Address peer_address; if (peer->getAddress(MTP_UDP, peer_address)) { if (peer_address != sender) { - LOG(derr_con << m_connection->getDesc() - << " Peer " << peer_id << " sending from different address." - " Ignoring." << std::endl); + LOG(derr_con << m_connection->getDesc() << " Peer " + << peer_id + << " sending from different address." + " Ignoring." + << std::endl); return; } } else { - LOG(derr_con << m_connection->getDesc() - << " Peer " << peer_id << " doesn't have an address?!" - " Ignoring." << std::endl); + LOG(derr_con << m_connection->getDesc() << " Peer " << peer_id + << " doesn't have an address?!" + " Ignoring." + << std::endl); return; } @@ -995,8 +1010,10 @@ void ConnectionReceiveThread::receive(SharedBuffer &packetdata, channel = &dynamic_cast(&peer)->channels[channelnum]; } else { LOG(derr_con << m_connection->getDesc() - << "Receive(): peer_id=" << peer_id << " isn't an UDPPeer?!" - " Ignoring." << std::endl); + << "Receive(): peer_id=" << peer_id + << " isn't an UDPPeer?!" + " Ignoring." + << std::endl); return; } @@ -1007,33 +1024,30 @@ void ConnectionReceiveThread::receive(SharedBuffer &packetdata, // Make a new SharedBuffer from the data without the base headers SharedBuffer strippeddata(received_size - BASE_HEADER_SIZE); memcpy(*strippeddata, &packetdata[BASE_HEADER_SIZE], - strippeddata.getSize()); + strippeddata.getSize()); try { // Process it (the result is some data with no headers made by us) - SharedBuffer resultdata = processPacket - (channel, strippeddata, peer_id, channelnum, false); + SharedBuffer resultdata = processPacket(channel, strippeddata, + peer_id, channelnum, false); LOG(dout_con << m_connection->getDesc() - << " ProcessPacket from peer_id: " << peer_id - << ", channel: " << (u32)channelnum << ", returned " - << resultdata.getSize() << " bytes" << std::endl); + << " ProcessPacket from peer_id: " << peer_id + << ", channel: " << (u32)channelnum << ", returned " + << resultdata.getSize() << " bytes" << std::endl); ConnectionEvent e; e.dataReceived(peer_id, resultdata); m_connection->putEvent(e); - } - catch (ProcessedSilentlyException &e) { - } - catch (ProcessedQueued &e) { + } catch (ProcessedSilentlyException &e) { + } catch (ProcessedQueued &e) { // we set it to true anyway (see below) } /* Every time we receive a packet it can happen that a previously * buffered packet is now ready to process. */ packet_queued = true; - } - catch (InvalidIncomingDataException &e) { + } catch (InvalidIncomingDataException &e) { } } @@ -1058,8 +1072,8 @@ bool ConnectionReceiveThread::getFromBuffers(session_t &peer_id, SharedBuffer &dst) +bool ConnectionReceiveThread::checkIncomingBuffers( + Channel *channel, session_t &peer_id, SharedBuffer &dst) { u16 firstseqnum = 0; if (channel->incoming_reliables.getFirstSeqnum(firstseqnum)) { @@ -1070,11 +1084,10 @@ bool ConnectionReceiveThread::checkIncomingBuffers(Channel *channel, u16 seqnum = readU16(&p.data[BASE_HEADER_SIZE + 1]); LOG(dout_con << m_connection->getDesc() - << "UNBUFFERING TYPE_RELIABLE" - << " seqnum=" << seqnum - << " peer_id=" << peer_id - << " channel=" << ((int) channelnum & 0xff) - << std::endl); + << "UNBUFFERING TYPE_RELIABLE" + << " seqnum=" << seqnum << " peer_id=" << peer_id + << " channel=" << ((int)channelnum & 0xff) + << std::endl); channel->incNextIncomingSeqNum(); @@ -1091,7 +1104,8 @@ bool ConnectionReceiveThread::checkIncomingBuffers(Channel *channel, } SharedBuffer ConnectionReceiveThread::processPacket(Channel *channel, - const SharedBuffer &packetdata, session_t peer_id, u8 channelnum, bool reliable) + const SharedBuffer &packetdata, session_t peer_id, u8 channelnum, + bool reliable) { PeerHelper peer = m_connection->getPeerNoEx(peer_id); @@ -1112,8 +1126,8 @@ SharedBuffer ConnectionReceiveThread::processPacket(Channel *channel, } if (type >= PACKET_TYPE_MAX) { - derr_con << m_connection->getDesc() << "Got invalid type=" << ((int) type & 0xff) - << std::endl; + derr_con << m_connection->getDesc() + << "Got invalid type=" << ((int)type & 0xff) << std::endl; throw InvalidIncomingDataException("Invalid packet type"); } @@ -1122,15 +1136,16 @@ SharedBuffer ConnectionReceiveThread::processPacket(Channel *channel, } const ConnectionReceiveThread::PacketTypeHandler - ConnectionReceiveThread::packetTypeRouter[PACKET_TYPE_MAX] = { - {&ConnectionReceiveThread::handlePacketType_Control}, - {&ConnectionReceiveThread::handlePacketType_Original}, - {&ConnectionReceiveThread::handlePacketType_Split}, - {&ConnectionReceiveThread::handlePacketType_Reliable}, + ConnectionReceiveThread::packetTypeRouter[PACKET_TYPE_MAX] = { + {&ConnectionReceiveThread::handlePacketType_Control}, + {&ConnectionReceiveThread::handlePacketType_Original}, + {&ConnectionReceiveThread::handlePacketType_Split}, + {&ConnectionReceiveThread::handlePacketType_Reliable}, }; SharedBuffer ConnectionReceiveThread::handlePacketType_Control(Channel *channel, - const SharedBuffer &packetdata, Peer *peer, u8 channelnum, bool reliable) + const SharedBuffer &packetdata, Peer *peer, u8 channelnum, + bool reliable) { if (packetdata.getSize() < 2) throw InvalidIncomingDataException("packetdata.getSize() < 2"); @@ -1142,26 +1157,29 @@ SharedBuffer ConnectionReceiveThread::handlePacketType_Control(Channel *chan if (packetdata.getSize() < 4) { throw InvalidIncomingDataException( - "packetdata.getSize() < 4 (ACK header size)"); + "packetdata.getSize() < 4 (ACK header size)"); } u16 seqnum = readU16(&packetdata[2]); - LOG(dout_con << m_connection->getDesc() << " [ CONTROLTYPE_ACK: channelnum=" - << ((int) channelnum & 0xff) << ", peer_id=" << peer->id << ", seqnum=" - << seqnum << " ]" << std::endl); + LOG(dout_con << m_connection->getDesc() + << " [ CONTROLTYPE_ACK: channelnum=" + << ((int)channelnum & 0xff) << ", peer_id=" << peer->id + << ", seqnum=" << seqnum << " ]" << std::endl); try { - BufferedPacket p = channel->outgoing_reliables_sent.popSeqnum(seqnum); + BufferedPacket p = channel->outgoing_reliables_sent.popSeqnum( + seqnum); // only calculate rtt from straight sent packets if (p.resend_count == 0) { // Get round trip time u64 current_time = porting::getTimeMs(); - // a overflow is quite unlikely but as it'd result in major - // rtt miscalculation we handle it here + // a overflow is quite unlikely but as it'd result in + // major rtt miscalculation we handle it here if (current_time > p.absolute_send_time) { - float rtt = (current_time - p.absolute_send_time) / 1000.0; + float rtt = (current_time - p.absolute_send_time) / + 1000.0; // Let peer calculate stuff according to it // (avg_rtt and resend_timeout) @@ -1180,8 +1198,8 @@ SharedBuffer ConnectionReceiveThread::handlePacketType_Control(Channel *chan m_connection->TriggerSend(); } catch (NotFoundException &e) { LOG(derr_con << m_connection->getDesc() - << "WARNING: ACKed packet not in outgoing queue" - << " seqnum=" << seqnum << std::endl); + << "WARNING: ACKed packet not in outgoing queue" + << " seqnum=" << seqnum << std::endl); channel->UpdatePacketTooLateCounter(); } @@ -1189,18 +1207,19 @@ SharedBuffer ConnectionReceiveThread::handlePacketType_Control(Channel *chan } else if (controltype == CONTROLTYPE_SET_PEER_ID) { // Got a packet to set our peer id if (packetdata.getSize() < 4) - throw InvalidIncomingDataException - ("packetdata.getSize() < 4 (SET_PEER_ID header size)"); + throw InvalidIncomingDataException("packetdata.getSize() < 4 " + "(SET_PEER_ID header size)"); session_t peer_id_new = readU16(&packetdata[2]); - LOG(dout_con << m_connection->getDesc() << "Got new peer id: " << peer_id_new - << "... " << std::endl); + LOG(dout_con << m_connection->getDesc() << "Got new peer id: " + << peer_id_new << "... " << std::endl); if (m_connection->GetPeerID() != PEER_ID_INEXISTENT) { LOG(derr_con << m_connection->getDesc() - << "WARNING: Not changing existing peer id." << std::endl); + << "WARNING: Not changing existing peer id." + << std::endl); } else { LOG(dout_con << m_connection->getDesc() << "changing own peer id" - << std::endl); + << std::endl); m_connection->SetPeerID(peer_id_new); } @@ -1214,29 +1233,31 @@ SharedBuffer ConnectionReceiveThread::handlePacketType_Control(Channel *chan // Just ignore it, the incoming data already reset // the timeout counter LOG(dout_con << m_connection->getDesc() << "DISCO: Removing peer " - << peer->id << std::endl); + << peer->id << std::endl); if (!m_connection->deletePeer(peer->id, false)) { - derr_con << m_connection->getDesc() << "DISCO: Peer not found" << std::endl; + derr_con << m_connection->getDesc() << "DISCO: Peer not found" + << std::endl; } throw ProcessedSilentlyException("Got a DISCO"); } else { LOG(derr_con << m_connection->getDesc() - << "INVALID TYPE_CONTROL: invalid controltype=" - << ((int) controltype & 0xff) << std::endl); + << "INVALID TYPE_CONTROL: invalid controltype=" + << ((int)controltype & 0xff) << std::endl); throw InvalidIncomingDataException("Invalid control type"); } } SharedBuffer ConnectionReceiveThread::handlePacketType_Original(Channel *channel, - const SharedBuffer &packetdata, Peer *peer, u8 channelnum, bool reliable) + const SharedBuffer &packetdata, Peer *peer, u8 channelnum, + bool reliable) { if (packetdata.getSize() <= ORIGINAL_HEADER_SIZE) - throw InvalidIncomingDataException - ("packetdata.getSize() <= ORIGINAL_HEADER_SIZE"); + throw InvalidIncomingDataException( + "packetdata.getSize() <= ORIGINAL_HEADER_SIZE"); LOG(dout_con << m_connection->getDesc() << "RETURNING TYPE_ORIGINAL to user" - << std::endl); + << std::endl); // Get the inside packet out and return it SharedBuffer payload(packetdata.getSize() - ORIGINAL_HEADER_SIZE); memcpy(*payload, &(packetdata[ORIGINAL_HEADER_SIZE]), payload.getSize()); @@ -1244,29 +1265,29 @@ SharedBuffer ConnectionReceiveThread::handlePacketType_Original(Channel *cha } SharedBuffer ConnectionReceiveThread::handlePacketType_Split(Channel *channel, - const SharedBuffer &packetdata, Peer *peer, u8 channelnum, bool reliable) + const SharedBuffer &packetdata, Peer *peer, u8 channelnum, + bool reliable) { Address peer_address; if (peer->getAddress(MTP_UDP, peer_address)) { // We have to create a packet again for buffering // This isn't actually too bad an idea. - BufferedPacket packet = makePacket(peer_address, - packetdata, - m_connection->GetProtocolID(), - peer->id, - channelnum); + BufferedPacket packet = makePacket(peer_address, packetdata, + m_connection->GetProtocolID(), peer->id, channelnum); // Buffer the packet - SharedBuffer data = peer->addSplitPacket(channelnum, packet, reliable); + SharedBuffer data = + peer->addSplitPacket(channelnum, packet, reliable); if (data.getSize() != 0) { LOG(dout_con << m_connection->getDesc() - << "RETURNING TYPE_SPLIT: Constructed full data, " - << "size=" << data.getSize() << std::endl); + << "RETURNING TYPE_SPLIT: Constructed full data, " + << "size=" << data.getSize() << std::endl); return data; } - LOG(dout_con << m_connection->getDesc() << "BUFFERED TYPE_SPLIT" << std::endl); + LOG(dout_con << m_connection->getDesc() << "BUFFERED TYPE_SPLIT" + << std::endl); throw ProcessedSilentlyException("Buffered a split packet chunk"); } @@ -1275,7 +1296,8 @@ SharedBuffer ConnectionReceiveThread::handlePacketType_Split(Channel *channe } SharedBuffer ConnectionReceiveThread::handlePacketType_Reliable(Channel *channel, - const SharedBuffer &packetdata, Peer *peer, u8 channelnum, bool reliable) + const SharedBuffer &packetdata, Peer *peer, u8 channelnum, + bool reliable) { assert(channel != NULL); @@ -1284,41 +1306,44 @@ SharedBuffer ConnectionReceiveThread::handlePacketType_Reliable(Channel *cha throw InvalidIncomingDataException("Found nested reliable packets"); if (packetdata.getSize() < RELIABLE_HEADER_SIZE) - throw InvalidIncomingDataException("packetdata.getSize() < RELIABLE_HEADER_SIZE"); + throw InvalidIncomingDataException( + "packetdata.getSize() < RELIABLE_HEADER_SIZE"); u16 seqnum = readU16(&packetdata[1]); bool is_future_packet = false; bool is_old_packet = false; /* packet is within our receive window send ack */ - if (seqnum_in_window(seqnum, - channel->readNextIncomingSeqNum(), MAX_RELIABLE_WINDOW_SIZE)) { + if (seqnum_in_window(seqnum, channel->readNextIncomingSeqNum(), + MAX_RELIABLE_WINDOW_SIZE)) { m_connection->sendAck(peer->id, channelnum, seqnum); } else { - is_future_packet = seqnum_higher(seqnum, channel->readNextIncomingSeqNum()); + is_future_packet = + seqnum_higher(seqnum, channel->readNextIncomingSeqNum()); is_old_packet = seqnum_higher(channel->readNextIncomingSeqNum(), seqnum); /* packet is not within receive window, don't send ack. * * if this was a valid packet it's gonna be retransmitted */ if (is_future_packet) - throw ProcessedSilentlyException( - "Received packet newer then expected, not sending ack"); + throw ProcessedSilentlyException("Received packet newer then " + "expected, not sending ack"); /* seems like our ack was lost, send another one for a old packet */ if (is_old_packet) { LOG(dout_con << m_connection->getDesc() - << "RE-SENDING ACK: peer_id: " << peer->id - << ", channel: " << (channelnum & 0xFF) - << ", seqnum: " << seqnum << std::endl;) + << "RE-SENDING ACK: peer_id: " << peer->id + << ", channel: " << (channelnum & 0xFF) + << ", seqnum: " << seqnum << std::endl;) m_connection->sendAck(peer->id, channelnum, seqnum); // we already have this packet so this one was on wire at least // the current timeout - // we don't know how long this packet was on wire don't do silly guessing - // dynamic_cast(&peer)-> + // we don't know how long this packet was on wire don't do silly + // guessing dynamic_cast(&peer)-> // reportRTT(dynamic_cast(&peer)->getResendTimeout()); - throw ProcessedSilentlyException("Retransmitting ack for old packet"); + throw ProcessedSilentlyException( + "Retransmitting ack for old packet"); } } @@ -1330,19 +1355,16 @@ SharedBuffer ConnectionReceiveThread::handlePacketType_Reliable(Channel *cha // This one comes later, buffer it. // Actually we have to make a packet to buffer one. // Well, we have all the ingredients, so just do it. - BufferedPacket packet = con::makePacket( - peer_address, - packetdata, - m_connection->GetProtocolID(), - peer->id, - channelnum); + BufferedPacket packet = con::makePacket(peer_address, packetdata, + m_connection->GetProtocolID(), peer->id, channelnum); try { - channel->incoming_reliables.insert(packet, channel->readNextIncomingSeqNum()); + channel->incoming_reliables.insert( + packet, channel->readNextIncomingSeqNum()); LOG(dout_con << m_connection->getDesc() - << "BUFFERING, TYPE_RELIABLE peer_id: " << peer->id - << ", channel: " << (channelnum & 0xFF) - << ", seqnum: " << seqnum << std::endl;) + << "BUFFERING, TYPE_RELIABLE peer_id: " << peer->id + << ", channel: " << (channelnum & 0xFF) + << ", seqnum: " << seqnum << std::endl;) throw ProcessedQueued("Buffered future reliable packet"); } catch (AlreadyExistsException &e) { @@ -1352,25 +1374,24 @@ SharedBuffer ConnectionReceiveThread::handlePacketType_Reliable(Channel *cha m_connection->putCommand(discon); LOG(derr_con << m_connection->getDesc() - << "INVALID, TYPE_RELIABLE peer_id: " << peer->id - << ", channel: " << (channelnum & 0xFF) - << ", seqnum: " << seqnum - << "DROPPING CLIENT!" << std::endl;) + << "INVALID, TYPE_RELIABLE peer_id: " << peer->id + << ", channel: " << (channelnum & 0xFF) + << ", seqnum: " << seqnum << "DROPPING CLIENT!" + << std::endl;) } } /* we got a packet to process right now */ - LOG(dout_con << m_connection->getDesc() - << "RECURSIVE, TYPE_RELIABLE peer_id: " << peer->id - << ", channel: " << (channelnum & 0xFF) - << ", seqnum: " << seqnum << std::endl;) - + LOG(dout_con << m_connection->getDesc() << "RECURSIVE, TYPE_RELIABLE peer_id: " + << peer->id << ", channel: " << (channelnum & 0xFF) + << ", seqnum: " << seqnum << std::endl;) /* check for resend case */ u16 queued_seqnum = 0; if (channel->incoming_reliables.getFirstSeqnum(queued_seqnum)) { if (queued_seqnum == seqnum) { - BufferedPacket queued_packet = channel->incoming_reliables.popFirst(); + BufferedPacket queued_packet = + channel->incoming_reliables.popFirst(); /** TODO find a way to verify the new against the old packet */ } } diff --git a/src/network/networkpacket.cpp b/src/network/networkpacket.cpp index 4d531b611..e82dd583e 100644 --- a/src/network/networkpacket.cpp +++ b/src/network/networkpacket.cpp @@ -23,14 +23,14 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/serialize.h" #include "networkprotocol.h" -NetworkPacket::NetworkPacket(u16 command, u32 datasize, session_t peer_id): -m_datasize(datasize), m_command(command), m_peer_id(peer_id) +NetworkPacket::NetworkPacket(u16 command, u32 datasize, session_t peer_id) : + m_datasize(datasize), m_command(command), m_peer_id(peer_id) { m_data.resize(m_datasize); } -NetworkPacket::NetworkPacket(u16 command, u32 datasize): -m_datasize(datasize), m_command(command) +NetworkPacket::NetworkPacket(u16 command, u32 datasize) : + m_datasize(datasize), m_command(command) { m_data.resize(m_datasize); } @@ -44,8 +44,8 @@ void NetworkPacket::checkReadOffset(u32 from_offset, u32 field_size) { if (from_offset + field_size > m_datasize) { std::stringstream ss; - ss << "Reading outside packet (offset: " << - from_offset << ", packet size: " << getSize() << ")"; + ss << "Reading outside packet (offset: " << from_offset + << ", packet size: " << getSize() << ")"; throw PacketError(ss.str()); } } @@ -75,14 +75,14 @@ void NetworkPacket::clear() m_peer_id = 0; } -const char* NetworkPacket::getString(u32 from_offset) +const char *NetworkPacket::getString(u32 from_offset) { checkReadOffset(from_offset, 0); - return (char*)&m_data[from_offset]; + return (char *)&m_data[from_offset]; } -void NetworkPacket::putRawString(const char* src, u32 len) +void NetworkPacket::putRawString(const char *src, u32 len) { if (m_read_offset + len > m_datasize) { m_datasize = m_read_offset + len; @@ -96,7 +96,7 @@ void NetworkPacket::putRawString(const char* src, u32 len) m_read_offset += len; } -NetworkPacket& NetworkPacket::operator>>(std::string& dst) +NetworkPacket &NetworkPacket::operator>>(std::string &dst) { checkReadOffset(m_read_offset, 2); u16 strLen = readU16(&m_data[m_read_offset]); @@ -111,13 +111,13 @@ NetworkPacket& NetworkPacket::operator>>(std::string& dst) checkReadOffset(m_read_offset, strLen); dst.reserve(strLen); - dst.append((char*)&m_data[m_read_offset], strLen); + dst.append((char *)&m_data[m_read_offset], strLen); m_read_offset += strLen; return *this; } -NetworkPacket& NetworkPacket::operator<<(const std::string &src) +NetworkPacket &NetworkPacket::operator<<(const std::string &src) { if (src.size() > STRING_MAX_LEN) { throw PacketError("String too long"); @@ -145,7 +145,7 @@ void NetworkPacket::putLongString(const std::string &src) putRawString(src.c_str(), msgsize); } -NetworkPacket& NetworkPacket::operator>>(std::wstring& dst) +NetworkPacket &NetworkPacket::operator>>(std::wstring &dst) { checkReadOffset(m_read_offset, 2); u16 strLen = readU16(&m_data[m_read_offset]); @@ -160,7 +160,7 @@ NetworkPacket& NetworkPacket::operator>>(std::wstring& dst) checkReadOffset(m_read_offset, strLen * 2); dst.reserve(strLen); - for(u16 i=0; i>(std::wstring& dst) return *this; } -NetworkPacket& NetworkPacket::operator<<(const std::wstring &src) +NetworkPacket &NetworkPacket::operator<<(const std::wstring &src) { if (src.size() > WIDE_STRING_MAX_LEN) { throw PacketError("String too long"); @@ -180,8 +180,8 @@ NetworkPacket& NetworkPacket::operator<<(const std::wstring &src) *this << msgsize; // Write string - for (u16 i=0; i>(char& dst) +NetworkPacket &NetworkPacket::operator>>(char &dst) { checkReadOffset(m_read_offset, 1); @@ -230,7 +230,7 @@ char NetworkPacket::getChar(u32 offset) return readU8(&m_data[offset]); } -NetworkPacket& NetworkPacket::operator<<(char src) +NetworkPacket &NetworkPacket::operator<<(char src) { checkDataSize(1); @@ -240,7 +240,7 @@ NetworkPacket& NetworkPacket::operator<<(char src) return *this; } -NetworkPacket& NetworkPacket::operator<<(u8 src) +NetworkPacket &NetworkPacket::operator<<(u8 src) { checkDataSize(1); @@ -250,7 +250,7 @@ NetworkPacket& NetworkPacket::operator<<(u8 src) return *this; } -NetworkPacket& NetworkPacket::operator<<(bool src) +NetworkPacket &NetworkPacket::operator<<(bool src) { checkDataSize(1); @@ -260,7 +260,7 @@ NetworkPacket& NetworkPacket::operator<<(bool src) return *this; } -NetworkPacket& NetworkPacket::operator<<(u16 src) +NetworkPacket &NetworkPacket::operator<<(u16 src) { checkDataSize(2); @@ -270,7 +270,7 @@ NetworkPacket& NetworkPacket::operator<<(u16 src) return *this; } -NetworkPacket& NetworkPacket::operator<<(u32 src) +NetworkPacket &NetworkPacket::operator<<(u32 src) { checkDataSize(4); @@ -280,7 +280,7 @@ NetworkPacket& NetworkPacket::operator<<(u32 src) return *this; } -NetworkPacket& NetworkPacket::operator<<(u64 src) +NetworkPacket &NetworkPacket::operator<<(u64 src) { checkDataSize(8); @@ -290,7 +290,7 @@ NetworkPacket& NetworkPacket::operator<<(u64 src) return *this; } -NetworkPacket& NetworkPacket::operator<<(float src) +NetworkPacket &NetworkPacket::operator<<(float src) { checkDataSize(4); @@ -300,7 +300,7 @@ NetworkPacket& NetworkPacket::operator<<(float src) return *this; } -NetworkPacket& NetworkPacket::operator>>(bool& dst) +NetworkPacket &NetworkPacket::operator>>(bool &dst) { checkReadOffset(m_read_offset, 1); @@ -310,7 +310,7 @@ NetworkPacket& NetworkPacket::operator>>(bool& dst) return *this; } -NetworkPacket& NetworkPacket::operator>>(u8& dst) +NetworkPacket &NetworkPacket::operator>>(u8 &dst) { checkReadOffset(m_read_offset, 1); @@ -327,7 +327,7 @@ u8 NetworkPacket::getU8(u32 offset) return readU8(&m_data[offset]); } -u8* NetworkPacket::getU8Ptr(u32 from_offset) +u8 *NetworkPacket::getU8Ptr(u32 from_offset) { if (m_datasize == 0) { return NULL; @@ -335,10 +335,10 @@ u8* NetworkPacket::getU8Ptr(u32 from_offset) checkReadOffset(from_offset, 1); - return (u8*)&m_data[from_offset]; + return (u8 *)&m_data[from_offset]; } -NetworkPacket& NetworkPacket::operator>>(u16& dst) +NetworkPacket &NetworkPacket::operator>>(u16 &dst) { checkReadOffset(m_read_offset, 2); @@ -355,7 +355,7 @@ u16 NetworkPacket::getU16(u32 from_offset) return readU16(&m_data[from_offset]); } -NetworkPacket& NetworkPacket::operator>>(u32& dst) +NetworkPacket &NetworkPacket::operator>>(u32 &dst) { checkReadOffset(m_read_offset, 4); @@ -365,7 +365,7 @@ NetworkPacket& NetworkPacket::operator>>(u32& dst) return *this; } -NetworkPacket& NetworkPacket::operator>>(u64& dst) +NetworkPacket &NetworkPacket::operator>>(u64 &dst) { checkReadOffset(m_read_offset, 8); @@ -375,7 +375,7 @@ NetworkPacket& NetworkPacket::operator>>(u64& dst) return *this; } -NetworkPacket& NetworkPacket::operator>>(float& dst) +NetworkPacket &NetworkPacket::operator>>(float &dst) { checkReadOffset(m_read_offset, 4); @@ -385,7 +385,7 @@ NetworkPacket& NetworkPacket::operator>>(float& dst) return *this; } -NetworkPacket& NetworkPacket::operator>>(v2f& dst) +NetworkPacket &NetworkPacket::operator>>(v2f &dst) { checkReadOffset(m_read_offset, 8); @@ -395,7 +395,7 @@ NetworkPacket& NetworkPacket::operator>>(v2f& dst) return *this; } -NetworkPacket& NetworkPacket::operator>>(v3f& dst) +NetworkPacket &NetworkPacket::operator>>(v3f &dst) { checkReadOffset(m_read_offset, 12); @@ -405,7 +405,7 @@ NetworkPacket& NetworkPacket::operator>>(v3f& dst) return *this; } -NetworkPacket& NetworkPacket::operator>>(s16& dst) +NetworkPacket &NetworkPacket::operator>>(s16 &dst) { checkReadOffset(m_read_offset, 2); @@ -415,13 +415,13 @@ NetworkPacket& NetworkPacket::operator>>(s16& dst) return *this; } -NetworkPacket& NetworkPacket::operator<<(s16 src) +NetworkPacket &NetworkPacket::operator<<(s16 src) { - *this << (u16) src; + *this << (u16)src; return *this; } -NetworkPacket& NetworkPacket::operator>>(s32& dst) +NetworkPacket &NetworkPacket::operator>>(s32 &dst) { checkReadOffset(m_read_offset, 4); @@ -431,13 +431,13 @@ NetworkPacket& NetworkPacket::operator>>(s32& dst) return *this; } -NetworkPacket& NetworkPacket::operator<<(s32 src) +NetworkPacket &NetworkPacket::operator<<(s32 src) { - *this << (u32) src; + *this << (u32)src; return *this; } -NetworkPacket& NetworkPacket::operator>>(v3s16& dst) +NetworkPacket &NetworkPacket::operator>>(v3s16 &dst) { checkReadOffset(m_read_offset, 6); @@ -447,7 +447,7 @@ NetworkPacket& NetworkPacket::operator>>(v3s16& dst) return *this; } -NetworkPacket& NetworkPacket::operator>>(v2s32& dst) +NetworkPacket &NetworkPacket::operator>>(v2s32 &dst) { checkReadOffset(m_read_offset, 8); @@ -457,7 +457,7 @@ NetworkPacket& NetworkPacket::operator>>(v2s32& dst) return *this; } -NetworkPacket& NetworkPacket::operator>>(v3s32& dst) +NetworkPacket &NetworkPacket::operator>>(v3s32 &dst) { checkReadOffset(m_read_offset, 12); @@ -467,45 +467,45 @@ NetworkPacket& NetworkPacket::operator>>(v3s32& dst) return *this; } -NetworkPacket& NetworkPacket::operator<<(v2f src) +NetworkPacket &NetworkPacket::operator<<(v2f src) { - *this << (float) src.X; - *this << (float) src.Y; + *this << (float)src.X; + *this << (float)src.Y; return *this; } -NetworkPacket& NetworkPacket::operator<<(v3f src) +NetworkPacket &NetworkPacket::operator<<(v3f src) { - *this << (float) src.X; - *this << (float) src.Y; - *this << (float) src.Z; + *this << (float)src.X; + *this << (float)src.Y; + *this << (float)src.Z; return *this; } -NetworkPacket& NetworkPacket::operator<<(v3s16 src) +NetworkPacket &NetworkPacket::operator<<(v3s16 src) { - *this << (s16) src.X; - *this << (s16) src.Y; - *this << (s16) src.Z; + *this << (s16)src.X; + *this << (s16)src.Y; + *this << (s16)src.Z; return *this; } -NetworkPacket& NetworkPacket::operator<<(v2s32 src) +NetworkPacket &NetworkPacket::operator<<(v2s32 src) { - *this << (s32) src.X; - *this << (s32) src.Y; + *this << (s32)src.X; + *this << (s32)src.Y; return *this; } -NetworkPacket& NetworkPacket::operator<<(v3s32 src) +NetworkPacket &NetworkPacket::operator<<(v3s32 src) { - *this << (s32) src.X; - *this << (s32) src.Y; - *this << (s32) src.Z; + *this << (s32)src.X; + *this << (s32)src.Y; + *this << (s32)src.Z; return *this; } -NetworkPacket& NetworkPacket::operator>>(video::SColor& dst) +NetworkPacket &NetworkPacket::operator>>(video::SColor &dst) { checkReadOffset(m_read_offset, 4); @@ -515,7 +515,7 @@ NetworkPacket& NetworkPacket::operator>>(video::SColor& dst) return *this; } -NetworkPacket& NetworkPacket::operator<<(video::SColor src) +NetworkPacket &NetworkPacket::operator<<(video::SColor src) { checkDataSize(4); @@ -530,7 +530,7 @@ SharedBuffer NetworkPacket::oldForgePacket() SharedBuffer sb(m_datasize + 2); writeU16(&sb[0], m_command); - u8* datas = getU8Ptr(0); + u8 *datas = getU8Ptr(0); if (datas != NULL) memcpy(&sb[2], datas, m_datasize); diff --git a/src/network/networkprotocol.h b/src/network/networkprotocol.h index e57a7a794..c6b3d43d0 100644 --- a/src/network/networkprotocol.h +++ b/src/network/networkprotocol.h @@ -127,9 +127,8 @@ with this program; if not, write to the Free Software Foundation, Inc., TOSERVER_SRP_BYTES_M, TOCLIENT_SRP_BYTES_S_B for the three supported auth mechanisms around srp Add new opcodes TOCLIENT_ACCEPT_SUDO_MODE and TOCLIENT_DENY_SUDO_MODE - for sudo mode handling (auth mech generic way of changing password). - Add TOCLIENT_HELLO for presenting server to client after client - presentation + for sudo mode handling (auth mech generic way of changing + password). Add TOCLIENT_HELLO for presenting server to client after client presentation Add TOCLIENT_AUTH_ACCEPT to accept connection from client Rename GENERIC_CMD_SET_ATTACHMENT to AO_CMD_ATTACH_TO PROTOCOL_VERSION 26: @@ -161,14 +160,14 @@ with this program; if not, write to the Free Software Foundation, Inc., PROTOCOL VERSION 34: Add sound pitch PROTOCOL VERSION 35: - Rename TOCLIENT_CHAT_MESSAGE to TOCLIENT_CHAT_MESSAGE_OLD (0x30) - Add TOCLIENT_CHAT_MESSAGE (0x2F) - This chat message is a signalisation message containing various + Rename TOCLIENT_CHAT_MESSAGE to TOCLIENT_CHAT_MESSAGE_OLD (0x30) + Add TOCLIENT_CHAT_MESSAGE (0x2F) + This chat message is a signalisation message containing various informations: - * timestamp - * sender - * type (RAW, NORMAL, ANNOUNCE, SYSTEM) - * content + * timestamp + * sender + * type (RAW, NORMAL, ANNOUNCE, SYSTEM) + * content Add TOCLIENT_CSM_RESTRICTION_FLAGS to define which CSM features should be limited Add settable player collisionbox. Breaks compatibility with older @@ -222,8 +221,9 @@ with this program; if not, write to the Free Software Foundation, Inc., // Constant that differentiates the protocol from random data and other protocols #define PROTOCOL_ID 0x4f457403 -#define PASSWORD_SIZE 28 // Maximum password length. Allows for - // base64-encoded SHA-1 (27+\0). +#define PASSWORD_SIZE \ + 28 // Maximum password length. Allows for + // base64-encoded SHA-1 (27+\0). /* Changes by FORMSPEC_API_VERSION: @@ -243,7 +243,8 @@ with this program; if not, write to the Free Software Foundation, Inc., */ #define FORMSPEC_API_VERSION 4 -#define TEXTURENAME_ALLOWED_CHARS "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.-" +#define TEXTURENAME_ALLOWED_CHARS \ + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.-" typedef u16 session_t; @@ -257,7 +258,8 @@ enum ToClientCommand u16 deployed network compression mode u16 deployed protocol version u32 supported auth methods - std::string username that should be used for legacy hash (for proper casing) + std::string username that should be used for legacy hash (for proper + casing) */ TOCLIENT_AUTH_ACCEPT = 0x03, /* @@ -286,7 +288,7 @@ enum ToClientCommand TOCLIENT_INIT_LEGACY = 0x10, // Obsolete - TOCLIENT_BLOCKDATA = 0x20, //TODO: Multiple blocks + TOCLIENT_BLOCKDATA = 0x20, // TODO: Multiple blocks TOCLIENT_ADDNODE = 0x21, /* v3s16 position @@ -295,10 +297,10 @@ enum ToClientCommand */ TOCLIENT_REMOVENODE = 0x22, - TOCLIENT_PLAYERPOS = 0x23, // Obsolete - TOCLIENT_PLAYERINFO = 0x24, // Obsolete + TOCLIENT_PLAYERPOS = 0x23, // Obsolete + TOCLIENT_PLAYERINFO = 0x24, // Obsolete TOCLIENT_OPT_BLOCK_NOT_FOUND = 0x25, // Obsolete - TOCLIENT_SECTORMETA = 0x26, // Obsolete + TOCLIENT_SECTORMETA = 0x26, // Obsolete TOCLIENT_INVENTORY = 0x27, /* @@ -696,8 +698,8 @@ enum ToClientCommand */ TOCLIENT_UPDATE_PLAYER_LIST = 0x56, /* - u8 type - u16 number of players + u8 type + u16 number of players for each player u16 len u8[len] player name @@ -706,18 +708,18 @@ enum ToClientCommand TOCLIENT_MODCHANNEL_MSG = 0x57, /* u16 channel name length - std::string channel name - u16 channel name sender - std::string channel name - u16 message length - std::string message + std::string channel name + u16 channel name sender + std::string channel name + u16 message length + std::string message */ TOCLIENT_MODCHANNEL_SIGNAL = 0x58, /* u8 signal id - u16 channel name length - std::string channel name + u16 channel name length + std::string channel name */ TOCLIENT_NODEMETA_CHANGED = 0x59, @@ -793,25 +795,25 @@ enum ToServerCommand TOSERVER_MODCHANNEL_JOIN = 0x17, /* u16 channel name length - std::string channel name + std::string channel name */ TOSERVER_MODCHANNEL_LEAVE = 0x18, /* u16 channel name length - std::string channel name + std::string channel name */ TOSERVER_MODCHANNEL_MSG = 0x19, /* u16 channel name length - std::string channel name - u16 message length - std::string message + std::string channel name + u16 message length + std::string message */ - TOSERVER_GETBLOCK = 0x20, // Obsolete - TOSERVER_ADDNODE = 0x21, // Obsolete + TOSERVER_GETBLOCK = 0x20, // Obsolete + TOSERVER_ADDNODE = 0x21, // Obsolete TOSERVER_REMOVENODE = 0x22, // Obsolete TOSERVER_PLAYERPOS = 0x23, @@ -845,10 +847,10 @@ enum ToServerCommand */ TOSERVER_ADDNODE_FROM_INVENTORY = 0x26, // Obsolete - TOSERVER_CLICK_OBJECT = 0x27, // Obsolete - TOSERVER_GROUND_ACTION = 0x28, // Obsolete - TOSERVER_RELEASE = 0x29, // Obsolete - TOSERVER_SIGNTEXT = 0x30, // Obsolete + TOSERVER_CLICK_OBJECT = 0x27, // Obsolete + TOSERVER_GROUND_ACTION = 0x28, // Obsolete + TOSERVER_RELEASE = 0x29, // Obsolete + TOSERVER_SIGNTEXT = 0x30, // Obsolete TOSERVER_INVENTORY_ACTION = 0x31, /* @@ -861,7 +863,7 @@ enum ToServerCommand wstring message */ - TOSERVER_SIGNNODETEXT = 0x33, // Obsolete + TOSERVER_SIGNNODETEXT = 0x33, // Obsolete TOSERVER_CLICK_ACTIVEOBJECT = 0x34, // Obsolete TOSERVER_DAMAGE = 0x35, @@ -940,7 +942,7 @@ enum ToServerCommand */ TOSERVER_RECEIVED_MEDIA = 0x41, // Obsolete - TOSERVER_BREATH = 0x42, // Obsolete + TOSERVER_BREATH = 0x42, // Obsolete TOSERVER_CLIENT_READY = 0x43, /* @@ -968,8 +970,8 @@ enum ToServerCommand std::string bytes_A u8 current_login_based_on : on which version of the password's - hash this login is based on (0 legacy hash, - or 1 directly the password) + hash this login is based on (0 legacy hash, + or 1 directly the password) */ TOSERVER_SRP_BYTES_M = 0x52, @@ -997,7 +999,8 @@ enum AuthMechanism AUTH_MECHANISM_FIRST_SRP = 1 << 2, }; -enum AccessDeniedCode { +enum AccessDeniedCode +{ SERVER_ACCESSDENIED_WRONG_PASSWORD, SERVER_ACCESSDENIED_UNEXPECTED_DATA, SERVER_ACCESSDENIED_SINGLEPLAYER, @@ -1014,54 +1017,58 @@ enum AccessDeniedCode { SERVER_ACCESSDENIED_MAX, }; -enum NetProtoCompressionMode { +enum NetProtoCompressionMode +{ NETPROTO_COMPRESSION_NONE = 0, }; const static std::string accessDeniedStrings[SERVER_ACCESSDENIED_MAX] = { - "Invalid password", - "Your client sent something the server didn't expect. Try reconnecting or updating your client", - "The server is running in simple singleplayer mode. You cannot connect.", - "Your client's version is not supported.\nPlease contact server administrator.", - "Player name contains disallowed characters.", - "Player name not allowed.", - "Too many users.", - "Empty passwords are disallowed. Set a password and try again.", - "Another client is connected with this name. If your client closed unexpectedly, try again in a minute.", - "Server authentication failed. This is likely a server error.", - "", - "Server shutting down.", - "This server has experienced an internal error. You will now be disconnected." -}; + "Invalid password", + "Your client sent something the server didn't expect. Try reconnecting " + "or updating your client", + "The server is running in simple singleplayer mode. You cannot connect.", + "Your client's version is not supported.\nPlease contact server " + "administrator.", + "Player name contains disallowed characters.", "Player name not allowed.", + "Too many users.", + "Empty passwords are disallowed. Set a password and try again.", + "Another client is connected with this name. If your client closed " + "unexpectedly, try again in a minute.", + "Server authentication failed. This is likely a server error.", "", + "Server shutting down.", + "This server has experienced an internal error. You will now be " + "disconnected."}; -enum PlayerListModifer: u8 +enum PlayerListModifer : u8 { PLAYER_LIST_INIT, PLAYER_LIST_ADD, PLAYER_LIST_REMOVE, }; -enum CSMRestrictionFlags : u64 { +enum CSMRestrictionFlags : u64 +{ CSM_RF_NONE = 0x00000000, // Until server-sent CSM and verifying of builtin are complete, // 'CSM_RF_LOAD_CLIENT_MODS' also disables loading 'builtin'. // When those are complete, this should return to only being a restriction on the // loading of client mods. - CSM_RF_LOAD_CLIENT_MODS = 0x00000001, // Don't load client-provided mods or 'builtin' - CSM_RF_CHAT_MESSAGES = 0x00000002, // Disable chat message sending from CSM - CSM_RF_READ_ITEMDEFS = 0x00000004, // Disable itemdef lookups - CSM_RF_READ_NODEDEFS = 0x00000008, // Disable nodedef lookups - CSM_RF_LOOKUP_NODES = 0x00000010, // Limit node lookups - CSM_RF_READ_PLAYERINFO = 0x00000020, // Disable player info lookups + CSM_RF_LOAD_CLIENT_MODS = + 0x00000001, // Don't load client-provided mods or 'builtin' + CSM_RF_CHAT_MESSAGES = 0x00000002, // Disable chat message sending from CSM + CSM_RF_READ_ITEMDEFS = 0x00000004, // Disable itemdef lookups + CSM_RF_READ_NODEDEFS = 0x00000008, // Disable nodedef lookups + CSM_RF_LOOKUP_NODES = 0x00000010, // Limit node lookups + CSM_RF_READ_PLAYERINFO = 0x00000020, // Disable player info lookups CSM_RF_ALL = 0xFFFFFFFF, }; enum InteractAction : u8 { - INTERACT_START_DIGGING, // 0: start digging (from undersurface) or use - INTERACT_STOP_DIGGING, // 1: stop digging (all parameters ignored) + INTERACT_START_DIGGING, // 0: start digging (from undersurface) or use + INTERACT_STOP_DIGGING, // 1: stop digging (all parameters ignored) INTERACT_DIGGING_COMPLETED, // 2: digging completed - INTERACT_PLACE, // 3: place block or item (to abovesurface) - INTERACT_USE, // 4: use item - INTERACT_ACTIVATE // 5: rightclick air ("activate") + INTERACT_PLACE, // 3: place block or item (to abovesurface) + INTERACT_USE, // 4: use item + INTERACT_ACTIVATE // 5: rightclick air ("activate") }; diff --git a/src/network/serveropcodes.cpp b/src/network/serveropcodes.cpp index 2fc3197c2..ecfac0fcd 100644 --- a/src/network/serveropcodes.cpp +++ b/src/network/serveropcodes.cpp @@ -20,96 +20,118 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "serveropcodes.h" -const static ToServerCommandHandler null_command_handler = { "TOSERVER_NULL", TOSERVER_STATE_ALL, &Server::handleCommand_Null }; +const static ToServerCommandHandler null_command_handler = { + "TOSERVER_NULL", TOSERVER_STATE_ALL, &Server::handleCommand_Null}; -const ToServerCommandHandler toServerCommandTable[TOSERVER_NUM_MSG_TYPES] = -{ - null_command_handler, // 0x00 (never use this) - null_command_handler, // 0x01 - { "TOSERVER_INIT", TOSERVER_STATE_NOT_CONNECTED, &Server::handleCommand_Init }, // 0x02 - null_command_handler, // 0x03 - null_command_handler, // 0x04 - null_command_handler, // 0x05 - null_command_handler, // 0x06 - null_command_handler, // 0x07 - null_command_handler, // 0x08 - null_command_handler, // 0x09 - null_command_handler, // 0x0a - null_command_handler, // 0x0b - null_command_handler, // 0x0c - null_command_handler, // 0x0d - null_command_handler, // 0x0e - null_command_handler, // 0x0f - null_command_handler, // 0x10 - { "TOSERVER_INIT2", TOSERVER_STATE_NOT_CONNECTED, &Server::handleCommand_Init2 }, // 0x11 - null_command_handler, // 0x12 - null_command_handler, // 0x13 - null_command_handler, // 0x14 - null_command_handler, // 0x15 - null_command_handler, // 0x16 - { "TOSERVER_MODCHANNEL_JOIN", TOSERVER_STATE_INGAME, &Server::handleCommand_ModChannelJoin }, // 0x17 - { "TOSERVER_MODCHANNEL_LEAVE", TOSERVER_STATE_INGAME, &Server::handleCommand_ModChannelLeave }, // 0x18 - { "TOSERVER_MODCHANNEL_MSG", TOSERVER_STATE_INGAME, &Server::handleCommand_ModChannelMsg }, // 0x19 - null_command_handler, // 0x1a - null_command_handler, // 0x1b - null_command_handler, // 0x1c - null_command_handler, // 0x1d - null_command_handler, // 0x1e - null_command_handler, // 0x1f - null_command_handler, // 0x20 - null_command_handler, // 0x21 - null_command_handler, // 0x22 - { "TOSERVER_PLAYERPOS", TOSERVER_STATE_INGAME, &Server::handleCommand_PlayerPos }, // 0x23 - { "TOSERVER_GOTBLOCKS", TOSERVER_STATE_STARTUP, &Server::handleCommand_GotBlocks }, // 0x24 - { "TOSERVER_DELETEDBLOCKS", TOSERVER_STATE_INGAME, &Server::handleCommand_DeletedBlocks }, // 0x25 - null_command_handler, // 0x26 - null_command_handler, // 0x27 - null_command_handler, // 0x28 - null_command_handler, // 0x29 - null_command_handler, // 0x2a - null_command_handler, // 0x2b - null_command_handler, // 0x2c - null_command_handler, // 0x2d - null_command_handler, // 0x2e - null_command_handler, // 0x2f - null_command_handler, // 0x30 - { "TOSERVER_INVENTORY_ACTION", TOSERVER_STATE_INGAME, &Server::handleCommand_InventoryAction }, // 0x31 - { "TOSERVER_CHAT_MESSAGE", TOSERVER_STATE_INGAME, &Server::handleCommand_ChatMessage }, // 0x32 - null_command_handler, // 0x33 - null_command_handler, // 0x34 - { "TOSERVER_DAMAGE", TOSERVER_STATE_INGAME, &Server::handleCommand_Damage }, // 0x35 - null_command_handler, // 0x36 - { "TOSERVER_PLAYERITEM", TOSERVER_STATE_INGAME, &Server::handleCommand_PlayerItem }, // 0x37 - { "TOSERVER_RESPAWN", TOSERVER_STATE_INGAME, &Server::handleCommand_Respawn }, // 0x38 - { "TOSERVER_INTERACT", TOSERVER_STATE_INGAME, &Server::handleCommand_Interact }, // 0x39 - { "TOSERVER_REMOVED_SOUNDS", TOSERVER_STATE_INGAME, &Server::handleCommand_RemovedSounds }, // 0x3a - { "TOSERVER_NODEMETA_FIELDS", TOSERVER_STATE_INGAME, &Server::handleCommand_NodeMetaFields }, // 0x3b - { "TOSERVER_INVENTORY_FIELDS", TOSERVER_STATE_INGAME, &Server::handleCommand_InventoryFields }, // 0x3c - null_command_handler, // 0x3d - null_command_handler, // 0x3e - null_command_handler, // 0x3f - { "TOSERVER_REQUEST_MEDIA", TOSERVER_STATE_STARTUP, &Server::handleCommand_RequestMedia }, // 0x40 - null_command_handler, // 0x41 - null_command_handler, // 0x42 - { "TOSERVER_CLIENT_READY", TOSERVER_STATE_STARTUP, &Server::handleCommand_ClientReady }, // 0x43 - null_command_handler, // 0x44 - null_command_handler, // 0x45 - null_command_handler, // 0x46 - null_command_handler, // 0x47 - null_command_handler, // 0x48 - null_command_handler, // 0x49 - null_command_handler, // 0x4a - null_command_handler, // 0x4b - null_command_handler, // 0x4c - null_command_handler, // 0x4d - null_command_handler, // 0x4e - null_command_handler, // 0x4f - { "TOSERVER_FIRST_SRP", TOSERVER_STATE_NOT_CONNECTED, &Server::handleCommand_FirstSrp }, // 0x50 - { "TOSERVER_SRP_BYTES_A", TOSERVER_STATE_NOT_CONNECTED, &Server::handleCommand_SrpBytesA }, // 0x51 - { "TOSERVER_SRP_BYTES_M", TOSERVER_STATE_NOT_CONNECTED, &Server::handleCommand_SrpBytesM }, // 0x52 +const ToServerCommandHandler toServerCommandTable[TOSERVER_NUM_MSG_TYPES] = { + null_command_handler, // 0x00 (never use this) + null_command_handler, // 0x01 + {"TOSERVER_INIT", TOSERVER_STATE_NOT_CONNECTED, + &Server::handleCommand_Init}, // 0x02 + null_command_handler, // 0x03 + null_command_handler, // 0x04 + null_command_handler, // 0x05 + null_command_handler, // 0x06 + null_command_handler, // 0x07 + null_command_handler, // 0x08 + null_command_handler, // 0x09 + null_command_handler, // 0x0a + null_command_handler, // 0x0b + null_command_handler, // 0x0c + null_command_handler, // 0x0d + null_command_handler, // 0x0e + null_command_handler, // 0x0f + null_command_handler, // 0x10 + {"TOSERVER_INIT2", TOSERVER_STATE_NOT_CONNECTED, + &Server::handleCommand_Init2}, // 0x11 + null_command_handler, // 0x12 + null_command_handler, // 0x13 + null_command_handler, // 0x14 + null_command_handler, // 0x15 + null_command_handler, // 0x16 + {"TOSERVER_MODCHANNEL_JOIN", TOSERVER_STATE_INGAME, + &Server::handleCommand_ModChannelJoin}, // 0x17 + {"TOSERVER_MODCHANNEL_LEAVE", TOSERVER_STATE_INGAME, + &Server::handleCommand_ModChannelLeave}, // 0x18 + {"TOSERVER_MODCHANNEL_MSG", TOSERVER_STATE_INGAME, + &Server::handleCommand_ModChannelMsg}, // 0x19 + null_command_handler, // 0x1a + null_command_handler, // 0x1b + null_command_handler, // 0x1c + null_command_handler, // 0x1d + null_command_handler, // 0x1e + null_command_handler, // 0x1f + null_command_handler, // 0x20 + null_command_handler, // 0x21 + null_command_handler, // 0x22 + {"TOSERVER_PLAYERPOS", TOSERVER_STATE_INGAME, + &Server::handleCommand_PlayerPos}, // 0x23 + {"TOSERVER_GOTBLOCKS", TOSERVER_STATE_STARTUP, + &Server::handleCommand_GotBlocks}, // 0x24 + {"TOSERVER_DELETEDBLOCKS", TOSERVER_STATE_INGAME, + &Server::handleCommand_DeletedBlocks}, // 0x25 + null_command_handler, // 0x26 + null_command_handler, // 0x27 + null_command_handler, // 0x28 + null_command_handler, // 0x29 + null_command_handler, // 0x2a + null_command_handler, // 0x2b + null_command_handler, // 0x2c + null_command_handler, // 0x2d + null_command_handler, // 0x2e + null_command_handler, // 0x2f + null_command_handler, // 0x30 + {"TOSERVER_INVENTORY_ACTION", TOSERVER_STATE_INGAME, + &Server::handleCommand_InventoryAction}, // 0x31 + {"TOSERVER_CHAT_MESSAGE", TOSERVER_STATE_INGAME, + &Server::handleCommand_ChatMessage}, // 0x32 + null_command_handler, // 0x33 + null_command_handler, // 0x34 + {"TOSERVER_DAMAGE", TOSERVER_STATE_INGAME, + &Server::handleCommand_Damage}, // 0x35 + null_command_handler, // 0x36 + {"TOSERVER_PLAYERITEM", TOSERVER_STATE_INGAME, + &Server::handleCommand_PlayerItem}, // 0x37 + {"TOSERVER_RESPAWN", TOSERVER_STATE_INGAME, + &Server::handleCommand_Respawn}, // 0x38 + {"TOSERVER_INTERACT", TOSERVER_STATE_INGAME, + &Server::handleCommand_Interact}, // 0x39 + {"TOSERVER_REMOVED_SOUNDS", TOSERVER_STATE_INGAME, + &Server::handleCommand_RemovedSounds}, // 0x3a + {"TOSERVER_NODEMETA_FIELDS", TOSERVER_STATE_INGAME, + &Server::handleCommand_NodeMetaFields}, // 0x3b + {"TOSERVER_INVENTORY_FIELDS", TOSERVER_STATE_INGAME, + &Server::handleCommand_InventoryFields}, // 0x3c + null_command_handler, // 0x3d + null_command_handler, // 0x3e + null_command_handler, // 0x3f + {"TOSERVER_REQUEST_MEDIA", TOSERVER_STATE_STARTUP, + &Server::handleCommand_RequestMedia}, // 0x40 + null_command_handler, // 0x41 + null_command_handler, // 0x42 + {"TOSERVER_CLIENT_READY", TOSERVER_STATE_STARTUP, + &Server::handleCommand_ClientReady}, // 0x43 + null_command_handler, // 0x44 + null_command_handler, // 0x45 + null_command_handler, // 0x46 + null_command_handler, // 0x47 + null_command_handler, // 0x48 + null_command_handler, // 0x49 + null_command_handler, // 0x4a + null_command_handler, // 0x4b + null_command_handler, // 0x4c + null_command_handler, // 0x4d + null_command_handler, // 0x4e + null_command_handler, // 0x4f + {"TOSERVER_FIRST_SRP", TOSERVER_STATE_NOT_CONNECTED, + &Server::handleCommand_FirstSrp}, // 0x50 + {"TOSERVER_SRP_BYTES_A", TOSERVER_STATE_NOT_CONNECTED, + &Server::handleCommand_SrpBytesA}, // 0x51 + {"TOSERVER_SRP_BYTES_M", TOSERVER_STATE_NOT_CONNECTED, + &Server::handleCommand_SrpBytesM}, // 0x52 }; -const static ClientCommandFactory null_command_factory = { "TOCLIENT_NULL", 0, false }; +const static ClientCommandFactory null_command_factory = {"TOCLIENT_NULL", 0, false}; /* Channels used for Server -> Client communication @@ -121,104 +143,104 @@ const static ClientCommandFactory null_command_factory = { "TOCLIENT_NULL", 0, f the same objects are *required* to be in the same channel. */ -const ClientCommandFactory clientCommandFactoryTable[TOCLIENT_NUM_MSG_TYPES] = -{ - null_command_factory, // 0x00 - null_command_factory, // 0x01 - { "TOCLIENT_HELLO", 0, true }, // 0x02 - { "TOCLIENT_AUTH_ACCEPT", 0, true }, // 0x03 - { "TOCLIENT_ACCEPT_SUDO_MODE", 0, true }, // 0x04 - { "TOCLIENT_DENY_SUDO_MODE", 0, true }, // 0x05 - null_command_factory, // 0x06 - null_command_factory, // 0x07 - null_command_factory, // 0x08 - null_command_factory, // 0x09 - { "TOCLIENT_ACCESS_DENIED", 0, true }, // 0x0A - null_command_factory, // 0x0B - null_command_factory, // 0x0C - null_command_factory, // 0x0D - null_command_factory, // 0x0E - null_command_factory, // 0x0F - { "TOCLIENT_INIT", 0, true }, // 0x10 - null_command_factory, // 0x11 - null_command_factory, // 0x12 - null_command_factory, // 0x13 - null_command_factory, // 0x14 - null_command_factory, // 0x15 - null_command_factory, // 0x16 - null_command_factory, // 0x17 - null_command_factory, // 0x18 - null_command_factory, // 0x19 - null_command_factory, // 0x1A - null_command_factory, // 0x1B - null_command_factory, // 0x1C - null_command_factory, // 0x1D - null_command_factory, // 0x1E - null_command_factory, // 0x1F - { "TOCLIENT_BLOCKDATA", 2, true }, // 0x20 - { "TOCLIENT_ADDNODE", 0, true }, // 0x21 - { "TOCLIENT_REMOVENODE", 0, true }, // 0x22 - null_command_factory, // 0x23 - null_command_factory, // 0x24 - null_command_factory, // 0x25 - null_command_factory, // 0x26 - { "TOCLIENT_INVENTORY", 0, true }, // 0x27 - null_command_factory, // 0x28 - { "TOCLIENT_TIME_OF_DAY", 0, true }, // 0x29 - { "TOCLIENT_CSM_RESTRICTION_FLAGS", 0, true }, // 0x2A - { "TOCLIENT_PLAYER_SPEED", 0, true }, // 0x2B - { "TOCLIENT_MEDIA_PUSH", 0, true }, // 0x2C (sent over channel 1 too) - null_command_factory, // 0x2D - null_command_factory, // 0x2E - { "TOCLIENT_CHAT_MESSAGE", 0, true }, // 0x2F - null_command_factory, // 0x30 - { "TOCLIENT_ACTIVE_OBJECT_REMOVE_ADD", 0, true }, // 0x31 - { "TOCLIENT_ACTIVE_OBJECT_MESSAGES", 0, true }, // 0x32 (may be sent as unrel over channel 1 too) - { "TOCLIENT_HP", 0, true }, // 0x33 - { "TOCLIENT_MOVE_PLAYER", 0, true }, // 0x34 - { "TOCLIENT_ACCESS_DENIED_LEGACY", 0, true }, // 0x35 - { "TOCLIENT_FOV", 0, true }, // 0x36 - { "TOCLIENT_DEATHSCREEN", 0, true }, // 0x37 - { "TOCLIENT_MEDIA", 2, true }, // 0x38 - null_command_factory, // 0x39 - { "TOCLIENT_NODEDEF", 0, true }, // 0x3A - null_command_factory, // 0x3B - { "TOCLIENT_ANNOUNCE_MEDIA", 0, true }, // 0x3C - { "TOCLIENT_ITEMDEF", 0, true }, // 0x3D - null_command_factory, // 0x3E - { "TOCLIENT_PLAY_SOUND", 0, true }, // 0x3f (may be sent as unrel too) - { "TOCLIENT_STOP_SOUND", 0, true }, // 0x40 - { "TOCLIENT_PRIVILEGES", 0, true }, // 0x41 - { "TOCLIENT_INVENTORY_FORMSPEC", 0, true }, // 0x42 - { "TOCLIENT_DETACHED_INVENTORY", 0, true }, // 0x43 - { "TOCLIENT_SHOW_FORMSPEC", 0, true }, // 0x44 - { "TOCLIENT_MOVEMENT", 0, true }, // 0x45 - { "TOCLIENT_SPAWN_PARTICLE", 0, true }, // 0x46 - { "TOCLIENT_ADD_PARTICLESPAWNER", 0, true }, // 0x47 - null_command_factory, // 0x48 - { "TOCLIENT_HUDADD", 1, true }, // 0x49 - { "TOCLIENT_HUDRM", 1, true }, // 0x4a - { "TOCLIENT_HUDCHANGE", 1, true }, // 0x4b - { "TOCLIENT_HUD_SET_FLAGS", 1, true }, // 0x4c - { "TOCLIENT_HUD_SET_PARAM", 1, true }, // 0x4d - { "TOCLIENT_BREATH", 0, true }, // 0x4e - { "TOCLIENT_SET_SKY", 0, true }, // 0x4f - { "TOCLIENT_OVERRIDE_DAY_NIGHT_RATIO", 0, true }, // 0x50 - { "TOCLIENT_LOCAL_PLAYER_ANIMATIONS", 0, true }, // 0x51 - { "TOCLIENT_EYE_OFFSET", 0, true }, // 0x52 - { "TOCLIENT_DELETE_PARTICLESPAWNER", 0, true }, // 0x53 - { "TOCLIENT_CLOUD_PARAMS", 0, true }, // 0x54 - { "TOCLIENT_FADE_SOUND", 0, true }, // 0x55 - { "TOCLIENT_UPDATE_PLAYER_LIST", 0, true }, // 0x56 - { "TOCLIENT_MODCHANNEL_MSG", 0, true }, // 0x57 - { "TOCLIENT_MODCHANNEL_SIGNAL", 0, true }, // 0x58 - { "TOCLIENT_NODEMETA_CHANGED", 0, true }, // 0x59 - { "TOCLIENT_SET_SUN", 0, true }, // 0x5a - { "TOCLIENT_SET_MOON", 0, true }, // 0x5b - { "TOCLIENT_SET_STARS", 0, true }, // 0x5c - null_command_factory, // 0x5d - null_command_factory, // 0x5e - null_command_factory, // 0x5f - { "TOSERVER_SRP_BYTES_S_B", 0, true }, // 0x60 - { "TOCLIENT_FORMSPEC_PREPEND", 0, true }, // 0x61 +const ClientCommandFactory clientCommandFactoryTable[TOCLIENT_NUM_MSG_TYPES] = { + null_command_factory, // 0x00 + null_command_factory, // 0x01 + {"TOCLIENT_HELLO", 0, true}, // 0x02 + {"TOCLIENT_AUTH_ACCEPT", 0, true}, // 0x03 + {"TOCLIENT_ACCEPT_SUDO_MODE", 0, true}, // 0x04 + {"TOCLIENT_DENY_SUDO_MODE", 0, true}, // 0x05 + null_command_factory, // 0x06 + null_command_factory, // 0x07 + null_command_factory, // 0x08 + null_command_factory, // 0x09 + {"TOCLIENT_ACCESS_DENIED", 0, true}, // 0x0A + null_command_factory, // 0x0B + null_command_factory, // 0x0C + null_command_factory, // 0x0D + null_command_factory, // 0x0E + null_command_factory, // 0x0F + {"TOCLIENT_INIT", 0, true}, // 0x10 + null_command_factory, // 0x11 + null_command_factory, // 0x12 + null_command_factory, // 0x13 + null_command_factory, // 0x14 + null_command_factory, // 0x15 + null_command_factory, // 0x16 + null_command_factory, // 0x17 + null_command_factory, // 0x18 + null_command_factory, // 0x19 + null_command_factory, // 0x1A + null_command_factory, // 0x1B + null_command_factory, // 0x1C + null_command_factory, // 0x1D + null_command_factory, // 0x1E + null_command_factory, // 0x1F + {"TOCLIENT_BLOCKDATA", 2, true}, // 0x20 + {"TOCLIENT_ADDNODE", 0, true}, // 0x21 + {"TOCLIENT_REMOVENODE", 0, true}, // 0x22 + null_command_factory, // 0x23 + null_command_factory, // 0x24 + null_command_factory, // 0x25 + null_command_factory, // 0x26 + {"TOCLIENT_INVENTORY", 0, true}, // 0x27 + null_command_factory, // 0x28 + {"TOCLIENT_TIME_OF_DAY", 0, true}, // 0x29 + {"TOCLIENT_CSM_RESTRICTION_FLAGS", 0, true}, // 0x2A + {"TOCLIENT_PLAYER_SPEED", 0, true}, // 0x2B + {"TOCLIENT_MEDIA_PUSH", 0, true}, // 0x2C (sent over channel 1 too) + null_command_factory, // 0x2D + null_command_factory, // 0x2E + {"TOCLIENT_CHAT_MESSAGE", 0, true}, // 0x2F + null_command_factory, // 0x30 + {"TOCLIENT_ACTIVE_OBJECT_REMOVE_ADD", 0, true}, // 0x31 + {"TOCLIENT_ACTIVE_OBJECT_MESSAGES", 0, + true}, // 0x32 (may be sent as unrel over channel 1 too) + {"TOCLIENT_HP", 0, true}, // 0x33 + {"TOCLIENT_MOVE_PLAYER", 0, true}, // 0x34 + {"TOCLIENT_ACCESS_DENIED_LEGACY", 0, true}, // 0x35 + {"TOCLIENT_FOV", 0, true}, // 0x36 + {"TOCLIENT_DEATHSCREEN", 0, true}, // 0x37 + {"TOCLIENT_MEDIA", 2, true}, // 0x38 + null_command_factory, // 0x39 + {"TOCLIENT_NODEDEF", 0, true}, // 0x3A + null_command_factory, // 0x3B + {"TOCLIENT_ANNOUNCE_MEDIA", 0, true}, // 0x3C + {"TOCLIENT_ITEMDEF", 0, true}, // 0x3D + null_command_factory, // 0x3E + {"TOCLIENT_PLAY_SOUND", 0, true}, // 0x3f (may be sent as unrel too) + {"TOCLIENT_STOP_SOUND", 0, true}, // 0x40 + {"TOCLIENT_PRIVILEGES", 0, true}, // 0x41 + {"TOCLIENT_INVENTORY_FORMSPEC", 0, true}, // 0x42 + {"TOCLIENT_DETACHED_INVENTORY", 0, true}, // 0x43 + {"TOCLIENT_SHOW_FORMSPEC", 0, true}, // 0x44 + {"TOCLIENT_MOVEMENT", 0, true}, // 0x45 + {"TOCLIENT_SPAWN_PARTICLE", 0, true}, // 0x46 + {"TOCLIENT_ADD_PARTICLESPAWNER", 0, true}, // 0x47 + null_command_factory, // 0x48 + {"TOCLIENT_HUDADD", 1, true}, // 0x49 + {"TOCLIENT_HUDRM", 1, true}, // 0x4a + {"TOCLIENT_HUDCHANGE", 1, true}, // 0x4b + {"TOCLIENT_HUD_SET_FLAGS", 1, true}, // 0x4c + {"TOCLIENT_HUD_SET_PARAM", 1, true}, // 0x4d + {"TOCLIENT_BREATH", 0, true}, // 0x4e + {"TOCLIENT_SET_SKY", 0, true}, // 0x4f + {"TOCLIENT_OVERRIDE_DAY_NIGHT_RATIO", 0, true}, // 0x50 + {"TOCLIENT_LOCAL_PLAYER_ANIMATIONS", 0, true}, // 0x51 + {"TOCLIENT_EYE_OFFSET", 0, true}, // 0x52 + {"TOCLIENT_DELETE_PARTICLESPAWNER", 0, true}, // 0x53 + {"TOCLIENT_CLOUD_PARAMS", 0, true}, // 0x54 + {"TOCLIENT_FADE_SOUND", 0, true}, // 0x55 + {"TOCLIENT_UPDATE_PLAYER_LIST", 0, true}, // 0x56 + {"TOCLIENT_MODCHANNEL_MSG", 0, true}, // 0x57 + {"TOCLIENT_MODCHANNEL_SIGNAL", 0, true}, // 0x58 + {"TOCLIENT_NODEMETA_CHANGED", 0, true}, // 0x59 + {"TOCLIENT_SET_SUN", 0, true}, // 0x5a + {"TOCLIENT_SET_MOON", 0, true}, // 0x5b + {"TOCLIENT_SET_STARS", 0, true}, // 0x5c + null_command_factory, // 0x5d + null_command_factory, // 0x5e + null_command_factory, // 0x5f + {"TOSERVER_SRP_BYTES_S_B", 0, true}, // 0x60 + {"TOCLIENT_FORMSPEC_PREPEND", 0, true}, // 0x61 }; diff --git a/src/network/serveropcodes.h b/src/network/serveropcodes.h index 6df09d5ef..9d3c1ab9e 100644 --- a/src/network/serveropcodes.h +++ b/src/network/serveropcodes.h @@ -25,7 +25,8 @@ with this program; if not, write to the Free Software Foundation, Inc., class NetworkPacket; -enum ToServerConnectionState { +enum ToServerConnectionState +{ TOSERVER_STATE_NOT_CONNECTED, TOSERVER_STATE_STARTUP, TOSERVER_STATE_INGAME, @@ -33,14 +34,14 @@ enum ToServerConnectionState { }; struct ToServerCommandHandler { - const std::string name; - ToServerConnectionState state; - void (Server::*handler)(NetworkPacket* pkt); + const std::string name; + ToServerConnectionState state; + void (Server::*handler)(NetworkPacket *pkt); }; struct ClientCommandFactory { - const char* name; + const char *name; u8 channel; bool reliable; }; diff --git a/src/network/serverpackethandler.cpp b/src/network/serverpackethandler.cpp index b3008bb50..2b4c5c718 100644 --- a/src/network/serverpackethandler.cpp +++ b/src/network/serverpackethandler.cpp @@ -41,16 +41,16 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/serialize.h" #include "util/srp.h" -void Server::handleCommand_Deprecated(NetworkPacket* pkt) +void Server::handleCommand_Deprecated(NetworkPacket *pkt) { infostream << "Server: " << toServerCommandTable[pkt->getCommand()].name - << " not supported anymore" << std::endl; + << " not supported anymore" << std::endl; } -void Server::handleCommand_Init(NetworkPacket* pkt) +void Server::handleCommand_Init(NetworkPacket *pkt) { - if(pkt->getSize() < 1) + if (pkt->getSize() < 1) return; session_t peer_id = pkt->getPeerId(); @@ -60,34 +60,33 @@ void Server::handleCommand_Init(NetworkPacket* pkt) try { Address address = getPeerAddress(peer_id); addr_s = address.serializeString(); - } - catch (con::PeerNotFoundException &e) { + } catch (con::PeerNotFoundException &e) { /* * no peer for this packet found * most common reason is peer timeout, e.g. peer didn't * respond for some time, your server was overloaded or * things like that. */ - infostream << "Server::ProcessData(): Canceling: peer " << peer_id << - " not found" << std::endl; + infostream << "Server::ProcessData(): Canceling: peer " << peer_id + << " not found" << std::endl; return; } // If net_proto_version is set, this client has already been handled if (client->getState() > CS_Created) { - verbosestream << "Server: Ignoring multiple TOSERVER_INITs from " << - addr_s << " (peer_id=" << peer_id << ")" << std::endl; + verbosestream << "Server: Ignoring multiple TOSERVER_INITs from " + << addr_s << " (peer_id=" << peer_id << ")" << std::endl; return; } - verbosestream << "Server: Got TOSERVER_INIT from " << addr_s << - " (peer_id=" << peer_id << ")" << std::endl; + verbosestream << "Server: Got TOSERVER_INIT from " << addr_s + << " (peer_id=" << peer_id << ")" << std::endl; // Do not allow multiple players in simple singleplayer mode. // This isn't a perfect way to do it, but will suffice for now if (m_simple_singleplayer_mode && m_clients.getClientIDs().size() > 1) { - infostream << "Server: Not allowing another client (" << addr_s << - ") to connect in simple singleplayer mode" << std::endl; + infostream << "Server: Not allowing another client (" << addr_s + << ") to connect in simple singleplayer mode" << std::endl; DenyAccess(peer_id, SERVER_ACCESSDENIED_SINGLEPLAYER); return; } @@ -100,8 +99,8 @@ void Server::handleCommand_Init(NetworkPacket* pkt) u16 max_net_proto_version; std::string playerName; - *pkt >> client_max >> supp_compr_modes >> min_net_proto_version - >> max_net_proto_version >> playerName; + *pkt >> client_max >> supp_compr_modes >> min_net_proto_version >> + max_net_proto_version >> playerName; u8 our_max = SER_FMT_VER_HIGHEST_READ; // Use the highest version supported by both @@ -111,8 +110,8 @@ void Server::handleCommand_Init(NetworkPacket* pkt) depl_serial_v = SER_FMT_VER_INVALID; if (depl_serial_v == SER_FMT_VER_INVALID) { - actionstream << "Server: A mismatched client tried to connect from " << - addr_s << " ser_fmt_max=" << (int)client_max << std::endl; + actionstream << "Server: A mismatched client tried to connect from " + << addr_s << " ser_fmt_max=" << (int)client_max << std::endl; DenyAccess(peer_id, SERVER_ACCESSDENIED_WRONG_VERSION); return; } @@ -136,18 +135,20 @@ void Server::handleCommand_Init(NetworkPacket* pkt) net_proto_version = max_net_proto_version; } - verbosestream << "Server: " << addr_s << ": Protocol version: min: " - << min_net_proto_version << ", max: " << max_net_proto_version - << ", chosen: " << net_proto_version << std::endl; + verbosestream << "Server: " << addr_s + << ": Protocol version: min: " << min_net_proto_version + << ", max: " << max_net_proto_version + << ", chosen: " << net_proto_version << std::endl; client->net_proto_version = net_proto_version; if ((g_settings->getBool("strict_protocol_version_checking") && - net_proto_version != LATEST_PROTOCOL_VERSION) || + net_proto_version != LATEST_PROTOCOL_VERSION) || net_proto_version < SERVER_PROTOCOL_VERSION_MIN || net_proto_version > SERVER_PROTOCOL_VERSION_MAX) { - actionstream << "Server: A mismatched client tried to connect from " << - addr_s << " proto_max=" << (int)max_net_proto_version << std::endl; + actionstream << "Server: A mismatched client tried to connect from " + << addr_s << " proto_max=" << (int)max_net_proto_version + << std::endl; DenyAccess(peer_id, SERVER_ACCESSDENIED_WRONG_VERSION); return; } @@ -155,32 +156,34 @@ void Server::handleCommand_Init(NetworkPacket* pkt) /* Validate player name */ - const char* playername = playerName.c_str(); + const char *playername = playerName.c_str(); size_t pns = playerName.size(); if (pns == 0 || pns > PLAYERNAME_SIZE) { - actionstream << "Server: Player with " << - ((pns > PLAYERNAME_SIZE) ? "a too long" : "an empty") << - " name tried to connect from " << addr_s << std::endl; + actionstream << "Server: Player with " + << ((pns > PLAYERNAME_SIZE) ? "a too long" : "an empty") + << " name tried to connect from " << addr_s << std::endl; DenyAccess(peer_id, SERVER_ACCESSDENIED_WRONG_NAME); return; } if (!string_allowed(playerName, PLAYERNAME_ALLOWED_CHARS)) { actionstream << "Server: Player with an invalid name tried to connect " - "from " << addr_s << std::endl; + "from " + << addr_s << std::endl; DenyAccess(peer_id, SERVER_ACCESSDENIED_WRONG_CHARS_IN_NAME); return; } m_clients.setPlayerName(peer_id, playername); - //TODO (later) case insensitivity + // TODO (later) case insensitivity std::string legacyPlayerNameCasing = playerName; if (!isSingleplayer() && strcasecmp(playername, "singleplayer") == 0) { actionstream << "Server: Player with the name \"singleplayer\" tried " - "to connect from " << addr_s << std::endl; + "to connect from " + << addr_s << std::endl; DenyAccess(peer_id, SERVER_ACCESSDENIED_WRONG_NAME); return; } @@ -188,26 +191,27 @@ void Server::handleCommand_Init(NetworkPacket* pkt) { std::string reason; if (m_script->on_prejoinplayer(playername, addr_s, &reason)) { - actionstream << "Server: Player with the name \"" << playerName << - "\" tried to connect from " << addr_s << - " but it was disallowed for the following reason: " << reason << - std::endl; + actionstream << "Server: Player with the name \"" << playerName + << "\" tried to connect from " << addr_s + << " but it was disallowed for the following " + "reason: " + << reason << std::endl; DenyAccess(peer_id, SERVER_ACCESSDENIED_CUSTOM_STRING, reason); return; } } - infostream << "Server: New connection: \"" << playerName << "\" from " << - addr_s << " (peer_id=" << peer_id << ")" << std::endl; + infostream << "Server: New connection: \"" << playerName << "\" from " << addr_s + << " (peer_id=" << peer_id << ")" << std::endl; // Enforce user limit. // Don't enforce for users that have some admin right or mod permits it. - if (m_clients.isUserLimitReached() && - playername != g_settings->get("name") && + if (m_clients.isUserLimitReached() && playername != g_settings->get("name") && !m_script->can_bypass_userlimit(playername, addr_s)) { - actionstream << "Server: " << playername << " tried to join from " << - addr_s << ", but there are already max_users=" << - g_settings->getU16("max_users") << " players." << std::endl; + actionstream << "Server: " << playername << " tried to join from " + << addr_s << ", but there are already max_users=" + << g_settings->getU16("max_users") << " players." + << std::endl; DenyAccess(peer_id, SERVER_ACCESSDENIED_TOO_MANY_USERS); return; } @@ -228,9 +232,11 @@ void Server::handleCommand_Init(NetworkPacket* pkt) auth_mechs |= AUTH_MECHANISM_SRP; client->enc_pwd = encpwd; } else { - actionstream << "User " << playername << " tried to log in, " - "but password field was invalid (unknown mechcode)." << - std::endl; + actionstream << "User " << playername + << " tried to log in, " + "but password field was invalid (unknown " + "mechcode)." + << std::endl; DenyAccess(peer_id, SERVER_ACCESSDENIED_SERVER_FAIL); return; } @@ -238,8 +244,10 @@ void Server::handleCommand_Init(NetworkPacket* pkt) auth_mechs |= AUTH_MECHANISM_LEGACY_PASSWORD; client->enc_pwd = encpwd; } else { - actionstream << "User " << playername << " tried to log in, but " - "password field was invalid (invalid base64)." << std::endl; + actionstream << "User " << playername + << " tried to log in, but " + "password field was invalid (invalid base64)." + << std::endl; DenyAccess(peer_id, SERVER_ACCESSDENIED_SERVER_FAIL); return; } @@ -249,7 +257,8 @@ void Server::handleCommand_Init(NetworkPacket* pkt) auth_mechs |= AUTH_MECHANISM_FIRST_SRP; } else { // Take care of default passwords. - client->enc_pwd = get_encoded_srp_verifier(playerName, default_password); + client->enc_pwd = get_encoded_srp_verifier( + playerName, default_password); auth_mechs |= AUTH_MECHANISM_SRP; // Allocate player in db, but only on successful login. client->create_player_on_auth_success = true; @@ -260,15 +269,15 @@ void Server::handleCommand_Init(NetworkPacket* pkt) Answer with a TOCLIENT_HELLO */ - verbosestream << "Sending TOCLIENT_HELLO with auth method field: " - << auth_mechs << std::endl; + verbosestream << "Sending TOCLIENT_HELLO with auth method field: " << auth_mechs + << std::endl; - NetworkPacket resp_pkt(TOCLIENT_HELLO, - 1 + 4 + legacyPlayerNameCasing.size(), peer_id); + NetworkPacket resp_pkt( + TOCLIENT_HELLO, 1 + 4 + legacyPlayerNameCasing.size(), peer_id); u16 depl_compress_mode = NETPROTO_COMPRESSION_NONE; - resp_pkt << depl_serial_v << depl_compress_mode << net_proto_version - << auth_mechs << legacyPlayerNameCasing; + resp_pkt << depl_serial_v << depl_compress_mode << net_proto_version << auth_mechs + << legacyPlayerNameCasing; Send(&resp_pkt); @@ -278,7 +287,7 @@ void Server::handleCommand_Init(NetworkPacket* pkt) m_clients.event(peer_id, CSE_Hello); } -void Server::handleCommand_Init2(NetworkPacket* pkt) +void Server::handleCommand_Init2(NetworkPacket *pkt) { session_t peer_id = pkt->getPeerId(); verbosestream << "Server: Got TOSERVER_INIT2 from " << peer_id << std::endl; @@ -294,8 +303,8 @@ void Server::handleCommand_Init2(NetworkPacket* pkt) Send some initialization data */ - infostream << "Server: Sending content to " << getPlayerName(peer_id) << - std::endl; + infostream << "Server: Sending content to " << getPlayerName(peer_id) + << std::endl; // Send item definitions SendItemDef(peer_id, m_itemdef, protocol_version); @@ -335,13 +344,15 @@ void Server::handleCommand_Init2(NetworkPacket* pkt) // Warnings about protocol version can be issued here if (client->net_proto_version < LATEST_PROTOCOL_VERSION) { - SendChatMessage(peer_id, ChatMessage(CHATMESSAGE_TYPE_SYSTEM, - L"# Server: WARNING: YOUR CLIENT'S VERSION MAY NOT BE FULLY COMPATIBLE " - L"WITH THIS SERVER!")); + SendChatMessage(peer_id, + ChatMessage(CHATMESSAGE_TYPE_SYSTEM, + L"# Server: WARNING: YOUR CLIENT'S " + L"VERSION MAY NOT BE FULLY COMPATIBLE " + L"WITH THIS SERVER!")); } } -void Server::handleCommand_RequestMedia(NetworkPacket* pkt) +void Server::handleCommand_RequestMedia(NetworkPacket *pkt) { std::vector tosend; u16 numfiles; @@ -349,8 +360,8 @@ void Server::handleCommand_RequestMedia(NetworkPacket* pkt) *pkt >> numfiles; session_t peer_id = pkt->getPeerId(); - infostream << "Sending " << numfiles << " files to " << - getPlayerName(peer_id) << std::endl; + infostream << "Sending " << numfiles << " files to " << getPlayerName(peer_id) + << std::endl; verbosestream << "TOSERVER_REQUEST_MEDIA: " << std::endl; for (u16 i = 0; i < numfiles; i++) { @@ -359,30 +370,31 @@ void Server::handleCommand_RequestMedia(NetworkPacket* pkt) *pkt >> name; tosend.push_back(name); - verbosestream << "TOSERVER_REQUEST_MEDIA: requested file " - << name << std::endl; + verbosestream << "TOSERVER_REQUEST_MEDIA: requested file " << name + << std::endl; } sendRequestedMedia(peer_id, tosend); } -void Server::handleCommand_ClientReady(NetworkPacket* pkt) +void Server::handleCommand_ClientReady(NetworkPacket *pkt) { session_t peer_id = pkt->getPeerId(); - PlayerSAO* playersao = StageTwoClientInit(peer_id); + PlayerSAO *playersao = StageTwoClientInit(peer_id); if (playersao == NULL) { errorstream << "TOSERVER_CLIENT_READY stage 2 client init failed " - "peer_id=" << peer_id << std::endl; + "peer_id=" + << peer_id << std::endl; DisconnectPeer(peer_id); return; } - if (pkt->getSize() < 8) { errorstream << "TOSERVER_CLIENT_READY client sent inconsistent data, " - "disconnecting peer_id: " << peer_id << std::endl; + "disconnecting peer_id: " + << peer_id << std::endl; DisconnectPeer(peer_id); return; } @@ -391,28 +403,29 @@ void Server::handleCommand_ClientReady(NetworkPacket* pkt) std::string full_ver; *pkt >> major_ver >> minor_ver >> patch_ver >> reserved >> full_ver; - m_clients.setClientVersion(peer_id, major_ver, minor_ver, patch_ver, - full_ver); + m_clients.setClientVersion(peer_id, major_ver, minor_ver, patch_ver, full_ver); if (pkt->getRemainingBytes() >= 2) *pkt >> playersao->getPlayer()->formspec_version; const std::vector &players = m_clients.getPlayerNames(); NetworkPacket list_pkt(TOCLIENT_UPDATE_PLAYER_LIST, 0, peer_id); - list_pkt << (u8) PLAYER_LIST_INIT << (u16) players.size(); - for (const std::string &player: players) { - list_pkt << player; + list_pkt << (u8)PLAYER_LIST_INIT << (u16)players.size(); + for (const std::string &player : players) { + list_pkt << player; } m_clients.send(peer_id, 0, &list_pkt, true); NetworkPacket notice_pkt(TOCLIENT_UPDATE_PLAYER_LIST, 0, PEER_ID_INEXISTENT); // (u16) 1 + std::string represents a pseudo vector serialization representation - notice_pkt << (u8) PLAYER_LIST_ADD << (u16) 1 << std::string(playersao->getPlayer()->getName()); + notice_pkt << (u8)PLAYER_LIST_ADD << (u16)1 + << std::string(playersao->getPlayer()->getName()); m_clients.sendToAll(¬ice_pkt); m_clients.event(peer_id, CSE_SetClientReady); s64 last_login; - m_script->getAuth(playersao->getPlayer()->getName(), nullptr, nullptr, &last_login); + m_script->getAuth( + playersao->getPlayer()->getName(), nullptr, nullptr, &last_login); m_script->on_joinplayer(playersao, last_login); // Send shutdown timer if shutdown has been scheduled @@ -421,7 +434,7 @@ void Server::handleCommand_ClientReady(NetworkPacket* pkt) } } -void Server::handleCommand_GotBlocks(NetworkPacket* pkt) +void Server::handleCommand_GotBlocks(NetworkPacket *pkt) { if (pkt->getSize() < 1) return; @@ -440,8 +453,7 @@ void Server::handleCommand_GotBlocks(NetworkPacket* pkt) RemoteClient *client = getClient(pkt->getPeerId()); if ((s16)pkt->getSize() < 1 + (int)count * 6) { - throw con::InvalidIncomingDataException - ("GOTBLOCKS length is too short"); + throw con::InvalidIncomingDataException("GOTBLOCKS length is too short"); } for (u16 i = 0; i < count; i++) { @@ -451,8 +463,8 @@ void Server::handleCommand_GotBlocks(NetworkPacket* pkt) } } -void Server::process_PlayerPos(RemotePlayer *player, PlayerSAO *playersao, - NetworkPacket *pkt) +void Server::process_PlayerPos( + RemotePlayer *player, PlayerSAO *playersao, NetworkPacket *pkt) { if (pkt->getRemainingBytes() < 12 + 12 + 4 + 4 + 4 + 1 + 1) return; @@ -510,23 +522,22 @@ void Server::process_PlayerPos(RemotePlayer *player, PlayerSAO *playersao, } } -void Server::handleCommand_PlayerPos(NetworkPacket* pkt) +void Server::handleCommand_PlayerPos(NetworkPacket *pkt) { session_t peer_id = pkt->getPeerId(); RemotePlayer *player = m_env->getPlayer(peer_id); if (player == NULL) { - errorstream << - "Server::ProcessData(): Canceling: No player for peer_id=" << - peer_id << " disconnecting peer!" << std::endl; + errorstream << "Server::ProcessData(): Canceling: No player for peer_id=" + << peer_id << " disconnecting peer!" << std::endl; DisconnectPeer(peer_id); return; } PlayerSAO *playersao = player->getPlayerSAO(); if (playersao == NULL) { - errorstream << - "Server::ProcessData(): Canceling: No player object for peer_id=" << - peer_id << " disconnecting peer!" << std::endl; + errorstream << "Server::ProcessData(): Canceling: No player object for " + "peer_id=" + << peer_id << " disconnecting peer!" << std::endl; DisconnectPeer(peer_id); return; } @@ -534,14 +545,14 @@ void Server::handleCommand_PlayerPos(NetworkPacket* pkt) // If player is dead we don't care of this packet if (playersao->isDead()) { verbosestream << "TOSERVER_PLAYERPOS: " << player->getName() - << " is dead. Ignoring packet"; + << " is dead. Ignoring packet"; return; } process_PlayerPos(player, playersao, pkt); } -void Server::handleCommand_DeletedBlocks(NetworkPacket* pkt) +void Server::handleCommand_DeletedBlocks(NetworkPacket *pkt) { if (pkt->getSize() < 1) return; @@ -560,8 +571,8 @@ void Server::handleCommand_DeletedBlocks(NetworkPacket* pkt) RemoteClient *client = getClient(pkt->getPeerId()); if ((s16)pkt->getSize() < 1 + (int)count * 6) { - throw con::InvalidIncomingDataException - ("DELETEDBLOCKS length is too short"); + throw con::InvalidIncomingDataException( + "DELETEDBLOCKS length is too short"); } for (u16 i = 0; i < count; i++) { @@ -571,45 +582,42 @@ void Server::handleCommand_DeletedBlocks(NetworkPacket* pkt) } } -void Server::handleCommand_InventoryAction(NetworkPacket* pkt) +void Server::handleCommand_InventoryAction(NetworkPacket *pkt) { session_t peer_id = pkt->getPeerId(); RemotePlayer *player = m_env->getPlayer(peer_id); if (player == NULL) { - errorstream << - "Server::ProcessData(): Canceling: No player for peer_id=" << - peer_id << " disconnecting peer!" << std::endl; + errorstream << "Server::ProcessData(): Canceling: No player for peer_id=" + << peer_id << " disconnecting peer!" << std::endl; DisconnectPeer(peer_id); return; } PlayerSAO *playersao = player->getPlayerSAO(); if (playersao == NULL) { - errorstream << - "Server::ProcessData(): Canceling: No player object for peer_id=" << - peer_id << " disconnecting peer!" << std::endl; + errorstream << "Server::ProcessData(): Canceling: No player object for " + "peer_id=" + << peer_id << " disconnecting peer!" << std::endl; DisconnectPeer(peer_id); return; } // Strip command and create a stream std::string datastring(pkt->getString(0), pkt->getSize()); - verbosestream << "TOSERVER_INVENTORY_ACTION: data=" << datastring - << std::endl; + verbosestream << "TOSERVER_INVENTORY_ACTION: data=" << datastring << std::endl; std::istringstream is(datastring, std::ios_base::binary); // Create an action InventoryAction *a = InventoryAction::deSerialize(is); if (!a) { infostream << "TOSERVER_INVENTORY_ACTION: " - << "InventoryAction::deSerialize() returned NULL" - << std::endl; + << "InventoryAction::deSerialize() returned NULL" << std::endl; return; } // If something goes wrong, this player is to blame - RollbackScopeActor rollback_scope(m_rollback, - std::string("player:")+player->getName()); + RollbackScopeActor rollback_scope( + m_rollback, std::string("player:") + player->getName()); /* Note: Always set inventory not sent, to repair cases @@ -620,7 +628,7 @@ void Server::handleCommand_InventoryAction(NetworkPacket* pkt) Handle restrictions and special cases of the move action */ if (a->getType() == IAction::Move) { - IMoveAction *ma = (IMoveAction*)a; + IMoveAction *ma = (IMoveAction *)a; ma->from_inv.applyCurrentPlayer(player->getName()); ma->to_inv.applyCurrentPlayer(player->getName()); @@ -630,19 +638,19 @@ void Server::handleCommand_InventoryAction(NetworkPacket* pkt) m_inventory_mgr->setInventoryModified(ma->to_inv); bool from_inv_is_current_player = - (ma->from_inv.type == InventoryLocation::PLAYER) && - (ma->from_inv.name == player->getName()); + (ma->from_inv.type == InventoryLocation::PLAYER) && + (ma->from_inv.name == player->getName()); bool to_inv_is_current_player = - (ma->to_inv.type == InventoryLocation::PLAYER) && - (ma->to_inv.name == player->getName()); + (ma->to_inv.type == InventoryLocation::PLAYER) && + (ma->to_inv.name == player->getName()); - InventoryLocation *remote = from_inv_is_current_player ? - &ma->to_inv : &ma->from_inv; + InventoryLocation *remote = + from_inv_is_current_player ? &ma->to_inv : &ma->from_inv; // Check for out-of-range interaction if (remote->type == InventoryLocation::NODEMETA) { - v3f node_pos = intToFloat(remote->p, BS); + v3f node_pos = intToFloat(remote->p, BS); v3f player_pos = player->getPlayerSAO()->getEyePosition(); f32 d = player_pos.getDistanceFrom(node_pos); if (!checkInteractDistance(player, d, "inventory")) @@ -654,9 +662,9 @@ void Server::handleCommand_InventoryAction(NetworkPacket* pkt) */ if (ma->from_list == "craftpreview") { infostream << "Ignoring IMoveAction from " - << (ma->from_inv.dump()) << ":" << ma->from_list - << " to " << (ma->to_inv.dump()) << ":" << ma->to_list - << " because src is " << ma->from_list << std::endl; + << (ma->from_inv.dump()) << ":" << ma->from_list + << " to " << (ma->to_inv.dump()) << ":" << ma->to_list + << " because src is " << ma->from_list << std::endl; delete a; return; } @@ -666,9 +674,9 @@ void Server::handleCommand_InventoryAction(NetworkPacket* pkt) */ if (ma->to_list == "craftpreview" || ma->to_list == "craftresult") { infostream << "Ignoring IMoveAction from " - << (ma->from_inv.dump()) << ":" << ma->from_list - << " to " << (ma->to_inv.dump()) << ":" << ma->to_list - << " because dst is " << ma->to_list << std::endl; + << (ma->from_inv.dump()) << ":" << ma->from_list + << " to " << (ma->to_inv.dump()) << ":" << ma->to_list + << " because dst is " << ma->to_list << std::endl; delete a; return; } @@ -677,9 +685,9 @@ void Server::handleCommand_InventoryAction(NetworkPacket* pkt) // if not allowed to interact if (!checkPriv(player->getName(), "interact") && (!from_inv_is_current_player || - !to_inv_is_current_player)) { + !to_inv_is_current_player)) { infostream << "Cannot move outside of player's inventory: " - << "No interact privilege" << std::endl; + << "No interact privilege" << std::endl; delete a; return; } @@ -688,7 +696,7 @@ void Server::handleCommand_InventoryAction(NetworkPacket* pkt) Handle restrictions and special cases of the drop action */ else if (a->getType() == IAction::Drop) { - IDropAction *da = (IDropAction*)a; + IDropAction *da = (IDropAction *)a; da->from_inv.applyCurrentPlayer(player->getName()); @@ -699,8 +707,8 @@ void Server::handleCommand_InventoryAction(NetworkPacket* pkt) */ if (da->from_list == "craftpreview") { infostream << "Ignoring IDropAction from " - << (da->from_inv.dump()) << ":" << da->from_list - << " because src is " << da->from_list << std::endl; + << (da->from_inv.dump()) << ":" << da->from_list + << " because src is " << da->from_list << std::endl; delete a; return; } @@ -714,8 +722,8 @@ void Server::handleCommand_InventoryAction(NetworkPacket* pkt) // Disallow dropping items if dead if (playersao->isDead()) { infostream << "Ignoring IDropAction from " - << (da->from_inv.dump()) << ":" << da->from_list - << " because player is dead." << std::endl; + << (da->from_inv.dump()) << ":" << da->from_list + << " because player is dead." << std::endl; delete a; return; } @@ -724,20 +732,20 @@ void Server::handleCommand_InventoryAction(NetworkPacket* pkt) Handle restrictions and special cases of the craft action */ else if (a->getType() == IAction::Craft) { - ICraftAction *ca = (ICraftAction*)a; + ICraftAction *ca = (ICraftAction *)a; ca->craft_inv.applyCurrentPlayer(player->getName()); m_inventory_mgr->setInventoryModified(ca->craft_inv); - //bool craft_inv_is_current_player = + // bool craft_inv_is_current_player = // (ca->craft_inv.type == InventoryLocation::PLAYER) && // (ca->craft_inv.name == player->getName()); // Disallow crafting if not allowed to interact if (!checkPriv(player->getName(), "interact")) { infostream << "Cannot craft: " - << "No interact privilege" << std::endl; + << "No interact privilege" << std::endl; delete a; return; } @@ -749,7 +757,7 @@ void Server::handleCommand_InventoryAction(NetworkPacket* pkt) delete a; } -void Server::handleCommand_ChatMessage(NetworkPacket* pkt) +void Server::handleCommand_ChatMessage(NetworkPacket *pkt) { /* u16 command @@ -770,9 +778,8 @@ void Server::handleCommand_ChatMessage(NetworkPacket* pkt) session_t peer_id = pkt->getPeerId(); RemotePlayer *player = m_env->getPlayer(peer_id); if (player == NULL) { - errorstream << - "Server::ProcessData(): Canceling: No player for peer_id=" << - peer_id << " disconnecting peer!" << std::endl; + errorstream << "Server::ProcessData(): Canceling: No player for peer_id=" + << peer_id << " disconnecting peer!" << std::endl; DisconnectPeer(peer_id); return; } @@ -785,11 +792,11 @@ void Server::handleCommand_ChatMessage(NetworkPacket* pkt) if (!answer_to_sender.empty()) { // Send the answer to sender SendChatMessage(peer_id, ChatMessage(CHATMESSAGE_TYPE_NORMAL, - answer_to_sender, wname)); + answer_to_sender, wname)); } } -void Server::handleCommand_Damage(NetworkPacket* pkt) +void Server::handleCommand_Damage(NetworkPacket *pkt) { u16 damage; @@ -799,18 +806,17 @@ void Server::handleCommand_Damage(NetworkPacket* pkt) RemotePlayer *player = m_env->getPlayer(peer_id); if (player == NULL) { - errorstream << - "Server::ProcessData(): Canceling: No player for peer_id=" << - peer_id << " disconnecting peer!" << std::endl; + errorstream << "Server::ProcessData(): Canceling: No player for peer_id=" + << peer_id << " disconnecting peer!" << std::endl; DisconnectPeer(peer_id); return; } PlayerSAO *playersao = player->getPlayerSAO(); if (playersao == NULL) { - errorstream << - "Server::ProcessData(): Canceling: No player object for peer_id=" << - peer_id << " disconnecting peer!" << std::endl; + errorstream << "Server::ProcessData(): Canceling: No player object for " + "peer_id=" + << peer_id << " disconnecting peer!" << std::endl; DisconnectPeer(peer_id); return; } @@ -818,14 +824,15 @@ void Server::handleCommand_Damage(NetworkPacket* pkt) if (!playersao->isImmortal()) { if (playersao->isDead()) { verbosestream << "Server::ProcessData(): Info: " - "Ignoring damage as player " << player->getName() - << " is already dead." << std::endl; + "Ignoring damage as player " + << player->getName() << " is already dead." + << std::endl; return; } - actionstream << player->getName() << " damaged by " - << (int)damage << " hp at " << PP(playersao->getBasePosition() / BS) - << std::endl; + actionstream << player->getName() << " damaged by " << (int)damage + << " hp at " << PP(playersao->getBasePosition() / BS) + << std::endl; PlayerHPChangeReason reason(PlayerHPChangeReason::FALL); playersao->setHP((s32)playersao->getHP() - (s32)damage, reason); @@ -833,7 +840,7 @@ void Server::handleCommand_Damage(NetworkPacket* pkt) } } -void Server::handleCommand_PlayerItem(NetworkPacket* pkt) +void Server::handleCommand_PlayerItem(NetworkPacket *pkt) { if (pkt->getSize() < 2) return; @@ -842,18 +849,17 @@ void Server::handleCommand_PlayerItem(NetworkPacket* pkt) RemotePlayer *player = m_env->getPlayer(peer_id); if (player == NULL) { - errorstream << - "Server::ProcessData(): Canceling: No player for peer_id=" << - peer_id << " disconnecting peer!" << std::endl; + errorstream << "Server::ProcessData(): Canceling: No player for peer_id=" + << peer_id << " disconnecting peer!" << std::endl; DisconnectPeer(peer_id); return; } PlayerSAO *playersao = player->getPlayerSAO(); if (playersao == NULL) { - errorstream << - "Server::ProcessData(): Canceling: No player object for peer_id=" << - peer_id << " disconnecting peer!" << std::endl; + errorstream << "Server::ProcessData(): Canceling: No player object for " + "peer_id=" + << peer_id << " disconnecting peer!" << std::endl; DisconnectPeer(peer_id); return; } @@ -865,14 +871,13 @@ void Server::handleCommand_PlayerItem(NetworkPacket* pkt) playersao->getPlayer()->setWieldIndex(item); } -void Server::handleCommand_Respawn(NetworkPacket* pkt) +void Server::handleCommand_Respawn(NetworkPacket *pkt) { session_t peer_id = pkt->getPeerId(); RemotePlayer *player = m_env->getPlayer(peer_id); if (player == NULL) { - errorstream << - "Server::ProcessData(): Canceling: No player for peer_id=" << - peer_id << " disconnecting peer!" << std::endl; + errorstream << "Server::ProcessData(): Canceling: No player for peer_id=" + << peer_id << " disconnecting peer!" << std::endl; DisconnectPeer(peer_id); return; } @@ -886,27 +891,27 @@ void Server::handleCommand_Respawn(NetworkPacket* pkt) RespawnPlayer(peer_id); actionstream << player->getName() << " respawns at " - << PP(playersao->getBasePosition() / BS) << std::endl; + << PP(playersao->getBasePosition() / BS) << std::endl; // ActiveObject is added to environment in AsyncRunStep after // the previous addition has been successfully removed } -bool Server::checkInteractDistance(RemotePlayer *player, const f32 d, const std::string &what) +bool Server::checkInteractDistance( + RemotePlayer *player, const f32 d, const std::string &what) { ItemStack selected_item, hand_item; player->getWieldedItem(&selected_item, &hand_item); f32 max_d = BS * getToolRange(selected_item.getDefinition(m_itemdef), - hand_item.getDefinition(m_itemdef)); + hand_item.getDefinition(m_itemdef)); // Cube diagonal * 1.5 for maximal supported node extents: // sqrt(3) * 1.5 ≅ 2.6 if (d > max_d + 2.6f * BS) { - actionstream << "Player " << player->getName() - << " tried to access " << what - << " from too far: " - << "d=" << d << ", max_d=" << max_d - << "; ignoring." << std::endl; + actionstream << "Player " << player->getName() << " tried to access " + << what << " from too far: " + << "d=" << d << ", max_d=" << max_d << "; ignoring." + << std::endl; // Call callbacks m_script->on_cheat(player->getPlayerSAO(), "interacted_too_far"); return false; @@ -935,32 +940,32 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) PointedThing pointed; pointed.deSerialize(tmp_is); - verbosestream << "TOSERVER_INTERACT: action=" << (int)action << ", item=" - << item_i << ", pointed=" << pointed.dump() << std::endl; + verbosestream << "TOSERVER_INTERACT: action=" << (int)action + << ", item=" << item_i << ", pointed=" << pointed.dump() + << std::endl; session_t peer_id = pkt->getPeerId(); RemotePlayer *player = m_env->getPlayer(peer_id); if (player == NULL) { - errorstream << - "Server::ProcessData(): Canceling: No player for peer_id=" << - peer_id << " disconnecting peer!" << std::endl; + errorstream << "Server::ProcessData(): Canceling: No player for peer_id=" + << peer_id << " disconnecting peer!" << std::endl; DisconnectPeer(peer_id); return; } PlayerSAO *playersao = player->getPlayerSAO(); if (playersao == NULL) { - errorstream << - "Server::ProcessData(): Canceling: No player object for peer_id=" << - peer_id << " disconnecting peer!" << std::endl; + errorstream << "Server::ProcessData(): Canceling: No player object for " + "peer_id=" + << peer_id << " disconnecting peer!" << std::endl; DisconnectPeer(peer_id); return; } if (playersao->isDead()) { actionstream << "Server: " << player->getName() - << " tried to interact while dead; ignoring." << std::endl; + << " tried to interact while dead; ignoring." << std::endl; if (pointed.type == POINTEDTHING_NODE) { // Re-send block to revert change on client-side RemoteClient *client = getClient(peer_id); @@ -989,10 +994,10 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) pointed_object = m_env->getActiveObject(pointed.object_id); if (pointed_object == NULL) { verbosestream << "TOSERVER_INTERACT: " - "pointed object is NULL" << std::endl; + "pointed object is NULL" + << std::endl; return; } - } v3f pointed_pos_under = player_pos; @@ -1000,8 +1005,7 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) if (pointed.type == POINTEDTHING_NODE) { pointed_pos_under = intToFloat(p_under, BS); pointed_pos_above = intToFloat(p_above, BS); - } - else if (pointed.type == POINTEDTHING_OBJECT) { + } else if (pointed.type == POINTEDTHING_OBJECT) { pointed_pos_under = pointed_object->getBasePosition(); pointed_pos_above = pointed_pos_under; } @@ -1010,19 +1014,22 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) Make sure the player is allowed to do it */ if (!checkPriv(player->getName(), "interact")) { - actionstream << player->getName() << " attempted to interact with " << - pointed.dump() << " without 'interact' privilege" << std::endl; + actionstream << player->getName() << " attempted to interact with " + << pointed.dump() << " without 'interact' privilege" + << std::endl; // Re-send block to revert change on client-side RemoteClient *client = getClient(peer_id); // Digging completed -> under if (action == INTERACT_DIGGING_COMPLETED) { - v3s16 blockpos = getNodeBlockPos(floatToInt(pointed_pos_under, BS)); + v3s16 blockpos = getNodeBlockPos( + floatToInt(pointed_pos_under, BS)); client->SetBlockNotSent(blockpos); } // Placement -> above else if (action == INTERACT_PLACE) { - v3s16 blockpos = getNodeBlockPos(floatToInt(pointed_pos_above, BS)); + v3s16 blockpos = getNodeBlockPos( + floatToInt(pointed_pos_above, BS)); client->SetBlockNotSent(blockpos); } return; @@ -1036,14 +1043,15 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) !g_settings->getBool("disable_anticheat"); if ((action == INTERACT_START_DIGGING || action == INTERACT_DIGGING_COMPLETED || - action == INTERACT_PLACE || action == INTERACT_USE) && + action == INTERACT_PLACE || action == INTERACT_USE) && enable_anticheat && !isSingleplayer()) { float d = playersao->getEyePosition().getDistanceFrom(pointed_pos_under); if (!checkInteractDistance(player, d, pointed.dump())) { // Re-send block to revert change on client-side RemoteClient *client = getClient(peer_id); - v3s16 blockpos = getNodeBlockPos(floatToInt(pointed_pos_under, BS)); + v3s16 blockpos = getNodeBlockPos( + floatToInt(pointed_pos_under, BS)); client->SetBlockNotSent(blockpos); return; } @@ -1052,8 +1060,8 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) /* If something goes wrong, this player is to blame */ - RollbackScopeActor rollback_scope(m_rollback, - std::string("player:")+player->getName()); + RollbackScopeActor rollback_scope( + m_rollback, std::string("player:") + player->getName()); /* 0: start digging or punch object @@ -1066,9 +1074,10 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) n = m_env->getMap().getNode(p_under, &pos_ok); if (!pos_ok) { infostream << "Server: Not punching: Node not found. " - "Adding block to emerge queue." << std::endl; - m_emerge->enqueueBlockEmerge(peer_id, getNodeBlockPos(p_above), - false); + "Adding block to emerge queue." + << std::endl; + m_emerge->enqueueBlockEmerge( + peer_id, getNodeBlockPos(p_above), false); } if (n.getContent() != CONTENT_IGNORE) @@ -1076,27 +1085,27 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) // Cheat prevention playersao->noCheatDigStart(p_under); - } - else if (pointed.type == POINTEDTHING_OBJECT) { + } else if (pointed.type == POINTEDTHING_OBJECT) { // Skip if object can't be interacted with anymore if (pointed_object->isGone()) return; ItemStack selected_item, hand_item; - ItemStack tool_item = playersao->getWieldedItem(&selected_item, &hand_item); + ItemStack tool_item = playersao->getWieldedItem( + &selected_item, &hand_item); ToolCapabilities toolcap = tool_item.getToolCapabilities(m_itemdef); v3f dir = (pointed_object->getBasePosition() - - (playersao->getBasePosition() + playersao->getEyeOffset()) - ).normalize(); - float time_from_last_punch = - playersao->resetTimeFromLastPunch(); + (playersao->getBasePosition() + + playersao->getEyeOffset())) + .normalize(); + float time_from_last_punch = playersao->resetTimeFromLastPunch(); u16 src_original_hp = pointed_object->getHP(); u16 dst_origin_hp = playersao->getHP(); - u16 wear = pointed_object->punch(dir, &toolcap, playersao, - time_from_last_punch); + u16 wear = pointed_object->punch( + dir, &toolcap, playersao, time_from_last_punch); // Callback may have changed item, so get it again playersao->getWieldedItem(&selected_item); @@ -1106,15 +1115,22 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) // If the object is a player and its HP changed if (src_original_hp != pointed_object->getHP() && - pointed_object->getType() == ACTIVEOBJECT_TYPE_PLAYER) { + pointed_object->getType() == + ACTIVEOBJECT_TYPE_PLAYER) { SendPlayerHPOrDie((PlayerSAO *)pointed_object, - PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, playersao)); + PlayerHPChangeReason( + PlayerHPChangeReason:: + PLAYER_PUNCH, + playersao)); } // If the puncher is a player and its HP changed if (dst_origin_hp != playersao->getHP()) SendPlayerHPOrDie(playersao, - PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, pointed_object)); + PlayerHPChangeReason( + PlayerHPChangeReason:: + PLAYER_PUNCH, + pointed_object)); } } // action == INTERACT_START_DIGGING @@ -1134,10 +1150,12 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) bool pos_ok; MapNode n = m_env->getMap().getNode(p_under, &pos_ok); if (!pos_ok) { - infostream << "Server: Not finishing digging: Node not found. " - "Adding block to emerge queue." << std::endl; - m_emerge->enqueueBlockEmerge(peer_id, getNodeBlockPos(p_above), - false); + infostream << "Server: Not finishing digging: Node not " + "found. " + "Adding block to emerge queue." + << std::endl; + m_emerge->enqueueBlockEmerge( + peer_id, getNodeBlockPos(p_above), false); } /* Cheat prevention */ @@ -1149,33 +1167,39 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) // If player didn't start digging this, ignore dig if (nocheat_p != p_under) { infostream << "Server: " << player->getName() - << " started digging " - << PP(nocheat_p) << " and completed digging " - << PP(p_under) << "; not digging." << std::endl; + << " started digging " << PP(nocheat_p) + << " and completed digging " + << PP(p_under) << "; not digging." + << std::endl; is_valid_dig = false; // Call callbacks - m_script->on_cheat(playersao, "finished_unknown_dig"); + m_script->on_cheat(playersao, + "finished_unknown_dig"); } // Get player's wielded item // See also: Game::handleDigging ItemStack selected_item, hand_item; - playersao->getPlayer()->getWieldedItem(&selected_item, &hand_item); + playersao->getPlayer()->getWieldedItem( + &selected_item, &hand_item); // Get diggability and expected digging time DigParams params = getDigParams(m_nodedef->get(n).groups, - &selected_item.getToolCapabilities(m_itemdef)); + &selected_item.getToolCapabilities( + m_itemdef)); // If can't dig, try hand if (!params.diggable) { params = getDigParams(m_nodedef->get(n).groups, - &hand_item.getToolCapabilities(m_itemdef)); + &hand_item.getToolCapabilities( + m_itemdef)); } // If can't dig, ignore dig if (!params.diggable) { infostream << "Server: " << player->getName() - << " completed digging " << PP(p_under) - << ", which is not diggable with tool; not digging." - << std::endl; + << " completed digging " << PP(p_under) + << ", which is not diggable with " + "tool; not digging." + << std::endl; is_valid_dig = false; // Call callbacks m_script->on_cheat(playersao, "dug_unbreakable"); @@ -1186,9 +1210,10 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) // Well not our problem then } // Clean and long dig - else if (params.time > 2.0 && nocheat_t * 1.2 > params.time) { - // All is good, but grab time from pool; don't care if - // it's actually available + else if (params.time > 2.0 && + nocheat_t * 1.2 > params.time) { + // All is good, but grab time from pool; don't + // care if it's actually available playersao->getDigPool().grab(params.time); } // Short or laggy dig @@ -1199,8 +1224,9 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) // Dig not possible else { infostream << "Server: " << player->getName() - << " completed digging " << PP(p_under) - << "too fast; not digging." << std::endl; + << " completed digging " << PP(p_under) + << "too fast; not digging." + << std::endl; is_valid_dig = false; // Call callbacks m_script->on_cheat(playersao, "dug_too_fast"); @@ -1212,14 +1238,15 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) if (is_valid_dig && n.getContent() != CONTENT_IGNORE) m_script->node_on_dig(p_under, n, playersao); - v3s16 blockpos = getNodeBlockPos(floatToInt(pointed_pos_under, BS)); + v3s16 blockpos = getNodeBlockPos( + floatToInt(pointed_pos_under, BS)); RemoteClient *client = getClient(peer_id); // Send unusual result (that is, node not being removed) - if (m_env->getMap().getNode(p_under).getContent() != CONTENT_AIR) { + if (m_env->getMap().getNode(p_under).getContent() != + CONTENT_AIR) { // Re-send block to revert change on client-side client->SetBlockNotSent(blockpos); - } - else { + } else { client->ResendBlockIfOnWire(blockpos); } } @@ -1245,20 +1272,19 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) return; actionstream << player->getName() << " right-clicks object " - << pointed.object_id << ": " - << pointed_object->getDescription() << std::endl; + << pointed.object_id << ": " + << pointed_object->getDescription() << std::endl; // Do stuff if (m_script->item_OnSecondaryUse( - selected_item, playersao, pointed)) { + selected_item, playersao, pointed)) { if (playersao->setWieldedItem(selected_item)) { SendInventory(playersao, true); } } pointed_object->rightClick(playersao); - } else if (m_script->item_OnPlace( - selected_item, playersao, pointed)) { + } else if (m_script->item_OnPlace(selected_item, playersao, pointed)) { // Placement was handled in lua // Apply returned ItemStack @@ -1272,13 +1298,13 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) RemoteClient *client = getClient(peer_id); v3s16 blockpos = getNodeBlockPos(floatToInt(pointed_pos_above, BS)); v3s16 blockpos2 = getNodeBlockPos(floatToInt(pointed_pos_under, BS)); - if (!selected_item.getDefinition(m_itemdef).node_placement_prediction.empty()) { + if (!selected_item.getDefinition(m_itemdef) + .node_placement_prediction.empty()) { client->SetBlockNotSent(blockpos); if (blockpos2 != blockpos) { client->SetBlockNotSent(blockpos2); } - } - else { + } else { client->ResendBlockIfOnWire(blockpos); if (blockpos2 != blockpos) { client->ResendBlockIfOnWire(blockpos2); @@ -1294,10 +1320,9 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) playersao->getWieldedItem(&selected_item, nullptr); actionstream << player->getName() << " uses " << selected_item.name - << ", pointing at " << pointed.dump() << std::endl; + << ", pointing at " << pointed.dump() << std::endl; - if (m_script->item_OnUse( - selected_item, playersao, pointed)) { + if (m_script->item_OnUse(selected_item, playersao, pointed)) { // Apply returned ItemStack if (playersao->setWieldedItem(selected_item)) { SendInventory(playersao, true); @@ -1313,30 +1338,27 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) ItemStack selected_item; playersao->getWieldedItem(&selected_item, nullptr); - actionstream << player->getName() << " activates " - << selected_item.name << std::endl; + actionstream << player->getName() << " activates " << selected_item.name + << std::endl; pointed.type = POINTEDTHING_NOTHING; // can only ever be NOTHING - if (m_script->item_OnSecondaryUse( - selected_item, playersao, pointed)) { + if (m_script->item_OnSecondaryUse(selected_item, playersao, pointed)) { if (playersao->setWieldedItem(selected_item)) { SendInventory(playersao, true); } } } // action == INTERACT_ACTIVATE - /* Catch invalid actions */ else { - warningstream << "Server: Invalid action " - << action << std::endl; + warningstream << "Server: Invalid action " << action << std::endl; } } -void Server::handleCommand_RemovedSounds(NetworkPacket* pkt) +void Server::handleCommand_RemovedSounds(NetworkPacket *pkt) { u16 num; *pkt >> num; @@ -1346,7 +1368,7 @@ void Server::handleCommand_RemovedSounds(NetworkPacket* pkt) *pkt >> id; std::unordered_map::iterator i = - m_playing_sounds.find(id); + m_playing_sounds.find(id); if (i == m_playing_sounds.end()) continue; @@ -1357,7 +1379,7 @@ void Server::handleCommand_RemovedSounds(NetworkPacket* pkt) } } -void Server::handleCommand_NodeMetaFields(NetworkPacket* pkt) +void Server::handleCommand_NodeMetaFields(NetworkPacket *pkt) { v3s16 p; std::string formname; @@ -1376,25 +1398,24 @@ void Server::handleCommand_NodeMetaFields(NetworkPacket* pkt) RemotePlayer *player = m_env->getPlayer(peer_id); if (player == NULL) { - errorstream << - "Server::ProcessData(): Canceling: No player for peer_id=" << - peer_id << " disconnecting peer!" << std::endl; + errorstream << "Server::ProcessData(): Canceling: No player for peer_id=" + << peer_id << " disconnecting peer!" << std::endl; DisconnectPeer(peer_id); return; } PlayerSAO *playersao = player->getPlayerSAO(); if (playersao == NULL) { - errorstream << - "Server::ProcessData(): Canceling: No player object for peer_id=" << - peer_id << " disconnecting peer!" << std::endl; + errorstream << "Server::ProcessData(): Canceling: No player object for " + "peer_id=" + << peer_id << " disconnecting peer!" << std::endl; DisconnectPeer(peer_id); return; } // If something goes wrong, this player is to blame - RollbackScopeActor rollback_scope(m_rollback, - std::string("player:")+player->getName()); + RollbackScopeActor rollback_scope( + m_rollback, std::string("player:") + player->getName()); // Check the target node for rollback data; leave others unnoticed RollbackNode rn_old(&m_env->getMap(), p, this); @@ -1410,7 +1431,7 @@ void Server::handleCommand_NodeMetaFields(NetworkPacket* pkt) } } -void Server::handleCommand_InventoryFields(NetworkPacket* pkt) +void Server::handleCommand_InventoryFields(NetworkPacket *pkt) { std::string client_formspec_name; u16 num; @@ -1428,18 +1449,17 @@ void Server::handleCommand_InventoryFields(NetworkPacket* pkt) RemotePlayer *player = m_env->getPlayer(peer_id); if (player == NULL) { - errorstream << - "Server::ProcessData(): Canceling: No player for peer_id=" << - peer_id << " disconnecting peer!" << std::endl; + errorstream << "Server::ProcessData(): Canceling: No player for peer_id=" + << peer_id << " disconnecting peer!" << std::endl; DisconnectPeer(peer_id); return; } PlayerSAO *playersao = player->getPlayerSAO(); if (playersao == NULL) { - errorstream << - "Server::ProcessData(): Canceling: No player object for peer_id=" << - peer_id << " disconnecting peer!" << std::endl; + errorstream << "Server::ProcessData(): Canceling: No player object for " + "peer_id=" + << peer_id << " disconnecting peer!" << std::endl; DisconnectPeer(peer_id); return; } @@ -1458,23 +1478,25 @@ void Server::handleCommand_InventoryFields(NetworkPacket* pkt) if (it != fields.end() && it->second == "true") m_formspec_state_data.erase(peer_state_iterator); - m_script->on_playerReceiveFields(playersao, client_formspec_name, fields); + m_script->on_playerReceiveFields( + playersao, client_formspec_name, fields); return; } - actionstream << "'" << player->getName() - << "' submitted formspec ('" << client_formspec_name - << "') but the name of the formspec doesn't match the" - " expected name ('" << server_formspec_name << "')"; + actionstream << "'" << player->getName() << "' submitted formspec ('" + << client_formspec_name + << "') but the name of the formspec doesn't match the" + " expected name ('" + << server_formspec_name << "')"; } else { - actionstream << "'" << player->getName() - << "' submitted formspec ('" << client_formspec_name - << "') but server hasn't sent formspec to client"; + actionstream << "'" << player->getName() << "' submitted formspec ('" + << client_formspec_name + << "') but server hasn't sent formspec to client"; } actionstream << ", possible exploitation attempt" << std::endl; } -void Server::handleCommand_FirstSrp(NetworkPacket* pkt) +void Server::handleCommand_FirstSrp(NetworkPacket *pkt) { session_t peer_id = pkt->getPeerId(); RemoteClient *client = getClient(peer_id, CS_Invalid); @@ -1491,23 +1513,24 @@ void Server::handleCommand_FirstSrp(NetworkPacket* pkt) *pkt >> salt >> verification_key >> is_empty; verbosestream << "Server: Got TOSERVER_FIRST_SRP from " << addr_s - << ", with is_empty=" << (is_empty == 1) << std::endl; + << ", with is_empty=" << (is_empty == 1) << std::endl; // Either this packet is sent because the user is new or to change the password if (cstate == CS_HelloSent) { if (!client->isMechAllowed(AUTH_MECHANISM_FIRST_SRP)) { actionstream << "Server: Client from " << addr_s - << " tried to set password without being " - << "authenticated, or the username being new." << std::endl; + << " tried to set password without being " + << "authenticated, or the username being new." + << std::endl; DenyAccess(peer_id, SERVER_ACCESSDENIED_UNEXPECTED_DATA); return; } - if (!isSingleplayer() && - g_settings->getBool("disallow_empty_password") && + if (!isSingleplayer() && g_settings->getBool("disallow_empty_password") && is_empty == 1) { actionstream << "Server: " << playername - << " supplied empty password from " << addr_s << std::endl; + << " supplied empty password from " << addr_s + << std::endl; DenyAccess(peer_id, SERVER_ACCESSDENIED_EMPTY_PASSWORD); return; } @@ -1521,9 +1544,11 @@ void Server::handleCommand_FirstSrp(NetworkPacket* pkt) acceptAuth(peer_id, false); } else { if (cstate < CS_SudoMode) { - infostream << "Server::ProcessData(): Ignoring TOSERVER_FIRST_SRP from " - << addr_s << ": " << "Client has wrong state " << cstate << "." - << std::endl; + infostream << "Server::ProcessData(): Ignoring " + "TOSERVER_FIRST_SRP from " + << addr_s << ": " + << "Client has wrong state " << cstate << "." + << std::endl; return; } m_clients.event(peer_id, CSE_SudoLeave); @@ -1531,18 +1556,22 @@ void Server::handleCommand_FirstSrp(NetworkPacket* pkt) bool success = m_script->setPassword(playername, pw_db_field); if (success) { actionstream << playername << " changes password" << std::endl; - SendChatMessage(peer_id, ChatMessage(CHATMESSAGE_TYPE_SYSTEM, - L"Password change successful.")); + SendChatMessage(peer_id, + ChatMessage(CHATMESSAGE_TYPE_SYSTEM, + L"Password change successful.")); } else { - actionstream << playername << - " tries to change password but it fails" << std::endl; - SendChatMessage(peer_id, ChatMessage(CHATMESSAGE_TYPE_SYSTEM, - L"Password change failed or unavailable.")); + actionstream << playername + << " tries to change password but it fails" + << std::endl; + SendChatMessage(peer_id, + ChatMessage(CHATMESSAGE_TYPE_SYSTEM, + L"Password change failed or " + L"unavailable.")); } } } -void Server::handleCommand_SrpBytesA(NetworkPacket* pkt) +void Server::handleCommand_SrpBytesA(NetworkPacket *pkt) { session_t peer_id = pkt->getPeerId(); RemoteClient *client = getClient(peer_id, CS_Invalid); @@ -1551,17 +1580,18 @@ void Server::handleCommand_SrpBytesA(NetworkPacket* pkt) bool wantSudo = (cstate == CS_Active); if (!((cstate == CS_HelloSent) || (cstate == CS_Active))) { - actionstream << "Server: got SRP _A packet in wrong state " << cstate << - " from " << getPeerAddress(peer_id).serializeString() << - ". Ignoring." << std::endl; + actionstream << "Server: got SRP _A packet in wrong state " << cstate + << " from " << getPeerAddress(peer_id).serializeString() + << ". Ignoring." << std::endl; return; } if (client->chosen_mech != AUTH_MECHANISM_NONE) { actionstream << "Server: got SRP _A packet, while auth is already " - "going on with mech " << client->chosen_mech << " from " << - getPeerAddress(peer_id).serializeString() << - " (wantSudo=" << wantSudo << "). Ignoring." << std::endl; + "going on with mech " + << client->chosen_mech << " from " + << getPeerAddress(peer_id).serializeString() + << " (wantSudo=" << wantSudo << "). Ignoring." << std::endl; if (wantSudo) { DenySudoAccess(peer_id); return; @@ -1576,26 +1606,28 @@ void Server::handleCommand_SrpBytesA(NetworkPacket* pkt) *pkt >> bytes_A >> based_on; infostream << "Server: TOSERVER_SRP_BYTES_A received with " - << "based_on=" << int(based_on) << " and len_A=" - << bytes_A.length() << "." << std::endl; + << "based_on=" << int(based_on) << " and len_A=" << bytes_A.length() + << "." << std::endl; - AuthMechanism chosen = (based_on == 0) ? - AUTH_MECHANISM_LEGACY_PASSWORD : AUTH_MECHANISM_SRP; + AuthMechanism chosen = (based_on == 0) ? AUTH_MECHANISM_LEGACY_PASSWORD + : AUTH_MECHANISM_SRP; if (wantSudo) { if (!client->isSudoMechAllowed(chosen)) { - actionstream << "Server: Player \"" << client->getName() << - "\" at " << getPeerAddress(peer_id).serializeString() << - " tried to change password using unallowed mech " << chosen << - "." << std::endl; + actionstream << "Server: Player \"" << client->getName() + << "\" at " + << getPeerAddress(peer_id).serializeString() + << " tried to change password using unallowed mech " + << chosen << "." << std::endl; DenySudoAccess(peer_id); return; } } else { if (!client->isMechAllowed(chosen)) { - actionstream << "Server: Client tried to authenticate from " << - getPeerAddress(peer_id).serializeString() << - " using unallowed mech " << chosen << "." << std::endl; + actionstream << "Server: Client tried to authenticate from " + << getPeerAddress(peer_id).serializeString() + << " using unallowed mech " << chosen << "." + << std::endl; DenyAccess(peer_id, SERVER_ACCESSDENIED_UNEXPECTED_DATA); return; } @@ -1608,13 +1640,15 @@ void Server::handleCommand_SrpBytesA(NetworkPacket* pkt) if (based_on == 0) { - generate_srp_verifier_and_salt(client->getName(), client->enc_pwd, - &verifier, &salt); + generate_srp_verifier_and_salt( + client->getName(), client->enc_pwd, &verifier, &salt); } else if (!decode_srp_verifier_and_salt(client->enc_pwd, &verifier, &salt)) { // Non-base64 errors should have been catched in the init handler - actionstream << "Server: User " << client->getName() << - " tried to log in, but srp verifier field was invalid (most likely " - "invalid base64)." << std::endl; + actionstream << "Server: User " << client->getName() + << " tried to log in, but srp verifier field was invalid " + "(most likely " + "invalid base64)." + << std::endl; DenyAccess(peer_id, SERVER_ACCESSDENIED_SERVER_FAIL); return; } @@ -1623,17 +1657,17 @@ void Server::handleCommand_SrpBytesA(NetworkPacket* pkt) size_t len_B = 0; client->auth_data = srp_verifier_new(SRP_SHA256, SRP_NG_2048, - client->getName().c_str(), - (const unsigned char *) salt.c_str(), salt.size(), - (const unsigned char *) verifier.c_str(), verifier.size(), - (const unsigned char *) bytes_A.c_str(), bytes_A.size(), - NULL, 0, - (unsigned char **) &bytes_B, &len_B, NULL, NULL); + client->getName().c_str(), (const unsigned char *)salt.c_str(), + salt.size(), (const unsigned char *)verifier.c_str(), + verifier.size(), (const unsigned char *)bytes_A.c_str(), + bytes_A.size(), NULL, 0, (unsigned char **)&bytes_B, &len_B, NULL, + NULL); if (!bytes_B) { actionstream << "Server: User " << client->getName() - << " tried to log in, SRP-6a safety check violated in _A handler." - << std::endl; + << " tried to log in, SRP-6a safety check violated in _A " + "handler." + << std::endl; if (wantSudo) { DenySudoAccess(peer_id); return; @@ -1648,7 +1682,7 @@ void Server::handleCommand_SrpBytesA(NetworkPacket* pkt) Send(&resp_pkt); } -void Server::handleCommand_SrpBytesM(NetworkPacket* pkt) +void Server::handleCommand_SrpBytesM(NetworkPacket *pkt) { session_t peer_id = pkt->getPeerId(); RemoteClient *client = getClient(peer_id, CS_Invalid); @@ -1661,17 +1695,17 @@ void Server::handleCommand_SrpBytesM(NetworkPacket* pkt) verbosestream << "Server: Received TOCLIENT_SRP_BYTES_M." << std::endl; if (!((cstate == CS_HelloSent) || (cstate == CS_Active))) { - actionstream << "Server: got SRP _M packet in wrong state " - << cstate << " from " << addr_s - << ". Ignoring." << std::endl; + actionstream << "Server: got SRP _M packet in wrong state " << cstate + << " from " << addr_s << ". Ignoring." << std::endl; return; } if (client->chosen_mech != AUTH_MECHANISM_SRP && client->chosen_mech != AUTH_MECHANISM_LEGACY_PASSWORD) { actionstream << "Server: got SRP _M packet, while auth" - << "is going on with mech " << client->chosen_mech << " from " - << addr_s << " (wantSudo=" << wantSudo << "). Denying." << std::endl; + << "is going on with mech " << client->chosen_mech + << " from " << addr_s << " (wantSudo=" << wantSudo + << "). Denying." << std::endl; if (wantSudo) { DenySudoAccess(peer_id); return; @@ -1684,30 +1718,34 @@ void Server::handleCommand_SrpBytesM(NetworkPacket* pkt) std::string bytes_M; *pkt >> bytes_M; - if (srp_verifier_get_session_key_length((SRPVerifier *) client->auth_data) - != bytes_M.size()) { + if (srp_verifier_get_session_key_length((SRPVerifier *)client->auth_data) != + bytes_M.size()) { actionstream << "Server: User " << playername << " at " << addr_s - << " sent bytes_M with invalid length " << bytes_M.size() << std::endl; + << " sent bytes_M with invalid length " << bytes_M.size() + << std::endl; DenyAccess(peer_id, SERVER_ACCESSDENIED_UNEXPECTED_DATA); return; } unsigned char *bytes_HAMK = 0; - srp_verifier_verify_session((SRPVerifier *) client->auth_data, - (unsigned char *)bytes_M.c_str(), &bytes_HAMK); + srp_verifier_verify_session((SRPVerifier *)client->auth_data, + (unsigned char *)bytes_M.c_str(), &bytes_HAMK); if (!bytes_HAMK) { if (wantSudo) { actionstream << "Server: User " << playername << " at " << addr_s - << " tried to change their password, but supplied wrong" - << " (SRP) password for authentication." << std::endl; + << " tried to change their password, but supplied " + "wrong" + << " (SRP) password for authentication." + << std::endl; DenySudoAccess(peer_id); return; } actionstream << "Server: User " << playername << " at " << addr_s - << " supplied wrong password (auth mechanism: SRP)." << std::endl; + << " supplied wrong password (auth mechanism: SRP)." + << std::endl; m_script->on_authplayer(playername, addr_s, false); DenyAccess(peer_id, SERVER_ACCESSDENIED_WRONG_PASSWORD); return; @@ -1718,9 +1756,10 @@ void Server::handleCommand_SrpBytesM(NetworkPacket* pkt) std::string checkpwd; // not used, but needed for passing something if (!m_script->getAuth(playername, &checkpwd, NULL)) { - actionstream << "Server: " << playername << - " cannot be authenticated (auth handler does not work?)" << - std::endl; + actionstream << "Server: " << playername + << " cannot be authenticated (auth handler does not " + "work?)" + << std::endl; DenyAccess(peer_id, SERVER_ACCESSDENIED_SERVER_FAIL); return; } @@ -1741,20 +1780,20 @@ void Server::handleCommand_ModChannelJoin(NetworkPacket *pkt) *pkt >> channel_name; session_t peer_id = pkt->getPeerId(); - NetworkPacket resp_pkt(TOCLIENT_MODCHANNEL_SIGNAL, - 1 + 2 + channel_name.size(), peer_id); + NetworkPacket resp_pkt( + TOCLIENT_MODCHANNEL_SIGNAL, 1 + 2 + channel_name.size(), peer_id); // Send signal to client to notify join succeed or not if (g_settings->getBool("enable_mod_channels") && m_modchannel_mgr->joinChannel(channel_name, peer_id)) { - resp_pkt << (u8) MODCHANNEL_SIGNAL_JOIN_OK; - infostream << "Peer " << peer_id << " joined channel " << - channel_name << std::endl; - } - else { + resp_pkt << (u8)MODCHANNEL_SIGNAL_JOIN_OK; + infostream << "Peer " << peer_id << " joined channel " << channel_name + << std::endl; + } else { resp_pkt << (u8)MODCHANNEL_SIGNAL_JOIN_FAILURE; - infostream << "Peer " << peer_id << " tried to join channel " << - channel_name << ", but was already registered." << std::endl; + infostream << "Peer " << peer_id << " tried to join channel " + << channel_name << ", but was already registered." + << std::endl; } resp_pkt << channel_name; Send(&resp_pkt); @@ -1766,19 +1805,19 @@ void Server::handleCommand_ModChannelLeave(NetworkPacket *pkt) *pkt >> channel_name; session_t peer_id = pkt->getPeerId(); - NetworkPacket resp_pkt(TOCLIENT_MODCHANNEL_SIGNAL, - 1 + 2 + channel_name.size(), peer_id); + NetworkPacket resp_pkt( + TOCLIENT_MODCHANNEL_SIGNAL, 1 + 2 + channel_name.size(), peer_id); // Send signal to client to notify join succeed or not if (g_settings->getBool("enable_mod_channels") && m_modchannel_mgr->leaveChannel(channel_name, peer_id)) { resp_pkt << (u8)MODCHANNEL_SIGNAL_LEAVE_OK; - infostream << "Peer " << peer_id << " left channel " << channel_name << - std::endl; + infostream << "Peer " << peer_id << " left channel " << channel_name + << std::endl; } else { - resp_pkt << (u8) MODCHANNEL_SIGNAL_LEAVE_FAILURE; - infostream << "Peer " << peer_id << " left channel " << channel_name << - ", but was not registered." << std::endl; + resp_pkt << (u8)MODCHANNEL_SIGNAL_LEAVE_FAILURE; + infostream << "Peer " << peer_id << " left channel " << channel_name + << ", but was not registered." << std::endl; } resp_pkt << channel_name; Send(&resp_pkt); @@ -1790,9 +1829,9 @@ void Server::handleCommand_ModChannelMsg(NetworkPacket *pkt) *pkt >> channel_name >> channel_msg; session_t peer_id = pkt->getPeerId(); - verbosestream << "Mod channel message received from peer " << peer_id << - " on channel " << channel_name << " message: " << channel_msg << - std::endl; + verbosestream << "Mod channel message received from peer " << peer_id + << " on channel " << channel_name << " message: " << channel_msg + << std::endl; // If mod channels are not enabled, discard message if (!g_settings->getBool("enable_mod_channels")) { @@ -1802,7 +1841,7 @@ void Server::handleCommand_ModChannelMsg(NetworkPacket *pkt) // If channel not registered, signal it and ignore message if (!m_modchannel_mgr->channelRegistered(channel_name)) { NetworkPacket resp_pkt(TOCLIENT_MODCHANNEL_SIGNAL, - 1 + 2 + channel_name.size(), peer_id); + 1 + 2 + channel_name.size(), peer_id); resp_pkt << (u8)MODCHANNEL_SIGNAL_CHANNEL_NOT_REGISTERED << channel_name; Send(&resp_pkt); return; diff --git a/src/nodedef.cpp b/src/nodedef.cpp index 540ed6086..f528a5a50 100644 --- a/src/nodedef.cpp +++ b/src/nodedef.cpp @@ -51,9 +51,10 @@ void NodeBox::reset() // default is empty fixed.clear(); // default is sign/ladder-like - wall_top = aabb3f(-BS/2, BS/2-BS/16., -BS/2, BS/2, BS/2, BS/2); - wall_bottom = aabb3f(-BS/2, -BS/2, -BS/2, BS/2, -BS/2+BS/16., BS/2); - wall_side = aabb3f(-BS/2, -BS/2, -BS/2, -BS/2+BS/16., BS/2, BS/2); + wall_top = aabb3f(-BS / 2, BS / 2 - BS / 16., -BS / 2, BS / 2, BS / 2, BS / 2); + wall_bottom = aabb3f( + -BS / 2, -BS / 2, -BS / 2, BS / 2, -BS / 2 + BS / 16., BS / 2); + wall_side = aabb3f(-BS / 2, -BS / 2, -BS / 2, -BS / 2 + BS / 16., BS / 2, BS / 2); // no default for other parts connect_top.clear(); connect_bottom.clear(); @@ -101,12 +102,12 @@ void NodeBox::serialize(std::ostream &os, u16 protocol_version) const case NODEBOX_CONNECTED: writeU8(os, type); -#define WRITEBOX(box) \ - writeU16(os, (box).size()); \ - for (const aabb3f &i: (box)) { \ - writeV3F32(os, i.MinEdge); \ - writeV3F32(os, i.MaxEdge); \ - }; +#define WRITEBOX(box) \ + writeU16(os, (box).size()); \ + for (const aabb3f &i : (box)) { \ + writeV3F32(os, i.MinEdge); \ + writeV3F32(os, i.MaxEdge); \ + }; WRITEBOX(fixed); WRITEBOX(connect_top); @@ -140,35 +141,32 @@ void NodeBox::deSerialize(std::istream &is) type = (enum NodeBoxType)readU8(is); - if(type == NODEBOX_FIXED || type == NODEBOX_LEVELED) - { + if (type == NODEBOX_FIXED || type == NODEBOX_LEVELED) { u16 fixed_count = readU16(is); - while(fixed_count--) - { + while (fixed_count--) { aabb3f box; box.MinEdge = readV3F32(is); box.MaxEdge = readV3F32(is); fixed.push_back(box); } - } - else if(type == NODEBOX_WALLMOUNTED) - { + } else if (type == NODEBOX_WALLMOUNTED) { wall_top.MinEdge = readV3F32(is); wall_top.MaxEdge = readV3F32(is); wall_bottom.MinEdge = readV3F32(is); wall_bottom.MaxEdge = readV3F32(is); wall_side.MinEdge = readV3F32(is); wall_side.MaxEdge = readV3F32(is); + } else if (type == NODEBOX_CONNECTED) { +#define READBOXES(box) \ + { \ + count = readU16(is); \ + (box).reserve(count); \ + while (count--) { \ + v3f min = readV3F32(is); \ + v3f max = readV3F32(is); \ + (box).emplace_back(min, max); \ + }; \ } - else if (type == NODEBOX_CONNECTED) - { -#define READBOXES(box) { \ - count = readU16(is); \ - (box).reserve(count); \ - while (count--) { \ - v3f min = readV3F32(is); \ - v3f max = readV3F32(is); \ - (box).emplace_back(min, max); }; } u16 count; @@ -194,12 +192,12 @@ void NodeBox::deSerialize(std::istream &is) TileDef */ -#define TILE_FLAG_BACKFACE_CULLING (1 << 0) -#define TILE_FLAG_TILEABLE_HORIZONTAL (1 << 1) -#define TILE_FLAG_TILEABLE_VERTICAL (1 << 2) -#define TILE_FLAG_HAS_COLOR (1 << 3) -#define TILE_FLAG_HAS_SCALE (1 << 4) -#define TILE_FLAG_HAS_ALIGN_STYLE (1 << 5) +#define TILE_FLAG_BACKFACE_CULLING (1 << 0) +#define TILE_FLAG_TILEABLE_HORIZONTAL (1 << 1) +#define TILE_FLAG_TILEABLE_VERTICAL (1 << 2) +#define TILE_FLAG_HAS_COLOR (1 << 3) +#define TILE_FLAG_HAS_SCALE (1 << 4) +#define TILE_FLAG_HAS_ALIGN_STYLE (1 << 5) void TileDef::serialize(std::ostream &os, u16 protocol_version) const { @@ -235,8 +233,8 @@ void TileDef::serialize(std::ostream &os, u16 protocol_version) const writeU8(os, align_style); } -void TileDef::deSerialize(std::istream &is, u8 contentfeatures_version, - NodeDrawType drawtype) +void TileDef::deSerialize( + std::istream &is, u8 contentfeatures_version, NodeDrawType drawtype) { int version = readU8(is); if (version < 6) @@ -264,16 +262,16 @@ void TileDef::deSerialize(std::istream &is, u8 contentfeatures_version, void TextureSettings::readSettings() { - connected_glass = g_settings->getBool("connected_glass"); - opaque_water = g_settings->getBool("opaque_water"); - bool enable_shaders = g_settings->getBool("enable_shaders"); - bool enable_bumpmapping = g_settings->getBool("enable_bumpmapping"); + connected_glass = g_settings->getBool("connected_glass"); + opaque_water = g_settings->getBool("opaque_water"); + bool enable_shaders = g_settings->getBool("enable_shaders"); + bool enable_bumpmapping = g_settings->getBool("enable_bumpmapping"); bool enable_parallax_occlusion = g_settings->getBool("enable_parallax_occlusion"); - bool smooth_lighting = g_settings->getBool("smooth_lighting"); - enable_mesh_cache = g_settings->getBool("enable_mesh_cache"); - enable_minimap = g_settings->getBool("enable_minimap"); - node_texture_size = g_settings->getU16("texture_min_size"); - std::string leaves_style_str = g_settings->get("leaves_style"); + bool smooth_lighting = g_settings->getBool("smooth_lighting"); + enable_mesh_cache = g_settings->getBool("enable_mesh_cache"); + enable_minimap = g_settings->getBool("enable_minimap"); + node_texture_size = g_settings->getU16("texture_min_size"); + std::string leaves_style_str = g_settings->get("leaves_style"); std::string world_aligned_mode_str = g_settings->get("world_aligned_mode"); std::string autoscale_mode_str = g_settings->get("autoscale_mode"); @@ -282,7 +280,7 @@ void TextureSettings::readSettings() enable_mesh_cache = false; use_normal_texture = enable_shaders && - (enable_bumpmapping || enable_parallax_occlusion); + (enable_bumpmapping || enable_parallax_occlusion); if (leaves_style_str == "fancy") { leaves_style = LEAVES_FANCY; } else if (leaves_style_str == "simple") { @@ -388,7 +386,7 @@ void ContentFeatures::reset() liquid_alternative_source_id = CONTENT_IGNORE; liquid_viscosity = 0; liquid_renewable = true; - liquid_range = LIQUID_LEVEL_MAX+1; + liquid_range = LIQUID_LEVEL_MAX + 1; drowning = 0; light_source = 0; damage_per_second = 0; @@ -527,11 +525,11 @@ void ContentFeatures::deSerialize(std::istream &is) int value = readS16(is); groups[name] = value; } - param_type = (enum ContentParamType) readU8(is); - param_type_2 = (enum ContentParamType2) readU8(is); + param_type = (enum ContentParamType)readU8(is); + param_type_2 = (enum ContentParamType2)readU8(is); // visual - drawtype = (enum NodeDrawType) readU8(is); + drawtype = (enum NodeDrawType)readU8(is); mesh = deSerializeString(is); visual_scale = readF32(is); if (readU8(is) != 6) @@ -577,7 +575,7 @@ void ContentFeatures::deSerialize(std::istream &is) damage_per_second = readU32(is); // liquid - liquid_type = (enum LiquidType) readU8(is); + liquid_type = (enum LiquidType)readU8(is); liquid_alternative_flowing = deSerializeString(is); liquid_alternative_source = deSerializeString(is); liquid_viscosity = readU8(is); @@ -606,22 +604,22 @@ void ContentFeatures::deSerialize(std::istream &is) if (is.eof()) /* readU8 doesn't throw exceptions so we have to do this */ throw SerializationError(""); leveled_max = tmp_leveled_max; - } catch(SerializationError &e) {}; + } catch (SerializationError &e) { + }; } #ifndef SERVER -static void fillTileAttribs(ITextureSource *tsrc, TileLayer *layer, - const TileSpec &tile, const TileDef &tiledef, video::SColor color, - u8 material_type, u32 shader_id, bool backface_culling, - const TextureSettings &tsettings) +static void fillTileAttribs(ITextureSource *tsrc, TileLayer *layer, const TileSpec &tile, + const TileDef &tiledef, video::SColor color, u8 material_type, + u32 shader_id, bool backface_culling, const TextureSettings &tsettings) { - layer->shader_id = shader_id; - layer->texture = tsrc->getTextureForMesh(tiledef.name, &layer->texture_id); + layer->shader_id = shader_id; + layer->texture = tsrc->getTextureForMesh(tiledef.name, &layer->texture_id); layer->material_type = material_type; bool has_scale = tiledef.scale > 0; bool use_autoscale = tsettings.autoscale_mode == AUTOSCALE_FORCE || - (tsettings.autoscale_mode == AUTOSCALE_ENABLE && !has_scale); + (tsettings.autoscale_mode == AUTOSCALE_ENABLE && !has_scale); if (use_autoscale && layer->texture) { auto texture_size = layer->texture->getOriginalSize(); float base_size = tsettings.node_texture_size; @@ -639,7 +637,8 @@ static void fillTileAttribs(ITextureSource *tsrc, TileLayer *layer, if (tsettings.use_normal_texture) { layer->normal_texture = tsrc->getNormalTexture(tiledef.name); } - layer->flags_texture = tsrc->getShaderFlagsTexture(layer->normal_texture ? true : false); + layer->flags_texture = + tsrc->getShaderFlagsTexture(layer->normal_texture ? true : false); // Material flags layer->material_flags = 0; @@ -684,10 +683,11 @@ static void fillTileAttribs(ITextureSource *tsrc, TileLayer *layer, os.str(""); os << tiledef.name; - tiledef.animation.getTextureModifer(os, - layer->texture->getOriginalSize(), i); + tiledef.animation.getTextureModifer( + os, layer->texture->getOriginalSize(), i); - frame.texture = tsrc->getTextureForMesh(os.str(), &frame.texture_id); + frame.texture = tsrc->getTextureForMesh( + os.str(), &frame.texture_id); if (layer->normal_texture) frame.normal_texture = tsrc->getNormalTexture(os.str()); frame.flags_texture = layer->flags_texture; @@ -714,7 +714,8 @@ bool isWorldAligned(AlignStyle style, WorldAlignMode mode, NodeDrawType drawtype } void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc, - scene::IMeshManipulator *meshmanip, Client *client, const TextureSettings &tsettings) + scene::IMeshManipulator *meshmanip, Client *client, + const TextureSettings &tsettings) { // minimap pixel color - the average color of a texture if (tsettings.enable_minimap && !tiledef[0].name.empty()) @@ -738,14 +739,13 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc bool is_liquid = false; - u8 material_type = (alpha == 255) ? - TILE_MATERIAL_BASIC : TILE_MATERIAL_ALPHA; + u8 material_type = (alpha == 255) ? TILE_MATERIAL_BASIC : TILE_MATERIAL_ALPHA; switch (drawtype) { default: case NDT_NORMAL: - material_type = (alpha == 255) ? - TILE_MATERIAL_OPAQUE : TILE_MATERIAL_ALPHA; + material_type = (alpha == 255) ? TILE_MATERIAL_OPAQUE + : TILE_MATERIAL_ALPHA; solidness = 2; break; case NDT_AIRLIKE: @@ -776,7 +776,8 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc case NDT_GLASSLIKE_FRAMED_OPTIONAL: solidness = 0; visual_solidness = 1; - drawtype = tsettings.connected_glass ? NDT_GLASSLIKE_FRAMED : NDT_GLASSLIKE; + drawtype = tsettings.connected_glass ? NDT_GLASSLIKE_FRAMED + : NDT_GLASSLIKE; break; case NDT_ALLFACES: solidness = 0; @@ -840,11 +841,11 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc correctAlpha(tdef_spec, CF_SPECIAL_COUNT); if (waving == 3) { - material_type = (alpha == 255) ? TILE_MATERIAL_WAVING_LIQUID_OPAQUE : - TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT; + material_type = (alpha == 255) ? TILE_MATERIAL_WAVING_LIQUID_OPAQUE + : TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT; } else { - material_type = (alpha == 255) ? TILE_MATERIAL_LIQUID_OPAQUE : - TILE_MATERIAL_LIQUID_TRANSPARENT; + material_type = (alpha == 255) ? TILE_MATERIAL_LIQUID_OPAQUE + : TILE_MATERIAL_LIQUID_TRANSPARENT; } } @@ -856,19 +857,21 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc else if (overlay_material == TILE_MATERIAL_LIQUID_OPAQUE) overlay_material = TILE_MATERIAL_LIQUID_TRANSPARENT; - u32 overlay_shader = shdsrc->getShader("nodes_shader", overlay_material, drawtype); + u32 overlay_shader = + shdsrc->getShader("nodes_shader", overlay_material, drawtype); // Tiles (fill in f->tiles[]) for (u16 j = 0; j < 6; j++) { tiles[j].world_aligned = isWorldAligned(tdef[j].align_style, tsettings.world_aligned_mode, drawtype); - fillTileAttribs(tsrc, &tiles[j].layers[0], tiles[j], tdef[j], - color, material_type, tile_shader, - tdef[j].backface_culling, tsettings); + fillTileAttribs(tsrc, &tiles[j].layers[0], tiles[j], tdef[j], color, + material_type, tile_shader, tdef[j].backface_culling, + tsettings); if (!tdef_overlay[j].name.empty()) - fillTileAttribs(tsrc, &tiles[j].layers[1], tiles[j], tdef_overlay[j], - color, overlay_material, overlay_shader, - tdef[j].backface_culling, tsettings); + fillTileAttribs(tsrc, &tiles[j].layers[1], tiles[j], + tdef_overlay[j], color, overlay_material, + overlay_shader, tdef[j].backface_culling, + tsettings); } u8 special_material = material_type; @@ -878,16 +881,16 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc else if (waving == 2) special_material = TILE_MATERIAL_WAVING_LEAVES; } - u32 special_shader = shdsrc->getShader("nodes_shader", special_material, drawtype); + u32 special_shader = + shdsrc->getShader("nodes_shader", special_material, drawtype); // Special tiles (fill in f->special_tiles[]) for (u16 j = 0; j < CF_SPECIAL_COUNT; j++) - fillTileAttribs(tsrc, &special_tiles[j].layers[0], special_tiles[j], tdef_spec[j], - color, special_material, special_shader, + fillTileAttribs(tsrc, &special_tiles[j].layers[0], special_tiles[j], + tdef_spec[j], color, special_material, special_shader, tdef_spec[j].backface_culling, tsettings); - if (param_type_2 == CPT2_COLOR || - param_type_2 == CPT2_COLORED_FACEDIR || + if (param_type_2 == CPT2_COLOR || param_type_2 == CPT2_COLORED_FACEDIR || param_type_2 == CPT2_COLORED_WALLMOUNTED) palette = tsrc->getPalette(palette_name); @@ -895,7 +898,7 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc // Meshnode drawtype // Read the mesh and apply scale mesh_ptr[0] = client->getMesh(mesh); - if (mesh_ptr[0]){ + if (mesh_ptr[0]) { v3f scale = v3f(1.0, 1.0, 1.0) * BS * visual_scale; scaleMesh(mesh_ptr[0], scale); recalculateBoundingBox(mesh_ptr[0]); @@ -903,20 +906,20 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc } } - //Cache 6dfacedir and wallmounted rotated clones of meshes + // Cache 6dfacedir and wallmounted rotated clones of meshes if (tsettings.enable_mesh_cache && mesh_ptr[0] && - (param_type_2 == CPT2_FACEDIR - || param_type_2 == CPT2_COLORED_FACEDIR)) { + (param_type_2 == CPT2_FACEDIR || + param_type_2 == CPT2_COLORED_FACEDIR)) { for (u16 j = 1; j < 24; j++) { mesh_ptr[j] = cloneMesh(mesh_ptr[0]); rotateMeshBy6dFacedir(mesh_ptr[j], j); recalculateBoundingBox(mesh_ptr[j]); meshmanip->recalculateNormals(mesh_ptr[j], true, false); } - } else if (tsettings.enable_mesh_cache && mesh_ptr[0] - && (param_type_2 == CPT2_WALLMOUNTED || - param_type_2 == CPT2_COLORED_WALLMOUNTED)) { - static const u8 wm_to_6d[6] = { 20, 0, 16 + 1, 12 + 3, 8, 4 + 2 }; + } else if (tsettings.enable_mesh_cache && mesh_ptr[0] && + (param_type_2 == CPT2_WALLMOUNTED || + param_type_2 == CPT2_COLORED_WALLMOUNTED)) { + static const u8 wm_to_6d[6] = {20, 0, 16 + 1, 12 + 3, 8, 4 + 2}; for (u16 j = 1; j < 6; j++) { mesh_ptr[j] = cloneMesh(mesh_ptr[0]); rotateMeshBy6dFacedir(mesh_ptr[j], wm_to_6d[j]); @@ -934,15 +937,11 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc NodeDefManager */ - - - NodeDefManager::NodeDefManager() { clear(); } - NodeDefManager::~NodeDefManager() { #ifndef SERVER @@ -955,7 +954,6 @@ NodeDefManager::~NodeDefManager() #endif } - void NodeDefManager::clear() { m_content_features.clear(); @@ -963,8 +961,8 @@ void NodeDefManager::clear() m_name_id_mapping_with_aliases.clear(); m_group_to_items.clear(); m_next_id = 0; - m_selection_box_union.reset(0,0,0); - m_selection_box_int_union.reset(0,0,0); + m_selection_box_union.reset(0, 0, 0); + m_selection_box_int_union.reset(0, 0, 0); resetNodeResolveState(); @@ -987,17 +985,17 @@ void NodeDefManager::clear() // Set CONTENT_AIR { ContentFeatures f; - f.name = "air"; - f.drawtype = NDT_AIRLIKE; - f.param_type = CPT_LIGHT; - f.light_propagates = true; + f.name = "air"; + f.drawtype = NDT_AIRLIKE; + f.param_type = CPT_LIGHT; + f.light_propagates = true; f.sunlight_propagates = true; - f.walkable = false; - f.pointable = false; - f.diggable = false; - f.buildable_to = true; - f.floodable = true; - f.is_ground_content = true; + f.walkable = false; + f.pointable = false; + f.diggable = false; + f.buildable_to = true; + f.floodable = true; + f.is_ground_content = true; // Insert directly into containers content_t c = CONTENT_AIR; m_content_features[c] = f; @@ -1007,16 +1005,16 @@ void NodeDefManager::clear() // Set CONTENT_IGNORE { ContentFeatures f; - f.name = "ignore"; - f.drawtype = NDT_AIRLIKE; - f.param_type = CPT_NONE; - f.light_propagates = false; + f.name = "ignore"; + f.drawtype = NDT_AIRLIKE; + f.param_type = CPT_NONE; + f.light_propagates = false; f.sunlight_propagates = false; - f.walkable = false; - f.pointable = false; - f.diggable = false; - f.buildable_to = true; // A way to remove accidental CONTENT_IGNOREs - f.is_ground_content = true; + f.walkable = false; + f.pointable = false; + f.diggable = false; + f.buildable_to = true; // A way to remove accidental CONTENT_IGNOREs + f.is_ground_content = true; // Insert directly into containers content_t c = CONTENT_IGNORE; m_content_features[c] = f; @@ -1024,18 +1022,16 @@ void NodeDefManager::clear() } } - bool NodeDefManager::getId(const std::string &name, content_t &result) const { - std::unordered_map::const_iterator - i = m_name_id_mapping_with_aliases.find(name); - if(i == m_name_id_mapping_with_aliases.end()) + std::unordered_map::const_iterator i = + m_name_id_mapping_with_aliases.find(name); + if (i == m_name_id_mapping_with_aliases.end()) return false; result = i->second; return true; } - content_t NodeDefManager::getId(const std::string &name) const { content_t id = CONTENT_IGNORE; @@ -1043,12 +1039,10 @@ content_t NodeDefManager::getId(const std::string &name) const return id; } - -bool NodeDefManager::getIds(const std::string &name, - std::vector &result) const +bool NodeDefManager::getIds(const std::string &name, std::vector &result) const { - //TimeTaker t("getIds", NULL, PRECISION_MICRO); - if (name.substr(0,6) != "group:") { + // TimeTaker t("getIds", NULL, PRECISION_MICRO); + if (name.substr(0, 6) != "group:") { content_t id = CONTENT_IGNORE; bool exists = getId(name, id); if (exists) @@ -1057,31 +1051,28 @@ bool NodeDefManager::getIds(const std::string &name, } std::string group = name.substr(6); - std::unordered_map>::const_iterator - i = m_group_to_items.find(group); + std::unordered_map>::const_iterator i = + m_group_to_items.find(group); if (i == m_group_to_items.end()) return true; const std::vector &items = i->second; result.insert(result.end(), items.begin(), items.end()); - //printf("getIds: %dus\n", t.stop()); + // printf("getIds: %dus\n", t.stop()); return true; } - -const ContentFeatures& NodeDefManager::get(const std::string &name) const +const ContentFeatures &NodeDefManager::get(const std::string &name) const { content_t id = CONTENT_UNKNOWN; getId(name, id); return get(id); } - // returns CONTENT_IGNORE if no free ID found content_t NodeDefManager::allocateId() { - for (content_t id = m_next_id; - id >= m_next_id; // overflow? + for (content_t id = m_next_id; id >= m_next_id; // overflow? ++id) { while (id >= m_content_features.size()) { m_content_features.emplace_back(); @@ -1097,7 +1088,6 @@ content_t NodeDefManager::allocateId() return CONTENT_IGNORE; } - /*! * Returns the smallest box that contains all boxes * in the vector. Box_union is expanded. @@ -1111,7 +1101,6 @@ void boxVectorUnion(const std::vector &boxes, aabb3f *box_union) } } - /*! * Returns a box that contains the nodebox in every case. * The argument node_union is expanded. @@ -1121,52 +1110,27 @@ void boxVectorUnion(const std::vector &boxes, aabb3f *box_union) * @param[in, out] box_union the union of the arguments */ void getNodeBoxUnion(const NodeBox &nodebox, const ContentFeatures &features, - aabb3f *box_union) + aabb3f *box_union) { - switch(nodebox.type) { - case NODEBOX_FIXED: - case NODEBOX_LEVELED: { - // Raw union - aabb3f half_processed(0, 0, 0, 0, 0, 0); - boxVectorUnion(nodebox.fixed, &half_processed); - // Set leveled boxes to maximal - if (nodebox.type == NODEBOX_LEVELED) { - half_processed.MaxEdge.Y = +BS / 2; - } - if (features.param_type_2 == CPT2_FACEDIR || - features.param_type_2 == CPT2_COLORED_FACEDIR) { - // Get maximal coordinate - f32 coords[] = { - fabsf(half_processed.MinEdge.X), + switch (nodebox.type) { + case NODEBOX_FIXED: + case NODEBOX_LEVELED: { + // Raw union + aabb3f half_processed(0, 0, 0, 0, 0, 0); + boxVectorUnion(nodebox.fixed, &half_processed); + // Set leveled boxes to maximal + if (nodebox.type == NODEBOX_LEVELED) { + half_processed.MaxEdge.Y = +BS / 2; + } + if (features.param_type_2 == CPT2_FACEDIR || + features.param_type_2 == CPT2_COLORED_FACEDIR) { + // Get maximal coordinate + f32 coords[] = {fabsf(half_processed.MinEdge.X), fabsf(half_processed.MinEdge.Y), fabsf(half_processed.MinEdge.Z), fabsf(half_processed.MaxEdge.X), fabsf(half_processed.MaxEdge.Y), - fabsf(half_processed.MaxEdge.Z) }; - f32 max = 0; - for (float coord : coords) { - if (max < coord) { - max = coord; - } - } - // Add the union of all possible rotated boxes - box_union->addInternalPoint(-max, -max, -max); - box_union->addInternalPoint(+max, +max, +max); - } else { - box_union->addInternalBox(half_processed); - } - break; - } - case NODEBOX_WALLMOUNTED: { - // Add fix boxes - box_union->addInternalBox(nodebox.wall_top); - box_union->addInternalBox(nodebox.wall_bottom); - // Find maximal coordinate in the X-Z plane - f32 coords[] = { - fabsf(nodebox.wall_side.MinEdge.X), - fabsf(nodebox.wall_side.MinEdge.Z), - fabsf(nodebox.wall_side.MaxEdge.X), - fabsf(nodebox.wall_side.MaxEdge.Z) }; + fabsf(half_processed.MaxEdge.Z)}; f32 max = 0; for (float coord : coords) { if (max < coord) { @@ -1174,55 +1138,76 @@ void getNodeBoxUnion(const NodeBox &nodebox, const ContentFeatures &features, } } // Add the union of all possible rotated boxes - box_union->addInternalPoint(-max, nodebox.wall_side.MinEdge.Y, -max); - box_union->addInternalPoint(max, nodebox.wall_side.MaxEdge.Y, max); - break; + box_union->addInternalPoint(-max, -max, -max); + box_union->addInternalPoint(+max, +max, +max); + } else { + box_union->addInternalBox(half_processed); } - case NODEBOX_CONNECTED: { - // Add all possible connected boxes - boxVectorUnion(nodebox.fixed, box_union); - boxVectorUnion(nodebox.connect_top, box_union); - boxVectorUnion(nodebox.connect_bottom, box_union); - boxVectorUnion(nodebox.connect_front, box_union); - boxVectorUnion(nodebox.connect_left, box_union); - boxVectorUnion(nodebox.connect_back, box_union); - boxVectorUnion(nodebox.connect_right, box_union); - boxVectorUnion(nodebox.disconnected_top, box_union); - boxVectorUnion(nodebox.disconnected_bottom, box_union); - boxVectorUnion(nodebox.disconnected_front, box_union); - boxVectorUnion(nodebox.disconnected_left, box_union); - boxVectorUnion(nodebox.disconnected_back, box_union); - boxVectorUnion(nodebox.disconnected_right, box_union); - boxVectorUnion(nodebox.disconnected, box_union); - boxVectorUnion(nodebox.disconnected_sides, box_union); - break; - } - default: { - // NODEBOX_REGULAR - box_union->addInternalPoint(-BS / 2, -BS / 2, -BS / 2); - box_union->addInternalPoint(+BS / 2, +BS / 2, +BS / 2); + break; + } + case NODEBOX_WALLMOUNTED: { + // Add fix boxes + box_union->addInternalBox(nodebox.wall_top); + box_union->addInternalBox(nodebox.wall_bottom); + // Find maximal coordinate in the X-Z plane + f32 coords[] = {fabsf(nodebox.wall_side.MinEdge.X), + fabsf(nodebox.wall_side.MinEdge.Z), + fabsf(nodebox.wall_side.MaxEdge.X), + fabsf(nodebox.wall_side.MaxEdge.Z)}; + f32 max = 0; + for (float coord : coords) { + if (max < coord) { + max = coord; + } } + // Add the union of all possible rotated boxes + box_union->addInternalPoint(-max, nodebox.wall_side.MinEdge.Y, -max); + box_union->addInternalPoint(max, nodebox.wall_side.MaxEdge.Y, max); + break; + } + case NODEBOX_CONNECTED: { + // Add all possible connected boxes + boxVectorUnion(nodebox.fixed, box_union); + boxVectorUnion(nodebox.connect_top, box_union); + boxVectorUnion(nodebox.connect_bottom, box_union); + boxVectorUnion(nodebox.connect_front, box_union); + boxVectorUnion(nodebox.connect_left, box_union); + boxVectorUnion(nodebox.connect_back, box_union); + boxVectorUnion(nodebox.connect_right, box_union); + boxVectorUnion(nodebox.disconnected_top, box_union); + boxVectorUnion(nodebox.disconnected_bottom, box_union); + boxVectorUnion(nodebox.disconnected_front, box_union); + boxVectorUnion(nodebox.disconnected_left, box_union); + boxVectorUnion(nodebox.disconnected_back, box_union); + boxVectorUnion(nodebox.disconnected_right, box_union); + boxVectorUnion(nodebox.disconnected, box_union); + boxVectorUnion(nodebox.disconnected_sides, box_union); + break; + } + default: { + // NODEBOX_REGULAR + box_union->addInternalPoint(-BS / 2, -BS / 2, -BS / 2); + box_union->addInternalPoint(+BS / 2, +BS / 2, +BS / 2); + } } } - inline void NodeDefManager::fixSelectionBoxIntUnion() { - m_selection_box_int_union.MinEdge.X = floorf( - m_selection_box_union.MinEdge.X / BS + 0.5f); - m_selection_box_int_union.MinEdge.Y = floorf( - m_selection_box_union.MinEdge.Y / BS + 0.5f); - m_selection_box_int_union.MinEdge.Z = floorf( - m_selection_box_union.MinEdge.Z / BS + 0.5f); - m_selection_box_int_union.MaxEdge.X = ceilf( - m_selection_box_union.MaxEdge.X / BS - 0.5f); - m_selection_box_int_union.MaxEdge.Y = ceilf( - m_selection_box_union.MaxEdge.Y / BS - 0.5f); - m_selection_box_int_union.MaxEdge.Z = ceilf( - m_selection_box_union.MaxEdge.Z / BS - 0.5f); + m_selection_box_int_union.MinEdge.X = + floorf(m_selection_box_union.MinEdge.X / BS + 0.5f); + m_selection_box_int_union.MinEdge.Y = + floorf(m_selection_box_union.MinEdge.Y / BS + 0.5f); + m_selection_box_int_union.MinEdge.Z = + floorf(m_selection_box_union.MinEdge.Z / BS + 0.5f); + m_selection_box_int_union.MaxEdge.X = + ceilf(m_selection_box_union.MaxEdge.X / BS - 0.5f); + m_selection_box_int_union.MaxEdge.Y = + ceilf(m_selection_box_union.MaxEdge.Y / BS - 0.5f); + m_selection_box_int_union.MaxEdge.Z = + ceilf(m_selection_box_union.MaxEdge.Z / BS - 0.5f); } - void NodeDefManager::eraseIdFromGroups(content_t id) { // For all groups in m_group_to_items... @@ -1242,21 +1227,20 @@ void NodeDefManager::eraseIdFromGroups(content_t id) } } - // IWritableNodeDefManager content_t NodeDefManager::set(const std::string &name, const ContentFeatures &d) { ContentFeatures def = d; - + // Pre-conditions assert(name != ""); assert(name != "ignore"); assert(name == def.name); content_t id = CONTENT_IGNORE; - + if (m_name_id_mapping.getId(name, id)) { -#ifndef SERVER +#ifndef SERVER ContentFeatures old_def = get(name); for (u32 j = 0; j < 6; j++) if (def.tiledef[j].name.empty()) @@ -1273,7 +1257,8 @@ content_t NodeDefManager::set(const std::string &name, const ContentFeatures &d) id = allocateId(); if (id == CONTENT_IGNORE) { warningstream << "NodeDefManager: Absolute " - "limit reached" << std::endl; + "limit reached" + << std::endl; return CONTENT_IGNORE; } assert(id != CONTENT_IGNORE); @@ -1286,7 +1271,7 @@ content_t NodeDefManager::set(const std::string &name, const ContentFeatures &d) m_content_features[id] = def; verbosestream << "NodeDefManager: registering content id \"" << id - << "\": name=\"" << def.name << "\""< all; @@ -1335,8 +1317,7 @@ void NodeDefManager::updateAliases(IItemDefManager *idef) const std::string &convert_to = idef->getAlias(name); content_t id; if (m_name_id_mapping.getId(convert_to, id)) { - m_name_id_mapping_with_aliases.insert( - std::make_pair(name, id)); + m_name_id_mapping_with_aliases.insert(std::make_pair(name, id)); } } } @@ -1344,9 +1325,10 @@ void NodeDefManager::updateAliases(IItemDefManager *idef) void NodeDefManager::applyTextureOverrides(const std::vector &overrides) { infostream << "NodeDefManager::applyTextureOverrides(): Applying " - "overrides to textures" << std::endl; + "overrides to textures" + << std::endl; - for (const TextureOverride& texture_override : overrides) { + for (const TextureOverride &texture_override : overrides) { content_t id; if (!getId(texture_override.id, id)) continue; // Ignore unknown node @@ -1374,18 +1356,20 @@ void NodeDefManager::applyTextureOverrides(const std::vector &o } void NodeDefManager::updateTextures(IGameDef *gamedef, - void (*progress_callback)(void *progress_args, u32 progress, u32 max_progress), - void *progress_callback_args) + void (*progress_callback)( + void *progress_args, u32 progress, u32 max_progress), + void *progress_callback_args) { #ifndef SERVER infostream << "NodeDefManager::updateTextures(): Updating " - "textures in node definitions" << std::endl; + "textures in node definitions" + << std::endl; Client *client = (Client *)gamedef; ITextureSource *tsrc = client->tsrc(); IShaderSource *shdsrc = client->getShaderSource(); scene::IMeshManipulator *meshmanip = - RenderingEngine::get_scene_manager()->getMeshManipulator(); + RenderingEngine::get_scene_manager()->getMeshManipulator(); TextureSettings tsettings; tsettings.readSettings(); @@ -1405,8 +1389,7 @@ void NodeDefManager::serialize(std::ostream &os, u16 protocol_version) const u16 count = 0; std::ostringstream os2(std::ios::binary); for (u32 i = 0; i < m_content_features.size(); i++) { - if (i == CONTENT_IGNORE || i == CONTENT_AIR - || i == CONTENT_UNKNOWN) + if (i == CONTENT_IGNORE || i == CONTENT_AIR || i == CONTENT_UNKNOWN) continue; const ContentFeatures *f = &m_content_features[i]; if (f->name.empty()) @@ -1416,7 +1399,7 @@ void NodeDefManager::serialize(std::ostream &os, u16 protocol_version) const // strict version incompatibilities std::ostringstream wrapper_os(std::ios::binary); f->serialize(wrapper_os, protocol_version); - os2<m_ndef = this; @@ -1504,7 +1486,6 @@ void NodeDefManager::pendNodeResolve(NodeResolver *nr) const m_pending_resolve_callbacks.push_back(nr); } - bool NodeDefManager::cancelNodeResolveCallback(NodeResolver *nr) const { size_t len = m_pending_resolve_callbacks.size(); @@ -1521,7 +1502,6 @@ bool NodeDefManager::cancelNodeResolveCallback(NodeResolver *nr) const return false; } - void NodeDefManager::runNodeResolveCallbacks() { for (size_t i = 0; i != m_pending_resolve_callbacks.size(); i++) { @@ -1532,7 +1512,6 @@ void NodeDefManager::runNodeResolveCallbacks() m_pending_resolve_callbacks.clear(); } - void NodeDefManager::resetNodeResolveState() { m_node_registration_complete = false; @@ -1550,8 +1529,10 @@ void NodeDefManager::resolveCrossrefs() { for (ContentFeatures &f : m_content_features) { if (f.liquid_type != LIQUID_NONE) { - f.liquid_alternative_flowing_id = getId(f.liquid_alternative_flowing); - f.liquid_alternative_source_id = getId(f.liquid_alternative_source); + f.liquid_alternative_flowing_id = + getId(f.liquid_alternative_flowing); + f.liquid_alternative_source_id = + getId(f.liquid_alternative_source); continue; } if (f.drawtype != NDT_NODEBOX || f.node_box.type != NODEBOX_CONNECTED) @@ -1564,8 +1545,7 @@ void NodeDefManager::resolveCrossrefs() } } -bool NodeDefManager::nodeboxConnects(MapNode from, MapNode to, - u8 connect_face) const +bool NodeDefManager::nodeboxConnects(MapNode from, MapNode to, u8 connect_face) const { const ContentFeatures &f1 = get(from); @@ -1585,22 +1565,24 @@ bool NodeDefManager::nodeboxConnects(MapNode from, MapNode to, // does to node declare usable faces? if (f2.connect_sides > 0) { if ((f2.param_type_2 == CPT2_FACEDIR || - f2.param_type_2 == CPT2_COLORED_FACEDIR) - && (connect_face >= 4)) { - static const u8 rot[33 * 4] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 4, 32, 16, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, // 4 - back - 8, 4, 32, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, // 8 - right - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 8, 4, 32, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, // 16 - front - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 32, 16, 8, 4 // 32 - left - }; - return (f2.connect_sides - & rot[(connect_face * 4) + (to.param2 & 0x1F)]); + f2.param_type_2 == CPT2_COLORED_FACEDIR) && + (connect_face >= 4)) { + static const u8 rot[33 * 4] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, + 32, 16, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, // 4 - back + 8, 4, 32, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, // 8 - right + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 16, 8, 4, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, // 16 - front + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 16, + 8, 4 // 32 - left + }; + return (f2.connect_sides & + rot[(connect_face * 4) + (to.param2 & 0x1F)]); } return (f2.connect_sides & connect_face); } @@ -1618,18 +1600,16 @@ NodeResolver::NodeResolver() m_nnlistsizes.reserve(4); } - NodeResolver::~NodeResolver() { if (!m_resolve_done && m_ndef) m_ndef->cancelNodeResolveCallback(this); } - void NodeResolver::cloneTo(NodeResolver *res) const { FATAL_ERROR_IF(!m_resolve_done, "NodeResolver can only be cloned" - " after resolving has completed"); + " after resolving has completed"); /* We don't actually do anything significant. Since the node resolving has * already completed, the class that called us will already have the * resolved IDs in its data structures (which it copies on its own) */ @@ -1637,10 +1617,9 @@ void NodeResolver::cloneTo(NodeResolver *res) const res->m_resolve_done = true; } - void NodeResolver::nodeResolveInternal() { - m_nodenames_idx = 0; + m_nodenames_idx = 0; m_nnlistsizes_idx = 0; resolveNodeNames(); @@ -1650,9 +1629,8 @@ void NodeResolver::nodeResolveInternal() m_nnlistsizes.clear(); } - -bool NodeResolver::getIdFromNrBacklog(content_t *result_out, - const std::string &node_alt, content_t c_fallback, bool error_on_fallback) +bool NodeResolver::getIdFromNrBacklog(content_t *result_out, const std::string &node_alt, + content_t c_fallback, bool error_on_fallback) { if (m_nodenames_idx == m_nodenames.size()) { *result_out = c_fallback; @@ -1671,8 +1649,8 @@ bool NodeResolver::getIdFromNrBacklog(content_t *result_out, if (!success) { if (error_on_fallback) - errorstream << "NodeResolver: failed to resolve node name '" << name - << "'." << std::endl; + errorstream << "NodeResolver: failed to resolve node name '" + << name << "'." << std::endl; c = c_fallback; } @@ -1680,9 +1658,8 @@ bool NodeResolver::getIdFromNrBacklog(content_t *result_out, return success; } - bool NodeResolver::getIdsFromNrBacklog(std::vector *result_out, - bool all_required, content_t c_fallback) + bool all_required, content_t c_fallback) { bool success = true; @@ -1702,12 +1679,13 @@ bool NodeResolver::getIdsFromNrBacklog(std::vector *result_out, content_t c; std::string &name = m_nodenames[m_nodenames_idx++]; - if (name.substr(0,6) != "group:") { + if (name.substr(0, 6) != "group:") { if (m_ndef->getId(name, c)) { result_out->push_back(c); } else if (all_required) { - errorstream << "NodeResolver: failed to resolve node name '" - << name << "'." << std::endl; + errorstream << "NodeResolver: failed to resolve node " + "name '" + << name << "'." << std::endl; result_out->push_back(c_fallback); success = false; } diff --git a/src/nodedef.h b/src/nodedef.h index cf03abaae..b1492588a 100644 --- a/src/nodedef.h +++ b/src/nodedef.h @@ -31,8 +31,8 @@ with this program; if not, write to the Free Software Foundation, Inc., class Client; #endif #include "itemgroup.h" -#include "sound.h" // SimpleSoundSpec -#include "constants.h" // BS +#include "sound.h" // SimpleSoundSpec +#include "constants.h" // BS #include "texture_override.h" // TextureOverride #include "tileanimation.h" @@ -87,10 +87,11 @@ enum LiquidType enum NodeBoxType { - NODEBOX_REGULAR, // Regular block; allows buildable_to - NODEBOX_FIXED, // Static separately defined box(es) + NODEBOX_REGULAR, // Regular block; allows buildable_to + NODEBOX_FIXED, // Static separately defined box(es) NODEBOX_WALLMOUNTED, // Box for wall mounted nodes; (top, bottom, side) - NODEBOX_LEVELED, // Same as fixed, but with dynamic height from param2. for snow, ... + NODEBOX_LEVELED, // Same as fixed, but with dynamic height from param2. for snow, + // ... NODEBOX_CONNECTED, // optionally draws nodeboxes if a neighbor node attaches }; @@ -120,8 +121,7 @@ struct NodeBox std::vector disconnected; std::vector disconnected_sides; - NodeBox() - { reset(); } + NodeBox() { reset(); } void reset(); void serialize(std::ostream &os, u16 protocol_version) const; @@ -131,26 +131,30 @@ struct NodeBox struct MapNode; class NodeMetadata; -enum LeavesStyle { +enum LeavesStyle +{ LEAVES_FANCY, LEAVES_SIMPLE, LEAVES_OPAQUE, }; -enum AutoScale : u8 { +enum AutoScale : u8 +{ AUTOSCALE_DISABLE, AUTOSCALE_ENABLE, AUTOSCALE_FORCE, }; -enum WorldAlignMode : u8 { +enum WorldAlignMode : u8 +{ WORLDALIGN_DISABLE, WORLDALIGN_ENABLE, WORLDALIGN_FORCE, WORLDALIGN_FORCE_NODEBOX, }; -class TextureSettings { +class TextureSettings +{ public: LeavesStyle leaves_style; WorldAlignMode world_aligned_mode; @@ -214,11 +218,12 @@ enum NodeDrawType }; // Mesh options for NDT_PLANTLIKE with CPT2_MESHOPTIONS -static const u8 MO_MASK_STYLE = 0x07; -static const u8 MO_BIT_RANDOM_OFFSET = 0x08; -static const u8 MO_BIT_SCALE_SQRT2 = 0x10; +static const u8 MO_MASK_STYLE = 0x07; +static const u8 MO_BIT_RANDOM_OFFSET = 0x08; +static const u8 MO_BIT_SCALE_SQRT2 = 0x10; static const u8 MO_BIT_RANDOM_OFFSET_Y = 0x20; -enum PlantlikeStyle { +enum PlantlikeStyle +{ PLANT_STYLE_CROSS, PLANT_STYLE_CROSS2, PLANT_STYLE_STAR, @@ -226,7 +231,8 @@ enum PlantlikeStyle { PLANT_STYLE_HASH2, }; -enum AlignStyle : u8 { +enum AlignStyle : u8 +{ ALIGN_STYLE_NODE, ALIGN_STYLE_WORLD, ALIGN_STYLE_USER_DEFINED, @@ -251,14 +257,11 @@ struct TileDef struct TileAnimationParams animation; - TileDef() - { - animation.type = TAT_NONE; - } + TileDef() { animation.type = TAT_NONE; } void serialize(std::ostream &os, u16 protocol_version) const; void deSerialize(std::istream &is, u8 contentfeatures_version, - NodeDrawType drawtype); + NodeDrawType drawtype); }; #define CF_SPECIAL_COUNT 6 @@ -275,7 +278,7 @@ struct ContentFeatures // Special tiles // - Currently used for flowing liquids TileSpec special_tiles[CF_SPECIAL_COUNT]; - u8 solidness; // Used when choosing which face is drawn + u8 solidness; // Used when choosing which face is drawn u8 visual_solidness; // When solidness=0, this tells how it looks like bool backface_culling; #endif @@ -291,7 +294,7 @@ struct ContentFeatures // --- GENERAL PROPERTIES --- - std::string name; // "" = undefined node + std::string name; // "" = undefined node ItemGroupList groups; // Same as in itemdef // Type of MapNode::param1 ContentParamType param_type; @@ -425,11 +428,11 @@ struct ContentFeatures /* Some handy methods */ - bool isLiquid() const{ - return (liquid_type != LIQUID_NONE); - } - bool sameLiquid(const ContentFeatures &f) const{ - if(!isLiquid() || !f.isLiquid()) return false; + bool isLiquid() const { return (liquid_type != LIQUID_NONE); } + bool sameLiquid(const ContentFeatures &f) const + { + if (!isLiquid() || !f.isLiquid()) + return false; return (liquid_alternative_flowing_id == f.liquid_alternative_flowing_id); } @@ -440,7 +443,8 @@ struct ContentFeatures #ifndef SERVER void updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc, - scene::IMeshManipulator *meshmanip, Client *client, const TextureSettings &tsettings); + scene::IMeshManipulator *meshmanip, Client *client, + const TextureSettings &tsettings); #endif }; @@ -456,7 +460,8 @@ struct ContentFeatures * functions only get `const` pointers to it, to prevent modification of * registered nodes. */ -class NodeDefManager { +class NodeDefManager +{ public: /*! * Creates a NodeDefManager, and registers three ContentFeatures: @@ -471,10 +476,11 @@ public: * @return properties of the given content type, or \ref CONTENT_UNKNOWN * if the given content type is not registered. */ - inline const ContentFeatures& get(content_t c) const { - return - c < m_content_features.size() ? - m_content_features[c] : m_content_features[CONTENT_UNKNOWN]; + inline const ContentFeatures &get(content_t c) const + { + return c < m_content_features.size() + ? m_content_features[c] + : m_content_features[CONTENT_UNKNOWN]; } /*! @@ -483,7 +489,8 @@ public: * @return properties of the given node or @ref CONTENT_UNKNOWN if the * given content type is not registered. */ - inline const ContentFeatures& get(const MapNode &n) const { + inline const ContentFeatures &get(const MapNode &n) const + { return get(n.getContent()); } @@ -493,7 +500,7 @@ public: * @return properties of the given node or @ref CONTENT_UNKNOWN if * not found */ - const ContentFeatures& get(const std::string &name) const; + const ContentFeatures &get(const std::string &name) const; /*! * Returns the content ID for the given name. @@ -526,7 +533,8 @@ public: * contains all nodes' selection boxes. The returned box might be larger * than the minimal size if the largest node is removed from the manager. */ - inline core::aabbox3d getSelectionBoxIntUnion() const { + inline core::aabbox3d getSelectionBoxIntUnion() const + { return m_selection_box_int_union; } @@ -539,8 +547,7 @@ public: * Bits: +y (least significant), -y, -z, -x, +z, +x (most significant). * @return true if the node connects, false otherwise */ - bool nodeboxConnects(MapNode from, MapNode to, - u8 connect_face) const; + bool nodeboxConnects(MapNode from, MapNode to, u8 connect_face) const; /*! * Registers a NodeResolver to wait for the registration of @@ -605,8 +612,9 @@ public: * @param progress_cbk_args passed to the callback function */ void updateTextures(IGameDef *gamedef, - void (*progress_cbk)(void *progress_args, u32 progress, u32 max_progress), - void *progress_cbk_args); + void (*progress_cbk)(void *progress_args, u32 progress, + u32 max_progress), + void *progress_cbk_args); /*! * Writes the content of this manager to the given output stream. @@ -625,7 +633,8 @@ public: * Used to indicate that node registration has finished. * @param completed tells whether registration is complete */ - inline void setNodeRegistrationStatus(bool completed) { + inline void setNodeRegistrationStatus(bool completed) + { m_node_registration_complete = completed; } @@ -738,7 +747,8 @@ private: NodeDefManager *createNodeDefManager(); -class NodeResolver { +class NodeResolver +{ public: NodeResolver(); virtual ~NodeResolver(); @@ -747,11 +757,10 @@ public: // required because this class is used as mixin for ObjDef void cloneTo(NodeResolver *res) const; - bool getIdFromNrBacklog(content_t *result_out, - const std::string &node_alt, content_t c_fallback, - bool error_on_fallback = true); + bool getIdFromNrBacklog(content_t *result_out, const std::string &node_alt, + content_t c_fallback, bool error_on_fallback = true); bool getIdsFromNrBacklog(std::vector *result_out, - bool all_required = false, content_t c_fallback = CONTENT_IGNORE); + bool all_required = false, content_t c_fallback = CONTENT_IGNORE); void nodeResolveInternal(); diff --git a/src/nodemetadata.cpp b/src/nodemetadata.cpp index b84ffc8cb..bab7a96af 100644 --- a/src/nodemetadata.cpp +++ b/src/nodemetadata.cpp @@ -31,9 +31,10 @@ with this program; if not, write to the Free Software Foundation, Inc., NodeMetadata */ -NodeMetadata::NodeMetadata(IItemDefManager *item_def_mgr): - m_inventory(new Inventory(item_def_mgr)) -{} +NodeMetadata::NodeMetadata(IItemDefManager *item_def_mgr) : + m_inventory(new Inventory(item_def_mgr)) +{ +} NodeMetadata::~NodeMetadata() { @@ -62,7 +63,7 @@ void NodeMetadata::deSerialize(std::istream &is, u8 version) { clear(); int num_vars = readU32(is); - for(int i=0; igetLists().empty(); } - void NodeMetadata::markPrivate(const std::string &name, bool set) { if (set) @@ -112,8 +112,8 @@ int NodeMetadata::countNonPrivate() const NodeMetadataList */ -void NodeMetadataList::serialize(std::ostream &os, u8 blockver, bool disk, - bool absolute_pos) const +void NodeMetadataList::serialize( + std::ostream &os, u8 blockver, bool disk, bool absolute_pos) const { /* Version 0 is a placeholder for "nothing to see here; go away." @@ -129,9 +129,7 @@ void NodeMetadataList::serialize(std::ostream &os, u8 blockver, bool disk, writeU8(os, version); writeU16(os, count); - for (NodeMetadataMap::const_iterator - i = m_data.begin(); - i != m_data.end(); ++i) { + for (NodeMetadataMap::const_iterator i = m_data.begin(); i != m_data.end(); ++i) { v3s16 p = i->first; NodeMetadata *data = i->second; if (data->empty()) @@ -150,8 +148,8 @@ void NodeMetadataList::serialize(std::ostream &os, u8 blockver, bool disk, } } -void NodeMetadataList::deSerialize(std::istream &is, - IItemDefManager *item_def_mgr, bool absolute_pos) +void NodeMetadataList::deSerialize( + std::istream &is, IItemDefManager *item_def_mgr, bool absolute_pos) { clear(); @@ -163,8 +161,8 @@ void NodeMetadataList::deSerialize(std::istream &is, } if (version > 2) { - std::string err_str = std::string(FUNCTION_NAME) - + ": version " + itos(version) + " not supported"; + std::string err_str = std::string(FUNCTION_NAME) + ": version " + + itos(version) + " not supported"; infostream << err_str << std::endl; throw SerializationError(err_str); } @@ -187,8 +185,8 @@ void NodeMetadataList::deSerialize(std::istream &is, } if (m_data.find(p) != m_data.end()) { warningstream << "NodeMetadataList::deSerialize(): " - << "already set data at position " << PP(p) - << ": Ignoring." << std::endl; + << "already set data at position " << PP(p) + << ": Ignoring." << std::endl; continue; } diff --git a/src/nodemetadata.h b/src/nodemetadata.h index c028caf88..9002151cc 100644 --- a/src/nodemetadata.h +++ b/src/nodemetadata.h @@ -40,17 +40,14 @@ public: NodeMetadata(IItemDefManager *item_def_mgr); ~NodeMetadata(); - void serialize(std::ostream &os, u8 version, bool disk=true) const; + void serialize(std::ostream &os, u8 version, bool disk = true) const; void deSerialize(std::istream &is, u8 version); void clear(); bool empty() const; // The inventory - Inventory *getInventory() - { - return m_inventory; - } + Inventory *getInventory() { return m_inventory; } inline bool isPrivate(const std::string &name) const { @@ -65,7 +62,6 @@ private: std::unordered_set m_privatevars; }; - /* List of metadata of all the nodes of a block */ @@ -76,15 +72,16 @@ class NodeMetadataList { public: NodeMetadataList(bool is_metadata_owner = true) : - m_is_metadata_owner(is_metadata_owner) - {} + m_is_metadata_owner(is_metadata_owner) + { + } ~NodeMetadataList(); void serialize(std::ostream &os, u8 blockver, bool disk = true, - bool absolute_pos = false) const; + bool absolute_pos = false) const; void deSerialize(std::istream &is, IItemDefManager *item_def_mgr, - bool absolute_pos = false); + bool absolute_pos = false); // Add all keys in this list to the vector keys std::vector getAllKeys(); @@ -99,15 +96,9 @@ public: size_t size() const { return m_data.size(); } - NodeMetadataMap::const_iterator begin() - { - return m_data.begin(); - } + NodeMetadataMap::const_iterator begin() { return m_data.begin(); } - NodeMetadataMap::const_iterator end() - { - return m_data.end(); - } + NodeMetadataMap::const_iterator end() { return m_data.end(); } private: int countNonEmpty() const; diff --git a/src/nodetimer.cpp b/src/nodetimer.cpp index ec8611a01..e0c5194fe 100644 --- a/src/nodetimer.cpp +++ b/src/nodetimer.cpp @@ -63,7 +63,7 @@ void NodeTimerList::serialize(std::ostream &os, u8 map_format_version) const for (const auto &timer : m_timers) { NodeTimer t = timer.second; NodeTimer nt = NodeTimer(t.timeout, - t.timeout - (f32)(timer.first - m_time), t.position); + t.timeout - (f32)(timer.first - m_time), t.position); v3s16 p = t.position; u16 p16 = p.Z * MAP_BLOCKSIZE * MAP_BLOCKSIZE + p.Y * MAP_BLOCKSIZE + p.X; @@ -78,15 +78,15 @@ void NodeTimerList::deSerialize(std::istream &is, u8 map_format_version) if (map_format_version == 24) { u8 timer_version = readU8(is); - if(timer_version == 0) + if (timer_version == 0) return; - if(timer_version != 1) + if (timer_version != 1) throw SerializationError("unsupported NodeTimerList version"); } if (map_format_version >= 25) { u8 timer_data_len = readU8(is); - if(timer_data_len != 2+4+4) + if (timer_data_len != 2 + 4 + 4) throw SerializationError("unsupported NodeTimer data length"); } @@ -106,18 +106,18 @@ void NodeTimerList::deSerialize(std::istream &is, u8 map_format_version) t.deSerialize(is); if (t.timeout <= 0) { - warningstream<<"NodeTimerList::deSerialize(): " - <<"invalid data at position" - <<"("<::iterator>::iterator n = - m_iterators.find(p); + m_iterators.find(p); if (n == m_iterators.end()) return NodeTimer(); NodeTimer t = n->second->second; @@ -74,10 +76,11 @@ public: return t; } // Deletes timer - void remove(v3s16 p) { + void remove(v3s16 p) + { std::map::iterator>::iterator n = - m_iterators.find(p); - if(n != m_iterators.end()) { + m_iterators.find(p); + if (n != m_iterators.end()) { double removed_time = n->second->first; m_timers.erase(n->second); m_iterators.erase(n); @@ -93,25 +96,26 @@ public: } } // Undefined behaviour if there already is a timer - void insert(NodeTimer timer) { + void insert(NodeTimer timer) + { v3s16 p = timer.position; double trigger_time = m_time + (double)(timer.timeout - timer.elapsed); - std::multimap::iterator it = - m_timers.insert(std::pair( - trigger_time, timer - )); - m_iterators.insert( - std::pair::iterator>(p, it)); + std::multimap::iterator it = m_timers.insert( + std::pair(trigger_time, timer)); + m_iterators.insert(std::pair::iterator>(p, it)); if (m_next_trigger_time == -1. || trigger_time < m_next_trigger_time) m_next_trigger_time = trigger_time; } // Deletes old timer and sets a new one - inline void set(const NodeTimer &timer) { + inline void set(const NodeTimer &timer) + { remove(timer.position); insert(timer); } // Deletes all timers - void clear() { + void clear() + { m_timers.clear(); m_iterators.clear(); m_next_trigger_time = -1.; diff --git a/src/noise.cpp b/src/noise.cpp index 5a1d989cb..04739423b 100644 --- a/src/noise.cpp +++ b/src/noise.cpp @@ -9,18 +9,18 @@ * 1. Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, this list - * of conditions and the following disclaimer in the documentation and/or other materials - * provided with the distribution. + * of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND - * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include @@ -32,28 +32,21 @@ #include "util/string.h" #include "exceptions.h" -#define NOISE_MAGIC_X 1619 -#define NOISE_MAGIC_Y 31337 -#define NOISE_MAGIC_Z 52591 +#define NOISE_MAGIC_X 1619 +#define NOISE_MAGIC_Y 31337 +#define NOISE_MAGIC_Z 52591 #define NOISE_MAGIC_SEED 1013 typedef float (*Interp2dFxn)( - float v00, float v10, float v01, float v11, - float x, float y); + float v00, float v10, float v01, float v11, float x, float y); -typedef float (*Interp3dFxn)( - float v000, float v100, float v010, float v110, - float v001, float v101, float v011, float v111, - float x, float y, float z); +typedef float (*Interp3dFxn)(float v000, float v100, float v010, float v110, float v001, + float v101, float v011, float v111, float x, float y, float z); -FlagDesc flagdesc_noiseparams[] = { - {"defaults", NOISE_FLAG_DEFAULTS}, - {"eased", NOISE_FLAG_EASED}, - {"absvalue", NOISE_FLAG_ABSVALUE}, - {"pointbuffer", NOISE_FLAG_POINTBUFFER}, - {"simplex", NOISE_FLAG_SIMPLEX}, - {NULL, 0} -}; +FlagDesc flagdesc_noiseparams[] = {{"defaults", NOISE_FLAG_DEFAULTS}, + {"eased", NOISE_FLAG_EASED}, {"absvalue", NOISE_FLAG_ABSVALUE}, + {"pointbuffer", NOISE_FLAG_POINTBUFFER}, {"simplex", NOISE_FLAG_SIMPLEX}, + {NULL, 0}}; /////////////////////////////////////////////////////////////////////////////// @@ -71,7 +64,6 @@ void PcgRandom::seed(u64 state, u64 seq) next(); } - u32 PcgRandom::next() { u64 oldstate = m_state; @@ -82,7 +74,6 @@ u32 PcgRandom::next() return (xorshifted >> rot) | (xorshifted << ((-rot) & 31)); } - u32 PcgRandom::range(u32 bound) { // If the bound is 0, we cover the whole RNG's range @@ -119,7 +110,6 @@ u32 PcgRandom::range(u32 bound) return r % bound; } - s32 PcgRandom::range(s32 min, s32 max) { if (max < min) @@ -131,7 +121,6 @@ s32 PcgRandom::range(s32 min, s32 max) return range(bound) + min; } - void PcgRandom::bytes(void *out, size_t len) { u8 *outb = (u8 *)out; @@ -151,7 +140,6 @@ void PcgRandom::bytes(void *out, size_t len) } } - s32 PcgRandom::randNormalDist(s32 min, s32 max, int num_trials) { s32 accum = 0; @@ -164,40 +152,36 @@ s32 PcgRandom::randNormalDist(s32 min, s32 max, int num_trials) float noise2d(int x, int y, s32 seed) { - unsigned int n = (NOISE_MAGIC_X * x + NOISE_MAGIC_Y * y - + NOISE_MAGIC_SEED * seed) & 0x7fffffff; + unsigned int n = (NOISE_MAGIC_X * x + NOISE_MAGIC_Y * y + + NOISE_MAGIC_SEED * seed) & + 0x7fffffff; n = (n >> 13) ^ n; n = (n * (n * n * 60493 + 19990303) + 1376312589) & 0x7fffffff; return 1.f - (float)(int)n / 0x40000000; } - float noise3d(int x, int y, int z, s32 seed) { - unsigned int n = (NOISE_MAGIC_X * x + NOISE_MAGIC_Y * y + NOISE_MAGIC_Z * z - + NOISE_MAGIC_SEED * seed) & 0x7fffffff; + unsigned int n = (NOISE_MAGIC_X * x + NOISE_MAGIC_Y * y + NOISE_MAGIC_Z * z + + NOISE_MAGIC_SEED * seed) & + 0x7fffffff; n = (n >> 13) ^ n; n = (n * (n * n * 60493 + 19990303) + 1376312589) & 0x7fffffff; return 1.f - (float)(int)n / 0x40000000; } - inline float dotProduct(float vx, float vy, float wx, float wy) { return vx * wx + vy * wy; } - inline float linearInterpolation(float v0, float v1, float t) { return v0 + (v1 - v0) * t; } - inline float biLinearInterpolation( - float v00, float v10, - float v01, float v11, - float x, float y) + float v00, float v10, float v01, float v11, float x, float y) { float tx = easeCurve(x); float ty = easeCurve(y); @@ -206,22 +190,16 @@ inline float biLinearInterpolation( return linearInterpolation(u, v, ty); } - inline float biLinearInterpolationNoEase( - float v00, float v10, - float v01, float v11, - float x, float y) + float v00, float v10, float v01, float v11, float x, float y) { float u = linearInterpolation(v00, v10, x); float v = linearInterpolation(v01, v11, x); return linearInterpolation(u, v, y); } - -float triLinearInterpolation( - float v000, float v100, float v010, float v110, - float v001, float v101, float v011, float v111, - float x, float y, float z) +float triLinearInterpolation(float v000, float v100, float v010, float v110, float v001, + float v101, float v011, float v111, float x, float y, float z) { float tx = easeCurve(x); float ty = easeCurve(y); @@ -231,10 +209,8 @@ float triLinearInterpolation( return linearInterpolation(u, v, tz); } -float triLinearInterpolationNoEase( - float v000, float v100, float v010, float v110, - float v001, float v101, float v011, float v111, - float x, float y, float z) +float triLinearInterpolationNoEase(float v000, float v100, float v010, float v110, + float v001, float v101, float v011, float v111, float x, float y, float z) { float u = biLinearInterpolationNoEase(v000, v100, v010, v110, x, y); float v = biLinearInterpolationNoEase(v001, v101, v011, v111, x, y); @@ -251,9 +227,9 @@ float noise2d_gradient(float x, float y, s32 seed, bool eased) float yl = y - (float)y0; // Get values for corners of square float v00 = noise2d(x0, y0, seed); - float v10 = noise2d(x0+1, y0, seed); - float v01 = noise2d(x0, y0+1, seed); - float v11 = noise2d(x0+1, y0+1, seed); + float v10 = noise2d(x0 + 1, y0, seed); + float v01 = noise2d(x0, y0 + 1, seed); + float v11 = noise2d(x0 + 1, y0 + 1, seed); // Interpolate if (eased) return biLinearInterpolation(v00, v10, v01, v11, xl, yl); @@ -261,7 +237,6 @@ float noise2d_gradient(float x, float y, s32 seed, bool eased) return biLinearInterpolationNoEase(v00, v10, v01, v11, xl, yl); } - float noise3d_gradient(float x, float y, float z, s32 seed, bool eased) { // Calculate the integer coordinates @@ -273,37 +248,31 @@ float noise3d_gradient(float x, float y, float z, s32 seed, bool eased) float yl = y - (float)y0; float zl = z - (float)z0; // Get values for corners of cube - float v000 = noise3d(x0, y0, z0, seed); - float v100 = noise3d(x0 + 1, y0, z0, seed); - float v010 = noise3d(x0, y0 + 1, z0, seed); - float v110 = noise3d(x0 + 1, y0 + 1, z0, seed); - float v001 = noise3d(x0, y0, z0 + 1, seed); - float v101 = noise3d(x0 + 1, y0, z0 + 1, seed); - float v011 = noise3d(x0, y0 + 1, z0 + 1, seed); + float v000 = noise3d(x0, y0, z0, seed); + float v100 = noise3d(x0 + 1, y0, z0, seed); + float v010 = noise3d(x0, y0 + 1, z0, seed); + float v110 = noise3d(x0 + 1, y0 + 1, z0, seed); + float v001 = noise3d(x0, y0, z0 + 1, seed); + float v101 = noise3d(x0 + 1, y0, z0 + 1, seed); + float v011 = noise3d(x0, y0 + 1, z0 + 1, seed); float v111 = noise3d(x0 + 1, y0 + 1, z0 + 1, seed); // Interpolate if (eased) { - return triLinearInterpolation( - v000, v100, v010, v110, - v001, v101, v011, v111, - xl, yl, zl); + return triLinearInterpolation(v000, v100, v010, v110, v001, v101, v011, + v111, xl, yl, zl); } return triLinearInterpolationNoEase( - v000, v100, v010, v110, - v001, v101, v011, v111, - xl, yl, zl); + v000, v100, v010, v110, v001, v101, v011, v111, xl, yl, zl); } - -float noise2d_perlin(float x, float y, s32 seed, - int octaves, float persistence, bool eased) +float noise2d_perlin( + float x, float y, s32 seed, int octaves, float persistence, bool eased) { float a = 0; float f = 1.0; float g = 1.0; - for (int i = 0; i < octaves; i++) - { + for (int i = 0; i < octaves; i++) { a += g * noise2d_gradient(x * f, y * f, seed + i, eased); f *= 2.0; g *= persistence; @@ -311,9 +280,8 @@ float noise2d_perlin(float x, float y, s32 seed, return a; } - -float noise2d_perlin_abs(float x, float y, s32 seed, - int octaves, float persistence, bool eased) +float noise2d_perlin_abs( + float x, float y, s32 seed, int octaves, float persistence, bool eased) { float a = 0; float f = 1.0; @@ -326,9 +294,8 @@ float noise2d_perlin_abs(float x, float y, s32 seed, return a; } - -float noise3d_perlin(float x, float y, float z, s32 seed, - int octaves, float persistence, bool eased) +float noise3d_perlin(float x, float y, float z, s32 seed, int octaves, float persistence, + bool eased) { float a = 0; float f = 1.0; @@ -341,22 +308,21 @@ float noise3d_perlin(float x, float y, float z, s32 seed, return a; } - -float noise3d_perlin_abs(float x, float y, float z, s32 seed, - int octaves, float persistence, bool eased) +float noise3d_perlin_abs(float x, float y, float z, s32 seed, int octaves, + float persistence, bool eased) { float a = 0; float f = 1.0; float g = 1.0; for (int i = 0; i < octaves; i++) { - a += g * std::fabs(noise3d_gradient(x * f, y * f, z * f, seed + i, eased)); + a += g * + std::fabs(noise3d_gradient(x * f, y * f, z * f, seed + i, eased)); f *= 2.0; g *= persistence; } return a; } - float contour(float v) { v = std::fabs(v); @@ -365,10 +331,8 @@ float contour(float v) return (1.0 - v); } - ///////////////////////// [ New noise ] //////////////////////////// - float NoisePerlin2D(NoiseParams *np, float x, float y, s32 seed) { float a = 0; @@ -381,7 +345,7 @@ float NoisePerlin2D(NoiseParams *np, float x, float y, s32 seed) for (size_t i = 0; i < np->octaves; i++) { float noiseval = noise2d_gradient(x * f, y * f, seed + i, - np->flags & (NOISE_FLAG_DEFAULTS | NOISE_FLAG_EASED)); + np->flags & (NOISE_FLAG_DEFAULTS | NOISE_FLAG_EASED)); if (np->flags & NOISE_FLAG_ABSVALUE) noiseval = std::fabs(noiseval); @@ -394,7 +358,6 @@ float NoisePerlin2D(NoiseParams *np, float x, float y, s32 seed) return np->offset + a * np->scale; } - float NoisePerlin3D(NoiseParams *np, float x, float y, float z, s32 seed) { float a = 0; @@ -408,7 +371,7 @@ float NoisePerlin3D(NoiseParams *np, float x, float y, float z, s32 seed) for (size_t i = 0; i < np->octaves; i++) { float noiseval = noise3d_gradient(x * f, y * f, z * f, seed + i, - np->flags & NOISE_FLAG_EASED); + np->flags & NOISE_FLAG_EASED); if (np->flags & NOISE_FLAG_ABSVALUE) noiseval = std::fabs(noiseval); @@ -421,19 +384,17 @@ float NoisePerlin3D(NoiseParams *np, float x, float y, float z, s32 seed) return np->offset + a * np->scale; } - Noise::Noise(NoiseParams *np_, s32 seed, u32 sx, u32 sy, u32 sz) { memcpy(&np, np_, sizeof(np)); this->seed = seed; - this->sx = sx; - this->sy = sy; - this->sz = sz; + this->sx = sx; + this->sy = sy; + this->sz = sz; allocBuffers(); } - Noise::~Noise() { delete[] gradient_buf; @@ -442,7 +403,6 @@ Noise::~Noise() delete[] result; } - void Noise::allocBuffers() { if (sx < 1) @@ -461,15 +421,14 @@ void Noise::allocBuffers() try { size_t bufsize = sx * sy * sz; - this->persist_buf = NULL; + this->persist_buf = NULL; this->gradient_buf = new float[bufsize]; - this->result = new float[bufsize]; + this->result = new float[bufsize]; } catch (std::bad_alloc &e) { throw InvalidNoiseParamsException(); } } - void Noise::setSize(u32 sx, u32 sy, u32 sz) { this->sx = sx; @@ -479,7 +438,6 @@ void Noise::setSize(u32 sx, u32 sy, u32 sz) allocBuffers(); } - void Noise::setSpreadFactor(v3f spread) { this->np.spread = spread; @@ -487,7 +445,6 @@ void Noise::setSpreadFactor(v3f spread) resizeNoiseBuf(sz > 1); } - void Noise::setOctaves(int octaves) { this->np.octaves = octaves; @@ -495,13 +452,11 @@ void Noise::setOctaves(int octaves) resizeNoiseBuf(sz > 1); } - void Noise::resizeNoiseBuf(bool is3d) { // Maximum possible spread value factor - float ofactor = (np.lacunarity > 1.0) ? - pow(np.lacunarity, np.octaves - 1) : - np.lacunarity; + float ofactor = (np.lacunarity > 1.0) ? pow(np.lacunarity, np.octaves - 1) + : np.lacunarity; // Noise lattice point count // (int)(sz * spread * ofactor) is # of lattice points crossed due to length @@ -510,18 +465,17 @@ void Noise::resizeNoiseBuf(bool is3d) float num_noise_points_z = sz * ofactor / np.spread.Z; // Protect against obviously invalid parameters - if (num_noise_points_x > 1000000000.f || - num_noise_points_y > 1000000000.f || + if (num_noise_points_x > 1000000000.f || num_noise_points_y > 1000000000.f || num_noise_points_z > 1000000000.f) throw InvalidNoiseParamsException(); // Protect against an octave having a spread < 1, causing broken noise values - if (np.spread.X / ofactor < 1.0f || - np.spread.Y / ofactor < 1.0f || + if (np.spread.X / ofactor < 1.0f || np.spread.Y / ofactor < 1.0f || np.spread.Z / ofactor < 1.0f) { - errorstream << "A noise parameter has too many octaves: " - << np.octaves << " octaves" << std::endl; - throw InvalidNoiseParamsException("A noise parameter has too many octaves"); + errorstream << "A noise parameter has too many octaves: " << np.octaves + << " octaves" << std::endl; + throw InvalidNoiseParamsException( + "A noise parameter has too many octaves"); } // + 2 for the two initial endpoints @@ -538,7 +492,6 @@ void Noise::resizeNoiseBuf(bool is3d) } } - /* * NB: This algorithm is not optimal in terms of space complexity. The entire * integer lattice of noise points could be done as 2 lines instead, and for 3D, @@ -550,11 +503,8 @@ void Noise::resizeNoiseBuf(bool is3d) * values from the previous noise lattice as midpoints in the new lattice for the * next octave. */ -#define idx(x, y) ((y) * nlx + (x)) -void Noise::gradientMap2D( - float x, float y, - float step_x, float step_y, - s32 seed) +#define idx(x, y) ((y)*nlx + (x)) +void Noise::gradientMap2D(float x, float y, float step_x, float step_y, s32 seed) { float v00, v01, v10, v11, u, v, orig_u; u32 index, i, j, noisex, noisey; @@ -562,8 +512,8 @@ void Noise::gradientMap2D( s32 x0, y0; bool eased = np.flags & (NOISE_FLAG_DEFAULTS | NOISE_FLAG_EASED); - Interp2dFxn interpolate = eased ? - biLinearInterpolation : biLinearInterpolationNoEase; + Interp2dFxn interpolate = + eased ? biLinearInterpolation : biLinearInterpolationNoEase; x0 = std::floor(x); y0 = std::floor(y); @@ -571,7 +521,7 @@ void Noise::gradientMap2D( v = y - (float)y0; orig_u = u; - //calculate noise point lattice + // calculate noise point lattice nlx = (u32)(u + sx * step_x) + 2; nly = (u32)(v + sy * step_y) + 2; index = 0; @@ -579,8 +529,8 @@ void Noise::gradientMap2D( for (i = 0; i != nlx; i++) noise_buf[index++] = noise2d(x0 + i, y0 + j, seed); - //calculate interpolations - index = 0; + // calculate interpolations + index = 0; noisey = 0; for (j = 0; j != sy; j++) { v00 = noise_buf[idx(0, noisey)]; @@ -613,12 +563,9 @@ void Noise::gradientMap2D( } #undef idx - -#define idx(x, y, z) ((z) * nly * nlx + (y) * nlx + (x)) -void Noise::gradientMap3D( - float x, float y, float z, - float step_x, float step_y, float step_z, - s32 seed) +#define idx(x, y, z) ((z)*nly * nlx + (y)*nlx + (x)) +void Noise::gradientMap3D(float x, float y, float z, float step_x, float step_y, + float step_z, s32 seed) { float v000, v010, v100, v110; float v001, v011, v101, v111; @@ -627,8 +574,9 @@ void Noise::gradientMap3D( u32 nlx, nly, nlz; s32 x0, y0, z0; - Interp3dFxn interpolate = (np.flags & NOISE_FLAG_EASED) ? - triLinearInterpolation : triLinearInterpolationNoEase; + Interp3dFxn interpolate = (np.flags & NOISE_FLAG_EASED) + ? triLinearInterpolation + : triLinearInterpolationNoEase; x0 = std::floor(x); y0 = std::floor(y); @@ -639,7 +587,7 @@ void Noise::gradientMap3D( orig_u = u; orig_v = v; - //calculate noise point lattice + // calculate noise point lattice nlx = (u32)(u + sx * step_x) + 2; nly = (u32)(v + sy * step_y) + 2; nlz = (u32)(w + sz * step_z) + 2; @@ -647,32 +595,31 @@ void Noise::gradientMap3D( for (k = 0; k != nlz; k++) for (j = 0; j != nly; j++) for (i = 0; i != nlx; i++) - noise_buf[index++] = noise3d(x0 + i, y0 + j, z0 + k, seed); + noise_buf[index++] = + noise3d(x0 + i, y0 + j, z0 + k, seed); - //calculate interpolations - index = 0; + // calculate interpolations + index = 0; noisey = 0; noisez = 0; for (k = 0; k != sz; k++) { v = orig_v; noisey = 0; for (j = 0; j != sy; j++) { - v000 = noise_buf[idx(0, noisey, noisez)]; - v100 = noise_buf[idx(1, noisey, noisez)]; + v000 = noise_buf[idx(0, noisey, noisez)]; + v100 = noise_buf[idx(1, noisey, noisez)]; v010 = noise_buf[idx(0, noisey + 1, noisez)]; v110 = noise_buf[idx(1, noisey + 1, noisez)]; - v001 = noise_buf[idx(0, noisey, noisez + 1)]; - v101 = noise_buf[idx(1, noisey, noisez + 1)]; + v001 = noise_buf[idx(0, noisey, noisez + 1)]; + v101 = noise_buf[idx(1, noisey, noisez + 1)]; v011 = noise_buf[idx(0, noisey + 1, noisez + 1)]; v111 = noise_buf[idx(1, noisey + 1, noisez + 1)]; u = orig_u; noisex = 0; for (i = 0; i != sx; i++) { - gradient_buf[index++] = interpolate( - v000, v100, v010, v110, - v001, v101, v011, v111, - u, v, w); + gradient_buf[index++] = interpolate(v000, v100, v010, + v110, v001, v101, v011, v111, u, v, w); u += step_x; if (u >= 1.0) { @@ -680,12 +627,15 @@ void Noise::gradientMap3D( noisex++; v000 = v100; v010 = v110; - v100 = noise_buf[idx(noisex + 1, noisey, noisez)]; - v110 = noise_buf[idx(noisex + 1, noisey + 1, noisez)]; + v100 = noise_buf[idx(noisex + 1, noisey, noisez)]; + v110 = noise_buf[idx( + noisex + 1, noisey + 1, noisez)]; v001 = v101; v011 = v111; - v101 = noise_buf[idx(noisex + 1, noisey, noisez + 1)]; - v111 = noise_buf[idx(noisex + 1, noisey + 1, noisez + 1)]; + v101 = noise_buf[idx( + noisex + 1, noisey, noisez + 1)]; + v111 = noise_buf[idx(noisex + 1, noisey + 1, + noisez + 1)]; } } @@ -705,7 +655,6 @@ void Noise::gradientMap3D( } #undef idx - float *Noise::perlinMap2D(float x, float y, float *persistence_map) { float f = 1.0, g = 1.0; @@ -724,9 +673,8 @@ float *Noise::perlinMap2D(float x, float y, float *persistence_map) } for (size_t oct = 0; oct < np.octaves; oct++) { - gradientMap2D(x * f, y * f, - f / np.spread.X, f / np.spread.Y, - seed + np.seed + oct); + gradientMap2D(x * f, y * f, f / np.spread.X, f / np.spread.Y, + seed + np.seed + oct); updateResults(g, persist_buf, persistence_map, bufsize); @@ -742,7 +690,6 @@ float *Noise::perlinMap2D(float x, float y, float *persistence_map) return result; } - float *Noise::perlinMap3D(float x, float y, float z, float *persistence_map) { float f = 1.0, g = 1.0; @@ -762,9 +709,8 @@ float *Noise::perlinMap3D(float x, float y, float z, float *persistence_map) } for (size_t oct = 0; oct < np.octaves; oct++) { - gradientMap3D(x * f, y * f, z * f, - f / np.spread.X, f / np.spread.Y, f / np.spread.Z, - seed + np.seed + oct); + gradientMap3D(x * f, y * f, z * f, f / np.spread.X, f / np.spread.Y, + f / np.spread.Z, seed + np.seed + oct); updateResults(g, persist_buf, persistence_map, bufsize); @@ -780,9 +726,8 @@ float *Noise::perlinMap3D(float x, float y, float z, float *persistence_map) return result; } - -void Noise::updateResults(float g, float *gmap, - const float *persistence_map, size_t bufsize) +void Noise::updateResults( + float g, float *gmap, const float *persistence_map, size_t bufsize) { // This looks very ugly, but it is 50-70% faster than having // conditional statements inside the loop diff --git a/src/noise.h b/src/noise.h index 7b5e83251..cb1c31904 100644 --- a/src/noise.h +++ b/src/noise.h @@ -9,18 +9,18 @@ * 1. Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, this list - * of conditions and the following disclaimer in the documentation and/or other materials - * provided with the distribution. + * of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND - * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma once @@ -33,19 +33,14 @@ extern FlagDesc flagdesc_noiseparams[]; // Note: this class is not polymorphic so that its high level of // optimizability may be preserved in the common use case -class PseudoRandom { +class PseudoRandom +{ public: const static u32 RANDOM_RANGE = 32767; - inline PseudoRandom(int seed=0): - m_next(seed) - { - } + inline PseudoRandom(int seed = 0) : m_next(seed) {} - inline void seed(int seed) - { - m_next = seed; - } + inline void seed(int seed) { m_next = seed; } inline int next() { @@ -73,34 +68,36 @@ private: int m_next; }; -class PcgRandom { +class PcgRandom +{ public: - const static s32 RANDOM_MIN = -0x7fffffff - 1; - const static s32 RANDOM_MAX = 0x7fffffff; + const static s32 RANDOM_MIN = -0x7fffffff - 1; + const static s32 RANDOM_MAX = 0x7fffffff; const static u32 RANDOM_RANGE = 0xffffffff; - PcgRandom(u64 state=0x853c49e6748fea9bULL, u64 seq=0xda3e39cb94b95bdbULL); - void seed(u64 state, u64 seq=0xda3e39cb94b95bdbULL); + PcgRandom(u64 state = 0x853c49e6748fea9bULL, u64 seq = 0xda3e39cb94b95bdbULL); + void seed(u64 state, u64 seq = 0xda3e39cb94b95bdbULL); u32 next(); u32 range(u32 bound); s32 range(s32 min, s32 max); void bytes(void *out, size_t len); - s32 randNormalDist(s32 min, s32 max, int num_trials=6); + s32 randNormalDist(s32 min, s32 max, int num_trials = 6); private: u64 m_state; u64 m_inc; }; -#define NOISE_FLAG_DEFAULTS 0x01 -#define NOISE_FLAG_EASED 0x02 -#define NOISE_FLAG_ABSVALUE 0x04 +#define NOISE_FLAG_DEFAULTS 0x01 +#define NOISE_FLAG_EASED 0x02 +#define NOISE_FLAG_ABSVALUE 0x04 //// TODO(hmmmm): implement these! #define NOISE_FLAG_POINTBUFFER 0x08 -#define NOISE_FLAG_SIMPLEX 0x10 +#define NOISE_FLAG_SIMPLEX 0x10 -struct NoiseParams { +struct NoiseParams +{ float offset = 0.0f; float scale = 1.0f; v3f spread = v3f(250, 250, 250); @@ -113,21 +110,22 @@ struct NoiseParams { NoiseParams() = default; NoiseParams(float offset_, float scale_, const v3f &spread_, s32 seed_, - u16 octaves_, float persist_, float lacunarity_, - u32 flags_=NOISE_FLAG_DEFAULTS) + u16 octaves_, float persist_, float lacunarity_, + u32 flags_ = NOISE_FLAG_DEFAULTS) { - offset = offset_; - scale = scale_; - spread = spread_; - seed = seed_; - octaves = octaves_; - persist = persist_; + offset = offset_; + scale = scale_; + spread = spread_; + seed = seed_; + octaves = octaves_; + persist = persist_; lacunarity = lacunarity_; - flags = flags_; + flags = flags_; } }; -class Noise { +class Noise +{ public: NoiseParams np; s32 seed; @@ -139,42 +137,32 @@ public: float *persist_buf = nullptr; float *result = nullptr; - Noise(NoiseParams *np, s32 seed, u32 sx, u32 sy, u32 sz=1); + Noise(NoiseParams *np, s32 seed, u32 sx, u32 sy, u32 sz = 1); ~Noise(); - void setSize(u32 sx, u32 sy, u32 sz=1); + void setSize(u32 sx, u32 sy, u32 sz = 1); void setSpreadFactor(v3f spread); void setOctaves(int octaves); - void gradientMap2D( - float x, float y, - float step_x, float step_y, - s32 seed); - void gradientMap3D( - float x, float y, float z, - float step_x, float step_y, float step_z, - s32 seed); + void gradientMap2D(float x, float y, float step_x, float step_y, s32 seed); + void gradientMap3D(float x, float y, float z, float step_x, float step_y, + float step_z, s32 seed); - float *perlinMap2D(float x, float y, float *persistence_map=NULL); - float *perlinMap3D(float x, float y, float z, float *persistence_map=NULL); + float *perlinMap2D(float x, float y, float *persistence_map = NULL); + float *perlinMap3D(float x, float y, float z, float *persistence_map = NULL); inline float *perlinMap2D_PO(float x, float xoff, float y, float yoff, - float *persistence_map=NULL) + float *persistence_map = NULL) { - return perlinMap2D( - x + xoff * np.spread.X, - y + yoff * np.spread.Y, - persistence_map); + return perlinMap2D(x + xoff * np.spread.X, y + yoff * np.spread.Y, + persistence_map); } - inline float *perlinMap3D_PO(float x, float xoff, float y, float yoff, - float z, float zoff, float *persistence_map=NULL) + inline float *perlinMap3D_PO(float x, float xoff, float y, float yoff, float z, + float zoff, float *persistence_map = NULL) { - return perlinMap3D( - x + xoff * np.spread.X, - y + yoff * np.spread.Y, - z + zoff * np.spread.Z, - persistence_map); + return perlinMap3D(x + xoff * np.spread.X, y + yoff * np.spread.Y, + z + zoff * np.spread.Z, persistence_map); } private: @@ -182,49 +170,42 @@ private: void resizeNoiseBuf(bool is3d); void updateResults(float g, float *gmap, const float *persistence_map, size_t bufsize); - }; float NoisePerlin2D(NoiseParams *np, float x, float y, s32 seed); float NoisePerlin3D(NoiseParams *np, float x, float y, float z, s32 seed); -inline float NoisePerlin2D_PO(NoiseParams *np, float x, float xoff, - float y, float yoff, s32 seed) +inline float NoisePerlin2D_PO( + NoiseParams *np, float x, float xoff, float y, float yoff, s32 seed) { - return NoisePerlin2D(np, - x + xoff * np->spread.X, - y + yoff * np->spread.Y, - seed); + return NoisePerlin2D(np, x + xoff * np->spread.X, y + yoff * np->spread.Y, seed); } -inline float NoisePerlin3D_PO(NoiseParams *np, float x, float xoff, - float y, float yoff, float z, float zoff, s32 seed) +inline float NoisePerlin3D_PO(NoiseParams *np, float x, float xoff, float y, float yoff, + float z, float zoff, s32 seed) { - return NoisePerlin3D(np, - x + xoff * np->spread.X, - y + yoff * np->spread.Y, - z + zoff * np->spread.Z, - seed); + return NoisePerlin3D(np, x + xoff * np->spread.X, y + yoff * np->spread.Y, + z + zoff * np->spread.Z, seed); } // Return value: -1 ... 1 float noise2d(int x, int y, s32 seed); float noise3d(int x, int y, int z, s32 seed); -float noise2d_gradient(float x, float y, s32 seed, bool eased=true); -float noise3d_gradient(float x, float y, float z, s32 seed, bool eased=false); +float noise2d_gradient(float x, float y, s32 seed, bool eased = true); +float noise3d_gradient(float x, float y, float z, s32 seed, bool eased = false); -float noise2d_perlin(float x, float y, s32 seed, - int octaves, float persistence, bool eased=true); +float noise2d_perlin(float x, float y, s32 seed, int octaves, float persistence, + bool eased = true); -float noise2d_perlin_abs(float x, float y, s32 seed, - int octaves, float persistence, bool eased=true); +float noise2d_perlin_abs(float x, float y, s32 seed, int octaves, float persistence, + bool eased = true); -float noise3d_perlin(float x, float y, float z, s32 seed, - int octaves, float persistence, bool eased=false); +float noise3d_perlin(float x, float y, float z, s32 seed, int octaves, float persistence, + bool eased = false); -float noise3d_perlin_abs(float x, float y, float z, s32 seed, - int octaves, float persistence, bool eased=false); +float noise3d_perlin_abs(float x, float y, float z, s32 seed, int octaves, + float persistence, bool eased = false); inline float easeCurve(float t) { diff --git a/src/objdef.cpp b/src/objdef.cpp index 482544d37..6a5294271 100644 --- a/src/objdef.cpp +++ b/src/objdef.cpp @@ -28,14 +28,12 @@ ObjDefManager::ObjDefManager(IGameDef *gamedef, ObjDefType type) m_ndef = gamedef ? gamedef->getNodeDefManager() : NULL; } - ObjDefManager::~ObjDefManager() { for (size_t i = 0; i != m_objects.size(); i++) delete m_objects[i]; } - ObjDefHandle ObjDefManager::add(ObjDef *obj) { assert(obj); @@ -51,14 +49,12 @@ ObjDefHandle ObjDefManager::add(ObjDef *obj) return obj->handle; } - ObjDef *ObjDefManager::get(ObjDefHandle handle) const { u32 index = validateHandle(handle); return (index != OBJDEF_INVALID_INDEX) ? getRaw(index) : NULL; } - ObjDef *ObjDefManager::set(ObjDefHandle handle, ObjDef *obj) { u32 index = validateHandle(handle); @@ -67,14 +63,13 @@ ObjDef *ObjDefManager::set(ObjDefHandle handle, ObjDef *obj) ObjDef *oldobj = setRaw(index, obj); - obj->uid = oldobj->uid; - obj->index = oldobj->index; + obj->uid = oldobj->uid; + obj->index = oldobj->index; obj->handle = oldobj->handle; return oldobj; } - u32 ObjDefManager::addRaw(ObjDef *obj) { size_t nobjects = m_objects.size(); @@ -91,22 +86,18 @@ u32 ObjDefManager::addRaw(ObjDef *obj) m_objects.push_back(obj); - infostream << "ObjDefManager: added " << getObjectTitle() - << ": name=\"" << obj->name - << "\" index=" << obj->index - << " uid=" << obj->uid - << std::endl; + infostream << "ObjDefManager: added " << getObjectTitle() << ": name=\"" + << obj->name << "\" index=" << obj->index << " uid=" << obj->uid + << std::endl; return nobjects; } - ObjDef *ObjDefManager::getRaw(u32 index) const { return m_objects[index]; } - ObjDef *ObjDefManager::setRaw(u32 index, ObjDef *obj) { ObjDef *old_obj = m_objects[index]; @@ -114,7 +105,6 @@ ObjDef *ObjDefManager::setRaw(u32 index, ObjDef *obj) return old_obj; } - ObjDef *ObjDefManager::getByName(const std::string &name) const { for (size_t i = 0; i != m_objects.size(); i++) { @@ -126,7 +116,6 @@ ObjDef *ObjDefManager::getByName(const std::string &name) const return NULL; } - void ObjDefManager::clear() { for (size_t i = 0; i != m_objects.size(); i++) @@ -135,24 +124,20 @@ void ObjDefManager::clear() m_objects.clear(); } - u32 ObjDefManager::validateHandle(ObjDefHandle handle) const { ObjDefType type; u32 index; u32 uid; - bool is_valid = - (handle != OBJDEF_INVALID_HANDLE) && - decodeHandle(handle, &index, &type, &uid) && - (type == m_objtype) && - (index < m_objects.size()) && - (m_objects[index]->uid == uid); + bool is_valid = (handle != OBJDEF_INVALID_HANDLE) && + decodeHandle(handle, &index, &type, &uid) && + (type == m_objtype) && (index < m_objects.size()) && + (m_objects[index]->uid == uid); return is_valid ? index : -1; } - ObjDefHandle ObjDefManager::createHandle(u32 index, ObjDefType type, u32 uid) { ObjDefHandle handle = 0; @@ -166,9 +151,8 @@ ObjDefHandle ObjDefManager::createHandle(u32 index, ObjDefType type, u32 uid) return handle ^ OBJDEF_HANDLE_SALT; } - -bool ObjDefManager::decodeHandle(ObjDefHandle handle, u32 *index, - ObjDefType *type, u32 *uid) +bool ObjDefManager::decodeHandle( + ObjDefHandle handle, u32 *index, ObjDefType *type, u32 *uid) { handle ^= OBJDEF_HANDLE_SALT; @@ -178,8 +162,8 @@ bool ObjDefManager::decodeHandle(ObjDefHandle handle, u32 *index, return false; *index = get_bits(handle, 0, 18); - *type = (ObjDefType)get_bits(handle, 18, 6); - *uid = get_bits(handle, 24, 7); + *type = (ObjDefType)get_bits(handle, 18, 6); + *uid = get_bits(handle, 24, 7); return true; } diff --git a/src/objdef.h b/src/objdef.h index e40324a88..7b3ae186f 100644 --- a/src/objdef.h +++ b/src/objdef.h @@ -33,7 +33,8 @@ class NodeDefManager; typedef u32 ObjDefHandle; -enum ObjDefType { +enum ObjDefType +{ OBJDEF_GENERIC, OBJDEF_BIOME, OBJDEF_ORE, @@ -41,7 +42,8 @@ enum ObjDefType { OBJDEF_SCHEMATIC, }; -class ObjDef { +class ObjDef +{ public: virtual ~ObjDef() = default; @@ -67,7 +69,8 @@ protected: // added/set to. Note that ObjDefs managed by ObjDefManager are NOT refcounted, // so the same ObjDef instance must not be referenced multiple // TODO: const correctness for getter methods -class ObjDefManager { +class ObjDefManager +{ public: ObjDefManager(IGameDef *gamedef, ObjDefType type); virtual ~ObjDefManager(); @@ -99,11 +102,11 @@ public: u32 validateHandle(ObjDefHandle handle) const; static ObjDefHandle createHandle(u32 index, ObjDefType type, u32 uid); - static bool decodeHandle(ObjDefHandle handle, u32 *index, - ObjDefType *type, u32 *uid); + static bool decodeHandle( + ObjDefHandle handle, u32 *index, ObjDefType *type, u32 *uid); protected: - ObjDefManager() {}; + ObjDefManager(){}; // Helper for child classes to implement clone() void cloneTo(ObjDefManager *mgr) const; diff --git a/src/object_properties.cpp b/src/object_properties.cpp index 8d51bcbfa..97914f53e 100644 --- a/src/object_properties.cpp +++ b/src/object_properties.cpp @@ -27,7 +27,7 @@ with this program; if not, write to the Free Software Foundation, Inc., ObjectProperties::ObjectProperties() { textures.emplace_back("unknown_object.png"); - colors.emplace_back(255,255,255,255); + colors.emplace_back(255, 255, 255, 255); } std::string ObjectProperties::dump() @@ -37,7 +37,8 @@ std::string ObjectProperties::dump() os << ", breath_max=" << breath_max; os << ", physical=" << physical; os << ", collideWithObjects=" << collideWithObjects; - os << ", collisionbox=" << PP(collisionbox.MinEdge) << "," << PP(collisionbox.MaxEdge); + os << ", collisionbox=" << PP(collisionbox.MinEdge) << "," + << PP(collisionbox.MaxEdge); os << ", visual=" << visual; os << ", mesh=" << mesh; os << ", visual_size=" << PP(visual_size); @@ -49,20 +50,22 @@ std::string ObjectProperties::dump() os << ", colors=["; for (const video::SColor &color : colors) { os << "\"" << color.getAlpha() << "," << color.getRed() << "," - << color.getGreen() << "," << color.getBlue() << "\" "; + << color.getGreen() << "," << color.getBlue() << "\" "; } os << "]"; os << ", spritediv=" << PP2(spritediv); os << ", initial_sprite_basepos=" << PP2(initial_sprite_basepos); os << ", is_visible=" << is_visible; os << ", makes_footstep_sound=" << makes_footstep_sound; - os << ", automatic_rotate="<< automatic_rotate; - os << ", backface_culling="<< backface_culling; + os << ", automatic_rotate=" << automatic_rotate; + os << ", backface_culling=" << backface_culling; os << ", glow=" << glow; os << ", nametag=" << nametag; - os << ", nametag_color=" << "\"" << nametag_color.getAlpha() << "," << nametag_color.getRed() - << "," << nametag_color.getGreen() << "," << nametag_color.getBlue() << "\" "; - os << ", selectionbox=" << PP(selectionbox.MinEdge) << "," << PP(selectionbox.MaxEdge); + os << ", nametag_color=" + << "\"" << nametag_color.getAlpha() << "," << nametag_color.getRed() << "," + << nametag_color.getGreen() << "," << nametag_color.getBlue() << "\" "; + os << ", selectionbox=" << PP(selectionbox.MinEdge) << "," + << PP(selectionbox.MaxEdge); os << ", pointable=" << pointable; os << ", static_save=" << static_save; os << ", eye_height=" << eye_height; @@ -141,7 +144,7 @@ void ObjectProperties::deSerialize(std::istream &is) visual_size = readV3F32(is); textures.clear(); u32 texture_count = readU16(is); - for (u32 i = 0; i < texture_count; i++){ + for (u32 i = 0; i < texture_count; i++) { textures.push_back(deSerializeString(is)); } spritediv = readV2S16(is); @@ -152,7 +155,7 @@ void ObjectProperties::deSerialize(std::istream &is) mesh = deSerializeString(is); colors.clear(); u32 color_count = readU16(is); - for (u32 i = 0; i < color_count; i++){ + for (u32 i = 0; i < color_count; i++) { colors.push_back(readARGB8(is)); } collideWithObjects = readU8(is); @@ -176,5 +179,6 @@ void ObjectProperties::deSerialize(std::istream &is) if (is.eof()) throw SerializationError(""); shaded = tmp; - } catch (SerializationError &e) {} + } catch (SerializationError &e) { + } } diff --git a/src/particles.cpp b/src/particles.cpp index fd81238dc..03373590e 100644 --- a/src/particles.cpp +++ b/src/particles.cpp @@ -41,23 +41,23 @@ void ParticleParameters::serialize(std::ostream &os, u16 protocol_ver) const void ParticleParameters::deSerialize(std::istream &is, u16 protocol_ver) { - pos = readV3F32(is); - vel = readV3F32(is); - acc = readV3F32(is); - expirationtime = readF32(is); - size = readF32(is); + pos = readV3F32(is); + vel = readV3F32(is); + acc = readV3F32(is); + expirationtime = readF32(is); + size = readF32(is); collisiondetection = readU8(is); - texture = deSerializeLongString(is); - vertical = readU8(is); - collision_removal = readU8(is); + texture = deSerializeLongString(is); + vertical = readU8(is); + collision_removal = readU8(is); animation.deSerialize(is, 6); /* NOT the protocol ver */ - glow = readU8(is); - object_collision = readU8(is); + glow = readU8(is); + object_collision = readU8(is); // This is kinda awful u16 tmp_param0 = readU16(is); if (is.eof()) return; node.param0 = tmp_param0; node.param2 = readU8(is); - node_tile = readU8(is); + node_tile = readU8(is); } diff --git a/src/particles.h b/src/particles.h index 6f518b771..aae460060 100644 --- a/src/particles.h +++ b/src/particles.h @@ -27,7 +27,8 @@ with this program; if not, write to the Free Software Foundation, Inc., // This file defines the particle-related structures that both the server and // client need. The ParticleManager and rendering is in client/particles.h -struct CommonParticleParams { +struct CommonParticleParams +{ bool collisiondetection = false; bool collision_removal = false; bool object_collision = false; @@ -38,14 +39,16 @@ struct CommonParticleParams { MapNode node; u8 node_tile = 0; - CommonParticleParams() { + CommonParticleParams() + { animation.type = TAT_NONE; node.setContent(CONTENT_IGNORE); } /* This helper is useful for copying params from * ParticleSpawnerParameters to ParticleParameters */ - inline void copyCommon(CommonParticleParams &to) const { + inline void copyCommon(CommonParticleParams &to) const + { to.collisiondetection = collisiondetection; to.collision_removal = collision_removal; to.object_collision = object_collision; @@ -58,7 +61,8 @@ struct CommonParticleParams { } }; -struct ParticleParameters : CommonParticleParams { +struct ParticleParameters : CommonParticleParams +{ v3f pos; v3f vel; v3f acc; @@ -69,7 +73,8 @@ struct ParticleParameters : CommonParticleParams { void deSerialize(std::istream &is, u16 protocol_ver); }; -struct ParticleSpawnerParameters : CommonParticleParams { +struct ParticleSpawnerParameters : CommonParticleParams +{ u16 amount = 1; v3f minpos, maxpos, minvel, maxvel, minacc, maxacc; f32 time = 1; diff --git a/src/pathfinder.cpp b/src/pathfinder.cpp index 3f0b98c10..ec418c45d 100644 --- a/src/pathfinder.cpp +++ b/src/pathfinder.cpp @@ -30,13 +30,13 @@ with this program; if not, write to the Free Software Foundation, Inc., //#define PATHFINDER_CALC_TIME #ifdef PATHFINDER_DEBUG - #include +#include #endif #ifdef PATHFINDER_DEBUG - #include +#include #endif #ifdef PATHFINDER_CALC_TIME - #include +#include #endif /******************************************************************************/ @@ -46,15 +46,15 @@ with this program; if not, write to the Free Software Foundation, Inc., #define LVL "(" << level << ")" << #ifdef PATHFINDER_DEBUG -#define DEBUG_OUT(a) std::cout << a -#define INFO_TARGET std::cout -#define VERBOSE_TARGET std::cout -#define ERROR_TARGET std::cout +#define DEBUG_OUT(a) std::cout << a +#define INFO_TARGET std::cout +#define VERBOSE_TARGET std::cout +#define ERROR_TARGET std::cout #else -#define DEBUG_OUT(a) while(0) -#define INFO_TARGET infostream << "Pathfinder: " -#define VERBOSE_TARGET verbosestream << "Pathfinder: " -#define ERROR_TARGET warningstream << "Pathfinder: " +#define DEBUG_OUT(a) while (0) +#define INFO_TARGET infostream << "Pathfinder: " +#define VERBOSE_TARGET verbosestream << "Pathfinder: " +#define ERROR_TARGET warningstream << "Pathfinder: " #endif #define PATHFINDER_MAX_WAYPOINTS 700 @@ -63,11 +63,10 @@ with this program; if not, write to the Free Software Foundation, Inc., /* Class definitions */ /******************************************************************************/ - /** representation of cost in specific direction */ -class PathCost { +class PathCost +{ public: - /** default constructor */ PathCost() = default; @@ -75,18 +74,17 @@ public: PathCost(const PathCost &b); /** assignment operator */ - PathCost &operator= (const PathCost &b); - - bool valid = false; /**< movement is possible */ - int value = 0; /**< cost of movement */ - int y_change = 0; /**< change of y position of movement */ - bool updated = false; /**< this cost has ben calculated */ + PathCost &operator=(const PathCost &b); + bool valid = false; /**< movement is possible */ + int value = 0; /**< cost of movement */ + int y_change = 0; /**< change of y position of movement */ + bool updated = false; /**< this cost has ben calculated */ }; - /** representation of a mapnode to be used for pathfinding */ -class PathGridnode { +class PathGridnode +{ public: /** default constructor */ @@ -99,7 +97,7 @@ public: * assignment operator * @param b node to copy */ - PathGridnode &operator= (const PathGridnode &b); + PathGridnode &operator=(const PathGridnode &b); /** * read cost in a specific direction @@ -112,37 +110,38 @@ public: * @param dir direction to set cost for * @cost cost to set */ - void setCost(v3s16 dir, const PathCost &cost); + void setCost(v3s16 dir, const PathCost &cost); - bool valid = false; /**< node is on surface */ - bool target = false; /**< node is target position */ - bool source = false; /**< node is stating position */ - int totalcost = -1; /**< cost to move here from starting point */ - int estimated_cost = -1; /**< totalcost + heuristic cost to end */ - v3s16 sourcedir; /**< origin of movement for current cost */ - v3s16 pos; /**< real position of node */ - PathCost directions[4]; /**< cost in different directions */ - bool is_closed = false; /**< for A* search: if true, is in closed list */ - bool is_open = false; /**< for A* search: if true, is in open list */ + bool valid = false; /**< node is on surface */ + bool target = false; /**< node is target position */ + bool source = false; /**< node is stating position */ + int totalcost = -1; /**< cost to move here from starting point */ + int estimated_cost = -1; /**< totalcost + heuristic cost to end */ + v3s16 sourcedir; /**< origin of movement for current cost */ + v3s16 pos; /**< real position of node */ + PathCost directions[4]; /**< cost in different directions */ + bool is_closed = false; /**< for A* search: if true, is in closed list */ + bool is_open = false; /**< for A* search: if true, is in open list */ /* debug values */ - bool is_element = false; /**< node is element of path detected */ - char type = 'u'; /**< Type of pathfinding node. - * u = unknown - * i = invalid - * s = surface (walkable node) - * - = non-walkable node (e.g. air) above surface - * g = other non-walkable node - */ + bool is_element = false; /**< node is element of path detected */ + char type = 'u'; /**< Type of pathfinding node. + * u = unknown + * i = invalid + * s = surface (walkable node) + * - = non-walkable node (e.g. air) above surface + * g = other non-walkable node + */ }; class Pathfinder; class PathfinderCompareHeuristic; /** Abstract class to manage the map data */ -class GridNodeContainer { +class GridNodeContainer +{ public: - virtual PathGridnode &access(v3s16 p)=0; + virtual PathGridnode &access(v3s16 p) = 0; virtual ~GridNodeContainer() = default; protected: @@ -151,12 +150,14 @@ protected: void initNode(v3s16 ipos, PathGridnode *p_node); }; -class ArrayGridNodeContainer : public GridNodeContainer { +class ArrayGridNodeContainer : public GridNodeContainer +{ public: virtual ~ArrayGridNodeContainer() = default; ArrayGridNodeContainer(Pathfinder *pathf, v3s16 dimensions); virtual PathGridnode &access(v3s16 p); + private: v3s16 m_dimensions; @@ -165,18 +166,21 @@ private: std::vector m_nodes_array; }; -class MapGridNodeContainer : public GridNodeContainer { +class MapGridNodeContainer : public GridNodeContainer +{ public: virtual ~MapGridNodeContainer() = default; MapGridNodeContainer(Pathfinder *pathf); virtual PathGridnode &access(v3s16 p); + private: std::map m_nodes; }; /** class doing pathfinding */ -class Pathfinder { +class Pathfinder +{ public: Pathfinder() = delete; @@ -194,12 +198,9 @@ public: * @param max_drop maximum number of blocks a path may drop * @param algo Algorithm to use for finding a path */ - std::vector getPath(v3s16 source, - v3s16 destination, - unsigned int searchdistance, - unsigned int max_jump, - unsigned int max_drop, - PathAlgorithm algo); + std::vector getPath(v3s16 source, v3s16 destination, + unsigned int searchdistance, unsigned int max_jump, + unsigned int max_drop, PathAlgorithm algo); private: /* helper functions */ @@ -209,14 +210,14 @@ private: * @param ipos a index position * @return map position */ - v3s16 getRealPos(v3s16 ipos); + v3s16 getRealPos(v3s16 ipos); /** * transform mappos to index pos * @param pos a real pos * @return index position */ - v3s16 getIndexPos(v3s16 pos); + v3s16 getIndexPos(v3s16 pos); /** * get gridnode at a specific index position @@ -236,15 +237,14 @@ private: * @param pos 3D position * @return pos *-1 */ - v3s16 invert(v3s16 pos); + v3s16 invert(v3s16 pos); /** * check if a index is within current search area * @param index position to validate * @return true/false */ - bool isValidIndex(v3s16 index); - + bool isValidIndex(v3s16 index); /* algorithm functions */ @@ -253,7 +253,7 @@ private: * @param pos position to calc distance * @return integer distance */ - int getXZManhattanDist(v3s16 pos); + int getXZManhattanDist(v3s16 pos); /** * calculate cost of movement @@ -261,7 +261,7 @@ private: * @param dir direction to move to * @return cost information */ - PathCost calcCost(v3s16 pos, v3s16 dir); + PathCost calcCost(v3s16 pos, v3s16 dir); /** * recursive update whole search areas total cost information @@ -271,7 +271,7 @@ private: * @param level current recursion depth * @return true/false path to destination has been found */ - bool updateAllCosts(v3s16 ipos, v3s16 srcdir, int current_cost, int level); + bool updateAllCosts(v3s16 ipos, v3s16 srcdir, int current_cost, int level); /** * try to find a path to destination using a heuristic function @@ -280,7 +280,7 @@ private: * @param idestination end position (index pos) * @return true/false path to destination has been found */ - bool updateCostHeuristic(v3s16 isource, v3s16 idestination); + bool updateCostHeuristic(v3s16 isource, v3s16 idestination); /** * build a vector containing all nodes from destination to source; @@ -289,7 +289,7 @@ private: * @param ipos initial pos to check (index pos) * @return true/false path has been fully built */ - bool buildPath(std::vector &path, v3s16 ipos); + bool buildPath(std::vector &path, v3s16 ipos); /** * go downwards from a position until some barrier @@ -299,23 +299,22 @@ private: * @return new position after movement; if too far down, * pos is returned */ - v3s16 walkDownwards(v3s16 pos, unsigned int max_down); + v3s16 walkDownwards(v3s16 pos, unsigned int max_down); /* variables */ - int m_max_index_x = 0; /**< max index of search area in x direction */ - int m_max_index_y = 0; /**< max index of search area in y direction */ - int m_max_index_z = 0; /**< max index of search area in z direction */ + int m_max_index_x = 0; /**< max index of search area in x direction */ + int m_max_index_y = 0; /**< max index of search area in y direction */ + int m_max_index_z = 0; /**< max index of search area in z direction */ + int m_searchdistance = 0; /**< max distance to search in each direction */ + int m_maxdrop = 0; /**< maximum number of blocks a path may drop */ + int m_maxjump = 0; /**< maximum number of blocks a path may jump */ + int m_min_target_distance = 0; /**< current smalest path to target */ - int m_searchdistance = 0; /**< max distance to search in each direction */ - int m_maxdrop = 0; /**< maximum number of blocks a path may drop */ - int m_maxjump = 0; /**< maximum number of blocks a path may jump */ - int m_min_target_distance = 0; /**< current smalest path to target */ + bool m_prefetch = true; /**< prefetch cost data */ - bool m_prefetch = true; /**< prefetch cost data */ - - v3s16 m_start; /**< source position */ - v3s16 m_destination; /**< destination position */ + v3s16 m_start; /**< source position */ + v3s16 m_destination; /**< destination position */ core::aabbox3d m_limits; /**< position limits in real map coordinates */ @@ -384,72 +383,62 @@ private: */ class PathfinderCompareHeuristic { - private: - Pathfinder *myPathfinder; - public: - PathfinderCompareHeuristic(Pathfinder *pf) - { - myPathfinder = pf; - } - bool operator() (v3s16 pos1, v3s16 pos2) { - v3s16 ipos1 = myPathfinder->getIndexPos(pos1); - v3s16 ipos2 = myPathfinder->getIndexPos(pos2); - PathGridnode &g_pos1 = myPathfinder->getIndexElement(ipos1); - PathGridnode &g_pos2 = myPathfinder->getIndexElement(ipos2); - if (!g_pos1.valid) - return false; - if (!g_pos2.valid) - return false; - return g_pos1.estimated_cost > g_pos2.estimated_cost; - } +private: + Pathfinder *myPathfinder; + +public: + PathfinderCompareHeuristic(Pathfinder *pf) { myPathfinder = pf; } + bool operator()(v3s16 pos1, v3s16 pos2) + { + v3s16 ipos1 = myPathfinder->getIndexPos(pos1); + v3s16 ipos2 = myPathfinder->getIndexPos(pos2); + PathGridnode &g_pos1 = myPathfinder->getIndexElement(ipos1); + PathGridnode &g_pos2 = myPathfinder->getIndexElement(ipos2); + if (!g_pos1.valid) + return false; + if (!g_pos2.valid) + return false; + return g_pos1.estimated_cost > g_pos2.estimated_cost; + } }; /******************************************************************************/ /* implementation */ /******************************************************************************/ -std::vector get_path(Map* map, const NodeDefManager *ndef, - v3s16 source, - v3s16 destination, - unsigned int searchdistance, - unsigned int max_jump, - unsigned int max_drop, - PathAlgorithm algo) +std::vector get_path(Map *map, const NodeDefManager *ndef, v3s16 source, + v3s16 destination, unsigned int searchdistance, unsigned int max_jump, + unsigned int max_drop, PathAlgorithm algo) { - return Pathfinder(map, ndef).getPath(source, destination, - searchdistance, max_jump, max_drop, algo); + return Pathfinder(map, ndef).getPath( + source, destination, searchdistance, max_jump, max_drop, algo); } /******************************************************************************/ PathCost::PathCost(const PathCost &b) { - valid = b.valid; - y_change = b.y_change; - value = b.value; - updated = b.updated; + valid = b.valid; + y_change = b.y_change; + value = b.value; + updated = b.updated; } /******************************************************************************/ -PathCost &PathCost::operator= (const PathCost &b) +PathCost &PathCost::operator=(const PathCost &b) { - valid = b.valid; - y_change = b.y_change; - value = b.value; - updated = b.updated; + valid = b.valid; + y_change = b.y_change; + value = b.value; + updated = b.updated; return *this; } /******************************************************************************/ -PathGridnode::PathGridnode(const PathGridnode &b) -: valid(b.valid), - target(b.target), - source(b.source), - totalcost(b.totalcost), - sourcedir(b.sourcedir), - pos(b.pos), - is_element(b.is_element), - type(b.type) +PathGridnode::PathGridnode(const PathGridnode &b) : + valid(b.valid), target(b.target), source(b.source), + totalcost(b.totalcost), sourcedir(b.sourcedir), pos(b.pos), + is_element(b.is_element), type(b.type) { directions[DIR_XP] = b.directions[DIR_XP]; @@ -459,16 +448,16 @@ PathGridnode::PathGridnode(const PathGridnode &b) } /******************************************************************************/ -PathGridnode &PathGridnode::operator= (const PathGridnode &b) +PathGridnode &PathGridnode::operator=(const PathGridnode &b) { - valid = b.valid; - target = b.target; - source = b.source; + valid = b.valid; + target = b.target; + source = b.source; is_element = b.is_element; - totalcost = b.totalcost; - sourcedir = b.sourcedir; - pos = b.pos; - type = b.type; + totalcost = b.totalcost; + sourcedir = b.sourcedir; + pos = b.pos; + type = b.type; directions[DIR_XP] = b.directions[DIR_XP]; directions[DIR_XM] = b.directions[DIR_XM]; @@ -522,13 +511,12 @@ void GridNodeContainer::initNode(v3s16 ipos, PathGridnode *p_node) v3s16 realpos = m_pathf->getRealPos(ipos); MapNode current = m_pathf->m_map->getNode(realpos); - MapNode below = m_pathf->m_map->getNode(realpos + v3s16(0, -1, 0)); + MapNode below = m_pathf->m_map->getNode(realpos + v3s16(0, -1, 0)); - - if ((current.param0 == CONTENT_IGNORE) || - (below.param0 == CONTENT_IGNORE)) { - DEBUG_OUT("Pathfinder: " << PP(realpos) << - " current or below is invalid element" << std::endl); + if ((current.param0 == CONTENT_IGNORE) || (below.param0 == CONTENT_IGNORE)) { + DEBUG_OUT("Pathfinder: " << PP(realpos) + << " current or below is invalid element" + << std::endl); if (current.param0 == CONTENT_IGNORE) { elem.type = 'i'; DEBUG_OUT(PP(ipos) << ": " << 'i' << std::endl); @@ -536,36 +524,35 @@ void GridNodeContainer::initNode(v3s16 ipos, PathGridnode *p_node) return; } - //don't add anything if it isn't an air node + // don't add anything if it isn't an air node if (ndef->get(current).walkable || !ndef->get(below).walkable) { - DEBUG_OUT("Pathfinder: " << PP(realpos) - << " not on surface" << std::endl); - if (ndef->get(current).walkable) { - elem.type = 's'; - DEBUG_OUT(PP(ipos) << ": " << 's' << std::endl); - } else { - elem.type = '-'; - DEBUG_OUT(PP(ipos) << ": " << '-' << std::endl); - } - return; + DEBUG_OUT("Pathfinder: " << PP(realpos) << " not on surface" + << std::endl); + if (ndef->get(current).walkable) { + elem.type = 's'; + DEBUG_OUT(PP(ipos) << ": " << 's' << std::endl); + } else { + elem.type = '-'; + DEBUG_OUT(PP(ipos) << ": " << '-' << std::endl); + } + return; } elem.valid = true; - elem.pos = realpos; - elem.type = 'g'; + elem.pos = realpos; + elem.type = 'g'; DEBUG_OUT(PP(ipos) << ": " << 'a' << std::endl); if (m_pathf->m_prefetch) { - elem.directions[DIR_XP] = m_pathf->calcCost(realpos, v3s16( 1, 0, 0)); + elem.directions[DIR_XP] = m_pathf->calcCost(realpos, v3s16(1, 0, 0)); elem.directions[DIR_XM] = m_pathf->calcCost(realpos, v3s16(-1, 0, 0)); - elem.directions[DIR_ZP] = m_pathf->calcCost(realpos, v3s16( 0, 0, 1)); - elem.directions[DIR_ZM] = m_pathf->calcCost(realpos, v3s16( 0, 0,-1)); + elem.directions[DIR_ZP] = m_pathf->calcCost(realpos, v3s16(0, 0, 1)); + elem.directions[DIR_ZM] = m_pathf->calcCost(realpos, v3s16(0, 0, -1)); } } ArrayGridNodeContainer::ArrayGridNodeContainer(Pathfinder *pathf, v3s16 dimensions) : - m_x_stride(dimensions.Y * dimensions.Z), - m_y_stride(dimensions.Z) + m_x_stride(dimensions.Y * dimensions.Z), m_y_stride(dimensions.Z) { m_pathf = pathf; @@ -573,7 +560,7 @@ ArrayGridNodeContainer::ArrayGridNodeContainer(Pathfinder *pathf, v3s16 dimensio INFO_TARGET << "Pathfinder ArrayGridNodeContainer constructor." << std::endl; for (int x = 0; x < dimensions.X; x++) { for (int y = 0; y < dimensions.Y; y++) { - for (int z= 0; z < dimensions.Z; z++) { + for (int z = 0; z < dimensions.Z; z++) { v3s16 ipos(x, y, z); initNode(ipos, &access(ipos)); } @@ -602,15 +589,10 @@ PathGridnode &MapGridNodeContainer::access(v3s16 p) return n; } - - /******************************************************************************/ -std::vector Pathfinder::getPath(v3s16 source, - v3s16 destination, - unsigned int searchdistance, - unsigned int max_jump, - unsigned int max_drop, - PathAlgorithm algo) +std::vector Pathfinder::getPath(v3s16 source, v3s16 destination, + unsigned int searchdistance, unsigned int max_jump, unsigned int max_drop, + PathAlgorithm algo) { #ifdef PATHFINDER_CALC_TIME timespec ts; @@ -618,11 +600,11 @@ std::vector Pathfinder::getPath(v3s16 source, #endif std::vector retval; - //initialization + // initialization m_searchdistance = searchdistance; m_maxjump = max_jump; m_maxdrop = max_drop; - m_start = source; + m_start = source; m_destination = destination; m_min_target_distance = -1; m_prefetch = true; @@ -631,7 +613,7 @@ std::vector Pathfinder::getPath(v3s16 source, m_prefetch = false; } - //calculate boundaries within we're allowed to search + // calculate boundaries within we're allowed to search int min_x = MYMIN(source.X, destination.X); int max_x = MYMAX(source.X, destination.X); @@ -667,73 +649,73 @@ std::vector Pathfinder::getPath(v3s16 source, printYdir(); #endif - //fail if source or destination is walkable + // fail if source or destination is walkable MapNode node_at_pos = m_map->getNode(destination); if (m_ndef->get(node_at_pos).walkable) { - VERBOSE_TARGET << "Destination is walkable. " << - "Pos: " << PP(destination) << std::endl; + VERBOSE_TARGET << "Destination is walkable. " + << "Pos: " << PP(destination) << std::endl; return retval; } node_at_pos = m_map->getNode(source); if (m_ndef->get(node_at_pos).walkable) { - VERBOSE_TARGET << "Source is walkable. " << - "Pos: " << PP(source) << std::endl; + VERBOSE_TARGET << "Source is walkable. " + << "Pos: " << PP(source) << std::endl; return retval; } - //If source pos is hovering above air, drop - //to the first walkable node (up to m_maxdrop). - //All algorithms expect the source pos to be *directly* above - //a walkable node. + // If source pos is hovering above air, drop + // to the first walkable node (up to m_maxdrop). + // All algorithms expect the source pos to be *directly* above + // a walkable node. v3s16 true_source = v3s16(source); source = walkDownwards(source, m_maxdrop); - //If destination pos is hovering above air, go downwards - //to the first walkable node (up to m_maxjump). - //This means a hovering destination pos could be reached - //by a final upwards jump. + // If destination pos is hovering above air, go downwards + // to the first walkable node (up to m_maxjump). + // This means a hovering destination pos could be reached + // by a final upwards jump. v3s16 true_destination = v3s16(destination); destination = walkDownwards(destination, m_maxjump); - //validate and mark start and end pos + // validate and mark start and end pos - v3s16 StartIndex = getIndexPos(source); - v3s16 EndIndex = getIndexPos(destination); + v3s16 StartIndex = getIndexPos(source); + v3s16 EndIndex = getIndexPos(destination); PathGridnode &startpos = getIndexElement(StartIndex); - PathGridnode &endpos = getIndexElement(EndIndex); + PathGridnode &endpos = getIndexElement(EndIndex); if (!startpos.valid) { - VERBOSE_TARGET << "Invalid startpos " << - "Index: " << PP(StartIndex) << - "Realpos: " << PP(getRealPos(StartIndex)) << std::endl; + VERBOSE_TARGET << "Invalid startpos " + << "Index: " << PP(StartIndex) + << "Realpos: " << PP(getRealPos(StartIndex)) << std::endl; return retval; } if (!endpos.valid) { - VERBOSE_TARGET << "Invalid stoppos " << - "Index: " << PP(EndIndex) << - "Realpos: " << PP(getRealPos(EndIndex)) << std::endl; + VERBOSE_TARGET << "Invalid stoppos " + << "Index: " << PP(EndIndex) + << "Realpos: " << PP(getRealPos(EndIndex)) << std::endl; return retval; } - endpos.target = true; - startpos.source = true; + endpos.target = true; + startpos.source = true; startpos.totalcost = 0; bool update_cost_retval = false; - //calculate node costs + // calculate node costs switch (algo) { - case PA_DIJKSTRA: - update_cost_retval = updateAllCosts(StartIndex, v3s16(0, 0, 0), 0, 0); - break; - case PA_PLAIN_NP: - case PA_PLAIN: - update_cost_retval = updateCostHeuristic(StartIndex, EndIndex); - break; - default: - ERROR_TARGET << "Missing PathAlgorithm" << std::endl; - break; + case PA_DIJKSTRA: + update_cost_retval = updateAllCosts(StartIndex, v3s16(0, 0, 0), 0, 0); + break; + case PA_PLAIN_NP: + case PA_PLAIN: + update_cost_retval = updateCostHeuristic(StartIndex, EndIndex); + break; + default: + ERROR_TARGET << "Missing PathAlgorithm" << std::endl; + break; } if (update_cost_retval) { @@ -743,22 +725,22 @@ std::vector Pathfinder::getPath(v3s16 source, printPathLen(); #endif - //find path + // find path std::vector index_path; buildPath(index_path, EndIndex); - //Now we have a path of index positions, - //and it's in reverse. - //The "true" start or end position might be missing - //since those have been given special treatment. + // Now we have a path of index positions, + // and it's in reverse. + // The "true" start or end position might be missing + // since those have been given special treatment. #ifdef PATHFINDER_DEBUG std::cout << "Index path:" << std::endl; printPath(index_path); #endif - //from here we'll make the final changes to the path + // from here we'll make the final changes to the path std::vector full_path; - //calculate required size + // calculate required size int full_path_size = index_path.size(); if (source != true_source) { full_path_size++; @@ -768,23 +750,22 @@ std::vector Pathfinder::getPath(v3s16 source, } full_path.reserve(full_path_size); - //manually add true_source to start of path, if needed + // manually add true_source to start of path, if needed if (source != true_source) { full_path.push_back(true_source); } - //convert all index positions to "normal" positions and insert - //them into full_path in reverse + // convert all index positions to "normal" positions and insert + // them into full_path in reverse std::vector::reverse_iterator rit = index_path.rbegin(); for (; rit != index_path.rend(); ++rit) { full_path.push_back(getIndexElement(*rit).pos); } - //manually add true_destination to end of path, if needed + // manually add true_destination to end of path, if needed if (destination != true_destination) { full_path.push_back(true_destination); } - //Done! We now have a complete path of normal positions. - + // Done! We now have a complete path of normal positions. #ifdef PATHFINDER_DEBUG std::cout << "Full path:" << std::endl; @@ -794,25 +775,23 @@ std::vector Pathfinder::getPath(v3s16 source, timespec ts2; clock_gettime(CLOCK_REALTIME, &ts2); - int ms = (ts2.tv_nsec - ts.tv_nsec)/(1000*1000); - int us = ((ts2.tv_nsec - ts.tv_nsec) - (ms*1000*1000))/1000; - int ns = ((ts2.tv_nsec - ts.tv_nsec) - ( (ms*1000*1000) + (us*1000))); + int ms = (ts2.tv_nsec - ts.tv_nsec) / (1000 * 1000); + int us = ((ts2.tv_nsec - ts.tv_nsec) - (ms * 1000 * 1000)) / 1000; + int ns = ((ts2.tv_nsec - ts.tv_nsec) - + ((ms * 1000 * 1000) + (us * 1000))); - - std::cout << "Calculating path took: " << (ts2.tv_sec - ts.tv_sec) << - "s " << ms << "ms " << us << "us " << ns << "ns " << std::endl; + std::cout << "Calculating path took: " << (ts2.tv_sec - ts.tv_sec) << "s " + << ms << "ms " << us << "us " << ns << "ns " << std::endl; #endif return full_path; - } - else { + } else { #ifdef PATHFINDER_DEBUG printPathLen(); #endif INFO_TARGET << "No path found" << std::endl; } - - //return + // return return retval; } @@ -835,44 +814,42 @@ PathCost Pathfinder::calcCost(v3s16 pos, v3s16 dir) v3s16 pos2 = pos + dir; - //check limits + // check limits if (!m_limits.isPointInside(pos2)) { - DEBUG_OUT("Pathfinder: " << PP(pos2) << - " no cost -> out of limits" << std::endl); + DEBUG_OUT("Pathfinder: " << PP(pos2) << " no cost -> out of limits" + << std::endl); return retval; } MapNode node_at_pos2 = m_map->getNode(pos2); - //did we get information about node? - if (node_at_pos2.param0 == CONTENT_IGNORE ) { - VERBOSE_TARGET << "Pathfinder: (1) area at pos: " - << PP(pos2) << " not loaded"; - return retval; + // did we get information about node? + if (node_at_pos2.param0 == CONTENT_IGNORE) { + VERBOSE_TARGET << "Pathfinder: (1) area at pos: " << PP(pos2) + << " not loaded"; + return retval; } if (!m_ndef->get(node_at_pos2).walkable) { - MapNode node_below_pos2 = - m_map->getNode(pos2 + v3s16(0, -1, 0)); + MapNode node_below_pos2 = m_map->getNode(pos2 + v3s16(0, -1, 0)); - //did we get information about node? - if (node_below_pos2.param0 == CONTENT_IGNORE ) { - VERBOSE_TARGET << "Pathfinder: (2) area at pos: " - << PP((pos2 + v3s16(0, -1, 0))) << " not loaded"; - return retval; + // did we get information about node? + if (node_below_pos2.param0 == CONTENT_IGNORE) { + VERBOSE_TARGET << "Pathfinder: (2) area at pos: " + << PP((pos2 + v3s16(0, -1, 0))) << " not loaded"; + return retval; } - //test if the same-height neighbor is suitable + // test if the same-height neighbor is suitable if (m_ndef->get(node_below_pos2).walkable) { - //SUCCESS! + // SUCCESS! retval.valid = true; retval.value = 1; retval.y_change = 0; - DEBUG_OUT("Pathfinder: "<< PP(pos) - << " cost same height found" << std::endl); - } - else { - //test if we can fall a couple of nodes (m_maxdrop) + DEBUG_OUT("Pathfinder: " << PP(pos) << " cost same height found" + << std::endl); + } else { + // test if we can fall a couple of nodes (m_maxdrop) v3s16 testpos = pos2 + v3s16(0, -1, 0); MapNode node_at_pos = m_map->getNode(testpos); @@ -883,35 +860,36 @@ PathCost Pathfinder::calcCost(v3s16 pos, v3s16 dir) node_at_pos = m_map->getNode(testpos); } - //did we find surface? + // did we find surface? if ((testpos.Y >= m_limits.MinEdge.Y) && (node_at_pos.param0 != CONTENT_IGNORE) && (m_ndef->get(node_at_pos).walkable)) { if ((pos2.Y - testpos.Y - 1) <= m_maxdrop) { - //SUCCESS! + // SUCCESS! retval.valid = true; retval.value = 2; - //difference of y-pos +1 (target node is ABOVE solid node) - retval.y_change = ((testpos.Y - pos2.Y) +1); - DEBUG_OUT("Pathfinder cost below height found" << std::endl); - } - else { + // difference of y-pos +1 (target node is ABOVE + // solid node) + retval.y_change = ((testpos.Y - pos2.Y) + 1); + DEBUG_OUT("Pathfinder cost below height found" + << std::endl); + } else { INFO_TARGET << "Pathfinder:" - " distance to surface below too big: " - << (testpos.Y - pos2.Y) << " max: " << m_maxdrop - << std::endl; + " distance to surface below too " + "big: " + << (testpos.Y - pos2.Y) + << " max: " << m_maxdrop << std::endl; } - } - else { - DEBUG_OUT("Pathfinder: no surface below found" << std::endl); + } else { + DEBUG_OUT("Pathfinder: no surface below found" + << std::endl); } } - } - else { - //test if we can jump upwards (m_maxjump) + } else { + // test if we can jump upwards (m_maxjump) v3s16 targetpos = pos2; // position for jump target - v3s16 jumppos = pos; // position for checking if jumping space is free + v3s16 jumppos = pos; // position for checking if jumping space is free MapNode node_target = m_map->getNode(targetpos); MapNode node_jump = m_map->getNode(jumppos); bool headbanger = false; // true if anything blocks jumppath @@ -919,42 +897,40 @@ PathCost Pathfinder::calcCost(v3s16 pos, v3s16 dir) while ((node_target.param0 != CONTENT_IGNORE) && (m_ndef->get(node_target).walkable) && (targetpos.Y < m_limits.MaxEdge.Y)) { - //if the jump would hit any solid node, discard + // if the jump would hit any solid node, discard if ((node_jump.param0 == CONTENT_IGNORE) || (m_ndef->get(node_jump).walkable)) { - headbanger = true; + headbanger = true; break; } targetpos += v3s16(0, 1, 0); - jumppos += v3s16(0, 1, 0); + jumppos += v3s16(0, 1, 0); node_target = m_map->getNode(targetpos); - node_jump = m_map->getNode(jumppos); - + node_jump = m_map->getNode(jumppos); } - //check headbanger one last time + // check headbanger one last time if ((node_jump.param0 == CONTENT_IGNORE) || - (m_ndef->get(node_jump).walkable)) { + (m_ndef->get(node_jump).walkable)) { headbanger = true; } - //did we find surface without banging our head? + // did we find surface without banging our head? if ((!headbanger) && (targetpos.Y <= m_limits.MaxEdge.Y) && (!m_ndef->get(node_target).walkable)) { if (targetpos.Y - pos2.Y <= m_maxjump) { - //SUCCESS! + // SUCCESS! retval.valid = true; retval.value = 2; retval.y_change = (targetpos.Y - pos2.Y); DEBUG_OUT("Pathfinder cost above found" << std::endl); + } else { + DEBUG_OUT("Pathfinder: distance to surface above too " + "big: " + << (targetpos.Y - pos2.Y) + << " max: " << m_maxjump << std::endl); } - else { - DEBUG_OUT("Pathfinder: distance to surface above too big: " - << (targetpos.Y - pos2.Y) << " max: " << m_maxjump - << std::endl); - } - } - else { + } else { DEBUG_OUT("Pathfinder: no surface above found" << std::endl); } } @@ -976,17 +952,14 @@ PathGridnode &Pathfinder::getIndexElement(v3s16 ipos) /******************************************************************************/ inline PathGridnode &Pathfinder::getIdxElem(s16 x, s16 y, s16 z) { - return m_nodes_container->access(v3s16(x,y,z)); + return m_nodes_container->access(v3s16(x, y, z)); } /******************************************************************************/ bool Pathfinder::isValidIndex(v3s16 index) { - if ( (index.X < m_max_index_x) && - (index.Y < m_max_index_y) && - (index.Z < m_max_index_z) && - (index.X >= 0) && - (index.Y >= 0) && + if ((index.X < m_max_index_x) && (index.Y < m_max_index_y) && + (index.Z < m_max_index_z) && (index.X >= 0) && (index.Y >= 0) && (index.Z >= 0)) return true; @@ -998,26 +971,23 @@ v3s16 Pathfinder::invert(v3s16 pos) { v3s16 retval = pos; - retval.X *=-1; - retval.Y *=-1; - retval.Z *=-1; + retval.X *= -1; + retval.Y *= -1; + retval.Z *= -1; return retval; } /******************************************************************************/ -bool Pathfinder::updateAllCosts(v3s16 ipos, - v3s16 srcdir, - int current_cost, - int level) +bool Pathfinder::updateAllCosts(v3s16 ipos, v3s16 srcdir, int current_cost, int level) { PathGridnode &g_pos = getIndexElement(ipos); g_pos.totalcost = current_cost; g_pos.sourcedir = srcdir; - level ++; + level++; - //check if target has been found + // check if target has been found if (g_pos.target) { m_min_target_distance = current_cost; DEBUG_OUT(LVL " Pathfinder: target found!" << std::endl); @@ -1028,11 +998,7 @@ bool Pathfinder::updateAllCosts(v3s16 ipos, // the 4 cardinal directions const static v3s16 directions[4] = { - v3s16(1,0, 0), - v3s16(-1,0, 0), - v3s16(0,0, 1), - v3s16(0,0,-1) - }; + v3s16(1, 0, 0), v3s16(-1, 0, 0), v3s16(0, 0, 1), v3s16(0, 0, -1)}; for (v3s16 direction : directions) { if (direction != srcdir) { @@ -1044,16 +1010,20 @@ bool Pathfinder::updateAllCosts(v3s16 ipos, v3s16 ipos2 = ipos + direction; if (!isValidIndex(ipos2)) { - DEBUG_OUT(LVL " Pathfinder: " << PP(ipos2) << - " out of range, max=" << PP(m_limits.MaxEdge) << std::endl); + DEBUG_OUT(LVL " Pathfinder: " + << PP(ipos2) + << " out of range, max=" + << PP(m_limits.MaxEdge) + << std::endl); continue; } PathGridnode &g_pos2 = getIndexElement(ipos2); if (!g_pos2.valid) { - VERBOSE_TARGET << LVL "Pathfinder: no data for new position: " - << PP(ipos2) << std::endl; + VERBOSE_TARGET << LVL "Pathfinder: no data for " + "new position: " + << PP(ipos2) << std::endl; continue; } @@ -1069,23 +1039,22 @@ bool Pathfinder::updateAllCosts(v3s16 ipos, if ((g_pos2.totalcost < 0) || (g_pos2.totalcost > new_cost)) { - DEBUG_OUT(LVL "Pathfinder: updating path at: "<< - PP(ipos2) << " from: " << g_pos2.totalcost << " to "<< - new_cost << std::endl); + DEBUG_OUT(LVL "Pathfinder: updating path at: " + << PP(ipos2) << " from: " + << g_pos2.totalcost << " to " + << new_cost << std::endl); if (updateAllCosts(ipos2, invert(direction), - new_cost, level)) { + new_cost, level)) { retval = true; - } } - else { + } else { DEBUG_OUT(LVL "Pathfinder:" - " already found shorter path to: " + " already found shorter path to: " << PP(ipos2) << std::endl); } - } - else { + } else { DEBUG_OUT(LVL "Pathfinder:" - " not moving to invalid direction: " + " not moving to invalid direction: " << PP(directions[i]) << std::endl); } } @@ -1104,8 +1073,6 @@ int Pathfinder::getXZManhattanDist(v3s16 pos) return (max_x - min_x) + (max_z - min_z); } - - /******************************************************************************/ bool Pathfinder::updateCostHeuristic(v3s16 isource, v3s16 idestination) { @@ -1125,14 +1092,10 @@ bool Pathfinder::updateCostHeuristic(v3s16 isource, v3s16 idestination) // the 4 cardinal directions const static v3s16 directions[4] = { - v3s16(1,0, 0), - v3s16(-1,0, 0), - v3s16(0,0, 1), - v3s16(0,0,-1) - }; + v3s16(1, 0, 0), v3s16(-1, 0, 0), v3s16(0, 0, 1), v3s16(0, 0, -1)}; v3s16 current_pos; - PathGridnode& s_pos = getIndexElement(isource); + PathGridnode &s_pos = getIndexElement(isource); s_pos.source = true; s_pos.totalcost = 0; @@ -1149,12 +1112,14 @@ bool Pathfinder::updateCostHeuristic(v3s16 isource, v3s16 idestination) // check if node is inside searchdistance and valid if (!isValidIndex(ipos)) { - DEBUG_OUT(LVL " Pathfinder: " << PP(current_pos) << - " out of search distance, max=" << PP(m_limits.MaxEdge) << std::endl); + DEBUG_OUT(LVL " Pathfinder: " << PP(current_pos) + << " out of search distance, max=" + << PP(m_limits.MaxEdge) + << std::endl); continue; } - PathGridnode& g_pos = getIndexElement(ipos); + PathGridnode &g_pos = getIndexElement(ipos); g_pos.is_closed = true; g_pos.is_open = false; if (!g_pos.valid) { @@ -1177,7 +1142,8 @@ bool Pathfinder::updateCostHeuristic(v3s16 isource, v3s16 idestination) cost = calcCost(current_pos, direction_flat); g_pos.setCost(direction_flat, cost); } - // update Y component of direction if neighbor requires jump or fall + // update Y component of direction if neighbor requires jump or + // fall v3s16 direction_3d = v3s16(direction_flat); direction_3d.Y = cost.y_change; @@ -1187,13 +1153,15 @@ bool Pathfinder::updateCostHeuristic(v3s16 isource, v3s16 idestination) PathGridnode &n_pos = getIndexElement(ineighbor); if (cost.valid && !n_pos.is_closed && !n_pos.is_open) { - // heuristic function; estimate cost from neighbor to destination + // heuristic function; estimate cost from neighbor to + // destination cur_manhattan = getXZManhattanDist(neighbor); // add neighbor to open list n_pos.sourcedir = invert(direction_3d); n_pos.totalcost = current_totalcost + cost.value; - n_pos.estimated_cost = current_totalcost + cost.value + cur_manhattan; + n_pos.estimated_cost = current_totalcost + cost.value + + cur_manhattan; n_pos.is_open = true; openList.push(neighbor); } @@ -1207,16 +1175,21 @@ bool Pathfinder::updateCostHeuristic(v3s16 isource, v3s16 idestination) bool Pathfinder::buildPath(std::vector &path, v3s16 ipos) { // The cost calculation should have set a source direction for all relevant nodes. - // To build the path, we go backwards from the destination until we reach the start. - for(u32 waypoints = 1; waypoints++; ) { + // To build the path, we go backwards from the destination until we reach the + // start. + for (u32 waypoints = 1; waypoints++;) { if (waypoints > PATHFINDER_MAX_WAYPOINTS) { - ERROR_TARGET << "Pathfinder: buildPath: path is too long (too many waypoints), aborting" << std::endl; + ERROR_TARGET << "Pathfinder: buildPath: path is too long (too " + "many waypoints), aborting" + << std::endl; return false; } // Insert node into path PathGridnode &g_pos = getIndexElement(ipos); if (!g_pos.valid) { - ERROR_TARGET << "Pathfinder: buildPath: invalid next pos detected, aborting" << std::endl; + ERROR_TARGET << "Pathfinder: buildPath: invalid next pos " + "detected, aborting" + << std::endl; return false; } @@ -1235,7 +1208,8 @@ bool Pathfinder::buildPath(std::vector &path, v3s16 ipos) } /******************************************************************************/ -v3s16 Pathfinder::walkDownwards(v3s16 pos, unsigned int max_down) { +v3s16 Pathfinder::walkDownwards(v3s16 pos, unsigned int max_down) +{ if (max_down == 0) return pos; v3s16 testpos = v3s16(pos); @@ -1243,27 +1217,25 @@ v3s16 Pathfinder::walkDownwards(v3s16 pos, unsigned int max_down) { unsigned int down = 0; while ((node_at_pos.param0 != CONTENT_IGNORE) && (!m_ndef->get(node_at_pos).walkable) && - (testpos.Y > m_limits.MinEdge.Y) && - (down <= max_down)) { + (testpos.Y > m_limits.MinEdge.Y) && (down <= max_down)) { testpos += v3s16(0, -1, 0); down++; node_at_pos = m_map->getNode(testpos); } - //did we find surface? - if ((testpos.Y >= m_limits.MinEdge.Y) && - (node_at_pos.param0 != CONTENT_IGNORE) && + // did we find surface? + if ((testpos.Y >= m_limits.MinEdge.Y) && (node_at_pos.param0 != CONTENT_IGNORE) && (m_ndef->get(node_at_pos).walkable)) { if (down == 0) { pos = testpos; } else if ((down - 1) <= max_down) { - //difference of y-pos +1 (target node is ABOVE solid node) + // difference of y-pos +1 (target node is ABOVE solid node) testpos += v3s16(0, 1, 0); pos = testpos; - } - else { - VERBOSE_TARGET << "Pos too far above ground: " << - "Index: " << PP(getIndexPos(pos)) << - "Realpos: " << PP(getRealPos(getIndexPos(pos))) << std::endl; + } else { + VERBOSE_TARGET << "Pos too far above ground: " + << "Index: " << PP(getIndexPos(pos)) + << "Realpos: " << PP(getRealPos(getIndexPos(pos))) + << std::endl; } } else { DEBUG_OUT("Pathfinder: no surface found below pos" << std::endl); @@ -1301,21 +1273,23 @@ void Pathfinder::printCost(PathDirections dir) std::cout << "Level: " << y << std::endl; - std::cout << std::setw(4) << " " << " "; + std::cout << std::setw(4) << " " + << " "; for (int x = 0; x < m_max_index_x; x++) { std::cout << std::setw(4) << x; } std::cout << std::endl; for (int z = 0; z < m_max_index_z; z++) { - std::cout << std::setw(4) << z <<": "; + std::cout << std::setw(4) << z << ": "; for (int x = 0; x < m_max_index_x; x++) { if (getIdxElem(x, y, z).directions[dir].valid) std::cout << std::setw(4) - << getIdxElem(x, y, z).directions[dir].value; + << getIdxElem(x, y, z).directions[dir] + .value; else std::cout << std::setw(4) << "-"; - } + } std::cout << std::endl; } std::cout << std::endl; @@ -1332,21 +1306,23 @@ void Pathfinder::printYdir(PathDirections dir) std::cout << "Level: " << y << std::endl; - std::cout << std::setw(4) << " " << " "; + std::cout << std::setw(4) << " " + << " "; for (int x = 0; x < m_max_index_x; x++) { std::cout << std::setw(4) << x; } std::cout << std::endl; for (int z = 0; z < m_max_index_z; z++) { - std::cout << std::setw(4) << z <<": "; + std::cout << std::setw(4) << z << ": "; for (int x = 0; x < m_max_index_x; x++) { if (getIdxElem(x, y, z).directions[dir].valid) std::cout << std::setw(4) - << getIdxElem(x, y, z).directions[dir].y_change; + << getIdxElem(x, y, z).directions[dir] + .y_change; else std::cout << std::setw(4) << "-"; - } + } std::cout << std::endl; } std::cout << std::endl; @@ -1363,14 +1339,15 @@ void Pathfinder::printType() std::cout << "Level: " << y << std::endl; - std::cout << std::setw(3) << " " << " "; + std::cout << std::setw(3) << " " + << " "; for (int x = 0; x < m_max_index_x; x++) { std::cout << std::setw(3) << x; } std::cout << std::endl; for (int z = 0; z < m_max_index_z; z++) { - std::cout << std::setw(3) << z <<": "; + std::cout << std::setw(3) << z << ": "; for (int x = 0; x < m_max_index_x; x++) { char toshow = getIdxElem(x, y, z).type; std::cout << std::setw(3) << toshow; @@ -1386,28 +1363,30 @@ void Pathfinder::printType() void Pathfinder::printPathLen() { std::cout << "Pathlen:" << std::endl; - std::cout << std::setfill('-') << std::setw(80) << "-" << std::endl; - std::cout << std::setfill(' '); - for (int y = 0; y < m_max_index_y; y++) { + std::cout << std::setfill('-') << std::setw(80) << "-" << std::endl; + std::cout << std::setfill(' '); + for (int y = 0; y < m_max_index_y; y++) { - std::cout << "Level: " << y << std::endl; + std::cout << "Level: " << y << std::endl; - std::cout << std::setw(3) << " " << " "; + std::cout << std::setw(3) << " " + << " "; + for (int x = 0; x < m_max_index_x; x++) { + std::cout << std::setw(3) << x; + } + std::cout << std::endl; + + for (int z = 0; z < m_max_index_z; z++) { + std::cout << std::setw(3) << z << ": "; for (int x = 0; x < m_max_index_x; x++) { - std::cout << std::setw(3) << x; - } - std::cout << std::endl; - - for (int z = 0; z < m_max_index_z; z++) { - std::cout << std::setw(3) << z <<": "; - for (int x = 0; x < m_max_index_x; x++) { - std::cout << std::setw(3) << getIdxElem(x, y, z).totalcost; - } - std::cout << std::endl; + std::cout << std::setw(3) + << getIdxElem(x, y, z).totalcost; } std::cout << std::endl; } std::cout << std::endl; + } + std::cout << std::endl; } /******************************************************************************/ @@ -1435,8 +1414,7 @@ std::string Pathfinder::dirToName(PathDirections dir) void Pathfinder::printPath(std::vector path) { unsigned int current = 0; - for (std::vector::iterator i = path.begin(); - i != path.end(); ++i) { + for (std::vector::iterator i = path.begin(); i != path.end(); ++i) { std::cout << std::setw(3) << current << ":" << PP((*i)) << std::endl; current++; } diff --git a/src/pathfinder.h b/src/pathfinder.h index 526aa0ee8..f8a463361 100644 --- a/src/pathfinder.h +++ b/src/pathfinder.h @@ -36,7 +36,8 @@ class Map; /* Typedefs and macros */ /******************************************************************************/ -typedef enum { +typedef enum +{ DIR_XP, DIR_XM, DIR_ZP, @@ -44,10 +45,11 @@ typedef enum { } PathDirections; /** List of supported algorithms */ -typedef enum { - PA_DIJKSTRA, /**< Dijkstra shortest path algorithm */ - PA_PLAIN, /**< A* algorithm using heuristics to find a path */ - PA_PLAIN_NP /**< A* algorithm without prefetching of map data */ +typedef enum +{ + PA_DIJKSTRA, /**< Dijkstra shortest path algorithm */ + PA_PLAIN, /**< A* algorithm using heuristics to find a path */ + PA_PLAIN_NP /**< A* algorithm without prefetching of map data */ } PathAlgorithm; /******************************************************************************/ @@ -55,10 +57,6 @@ typedef enum { /******************************************************************************/ /** c wrapper function to use from scriptapi */ -std::vector get_path(Map *map, const NodeDefManager *ndef, - v3s16 source, - v3s16 destination, - unsigned int searchdistance, - unsigned int max_jump, - unsigned int max_drop, - PathAlgorithm algo); +std::vector get_path(Map *map, const NodeDefManager *ndef, v3s16 source, + v3s16 destination, unsigned int searchdistance, unsigned int max_jump, + unsigned int max_drop, PathAlgorithm algo); diff --git a/src/player.cpp b/src/player.cpp index 13b79da04..ac6dc18b9 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -26,11 +26,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "gamedef.h" #include "settings.h" #include "log.h" -#include "porting.h" // strlcpy +#include "porting.h" // strlcpy - -Player::Player(const char *name, IItemDefManager *idef): - inventory(idef) +Player::Player(const char *name, IItemDefManager *idef) : inventory(idef) { strlcpy(m_name, name, PLAYERNAME_SIZE); @@ -44,33 +42,32 @@ Player::Player(const char *name, IItemDefManager *idef): // Can be redefined via Lua inventory_formspec = "size[8,7.5]" - //"image[1,0.6;1,2;player.png]" - "list[current_player;main;0,3.5;8,4;]" - "list[current_player;craft;3,0;3,3;]" - "listring[]" - "list[current_player;craftpreview;7,1;1,1;]"; + //"image[1,0.6;1,2;player.png]" + "list[current_player;main;0,3.5;8,4;]" + "list[current_player;craft;3,0;3,3;]" + "listring[]" + "list[current_player;craftpreview;7,1;1,1;]"; // Initialize movement settings at default values, so movement can work // if the server fails to send them - movement_acceleration_default = 3 * BS; - movement_acceleration_air = 2 * BS; - movement_acceleration_fast = 10 * BS; - movement_speed_walk = 4 * BS; - movement_speed_crouch = 1.35 * BS; - movement_speed_fast = 20 * BS; - movement_speed_climb = 2 * BS; - movement_speed_jump = 6.5 * BS; - movement_liquid_fluidity = 1 * BS; - movement_liquid_fluidity_smooth = 0.5 * BS; - movement_liquid_sink = 10 * BS; - movement_gravity = 9.81 * BS; - local_animation_speed = 0.0; + movement_acceleration_default = 3 * BS; + movement_acceleration_air = 2 * BS; + movement_acceleration_fast = 10 * BS; + movement_speed_walk = 4 * BS; + movement_speed_crouch = 1.35 * BS; + movement_speed_fast = 20 * BS; + movement_speed_climb = 2 * BS; + movement_speed_jump = 6.5 * BS; + movement_liquid_fluidity = 1 * BS; + movement_liquid_fluidity_smooth = 0.5 * BS; + movement_liquid_sink = 10 * BS; + movement_gravity = 9.81 * BS; + local_animation_speed = 0.0; - hud_flags = - HUD_FLAG_HOTBAR_VISIBLE | HUD_FLAG_HEALTHBAR_VISIBLE | - HUD_FLAG_CROSSHAIR_VISIBLE | HUD_FLAG_WIELDITEM_VISIBLE | - HUD_FLAG_BREATHBAR_VISIBLE | HUD_FLAG_MINIMAP_VISIBLE | - HUD_FLAG_MINIMAP_RADAR_VISIBLE; + hud_flags = HUD_FLAG_HOTBAR_VISIBLE | HUD_FLAG_HEALTHBAR_VISIBLE | + HUD_FLAG_CROSSHAIR_VISIBLE | HUD_FLAG_WIELDITEM_VISIBLE | + HUD_FLAG_BREATHBAR_VISIBLE | HUD_FLAG_MINIMAP_VISIBLE | + HUD_FLAG_MINIMAP_RADAR_VISIBLE; hud_hotbar_itemcount = HUD_HOTBAR_ITEMCOUNT_DEFAULT; @@ -78,7 +75,7 @@ Player::Player(const char *name, IItemDefManager *idef): // Register player setting callbacks for (const std::string &name : m_player_settings.setting_names) g_settings->registerChangedCallback(name, - &Player::settingsChangedCallback, &m_player_settings); + &Player::settingsChangedCallback, &m_player_settings); } Player::~Player() @@ -86,7 +83,7 @@ Player::~Player() // m_player_settings becomes invalid, remove callbacks for (const std::string &name : m_player_settings.setting_names) g_settings->deregisterChangedCallback(name, - &Player::settingsChangedCallback, &m_player_settings); + &Player::settingsChangedCallback, &m_player_settings); clearHud(); } @@ -127,7 +124,7 @@ u32 Player::addHud(HudElement *toadd) return id; } -HudElement* Player::getHud(u32 id) +HudElement *Player::getHud(u32 id) { MutexAutoLock lock(m_mutex); @@ -137,11 +134,11 @@ HudElement* Player::getHud(u32 id) return NULL; } -HudElement* Player::removeHud(u32 id) +HudElement *Player::removeHud(u32 id) { MutexAutoLock lock(m_mutex); - HudElement* retval = NULL; + HudElement *retval = NULL; if (id < hud.size()) { retval = hud[id]; hud[id] = NULL; @@ -153,7 +150,7 @@ void Player::clearHud() { MutexAutoLock lock(m_mutex); - while(!hud.empty()) { + while (!hud.empty()) { delete hud.back(); hud.pop_back(); } diff --git a/src/player.h b/src/player.h index f1b848a2a..9a7e1d5b6 100644 --- a/src/player.h +++ b/src/player.h @@ -29,7 +29,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #define PLAYERNAME_SIZE 20 -#define PLAYERNAME_ALLOWED_CHARS "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_" +#define PLAYERNAME_ALLOWED_CHARS \ + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_" #define PLAYERNAME_ALLOWED_CHARS_USER_EXPL "'a' to 'z', 'A' to 'Z', '0' to '9', '-', '_'" struct PlayerFovSpec @@ -48,22 +49,10 @@ struct PlayerControl { PlayerControl() = default; - PlayerControl( - bool a_up, - bool a_down, - bool a_left, - bool a_right, - bool a_jump, - bool a_aux1, - bool a_sneak, - bool a_zoom, - bool a_LMB, - bool a_RMB, - float a_pitch, - float a_yaw, - float a_sidew_move_joystick_axis, - float a_forw_move_joystick_axis - ) + PlayerControl(bool a_up, bool a_down, bool a_left, bool a_right, bool a_jump, + bool a_aux1, bool a_sneak, bool a_zoom, bool a_LMB, bool a_RMB, + float a_pitch, float a_yaw, float a_sidew_move_joystick_axis, + float a_forw_move_joystick_axis) { up = a_up; down = a_down; @@ -108,10 +97,9 @@ struct PlayerSettings bool noclip = false; bool autojump = false; - const std::string setting_names[9] = { - "free_move", "pitch_move", "fast_move", "freecam", "continuous_forward", "always_fly_fast", - "aux1_descends", "noclip", "autojump" - }; + const std::string setting_names[9] = {"free_move", "pitch_move", "fast_move", + "freecam", "continuous_forward", "always_fly_fast", + "aux1_descends", "noclip", "autojump"}; void readGlobalSettings(); }; @@ -123,27 +111,20 @@ class Environment; class Player { public: - Player(const char *name, IItemDefManager *idef); virtual ~Player() = 0; DISABLE_CLASS_COPY(Player); - virtual void move(f32 dtime, Environment *env, f32 pos_max_d) - {} + virtual void move(f32 dtime, Environment *env, f32 pos_max_d) {} virtual void move(f32 dtime, Environment *env, f32 pos_max_d, std::vector *collision_info) - {} - - const v3f &getSpeed() const { - return m_speed; } - void setSpeed(const v3f &speed) - { - m_speed = speed; - } + const v3f &getSpeed() const { return m_speed; } + + void setSpeed(const v3f &speed) { m_speed = speed; } const char *getName() const { return m_name; } @@ -182,7 +163,7 @@ public: std::string formspec_prepend; PlayerControl control; - const PlayerControl& getPlayerControl() { return control; } + const PlayerControl &getPlayerControl() { return control; } PlayerSettings &getPlayerSettings() { return m_player_settings; } static void settingsChangedCallback(const std::string &name, void *data); @@ -191,22 +172,16 @@ public: void setWieldIndex(u16 index); u16 getWieldIndex() const { return m_wield_index; } - void setFov(const PlayerFovSpec &spec) - { - m_fov_override_spec = spec; - } + void setFov(const PlayerFovSpec &spec) { m_fov_override_spec = spec; } - const PlayerFovSpec &getFov() const - { - return m_fov_override_spec; - } + const PlayerFovSpec &getFov() const { return m_fov_override_spec; } u32 keyPressed = 0; - HudElement* getHud(u32 id); - u32 addHud(HudElement* hud); - HudElement* removeHud(u32 id); - void clearHud(); + HudElement *getHud(u32 id); + u32 addHud(HudElement *hud); + HudElement *removeHud(u32 id); + void clearHud(); u32 hud_flags; s32 hud_hotbar_itemcount; @@ -215,9 +190,10 @@ protected: char m_name[PLAYERNAME_SIZE]; v3f m_speed; u16 m_wield_index = 0; - PlayerFovSpec m_fov_override_spec = { 0.0f, false, 0.0f }; + PlayerFovSpec m_fov_override_spec = {0.0f, false, 0.0f}; std::vector hud; + private: // Protect some critical areas // hud for example can be modified by EmergeThread diff --git a/src/porting.cpp b/src/porting.cpp index d902d3737..dc247acbf 100644 --- a/src/porting.cpp +++ b/src/porting.cpp @@ -25,35 +25,35 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "porting.h" -#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__DragonFly__) - #include - #include - extern char **environ; +#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__DragonFly__) +#include +#include +extern char **environ; #elif defined(_WIN32) - #include - #include - #include - #include - #include +#include +#include +#include +#include +#include #endif #if !defined(_WIN32) - #include - #include - #if !defined(__ANDROID__) - #include - #endif +#include +#include +#if !defined(__ANDROID__) +#include +#endif #endif #if defined(__hpux) - #define _PSTAT64 - #include +#define _PSTAT64 +#include #endif #if defined(__ANDROID__) - #include "porting_android.h" +#include "porting_android.h" #endif #if defined(__APPLE__) - // For _NSGetEnviron() - // Related: https://gitlab.haskell.org/ghc/ghc/issues/2458 - #include +// For _NSGetEnviron() +// Related: https://gitlab.haskell.org/ghc/ghc/issues/2458 +#include #endif #include "config.h" @@ -80,7 +80,7 @@ bool *signal_handler_killstatus() } #if !defined(_WIN32) // POSIX - #include +#include void signal_handler(int sig) { @@ -111,7 +111,7 @@ void signal_handler_init(void) } #else // _WIN32 - #include +#include BOOL WINAPI event_handler(DWORD sig) { @@ -123,7 +123,8 @@ BOOL WINAPI event_handler(DWORD sig) if (!g_killed) { dstream << "INFO: event_handler(): " << "Ctrl+C, Close Event, Logoff Event or Shutdown Event," - " shutting down." << std::endl; + " shutting down." + << std::endl; g_killed = true; } else { (void)signal(SIGINT, SIG_DFL); @@ -143,7 +144,6 @@ void signal_handler_init(void) #endif - /* Path mangler */ @@ -154,7 +154,6 @@ std::string path_user = ".."; std::string path_locale = path_share + DIR_DELIM + "locale"; std::string path_cache = path_user + DIR_DELIM + "cache"; - std::string getDataPath(const char *subpath) { return path_share + DIR_DELIM + subpath; @@ -164,9 +163,8 @@ void pathRemoveFile(char *path, char delim) { // Remove filename and path delimiter int i; - for(i = strlen(path)-1; i>=0; i--) - { - if(path[i] == delim) + for (i = strlen(path) - 1; i >= 0; i--) { + if (path[i] == delim) break; } path[i] = 0; @@ -174,14 +172,8 @@ void pathRemoveFile(char *path, char delim) bool detectMSVCBuildDir(const std::string &path) { - const char *ends[] = { - "bin\\Release", - "bin\\MinSizeRel", - "bin\\RelWithDebInfo", - "bin\\Debug", - "bin\\Build", - NULL - }; + const char *ends[] = {"bin\\Release", "bin\\MinSizeRel", "bin\\RelWithDebInfo", + "bin\\Debug", "bin\\Build", NULL}; return (!removeStringEnd(path, ends).empty()); } @@ -203,20 +195,19 @@ std::string get_sysinfo() GetFileVersionInfoA(filePath, 0, dwVersionSize, lpVersionInfo); VerQueryValueA(lpVersionInfo, "\\", (LPVOID *)&fixedFileInfo, &blockSize); - oss << "Windows/" - << HIWORD(fixedFileInfo->dwProductVersionMS) << '.' // Major - << LOWORD(fixedFileInfo->dwProductVersionMS) << '.' // Minor - << HIWORD(fixedFileInfo->dwProductVersionLS) << ' '; // Build + oss << "Windows/" << HIWORD(fixedFileInfo->dwProductVersionMS) << '.' // Major + << LOWORD(fixedFileInfo->dwProductVersionMS) << '.' // Minor + << HIWORD(fixedFileInfo->dwProductVersionLS) << ' '; // Build - #ifdef _WIN64 +#ifdef _WIN64 oss << "x86_64"; - #else +#else BOOL is64 = FALSE; if (IsWow64Process(GetCurrentProcess(), &is64) && is64) oss << "x86_64"; // 32-bit app on 64-bit OS else oss << "x86"; - #endif +#endif delete[] lpVersionInfo; delete[] filePath; @@ -225,12 +216,10 @@ std::string get_sysinfo() #else struct utsname osinfo; uname(&osinfo); - return std::string(osinfo.sysname) + "/" - + osinfo.release + " " + osinfo.machine; + return std::string(osinfo.sysname) + "/" + osinfo.release + " " + osinfo.machine; #endif } - bool getCurrentWorkingDir(char *buf, size_t len) { #ifdef _WIN32 @@ -241,16 +230,15 @@ bool getCurrentWorkingDir(char *buf, size_t len) #endif } - bool getExecPathFromProcfs(char *buf, size_t buflen) { #ifndef _WIN32 buflen--; ssize_t len; - if ((len = readlink("/proc/self/exe", buf, buflen)) == -1 && - (len = readlink("/proc/curproc/file", buf, buflen)) == -1 && - (len = readlink("/proc/curproc/exe", buf, buflen)) == -1) + if ((len = readlink("/proc/self/exe", buf, buflen)) == -1 && + (len = readlink("/proc/curproc/file", buf, buflen)) == -1 && + (len = readlink("/proc/curproc/exe", buf, buflen)) == -1) return false; buf[len] = '\0'; @@ -272,7 +260,6 @@ bool getCurrentExecPath(char *buf, size_t len) return true; } - //// Linux #elif defined(__linux__) @@ -284,7 +271,6 @@ bool getCurrentExecPath(char *buf, size_t len) return true; } - //// Mac OS X, Darwin #elif defined(__APPLE__) @@ -297,7 +283,6 @@ bool getCurrentExecPath(char *buf, size_t len) return true; } - //// FreeBSD, NetBSD, DragonFlyBSD #elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__DragonFly__) @@ -321,7 +306,6 @@ bool getCurrentExecPath(char *buf, size_t len) return true; } - //// Solaris #elif defined(__sun) || defined(sun) @@ -337,7 +321,6 @@ bool getCurrentExecPath(char *buf, size_t len) return true; } - // HP-UX #elif defined(__hpux) @@ -354,7 +337,6 @@ bool getCurrentExecPath(char *buf, size_t len) return true; } - #else bool getCurrentExecPath(char *buf, size_t len) @@ -364,7 +346,6 @@ bool getCurrentExecPath(char *buf, size_t len) #endif - //// Non-Windows #if !defined(_WIN32) @@ -372,14 +353,12 @@ const char *getHomeOrFail() { const char *home = getenv("HOME"); // In rare cases the HOME environment variable may be unset - FATAL_ERROR_IF(!home, - "Required environment variable HOME is not set"); + FATAL_ERROR_IF(!home, "Required environment variable HOME is not set"); return home; } #endif - //// Windows #if defined(_WIN32) @@ -389,7 +368,7 @@ bool setSystemPaths() // Find path of executable and set path_share relative to it FATAL_ERROR_IF(!getCurrentExecPath(buf, sizeof(buf)), - "Failed to get current executable path"); + "Failed to get current executable path"); pathRemoveFile(buf, '\\'); std::string exepath(buf); @@ -397,8 +376,8 @@ bool setSystemPaths() // Use ".\bin\.." path_share = exepath + "\\.."; if (detectMSVCBuildDir(exepath)) { - // The msvc build dir schould normaly not be present if properly installed, - // but its usefull for debugging. + // The msvc build dir schould normaly not be present if properly + // installed, but its usefull for debugging. path_share += DIR_DELIM ".."; } @@ -410,9 +389,9 @@ bool setSystemPaths() return true; } - //// Linux -#elif defined(__linux__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__DragonFly__) +#elif defined(__linux__) || defined(__FreeBSD__) || defined(__NetBSD__) || \ + defined(__DragonFly__) bool setSystemPaths() { @@ -420,7 +399,7 @@ bool setSystemPaths() if (!getCurrentExecPath(buf, sizeof(buf))) { #ifdef __ANDROID__ - errorstream << "Unable to read bindir "<< std::endl; + errorstream << "Unable to read bindir " << std::endl; #else FATAL_ERROR("Unable to read bindir"); #endif @@ -437,28 +416,28 @@ bool setSystemPaths() if (!static_sharedir.empty() && static_sharedir != ".") trylist.push_back(static_sharedir); - trylist.push_back(bindir + DIR_DELIM ".." DIR_DELIM "share" - DIR_DELIM + PROJECT_NAME); + trylist.push_back(bindir + DIR_DELIM ".." DIR_DELIM "share" DIR_DELIM + + PROJECT_NAME); trylist.push_back(bindir + DIR_DELIM ".."); #ifdef __ANDROID__ trylist.push_back(path_user); #endif - for (std::list::const_iterator - i = trylist.begin(); i != trylist.end(); ++i) { + for (std::list::const_iterator i = trylist.begin(); + i != trylist.end(); ++i) { const std::string &trypath = *i; if (!fs::PathExists(trypath) || - !fs::PathExists(trypath + DIR_DELIM + "builtin")) { - warningstream << "system-wide share not found at \"" - << trypath << "\""<< std::endl; + !fs::PathExists(trypath + DIR_DELIM + "builtin")) { + warningstream << "system-wide share not found at \"" << trypath + << "\"" << std::endl; continue; } // Warn if was not the first alternative if (i != trylist.begin()) { - warningstream << "system-wide share found at \"" - << trypath << "\"" << std::endl; + warningstream << "system-wide share found at \"" << trypath + << "\"" << std::endl; } path_share = trypath; @@ -466,14 +445,12 @@ bool setSystemPaths() } #ifndef __ANDROID__ - path_user = std::string(getHomeOrFail()) + DIR_DELIM "." - + PROJECT_NAME; + path_user = std::string(getHomeOrFail()) + DIR_DELIM "." + PROJECT_NAME; #endif return true; } - //// Mac OS X #elif defined(__APPLE__) @@ -482,32 +459,29 @@ bool setSystemPaths() CFBundleRef main_bundle = CFBundleGetMainBundle(); CFURLRef resources_url = CFBundleCopyResourcesDirectoryURL(main_bundle); char path[PATH_MAX]; - if (CFURLGetFileSystemRepresentation(resources_url, - TRUE, (UInt8 *)path, PATH_MAX)) { + if (CFURLGetFileSystemRepresentation( + resources_url, TRUE, (UInt8 *)path, PATH_MAX)) { path_share = std::string(path); } else { warningstream << "Could not determine bundle resource path" << std::endl; } CFRelease(resources_url); - path_user = std::string(getHomeOrFail()) - + "/Library/Application Support/" - + PROJECT_NAME; + path_user = std::string(getHomeOrFail()) + "/Library/Application Support/" + + PROJECT_NAME; return true; } - #else bool setSystemPaths() { path_share = STATIC_SHAREDIR; - path_user = std::string(getHomeOrFail()) + DIR_DELIM "." - + lowercase(PROJECT_NAME); + path_user = std::string(getHomeOrFail()) + DIR_DELIM "." + + lowercase(PROJECT_NAME); return true; } - #endif void migrateCachePath() @@ -520,13 +494,14 @@ void migrateCachePath() fs::RecursiveDelete(local_cache_path + DIR_DELIM + "tmp"); // Bail if migration impossible - if (path_cache == local_cache_path || !fs::PathExists(local_cache_path) - || fs::PathExists(path_cache)) { + if (path_cache == local_cache_path || !fs::PathExists(local_cache_path) || + fs::PathExists(path_cache)) { return; } if (!fs::Rename(local_cache_path, path_cache)) { errorstream << "Failed to migrate local cache path " - "to system path!" << std::endl; + "to system path!" + << std::endl; } } @@ -537,24 +512,24 @@ void initializePaths() infostream << "Using relative paths (RUN_IN_PLACE)" << std::endl; - bool success = - getCurrentExecPath(buf, sizeof(buf)) || - getExecPathFromProcfs(buf, sizeof(buf)); + bool success = getCurrentExecPath(buf, sizeof(buf)) || + getExecPathFromProcfs(buf, sizeof(buf)); if (success) { pathRemoveFile(buf, DIR_DELIM_CHAR); std::string execpath(buf); path_share = execpath + DIR_DELIM ".."; - path_user = execpath + DIR_DELIM ".."; + path_user = execpath + DIR_DELIM ".."; if (detectMSVCBuildDir(execpath)) { path_share += DIR_DELIM ".."; - path_user += DIR_DELIM ".."; + path_user += DIR_DELIM ".."; } } else { errorstream << "Failed to get paths by executable location, " - "trying cwd" << std::endl; + "trying cwd" + << std::endl; if (!getCurrentWorkingDir(buf, sizeof(buf))) FATAL_ERROR("Ran out of methods to get paths"); @@ -571,7 +546,7 @@ void initializePaths() std::string execpath(buf); path_share = execpath; - path_user = execpath; + path_user = execpath; } path_cache = path_user + DIR_DELIM + "cache"; #else @@ -580,10 +555,9 @@ void initializePaths() if (!setSystemPaths()) errorstream << "Failed to get one or more system-wide path" << std::endl; - -# ifdef _WIN32 +#ifdef _WIN32 path_cache = path_user + DIR_DELIM + "cache"; -# else +#else // Initialize path_cache // First try $XDG_CACHE_HOME/PROJECT_NAME const char *cache_dir = getenv("XDG_CACHE_HOME"); @@ -592,15 +566,15 @@ void initializePaths() path_cache = std::string(cache_dir) + DIR_DELIM + PROJECT_NAME; } else if (home_dir) { // Then try $HOME/.cache/PROJECT_NAME - path_cache = std::string(home_dir) + DIR_DELIM + ".cache" - + DIR_DELIM + PROJECT_NAME; + path_cache = std::string(home_dir) + DIR_DELIM + ".cache" + DIR_DELIM + + PROJECT_NAME; } else { // If neither works, use $PATH_USER/cache path_cache = path_user + DIR_DELIM + "cache"; } // Migrate cache folder to new location if possible migrateCachePath(); -# endif // _WIN32 +#endif // _WIN32 #endif // RUN_IN_PLACE infostream << "Detected share path: " << path_share << std::endl; @@ -609,30 +583,30 @@ void initializePaths() #if USE_GETTEXT bool found_localedir = false; -# ifdef STATIC_LOCALEDIR +#ifdef STATIC_LOCALEDIR /* STATIC_LOCALEDIR may be a generalized path such as /usr/share/locale that * doesn't necessarily contain our locale files, so check data path first. */ path_locale = getDataPath("locale"); if (fs::PathExists(path_locale)) { found_localedir = true; infostream << "Using in-place locale directory " << path_locale - << " even though a static one was provided." << std::endl; + << " even though a static one was provided." << std::endl; } else if (STATIC_LOCALEDIR[0] && fs::PathExists(STATIC_LOCALEDIR)) { found_localedir = true; path_locale = STATIC_LOCALEDIR; infostream << "Using static locale directory " << STATIC_LOCALEDIR - << std::endl; + << std::endl; } -# else +#else path_locale = getDataPath("locale"); if (fs::PathExists(path_locale)) { found_localedir = true; } -# endif +#endif if (!found_localedir) { warningstream << "Couldn't find a locale directory!" << std::endl; } -#endif // USE_GETTEXT +#endif // USE_GETTEXT } //// @@ -678,8 +652,11 @@ void attachOrCreateConsole() { #ifdef _WIN32 static bool consoleAllocated = false; - const bool redirected = (_fileno(stdout) == -2 || _fileno(stdout) == -1); // If output is redirected to e.g a file - if (!consoleAllocated && redirected && (AttachConsole(ATTACH_PARENT_PROCESS) || AllocConsole())) { + const bool redirected = (_fileno(stdout) == -2 || + _fileno(stdout) == -1); // If output is redirected to e.g + // a file + if (!consoleAllocated && redirected && + (AttachConsole(ATTACH_PARENT_PROCESS) || AllocConsole())) { freopen("CONOUT$", "w", stdout); freopen("CONOUT$", "w", stderr); consoleAllocated = true; @@ -718,17 +695,18 @@ bool openURL(const std::string &url) } #if defined(_WIN32) - return (intptr_t)ShellExecuteA(NULL, NULL, url.c_str(), NULL, NULL, SW_SHOWNORMAL) > 32; + return (intptr_t)ShellExecuteA( + NULL, NULL, url.c_str(), NULL, NULL, SW_SHOWNORMAL) > 32; #elif defined(__ANDROID__) openURLAndroid(url); return true; #elif defined(__APPLE__) const char *argv[] = {"open", url.c_str(), NULL}; - return posix_spawnp(NULL, "open", NULL, NULL, (char**)argv, - (*_NSGetEnviron())) == 0; + return posix_spawnp(NULL, "open", NULL, NULL, (char **)argv, + (*_NSGetEnviron())) == 0; #else const char *argv[] = {"xdg-open", url.c_str(), NULL}; - return posix_spawnp(NULL, "xdg-open", NULL, NULL, (char**)argv, environ) == 0; + return posix_spawnp(NULL, "xdg-open", NULL, NULL, (char **)argv, environ) == 0; #endif } @@ -746,4 +724,4 @@ double perf_freq = get_perf_freq(); #endif -} //namespace porting +} // namespace porting diff --git a/src/porting.h b/src/porting.h index f50f0a950..e187b6e4b 100644 --- a/src/porting.h +++ b/src/porting.h @@ -24,11 +24,11 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once #ifdef _WIN32 - #ifdef _WIN32_WINNT - #undef _WIN32_WINNT - #endif - #define _WIN32_WINNT 0x0501 // We need to do this before any other headers - // because those might include sdkddkver.h which defines _WIN32_WINNT if not already set +#ifdef _WIN32_WINNT +#undef _WIN32_WINNT +#endif +#define _WIN32_WINNT 0x0501 // We need to do this before any other headers +// because those might include sdkddkver.h which defines _WIN32_WINNT if not already set #endif #include @@ -41,48 +41,48 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "gettime.h" #ifdef _MSC_VER - #define SWPRINTF_CHARSTRING L"%S" +#define SWPRINTF_CHARSTRING L"%S" #else - #define SWPRINTF_CHARSTRING L"%s" +#define SWPRINTF_CHARSTRING L"%s" #endif -//currently not needed -//template struct alignment_trick { char c; T member; }; +// currently not needed +// template struct alignment_trick { char c; T member; }; //#define ALIGNOF(type) offsetof (alignment_trick, member) #ifdef _WIN32 - #include +#include - #define sleep_ms(x) Sleep(x) +#define sleep_ms(x) Sleep(x) #else - #include - #include //for uintptr_t +#include +#include //for uintptr_t - // Use standard Posix macro for Linux - #if (defined(linux) || defined(__linux)) && !defined(__linux__) - #define __linux__ - #endif - #if (defined(__linux__) || defined(__GNU__)) && !defined(_GNU_SOURCE) - #define _GNU_SOURCE - #endif +// Use standard Posix macro for Linux +#if (defined(linux) || defined(__linux)) && !defined(__linux__) +#define __linux__ +#endif +#if (defined(__linux__) || defined(__GNU__)) && !defined(_GNU_SOURCE) +#define _GNU_SOURCE +#endif - #define sleep_ms(x) usleep(x*1000) +#define sleep_ms(x) usleep(x * 1000) #endif #ifdef _MSC_VER - #define ALIGNOF(x) __alignof(x) - #define strtok_r(x, y, z) strtok_s(x, y, z) - #define strtof(x, y) (float)strtod(x, y) - #define strtoll(x, y, z) _strtoi64(x, y, z) - #define strtoull(x, y, z) _strtoui64(x, y, z) - #define strcasecmp(x, y) stricmp(x, y) - #define strncasecmp(x, y, n) strnicmp(x, y, n) +#define ALIGNOF(x) __alignof(x) +#define strtok_r(x, y, z) strtok_s(x, y, z) +#define strtof(x, y) (float)strtod(x, y) +#define strtoll(x, y, z) _strtoi64(x, y, z) +#define strtoull(x, y, z) _strtoui64(x, y, z) +#define strcasecmp(x, y) stricmp(x, y) +#define strncasecmp(x, y, n) strnicmp(x, y, n) #else - #define ALIGNOF(x) __alignof__(x) +#define ALIGNOF(x) __alignof__(x) #endif #ifdef __MINGW32__ - #define strtok_r(x, y, z) mystrtok_r(x, y, z) +#define strtok_r(x, y, z) mystrtok_r(x, y, z) #endif // strlcpy is missing from glibc. thanks a lot, drepper. @@ -90,34 +90,33 @@ with this program; if not, write to the Free Software Foundation, Inc., // We can't simply alias strlcpy to MSVC's strcpy_s, since strcpy_s by // default raises an assertion error and aborts the program if the buffer is // too small. -#if defined(__FreeBSD__) || defined(__NetBSD__) || \ - defined(__OpenBSD__) || defined(__DragonFly__) || \ - defined(__APPLE__) || \ - defined(__sun) || defined(sun) || \ - defined(__QNX__) || defined(__QNXNTO__) - #define HAVE_STRLCPY +#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || \ + defined(__DragonFly__) || defined(__APPLE__) || defined(__sun) || \ + defined(sun) || defined(__QNX__) || defined(__QNXNTO__) +#define HAVE_STRLCPY #endif // So we need to define our own. #ifndef HAVE_STRLCPY - #define strlcpy(d, s, n) mystrlcpy(d, s, n) +#define strlcpy(d, s, n) mystrlcpy(d, s, n) #endif -#define PADDING(x, y) ((ALIGNOF(y) - ((uintptr_t)(x) & (ALIGNOF(y) - 1))) & (ALIGNOF(y) - 1)) +#define PADDING(x, y) \ + ((ALIGNOF(y) - ((uintptr_t)(x) & (ALIGNOF(y) - 1))) & (ALIGNOF(y) - 1)) #if defined(__APPLE__) - #include - #include +#include +#include #endif #ifndef _WIN32 // Posix - #include - #include +#include +#include #if defined(__MACH__) && defined(__APPLE__) - #include - #include - #endif +#include +#include +#endif #endif namespace porting @@ -130,7 +129,7 @@ namespace porting void signal_handler_init(); // Returns a pointer to a bool. // When the bool is true, program should quit. -bool * signal_handler_killstatus(); +bool *signal_handler_killstatus(); /* Path of static data directory. @@ -178,7 +177,6 @@ void initializePaths(); */ std::string get_sysinfo(); - // Monotonic counter getters. #ifdef _WIN32 // Windows @@ -193,18 +191,31 @@ inline u64 os_get_time(double mult) } // Resolution is <1us. -inline u64 getTimeS() { return os_get_time(1); } -inline u64 getTimeMs() { return os_get_time(1000); } -inline u64 getTimeUs() { return os_get_time(1000*1000); } -inline u64 getTimeNs() { return os_get_time(1000*1000*1000); } +inline u64 getTimeS() +{ + return os_get_time(1); +} +inline u64 getTimeMs() +{ + return os_get_time(1000); +} +inline u64 getTimeUs() +{ + return os_get_time(1000 * 1000); +} +inline u64 getTimeNs() +{ + return os_get_time(1000 * 1000 * 1000); +} #else // Posix inline void os_get_clock(struct timespec *ts) { #if defined(__MACH__) && defined(__APPLE__) -// From http://stackoverflow.com/questions/5167269/clock-gettime-alternative-in-mac-os-x -// OS X does not have clock_gettime, use clock_get_time + // From + // http://stackoverflow.com/questions/5167269/clock-gettime-alternative-in-mac-os-x + // OS X does not have clock_gettime, use clock_get_time clock_serv_t cclock; mach_timespec_t mts; host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock); @@ -256,10 +267,14 @@ inline u64 getTimeNs() inline u64 getTime(TimePrecision prec) { switch (prec) { - case PRECISION_SECONDS: return getTimeS(); - case PRECISION_MILLI: return getTimeMs(); - case PRECISION_MICRO: return getTimeUs(); - case PRECISION_NANO: return getTimeNs(); + case PRECISION_SECONDS: + return getTimeS(); + case PRECISION_MILLI: + return getTimeMs(); + case PRECISION_MICRO: + return getTimeUs(); + case PRECISION_NANO: + return getTimeNs(); } FATAL_ERROR("Called getTime with invalid time precision"); } @@ -283,44 +298,44 @@ inline const char *getPlatformName() { return #if defined(ANDROID) - "Android" + "Android" #elif defined(__linux__) - "Linux" + "Linux" #elif defined(_WIN32) || defined(_WIN64) - "Windows" -#elif defined(__DragonFly__) || defined(__FreeBSD__) || \ - defined(__NetBSD__) || defined(__OpenBSD__) - "BSD" + "Windows" +#elif defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || \ + defined(__OpenBSD__) + "BSD" #elif defined(__APPLE__) && defined(__MACH__) - #if TARGET_OS_MAC - "OSX" - #elif TARGET_OS_IPHONE - "iOS" - #else - "Apple" - #endif -#elif defined(_AIX) - "AIX" -#elif defined(__hpux) - "HP-UX" -#elif defined(__sun) || defined(sun) - #if defined(__SVR4) - "Solaris" - #else - "SunOS" - #endif -#elif defined(__CYGWIN__) - "Cygwin" -#elif defined(__unix__) || defined(__unix) - #if defined(_POSIX_VERSION) - "Posix" - #else - "Unix" - #endif +#if TARGET_OS_MAC + "OSX" +#elif TARGET_OS_IPHONE + "iOS" #else - "?" + "Apple" #endif - ; +#elif defined(_AIX) + "AIX" +#elif defined(__hpux) + "HP-UX" +#elif defined(__sun) || defined(sun) +#if defined(__SVR4) + "Solaris" +#else + "SunOS" +#endif +#elif defined(__CYGWIN__) + "Cygwin" +#elif defined(__unix__) || defined(__unix) +#if defined(_POSIX_VERSION) + "Posix" +#else + "Unix" +#endif +#else + "?" +#endif + ; } bool secure_rand_fill_buf(void *buf, size_t len); diff --git a/src/porting_android.cpp b/src/porting_android.cpp index 41b521ec2..f3754d835 100644 --- a/src/porting_android.cpp +++ b/src/porting_android.cpp @@ -51,7 +51,8 @@ void android_main(android_app *app) main(ARRLEN(argv) - 1, argv); free(argv[0]); } catch (std::exception &e) { - errorstream << "Uncaught exception in main thread: " << e.what() << std::endl; + errorstream << "Uncaught exception in main thread: " << e.what() + << std::endl; retval = -1; } catch (...) { errorstream << "Uncaught exception in main thread!" << std::endl; @@ -69,19 +70,20 @@ void android_main(android_app *app) * ToDo: this doesn't work as expected, there's a workaround for it right now */ extern "C" { - JNIEXPORT void JNICALL Java_net_minetest_minetest_GameActivity_putMessageBoxResult( - JNIEnv *env, jclass thiz, jstring text) - { - errorstream << - "Java_net_minetest_minetest_GameActivity_putMessageBoxResult got: " << - std::string((const char*) env->GetStringChars(text, nullptr)) << std::endl; - } +JNIEXPORT void JNICALL Java_net_minetest_minetest_GameActivity_putMessageBoxResult( + JNIEnv *env, jclass thiz, jstring text) +{ + errorstream << "Java_net_minetest_minetest_GameActivity_putMessageBoxResult got: " + << std::string((const char *)env->GetStringChars(text, nullptr)) + << std::endl; +} } -namespace porting { +namespace porting +{ android_app *app_global; -JNIEnv *jnienv; -jclass nativeActivity; +JNIEnv *jnienv; +jclass nativeActivity; jclass findClass(const std::string &classname) { @@ -92,12 +94,12 @@ jclass findClass(const std::string &classname) jmethodID getClassLoader = jnienv->GetMethodID( nativeactivity, "getClassLoader", "()Ljava/lang/ClassLoader;"); jobject cls = jnienv->CallObjectMethod( - app_global->activity->clazz, getClassLoader); + app_global->activity->clazz, getClassLoader); jclass classLoader = jnienv->FindClass("java/lang/ClassLoader"); jmethodID findClass = jnienv->GetMethodID(classLoader, "loadClass", - "(Ljava/lang/String;)Ljava/lang/Class;"); + "(Ljava/lang/String;)Ljava/lang/Class;"); jstring strClassName = jnienv->NewStringUTF(classname.c_str()); - return (jclass) jnienv->CallObjectMethod(cls, findClass, strClassName); + return (jclass)jnienv->CallObjectMethod(cls, findClass, strClassName); } void initAndroid() @@ -116,14 +118,14 @@ void initAndroid() nativeActivity = findClass("net/minetest/minetest/GameActivity"); if (nativeActivity == nullptr) - errorstream << - "porting::initAndroid unable to find java native activity class" << - std::endl; + errorstream << "porting::initAndroid unable to find java native activity " + "class" + << std::endl; #ifdef GPROF // in the start-up code - __android_log_print(ANDROID_LOG_ERROR, PROJECT_NAME_C, - "Initializing GPROF profiler"); + __android_log_print( + ANDROID_LOG_ERROR, PROJECT_NAME_C, "Initializing GPROF profiler"); monstartup("libMinetest.so"); #endif } @@ -171,7 +173,7 @@ static std::string getAndroidPath( ob_file = jnienv->CallStaticObjectMethod(cls, mt_getter); // Call getAbsolutePath - auto js_path = (jstring) jnienv->CallObjectMethod(ob_file, mt_getAbsPath); + auto js_path = (jstring)jnienv->CallObjectMethod(ob_file, mt_getAbsPath); return javaStringToUTF8(js_path); } @@ -183,15 +185,15 @@ void initializePathsAndroid() // Get File class jclass cls_File = jnienv->FindClass("java/io/File"); // Get getAbsolutePath method - jmethodID mt_getAbsPath = jnienv->GetMethodID(cls_File, - "getAbsolutePath", "()Ljava/lang/String;"); - std::string path_storage = getAndroidPath(cls_Env, nullptr, - mt_getAbsPath, "getExternalStorageDirectory"); + jmethodID mt_getAbsPath = jnienv->GetMethodID( + cls_File, "getAbsolutePath", "()Ljava/lang/String;"); + std::string path_storage = getAndroidPath( + cls_Env, nullptr, mt_getAbsPath, "getExternalStorageDirectory"); - path_user = path_storage + DIR_DELIM + PROJECT_NAME_C; - path_share = path_storage + DIR_DELIM + PROJECT_NAME_C; - path_cache = getAndroidPath(nativeActivity, - app_global->activity->clazz, mt_getAbsPath, "getCacheDir"); + path_user = path_storage + DIR_DELIM + PROJECT_NAME_C; + path_share = path_storage + DIR_DELIM + PROJECT_NAME_C; + path_cache = getAndroidPath(nativeActivity, app_global->activity->clazz, + mt_getAbsPath, "getCacheDir"); migrateCachePath(); } @@ -199,27 +201,27 @@ void showInputDialog(const std::string &acceptButton, const std::string &hint, const std::string ¤t, int editType) { jmethodID showdialog = jnienv->GetMethodID(nativeActivity, "showDialog", - "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)V"); + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)V"); - FATAL_ERROR_IF(showdialog == nullptr, - "porting::showInputDialog unable to find java show dialog method"); + FATAL_ERROR_IF(showdialog == nullptr, "porting::showInputDialog unable to find " + "java show dialog method"); jstring jacceptButton = jnienv->NewStringUTF(acceptButton.c_str()); - jstring jhint = jnienv->NewStringUTF(hint.c_str()); - jstring jcurrent = jnienv->NewStringUTF(current.c_str()); - jint jeditType = editType; + jstring jhint = jnienv->NewStringUTF(hint.c_str()); + jstring jcurrent = jnienv->NewStringUTF(current.c_str()); + jint jeditType = editType; - jnienv->CallVoidMethod(app_global->activity->clazz, showdialog, - jacceptButton, jhint, jcurrent, jeditType); + jnienv->CallVoidMethod(app_global->activity->clazz, showdialog, jacceptButton, + jhint, jcurrent, jeditType); } void openURLAndroid(const std::string &url) { - jmethodID url_open = jnienv->GetMethodID(nativeActivity, "openURL", - "(Ljava/lang/String;)V"); + jmethodID url_open = jnienv->GetMethodID( + nativeActivity, "openURL", "(Ljava/lang/String;)V"); FATAL_ERROR_IF(url_open == nullptr, - "porting::openURLAndroid unable to find java openURL method"); + "porting::openURLAndroid unable to find java openURL method"); jstring jurl = jnienv->NewStringUTF(url.c_str()); jnienv->CallVoidMethod(app_global->activity->clazz, url_open, jurl); @@ -227,29 +229,29 @@ void openURLAndroid(const std::string &url) int getInputDialogState() { - jmethodID dialogstate = jnienv->GetMethodID(nativeActivity, - "getDialogState", "()I"); + jmethodID dialogstate = + jnienv->GetMethodID(nativeActivity, "getDialogState", "()I"); - FATAL_ERROR_IF(dialogstate == nullptr, - "porting::getInputDialogState unable to find java dialog state method"); + FATAL_ERROR_IF(dialogstate == nullptr, "porting::getInputDialogState unable to " + "find java dialog state method"); return jnienv->CallIntMethod(app_global->activity->clazz, dialogstate); } std::string getInputDialogValue() { - jmethodID dialogvalue = jnienv->GetMethodID(nativeActivity, - "getDialogValue", "()Ljava/lang/String;"); + jmethodID dialogvalue = jnienv->GetMethodID( + nativeActivity, "getDialogValue", "()Ljava/lang/String;"); - FATAL_ERROR_IF(dialogvalue == nullptr, - "porting::getInputDialogValue unable to find java dialog value method"); + FATAL_ERROR_IF(dialogvalue == nullptr, "porting::getInputDialogValue unable to " + "find java dialog value method"); - jobject result = jnienv->CallObjectMethod(app_global->activity->clazz, - dialogvalue); + jobject result = jnienv->CallObjectMethod( + app_global->activity->clazz, dialogvalue); - const char *javachars = jnienv->GetStringUTFChars((jstring) result, nullptr); + const char *javachars = jnienv->GetStringUTFChars((jstring)result, nullptr); std::string text(javachars); - jnienv->ReleaseStringUTFChars((jstring) result, javachars); + jnienv->ReleaseStringUTFChars((jstring)result, javachars); return text; } @@ -261,11 +263,11 @@ float getDisplayDensity() static float value = 0; if (firstrun) { - jmethodID getDensity = jnienv->GetMethodID(nativeActivity, - "getDensity", "()F"); + jmethodID getDensity = + jnienv->GetMethodID(nativeActivity, "getDensity", "()F"); - FATAL_ERROR_IF(getDensity == nullptr, - "porting::getDisplayDensity unable to find java getDensity method"); + FATAL_ERROR_IF(getDensity == nullptr, "porting::getDisplayDensity unable " + "to find java getDensity method"); value = jnienv->CallFloatMethod(app_global->activity->clazz, getDensity); firstrun = false; @@ -279,23 +281,25 @@ v2u32 getDisplaySize() static v2u32 retval; if (firstrun) { - jmethodID getDisplayWidth = jnienv->GetMethodID(nativeActivity, - "getDisplayWidth", "()I"); + jmethodID getDisplayWidth = jnienv->GetMethodID( + nativeActivity, "getDisplayWidth", "()I"); FATAL_ERROR_IF(getDisplayWidth == nullptr, - "porting::getDisplayWidth unable to find java getDisplayWidth method"); + "porting::getDisplayWidth unable to find java " + "getDisplayWidth method"); - retval.X = jnienv->CallIntMethod(app_global->activity->clazz, - getDisplayWidth); + retval.X = jnienv->CallIntMethod( + app_global->activity->clazz, getDisplayWidth); - jmethodID getDisplayHeight = jnienv->GetMethodID(nativeActivity, - "getDisplayHeight", "()I"); + jmethodID getDisplayHeight = jnienv->GetMethodID( + nativeActivity, "getDisplayHeight", "()I"); FATAL_ERROR_IF(getDisplayHeight == nullptr, - "porting::getDisplayHeight unable to find java getDisplayHeight method"); + "porting::getDisplayHeight unable to find java " + "getDisplayHeight method"); - retval.Y = jnienv->CallIntMethod(app_global->activity->clazz, - getDisplayHeight); + retval.Y = jnienv->CallIntMethod( + app_global->activity->clazz, getDisplayHeight); firstrun = false; } diff --git a/src/porting_android.h b/src/porting_android.h index 6eb054041..da78379b4 100644 --- a/src/porting_android.h +++ b/src/porting_android.h @@ -29,7 +29,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include -namespace porting { +namespace porting +{ // java app extern android_app *app_global; @@ -55,8 +56,8 @@ void initializePathsAndroid(); * @param editType type of texfield * (1==multiline text input; 2==single line text input; 3=password field) */ -void showInputDialog(const std::string &acceptButton, - const std::string &hint, const std::string ¤t, int editType); +void showInputDialog(const std::string &acceptButton, const std::string &hint, + const std::string ¤t, int editType); void openURLAndroid(const std::string &url); diff --git a/src/profiler.h b/src/profiler.h index b4a0657f9..a68043d2a 100644 --- a/src/profiler.h +++ b/src/profiler.h @@ -27,7 +27,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "threading/mutex_auto_lock.h" #include "util/timetaker.h" -#include "util/numeric.h" // paging() +#include "util/numeric.h" // paging() // Global profiler class Profiler; @@ -56,13 +56,11 @@ public: int print(std::ostream &o, u32 page = 1, u32 pagecount = 1); void getPage(GraphValues &o, u32 page, u32 pagecount); - void graphAdd(const std::string &id, float value) { MutexAutoLock lock(m_mutex); - std::map::iterator i = - m_graphvalues.find(id); - if(i == m_graphvalues.end()) + std::map::iterator i = m_graphvalues.find(id); + if (i == m_graphvalues.end()) m_graphvalues[id] = value; else i->second += value; @@ -74,7 +72,7 @@ public: m_graphvalues.clear(); } - void remove(const std::string& name) + void remove(const std::string &name) { MutexAutoLock lock(m_mutex); m_avgcounts.erase(name); @@ -89,7 +87,8 @@ private: u64 m_start_time; }; -enum ScopeProfilerType{ +enum ScopeProfilerType +{ SPT_ADD, SPT_AVG, SPT_GRAPH_ADD @@ -101,6 +100,7 @@ public: ScopeProfiler(Profiler *profiler, const std::string &name, ScopeProfilerType type = SPT_ADD); ~ScopeProfiler(); + private: Profiler *m_profiler = nullptr; std::string m_name; diff --git a/src/raycast.cpp b/src/raycast.cpp index a1993606f..bb13e6108 100644 --- a/src/raycast.cpp +++ b/src/raycast.cpp @@ -22,8 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irr_aabb3d.h" #include "constants.h" -bool RaycastSort::operator() (const PointedThing &pt1, - const PointedThing &pt2) const +bool RaycastSort::operator()(const PointedThing &pt1, const PointedThing &pt2) const { // "nothing" can not be sorted assert(pt1.type != POINTEDTHING_NOTHING); @@ -46,30 +45,27 @@ bool RaycastSort::operator() (const PointedThing &pt1, if (pt1_distSq == pt2.distanceSq) { // Sort them to allow only one order if (pt1.type == POINTEDTHING_OBJECT) - return (pt2.type == POINTEDTHING_OBJECT - && pt1.object_id < pt2.object_id); + return (pt2.type == POINTEDTHING_OBJECT && + pt1.object_id < pt2.object_id); - return (pt2.type == POINTEDTHING_OBJECT - || pt1.node_undersurface < pt2.node_undersurface); + return (pt2.type == POINTEDTHING_OBJECT || + pt1.node_undersurface < pt2.node_undersurface); } return true; } - -RaycastState::RaycastState(const core::line3d &shootline, - bool objects_pointable, bool liquids_pointable, bool nodes_pointable) : - m_shootline(shootline), - m_iterator(shootline.start / BS, shootline.getVector() / BS), - m_previous_node(m_iterator.m_current_node_pos), - m_objects_pointable(objects_pointable), - m_liquids_pointable(liquids_pointable), - m_nodes_pointable(nodes_pointable) +RaycastState::RaycastState(const core::line3d &shootline, bool objects_pointable, + bool liquids_pointable, bool nodes_pointable) : + m_shootline(shootline), + m_iterator(shootline.start / BS, shootline.getVector() / BS), + m_previous_node(m_iterator.m_current_node_pos), + m_objects_pointable(objects_pointable), + m_liquids_pointable(liquids_pointable), m_nodes_pointable(nodes_pointable) { } - -bool boxLineCollision(const aabb3f &box, const v3f &start, - const v3f &dir, v3f *collision_point, v3s16 *collision_normal) +bool boxLineCollision(const aabb3f &box, const v3f &start, const v3f &dir, + v3f *collision_point, v3s16 *collision_normal) { if (box.isPointInside(start)) { *collision_point = start; @@ -87,10 +83,10 @@ bool boxLineCollision(const aabb3f &box, const v3f &start, if (m >= 0 && m <= 1) { *collision_point = start + dir * m; - if ((collision_point->Y >= box.MinEdge.Y) - && (collision_point->Y <= box.MaxEdge.Y) - && (collision_point->Z >= box.MinEdge.Z) - && (collision_point->Z <= box.MaxEdge.Z)) { + if ((collision_point->Y >= box.MinEdge.Y) && + (collision_point->Y <= box.MaxEdge.Y) && + (collision_point->Z >= box.MinEdge.Z) && + (collision_point->Z <= box.MaxEdge.Z)) { collision_normal->set((dir.X > 0) ? -1 : 1, 0, 0); return true; } @@ -106,10 +102,10 @@ bool boxLineCollision(const aabb3f &box, const v3f &start, if (m >= 0 && m <= 1) { *collision_point = start + dir * m; - if ((collision_point->X >= box.MinEdge.X) - && (collision_point->X <= box.MaxEdge.X) - && (collision_point->Z >= box.MinEdge.Z) - && (collision_point->Z <= box.MaxEdge.Z)) { + if ((collision_point->X >= box.MinEdge.X) && + (collision_point->X <= box.MaxEdge.X) && + (collision_point->Z >= box.MinEdge.Z) && + (collision_point->Z <= box.MaxEdge.Z)) { collision_normal->set(0, (dir.Y > 0) ? -1 : 1, 0); return true; } @@ -125,10 +121,10 @@ bool boxLineCollision(const aabb3f &box, const v3f &start, if (m >= 0 && m <= 1) { *collision_point = start + dir * m; - if ((collision_point->X >= box.MinEdge.X) - && (collision_point->X <= box.MaxEdge.X) - && (collision_point->Y >= box.MinEdge.Y) - && (collision_point->Y <= box.MaxEdge.Y)) { + if ((collision_point->X >= box.MinEdge.X) && + (collision_point->X <= box.MaxEdge.X) && + (collision_point->Y >= box.MinEdge.Y) && + (collision_point->Y <= box.MaxEdge.Y)) { collision_normal->set(0, 0, (dir.Z > 0) ? -1 : 1); return true; } diff --git a/src/raycast.h b/src/raycast.h index 4f5ca2a5b..38190287f 100644 --- a/src/raycast.h +++ b/src/raycast.h @@ -25,7 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc., //! Sorts PointedThings based on their distance. struct RaycastSort { - bool operator() (const PointedThing &pt1, const PointedThing &pt2) const; + bool operator()(const PointedThing &pt1, const PointedThing &pt2) const; }; //! Describes the state of a raycast. @@ -38,7 +38,7 @@ public: * @param liquids pointable if false, liquid nodes won't be found */ RaycastState(const core::line3d &shootline, bool objects_pointable, - bool liquids_pointable, bool nodes_pointable = true); + bool liquids_pointable, bool nodes_pointable = true); //! Shootline of the raycast. core::line3d m_shootline; @@ -58,7 +58,7 @@ public: bool m_nodes_pointable; //! The code needs to search these nodes around the center node. - core::aabbox3d m_search_range { 0, 0, 0, 0, 0, 0 }; + core::aabbox3d m_search_range{0, 0, 0, 0, 0, 0}; //! If true, the Environment will initialize this state. bool m_initialization_needed = true; @@ -75,4 +75,4 @@ public: * @returns true if a collision point was found */ bool boxLineCollision(const aabb3f &box, const v3f &start, const v3f &dir, - v3f *collision_point, v3s16 *collision_normal); + v3f *collision_point, v3s16 *collision_normal); diff --git a/src/reflowscan.cpp b/src/reflowscan.cpp index 9d5c965d8..2b2581cfc 100644 --- a/src/reflowscan.cpp +++ b/src/reflowscan.cpp @@ -23,10 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "nodedef.h" #include "util/timetaker.h" - -ReflowScan::ReflowScan(Map *map, const NodeDefManager *ndef) : - m_map(map), - m_ndef(ndef) +ReflowScan::ReflowScan(Map *map, const NodeDefManager *ndef) : m_map(map), m_ndef(ndef) { } @@ -48,9 +45,9 @@ void ReflowScan::scan(MapBlock *block, UniqueQueue *liquid_queue) // Scan the columns in the block for (s16 z = 0; z < MAP_BLOCKSIZE; z++) - for (s16 x = 0; x < MAP_BLOCKSIZE; x++) { - scanColumn(x, z); - } + for (s16 x = 0; x < MAP_BLOCKSIZE; x++) { + scanColumn(x, z); + } // Scan neighbouring columns from the nearby blocks as they might contain // liquid nodes that weren't allowed to flow to prevent gaps. @@ -94,9 +91,9 @@ inline bool ReflowScan::isLiquidFlowableTo(int x, int y, int z) MapNode node = block->getNodeNoCheck(dx, dy, dz, &valid_position); if (node.getContent() != CONTENT_IGNORE) { const ContentFeatures &f = m_ndef->get(node); - // NOTE: No need to check for flowing nodes with lower liquid level - // as they should only occur on top of other columns where they - // will be added to the queue themselves. + // NOTE: No need to check for flowing nodes with lower liquid + // level as they should only occur on top of other columns where + // they will be added to the queue themselves. return f.floodable; } } @@ -107,10 +104,8 @@ inline bool ReflowScan::isLiquidHorizontallyFlowable(int x, int y, int z) { // Check if the (x,y,z) might spread to one of the horizontally // neighbouring nodes - return isLiquidFlowableTo(x - 1, y, z) || - isLiquidFlowableTo(x + 1, y, z) || - isLiquidFlowableTo(x, y, z - 1) || - isLiquidFlowableTo(x, y, z + 1); + return isLiquidFlowableTo(x - 1, y, z) || isLiquidFlowableTo(x + 1, y, z) || + isLiquidFlowableTo(x, y, z - 1) || isLiquidFlowableTo(x, y, z + 1); } void ReflowScan::scanColumn(int x, int z) @@ -147,7 +142,8 @@ void ReflowScan::scanColumn(int x, int z) bool is_liquid = f.isLiquid(); if (is_ignore || was_ignore || is_liquid == was_liquid) { - // Neither topmost node of liquid column nor topmost node below column + // Neither topmost node of liquid column nor topmost node below + // column was_checked = false; was_pushed = false; } else if (is_liquid) { @@ -155,7 +151,8 @@ void ReflowScan::scanColumn(int x, int z) bool is_pushed = false; if (f.liquid_type == LIQUID_FLOWING || isLiquidHorizontallyFlowable(x, y, z)) { - m_liquid_queue->push_back(m_rel_block_pos + v3s16(x, y, z)); + m_liquid_queue->push_back( + m_rel_block_pos + v3s16(x, y, z)); is_pushed = true; } // Remember waschecked and waspushed to avoid repeated @@ -164,11 +161,16 @@ void ReflowScan::scanColumn(int x, int z) was_pushed = is_pushed; } else { // This is the topmost node below a liquid column - if (!was_pushed && (f.floodable || - (!was_checked && isLiquidHorizontallyFlowable(x, y + 1, z)))) { + if (!was_pushed && + (f.floodable || (!was_checked && + isLiquidHorizontallyFlowable( + x, + y + 1, + z)))) { // Activate the lowest node in the column which is one // node above this one - m_liquid_queue->push_back(m_rel_block_pos + v3s16(x, y + 1, z)); + m_liquid_queue->push_back( + m_rel_block_pos + v3s16(x, y + 1, z)); } } @@ -179,26 +181,35 @@ void ReflowScan::scanColumn(int x, int z) // Check the node below the current block MapBlock *below = lookupBlock(x, -1, z); if (below) { - MapNode node = below->getNodeNoCheck(dx, MAP_BLOCKSIZE - 1, dz, &valid_position); + MapNode node = below->getNodeNoCheck( + dx, MAP_BLOCKSIZE - 1, dz, &valid_position); const ContentFeatures &f = m_ndef->get(node); bool is_ignore = node.getContent() == CONTENT_IGNORE; bool is_liquid = f.isLiquid(); if (is_ignore || was_ignore || is_liquid == was_liquid) { - // Neither topmost node of liquid column nor topmost node below column + // Neither topmost node of liquid column nor topmost node below + // column } else if (is_liquid) { - // This is the topmost node in the column and might want to flow away + // This is the topmost node in the column and might want to flow + // away if (f.liquid_type == LIQUID_FLOWING || isLiquidHorizontallyFlowable(x, -1, z)) { - m_liquid_queue->push_back(m_rel_block_pos + v3s16(x, -1, z)); + m_liquid_queue->push_back( + m_rel_block_pos + v3s16(x, -1, z)); } } else { // This is the topmost node below a liquid column - if (!was_pushed && (f.floodable || - (!was_checked && isLiquidHorizontallyFlowable(x, 0, z)))) { + if (!was_pushed && + (f.floodable || (!was_checked && + isLiquidHorizontallyFlowable( + x, + 0, + z)))) { // Activate the lowest node in the column which is one // node above this one - m_liquid_queue->push_back(m_rel_block_pos + v3s16(x, 0, z)); + m_liquid_queue->push_back( + m_rel_block_pos + v3s16(x, 0, z)); } } } diff --git a/src/reflowscan.h b/src/reflowscan.h index 7961432bd..29f35d2d8 100644 --- a/src/reflowscan.h +++ b/src/reflowscan.h @@ -26,7 +26,8 @@ class NodeDefManager; class Map; class MapBlock; -class ReflowScan { +class ReflowScan +{ public: ReflowScan(Map *map, const NodeDefManager *ndef); void scan(MapBlock *block, UniqueQueue *liquid_queue); diff --git a/src/remoteplayer.cpp b/src/remoteplayer.cpp index bef60c792..0fb337065 100644 --- a/src/remoteplayer.cpp +++ b/src/remoteplayer.cpp @@ -22,7 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include "filesys.h" #include "gamedef.h" -#include "porting.h" // strlcpy +#include "porting.h" // strlcpy #include "server.h" #include "settings.h" #include "convert_json.h" @@ -36,28 +36,31 @@ bool RemotePlayer::m_setting_cache_loaded = false; float RemotePlayer::m_setting_chat_message_limit_per_10sec = 0.0f; u16 RemotePlayer::m_setting_chat_message_limit_trigger_kick = 0; -RemotePlayer::RemotePlayer(const char *name, IItemDefManager *idef): - Player(name, idef) +RemotePlayer::RemotePlayer(const char *name, IItemDefManager *idef) : Player(name, idef) { if (!RemotePlayer::m_setting_cache_loaded) { RemotePlayer::m_setting_chat_message_limit_per_10sec = - g_settings->getFloat("chat_message_limit_per_10sec"); + g_settings->getFloat("chat_message_limit_per_10sec"); RemotePlayer::m_setting_chat_message_limit_trigger_kick = - g_settings->getU16("chat_message_limit_trigger_kick"); + g_settings->getU16("chat_message_limit_trigger_kick"); RemotePlayer::m_setting_cache_loaded = true; } - movement_acceleration_default = g_settings->getFloat("movement_acceleration_default") * BS; - movement_acceleration_air = g_settings->getFloat("movement_acceleration_air") * BS; - movement_acceleration_fast = g_settings->getFloat("movement_acceleration_fast") * BS; - movement_speed_walk = g_settings->getFloat("movement_speed_walk") * BS; - movement_speed_crouch = g_settings->getFloat("movement_speed_crouch") * BS; - movement_speed_fast = g_settings->getFloat("movement_speed_fast") * BS; - movement_speed_climb = g_settings->getFloat("movement_speed_climb") * BS; - movement_speed_jump = g_settings->getFloat("movement_speed_jump") * BS; - movement_liquid_fluidity = g_settings->getFloat("movement_liquid_fluidity") * BS; - movement_liquid_fluidity_smooth = g_settings->getFloat("movement_liquid_fluidity_smooth") * BS; - movement_liquid_sink = g_settings->getFloat("movement_liquid_sink") * BS; - movement_gravity = g_settings->getFloat("movement_gravity") * BS; + movement_acceleration_default = + g_settings->getFloat("movement_acceleration_default") * BS; + movement_acceleration_air = + g_settings->getFloat("movement_acceleration_air") * BS; + movement_acceleration_fast = + g_settings->getFloat("movement_acceleration_fast") * BS; + movement_speed_walk = g_settings->getFloat("movement_speed_walk") * BS; + movement_speed_crouch = g_settings->getFloat("movement_speed_crouch") * BS; + movement_speed_fast = g_settings->getFloat("movement_speed_fast") * BS; + movement_speed_climb = g_settings->getFloat("movement_speed_climb") * BS; + movement_speed_jump = g_settings->getFloat("movement_speed_jump") * BS; + movement_liquid_fluidity = g_settings->getFloat("movement_liquid_fluidity") * BS; + movement_liquid_fluidity_smooth = + g_settings->getFloat("movement_liquid_fluidity_smooth") * BS; + movement_liquid_sink = g_settings->getFloat("movement_liquid_sink") * BS; + movement_gravity = g_settings->getFloat("movement_gravity") * BS; // copy defaults m_cloud_params.density = 0.4f; @@ -96,45 +99,50 @@ void RemotePlayer::serializeExtraAttributes(std::string &output) output = fastWriteJson(json_root); } - -void RemotePlayer::deSerialize(std::istream &is, const std::string &playername, - PlayerSAO *sao) +void RemotePlayer::deSerialize( + std::istream &is, const std::string &playername, PlayerSAO *sao) { Settings args; if (!args.parseConfigLines(is, "PlayerArgsEnd")) { - throw SerializationError("PlayerArgsEnd of player " + playername + " not found!"); + throw SerializationError( + "PlayerArgsEnd of player " + playername + " not found!"); } m_dirty = true; - //args.getS32("version"); // Version field value not used + // args.getS32("version"); // Version field value not used const std::string &name = args.get("name"); strlcpy(m_name, name.c_str(), PLAYERNAME_SIZE); if (sao) { try { sao->setHPRaw(args.getU16("hp")); - } catch(SettingNotFoundException &e) { + } catch (SettingNotFoundException &e) { sao->setHPRaw(PLAYER_MAX_HP_DEFAULT); } try { sao->setBasePosition(args.getV3F("position")); - } catch (SettingNotFoundException &e) {} + } catch (SettingNotFoundException &e) { + } try { sao->setLookPitch(args.getFloat("pitch")); - } catch (SettingNotFoundException &e) {} + } catch (SettingNotFoundException &e) { + } try { sao->setPlayerYaw(args.getFloat("yaw")); - } catch (SettingNotFoundException &e) {} + } catch (SettingNotFoundException &e) { + } try { sao->setBreath(args.getU16("breath"), false); - } catch (SettingNotFoundException &e) {} + } catch (SettingNotFoundException &e) { + } try { - const std::string &extended_attributes = args.get("extended_attributes"); + const std::string &extended_attributes = + args.get("extended_attributes"); std::istringstream iss(extended_attributes); Json::CharReaderBuilder builder; builder.settings_["collectComments"] = false; @@ -149,14 +157,15 @@ void RemotePlayer::deSerialize(std::istream &is, const std::string &playername, sao->getMeta().setString(it, attr_value.asString()); } sao->getMeta().setModified(false); - } catch (SettingNotFoundException &e) {} + } catch (SettingNotFoundException &e) { + } } try { inventory.deSerialize(is); } catch (SerializationError &e) { errorstream << "Failed to deserialize player inventory. player_name=" - << name << " " << e.what() << std::endl; + << name << " " << e.what() << std::endl; } if (!inventory.getList("craftpreview") && inventory.getList("craftresult")) { @@ -164,10 +173,9 @@ void RemotePlayer::deSerialize(std::istream &is, const std::string &playername, inventory.addList("craftpreview", 1); bool craftresult_is_preview = true; - if(args.exists("craftresult_is_preview")) + if (args.exists("craftresult_is_preview")) craftresult_is_preview = args.getBool("craftresult_is_preview"); - if(craftresult_is_preview) - { + if (craftresult_is_preview) { // Clear craftresult inventory.getList("craftresult")->changeItem(0, ItemStack()); } @@ -195,7 +203,7 @@ void RemotePlayer::serialize(std::ostream &os) args.writeLines(os); - os<<"PlayerArgsEnd\n"; + os << "PlayerArgsEnd\n"; inventory.serialize(os); } @@ -212,18 +220,21 @@ const RemotePlayerChatResult RemotePlayer::canSendChatMessage() return RPLAYER_CHATRESULT_OK; } - m_chat_message_allowance += time_passed * (m_setting_chat_message_limit_per_10sec / 8.0f); + m_chat_message_allowance += + time_passed * (m_setting_chat_message_limit_per_10sec / 8.0f); if (m_chat_message_allowance > m_setting_chat_message_limit_per_10sec) { m_chat_message_allowance = m_setting_chat_message_limit_per_10sec; } if (m_chat_message_allowance < 1.0f) { infostream << "Player " << m_name - << " chat limited due to excessive message amount." << std::endl; + << " chat limited due to excessive message amount." + << std::endl; // Kick player if flooding is too intensive m_message_rate_overhead++; - if (m_message_rate_overhead > RemotePlayer::m_setting_chat_message_limit_trigger_kick) { + if (m_message_rate_overhead > + RemotePlayer::m_setting_chat_message_limit_trigger_kick) { return RPLAYER_CHATRESULT_KICK; } diff --git a/src/rollback.cpp b/src/rollback.cpp index 3cd9c7ce7..487460dd0 100644 --- a/src/rollback.cpp +++ b/src/rollback.cpp @@ -34,26 +34,28 @@ with this program; if not, write to the Free Software Foundation, Inc., #define POINTS_PER_NODE (16.0) -#define SQLRES(f, good) \ - if ((f) != (good)) {\ - throw FileNotGoodException(std::string("RollbackManager: " \ - "SQLite3 error (" __FILE__ ":" TOSTRING(__LINE__) \ - "): ") + sqlite3_errmsg(db)); \ +#define SQLRES(f, good) \ + if ((f) != (good)) { \ + throw FileNotGoodException(std::string("RollbackManager: " \ + "SQLite3 error (" __FILE__ \ + ":" TOSTRING(__LINE__) "): ") + \ + sqlite3_errmsg(db)); \ } #define SQLOK(f) SQLRES(f, SQLITE_OK) -#define SQLOK_ERRSTREAM(s, m) \ - if ((s) != SQLITE_OK) { \ - errorstream << "RollbackManager: " << (m) << ": " \ - << sqlite3_errmsg(db) << std::endl; \ +#define SQLOK_ERRSTREAM(s, m) \ + if ((s) != SQLITE_OK) { \ + errorstream << "RollbackManager: " << (m) << ": " << sqlite3_errmsg(db) \ + << std::endl; \ } -#define FINALIZE_STATEMENT(statement) \ +#define FINALIZE_STATEMENT(statement) \ SQLOK_ERRSTREAM(sqlite3_finalize(statement), "Failed to finalize " #statement) -class ItemStackRow : public ItemStack { +class ItemStackRow : public ItemStack +{ public: - ItemStackRow & operator = (const ItemStack & other) + ItemStackRow &operator=(const ItemStack &other) { *static_cast(this) = other; return *this; @@ -62,39 +64,37 @@ public: int id; }; -struct ActionRow { - int id; - int actor; - time_t timestamp; - int type; - std::string location, list; - int index, add; +struct ActionRow +{ + int id; + int actor; + time_t timestamp; + int type; + std::string location, list; + int index, add; ItemStackRow stack; - int nodeMeta; - int x, y, z; - int oldNode; - int oldParam1, oldParam2; - std::string oldMeta; - int newNode; - int newParam1, newParam2; - std::string newMeta; - int guessed; + int nodeMeta; + int x, y, z; + int oldNode; + int oldParam1, oldParam2; + std::string oldMeta; + int newNode; + int newParam1, newParam2; + std::string newMeta; + int guessed; }; - -struct Entity { - int id; +struct Entity +{ + int id; std::string name; }; - - -RollbackManager::RollbackManager(const std::string & world_path, - IGameDef * gamedef_) : - gamedef(gamedef_) +RollbackManager::RollbackManager(const std::string &world_path, IGameDef *gamedef_) : + gamedef(gamedef_) { - verbosestream << "RollbackManager::RollbackManager(" << world_path - << ")" << std::endl; + verbosestream << "RollbackManager::RollbackManager(" << world_path << ")" + << std::endl; std::string txt_filename = world_path + DIR_DELIM "rollback.txt"; std::string migrating_flag = txt_filename + ".migrating"; @@ -102,8 +102,7 @@ RollbackManager::RollbackManager(const std::string & world_path, bool created = initDatabase(); - if (fs::PathExists(txt_filename) && (created || - fs::PathExists(migrating_flag))) { + if (fs::PathExists(txt_filename) && (created || fs::PathExists(migrating_flag))) { std::ofstream of(migrating_flag.c_str()); of.close(); migrate(txt_filename); @@ -111,7 +110,6 @@ RollbackManager::RollbackManager(const std::string & world_path, } } - RollbackManager::~RollbackManager() { flush(); @@ -129,21 +127,18 @@ RollbackManager::~RollbackManager() SQLOK_ERRSTREAM(sqlite3_close(db), "Could not close db"); } - void RollbackManager::registerNewActor(const int id, const std::string &name) { Entity actor = {id, name}; knownActors.push_back(actor); } - void RollbackManager::registerNewNode(const int id, const std::string &name) { Entity node = {id, name}; knownNodes.push_back(node); } - int RollbackManager::getActorId(const std::string &name) { for (std::vector::const_iterator iter = knownActors.begin(); @@ -153,7 +148,8 @@ int RollbackManager::getActorId(const std::string &name) } } - SQLOK(sqlite3_bind_text(stmt_knownActor_insert, 1, name.c_str(), name.size(), NULL)); + SQLOK(sqlite3_bind_text( + stmt_knownActor_insert, 1, name.c_str(), name.size(), NULL)); SQLRES(sqlite3_step(stmt_knownActor_insert), SQLITE_DONE); SQLOK(sqlite3_reset(stmt_knownActor_insert)); @@ -163,7 +159,6 @@ int RollbackManager::getActorId(const std::string &name) return id; } - int RollbackManager::getNodeId(const std::string &name) { for (std::vector::const_iterator iter = knownNodes.begin(); @@ -173,7 +168,8 @@ int RollbackManager::getNodeId(const std::string &name) } } - SQLOK(sqlite3_bind_text(stmt_knownNode_insert, 1, name.c_str(), name.size(), NULL)); + SQLOK(sqlite3_bind_text( + stmt_knownNode_insert, 1, name.c_str(), name.size(), NULL)); SQLRES(sqlite3_step(stmt_knownNode_insert), SQLITE_DONE); SQLOK(sqlite3_reset(stmt_knownNode_insert)); @@ -183,8 +179,7 @@ int RollbackManager::getNodeId(const std::string &name) return id; } - -const char * RollbackManager::getActorName(const int id) +const char *RollbackManager::getActorName(const int id) { for (std::vector::const_iterator iter = knownActors.begin(); iter != knownActors.end(); ++iter) { @@ -196,8 +191,7 @@ const char * RollbackManager::getActorName(const int id) return ""; } - -const char * RollbackManager::getNodeName(const int id) +const char *RollbackManager::getNodeName(const int id) { for (std::vector::const_iterator iter = knownNodes.begin(); iter != knownNodes.end(); ++iter) { @@ -209,54 +203,54 @@ const char * RollbackManager::getNodeName(const int id) return ""; } - bool RollbackManager::createTables() { SQLOK(sqlite3_exec(db, - "CREATE TABLE IF NOT EXISTS `actor` (\n" - " `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,\n" - " `name` TEXT NOT NULL\n" - ");\n" - "CREATE TABLE IF NOT EXISTS `node` (\n" - " `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,\n" - " `name` TEXT NOT NULL\n" - ");\n" - "CREATE TABLE IF NOT EXISTS `action` (\n" - " `id` INTEGER PRIMARY KEY AUTOINCREMENT,\n" - " `actor` INTEGER NOT NULL,\n" - " `timestamp` TIMESTAMP NOT NULL,\n" - " `type` INTEGER NOT NULL,\n" - " `list` TEXT,\n" - " `index` INTEGER,\n" - " `add` INTEGER,\n" - " `stackNode` INTEGER,\n" - " `stackQuantity` INTEGER,\n" - " `nodeMeta` INTEGER,\n" - " `x` INT,\n" - " `y` INT,\n" - " `z` INT,\n" - " `oldNode` INTEGER,\n" - " `oldParam1` INTEGER,\n" - " `oldParam2` INTEGER,\n" - " `oldMeta` TEXT,\n" - " `newNode` INTEGER,\n" - " `newParam1` INTEGER,\n" - " `newParam2` INTEGER,\n" - " `newMeta` TEXT,\n" - " `guessedActor` INTEGER,\n" - " FOREIGN KEY (`actor`) REFERENCES `actor`(`id`),\n" - " FOREIGN KEY (`stackNode`) REFERENCES `node`(`id`),\n" - " FOREIGN KEY (`oldNode`) REFERENCES `node`(`id`),\n" - " FOREIGN KEY (`newNode`) REFERENCES `node`(`id`)\n" - ");\n" - "CREATE INDEX IF NOT EXISTS `actionIndex` ON `action`(`x`,`y`,`z`,`timestamp`,`actor`);\n", - NULL, NULL, NULL)); - verbosestream << "SQL Rollback: SQLite3 database structure was created" << std::endl; + "CREATE TABLE IF NOT EXISTS `actor` (\n" + " `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,\n" + " `name` TEXT NOT NULL\n" + ");\n" + "CREATE TABLE IF NOT EXISTS `node` (\n" + " `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,\n" + " `name` TEXT NOT NULL\n" + ");\n" + "CREATE TABLE IF NOT EXISTS `action` (\n" + " `id` INTEGER PRIMARY KEY AUTOINCREMENT,\n" + " `actor` INTEGER NOT NULL,\n" + " `timestamp` TIMESTAMP NOT NULL,\n" + " `type` INTEGER NOT NULL,\n" + " `list` TEXT,\n" + " `index` INTEGER,\n" + " `add` INTEGER,\n" + " `stackNode` INTEGER,\n" + " `stackQuantity` INTEGER,\n" + " `nodeMeta` INTEGER,\n" + " `x` INT,\n" + " `y` INT,\n" + " `z` INT,\n" + " `oldNode` INTEGER,\n" + " `oldParam1` INTEGER,\n" + " `oldParam2` INTEGER,\n" + " `oldMeta` TEXT,\n" + " `newNode` INTEGER,\n" + " `newParam1` INTEGER,\n" + " `newParam2` INTEGER,\n" + " `newMeta` TEXT,\n" + " `guessedActor` INTEGER,\n" + " FOREIGN KEY (`actor`) REFERENCES `actor`(`id`),\n" + " FOREIGN KEY (`stackNode`) REFERENCES `node`(`id`),\n" + " FOREIGN KEY (`oldNode`) REFERENCES `node`(`id`),\n" + " FOREIGN KEY (`newNode`) REFERENCES `node`(`id`)\n" + ");\n" + "CREATE INDEX IF NOT EXISTS `actionIndex` ON " + "`action`(`x`,`y`,`z`,`timestamp`,`actor`);\n", + NULL, NULL, NULL)); + verbosestream << "SQL Rollback: SQLite3 database structure was created" + << std::endl; return true; } - bool RollbackManager::initDatabase() { verbosestream << "RollbackManager: Database connection setup" << std::endl; @@ -270,142 +264,145 @@ bool RollbackManager::initDatabase() } SQLOK(sqlite3_prepare_v2(db, - "INSERT INTO `action` (\n" - " `actor`, `timestamp`, `type`,\n" - " `list`, `index`, `add`, `stackNode`, `stackQuantity`, `nodeMeta`,\n" - " `x`, `y`, `z`,\n" - " `oldNode`, `oldParam1`, `oldParam2`, `oldMeta`,\n" - " `newNode`, `newParam1`, `newParam2`, `newMeta`,\n" - " `guessedActor`\n" - ") VALUES (\n" - " ?, ?, ?,\n" - " ?, ?, ?, ?, ?, ?,\n" - " ?, ?, ?,\n" - " ?, ?, ?, ?,\n" - " ?, ?, ?, ?,\n" - " ?" - ");", - -1, &stmt_insert, NULL)); + "INSERT INTO `action` (\n" + " `actor`, `timestamp`, `type`,\n" + " `list`, `index`, `add`, `stackNode`, `stackQuantity`, " + "`nodeMeta`,\n" + " `x`, `y`, `z`,\n" + " `oldNode`, `oldParam1`, `oldParam2`, `oldMeta`,\n" + " `newNode`, `newParam1`, `newParam2`, `newMeta`,\n" + " `guessedActor`\n" + ") VALUES (\n" + " ?, ?, ?,\n" + " ?, ?, ?, ?, ?, ?,\n" + " ?, ?, ?,\n" + " ?, ?, ?, ?,\n" + " ?, ?, ?, ?,\n" + " ?" + ");", + -1, &stmt_insert, NULL)); SQLOK(sqlite3_prepare_v2(db, - "REPLACE INTO `action` (\n" - " `actor`, `timestamp`, `type`,\n" - " `list`, `index`, `add`, `stackNode`, `stackQuantity`, `nodeMeta`,\n" - " `x`, `y`, `z`,\n" - " `oldNode`, `oldParam1`, `oldParam2`, `oldMeta`,\n" - " `newNode`, `newParam1`, `newParam2`, `newMeta`,\n" - " `guessedActor`, `id`\n" - ") VALUES (\n" - " ?, ?, ?,\n" - " ?, ?, ?, ?, ?, ?,\n" - " ?, ?, ?,\n" - " ?, ?, ?, ?,\n" - " ?, ?, ?, ?,\n" - " ?, ?\n" - ");", - -1, &stmt_replace, NULL)); + "REPLACE INTO `action` (\n" + " `actor`, `timestamp`, `type`,\n" + " `list`, `index`, `add`, `stackNode`, `stackQuantity`, " + "`nodeMeta`,\n" + " `x`, `y`, `z`,\n" + " `oldNode`, `oldParam1`, `oldParam2`, `oldMeta`,\n" + " `newNode`, `newParam1`, `newParam2`, `newMeta`,\n" + " `guessedActor`, `id`\n" + ") VALUES (\n" + " ?, ?, ?,\n" + " ?, ?, ?, ?, ?, ?,\n" + " ?, ?, ?,\n" + " ?, ?, ?, ?,\n" + " ?, ?, ?, ?,\n" + " ?, ?\n" + ");", + -1, &stmt_replace, NULL)); SQLOK(sqlite3_prepare_v2(db, - "SELECT\n" - " `actor`, `timestamp`, `type`,\n" - " `list`, `index`, `add`, `stackNode`, `stackQuantity`, `nodemeta`,\n" - " `x`, `y`, `z`,\n" - " `oldNode`, `oldParam1`, `oldParam2`, `oldMeta`,\n" - " `newNode`, `newParam1`, `newParam2`, `newMeta`,\n" - " `guessedActor`\n" - " FROM `action`\n" - " WHERE `timestamp` >= ?\n" - " ORDER BY `timestamp` DESC, `id` DESC", - -1, &stmt_select, NULL)); + "SELECT\n" + " `actor`, `timestamp`, `type`,\n" + " `list`, `index`, `add`, `stackNode`, `stackQuantity`, " + "`nodemeta`,\n" + " `x`, `y`, `z`,\n" + " `oldNode`, `oldParam1`, `oldParam2`, `oldMeta`,\n" + " `newNode`, `newParam1`, `newParam2`, `newMeta`,\n" + " `guessedActor`\n" + " FROM `action`\n" + " WHERE `timestamp` >= ?\n" + " ORDER BY `timestamp` DESC, `id` DESC", + -1, &stmt_select, NULL)); SQLOK(sqlite3_prepare_v2(db, - "SELECT\n" - " `actor`, `timestamp`, `type`,\n" - " `list`, `index`, `add`, `stackNode`, `stackQuantity`, `nodemeta`,\n" - " `x`, `y`, `z`,\n" - " `oldNode`, `oldParam1`, `oldParam2`, `oldMeta`,\n" - " `newNode`, `newParam1`, `newParam2`, `newMeta`,\n" - " `guessedActor`\n" - "FROM `action`\n" - "WHERE `timestamp` >= ?\n" - " AND `x` IS NOT NULL\n" - " AND `y` IS NOT NULL\n" - " AND `z` IS NOT NULL\n" - " AND `x` BETWEEN ? AND ?\n" - " AND `y` BETWEEN ? AND ?\n" - " AND `z` BETWEEN ? AND ?\n" - "ORDER BY `timestamp` DESC, `id` DESC\n" - "LIMIT 0,?", - -1, &stmt_select_range, NULL)); + "SELECT\n" + " `actor`, `timestamp`, `type`,\n" + " `list`, `index`, `add`, `stackNode`, `stackQuantity`, " + "`nodemeta`,\n" + " `x`, `y`, `z`,\n" + " `oldNode`, `oldParam1`, `oldParam2`, `oldMeta`,\n" + " `newNode`, `newParam1`, `newParam2`, `newMeta`,\n" + " `guessedActor`\n" + "FROM `action`\n" + "WHERE `timestamp` >= ?\n" + " AND `x` IS NOT NULL\n" + " AND `y` IS NOT NULL\n" + " AND `z` IS NOT NULL\n" + " AND `x` BETWEEN ? AND ?\n" + " AND `y` BETWEEN ? AND ?\n" + " AND `z` BETWEEN ? AND ?\n" + "ORDER BY `timestamp` DESC, `id` DESC\n" + "LIMIT 0,?", + -1, &stmt_select_range, NULL)); SQLOK(sqlite3_prepare_v2(db, - "SELECT\n" - " `actor`, `timestamp`, `type`,\n" - " `list`, `index`, `add`, `stackNode`, `stackQuantity`, `nodemeta`,\n" - " `x`, `y`, `z`,\n" - " `oldNode`, `oldParam1`, `oldParam2`, `oldMeta`,\n" - " `newNode`, `newParam1`, `newParam2`, `newMeta`,\n" - " `guessedActor`\n" - "FROM `action`\n" - "WHERE `timestamp` >= ?\n" - " AND `actor` = ?\n" - "ORDER BY `timestamp` DESC, `id` DESC\n", - -1, &stmt_select_withActor, NULL)); + "SELECT\n" + " `actor`, `timestamp`, `type`,\n" + " `list`, `index`, `add`, `stackNode`, `stackQuantity`, " + "`nodemeta`,\n" + " `x`, `y`, `z`,\n" + " `oldNode`, `oldParam1`, `oldParam2`, `oldMeta`,\n" + " `newNode`, `newParam1`, `newParam2`, `newMeta`,\n" + " `guessedActor`\n" + "FROM `action`\n" + "WHERE `timestamp` >= ?\n" + " AND `actor` = ?\n" + "ORDER BY `timestamp` DESC, `id` DESC\n", + -1, &stmt_select_withActor, NULL)); - SQLOK(sqlite3_prepare_v2(db, "SELECT `id`, `name` FROM `actor`", - -1, &stmt_knownActor_select, NULL)); + SQLOK(sqlite3_prepare_v2(db, "SELECT `id`, `name` FROM `actor`", -1, + &stmt_knownActor_select, NULL)); - SQLOK(sqlite3_prepare_v2(db, "INSERT INTO `actor` (`name`) VALUES (?)", - -1, &stmt_knownActor_insert, NULL)); + SQLOK(sqlite3_prepare_v2(db, "INSERT INTO `actor` (`name`) VALUES (?)", -1, + &stmt_knownActor_insert, NULL)); - SQLOK(sqlite3_prepare_v2(db, "SELECT `id`, `name` FROM `node`", - -1, &stmt_knownNode_select, NULL)); + SQLOK(sqlite3_prepare_v2(db, "SELECT `id`, `name` FROM `node`", -1, + &stmt_knownNode_select, NULL)); - SQLOK(sqlite3_prepare_v2(db, "INSERT INTO `node` (`name`) VALUES (?)", - -1, &stmt_knownNode_insert, NULL)); + SQLOK(sqlite3_prepare_v2(db, "INSERT INTO `node` (`name`) VALUES (?)", -1, + &stmt_knownNode_insert, NULL)); verbosestream << "SQL prepared statements setup correctly" << std::endl; while (sqlite3_step(stmt_knownActor_select) == SQLITE_ROW) { - registerNewActor( - sqlite3_column_int(stmt_knownActor_select, 0), - reinterpret_cast(sqlite3_column_text(stmt_knownActor_select, 1)) - ); + registerNewActor(sqlite3_column_int(stmt_knownActor_select, 0), + reinterpret_cast(sqlite3_column_text( + stmt_knownActor_select, 1))); } SQLOK(sqlite3_reset(stmt_knownActor_select)); while (sqlite3_step(stmt_knownNode_select) == SQLITE_ROW) { - registerNewNode( - sqlite3_column_int(stmt_knownNode_select, 0), - reinterpret_cast(sqlite3_column_text(stmt_knownNode_select, 1)) - ); + registerNewNode(sqlite3_column_int(stmt_knownNode_select, 0), + reinterpret_cast(sqlite3_column_text( + stmt_knownNode_select, 1))); } SQLOK(sqlite3_reset(stmt_knownNode_select)); return needs_create; } - -bool RollbackManager::registerRow(const ActionRow & row) +bool RollbackManager::registerRow(const ActionRow &row) { - sqlite3_stmt * stmt_do = (row.id) ? stmt_replace : stmt_insert; + sqlite3_stmt *stmt_do = (row.id) ? stmt_replace : stmt_insert; bool nodeMeta = false; - SQLOK(sqlite3_bind_int (stmt_do, 1, row.actor)); + SQLOK(sqlite3_bind_int(stmt_do, 1, row.actor)); SQLOK(sqlite3_bind_int64(stmt_do, 2, row.timestamp)); - SQLOK(sqlite3_bind_int (stmt_do, 3, row.type)); + SQLOK(sqlite3_bind_int(stmt_do, 3, row.type)); if (row.type == RollbackAction::TYPE_MODIFY_INVENTORY_STACK) { - const std::string & loc = row.location; + const std::string &loc = row.location; nodeMeta = (loc.substr(0, 9) == "nodemeta:"); - SQLOK(sqlite3_bind_text(stmt_do, 4, row.list.c_str(), row.list.size(), NULL)); - SQLOK(sqlite3_bind_int (stmt_do, 5, row.index)); - SQLOK(sqlite3_bind_int (stmt_do, 6, row.add)); - SQLOK(sqlite3_bind_int (stmt_do, 7, row.stack.id)); - SQLOK(sqlite3_bind_int (stmt_do, 8, row.stack.count)); - SQLOK(sqlite3_bind_int (stmt_do, 9, (int) nodeMeta)); + SQLOK(sqlite3_bind_text( + stmt_do, 4, row.list.c_str(), row.list.size(), NULL)); + SQLOK(sqlite3_bind_int(stmt_do, 5, row.index)); + SQLOK(sqlite3_bind_int(stmt_do, 6, row.add)); + SQLOK(sqlite3_bind_int(stmt_do, 7, row.stack.id)); + SQLOK(sqlite3_bind_int(stmt_do, 8, row.stack.count)); + SQLOK(sqlite3_bind_int(stmt_do, 9, (int)nodeMeta)); if (nodeMeta) { std::string::size_type p1, p2; @@ -430,18 +427,20 @@ bool RollbackManager::registerRow(const ActionRow & row) } if (row.type == RollbackAction::TYPE_SET_NODE) { - SQLOK(sqlite3_bind_int (stmt_do, 10, row.x)); - SQLOK(sqlite3_bind_int (stmt_do, 11, row.y)); - SQLOK(sqlite3_bind_int (stmt_do, 12, row.z)); - SQLOK(sqlite3_bind_int (stmt_do, 13, row.oldNode)); - SQLOK(sqlite3_bind_int (stmt_do, 14, row.oldParam1)); - SQLOK(sqlite3_bind_int (stmt_do, 15, row.oldParam2)); - SQLOK(sqlite3_bind_text(stmt_do, 16, row.oldMeta.c_str(), row.oldMeta.size(), NULL)); - SQLOK(sqlite3_bind_int (stmt_do, 17, row.newNode)); - SQLOK(sqlite3_bind_int (stmt_do, 18, row.newParam1)); - SQLOK(sqlite3_bind_int (stmt_do, 19, row.newParam2)); - SQLOK(sqlite3_bind_text(stmt_do, 20, row.newMeta.c_str(), row.newMeta.size(), NULL)); - SQLOK(sqlite3_bind_int (stmt_do, 21, row.guessed ? 1 : 0)); + SQLOK(sqlite3_bind_int(stmt_do, 10, row.x)); + SQLOK(sqlite3_bind_int(stmt_do, 11, row.y)); + SQLOK(sqlite3_bind_int(stmt_do, 12, row.z)); + SQLOK(sqlite3_bind_int(stmt_do, 13, row.oldNode)); + SQLOK(sqlite3_bind_int(stmt_do, 14, row.oldParam1)); + SQLOK(sqlite3_bind_int(stmt_do, 15, row.oldParam2)); + SQLOK(sqlite3_bind_text(stmt_do, 16, row.oldMeta.c_str(), + row.oldMeta.size(), NULL)); + SQLOK(sqlite3_bind_int(stmt_do, 17, row.newNode)); + SQLOK(sqlite3_bind_int(stmt_do, 18, row.newParam1)); + SQLOK(sqlite3_bind_int(stmt_do, 19, row.newParam2)); + SQLOK(sqlite3_bind_text(stmt_do, 20, row.newMeta.c_str(), + row.newMeta.size(), NULL)); + SQLOK(sqlite3_bind_int(stmt_do, 21, row.guessed ? 1 : 0)); } else { if (!nodeMeta) { SQLOK(sqlite3_bind_null(stmt_do, 10)); @@ -470,51 +469,53 @@ bool RollbackManager::registerRow(const ActionRow & row) return written == SQLITE_DONE; } - -const std::list RollbackManager::actionRowsFromSelect(sqlite3_stmt* stmt) +const std::list RollbackManager::actionRowsFromSelect(sqlite3_stmt *stmt) { std::list rows; - const unsigned char * text; + const unsigned char *text; size_t size; while (sqlite3_step(stmt) == SQLITE_ROW) { ActionRow row; - row.actor = sqlite3_column_int (stmt, 0); + row.actor = sqlite3_column_int(stmt, 0); row.timestamp = sqlite3_column_int64(stmt, 1); - row.type = sqlite3_column_int (stmt, 2); + row.type = sqlite3_column_int(stmt, 2); if (row.type == RollbackAction::TYPE_MODIFY_INVENTORY_STACK) { - text = sqlite3_column_text (stmt, 3); + text = sqlite3_column_text(stmt, 3); size = sqlite3_column_bytes(stmt, 3); - row.list = std::string(reinterpret_cast(text), size); - row.index = sqlite3_column_int(stmt, 4); - row.add = sqlite3_column_int(stmt, 5); - row.stack.id = sqlite3_column_int(stmt, 6); + row.list = std::string( + reinterpret_cast(text), size); + row.index = sqlite3_column_int(stmt, 4); + row.add = sqlite3_column_int(stmt, 5); + row.stack.id = sqlite3_column_int(stmt, 6); row.stack.count = sqlite3_column_int(stmt, 7); - row.nodeMeta = sqlite3_column_int(stmt, 8); + row.nodeMeta = sqlite3_column_int(stmt, 8); } if (row.type == RollbackAction::TYPE_SET_NODE || row.nodeMeta) { - row.x = sqlite3_column_int(stmt, 9); + row.x = sqlite3_column_int(stmt, 9); row.y = sqlite3_column_int(stmt, 10); row.z = sqlite3_column_int(stmt, 11); } if (row.type == RollbackAction::TYPE_SET_NODE) { - row.oldNode = sqlite3_column_int(stmt, 12); + row.oldNode = sqlite3_column_int(stmt, 12); row.oldParam1 = sqlite3_column_int(stmt, 13); row.oldParam2 = sqlite3_column_int(stmt, 14); - text = sqlite3_column_text (stmt, 15); + text = sqlite3_column_text(stmt, 15); size = sqlite3_column_bytes(stmt, 15); - row.oldMeta = std::string(reinterpret_cast(text), size); - row.newNode = sqlite3_column_int(stmt, 16); + row.oldMeta = std::string( + reinterpret_cast(text), size); + row.newNode = sqlite3_column_int(stmt, 16); row.newParam1 = sqlite3_column_int(stmt, 17); row.newParam2 = sqlite3_column_int(stmt, 18); text = sqlite3_column_text(stmt, 19); size = sqlite3_column_bytes(stmt, 19); - row.newMeta = std::string(reinterpret_cast(text), size); - row.guessed = sqlite3_column_int(stmt, 20); + row.newMeta = std::string( + reinterpret_cast(text), size); + row.guessed = sqlite3_column_int(stmt, 20); } if (row.nodeMeta) { @@ -536,79 +537,77 @@ const std::list RollbackManager::actionRowsFromSelect(sqlite3_stmt* s return rows; } - -ActionRow RollbackManager::actionRowFromRollbackAction(const RollbackAction & action) +ActionRow RollbackManager::actionRowFromRollbackAction(const RollbackAction &action) { ActionRow row; - row.id = 0; - row.actor = getActorId(action.actor); + row.id = 0; + row.actor = getActorId(action.actor); row.timestamp = action.unix_time; - row.type = action.type; + row.type = action.type; if (row.type == RollbackAction::TYPE_MODIFY_INVENTORY_STACK) { row.location = action.inventory_location; - row.list = action.inventory_list; - row.index = action.inventory_index; - row.add = action.inventory_add; - row.stack = action.inventory_stack; + row.list = action.inventory_list; + row.index = action.inventory_index; + row.add = action.inventory_add; + row.stack = action.inventory_stack; row.stack.id = getNodeId(row.stack.name); } else { - row.x = action.p.X; - row.y = action.p.Y; - row.z = action.p.Z; - row.oldNode = getNodeId(action.n_old.name); + row.x = action.p.X; + row.y = action.p.Y; + row.z = action.p.Z; + row.oldNode = getNodeId(action.n_old.name); row.oldParam1 = action.n_old.param1; row.oldParam2 = action.n_old.param2; - row.oldMeta = action.n_old.meta; - row.newNode = getNodeId(action.n_new.name); + row.oldMeta = action.n_old.meta; + row.newNode = getNodeId(action.n_new.name); row.newParam1 = action.n_new.param1; row.newParam2 = action.n_new.param2; - row.newMeta = action.n_new.meta; - row.guessed = action.actor_is_guess; + row.newMeta = action.n_new.meta; + row.guessed = action.actor_is_guess; } return row; } - const std::list RollbackManager::rollbackActionsFromActionRows( - const std::list & rows) + const std::list &rows) { std::list actions; for (const ActionRow &row : rows) { RollbackAction action; - action.actor = (row.actor) ? getActorName(row.actor) : ""; + action.actor = (row.actor) ? getActorName(row.actor) : ""; action.unix_time = row.timestamp; - action.type = static_cast(row.type); + action.type = static_cast(row.type); switch (action.type) { case RollbackAction::TYPE_MODIFY_INVENTORY_STACK: action.inventory_location = row.location; - action.inventory_list = row.list; - action.inventory_index = row.index; - action.inventory_add = row.add; - action.inventory_stack = row.stack; + action.inventory_list = row.list; + action.inventory_index = row.index; + action.inventory_add = row.add; + action.inventory_stack = row.stack; if (action.inventory_stack.name.empty()) { action.inventory_stack.name = getNodeName(row.stack.id); } break; case RollbackAction::TYPE_SET_NODE: - action.p = v3s16(row.x, row.y, row.z); - action.n_old.name = getNodeName(row.oldNode); + action.p = v3s16(row.x, row.y, row.z); + action.n_old.name = getNodeName(row.oldNode); action.n_old.param1 = row.oldParam1; action.n_old.param2 = row.oldParam2; - action.n_old.meta = row.oldMeta; - action.n_new.name = getNodeName(row.newNode); + action.n_old.meta = row.oldMeta; + action.n_new.name = getNodeName(row.newNode); action.n_new.param1 = row.newParam1; action.n_new.param2 = row.newParam2; - action.n_new.meta = row.newMeta; + action.n_new.meta = row.newMeta; break; default: - throw ("W.T.F."); + throw("W.T.F."); break; } @@ -618,8 +617,8 @@ const std::list RollbackManager::rollbackActionsFromActionRows( return actions; } - -const std::list RollbackManager::getRowsSince(time_t firstTime, const std::string & actor) +const std::list RollbackManager::getRowsSince( + time_t firstTime, const std::string &actor) { sqlite3_stmt *stmt_stmt = actor.empty() ? stmt_select : stmt_select_withActor; sqlite3_bind_int64(stmt_stmt, 1, firstTime); @@ -628,48 +627,45 @@ const std::list RollbackManager::getRowsSince(time_t firstTime, const sqlite3_bind_int(stmt_stmt, 2, getActorId(actor)); } - const std::list & rows = actionRowsFromSelect(stmt_stmt); + const std::list &rows = actionRowsFromSelect(stmt_stmt); sqlite3_reset(stmt_stmt); return rows; } - const std::list RollbackManager::getRowsSince_range( time_t start_time, v3s16 p, int range, int limit) { sqlite3_bind_int64(stmt_select_range, 1, start_time); - sqlite3_bind_int (stmt_select_range, 2, static_cast(p.X - range)); - sqlite3_bind_int (stmt_select_range, 3, static_cast(p.X + range)); - sqlite3_bind_int (stmt_select_range, 4, static_cast(p.Y - range)); - sqlite3_bind_int (stmt_select_range, 5, static_cast(p.Y + range)); - sqlite3_bind_int (stmt_select_range, 6, static_cast(p.Z - range)); - sqlite3_bind_int (stmt_select_range, 7, static_cast(p.Z + range)); - sqlite3_bind_int (stmt_select_range, 8, limit); + sqlite3_bind_int(stmt_select_range, 2, static_cast(p.X - range)); + sqlite3_bind_int(stmt_select_range, 3, static_cast(p.X + range)); + sqlite3_bind_int(stmt_select_range, 4, static_cast(p.Y - range)); + sqlite3_bind_int(stmt_select_range, 5, static_cast(p.Y + range)); + sqlite3_bind_int(stmt_select_range, 6, static_cast(p.Z - range)); + sqlite3_bind_int(stmt_select_range, 7, static_cast(p.Z + range)); + sqlite3_bind_int(stmt_select_range, 8, limit); - const std::list & rows = actionRowsFromSelect(stmt_select_range); + const std::list &rows = actionRowsFromSelect(stmt_select_range); sqlite3_reset(stmt_select_range); return rows; } - const std::list RollbackManager::getActionsSince_range( time_t start_time, v3s16 p, int range, int limit) { - return rollbackActionsFromActionRows(getRowsSince_range(start_time, p, range, limit)); + return rollbackActionsFromActionRows( + getRowsSince_range(start_time, p, range, limit)); } - const std::list RollbackManager::getActionsSince( - time_t start_time, const std::string & actor) + time_t start_time, const std::string &actor) { return rollbackActionsFromActionRows(getRowsSince(start_time, actor)); } - -void RollbackManager::migrate(const std::string & file_path) +void RollbackManager::migrate(const std::string &file_path) { std::cout << "Migrating from rollback.txt to rollback.sqlite." << std::endl; @@ -722,12 +718,12 @@ void RollbackManager::migrate(const std::string & file_path) row.type = RollbackAction::TYPE_MODIFY_INVENTORY_STACK; row.location = trim(deSerializeJsonString(fh)); std::getline(fh, bit, ' '); - row.list = trim(deSerializeJsonString(fh)); + row.list = trim(deSerializeJsonString(fh)); std::getline(fh, bit, ' '); std::getline(fh, bit, ' '); - row.index = atoi(trim(bit).c_str()); + row.index = atoi(trim(bit).c_str()); std::getline(fh, bit, ' '); - row.add = (int)(trim(bit) == "add"); + row.add = (int)(trim(bit) == "add"); row.stack.deSerialize(deSerializeJsonString(fh)); row.stack.id = getNodeId(row.stack.name); std::getline(fh, bit); @@ -735,11 +731,11 @@ void RollbackManager::migrate(const std::string & file_path) row.type = RollbackAction::TYPE_SET_NODE; std::getline(fh, bit, '('); std::getline(fh, bit, ','); - row.x = atoi(trim(bit).c_str()); + row.x = atoi(trim(bit).c_str()); std::getline(fh, bit, ','); - row.y = atoi(trim(bit).c_str()); + row.y = atoi(trim(bit).c_str()); std::getline(fh, bit, ')'); - row.z = atoi(trim(bit).c_str()); + row.z = atoi(trim(bit).c_str()); std::getline(fh, bit, ' '); row.oldNode = getNodeId(trim(deSerializeJsonString(fh))); std::getline(fh, bit, ' '); @@ -747,22 +743,22 @@ void RollbackManager::migrate(const std::string & file_path) row.oldParam1 = atoi(trim(bit).c_str()); std::getline(fh, bit, ' '); row.oldParam2 = atoi(trim(bit).c_str()); - row.oldMeta = trim(deSerializeJsonString(fh)); + row.oldMeta = trim(deSerializeJsonString(fh)); std::getline(fh, bit, ' '); - row.newNode = getNodeId(trim(deSerializeJsonString(fh))); + row.newNode = getNodeId(trim(deSerializeJsonString(fh))); std::getline(fh, bit, ' '); std::getline(fh, bit, ' '); row.newParam1 = atoi(trim(bit).c_str()); std::getline(fh, bit, ' '); row.newParam2 = atoi(trim(bit).c_str()); - row.newMeta = trim(deSerializeJsonString(fh)); + row.newMeta = trim(deSerializeJsonString(fh)); std::getline(fh, bit, ' '); std::getline(fh, bit, ' '); std::getline(fh, bit); row.guessed = (int)(trim(bit) == "actor_is_guess"); } else { - errorstream << "Unrecognized rollback action type \"" - << bit << "\"!" << std::endl; + errorstream << "Unrecognized rollback action type \"" << bit + << "\"!" << std::endl; continue; } @@ -773,9 +769,15 @@ void RollbackManager::migrate(const std::string & file_path) SQLRES(sqlite3_step(stmt_commit), SQLITE_DONE); sqlite3_reset(stmt_commit); t = time(0); - std::cout - << " Done: " << static_cast((static_cast(fh.tellg()) / static_cast(file_size)) * 100) << "%" - << " Speed: " << i / (t - start) << "/second \r" << std::flush; + std::cout << " Done: " + << static_cast( + (static_cast(fh.tellg()) / + static_cast( + file_size)) * + 100) + << "%" + << " Speed: " << i / (t - start) << "/second \r" + << std::flush; SQLRES(sqlite3_step(stmt_begin), SQLITE_DONE); sqlite3_reset(stmt_begin); } @@ -786,12 +788,10 @@ void RollbackManager::migrate(const std::string & file_path) SQLOK(sqlite3_finalize(stmt_begin)); SQLOK(sqlite3_finalize(stmt_commit)); - std::cout - << " Done: 100% " << std::endl - << "Now you can delete the old rollback.txt file." << std::endl; + std::cout << " Done: 100% " << std::endl + << "Now you can delete the old rollback.txt file." << std::endl; } - // Get nearness factor for subject's action for this action // Return value: 0 = impossible, >0 = factor float RollbackManager::getSuspectNearness(bool is_guess, v3s16 suspect_p, @@ -799,12 +799,13 @@ float RollbackManager::getSuspectNearness(bool is_guess, v3s16 suspect_p, { // Suspect cannot cause things in the past if (action_t < suspect_t) { - return 0; // 0 = cannot be + return 0; // 0 = cannot be } // Start from 100 int f = 100; // Distance (1 node = -x points) - f -= POINTS_PER_NODE * intToFloat(suspect_p, 1).getDistanceFrom(intToFloat(action_p, 1)); + f -= POINTS_PER_NODE * + intToFloat(suspect_p, 1).getDistanceFrom(intToFloat(action_p, 1)); // Time (1 second = -x points) f -= 1 * (action_t - suspect_t); // If is a guess, halve the points @@ -818,7 +819,6 @@ float RollbackManager::getSuspectNearness(bool is_guess, v3s16 suspect_p, return f; } - void RollbackManager::reportAction(const RollbackAction &action_) { // Ignore if not important @@ -860,14 +860,14 @@ bool RollbackManager::isActorGuess() return current_actor_is_guess; } -void RollbackManager::setActor(const std::string & actor, bool is_guess) +void RollbackManager::setActor(const std::string &actor, bool is_guess) { current_actor = actor; current_actor_is_guess = is_guess; } -std::string RollbackManager::getSuspect(v3s16 p, float nearness_shortcut, - float min_nearness) +std::string RollbackManager::getSuspect( + v3s16 p, float nearness_shortcut, float min_nearness) { if (!current_actor.empty()) { return current_actor; @@ -876,9 +876,9 @@ std::string RollbackManager::getSuspect(v3s16 p, float nearness_shortcut, time_t first_time = cur_time - (100 - min_nearness); RollbackAction likely_suspect; float likely_suspect_nearness = 0; - for (std::list::const_reverse_iterator - i = action_latest_buffer.rbegin(); - i != action_latest_buffer.rend(); ++i) { + for (std::list::const_reverse_iterator i = + action_latest_buffer.rbegin(); + i != action_latest_buffer.rend(); ++i) { if (i->unix_time < first_time) { break; } @@ -890,8 +890,8 @@ std::string RollbackManager::getSuspect(v3s16 p, float nearness_shortcut, if (!i->getPosition(&suspect_p)) { continue; } - float f = getSuspectNearness(i->actor_is_guess, suspect_p, - i->unix_time, p, cur_time); + float f = getSuspectNearness( + i->actor_is_guess, suspect_p, i->unix_time, p, cur_time); if (f >= min_nearness && f > likely_suspect_nearness) { likely_suspect_nearness = f; likely_suspect = *i; @@ -908,15 +908,13 @@ std::string RollbackManager::getSuspect(v3s16 p, float nearness_shortcut, return likely_suspect.actor; } - void RollbackManager::flush() { sqlite3_exec(db, "BEGIN", NULL, NULL, NULL); std::list::const_iterator iter; - for (iter = action_todisk_buffer.begin(); - iter != action_todisk_buffer.end(); + for (iter = action_todisk_buffer.begin(); iter != action_todisk_buffer.end(); ++iter) { if (iter->actor.empty()) { continue; @@ -929,8 +927,7 @@ void RollbackManager::flush() action_todisk_buffer.clear(); } - -void RollbackManager::addAction(const RollbackAction & action) +void RollbackManager::addAction(const RollbackAction &action) { action_todisk_buffer.push_back(action); action_latest_buffer.push_back(action); @@ -947,8 +944,8 @@ std::list RollbackManager::getEntriesSince(time_t first_time) return getActionsSince(first_time); } -std::list RollbackManager::getNodeActors(v3s16 pos, int range, - time_t seconds, int limit) +std::list RollbackManager::getNodeActors( + v3s16 pos, int range, time_t seconds, int limit) { flush(); time_t cur_time = time(0); @@ -958,8 +955,7 @@ std::list RollbackManager::getNodeActors(v3s16 pos, int range, } std::list RollbackManager::getRevertActions( - const std::string &actor_filter, - time_t seconds) + const std::string &actor_filter, time_t seconds) { time_t cur_time = time(0); time_t first_time = cur_time - seconds; @@ -968,4 +964,3 @@ std::list RollbackManager::getRevertActions( return getActionsSince(first_time, actor_filter); } - diff --git a/src/rollback.h b/src/rollback.h index 1d9949d15..b505750d2 100644 --- a/src/rollback.h +++ b/src/rollback.h @@ -31,53 +31,51 @@ class IGameDef; struct ActionRow; struct Entity; -class RollbackManager: public IRollbackManager +class RollbackManager : public IRollbackManager { public: - RollbackManager(const std::string & world_path, IGameDef * gamedef); + RollbackManager(const std::string &world_path, IGameDef *gamedef); ~RollbackManager(); - void reportAction(const RollbackAction & action_); + void reportAction(const RollbackAction &action_); std::string getActor(); bool isActorGuess(); - void setActor(const std::string & actor, bool is_guess); - std::string getSuspect(v3s16 p, float nearness_shortcut, - float min_nearness); + void setActor(const std::string &actor, bool is_guess); + std::string getSuspect(v3s16 p, float nearness_shortcut, float min_nearness); void flush(); - void addAction(const RollbackAction & action); + void addAction(const RollbackAction &action); std::list getEntriesSince(time_t first_time); - std::list getNodeActors(v3s16 pos, int range, - time_t seconds, int limit); + std::list getNodeActors( + v3s16 pos, int range, time_t seconds, int limit); std::list getRevertActions( - const std::string & actor_filter, time_t seconds); + const std::string &actor_filter, time_t seconds); private: - void registerNewActor(const int id, const std::string & name); - void registerNewNode(const int id, const std::string & name); - int getActorId(const std::string & name); - int getNodeId(const std::string & name); - const char * getActorName(const int id); - const char * getNodeName(const int id); + void registerNewActor(const int id, const std::string &name); + void registerNewNode(const int id, const std::string &name); + int getActorId(const std::string &name); + int getNodeId(const std::string &name); + const char *getActorName(const int id); + const char *getNodeName(const int id); bool createTables(); bool initDatabase(); - bool registerRow(const ActionRow & row); - const std::list actionRowsFromSelect(sqlite3_stmt * stmt); - ActionRow actionRowFromRollbackAction(const RollbackAction & action); + bool registerRow(const ActionRow &row); + const std::list actionRowsFromSelect(sqlite3_stmt *stmt); + ActionRow actionRowFromRollbackAction(const RollbackAction &action); const std::list rollbackActionsFromActionRows( - const std::list & rows); - const std::list getRowsSince(time_t firstTime, - const std::string & actor); - const std::list getRowsSince_range(time_t firstTime, v3s16 p, - int range, int limit); - const std::list getActionsSince_range(time_t firstTime, v3s16 p, - int range, int limit); - const std::list getActionsSince(time_t firstTime, - const std::string & actor = ""); - void migrate(const std::string & filepath); - static float getSuspectNearness(bool is_guess, v3s16 suspect_p, - time_t suspect_t, v3s16 action_p, time_t action_t); - + const std::list &rows); + const std::list getRowsSince( + time_t firstTime, const std::string &actor); + const std::list getRowsSince_range( + time_t firstTime, v3s16 p, int range, int limit); + const std::list getActionsSince_range( + time_t firstTime, v3s16 p, int range, int limit); + const std::list getActionsSince( + time_t firstTime, const std::string &actor = ""); + void migrate(const std::string &filepath); + static float getSuspectNearness(bool is_guess, v3s16 suspect_p, time_t suspect_t, + v3s16 action_p, time_t action_t); IGameDef *gamedef = nullptr; @@ -88,16 +86,16 @@ private: std::list action_latest_buffer; std::string database_path; - sqlite3 * db; - sqlite3_stmt * stmt_insert; - sqlite3_stmt * stmt_replace; - sqlite3_stmt * stmt_select; - sqlite3_stmt * stmt_select_range; - sqlite3_stmt * stmt_select_withActor; - sqlite3_stmt * stmt_knownActor_select; - sqlite3_stmt * stmt_knownActor_insert; - sqlite3_stmt * stmt_knownNode_select; - sqlite3_stmt * stmt_knownNode_insert; + sqlite3 *db; + sqlite3_stmt *stmt_insert; + sqlite3_stmt *stmt_replace; + sqlite3_stmt *stmt_select; + sqlite3_stmt *stmt_select_range; + sqlite3_stmt *stmt_select_withActor; + sqlite3_stmt *stmt_knownActor_select; + sqlite3_stmt *stmt_knownActor_insert; + sqlite3_stmt *stmt_knownNode_select; + sqlite3_stmt *stmt_knownNode_insert; std::vector knownActors; std::vector knownNodes; diff --git a/src/rollback_interface.cpp b/src/rollback_interface.cpp index c00206e98..2ab011192 100644 --- a/src/rollback_interface.cpp +++ b/src/rollback_interface.cpp @@ -33,7 +33,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "inventory.h" #include "mapblock.h" - RollbackNode::RollbackNode(Map *map, v3s16 p, IGameDef *gamedef) { const NodeDefManager *ndef = gamedef->ndef(); @@ -49,7 +48,6 @@ RollbackNode::RollbackNode(Map *map, v3s16 p, IGameDef *gamedef) } } - std::string RollbackAction::toString() const { std::ostringstream os(std::ios::binary); @@ -79,16 +77,15 @@ std::string RollbackAction::toString() const return os.str(); } - bool RollbackAction::isImportant(IGameDef *gamedef) const { if (type != TYPE_SET_NODE) return true; // If names differ, action is always important - if(n_old.name != n_new.name) + if (n_old.name != n_new.name) return true; // If metadata differs, action is always important - if(n_old.meta != n_new.meta) + if (n_old.meta != n_new.meta) return true; const NodeDefManager *ndef = gamedef->ndef(); // Both are of the same name, so a single definition is needed @@ -100,12 +97,12 @@ bool RollbackAction::isImportant(IGameDef *gamedef) const return true; } - bool RollbackAction::getPosition(v3s16 *dst) const { switch (type) { case TYPE_SET_NODE: - if (dst) *dst = p; + if (dst) + *dst = p; return true; case TYPE_MODIFY_INVENTORY_STACK: { InventoryLocation loc; @@ -113,15 +110,17 @@ bool RollbackAction::getPosition(v3s16 *dst) const if (loc.type != InventoryLocation::NODEMETA) { return false; } - if (dst) *dst = loc.p; - return true; } + if (dst) + *dst = loc.p; + return true; + } default: return false; } } - -bool RollbackAction::applyRevert(Map *map, InventoryManager *imgr, IGameDef *gamedef) const +bool RollbackAction::applyRevert( + Map *map, InventoryManager *imgr, IGameDef *gamedef) const { try { switch (type) { @@ -149,9 +148,9 @@ bool RollbackAction::applyRevert(Map *map, InventoryManager *imgr, IGameDef *gam try { if (!map->addNodeWithEvent(p, n)) { infostream << "RollbackAction::applyRevert(): " - << "AddNodeWithEvent failed at " - << PP(p) << " for " << n_old.name - << std::endl; + << "AddNodeWithEvent failed at " + << PP(p) << " for " << n_old.name + << std::endl; return false; } if (n_old.meta.empty()) { @@ -162,14 +161,18 @@ bool RollbackAction::applyRevert(Map *map, InventoryManager *imgr, IGameDef *gam meta = new NodeMetadata(gamedef->idef()); if (!map->setNodeMetadata(p, meta)) { delete meta; - infostream << "RollbackAction::applyRevert(): " - << "setNodeMetadata failed at " - << PP(p) << " for " << n_old.name - << std::endl; + infostream << "RollbackAction::" + "applyRevert(): " + << "setNodeMetadata " + "failed at " + << PP(p) << " for " + << n_old.name + << std::endl; return false; } } - std::istringstream is(n_old.meta, std::ios::binary); + std::istringstream is( + n_old.meta, std::ios::binary); meta->deSerialize(is, 1); // FIXME: version bump?? } // Inform other things that the meta data has changed @@ -179,33 +182,38 @@ bool RollbackAction::applyRevert(Map *map, InventoryManager *imgr, IGameDef *gam map->dispatchEvent(event); } catch (InvalidPositionException &e) { infostream << "RollbackAction::applyRevert(): " - << "InvalidPositionException: " << e.what() - << std::endl; + << "InvalidPositionException: " << e.what() + << std::endl; return false; } // Success - return true; } + return true; + } case TYPE_MODIFY_INVENTORY_STACK: { InventoryLocation loc; loc.deSerialize(inventory_location); Inventory *inv = imgr->getInventory(loc); if (!inv) { - infostream << "RollbackAction::applyRevert(): Could not get " - "inventory at " << inventory_location << std::endl; + infostream << "RollbackAction::applyRevert(): Could not " + "get " + "inventory at " + << inventory_location << std::endl; return false; } InventoryList *list = inv->getList(inventory_list); if (!list) { - infostream << "RollbackAction::applyRevert(): Could not get " - "inventory list \"" << inventory_list << "\" in " - << inventory_location << std::endl; + infostream << "RollbackAction::applyRevert(): Could not " + "get " + "inventory list \"" + << inventory_list << "\" in " + << inventory_location << std::endl; return false; } if (list->getSize() <= inventory_index) { infostream << "RollbackAction::applyRevert(): List index " - << inventory_index << " too large in " - << "inventory list \"" << inventory_list << "\" in " - << inventory_location << std::endl; + << inventory_index << " too large in " + << "inventory list \"" << inventory_list + << "\" in " << inventory_location << std::endl; return false; } @@ -213,7 +221,8 @@ bool RollbackAction::applyRevert(Map *map, InventoryManager *imgr, IGameDef *gam if (inventory_add) { // Silently ignore different current item if (list->getItem(inventory_index).name != - gamedef->idef()->getAlias(inventory_stack.name)) + gamedef->idef()->getAlias( + inventory_stack.name)) return false; list->takeItem(inventory_index, inventory_stack.count); } else { @@ -221,16 +230,16 @@ bool RollbackAction::applyRevert(Map *map, InventoryManager *imgr, IGameDef *gam } // Inventory was modified; send to clients imgr->setInventoryModified(loc); - return true; } + return true; + } default: errorstream << "RollbackAction::applyRevert(): type not handled" - << std::endl; + << std::endl; return false; } - } catch(SerializationError &e) { + } catch (SerializationError &e) { errorstream << "RollbackAction::applyRevert(): n_old.name=" << n_old.name - << ", SerializationError: " << e.what() << std::endl; + << ", SerializationError: " << e.what() << std::endl; } return false; } - diff --git a/src/rollback_interface.h b/src/rollback_interface.h index 94b800579..827593782 100644 --- a/src/rollback_interface.h +++ b/src/rollback_interface.h @@ -38,22 +38,22 @@ struct RollbackNode int param2 = 0; std::string meta; - bool operator == (const RollbackNode &other) + bool operator==(const RollbackNode &other) { return (name == other.name && param1 == other.param1 && param2 == other.param2 && meta == other.meta); } - bool operator != (const RollbackNode &other) { return !(*this == other); } + bool operator!=(const RollbackNode &other) { return !(*this == other); } RollbackNode() = default; RollbackNode(Map *map, v3s16 p, IGameDef *gamedef); }; - struct RollbackAction { - enum Type{ + enum Type + { TYPE_NOTHING, TYPE_SET_NODE, TYPE_MODIFY_INVENTORY_STACK, @@ -75,8 +75,7 @@ struct RollbackAction RollbackAction() = default; - void setSetNode(v3s16 p_, const RollbackNode &n_old_, - const RollbackNode &n_new_) + void setSetNode(v3s16 p_, const RollbackNode &n_old_, const RollbackNode &n_new_) { type = TYPE_SET_NODE; p = p_; @@ -85,8 +84,8 @@ struct RollbackAction } void setModifyInventoryStack(const std::string &inventory_location_, - const std::string &inventory_list_, u32 index_, - bool add_, const ItemStack &inventory_stack_) + const std::string &inventory_list_, u32 index_, bool add_, + const ItemStack &inventory_stack_) { type = TYPE_MODIFY_INVENTORY_STACK; inventory_location = inventory_location_; @@ -107,7 +106,6 @@ struct RollbackAction bool applyRevert(Map *map, InventoryManager *imgr, IGameDef *gamedef) const; }; - class IRollbackManager { public: @@ -115,27 +113,27 @@ public: virtual std::string getActor() = 0; virtual bool isActorGuess() = 0; virtual void setActor(const std::string &actor, bool is_guess) = 0; - virtual std::string getSuspect(v3s16 p, float nearness_shortcut, - float min_nearness) = 0; + virtual std::string getSuspect( + v3s16 p, float nearness_shortcut, float min_nearness) = 0; - virtual ~IRollbackManager() = default;; + virtual ~IRollbackManager() = default; + ; virtual void flush() = 0; // Get all actors that did something to position p, but not further than // in history - virtual std::list getNodeActors(v3s16 pos, int range, - time_t seconds, int limit) = 0; + virtual std::list getNodeActors( + v3s16 pos, int range, time_t seconds, int limit) = 0; // Get actions to revert of history made by - virtual std::list getRevertActions(const std::string &actor, - time_t seconds) = 0; + virtual std::list getRevertActions( + const std::string &actor, time_t seconds) = 0; }; - class RollbackScopeActor { public: - RollbackScopeActor(IRollbackManager * rollback_, - const std::string & actor, bool is_guess = false) : - rollback(rollback_) + RollbackScopeActor(IRollbackManager *rollback_, const std::string &actor, + bool is_guess = false) : + rollback(rollback_) { if (rollback) { old_actor = rollback->getActor(); @@ -151,7 +149,7 @@ public: } private: - IRollbackManager * rollback; + IRollbackManager *rollback; std::string old_actor; bool old_actor_guess; }; diff --git a/src/script/common/c_content.cpp b/src/script/common/c_content.cpp index 7d4c1e748..9e99d5673 100644 --- a/src/script/common/c_content.cpp +++ b/src/script/common/c_content.cpp @@ -38,23 +38,21 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "debug.h" // For FATAL_ERROR #include -struct EnumString es_TileAnimationType[] = -{ - {TAT_NONE, "none"}, - {TAT_VERTICAL_FRAMES, "vertical_frames"}, - {TAT_SHEET_2D, "sheet_2d"}, - {0, NULL}, +struct EnumString es_TileAnimationType[] = { + {TAT_NONE, "none"}, + {TAT_VERTICAL_FRAMES, "vertical_frames"}, + {TAT_SHEET_2D, "sheet_2d"}, + {0, NULL}, }; /******************************************************************************/ -void read_item_definition(lua_State* L, int index, - const ItemDefinition &default_def, ItemDefinition &def) +void read_item_definition(lua_State *L, int index, const ItemDefinition &default_def, + ItemDefinition &def) { if (index < 0) index = lua_gettop(L) + 1 + index; - def.type = (ItemType)getenumfield(L, index, "type", - es_ItemType, ITEM_NONE); + def.type = (ItemType)getenumfield(L, index, "type", es_ItemType, ITEM_NONE); getstringfield(L, index, "name", def.name); getstringfield(L, index, "description", def.description); getstringfield(L, index, "inventory_image", def.inventory_image); @@ -69,7 +67,7 @@ void read_item_definition(lua_State* L, int index, lua_pop(L, 1); lua_getfield(L, index, "wield_scale"); - if(lua_istable(L, -1)){ + if (lua_istable(L, -1)) { def.wield_scale = check_v3f(L, -1); } lua_pop(L, 1); @@ -87,15 +85,15 @@ void read_item_definition(lua_State* L, int index, "Obsolete; use tool_capabilities"); lua_getfield(L, index, "tool_capabilities"); - if(lua_istable(L, -1)){ - def.tool_capabilities = new ToolCapabilities( - read_tool_capabilities(L, -1)); + if (lua_istable(L, -1)) { + def.tool_capabilities = + new ToolCapabilities(read_tool_capabilities(L, -1)); } // If name is "" (hand), ensure there are ToolCapabilities // because it will be looked up there whenever any other item has // no ToolCapabilities - if (def.name.empty() && def.tool_capabilities == NULL){ + if (def.name.empty() && def.tool_capabilities == NULL) { def.tool_capabilities = new ToolCapabilities(); } @@ -182,10 +180,10 @@ void push_item_definition_full(lua_State *L, const ItemDefinition &i) } /******************************************************************************/ -void read_object_properties(lua_State *L, int index, - ServerActiveObject *sao, ObjectProperties *prop, IItemDefManager *idef) +void read_object_properties(lua_State *L, int index, ServerActiveObject *sao, + ObjectProperties *prop, IItemDefManager *idef) { - if(index < 0) + if (index < 0) index = lua_gettop(L) + 1 + index; if (lua_isnil(L, index)) return; @@ -200,7 +198,8 @@ void read_object_properties(lua_State *L, int index, PlayerHPChangeReason reason(PlayerHPChangeReason::SET_HP); sao->setHP(prop->hp_max, reason); if (sao->getType() == ACTIVEOBJECT_TYPE_PLAYER) - sao->getEnv()->getGameDef()->SendPlayerHPOrDie((PlayerSAO *)sao, reason); + sao->getEnv()->getGameDef()->SendPlayerHPOrDie( + (PlayerSAO *)sao, reason); } } @@ -248,13 +247,13 @@ void read_object_properties(lua_State *L, int index, lua_pop(L, 1); lua_getfield(L, -1, "textures"); - if(lua_istable(L, -1)){ + if (lua_istable(L, -1)) { prop->textures.clear(); int table = lua_gettop(L); lua_pushnil(L); - while(lua_next(L, table) != 0){ + while (lua_next(L, table) != 0) { // key at index -2 and value at index -1 - if(lua_isstring(L, -1)) + if (lua_isstring(L, -1)) prop->textures.emplace_back(lua_tostring(L, -1)); else prop->textures.emplace_back(""); @@ -277,12 +276,12 @@ void read_object_properties(lua_State *L, int index, lua_pop(L, 1); lua_getfield(L, -1, "spritediv"); - if(lua_istable(L, -1)) + if (lua_istable(L, -1)) prop->spritediv = read_v2s16(L, -1); lua_pop(L, 1); lua_getfield(L, -1, "initial_sprite_basepos"); - if(lua_istable(L, -1)) + if (lua_istable(L, -1)) prop->initial_sprite_basepos = read_v2s16(L, -1); lua_pop(L, 1); @@ -316,7 +315,8 @@ void read_object_properties(lua_State *L, int index, lua_getfield(L, -1, "automatic_face_movement_max_rotation_per_sec"); if (lua_isnumber(L, -1)) { - prop->automatic_face_movement_max_rotation_per_sec = luaL_checknumber(L, -1); + prop->automatic_face_movement_max_rotation_per_sec = + luaL_checknumber(L, -1); } lua_pop(L, 1); @@ -417,14 +417,15 @@ void push_object_properties(lua_State *L, ObjectProperties *prop) lua_setfield(L, -2, "use_texture_alpha"); lua_pushboolean(L, prop->shaded); lua_setfield(L, -2, "shaded"); - lua_pushlstring(L, prop->damage_texture_modifier.c_str(), prop->damage_texture_modifier.size()); + lua_pushlstring(L, prop->damage_texture_modifier.c_str(), + prop->damage_texture_modifier.size()); lua_setfield(L, -2, "damage_texture_modifier"); } /******************************************************************************/ TileDef read_tiledef(lua_State *L, int index, u8 drawtype) { - if(index < 0) + if (index < 0) index = lua_gettop(L) + 1 + index; TileDef tiledef; @@ -432,41 +433,39 @@ TileDef read_tiledef(lua_State *L, int index, u8 drawtype) bool default_tiling = true; bool default_culling = true; switch (drawtype) { - case NDT_PLANTLIKE: - case NDT_PLANTLIKE_ROOTED: - case NDT_FIRELIKE: - default_tiling = false; - // "break" is omitted here intentionaly, as PLANTLIKE - // FIRELIKE drawtype both should default to having - // backface_culling to false. - case NDT_MESH: - case NDT_LIQUID: - default_culling = false; - break; - default: - break; + case NDT_PLANTLIKE: + case NDT_PLANTLIKE_ROOTED: + case NDT_FIRELIKE: + default_tiling = false; + // "break" is omitted here intentionaly, as PLANTLIKE + // FIRELIKE drawtype both should default to having + // backface_culling to false. + case NDT_MESH: + case NDT_LIQUID: + default_culling = false; + break; + default: + break; } // key at index -2 and value at index - if(lua_isstring(L, index)){ + if (lua_isstring(L, index)) { // "default_lava.png" tiledef.name = lua_tostring(L, index); tiledef.tileable_vertical = default_tiling; tiledef.tileable_horizontal = default_tiling; tiledef.backface_culling = default_culling; - } - else if(lua_istable(L, index)) - { + } else if (lua_istable(L, index)) { // name="default_lava.png" tiledef.name = ""; getstringfield(L, index, "name", tiledef.name); getstringfield(L, index, "image", tiledef.name); // MaterialSpec compat. tiledef.backface_culling = getboolfield_default( - L, index, "backface_culling", default_culling); + L, index, "backface_culling", default_culling); tiledef.tileable_horizontal = getboolfield_default( - L, index, "tileable_horizontal", default_tiling); + L, index, "tileable_horizontal", default_tiling); tiledef.tileable_vertical = getboolfield_default( - L, index, "tileable_vertical", default_tiling); + L, index, "tileable_vertical", default_tiling); std::string align_style; if (getstringfield(L, index, "align_style", align_style)) { if (align_style == "user") @@ -493,20 +492,23 @@ TileDef read_tiledef(lua_State *L, int index, u8 drawtype) /******************************************************************************/ ContentFeatures read_content_features(lua_State *L, int index) { - if(index < 0) + if (index < 0) index = lua_gettop(L) + 1 + index; ContentFeatures f; /* Cache existence of some callbacks */ lua_getfield(L, index, "on_construct"); - if(!lua_isnil(L, -1)) f.has_on_construct = true; + if (!lua_isnil(L, -1)) + f.has_on_construct = true; lua_pop(L, 1); lua_getfield(L, index, "on_destruct"); - if(!lua_isnil(L, -1)) f.has_on_destruct = true; + if (!lua_isnil(L, -1)) + f.has_on_destruct = true; lua_pop(L, 1); lua_getfield(L, index, "after_destruct"); - if(!lua_isnil(L, -1)) f.has_after_destruct = true; + if (!lua_isnil(L, -1)) + f.has_after_destruct = true; lua_pop(L, 1); lua_getfield(L, index, "on_rightclick"); @@ -523,8 +525,8 @@ ContentFeatures read_content_features(lua_State *L, int index) /* Visual definition */ - f.drawtype = (NodeDrawType)getenumfield(L, index, "drawtype", - ScriptApiNode::es_DrawType,NDT_NORMAL); + f.drawtype = (NodeDrawType)getenumfield( + L, index, "drawtype", ScriptApiNode::es_DrawType, NDT_NORMAL); getfloatfield(L, index, "visual_scale", f.visual_scale); /* Meshnode model filename */ @@ -533,31 +535,31 @@ ContentFeatures read_content_features(lua_State *L, int index) // tiles = {} lua_getfield(L, index, "tiles"); // If nil, try the deprecated name "tile_images" instead - if(lua_isnil(L, -1)){ + if (lua_isnil(L, -1)) { lua_pop(L, 1); warn_if_field_exists(L, index, "tile_images", "Deprecated; new name is \"tiles\"."); lua_getfield(L, index, "tile_images"); } - if(lua_istable(L, -1)){ + if (lua_istable(L, -1)) { int table = lua_gettop(L); lua_pushnil(L); int i = 0; - while(lua_next(L, table) != 0){ + while (lua_next(L, table) != 0) { // Read tiledef from value f.tiledef[i] = read_tiledef(L, -1, f.drawtype); // removes value, keeps key for next iteration lua_pop(L, 1); i++; - if(i==6){ + if (i == 6) { lua_pop(L, 1); break; } } // Copy last value to all remaining textures - if(i >= 1){ - TileDef lasttile = f.tiledef[i-1]; - while(i < 6){ + if (i >= 1) { + TileDef lasttile = f.tiledef[i - 1]; + while (i < 6) { f.tiledef[i] = lasttile; i++; } @@ -596,23 +598,23 @@ ContentFeatures read_content_features(lua_State *L, int index) // special_tiles = {} lua_getfield(L, index, "special_tiles"); // If nil, try the deprecated name "special_materials" instead - if(lua_isnil(L, -1)){ + if (lua_isnil(L, -1)) { lua_pop(L, 1); warn_if_field_exists(L, index, "special_materials", "Deprecated; new name is \"special_tiles\"."); lua_getfield(L, index, "special_materials"); } - if(lua_istable(L, -1)){ + if (lua_istable(L, -1)) { int table = lua_gettop(L); lua_pushnil(L); int i = 0; - while(lua_next(L, table) != 0){ + while (lua_next(L, table) != 0) { // Read tiledef from value f.tiledef_special[i] = read_tiledef(L, -1, f.drawtype); // removes value, keeps key for next iteration lua_pop(L, 1); i++; - if(i==CF_SPECIAL_COUNT){ + if (i == CF_SPECIAL_COUNT) { lua_pop(L, 1); break; } @@ -622,8 +624,7 @@ ContentFeatures read_content_features(lua_State *L, int index) f.alpha = getintfield_default(L, index, "alpha", 255); - bool usealpha = getboolfield_default(L, index, - "use_texture_alpha", false); + bool usealpha = getboolfield_default(L, index, "use_texture_alpha", false); if (usealpha) f.alpha = 0; @@ -647,22 +648,21 @@ ContentFeatures read_content_features(lua_State *L, int index) if (!f.palette_name.empty() && !(f.param_type_2 == CPT2_COLOR || - f.param_type_2 == CPT2_COLORED_FACEDIR || - f.param_type_2 == CPT2_COLORED_WALLMOUNTED)) + f.param_type_2 == CPT2_COLORED_FACEDIR || + f.param_type_2 == CPT2_COLORED_WALLMOUNTED)) warningstream << "Node " << f.name.c_str() - << " has a palette, but not a suitable paramtype2." << std::endl; + << " has a palette, but not a suitable paramtype2." + << std::endl; // Warn about some obsolete fields warn_if_field_exists(L, index, "wall_mounted", "Obsolete; use paramtype2 = 'wallmounted'"); warn_if_field_exists(L, index, "light_propagates", "Obsolete; determined from paramtype"); - warn_if_field_exists(L, index, "dug_item", - "Obsolete; use 'drop' field"); - warn_if_field_exists(L, index, "extra_dug_item", - "Obsolete; use 'drop' field"); - warn_if_field_exists(L, index, "extra_dug_item_rarity", - "Obsolete; use 'drop' field"); + warn_if_field_exists(L, index, "dug_item", "Obsolete; use 'drop' field"); + warn_if_field_exists(L, index, "extra_dug_item", "Obsolete; use 'drop' field"); + warn_if_field_exists( + L, index, "extra_dug_item_rarity", "Obsolete; use 'drop' field"); warn_if_field_exists(L, index, "metadata_name", "Obsolete; use on_add and metadata callbacks"); @@ -695,31 +695,27 @@ ContentFeatures read_content_features(lua_State *L, int index) // Viscosity for fluid flow, ranging from 1 to 7, with // 1 giving almost instantaneous propagation and 7 being // the slowest possible - f.liquid_viscosity = getintfield_default(L, index, - "liquid_viscosity", f.liquid_viscosity); - f.liquid_range = getintfield_default(L, index, - "liquid_range", f.liquid_range); + f.liquid_viscosity = getintfield_default( + L, index, "liquid_viscosity", f.liquid_viscosity); + f.liquid_range = getintfield_default(L, index, "liquid_range", f.liquid_range); f.leveled = getintfield_default(L, index, "leveled", f.leveled); - f.leveled_max = getintfield_default(L, index, - "leveled_max", f.leveled_max); + f.leveled_max = getintfield_default(L, index, "leveled_max", f.leveled_max); getboolfield(L, index, "liquid_renewable", f.liquid_renewable); - f.drowning = getintfield_default(L, index, - "drowning", f.drowning); + f.drowning = getintfield_default(L, index, "drowning", f.drowning); // Amount of light the node emits - f.light_source = getintfield_default(L, index, - "light_source", f.light_source); + f.light_source = getintfield_default(L, index, "light_source", f.light_source); if (f.light_source > LIGHT_MAX) { warningstream << "Node " << f.name.c_str() - << " had greater light_source than " << LIGHT_MAX - << ", it was reduced." << std::endl; + << " had greater light_source than " << LIGHT_MAX + << ", it was reduced." << std::endl; f.light_source = LIGHT_MAX; } - f.damage_per_second = getintfield_default(L, index, - "damage_per_second", f.damage_per_second); + f.damage_per_second = getintfield_default( + L, index, "damage_per_second", f.damage_per_second); lua_getfield(L, index, "node_box"); - if(lua_istable(L, -1)) + if (lua_istable(L, -1)) f.node_box = read_nodebox(L, -1); lua_pop(L, 1); @@ -757,24 +753,23 @@ ContentFeatures read_content_features(lua_State *L, int index) f.connect_sides |= 8; else warningstream << "Unknown value for \"connect_sides\": " - << side << std::endl; + << side << std::endl; lua_pop(L, 1); } } lua_pop(L, 1); lua_getfield(L, index, "selection_box"); - if(lua_istable(L, -1)) + if (lua_istable(L, -1)) f.selection_box = read_nodebox(L, -1); - lua_pop(L, 1); + lua_pop(L, 1); lua_getfield(L, index, "collision_box"); - if(lua_istable(L, -1)) + if (lua_istable(L, -1)) f.collision_box = read_nodebox(L, -1); lua_pop(L, 1); - f.waving = getintfield_default(L, index, - "waving", f.waving); + f.waving = getintfield_default(L, index, "waving", f.waving); // Set to true if paramtype used to be 'facedir_simple' getboolfield(L, index, "legacy_facedir_simple", f.legacy_facedir_simple); @@ -783,7 +778,7 @@ ContentFeatures read_content_features(lua_State *L, int index) // Sound table lua_getfield(L, index, "sounds"); - if(lua_istable(L, -1)){ + if (lua_istable(L, -1)) { lua_getfield(L, -1, "footstep"); read_soundspec(L, -1, f.sound_footstep); lua_pop(L, 1); @@ -797,8 +792,7 @@ ContentFeatures read_content_features(lua_State *L, int index) lua_pop(L, 1); // Node immediately placed by client when node is dug - getstringfield(L, index, "node_dig_prediction", - f.node_dig_prediction); + getstringfield(L, index, "node_dig_prediction", f.node_dig_prediction); return f; } @@ -806,7 +800,8 @@ ContentFeatures read_content_features(lua_State *L, int index) void push_content_features(lua_State *L, const ContentFeatures &c) { std::string paramtype(ScriptApiNode::es_ContentParamType[(int)c.param_type].str); - std::string paramtype2(ScriptApiNode::es_ContentParamType2[(int)c.param_type_2].str); + std::string paramtype2( + ScriptApiNode::es_ContentParamType2[(int)c.param_type_2].str); std::string drawtype(ScriptApiNode::es_DrawType[(int)c.drawtype].str); std::string liquid_type(ScriptApiNode::es_LiquidType[(int)c.liquid_type].str); @@ -834,7 +829,7 @@ void push_content_features(lua_State *L, const ContentFeatures &c) lua_setfield(L, -2, "mesh"); } #ifndef SERVER - push_ARGB8(L, c.minimap_color); // I know this is not set-able w/ register_node, + push_ARGB8(L, c.minimap_color); // I know this is not set-able w/ register_node, lua_setfield(L, -2, "minimap_color"); // but the people need to know! #endif lua_pushnumber(L, c.visual_scale); @@ -934,48 +929,47 @@ void push_content_features(lua_State *L, const ContentFeatures &c) void push_nodebox(lua_State *L, const NodeBox &box) { lua_newtable(L); - switch (box.type) - { - case NODEBOX_REGULAR: - lua_pushstring(L, "regular"); - lua_setfield(L, -2, "type"); - break; - case NODEBOX_LEVELED: - case NODEBOX_FIXED: - lua_pushstring(L, "fixed"); - lua_setfield(L, -2, "type"); - push_box(L, box.fixed); - lua_setfield(L, -2, "fixed"); - break; - case NODEBOX_WALLMOUNTED: - lua_pushstring(L, "wallmounted"); - lua_setfield(L, -2, "type"); - push_aabb3f(L, box.wall_top); - lua_setfield(L, -2, "wall_top"); - push_aabb3f(L, box.wall_bottom); - lua_setfield(L, -2, "wall_bottom"); - push_aabb3f(L, box.wall_side); - lua_setfield(L, -2, "wall_side"); - break; - case NODEBOX_CONNECTED: - lua_pushstring(L, "connected"); - lua_setfield(L, -2, "type"); - push_box(L, box.connect_top); - lua_setfield(L, -2, "connect_top"); - push_box(L, box.connect_bottom); - lua_setfield(L, -2, "connect_bottom"); - push_box(L, box.connect_front); - lua_setfield(L, -2, "connect_front"); - push_box(L, box.connect_back); - lua_setfield(L, -2, "connect_back"); - push_box(L, box.connect_left); - lua_setfield(L, -2, "connect_left"); - push_box(L, box.connect_right); - lua_setfield(L, -2, "connect_right"); - break; - default: - FATAL_ERROR("Invalid box.type"); - break; + switch (box.type) { + case NODEBOX_REGULAR: + lua_pushstring(L, "regular"); + lua_setfield(L, -2, "type"); + break; + case NODEBOX_LEVELED: + case NODEBOX_FIXED: + lua_pushstring(L, "fixed"); + lua_setfield(L, -2, "type"); + push_box(L, box.fixed); + lua_setfield(L, -2, "fixed"); + break; + case NODEBOX_WALLMOUNTED: + lua_pushstring(L, "wallmounted"); + lua_setfield(L, -2, "type"); + push_aabb3f(L, box.wall_top); + lua_setfield(L, -2, "wall_top"); + push_aabb3f(L, box.wall_bottom); + lua_setfield(L, -2, "wall_bottom"); + push_aabb3f(L, box.wall_side); + lua_setfield(L, -2, "wall_side"); + break; + case NODEBOX_CONNECTED: + lua_pushstring(L, "connected"); + lua_setfield(L, -2, "type"); + push_box(L, box.connect_top); + lua_setfield(L, -2, "connect_top"); + push_box(L, box.connect_bottom); + lua_setfield(L, -2, "connect_bottom"); + push_box(L, box.connect_front); + lua_setfield(L, -2, "connect_front"); + push_box(L, box.connect_back); + lua_setfield(L, -2, "connect_back"); + push_box(L, box.connect_left); + lua_setfield(L, -2, "connect_left"); + push_box(L, box.connect_right); + lua_setfield(L, -2, "connect_right"); + break; + default: + FATAL_ERROR("Invalid box.type"); + break; } } @@ -1004,37 +998,37 @@ void push_palette(lua_State *L, const std::vector *palette) } /******************************************************************************/ -void read_server_sound_params(lua_State *L, int index, - ServerSoundParams ¶ms) +void read_server_sound_params(lua_State *L, int index, ServerSoundParams ¶ms) { - if(index < 0) + if (index < 0) index = lua_gettop(L) + 1 + index; // Clear params = ServerSoundParams(); - if(lua_istable(L, index)){ + if (lua_istable(L, index)) { getfloatfield(L, index, "gain", params.gain); getstringfield(L, index, "to_player", params.to_player); getfloatfield(L, index, "fade", params.fade); getfloatfield(L, index, "pitch", params.pitch); lua_getfield(L, index, "pos"); - if(!lua_isnil(L, -1)){ - v3f p = read_v3f(L, -1)*BS; + if (!lua_isnil(L, -1)) { + v3f p = read_v3f(L, -1) * BS; params.pos = p; params.type = ServerSoundParams::SSP_POSITIONAL; } lua_pop(L, 1); lua_getfield(L, index, "object"); - if(!lua_isnil(L, -1)){ + if (!lua_isnil(L, -1)) { ObjectRef *ref = ObjectRef::checkobject(L, -1); ServerActiveObject *sao = ObjectRef::getobject(ref); - if(sao){ + if (sao) { params.object = sao->getId(); params.type = ServerSoundParams::SSP_OBJECT; } } lua_pop(L, 1); - params.max_hear_distance = BS*getfloatfield_default(L, index, - "max_hear_distance", params.max_hear_distance/BS); + params.max_hear_distance = + BS * getfloatfield_default(L, index, "max_hear_distance", + params.max_hear_distance / BS); getboolfield(L, index, "loop", params.loop); getstringfield(L, index, "exclude_player", params.exclude_player); } @@ -1043,7 +1037,7 @@ void read_server_sound_params(lua_State *L, int index, /******************************************************************************/ void read_soundspec(lua_State *L, int index, SimpleSoundSpec &spec) { - if(index < 0) + if (index < 0) index = lua_gettop(L) + 1 + index; if (lua_isnil(L, index)) return; @@ -1080,20 +1074,21 @@ NodeBox read_nodebox(lua_State *L, int index) luaL_checktype(L, -1, LUA_TTABLE); - nodebox.type = (NodeBoxType)getenumfield(L, index, "type", - ScriptApiNode::es_NodeBoxType, NODEBOX_REGULAR); + nodebox.type = (NodeBoxType)getenumfield( + L, index, "type", ScriptApiNode::es_NodeBoxType, NODEBOX_REGULAR); -#define NODEBOXREAD(n, s){ \ - lua_getfield(L, index, (s)); \ - if (lua_istable(L, -1)) \ - (n) = read_aabb3f(L, -1, BS); \ - lua_pop(L, 1); \ +#define NODEBOXREAD(n, s) \ + { \ + lua_getfield(L, index, (s)); \ + if (lua_istable(L, -1)) \ + (n) = read_aabb3f(L, -1, BS); \ + lua_pop(L, 1); \ } -#define NODEBOXREADVEC(n, s) \ - lua_getfield(L, index, (s)); \ - if (lua_istable(L, -1)) \ - (n) = read_aabb3f_vector(L, -1, BS); \ +#define NODEBOXREADVEC(n, s) \ + lua_getfield(L, index, (s)); \ + if (lua_istable(L, -1)) \ + (n) = read_aabb3f_vector(L, -1, BS); \ lua_pop(L, 1); NODEBOXREADVEC(nodebox.fixed, "fixed"); @@ -1159,34 +1154,31 @@ void pushnode(lua_State *L, const MapNode &n, const NodeDefManager *ndef) } /******************************************************************************/ -void warn_if_field_exists(lua_State *L, int table, - const char *name, const std::string &message) +void warn_if_field_exists( + lua_State *L, int table, const char *name, const std::string &message) { lua_getfield(L, table, name); if (!lua_isnil(L, -1)) { - warningstream << "Field \"" << name << "\": " - << message << std::endl; + warningstream << "Field \"" << name << "\": " << message << std::endl; infostream << script_get_backtrace(L) << std::endl; } lua_pop(L, 1); } /******************************************************************************/ -int getenumfield(lua_State *L, int table, - const char *fieldname, const EnumString *spec, int default_) +int getenumfield(lua_State *L, int table, const char *fieldname, const EnumString *spec, + int default_) { int result = default_; - string_to_enum(spec, result, - getstringfield_default(L, table, fieldname, "")); + string_to_enum(spec, result, getstringfield_default(L, table, fieldname, "")); return result; } /******************************************************************************/ -bool string_to_enum(const EnumString *spec, int &result, - const std::string &str) +bool string_to_enum(const EnumString *spec, int &result, const std::string &str) { const EnumString *esp = spec; - while(esp->str){ + while (esp->str) { if (!strcmp(str.c_str(), esp->str)) { result = esp->num; return true; @@ -1197,9 +1189,9 @@ bool string_to_enum(const EnumString *spec, int &result, } /******************************************************************************/ -ItemStack read_item(lua_State* L, int index, IItemDefManager *idef) +ItemStack read_item(lua_State *L, int index, IItemDefManager *idef) { - if(index < 0) + if (index < 0) index = lua_gettop(L) + 1 + index; if (lua_isnil(L, index)) { @@ -1215,21 +1207,16 @@ ItemStack read_item(lua_State* L, int index, IItemDefManager *idef) if (lua_isstring(L, index)) { // Convert from itemstring std::string itemstring = lua_tostring(L, index); - try - { + try { ItemStack item; item.deSerialize(itemstring, idef); return item; - } - catch(SerializationError &e) - { - warningstream<<"unable to create item from itemstring" - <<": "< lists = inventory->getLists(); - std::vector::iterator iter = lists.begin(); + std::vector lists = inventory->getLists(); + std::vector::iterator iter = lists.begin(); lua_createtable(L, 0, lists.size()); for (; iter != lists.end(); iter++) { - const char* name = (*iter)->getName().c_str(); + const char *name = (*iter)->getName().c_str(); lua_pushstring(L, name); push_inventory_list(L, inventory, name); lua_rawset(L, -3); @@ -1325,40 +1312,40 @@ void push_inventory(lua_State *L, Inventory *inventory) void push_inventory_list(lua_State *L, Inventory *inv, const char *name) { InventoryList *invlist = inv->getList(name); - if(invlist == NULL){ + if (invlist == NULL) { lua_pushnil(L); return; } std::vector items; - for(u32 i=0; igetSize(); i++) + for (u32 i = 0; i < invlist->getSize(); i++) items.push_back(invlist->getItem(i)); push_items(L, items); } /******************************************************************************/ -void read_inventory_list(lua_State *L, int tableindex, - Inventory *inv, const char *name, Server* srv, int forcesize) +void read_inventory_list(lua_State *L, int tableindex, Inventory *inv, const char *name, + Server *srv, int forcesize) { - if(tableindex < 0) + if (tableindex < 0) tableindex = lua_gettop(L) + 1 + tableindex; // If nil, delete list - if(lua_isnil(L, tableindex)){ + if (lua_isnil(L, tableindex)) { inv->deleteList(name); return; } // Otherwise set list - std::vector items = read_items(L, tableindex,srv); + std::vector items = read_items(L, tableindex, srv); int listsize = (forcesize != -1) ? forcesize : items.size(); InventoryList *invlist = inv->addList(name, listsize); int index = 0; - for(std::vector::const_iterator - i = items.begin(); i != items.end(); ++i){ - if(forcesize != -1 && index == forcesize) + for (std::vector::const_iterator i = items.begin(); i != items.end(); + ++i) { + if (forcesize != -1 && index == forcesize) break; invlist->changeItem(index, *i); index++; } - while(forcesize != -1 && index < forcesize){ + while (forcesize != -1 && index < forcesize) { invlist->deleteItem(index); index++; } @@ -1367,7 +1354,7 @@ void read_inventory_list(lua_State *L, int tableindex, /******************************************************************************/ struct TileAnimationParams read_animation_definition(lua_State *L, int index) { - if(index < 0) + if (index < 0) index = lua_gettop(L) + 1 + index; struct TileAnimationParams anim; @@ -1375,74 +1362,75 @@ struct TileAnimationParams read_animation_definition(lua_State *L, int index) if (!lua_istable(L, index)) return anim; - anim.type = (TileAnimationType) - getenumfield(L, index, "type", es_TileAnimationType, - TAT_NONE); + anim.type = (TileAnimationType)getenumfield( + L, index, "type", es_TileAnimationType, TAT_NONE); if (anim.type == TAT_VERTICAL_FRAMES) { // {type="vertical_frames", aspect_w=16, aspect_h=16, length=2.0} anim.vertical_frames.aspect_w = - getintfield_default(L, index, "aspect_w", 16); + getintfield_default(L, index, "aspect_w", 16); anim.vertical_frames.aspect_h = - getintfield_default(L, index, "aspect_h", 16); + getintfield_default(L, index, "aspect_h", 16); anim.vertical_frames.length = - getfloatfield_default(L, index, "length", 1.0); + getfloatfield_default(L, index, "length", 1.0); } else if (anim.type == TAT_SHEET_2D) { // {type="sheet_2d", frames_w=5, frames_h=3, frame_length=0.5} - getintfield(L, index, "frames_w", - anim.sheet_2d.frames_w); - getintfield(L, index, "frames_h", - anim.sheet_2d.frames_h); - getfloatfield(L, index, "frame_length", - anim.sheet_2d.frame_length); + getintfield(L, index, "frames_w", anim.sheet_2d.frames_w); + getintfield(L, index, "frames_h", anim.sheet_2d.frames_h); + getfloatfield(L, index, "frame_length", anim.sheet_2d.frame_length); } return anim; } /******************************************************************************/ -ToolCapabilities read_tool_capabilities( - lua_State *L, int table) +ToolCapabilities read_tool_capabilities(lua_State *L, int table) { ToolCapabilities toolcap; getfloatfield(L, table, "full_punch_interval", toolcap.full_punch_interval); getintfield(L, table, "max_drop_level", toolcap.max_drop_level); getintfield(L, table, "punch_attack_uses", toolcap.punch_attack_uses); lua_getfield(L, table, "groupcaps"); - if(lua_istable(L, -1)){ + if (lua_istable(L, -1)) { int table_groupcaps = lua_gettop(L); lua_pushnil(L); - while(lua_next(L, table_groupcaps) != 0){ + while (lua_next(L, table_groupcaps) != 0) { // key at index -2 and value at index -1 std::string groupname = luaL_checkstring(L, -2); - if(lua_istable(L, -1)){ + if (lua_istable(L, -1)) { int table_groupcap = lua_gettop(L); // This will be created ToolGroupCap groupcap; // Read simple parameters - getintfield(L, table_groupcap, "maxlevel", groupcap.maxlevel); + getintfield(L, table_groupcap, "maxlevel", + groupcap.maxlevel); getintfield(L, table_groupcap, "uses", groupcap.uses); // DEPRECATED: maxwear float maxwear = 0; - if (getfloatfield(L, table_groupcap, "maxwear", maxwear)){ + if (getfloatfield(L, table_groupcap, "maxwear", + maxwear)) { if (maxwear != 0) - groupcap.uses = 1.0/maxwear; + groupcap.uses = 1.0 / maxwear; else groupcap.uses = 0; - warningstream << "Field \"maxwear\" is deprecated; " - << "replace with uses=1/maxwear" << std::endl; - infostream << script_get_backtrace(L) << std::endl; + warningstream << "Field \"maxwear\" is " + "deprecated; " + << "replace with uses=1/maxwear" + << std::endl; + infostream << script_get_backtrace(L) + << std::endl; } // Read "times" table lua_getfield(L, table_groupcap, "times"); - if(lua_istable(L, -1)){ + if (lua_istable(L, -1)) { int table_times = lua_gettop(L); lua_pushnil(L); - while(lua_next(L, table_times) != 0){ + while (lua_next(L, table_times) != 0) { // key at index -2 and value at index -1 int rating = luaL_checkinteger(L, -2); float time = luaL_checknumber(L, -1); groupcap.times[rating] = time; - // removes value, keeps key for next iteration + // removes value, keeps key for next + // iteration lua_pop(L, 1); } } @@ -1457,10 +1445,10 @@ ToolCapabilities read_tool_capabilities( lua_pop(L, 1); lua_getfield(L, table, "damage_groups"); - if(lua_istable(L, -1)){ + if (lua_istable(L, -1)) { int table_damage_groups = lua_gettop(L); lua_pushnil(L); - while(lua_next(L, table_damage_groups) != 0){ + while (lua_next(L, table_damage_groups) != 0) { // key at index -2 and value at index -1 std::string groupname = luaL_checkstring(L, -2); u16 value = luaL_checkinteger(L, -1); @@ -1474,7 +1462,7 @@ ToolCapabilities read_tool_capabilities( } /******************************************************************************/ -void push_dig_params(lua_State *L,const DigParams ¶ms) +void push_dig_params(lua_State *L, const DigParams ¶ms) { lua_createtable(L, 0, 3); setboolfield(L, -1, "diggable", params.diggable); @@ -1483,7 +1471,7 @@ void push_dig_params(lua_State *L,const DigParams ¶ms) } /******************************************************************************/ -void push_hit_params(lua_State *L,const HitParams ¶ms) +void push_hit_params(lua_State *L, const HitParams ¶ms) { lua_createtable(L, 0, 3); setintfield(L, -1, "hp", params.hp); @@ -1492,8 +1480,8 @@ void push_hit_params(lua_State *L,const HitParams ¶ms) /******************************************************************************/ -bool getflagsfield(lua_State *L, int table, const char *fieldname, - FlagDesc *flagdesc, u32 *flags, u32 *flagmask) +bool getflagsfield(lua_State *L, int table, const char *fieldname, FlagDesc *flagdesc, + u32 *flags, u32 *flagmask) { lua_getfield(L, table, fieldname); @@ -1504,8 +1492,7 @@ bool getflagsfield(lua_State *L, int table, const char *fieldname, return success; } -bool read_flags(lua_State *L, int index, FlagDesc *flagdesc, - u32 *flags, u32 *flagmask) +bool read_flags(lua_State *L, int index, FlagDesc *flagdesc, u32 *flags, u32 *flagmask) { if (lua_isstring(L, index)) { std::string flagstr = lua_tostring(L, index); @@ -1601,7 +1588,7 @@ void push_items(lua_State *L, const std::vector &items) /******************************************************************************/ std::vector read_items(lua_State *L, int index, Server *srv) { - if(index < 0) + if (index < 0) index = lua_gettop(L) + 1 + index; std::vector items; @@ -1612,7 +1599,7 @@ std::vector read_items(lua_State *L, int index, Server *srv) if (key < 1) { throw LuaError("Invalid inventory list index"); } - if (items.size() < (u32) key) { + if (items.size() < (u32)key) { items.resize(key); } items[key - 1] = read_item(L, -1, srv->idef()); @@ -1643,21 +1630,23 @@ bool read_noiseparams(lua_State *L, int index, NoiseParams *np) if (!lua_istable(L, index)) return false; - getfloatfield(L, index, "offset", np->offset); - getfloatfield(L, index, "scale", np->scale); - getfloatfield(L, index, "persist", np->persist); + getfloatfield(L, index, "offset", np->offset); + getfloatfield(L, index, "scale", np->scale); + getfloatfield(L, index, "persist", np->persist); getfloatfield(L, index, "persistence", np->persist); - getfloatfield(L, index, "lacunarity", np->lacunarity); - getintfield(L, index, "seed", np->seed); - getintfield(L, index, "octaves", np->octaves); + getfloatfield(L, index, "lacunarity", np->lacunarity); + getintfield(L, index, "seed", np->seed); + getintfield(L, index, "octaves", np->octaves); - u32 flags = 0; + u32 flags = 0; u32 flagmask = 0; - np->flags = getflagsfield(L, index, "flags", flagdesc_noiseparams, - &flags, &flagmask) ? flags : NOISE_FLAG_DEFAULTS; + np->flags = getflagsfield(L, index, "flags", flagdesc_noiseparams, &flags, + &flagmask) + ? flags + : NOISE_FLAG_DEFAULTS; lua_getfield(L, index, "spread"); - np->spread = read_v3f(L, -1); + np->spread = read_v3f(L, -1); lua_pop(L, 1); return true; @@ -1679,8 +1668,7 @@ void push_noiseparams(lua_State *L, NoiseParams *np) lua_pushnumber(L, np->octaves); lua_setfield(L, -2, "octaves"); - push_flags_string(L, flagdesc_noiseparams, np->flags, - np->flags); + push_flags_string(L, flagdesc_noiseparams, np->flags, np->flags); lua_setfield(L, -2, "flags"); push_v3_float_string(L, np->spread); @@ -1703,55 +1691,52 @@ static int push_json_value_getdepth(const Json::Value &value) return maxdepth + 1; } // Recursive function to convert JSON --> Lua table -static bool push_json_value_helper(lua_State *L, const Json::Value &value, - int nullindex) +static bool push_json_value_helper(lua_State *L, const Json::Value &value, int nullindex) { - switch(value.type()) { - case Json::nullValue: - default: - lua_pushvalue(L, nullindex); - break; - case Json::intValue: - lua_pushinteger(L, value.asLargestInt()); - break; - case Json::uintValue: - lua_pushinteger(L, value.asLargestUInt()); - break; - case Json::realValue: - lua_pushnumber(L, value.asDouble()); - break; - case Json::stringValue: - { - const char *str = value.asCString(); - lua_pushstring(L, str ? str : ""); - } - break; - case Json::booleanValue: - lua_pushboolean(L, value.asInt()); - break; - case Json::arrayValue: - lua_createtable(L, value.size(), 0); - for (Json::Value::const_iterator it = value.begin(); - it != value.end(); ++it) { - push_json_value_helper(L, *it, nullindex); - lua_rawseti(L, -2, it.index() + 1); - } - break; - case Json::objectValue: - lua_createtable(L, 0, value.size()); - for (Json::Value::const_iterator it = value.begin(); - it != value.end(); ++it) { + switch (value.type()) { + case Json::nullValue: + default: + lua_pushvalue(L, nullindex); + break; + case Json::intValue: + lua_pushinteger(L, value.asLargestInt()); + break; + case Json::uintValue: + lua_pushinteger(L, value.asLargestUInt()); + break; + case Json::realValue: + lua_pushnumber(L, value.asDouble()); + break; + case Json::stringValue: { + const char *str = value.asCString(); + lua_pushstring(L, str ? str : ""); + } break; + case Json::booleanValue: + lua_pushboolean(L, value.asInt()); + break; + case Json::arrayValue: + lua_createtable(L, value.size(), 0); + for (Json::Value::const_iterator it = value.begin(); it != value.end(); + ++it) { + push_json_value_helper(L, *it, nullindex); + lua_rawseti(L, -2, it.index() + 1); + } + break; + case Json::objectValue: + lua_createtable(L, 0, value.size()); + for (Json::Value::const_iterator it = value.begin(); it != value.end(); + ++it) { #if !defined(JSONCPP_STRING) && (JSONCPP_VERSION_MAJOR < 1 || JSONCPP_VERSION_MINOR < 9) - const char *str = it.memberName(); - lua_pushstring(L, str ? str : ""); + const char *str = it.memberName(); + lua_pushstring(L, str ? str : ""); #else - std::string str = it.name(); - lua_pushstring(L, str.c_str()); + std::string str = it.name(); + lua_pushstring(L, str.c_str()); #endif - push_json_value_helper(L, *it, nullindex); - lua_rawset(L, -3); - } - break; + push_json_value_helper(L, *it, nullindex); + lua_rawset(L, -3); + } + break; } return true; } @@ -1759,7 +1744,7 @@ static bool push_json_value_helper(lua_State *L, const Json::Value &value, // nullindex: Lua stack index of value to use in place of JSON null bool push_json_value(lua_State *L, const Json::Value &value, int nullindex) { - if(nullindex < 0) + if (nullindex < 0) nullindex = lua_gettop(L) + 1 + nullindex; int depth = push_json_value_getdepth(value); @@ -1780,7 +1765,7 @@ void read_json_value(lua_State *L, Json::Value &root, int index, u8 recursion) } int type = lua_type(L, index); if (type == LUA_TBOOLEAN) { - root = (bool) lua_toboolean(L, index); + root = (bool)lua_toboolean(L, index); } else if (type == LUA_TNUMBER) { root = lua_tonumber(L, index); } else if (type == LUA_TSTRING) { @@ -1798,33 +1783,43 @@ void read_json_value(lua_State *L, Json::Value &root, int index, u8 recursion) int keytype = lua_type(L, -1); if (keytype == LUA_TNUMBER) { lua_Number key = lua_tonumber(L, -1); - if (roottype != Json::nullValue && roottype != Json::arrayValue) { - throw SerializationError("Can't mix array and object values in JSON"); + if (roottype != Json::nullValue && + roottype != Json::arrayValue) { + throw SerializationError("Can't mix array and " + "object values in JSON"); } else if (key < 1) { - throw SerializationError("Can't use zero-based or negative indexes in JSON"); + throw SerializationError( + "Can't use zero-based or " + "negative indexes in JSON"); } else if (floor(key) != key) { - throw SerializationError("Can't use indexes with a fractional part in JSON"); + throw SerializationError( + "Can't use indexes with a " + "fractional part in JSON"); } - root[(Json::ArrayIndex) key - 1] = value; + root[(Json::ArrayIndex)key - 1] = value; } else if (keytype == LUA_TSTRING) { - if (roottype != Json::nullValue && roottype != Json::objectValue) { - throw SerializationError("Can't mix array and object values in JSON"); + if (roottype != Json::nullValue && + roottype != Json::objectValue) { + throw SerializationError("Can't mix array and " + "object values in JSON"); } root[lua_tostring(L, -1)] = value; } else { - throw SerializationError("Lua key to convert to JSON is not a string or number"); + throw SerializationError("Lua key to convert to JSON is " + "not a string or number"); } } } else if (type == LUA_TNIL) { root = Json::nullValue; } else { - throw SerializationError("Can only store booleans, numbers, strings, objects, arrays, and null in JSON"); + throw SerializationError("Can only store booleans, numbers, strings, " + "objects, arrays, and null in JSON"); } lua_pop(L, 1); // Pop value } -void push_pointed_thing(lua_State *L, const PointedThing &pointed, bool csm, - bool hitpoint) +void push_pointed_thing( + lua_State *L, const PointedThing &pointed, bool csm, bool hitpoint) { lua_newtable(L); if (pointed.type == POINTEDTHING_NODE) { @@ -1843,7 +1838,7 @@ void push_pointed_thing(lua_State *L, const PointedThing &pointed, bool csm, } else { push_objectRef(L, pointed.object_id); } - + lua_setfield(L, -2, "ref"); } else { lua_pushstring(L, "nothing"); @@ -1873,8 +1868,8 @@ void push_objectRef(lua_State *L, const u16 id) void read_hud_element(lua_State *L, HudElement *elem) { - elem->type = (HudElementType)getenumfield(L, 2, "hud_elem_type", - es_HudElementType, HUD_ELEM_TEXT); + elem->type = (HudElementType)getenumfield( + L, 2, "hud_elem_type", es_HudElementType, HUD_ELEM_TEXT); lua_getfield(L, 2, "position"); elem->pos = lua_istable(L, -1) ? read_v2f(L, -1) : v2f(); @@ -1888,18 +1883,18 @@ void read_hud_element(lua_State *L, HudElement *elem) elem->size = lua_istable(L, -1) ? read_v2s32(L, -1) : v2s32(); lua_pop(L, 1); - elem->name = getstringfield_default(L, 2, "name", ""); - elem->text = getstringfield_default(L, 2, "text", ""); - elem->number = getintfield_default(L, 2, "number", 0); + elem->name = getstringfield_default(L, 2, "name", ""); + elem->text = getstringfield_default(L, 2, "text", ""); + elem->number = getintfield_default(L, 2, "number", 0); if (elem->type == HUD_ELEM_WAYPOINT) // waypoints reuse the item field to store precision, item = precision + 1 elem->item = getintfield_default(L, 2, "precision", -1) + 1; else elem->item = getintfield_default(L, 2, "item", 0); - elem->dir = getintfield_default(L, 2, "direction", 0); - elem->z_index = MYMAX(S16_MIN, MYMIN(S16_MAX, - getintfield_default(L, 2, "z_index", 0))); - elem->text2 = getstringfield_default(L, 2, "text2", ""); + elem->dir = getintfield_default(L, 2, "direction", 0); + elem->z_index = MYMAX( + S16_MIN, MYMIN(S16_MAX, getintfield_default(L, 2, "z_index", 0))); + elem->text2 = getstringfield_default(L, 2, "text2", ""); // Deprecated, only for compatibility's sake if (elem->dir == 0) @@ -1918,8 +1913,8 @@ void read_hud_element(lua_State *L, HudElement *elem) lua_pop(L, 1); /* check for known deprecated element usage */ - if ((elem->type == HUD_ELEM_STATBAR) && (elem->size == v2s32())) - log_deprecated(L,"Deprecated usage of statbar without size!"); + if ((elem->type == HUD_ELEM_STATBAR) && (elem->size == v2s32())) + log_deprecated(L, "Deprecated usage of statbar without size!"); } void push_hud_element(lua_State *L, HudElement *elem) @@ -1979,63 +1974,64 @@ HudElementStat read_hud_change(lua_State *L, HudElement *elem, void **value) if (lua_isstring(L, 3)) { int statint; std::string statstr = lua_tostring(L, 3); - stat = string_to_enum(es_HudElementStat, statint, statstr) ? - (HudElementStat)statint : stat; + stat = string_to_enum(es_HudElementStat, statint, statstr) + ? (HudElementStat)statint + : stat; } switch (stat) { - case HUD_STAT_POS: - elem->pos = read_v2f(L, 4); - *value = &elem->pos; - break; - case HUD_STAT_NAME: - elem->name = luaL_checkstring(L, 4); - *value = &elem->name; - break; - case HUD_STAT_SCALE: - elem->scale = read_v2f(L, 4); - *value = &elem->scale; - break; - case HUD_STAT_TEXT: - elem->text = luaL_checkstring(L, 4); - *value = &elem->text; - break; - case HUD_STAT_NUMBER: - elem->number = luaL_checknumber(L, 4); - *value = &elem->number; - break; - case HUD_STAT_ITEM: - elem->item = luaL_checknumber(L, 4); - *value = &elem->item; - break; - case HUD_STAT_DIR: - elem->dir = luaL_checknumber(L, 4); - *value = &elem->dir; - break; - case HUD_STAT_ALIGN: - elem->align = read_v2f(L, 4); - *value = &elem->align; - break; - case HUD_STAT_OFFSET: - elem->offset = read_v2f(L, 4); - *value = &elem->offset; - break; - case HUD_STAT_WORLD_POS: - elem->world_pos = read_v3f(L, 4); - *value = &elem->world_pos; - break; - case HUD_STAT_SIZE: - elem->size = read_v2s32(L, 4); - *value = &elem->size; - break; - case HUD_STAT_Z_INDEX: - elem->z_index = MYMAX(S16_MIN, MYMIN(S16_MAX, luaL_checknumber(L, 4))); - *value = &elem->z_index; - break; - case HUD_STAT_TEXT2: - elem->text2 = luaL_checkstring(L, 4); - *value = &elem->text2; - break; + case HUD_STAT_POS: + elem->pos = read_v2f(L, 4); + *value = &elem->pos; + break; + case HUD_STAT_NAME: + elem->name = luaL_checkstring(L, 4); + *value = &elem->name; + break; + case HUD_STAT_SCALE: + elem->scale = read_v2f(L, 4); + *value = &elem->scale; + break; + case HUD_STAT_TEXT: + elem->text = luaL_checkstring(L, 4); + *value = &elem->text; + break; + case HUD_STAT_NUMBER: + elem->number = luaL_checknumber(L, 4); + *value = &elem->number; + break; + case HUD_STAT_ITEM: + elem->item = luaL_checknumber(L, 4); + *value = &elem->item; + break; + case HUD_STAT_DIR: + elem->dir = luaL_checknumber(L, 4); + *value = &elem->dir; + break; + case HUD_STAT_ALIGN: + elem->align = read_v2f(L, 4); + *value = &elem->align; + break; + case HUD_STAT_OFFSET: + elem->offset = read_v2f(L, 4); + *value = &elem->offset; + break; + case HUD_STAT_WORLD_POS: + elem->world_pos = read_v3f(L, 4); + *value = &elem->world_pos; + break; + case HUD_STAT_SIZE: + elem->size = read_v2s32(L, 4); + *value = &elem->size; + break; + case HUD_STAT_Z_INDEX: + elem->z_index = MYMAX(S16_MIN, MYMIN(S16_MAX, luaL_checknumber(L, 4))); + *value = &elem->z_index; + break; + case HUD_STAT_TEXT2: + elem->text2 = luaL_checkstring(L, 4); + *value = &elem->text2; + break; } return stat; } @@ -2044,15 +2040,15 @@ HudElementStat read_hud_change(lua_State *L, HudElement *elem, void **value) // Indices must match values in `enum CollisionType` exactly!! static const char *collision_type_str[] = { - "node", - "object", + "node", + "object", }; // Indices must match values in `enum CollisionAxis` exactly!! static const char *collision_axis_str[] = { - "x", - "y", - "z", + "x", + "y", + "z", }; void push_collision_move_result(lua_State *L, const collisionMoveResult &res) diff --git a/src/script/common/c_content.h b/src/script/common/c_content.h index 5a8bf6700..49ab96fd5 100644 --- a/src/script/common/c_content.h +++ b/src/script/common/c_content.h @@ -17,7 +17,6 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ - /******************************************************************************/ /******************************************************************************/ /* WARNING!!!! do NOT add this header in any include file or any code file */ @@ -41,7 +40,10 @@ extern "C" { #include "c_types.h" #include "hud.h" -namespace Json { class Value; } +namespace Json +{ +class Value; +} struct MapNode; class NodeDefManager; @@ -67,138 +69,96 @@ struct collisionMoveResult; extern struct EnumString es_TileAnimationType[]; -ContentFeatures read_content_features (lua_State *L, int index); -void push_content_features (lua_State *L, - const ContentFeatures &c); +ContentFeatures read_content_features(lua_State *L, int index); +void push_content_features(lua_State *L, const ContentFeatures &c); -void push_nodebox (lua_State *L, - const NodeBox &box); -void push_box (lua_State *L, - const std::vector &box); +void push_nodebox(lua_State *L, const NodeBox &box); +void push_box(lua_State *L, const std::vector &box); -void push_palette (lua_State *L, - const std::vector *palette); +void push_palette(lua_State *L, const std::vector *palette); -TileDef read_tiledef (lua_State *L, int index, - u8 drawtype); +TileDef read_tiledef(lua_State *L, int index, u8 drawtype); -void read_soundspec (lua_State *L, int index, - SimpleSoundSpec &spec); -NodeBox read_nodebox (lua_State *L, int index); +void read_soundspec(lua_State *L, int index, SimpleSoundSpec &spec); +NodeBox read_nodebox(lua_State *L, int index); -void read_server_sound_params (lua_State *L, int index, - ServerSoundParams ¶ms); +void read_server_sound_params(lua_State *L, int index, ServerSoundParams ¶ms); -void push_dig_params (lua_State *L, - const DigParams ¶ms); -void push_hit_params (lua_State *L, - const HitParams ¶ms); +void push_dig_params(lua_State *L, const DigParams ¶ms); +void push_hit_params(lua_State *L, const HitParams ¶ms); -ItemStack read_item (lua_State *L, int index, IItemDefManager *idef); +ItemStack read_item(lua_State *L, int index, IItemDefManager *idef); struct TileAnimationParams read_animation_definition(lua_State *L, int index); -ToolCapabilities read_tool_capabilities (lua_State *L, int table); -void push_tool_capabilities (lua_State *L, - const ToolCapabilities &prop); +ToolCapabilities read_tool_capabilities(lua_State *L, int table); +void push_tool_capabilities(lua_State *L, const ToolCapabilities &prop); -void read_item_definition (lua_State *L, int index, const ItemDefinition &default_def, +void read_item_definition(lua_State *L, int index, const ItemDefinition &default_def, ItemDefinition &def); -void push_item_definition (lua_State *L, - const ItemDefinition &i); -void push_item_definition_full (lua_State *L, - const ItemDefinition &i); +void push_item_definition(lua_State *L, const ItemDefinition &i); +void push_item_definition_full(lua_State *L, const ItemDefinition &i); -void read_object_properties (lua_State *L, int index, - ServerActiveObject *sao, - ObjectProperties *prop, - IItemDefManager *idef); -void push_object_properties (lua_State *L, - ObjectProperties *prop); +void read_object_properties(lua_State *L, int index, ServerActiveObject *sao, + ObjectProperties *prop, IItemDefManager *idef); +void push_object_properties(lua_State *L, ObjectProperties *prop); -void push_inventory (lua_State *L, - Inventory *inventory); - -void push_inventory_list (lua_State *L, - Inventory *inv, - const char *name); -void read_inventory_list (lua_State *L, int tableindex, - Inventory *inv, const char *name, - Server *srv, int forcesize=-1); +void push_inventory(lua_State *L, Inventory *inventory); -MapNode readnode (lua_State *L, int index, - const NodeDefManager *ndef); -void pushnode (lua_State *L, const MapNode &n, - const NodeDefManager *ndef); +void push_inventory_list(lua_State *L, Inventory *inv, const char *name); +void read_inventory_list(lua_State *L, int tableindex, Inventory *inv, const char *name, + Server *srv, int forcesize = -1); +MapNode readnode(lua_State *L, int index, const NodeDefManager *ndef); +void pushnode(lua_State *L, const MapNode &n, const NodeDefManager *ndef); -void read_groups (lua_State *L, int index, - ItemGroupList &result); +void read_groups(lua_State *L, int index, ItemGroupList &result); -void push_groups (lua_State *L, - const ItemGroupList &groups); +void push_groups(lua_State *L, const ItemGroupList &groups); -//TODO rename to "read_enum_field" -int getenumfield (lua_State *L, int table, - const char *fieldname, - const EnumString *spec, - int default_); +// TODO rename to "read_enum_field" +int getenumfield(lua_State *L, int table, const char *fieldname, const EnumString *spec, + int default_); -bool getflagsfield (lua_State *L, int table, - const char *fieldname, - FlagDesc *flagdesc, - u32 *flags, u32 *flagmask); +bool getflagsfield(lua_State *L, int table, const char *fieldname, FlagDesc *flagdesc, + u32 *flags, u32 *flagmask); -bool read_flags (lua_State *L, int index, - FlagDesc *flagdesc, - u32 *flags, u32 *flagmask); +bool read_flags(lua_State *L, int index, FlagDesc *flagdesc, u32 *flags, u32 *flagmask); -void push_flags_string (lua_State *L, FlagDesc *flagdesc, - u32 flags, u32 flagmask); +void push_flags_string(lua_State *L, FlagDesc *flagdesc, u32 flags, u32 flagmask); -u32 read_flags_table (lua_State *L, int table, - FlagDesc *flagdesc, u32 *flagmask); +u32 read_flags_table(lua_State *L, int table, FlagDesc *flagdesc, u32 *flagmask); -void push_items (lua_State *L, - const std::vector &items); +void push_items(lua_State *L, const std::vector &items); -std::vector read_items (lua_State *L, - int index, - Server* srv); +std::vector read_items(lua_State *L, int index, Server *srv); -void push_soundspec (lua_State *L, - const SimpleSoundSpec &spec); +void push_soundspec(lua_State *L, const SimpleSoundSpec &spec); -bool string_to_enum (const EnumString *spec, - int &result, - const std::string &str); +bool string_to_enum(const EnumString *spec, int &result, const std::string &str); -bool read_noiseparams (lua_State *L, int index, - NoiseParams *np); -void push_noiseparams (lua_State *L, NoiseParams *np); +bool read_noiseparams(lua_State *L, int index, NoiseParams *np); +void push_noiseparams(lua_State *L, NoiseParams *np); -void luaentity_get (lua_State *L,u16 id); +void luaentity_get(lua_State *L, u16 id); -bool push_json_value (lua_State *L, - const Json::Value &value, - int nullindex); -void read_json_value (lua_State *L, Json::Value &root, - int index, u8 recursion = 0); +bool push_json_value(lua_State *L, const Json::Value &value, int nullindex); +void read_json_value(lua_State *L, Json::Value &root, int index, u8 recursion = 0); /*! * Pushes a Lua `pointed_thing` to the given Lua stack. * \param csm If true, a client side pointed thing is pushed * \param hitpoint If true, the exact pointing location is also pushed */ -void push_pointed_thing(lua_State *L, const PointedThing &pointed, bool csm = - false, bool hitpoint = false); +void push_pointed_thing(lua_State *L, const PointedThing &pointed, bool csm = false, + bool hitpoint = false); -void push_objectRef (lua_State *L, const u16 id); +void push_objectRef(lua_State *L, const u16 id); -void read_hud_element (lua_State *L, HudElement *elem); +void read_hud_element(lua_State *L, HudElement *elem); -void push_hud_element (lua_State *L, HudElement *elem); +void push_hud_element(lua_State *L, HudElement *elem); -HudElementStat read_hud_change (lua_State *L, HudElement *elem, void **value); +HudElementStat read_hud_change(lua_State *L, HudElement *elem, void **value); -void push_collision_move_result(lua_State *L, const collisionMoveResult &res); +void push_collision_move_result(lua_State *L, const collisionMoveResult &res); diff --git a/src/script/common/c_converter.cpp b/src/script/common/c_converter.cpp index eb6ab5331..349f148d3 100644 --- a/src/script/common/c_converter.cpp +++ b/src/script/common/c_converter.cpp @@ -30,27 +30,27 @@ extern "C" { #include "constants.h" #include - -#define CHECK_TYPE(index, name, type) { \ - int t = lua_type(L, (index)); \ - if (t != (type)) { \ - throw LuaError(std::string("Invalid ") + (name) + \ - " (expected " + lua_typename(L, (type)) + \ - " got " + lua_typename(L, t) + ")."); \ - } \ +#define CHECK_TYPE(index, name, type) \ + { \ + int t = lua_type(L, (index)); \ + if (t != (type)) { \ + throw LuaError(std::string("Invalid ") + (name) + \ + " (expected " + lua_typename(L, (type)) + \ + " got " + lua_typename(L, t) + ")."); \ + } \ + } +#define CHECK_POS_COORD(name) \ + CHECK_TYPE(-1, "position coordinate '" name "'", LUA_TNUMBER) +#define CHECK_FLOAT_RANGE(value, name) \ + if (value < F1000_MIN || value > F1000_MAX) { \ + std::ostringstream error_text; \ + error_text << "Invalid float vector dimension range '" name "' " \ + << "(expected " << F1000_MIN << " < " name " < " << F1000_MAX \ + << " got " << value << ")." << std::endl; \ + throw LuaError(error_text.str()); \ } -#define CHECK_POS_COORD(name) CHECK_TYPE(-1, "position coordinate '" name "'", LUA_TNUMBER) -#define CHECK_FLOAT_RANGE(value, name) \ -if (value < F1000_MIN || value > F1000_MAX) { \ - std::ostringstream error_text; \ - error_text << "Invalid float vector dimension range '" name "' " << \ - "(expected " << F1000_MIN << " < " name " < " << F1000_MAX << \ - " got " << value << ")." << std::endl; \ - throw LuaError(error_text.str()); \ -} #define CHECK_POS_TAB(index) CHECK_TYPE(index, "position", LUA_TTABLE) - void push_float_string(lua_State *L, float value) { std::stringstream ss; @@ -360,7 +360,7 @@ bool is_color_table(lua_State *L, int index) aabb3f read_aabb3f(lua_State *L, int index, f32 scale) { aabb3f box; - if(lua_istable(L, index)){ + if (lua_istable(L, index)) { lua_rawgeti(L, index, 1); box.MinEdge.X = lua_tonumber(L, -1) * scale; lua_pop(L, 1); @@ -404,22 +404,22 @@ void push_aabb3f(lua_State *L, aabb3f box) std::vector read_aabb3f_vector(lua_State *L, int index, f32 scale) { std::vector boxes; - if(lua_istable(L, index)){ + if (lua_istable(L, index)) { int n = lua_objlen(L, index); // Check if it's a single box or a list of boxes bool possibly_single_box = (n == 6); - for(int i = 1; i <= n && possibly_single_box; i++){ + for (int i = 1; i <= n && possibly_single_box; i++) { lua_rawgeti(L, index, i); - if(!lua_isnumber(L, -1)) + if (!lua_isnumber(L, -1)) possibly_single_box = false; lua_pop(L, 1); } - if(possibly_single_box){ + if (possibly_single_box) { // Read a single box boxes.push_back(read_aabb3f(L, index, scale)); } else { // Read a list of boxes - for(int i = 1; i <= n; i++){ + for (int i = 1; i <= n; i++) { lua_rawgeti(L, index, i); boxes.push_back(read_aabb3f(L, -1, scale)); lua_pop(L, 1); @@ -487,8 +487,8 @@ bool check_field_or_nil(lua_State *L, int index, int type, const char *fieldname // Types mismatch. Log unique line. std::string backtrace = std::string("Invalid field ") + fieldname + - " (expected " + lua_typename(L, type) + - " got " + lua_typename(L, t) + ").\n" + script_get_backtrace(L); + " (expected " + lua_typename(L, type) + " got " + + lua_typename(L, t) + ").\n" + script_get_backtrace(L); u64 hash = murmur_hash_64_ua(backtrace.data(), backtrace.length(), 0xBADBABE); if (warned_msgs.find(hash) == warned_msgs.end()) { @@ -499,8 +499,7 @@ bool check_field_or_nil(lua_State *L, int index, int type, const char *fieldname return false; } -bool getstringfield(lua_State *L, int table, - const char *fieldname, std::string &result) +bool getstringfield(lua_State *L, int table, const char *fieldname, std::string &result) { lua_getfield(L, table, fieldname); bool got = false; @@ -517,8 +516,7 @@ bool getstringfield(lua_State *L, int table, return got; } -bool getfloatfield(lua_State *L, int table, - const char *fieldname, float &result) +bool getfloatfield(lua_State *L, int table, const char *fieldname, float &result) { lua_getfield(L, table, fieldname); bool got = false; @@ -531,13 +529,12 @@ bool getfloatfield(lua_State *L, int table, return got; } -bool getboolfield(lua_State *L, int table, - const char *fieldname, bool &result) +bool getboolfield(lua_State *L, int table, const char *fieldname, bool &result) { lua_getfield(L, table, fieldname); bool got = false; - if (check_field_or_nil(L, -1, LUA_TBOOLEAN, fieldname)){ + if (check_field_or_nil(L, -1, LUA_TBOOLEAN, fieldname)) { result = lua_toboolean(L, -1); got = true; } @@ -556,93 +553,82 @@ size_t getstringlistfield(lua_State *L, int table, const char *fieldname, return num_strings_read; } -std::string getstringfield_default(lua_State *L, int table, - const char *fieldname, const std::string &default_) +std::string getstringfield_default(lua_State *L, int table, const char *fieldname, + const std::string &default_) { std::string result = default_; getstringfield(L, table, fieldname, result); return result; } -int getintfield_default(lua_State *L, int table, - const char *fieldname, int default_) +int getintfield_default(lua_State *L, int table, const char *fieldname, int default_) { int result = default_; getintfield(L, table, fieldname, result); return result; } -float getfloatfield_default(lua_State *L, int table, - const char *fieldname, float default_) +float getfloatfield_default( + lua_State *L, int table, const char *fieldname, float default_) { float result = default_; getfloatfield(L, table, fieldname, result); return result; } -bool getboolfield_default(lua_State *L, int table, - const char *fieldname, bool default_) +bool getboolfield_default(lua_State *L, int table, const char *fieldname, bool default_) { bool result = default_; getboolfield(L, table, fieldname, result); return result; } -v3s16 getv3s16field_default(lua_State *L, int table, - const char *fieldname, v3s16 default_) +v3s16 getv3s16field_default( + lua_State *L, int table, const char *fieldname, v3s16 default_) { getv3intfield(L, table, fieldname, default_); return default_; } -void setstringfield(lua_State *L, int table, - const char *fieldname, const std::string &value) +void setstringfield( + lua_State *L, int table, const char *fieldname, const std::string &value) { lua_pushlstring(L, value.c_str(), value.length()); - if(table < 0) + if (table < 0) table -= 1; lua_setfield(L, table, fieldname); } -void setintfield(lua_State *L, int table, - const char *fieldname, int value) +void setintfield(lua_State *L, int table, const char *fieldname, int value) { lua_pushinteger(L, value); - if(table < 0) + if (table < 0) table -= 1; lua_setfield(L, table, fieldname); } -void setfloatfield(lua_State *L, int table, - const char *fieldname, float value) +void setfloatfield(lua_State *L, int table, const char *fieldname, float value) { lua_pushnumber(L, value); - if(table < 0) + if (table < 0) table -= 1; lua_setfield(L, table, fieldname); } -void setboolfield(lua_State *L, int table, - const char *fieldname, bool value) +void setboolfield(lua_State *L, int table, const char *fieldname, bool value) { lua_pushboolean(L, value); - if(table < 0) + if (table < 0) table -= 1; lua_setfield(L, table, fieldname); } - //// //// Array table slices //// -size_t write_array_slice_float( - lua_State *L, - int table_index, - float *data, - v3u16 data_size, - v3u16 slice_offset, - v3u16 slice_size) +size_t write_array_slice_float(lua_State *L, int table_index, float *data, + v3u16 data_size, v3u16 slice_offset, v3u16 slice_size) { v3u16 pmin, pmax(data_size); @@ -669,25 +655,19 @@ size_t write_array_slice_float( u32 elem_index = 1; for (u32 z = pmin.Z; z != pmax.Z; z++) - for (u32 y = pmin.Y; y != pmax.Y; y++) - for (u32 x = pmin.X; x != pmax.X; x++) { - u32 i = z * zstride + y * ystride + x; - lua_pushnumber(L, data[i]); - lua_rawseti(L, table_index, elem_index); - elem_index++; - } + for (u32 y = pmin.Y; y != pmax.Y; y++) + for (u32 x = pmin.X; x != pmax.X; x++) { + u32 i = z * zstride + y * ystride + x; + lua_pushnumber(L, data[i]); + lua_rawseti(L, table_index, elem_index); + elem_index++; + } return elem_index - 1; } - -size_t write_array_slice_u16( - lua_State *L, - int table_index, - u16 *data, - v3u16 data_size, - v3u16 slice_offset, - v3u16 slice_size) +size_t write_array_slice_u16(lua_State *L, int table_index, u16 *data, v3u16 data_size, + v3u16 slice_offset, v3u16 slice_size) { v3u16 pmin, pmax(data_size); @@ -714,13 +694,13 @@ size_t write_array_slice_u16( u32 elem_index = 1; for (u32 z = pmin.Z; z != pmax.Z; z++) - for (u32 y = pmin.Y; y != pmax.Y; y++) - for (u32 x = pmin.X; x != pmax.X; x++) { - u32 i = z * zstride + y * ystride + x; - lua_pushinteger(L, data[i]); - lua_rawseti(L, table_index, elem_index); - elem_index++; - } + for (u32 y = pmin.Y; y != pmax.Y; y++) + for (u32 x = pmin.X; x != pmax.X; x++) { + u32 i = z * zstride + y * ystride + x; + lua_pushinteger(L, data[i]); + lua_rawseti(L, table_index, elem_index); + elem_index++; + } return elem_index - 1; } diff --git a/src/script/common/c_converter.h b/src/script/common/c_converter.h index a4a7079fd..16d21db3b 100644 --- a/src/script/common/c_converter.h +++ b/src/script/common/c_converter.h @@ -17,7 +17,6 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ - /******************************************************************************/ /******************************************************************************/ /* WARNING!!!! do NOT add this header in any include file or any code file */ @@ -36,24 +35,21 @@ extern "C" { #include } -std::string getstringfield_default(lua_State *L, int table, - const char *fieldname, const std::string &default_); -bool getboolfield_default(lua_State *L, int table, - const char *fieldname, bool default_); -float getfloatfield_default(lua_State *L, int table, - const char *fieldname, float default_); -int getintfield_default(lua_State *L, int table, - const char *fieldname, int default_); +std::string getstringfield_default(lua_State *L, int table, const char *fieldname, + const std::string &default_); +bool getboolfield_default(lua_State *L, int table, const char *fieldname, bool default_); +float getfloatfield_default( + lua_State *L, int table, const char *fieldname, float default_); +int getintfield_default(lua_State *L, int table, const char *fieldname, int default_); bool check_field_or_nil(lua_State *L, int index, int type, const char *fieldname); -template -bool getintfield(lua_State *L, int table, - const char *fieldname, T &result) +template +bool getintfield(lua_State *L, int table, const char *fieldname, T &result) { lua_getfield(L, table, fieldname); bool got = false; - if (check_field_or_nil(L, -1, LUA_TNUMBER, fieldname)){ + if (check_field_or_nil(L, -1, LUA_TNUMBER, fieldname)) { result = lua_tointeger(L, -1); got = true; } @@ -61,9 +57,8 @@ bool getintfield(lua_State *L, int table, return got; } -template -bool getv3intfield(lua_State *L, int index, - const char *fieldname, T &result) +template +bool getv3intfield(lua_State *L, int index, const char *fieldname, T &result) { lua_getfield(L, index, fieldname); bool got = false; @@ -76,65 +71,54 @@ bool getv3intfield(lua_State *L, int index, return got; } -v3s16 getv3s16field_default(lua_State *L, int table, - const char *fieldname, v3s16 default_); -bool getstringfield(lua_State *L, int table, - const char *fieldname, std::string &result); -size_t getstringlistfield(lua_State *L, int table, - const char *fieldname, - std::vector *result); -void read_groups(lua_State *L, int index, - std::unordered_map &result); -bool getboolfield(lua_State *L, int table, - const char *fieldname, bool &result); -bool getfloatfield(lua_State *L, int table, - const char *fieldname, float &result); +v3s16 getv3s16field_default( + lua_State *L, int table, const char *fieldname, v3s16 default_); +bool getstringfield(lua_State *L, int table, const char *fieldname, std::string &result); +size_t getstringlistfield(lua_State *L, int table, const char *fieldname, + std::vector *result); +void read_groups(lua_State *L, int index, std::unordered_map &result); +bool getboolfield(lua_State *L, int table, const char *fieldname, bool &result); +bool getfloatfield(lua_State *L, int table, const char *fieldname, float &result); -void setstringfield(lua_State *L, int table, - const char *fieldname, const std::string &value); -void setintfield(lua_State *L, int table, - const char *fieldname, int value); -void setfloatfield(lua_State *L, int table, - const char *fieldname, float value); -void setboolfield(lua_State *L, int table, - const char *fieldname, bool value); +void setstringfield( + lua_State *L, int table, const char *fieldname, const std::string &value); +void setintfield(lua_State *L, int table, const char *fieldname, int value); +void setfloatfield(lua_State *L, int table, const char *fieldname, float value); +void setboolfield(lua_State *L, int table, const char *fieldname, bool value); -v3f checkFloatPos (lua_State *L, int index); -v3f check_v3f (lua_State *L, int index); -v3s16 check_v3s16 (lua_State *L, int index); +v3f checkFloatPos(lua_State *L, int index); +v3f check_v3f(lua_State *L, int index); +v3s16 check_v3s16(lua_State *L, int index); -v3f read_v3f (lua_State *L, int index); -v2f read_v2f (lua_State *L, int index); -v2s16 read_v2s16 (lua_State *L, int index); -v2s32 read_v2s32 (lua_State *L, int index); -video::SColor read_ARGB8 (lua_State *L, int index); -bool read_color (lua_State *L, int index, - video::SColor *color); -bool is_color_table (lua_State *L, int index); +v3f read_v3f(lua_State *L, int index); +v2f read_v2f(lua_State *L, int index); +v2s16 read_v2s16(lua_State *L, int index); +v2s32 read_v2s32(lua_State *L, int index); +video::SColor read_ARGB8(lua_State *L, int index); +bool read_color(lua_State *L, int index, video::SColor *color); +bool is_color_table(lua_State *L, int index); -aabb3f read_aabb3f (lua_State *L, int index, f32 scale); -v3s16 read_v3s16 (lua_State *L, int index); -std::vector read_aabb3f_vector (lua_State *L, int index, f32 scale); -size_t read_stringlist (lua_State *L, int index, - std::vector *result); +aabb3f read_aabb3f(lua_State *L, int index, f32 scale); +v3s16 read_v3s16(lua_State *L, int index); +std::vector read_aabb3f_vector(lua_State *L, int index, f32 scale); +size_t read_stringlist(lua_State *L, int index, std::vector *result); -void push_float_string (lua_State *L, float value); -void push_v3_float_string(lua_State *L, v3f p); -void push_v2_float_string(lua_State *L, v2f p); -void push_v2s16 (lua_State *L, v2s16 p); -void push_v2s32 (lua_State *L, v2s32 p); -void push_v3s16 (lua_State *L, v3s16 p); -void push_aabb3f (lua_State *L, aabb3f box); -void push_ARGB8 (lua_State *L, video::SColor color); -void pushFloatPos (lua_State *L, v3f p); -void push_v3f (lua_State *L, v3f p); -void push_v2f (lua_State *L, v2f p); +void push_float_string(lua_State *L, float value); +void push_v3_float_string(lua_State *L, v3f p); +void push_v2_float_string(lua_State *L, v2f p); +void push_v2s16(lua_State *L, v2s16 p); +void push_v2s32(lua_State *L, v2s32 p); +void push_v3s16(lua_State *L, v3s16 p); +void push_aabb3f(lua_State *L, aabb3f box); +void push_ARGB8(lua_State *L, video::SColor color); +void pushFloatPos(lua_State *L, v3f p); +void push_v3f(lua_State *L, v3f p); +void push_v2f(lua_State *L, v2f p); -void warn_if_field_exists(lua_State *L, int table, - const char *fieldname, - const std::string &message); +void warn_if_field_exists(lua_State *L, int table, const char *fieldname, + const std::string &message); size_t write_array_slice_float(lua_State *L, int table_index, float *data, - v3u16 data_size, v3u16 slice_offset, v3u16 slice_size); -size_t write_array_slice_u16(lua_State *L, int table_index, u16 *data, - v3u16 data_size, v3u16 slice_offset, v3u16 slice_size); + v3u16 data_size, v3u16 slice_offset, v3u16 slice_size); +size_t write_array_slice_u16(lua_State *L, int table_index, u16 *data, v3u16 data_size, + v3u16 slice_offset, v3u16 slice_size); diff --git a/src/script/common/c_internal.cpp b/src/script/common/c_internal.cpp index 6df1f8b7b..19a874f63 100644 --- a/src/script/common/c_internal.cpp +++ b/src/script/common/c_internal.cpp @@ -35,13 +35,13 @@ std::string script_get_backtrace(lua_State *L) int script_exception_wrapper(lua_State *L, lua_CFunction f) { try { - return f(L); // Call wrapped function and return result. - } catch (const char *s) { // Catch and convert exceptions. + return f(L); // Call wrapped function and return result. + } catch (const char *s) { // Catch and convert exceptions. lua_pushstring(L, s); } catch (std::exception &e) { lua_pushstring(L, e.what()); } - return lua_error(L); // Rethrow as a Lua error. + return lua_error(L); // Rethrow as a Lua error. } /* @@ -85,15 +85,15 @@ void script_error(lua_State *L, int pcall_result, const char *mod, const char *f err_descr = ""; char buf[256]; - porting::mt_snprintf(buf, sizeof(buf), "%s error from mod '%s' in callback %s(): ", - err_type, mod, fxn); + porting::mt_snprintf(buf, sizeof(buf), + "%s error from mod '%s' in callback %s(): ", err_type, mod, fxn); std::string err_msg(buf); err_msg += err_descr; if (pcall_result == LUA_ERRMEM) { - err_msg += "\nCurrent Lua memory usage: " - + itos(lua_gc(L, LUA_GCCOUNT, 0) >> 10) + " MB"; + err_msg += "\nCurrent Lua memory usage: " + + itos(lua_gc(L, LUA_GCCOUNT, 0) >> 10) + " MB"; } throw LuaError(err_msg); @@ -105,8 +105,8 @@ void script_error(lua_State *L, int pcall_result, const char *mod, const char *f // - runs the callbacks // - replaces the table and arguments with the return value, // computed depending on mode -void script_run_callbacks_f(lua_State *L, int nargs, - RunCallbacksMode mode, const char *fxn) +void script_run_callbacks_f( + lua_State *L, int nargs, RunCallbacksMode mode, const char *fxn) { FATAL_ERROR_IF(lua_gettop(L) < nargs + 1, "Not enough arguments"); @@ -122,7 +122,7 @@ void script_run_callbacks_f(lua_State *L, int nargs, lua_insert(L, error_handler + 1); // Insert mode after table - lua_pushnumber(L, (int) mode); + lua_pushnumber(L, (int)mode); lua_insert(L, error_handler + 3); // Stack now looks like this: @@ -135,8 +135,8 @@ void script_run_callbacks_f(lua_State *L, int nargs, lua_remove(L, error_handler); } -static void script_log(lua_State *L, const std::string &message, - std::ostream &log_to, bool do_error, int stack_depth) +static void script_log(lua_State *L, const std::string &message, std::ostream &log_to, + bool do_error, int stack_depth) { lua_Debug ar; diff --git a/src/script/common/c_internal.h b/src/script/common/c_internal.h index 442546332..677194453 100644 --- a/src/script/common/c_internal.h +++ b/src/script/common/c_internal.h @@ -34,7 +34,6 @@ extern "C" { #include "config.h" #include "common/c_types.h" - /* Define our custom indices into the Lua registry table. @@ -45,15 +44,15 @@ extern "C" { so we can use numeric indices freely. */ #ifdef LUA_RIDX_LAST -#define CUSTOM_RIDX_BASE ((LUA_RIDX_LAST)+1) +#define CUSTOM_RIDX_BASE ((LUA_RIDX_LAST) + 1) #else #define CUSTOM_RIDX_BASE 1 #endif -#define CUSTOM_RIDX_SCRIPTAPI (CUSTOM_RIDX_BASE) -#define CUSTOM_RIDX_GLOBALS_BACKUP (CUSTOM_RIDX_BASE + 1) -#define CUSTOM_RIDX_CURRENT_MOD_NAME (CUSTOM_RIDX_BASE + 2) -#define CUSTOM_RIDX_BACKTRACE (CUSTOM_RIDX_BASE + 3) +#define CUSTOM_RIDX_SCRIPTAPI (CUSTOM_RIDX_BASE) +#define CUSTOM_RIDX_GLOBALS_BACKUP (CUSTOM_RIDX_BASE + 1) +#define CUSTOM_RIDX_CURRENT_MOD_NAME (CUSTOM_RIDX_BASE + 2) +#define CUSTOM_RIDX_BACKTRACE (CUSTOM_RIDX_BASE + 3) // Determine if CUSTOM_RIDX_SCRIPTAPI will hold a light or full userdata #if defined(__aarch64__) && USE_LUAJIT @@ -65,17 +64,18 @@ extern "C" { #endif // Pushes the error handler onto the stack and returns its index -#define PUSH_ERROR_HANDLER(L) \ +#define PUSH_ERROR_HANDLER(L) \ (lua_rawgeti((L), LUA_REGISTRYINDEX, CUSTOM_RIDX_BACKTRACE), lua_gettop((L))) -#define PCALL_RESL(L, RES) { \ - int result_ = (RES); \ - if (result_ != 0) { \ - script_error((L), result_, NULL, __FUNCTION__); \ - } \ -} +#define PCALL_RESL(L, RES) \ + { \ + int result_ = (RES); \ + if (result_ != 0) { \ + script_error((L), result_, NULL, __FUNCTION__); \ + } \ + } -#define script_run_callbacks(L, nargs, mode) \ +#define script_run_callbacks(L, nargs, mode) \ script_run_callbacks_f((L), (nargs), (mode), __FUNCTION__) // What script_run_callbacks does with the return values of callbacks. @@ -111,8 +111,7 @@ enum RunCallbacksMode std::string script_get_backtrace(lua_State *L); int script_exception_wrapper(lua_State *L, lua_CFunction f); void script_error(lua_State *L, int pcall_result, const char *mod, const char *fxn); -void script_run_callbacks_f(lua_State *L, int nargs, - RunCallbacksMode mode, const char *fxn); +void script_run_callbacks_f( + lua_State *L, int nargs, RunCallbacksMode mode, const char *fxn); -void log_deprecated(lua_State *L, const std::string &message, - int stack_depth=1); +void log_deprecated(lua_State *L, const std::string &message, int stack_depth = 1); diff --git a/src/script/common/c_types.cpp b/src/script/common/c_types.cpp index e832ff2ab..93bb5bd70 100644 --- a/src/script/common/c_types.cpp +++ b/src/script/common/c_types.cpp @@ -23,12 +23,10 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "common/c_internal.h" #include "itemdef.h" - -struct EnumString es_ItemType[] = - { +struct EnumString es_ItemType[] = { {ITEM_NONE, "none"}, {ITEM_NODE, "node"}, {ITEM_CRAFT, "craft"}, {ITEM_TOOL, "tool"}, {0, NULL}, - }; +}; diff --git a/src/script/common/c_types.h b/src/script/common/c_types.h index 86bfb0b6b..860bfaaa8 100644 --- a/src/script/common/c_types.h +++ b/src/script/common/c_types.h @@ -38,10 +38,9 @@ class StackUnroller private: lua_State *m_lua; int m_original_top; + public: - StackUnroller(lua_State *L): - m_lua(L), - m_original_top(-1) + StackUnroller(lua_State *L) : m_lua(L), m_original_top(-1) { m_original_top = lua_gettop(m_lua); // store stack height } @@ -57,5 +56,4 @@ public: LuaError(const std::string &s) : ModError(s) {} }; - extern EnumString es_ItemType[]; diff --git a/src/script/cpp_api/s_async.cpp b/src/script/cpp_api/s_async.cpp index 5f1f9297e..d0e520ae9 100644 --- a/src/script/cpp_api/s_async.cpp +++ b/src/script/cpp_api/s_async.cpp @@ -42,7 +42,6 @@ AsyncEngine::~AsyncEngine() workerThread->stop(); } - // Wake up all threads for (std::vector::iterator it = workerThreads.begin(); it != workerThreads.end(); ++it) { @@ -77,16 +76,16 @@ void AsyncEngine::initialize(unsigned int numEngines) initDone = true; for (unsigned int i = 0; i < numEngines; i++) { - AsyncWorkerThread *toAdd = new AsyncWorkerThread(this, - std::string("AsyncWorker-") + itos(i)); + AsyncWorkerThread *toAdd = new AsyncWorkerThread( + this, std::string("AsyncWorker-") + itos(i)); workerThreads.push_back(toAdd); toAdd->start(); } } /******************************************************************************/ -unsigned int AsyncEngine::queueAsyncJob(const std::string &func, - const std::string ¶ms) +unsigned int AsyncEngine::queueAsyncJob( + const std::string &func, const std::string ¶ms) { jobQueueMutex.lock(); LuaJobInfo toAdd; @@ -158,7 +157,8 @@ void AsyncEngine::step(lua_State *L) } /******************************************************************************/ -void AsyncEngine::pushFinishedJobs(lua_State* L) { +void AsyncEngine::pushFinishedJobs(lua_State *L) +{ // Result Table MutexAutoLock l(resultQueueMutex); @@ -170,7 +170,7 @@ void AsyncEngine::pushFinishedJobs(lua_State* L) { LuaJobInfo jobDone = resultQueue.front(); resultQueue.pop_front(); - lua_createtable(L, 0, 2); // Pre-allocate space for two map fields + lua_createtable(L, 0, 2); // Pre-allocate space for two map fields int top_lvl2 = lua_gettop(L); lua_pushstring(L, "jobid"); @@ -179,7 +179,7 @@ void AsyncEngine::pushFinishedJobs(lua_State* L) { lua_pushstring(L, "retval"); lua_pushlstring(L, jobDone.serializedResult.data(), - jobDone.serializedResult.size()); + jobDone.serializedResult.size()); lua_settable(L, top_lvl2); lua_rawseti(L, top, index++); @@ -187,7 +187,7 @@ void AsyncEngine::pushFinishedJobs(lua_State* L) { } /******************************************************************************/ -void AsyncEngine::prepareEnvironment(lua_State* L, int top) +void AsyncEngine::prepareEnvironment(lua_State *L, int top) { for (StateInitializer &stateInitializer : stateInitializers) { stateInitializer(L, top); @@ -195,11 +195,10 @@ void AsyncEngine::prepareEnvironment(lua_State* L, int top) } /******************************************************************************/ -AsyncWorkerThread::AsyncWorkerThread(AsyncEngine* jobDispatcher, - const std::string &name) : - Thread(name), - ScriptApiBase(ScriptingType::Async), - jobDispatcher(jobDispatcher) +AsyncWorkerThread::AsyncWorkerThread( + AsyncEngine *jobDispatcher, const std::string &name) : + Thread(name), + ScriptApiBase(ScriptingType::Async), jobDispatcher(jobDispatcher) { lua_State *L = getStack(); @@ -221,7 +220,7 @@ AsyncWorkerThread::~AsyncWorkerThread() } /******************************************************************************/ -void* AsyncWorkerThread::run() +void *AsyncWorkerThread::run() { lua_State *L = getStack(); @@ -229,8 +228,8 @@ void* AsyncWorkerThread::run() try { loadScript(script); } catch (const ModError &e) { - errorstream << "Execution of async base environment failed: " - << e.what() << std::endl; + errorstream << "Execution of async base environment failed: " << e.what() + << std::endl; FATAL_ERROR("Execution of async base environment failed"); } @@ -258,11 +257,9 @@ void* AsyncWorkerThread::run() luaL_checktype(L, -1, LUA_TFUNCTION); // Call it - lua_pushlstring(L, - toProcess.serializedFunction.data(), + lua_pushlstring(L, toProcess.serializedFunction.data(), toProcess.serializedFunction.size()); - lua_pushlstring(L, - toProcess.serializedParams.data(), + lua_pushlstring(L, toProcess.serializedParams.data(), toProcess.serializedParams.size()); int result = lua_pcall(L, 2, 1, error_handler); @@ -276,14 +273,13 @@ void* AsyncWorkerThread::run() toProcess.serializedResult = std::string(retval, length); } - lua_pop(L, 1); // Pop retval + lua_pop(L, 1); // Pop retval // Put job result jobDispatcher->putJobResult(toProcess); } - lua_pop(L, 2); // Pop core and error handler + lua_pop(L, 2); // Pop core and error handler return 0; } - diff --git a/src/script/cpp_api/s_async.h b/src/script/cpp_api/s_async.h index b1f4bf45f..1dc4145ba 100644 --- a/src/script/cpp_api/s_async.h +++ b/src/script/cpp_api/s_async.h @@ -31,7 +31,6 @@ with this program; if not, write to the Free Software Foundation, Inc., // Forward declarations class AsyncEngine; - // Declarations // Data required to queue a job @@ -52,9 +51,10 @@ struct LuaJobInfo }; // Asynchronous working environment -class AsyncWorkerThread : public Thread, public ScriptApiBase { +class AsyncWorkerThread : public Thread, public ScriptApiBase +{ public: - AsyncWorkerThread(AsyncEngine* jobDispatcher, const std::string &name); + AsyncWorkerThread(AsyncEngine *jobDispatcher, const std::string &name); virtual ~AsyncWorkerThread(); void *run(); @@ -64,9 +64,11 @@ private: }; // Asynchornous thread and job management -class AsyncEngine { +class AsyncEngine +{ friend class AsyncWorkerThread; typedef void (*StateInitializer)(lua_State *L, int top); + public: AsyncEngine() = default; ~AsyncEngine(); @@ -125,7 +127,7 @@ protected: * @param L Lua stack to initialize * @param top Stack position */ - void prepareEnvironment(lua_State* L, int top); + void prepareEnvironment(lua_State *L, int top); private: // Variable locking the engine against further modification @@ -149,7 +151,7 @@ private: std::deque resultQueue; // List of current worker threads - std::vector workerThreads; + std::vector workerThreads; // Counter semaphore for job dispatching Semaphore jobQueueCounter; diff --git a/src/script/cpp_api/s_base.cpp b/src/script/cpp_api/s_base.cpp index 1d62d8b65..72774f6ca 100644 --- a/src/script/cpp_api/s_base.cpp +++ b/src/script/cpp_api/s_base.cpp @@ -32,11 +32,10 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "client/client.h" #endif - extern "C" { #include "lualib.h" #if USE_LUAJIT - #include "luajit.h" +#include "luajit.h" #endif } @@ -45,14 +44,13 @@ extern "C" { #include "script/common/c_content.h" #include - class ModNameStorer { private: lua_State *L; + public: - ModNameStorer(lua_State *L_, const std::string &mod_name): - L(L_) + ModNameStorer(lua_State *L_, const std::string &mod_name) : L(L_) { // Store current mod name in registry lua_pushstring(L, mod_name.c_str()); @@ -66,13 +64,11 @@ public: } }; - /* ScriptApiBase */ -ScriptApiBase::ScriptApiBase(ScriptingType type): - m_type(type) +ScriptApiBase::ScriptApiBase(ScriptingType type) : m_type(type) { #ifdef SCRIPTAPI_LOCK_DEBUG m_lock_recursion_count = 0; @@ -86,7 +82,7 @@ ScriptApiBase::ScriptApiBase(ScriptingType type): /*if (m_type == ScriptingType::Client) clientOpenLibs(m_luastack); else*/ - luaL_openlibs(m_luastack); + luaL_openlibs(m_luastack); // Make the ScriptApiBase* accessible to ModApiBase #if INDIRECT_SCRIPTAPI_RIDX @@ -105,7 +101,7 @@ ScriptApiBase::ScriptApiBase(ScriptingType type): // If we are using LuaJIT add a C++ wrapper function to catch // exceptions thrown in Lua -> C++ calls #if USE_LUAJIT - lua_pushlightuserdata(m_luastack, (void*) script_exception_wrapper); + lua_pushlightuserdata(m_luastack, (void *)script_exception_wrapper); luaJIT_setmode(m_luastack, -1, LUAJIT_MODE_WRAPCFUNC | LUAJIT_MODE_ON); lua_pop(m_luastack, 1); #endif @@ -136,7 +132,7 @@ int ScriptApiBase::luaPanic(lua_State *L) { std::ostringstream oss; oss << "LUA PANIC: unprotected error in call to Lua API (" - << readParam(L, -1) << ")"; + << readParam(L, -1) << ")"; FATAL_ERROR(oss.str().c_str()); // NOTREACHED return 0; @@ -145,26 +141,25 @@ int ScriptApiBase::luaPanic(lua_State *L) void ScriptApiBase::clientOpenLibs(lua_State *L) { static const std::vector> m_libs = { - { "", luaopen_base }, - { LUA_TABLIBNAME, luaopen_table }, - { LUA_OSLIBNAME, luaopen_os }, - { LUA_STRLIBNAME, luaopen_string }, - { LUA_MATHLIBNAME, luaopen_math }, - { LUA_DBLIBNAME, luaopen_debug }, + {"", luaopen_base}, + {LUA_TABLIBNAME, luaopen_table}, + {LUA_OSLIBNAME, luaopen_os}, + {LUA_STRLIBNAME, luaopen_string}, + {LUA_MATHLIBNAME, luaopen_math}, + {LUA_DBLIBNAME, luaopen_debug}, #if USE_LUAJIT - { LUA_JITLIBNAME, luaopen_jit }, + {LUA_JITLIBNAME, luaopen_jit}, #endif }; for (const std::pair &lib : m_libs) { - lua_pushcfunction(L, lib.second); - lua_pushstring(L, lib.first.c_str()); - lua_call(L, 1, 0); + lua_pushcfunction(L, lib.second); + lua_pushstring(L, lib.first.c_str()); + lua_call(L, 1, 0); } } -void ScriptApiBase::loadMod(const std::string &script_path, - const std::string &mod_name) +void ScriptApiBase::loadMod(const std::string &script_path, const std::string &mod_name) { ModNameStorer mod_name_storer(getStack(), mod_name); @@ -191,8 +186,8 @@ void ScriptApiBase::loadScript(const std::string &script_path) if (!error_msg) error_msg = "(error object is not a string)"; lua_pop(L, 2); // Pop error message and error handler - throw ModError("Failed to load and run script from " + - script_path + ":\n" + error_msg); + throw ModError("Failed to load and run script from " + script_path + + ":\n" + error_msg); } lua_pop(L, 1); // Pop error handler } @@ -225,8 +220,8 @@ void ScriptApiBase::loadModFromMemory(const std::string &mod_name) if (!error_msg) error_msg = "(error object is not a string)"; lua_pop(L, 2); // Pop error message and error handler - throw ModError("Failed to load and run mod \"" + - mod_name + "\":\n" + error_msg); + throw ModError("Failed to load and run mod \"" + mod_name + "\":\n" + + error_msg); } lua_pop(L, 1); // Pop error handler } @@ -240,14 +235,13 @@ void ScriptApiBase::loadModFromMemory(const std::string &mod_name) // computed depending on mode // This function must only be called with scriptlock held (i.e. inside of a // code block with SCRIPTAPI_PRECHECKHEADER declared) -void ScriptApiBase::runCallbacksRaw(int nargs, - RunCallbacksMode mode, const char *fxn) +void ScriptApiBase::runCallbacksRaw(int nargs, RunCallbacksMode mode, const char *fxn) { #ifndef SERVER // Hard fail for bad guarded callbacks // Only run callbacks when the scripting enviroment is loaded - FATAL_ERROR_IF(m_type == ScriptingType::Client && - !getClient()->modsLoaded(), fxn); + FATAL_ERROR_IF(m_type == ScriptingType::Client && !getClient()->modsLoaded(), + fxn); #endif #ifdef SCRIPTAPI_LOCK_DEBUG @@ -300,24 +294,25 @@ void ScriptApiBase::scriptError(int result, const char *fxn) void ScriptApiBase::stackDump(std::ostream &o) { int top = lua_gettop(m_luastack); - for (int i = 1; i <= top; i++) { /* repeat for each level */ + for (int i = 1; i <= top; i++) { /* repeat for each level */ int t = lua_type(m_luastack, i); switch (t) { - case LUA_TSTRING: /* strings */ - o << "\"" << readParam(m_luastack, i) << "\""; - break; - case LUA_TBOOLEAN: /* booleans */ - o << (readParam(m_luastack, i) ? "true" : "false"); - break; - case LUA_TNUMBER: /* numbers */ { - char buf[10]; - porting::mt_snprintf(buf, sizeof(buf), "%lf", lua_tonumber(m_luastack, i)); - o << buf; - break; - } - default: /* other values */ - o << lua_typename(m_luastack, t); - break; + case LUA_TSTRING: /* strings */ + o << "\"" << readParam(m_luastack, i) << "\""; + break; + case LUA_TBOOLEAN: /* booleans */ + o << (readParam(m_luastack, i) ? "true" : "false"); + break; + case LUA_TNUMBER: /* numbers */ { + char buf[10]; + porting::mt_snprintf(buf, sizeof(buf), "%lf", + lua_tonumber(m_luastack, i)); + o << buf; + break; + } + default: /* other values */ + o << lua_typename(m_luastack, t); + break; } o << " "; } @@ -334,9 +329,10 @@ void ScriptApiBase::setOriginFromTableRaw(int index, const char *fxn) #ifdef SCRIPTAPI_DEBUG lua_State *L = getStack(); - m_last_run_mod = lua_istable(L, index) ? - getstringfield_default(L, index, "mod_origin", "") : ""; - //printf(">>>> running %s for mod: %s\n", fxn, m_last_run_mod.c_str()); + m_last_run_mod = lua_istable(L, index) ? getstringfield_default(L, index, + "mod_origin", "") + : ""; + // printf(">>>> running %s for mod: %s\n", fxn, m_last_run_mod.c_str()); #endif } @@ -357,7 +353,7 @@ void ScriptApiBase::setOriginFromTableRaw(int index, const char *fxn) void ScriptApiBase::addObjectReference(ServerActiveObject *cobj) { SCRIPTAPI_PRECHECKHEADER - //infostream<<"scriptapi_add_object_reference: id="<getId()<getId() == 0) { ObjectRef::create(L, cobj); @@ -409,12 +404,13 @@ void ScriptApiBase::objectrefGetOrCreate(lua_State *L, push_objectRef(L, cobj->getId()); if (cobj->isGone()) warningstream << "ScriptApiBase::objectrefGetOrCreate(): " - << "Pushing ObjectRef to removed/deactivated object" - << ", this is probably a bug." << std::endl; + << "Pushing ObjectRef to removed/deactivated object" + << ", this is probably a bug." << std::endl; } } -void ScriptApiBase::pushPlayerHPChangeReason(lua_State *L, const PlayerHPChangeReason &reason) +void ScriptApiBase::pushPlayerHPChangeReason( + lua_State *L, const PlayerHPChangeReason &reason) { if (reason.hasLuaReference()) lua_rawgeti(L, LUA_REGISTRYINDEX, reason.lua_reference); @@ -442,12 +438,12 @@ void ScriptApiBase::pushPlayerHPChangeReason(lua_State *L, const PlayerHPChangeR } } -Server* ScriptApiBase::getServer() +Server *ScriptApiBase::getServer() { return dynamic_cast(m_gamedef); } #ifndef SERVER -Client* ScriptApiBase::getClient() +Client *ScriptApiBase::getClient() { return dynamic_cast(m_gamedef); } diff --git a/src/script/cpp_api/s_base.h b/src/script/cpp_api/s_base.h index 36331ad37..d2081ad5c 100644 --- a/src/script/cpp_api/s_base.h +++ b/src/script/cpp_api/s_base.h @@ -45,20 +45,20 @@ extern "C" { // use that name to bypass security! #define BUILTIN_MOD_NAME "*builtin*" -#define PCALL_RES(RES) { \ - int result_ = (RES); \ - if (result_ != 0) { \ - scriptError(result_, __FUNCTION__); \ - } \ -} +#define PCALL_RES(RES) \ + { \ + int result_ = (RES); \ + if (result_ != 0) { \ + scriptError(result_, __FUNCTION__); \ + } \ + } -#define runCallbacks(nargs, mode) \ - runCallbacksRaw((nargs), (mode), __FUNCTION__) +#define runCallbacks(nargs, mode) runCallbacksRaw((nargs), (mode), __FUNCTION__) -#define setOriginFromTable(index) \ - setOriginFromTableRaw(index, __FUNCTION__) +#define setOriginFromTable(index) setOriginFromTableRaw(index, __FUNCTION__) -enum class ScriptingType: u8 { +enum class ScriptingType : u8 +{ Async, Client, MainMenu, @@ -76,14 +76,13 @@ class GUIEngine; class ServerActiveObject; struct PlayerHPChangeReason; -class ScriptApiBase : protected LuaHelper { +class ScriptApiBase : protected LuaHelper +{ public: ScriptApiBase(ScriptingType type); - // fake constructor to allow script API classes (e.g ScriptApiEnv) to virtually inherit from this one. - ScriptApiBase() - { - FATAL_ERROR("ScriptApiBase created without ScriptingType!"); - } + // fake constructor to allow script API classes (e.g ScriptApiEnv) to virtually + // inherit from this one. + ScriptApiBase() { FATAL_ERROR("ScriptApiBase created without ScriptingType!"); } virtual ~ScriptApiBase(); DISABLE_CLASS_COPY(ScriptApiBase); @@ -95,18 +94,17 @@ public: void loadModFromMemory(const std::string &mod_name); #endif - void runCallbacksRaw(int nargs, - RunCallbacksMode mode, const char *fxn); + void runCallbacksRaw(int nargs, RunCallbacksMode mode, const char *fxn); /* object */ void addObjectReference(ServerActiveObject *cobj); void removeObjectReference(ServerActiveObject *cobj); IGameDef *getGameDef() { return m_gamedef; } - Server* getServer(); + Server *getServer(); ScriptingType getType() { return m_type; } #ifndef SERVER - Client* getClient(); + Client *getClient(); Game *getGame() { return m_game; } #endif @@ -126,50 +124,49 @@ protected: friend class ModApiEnvMod; friend class LuaVoxelManip; - lua_State* getStack() - { return m_luastack; } + lua_State *getStack() { return m_luastack; } void realityCheck(); void scriptError(int result, const char *fxn); void stackDump(std::ostream &o); - void setGameDef(IGameDef* gamedef) { m_gamedef = gamedef; } + void setGameDef(IGameDef *gamedef) { m_gamedef = gamedef; } #ifndef SERVER void setGame(Game *game) { m_game = game; } #endif - Environment* getEnv() { return m_environment; } - void setEnv(Environment* env) { m_environment = env; } + Environment *getEnv() { return m_environment; } + void setEnv(Environment *env) { m_environment = env; } #ifndef SERVER - GUIEngine* getGuiEngine() { return m_guiengine; } - void setGuiEngine(GUIEngine* guiengine) { m_guiengine = guiengine; } + GUIEngine *getGuiEngine() { return m_guiengine; } + void setGuiEngine(GUIEngine *guiengine) { m_guiengine = guiengine; } #endif void objectrefGetOrCreate(lua_State *L, ServerActiveObject *cobj); - void pushPlayerHPChangeReason(lua_State *L, const PlayerHPChangeReason& reason); + void pushPlayerHPChangeReason(lua_State *L, const PlayerHPChangeReason &reason); std::recursive_mutex m_luastackmutex; - std::string m_last_run_mod; - bool m_secure = false; + std::string m_last_run_mod; + bool m_secure = false; #ifdef SCRIPTAPI_LOCK_DEBUG - int m_lock_recursion_count{}; + int m_lock_recursion_count{}; std::thread::id m_owning_thread; #endif private: static int luaPanic(lua_State *L); - lua_State *m_luastack = nullptr; + lua_State *m_luastack = nullptr; - IGameDef *m_gamedef = nullptr; + IGameDef *m_gamedef = nullptr; #ifndef SERVER - Game *m_game = nullptr; + Game *m_game = nullptr; #endif - Environment *m_environment = nullptr; + Environment *m_environment = nullptr; #ifndef SERVER - GUIEngine *m_guiengine = nullptr; + GUIEngine *m_guiengine = nullptr; #endif - ScriptingType m_type; + ScriptingType m_type; }; diff --git a/src/script/cpp_api/s_client.cpp b/src/script/cpp_api/s_client.cpp index dd9019d4d..e6c9eea1a 100644 --- a/src/script/cpp_api/s_client.cpp +++ b/src/script/cpp_api/s_client.cpp @@ -120,13 +120,13 @@ void ScriptApiClient::environment_step(float dtime) try { runCallbacks(1, RUN_CALLBACKS_MODE_FIRST); } catch (LuaError &e) { - getClient()->setFatalError(std::string("Client environment_step: ") + e.what() + "\n" - + script_get_backtrace(L)); + getClient()->setFatalError(std::string("Client environment_step: ") + + e.what() + "\n" + script_get_backtrace(L)); } } -void ScriptApiClient::on_formspec_input(const std::string &formname, - const StringMap &fields) +void ScriptApiClient::on_formspec_input( + const std::string &formname, const StringMap &fields) { SCRIPTAPI_PRECHECKHEADER @@ -187,7 +187,8 @@ bool ScriptApiClient::on_punchnode(v3s16 p, MapNode node) return readParam(L, -1); } -bool ScriptApiClient::on_placenode(const PointedThing &pointed, const ItemDefinition &item) +bool ScriptApiClient::on_placenode( + const PointedThing &pointed, const ItemDefinition &item) { SCRIPTAPI_PRECHECKHEADER @@ -237,11 +238,11 @@ bool ScriptApiClient::on_inventory_open(Inventory *inventory) void ScriptApiClient::open_enderchest() { SCRIPTAPI_PRECHECKHEADER - + PUSH_ERROR_HANDLER(L); int error_handler = lua_gettop(L) - 1; lua_insert(L, error_handler); - + lua_getglobal(L, "core"); lua_getfield(L, -1, "open_enderchest"); if (lua_isfunction(L, -1)) diff --git a/src/script/cpp_api/s_entity.cpp b/src/script/cpp_api/s_entity.cpp index ea9320051..baf455e1b 100644 --- a/src/script/cpp_api/s_entity.cpp +++ b/src/script/cpp_api/s_entity.cpp @@ -29,8 +29,8 @@ bool ScriptApiEntity::luaentity_Add(u16 id, const char *name) { SCRIPTAPI_PRECHECKHEADER - verbosestream<<"scriptapi_luaentity_add: id="<setAsyncFatalError( - std::string("environment_Step: ") + e.what() + "\n" - + script_get_backtrace(L)); + getServer()->setAsyncFatalError(std::string("environment_Step: ") + + e.what() + "\n" + + script_get_backtrace(L)); } } @@ -72,14 +71,13 @@ void ScriptApiEnv::player_event(ServerActiveObject *player, const std::string &t lua_getfield(L, -1, "registered_playerevents"); // Call callbacks - objectrefGetOrCreate(L, player); // player - lua_pushstring(L,type.c_str()); // event type + objectrefGetOrCreate(L, player); // player + lua_pushstring(L, type.c_str()); // event type try { runCallbacks(2, RUN_CALLBACKS_MODE_FIRST); } catch (LuaError &e) { - getServer()->setAsyncFatalError( - std::string("player_event: ") + e.what() + "\n" - + script_get_backtrace(L) ); + getServer()->setAsyncFatalError(std::string("player_event: ") + e.what() + + "\n" + script_get_backtrace(L)); } } @@ -116,7 +114,8 @@ void ScriptApiEnv::initializeEnvironment(ServerEnvironment *env) while (lua_next(L, table)) { // key at index -2 and value at index -1 luaL_checktype(L, -1, LUA_TSTRING); - trigger_contents.emplace_back(readParam(L, -1)); + trigger_contents.emplace_back( + readParam(L, -1)); // removes value, keeps key for next iteration lua_pop(L, 1); } @@ -133,7 +132,8 @@ void ScriptApiEnv::initializeEnvironment(ServerEnvironment *env) while (lua_next(L, table)) { // key at index -2 and value at index -1 luaL_checktype(L, -1, LUA_TSTRING); - required_neighbors.emplace_back(readParam(L, -1)); + required_neighbors.emplace_back( + readParam(L, -1)); // removes value, keeps key for next iteration lua_pop(L, 1); } @@ -156,7 +156,7 @@ void ScriptApiEnv::initializeEnvironment(ServerEnvironment *env) lua_pop(L, 1); LuaABM *abm = new LuaABM(L, id, trigger_contents, required_neighbors, - trigger_interval, trigger_chance, simple_catch_up); + trigger_interval, trigger_chance, simple_catch_up); env->addActiveBlockModifier(abm); @@ -201,15 +201,15 @@ void ScriptApiEnv::initializeEnvironment(ServerEnvironment *env) std::string name; getstringfield(L, current_lbm, "name", name); - bool run_at_every_load = getboolfield_default(L, current_lbm, - "run_at_every_load", false); + bool run_at_every_load = getboolfield_default( + L, current_lbm, "run_at_every_load", false); lua_getfield(L, current_lbm, "action"); luaL_checktype(L, current_lbm + 1, LUA_TFUNCTION); lua_pop(L, 1); - LuaLBM *lbm = new LuaLBM(L, id, trigger_contents, name, - run_at_every_load); + LuaLBM *lbm = new LuaLBM( + L, id, trigger_contents, name, run_at_every_load); env->addLoadingBlockModifierDef(lbm); @@ -220,7 +220,7 @@ void ScriptApiEnv::initializeEnvironment(ServerEnvironment *env) } void ScriptApiEnv::on_emerge_area_completion( - v3s16 blockpos, int action, ScriptCallbackState *state) + v3s16 blockpos, int action, ScriptCallbackState *state) { Server *server = getServer(); @@ -249,9 +249,8 @@ void ScriptApiEnv::on_emerge_area_completion( try { PCALL_RES(lua_pcall(L, 4, 0, error_handler)); } catch (LuaError &e) { - server->setAsyncFatalError( - std::string("on_emerge_area_completion: ") + e.what() + "\n" - + script_get_backtrace(L)); + server->setAsyncFatalError(std::string("on_emerge_area_completion: ") + + e.what() + "\n" + script_get_backtrace(L)); } lua_pop(L, 1); // Pop error handler diff --git a/src/script/cpp_api/s_env.h b/src/script/cpp_api/s_env.h index 232a08aaf..1848c7a94 100644 --- a/src/script/cpp_api/s_env.h +++ b/src/script/cpp_api/s_env.h @@ -38,8 +38,8 @@ public: void player_event(ServerActiveObject *player, const std::string &type); // Called after emerge of a block queued from core.emerge_area() - void on_emerge_area_completion(v3s16 blockpos, int action, - ScriptCallbackState *state); + void on_emerge_area_completion( + v3s16 blockpos, int action, ScriptCallbackState *state); void initializeEnvironment(ServerEnvironment *env); }; diff --git a/src/script/cpp_api/s_internal.h b/src/script/cpp_api/s_internal.h index 83b3b9d27..858b827de 100644 --- a/src/script/cpp_api/s_internal.h +++ b/src/script/cpp_api/s_internal.h @@ -34,13 +34,14 @@ with this program; if not, write to the Free Software Foundation, Inc., #ifdef SCRIPTAPI_LOCK_DEBUG #include -class LockChecker { +class LockChecker +{ public: LockChecker(int *recursion_counter, std::thread::id *owning_thread) { m_lock_recursion_counter = recursion_counter; - m_owning_thread = owning_thread; - m_original_level = *recursion_counter; + m_owning_thread = owning_thread; + m_original_level = *recursion_counter; if (*m_lock_recursion_counter > 0) { assert(*m_owning_thread == std::this_thread::get_id()); @@ -67,19 +68,18 @@ private: std::thread::id *m_owning_thread; }; -#define SCRIPTAPI_LOCK_CHECK \ - LockChecker scriptlock_checker( \ - &this->m_lock_recursion_count, \ - &this->m_owning_thread) +#define SCRIPTAPI_LOCK_CHECK \ + LockChecker scriptlock_checker( \ + &this->m_lock_recursion_count, &this->m_owning_thread) #else - #define SCRIPTAPI_LOCK_CHECK while(0) +#define SCRIPTAPI_LOCK_CHECK while (0) #endif -#define SCRIPTAPI_PRECHECKHEADER \ - RecursiveMutexAutoLock scriptlock(this->m_luastackmutex); \ - SCRIPTAPI_LOCK_CHECK; \ - realityCheck(); \ - lua_State *L = getStack(); \ - assert(lua_checkstack(L, 20)); \ - StackUnroller stack_unroller(L); +#define SCRIPTAPI_PRECHECKHEADER \ + RecursiveMutexAutoLock scriptlock(this->m_luastackmutex); \ + SCRIPTAPI_LOCK_CHECK; \ + realityCheck(); \ + lua_State *L = getStack(); \ + assert(lua_checkstack(L, 20)); \ + StackUnroller stack_unroller(L); diff --git a/src/script/cpp_api/s_inventory.cpp b/src/script/cpp_api/s_inventory.cpp index e9c09f72e..e4d3f990d 100644 --- a/src/script/cpp_api/s_inventory.cpp +++ b/src/script/cpp_api/s_inventory.cpp @@ -26,8 +26,7 @@ with this program; if not, write to the Free Software Foundation, Inc., // Return number of accepted items to be moved int ScriptApiDetached::detached_inventory_AllowMove( - const MoveAction &ma, int count, - ServerActiveObject *player) + const MoveAction &ma, int count, ServerActiveObject *player) { SCRIPTAPI_PRECHECKHEADER @@ -41,14 +40,15 @@ int ScriptApiDetached::detached_inventory_AllowMove( // inv InvRef::create(L, ma.from_inv); lua_pushstring(L, ma.from_list.c_str()); // from_list - lua_pushinteger(L, ma.from_i + 1); // from_index - lua_pushstring(L, ma.to_list.c_str()); // to_list - lua_pushinteger(L, ma.to_i + 1); // to_index - lua_pushinteger(L, count); // count - objectrefGetOrCreate(L, player); // player + lua_pushinteger(L, ma.from_i + 1); // from_index + lua_pushstring(L, ma.to_list.c_str()); // to_list + lua_pushinteger(L, ma.to_i + 1); // to_index + lua_pushinteger(L, count); // count + objectrefGetOrCreate(L, player); // player PCALL_RES(lua_pcall(L, 7, 1, error_handler)); - if(!lua_isnumber(L, -1)) - throw LuaError("allow_move should return a number. name=" + ma.from_inv.name); + if (!lua_isnumber(L, -1)) + throw LuaError("allow_move should return a number. name=" + + ma.from_inv.name); int ret = luaL_checkinteger(L, -1); lua_pop(L, 2); // Pop integer and error handler return ret; @@ -56,8 +56,7 @@ int ScriptApiDetached::detached_inventory_AllowMove( // Return number of accepted items to be put int ScriptApiDetached::detached_inventory_AllowPut( - const MoveAction &ma, const ItemStack &stack, - ServerActiveObject *player) + const MoveAction &ma, const ItemStack &stack, ServerActiveObject *player) { SCRIPTAPI_PRECHECKHEADER @@ -68,14 +67,15 @@ int ScriptApiDetached::detached_inventory_AllowPut( return stack.count; // All will be accepted // Call function(inv, listname, index, stack, player) - InvRef::create(L, ma.to_inv); // inv + InvRef::create(L, ma.to_inv); // inv lua_pushstring(L, ma.to_list.c_str()); // listname lua_pushinteger(L, ma.to_i + 1); // index - LuaItemStack::create(L, stack); // stack - objectrefGetOrCreate(L, player); // player + LuaItemStack::create(L, stack); // stack + objectrefGetOrCreate(L, player); // player PCALL_RES(lua_pcall(L, 5, 1, error_handler)); if (!lua_isnumber(L, -1)) - throw LuaError("allow_put should return a number. name=" + ma.to_inv.name); + throw LuaError("allow_put should return a number. name=" + + ma.to_inv.name); int ret = luaL_checkinteger(L, -1); lua_pop(L, 2); // Pop integer and error handler return ret; @@ -83,8 +83,7 @@ int ScriptApiDetached::detached_inventory_AllowPut( // Return number of accepted items to be taken int ScriptApiDetached::detached_inventory_AllowTake( - const MoveAction &ma, const ItemStack &stack, - ServerActiveObject *player) + const MoveAction &ma, const ItemStack &stack, ServerActiveObject *player) { SCRIPTAPI_PRECHECKHEADER @@ -95,14 +94,15 @@ int ScriptApiDetached::detached_inventory_AllowTake( return stack.count; // All will be accepted // Call function(inv, listname, index, stack, player) - InvRef::create(L, ma.from_inv); // inv + InvRef::create(L, ma.from_inv); // inv lua_pushstring(L, ma.from_list.c_str()); // listname - lua_pushinteger(L, ma.from_i + 1); // index - LuaItemStack::create(L, stack); // stack - objectrefGetOrCreate(L, player); // player + lua_pushinteger(L, ma.from_i + 1); // index + LuaItemStack::create(L, stack); // stack + objectrefGetOrCreate(L, player); // player PCALL_RES(lua_pcall(L, 5, 1, error_handler)); if (!lua_isnumber(L, -1)) - throw LuaError("allow_take should return a number. name=" + ma.from_inv.name); + throw LuaError("allow_take should return a number. name=" + + ma.from_inv.name); int ret = luaL_checkinteger(L, -1); lua_pop(L, 2); // Pop integer and error handler return ret; @@ -110,8 +110,7 @@ int ScriptApiDetached::detached_inventory_AllowTake( // Report moved items void ScriptApiDetached::detached_inventory_OnMove( - const MoveAction &ma, int count, - ServerActiveObject *player) + const MoveAction &ma, int count, ServerActiveObject *player) { SCRIPTAPI_PRECHECKHEADER @@ -125,19 +124,18 @@ void ScriptApiDetached::detached_inventory_OnMove( // inv InvRef::create(L, ma.from_inv); lua_pushstring(L, ma.from_list.c_str()); // from_list - lua_pushinteger(L, ma.from_i + 1); // from_index - lua_pushstring(L, ma.to_list.c_str()); // to_list - lua_pushinteger(L, ma.to_i + 1); // to_index - lua_pushinteger(L, count); // count - objectrefGetOrCreate(L, player); // player + lua_pushinteger(L, ma.from_i + 1); // from_index + lua_pushstring(L, ma.to_list.c_str()); // to_list + lua_pushinteger(L, ma.to_i + 1); // to_index + lua_pushinteger(L, count); // count + objectrefGetOrCreate(L, player); // player PCALL_RES(lua_pcall(L, 7, 0, error_handler)); - lua_pop(L, 1); // Pop error handler + lua_pop(L, 1); // Pop error handler } // Report put items void ScriptApiDetached::detached_inventory_OnPut( - const MoveAction &ma, const ItemStack &stack, - ServerActiveObject *player) + const MoveAction &ma, const ItemStack &stack, ServerActiveObject *player) { SCRIPTAPI_PRECHECKHEADER @@ -152,16 +150,15 @@ void ScriptApiDetached::detached_inventory_OnPut( InvRef::create(L, ma.to_inv); lua_pushstring(L, ma.to_list.c_str()); // listname lua_pushinteger(L, ma.to_i + 1); // index - LuaItemStack::create(L, stack); // stack + LuaItemStack::create(L, stack); // stack objectrefGetOrCreate(L, player); // player PCALL_RES(lua_pcall(L, 5, 0, error_handler)); - lua_pop(L, 1); // Pop error handler + lua_pop(L, 1); // Pop error handler } // Report taken items void ScriptApiDetached::detached_inventory_OnTake( - const MoveAction &ma, const ItemStack &stack, - ServerActiveObject *player) + const MoveAction &ma, const ItemStack &stack, ServerActiveObject *player) { SCRIPTAPI_PRECHECKHEADER @@ -175,11 +172,11 @@ void ScriptApiDetached::detached_inventory_OnTake( // inv InvRef::create(L, ma.from_inv); lua_pushstring(L, ma.from_list.c_str()); // listname - lua_pushinteger(L, ma.from_i + 1); // index - LuaItemStack::create(L, stack); // stack - objectrefGetOrCreate(L, player); // player + lua_pushinteger(L, ma.from_i + 1); // index + LuaItemStack::create(L, stack); // stack + objectrefGetOrCreate(L, player); // player PCALL_RES(lua_pcall(L, 5, 0, error_handler)); - lua_pop(L, 1); // Pop error handler + lua_pop(L, 1); // Pop error handler } // Retrieves core.detached_inventories[name][callbackname] @@ -199,7 +196,8 @@ bool ScriptApiDetached::getDetachedInventoryCallback( lua_remove(L, -2); // Should be a table if (lua_type(L, -1) != LUA_TTABLE) { - errorstream<<"Detached inventory \""<(L, -1, false); } -void ScriptApiNode::node_on_receive_fields(v3s16 p, - const std::string &formname, - const StringMap &fields, - ServerActiveObject *sender) +void ScriptApiNode::node_on_receive_fields(v3s16 p, const std::string &formname, + const StringMap &fields, ServerActiveObject *sender) { SCRIPTAPI_PRECHECKHEADER @@ -255,9 +246,9 @@ void ScriptApiNode::node_on_receive_fields(v3s16 p, return; // Call function - push_v3s16(L, p); // pos + push_v3s16(L, p); // pos lua_pushstring(L, formname.c_str()); // formname - lua_newtable(L); // fields + lua_newtable(L); // fields StringMap::const_iterator it; for (it = fields.begin(); it != fields.end(); ++it) { const std::string &name = it->first; @@ -266,7 +257,7 @@ void ScriptApiNode::node_on_receive_fields(v3s16 p, lua_pushlstring(L, value.c_str(), value.size()); lua_settable(L, -3); } - objectrefGetOrCreate(L, sender); // player + objectrefGetOrCreate(L, sender); // player PCALL_RES(lua_pcall(L, 4, 0, error_handler)); - lua_pop(L, 1); // Pop error handler + lua_pop(L, 1); // Pop error handler } diff --git a/src/script/cpp_api/s_node.h b/src/script/cpp_api/s_node.h index 81b44f0f0..8f4e41479 100644 --- a/src/script/cpp_api/s_node.h +++ b/src/script/cpp_api/s_node.h @@ -27,27 +27,23 @@ with this program; if not, write to the Free Software Foundation, Inc., struct MapNode; class ServerActiveObject; -class ScriptApiNode - : virtual public ScriptApiBase, - public ScriptApiNodemeta +class ScriptApiNode : virtual public ScriptApiBase, public ScriptApiNodemeta { public: ScriptApiNode() = default; virtual ~ScriptApiNode() = default; - bool node_on_punch(v3s16 p, MapNode node, - ServerActiveObject *puncher, const PointedThing &pointed); - bool node_on_dig(v3s16 p, MapNode node, - ServerActiveObject *digger); + bool node_on_punch(v3s16 p, MapNode node, ServerActiveObject *puncher, + const PointedThing &pointed); + bool node_on_dig(v3s16 p, MapNode node, ServerActiveObject *digger); void node_on_construct(v3s16 p, MapNode node); void node_on_destruct(v3s16 p, MapNode node); bool node_on_flood(v3s16 p, MapNode node, MapNode newnode); void node_after_destruct(v3s16 p, MapNode node); bool node_on_timer(v3s16 p, MapNode node, f32 dtime); - void node_on_receive_fields(v3s16 p, - const std::string &formname, - const StringMap &fields, - ServerActiveObject *sender); + void node_on_receive_fields(v3s16 p, const std::string &formname, + const StringMap &fields, ServerActiveObject *sender); + public: static struct EnumString es_DrawType[]; static struct EnumString es_ContentParamType[]; diff --git a/src/script/cpp_api/s_nodemeta.cpp b/src/script/cpp_api/s_nodemeta.cpp index c081e9fc4..b7bd5f7c8 100644 --- a/src/script/cpp_api/s_nodemeta.cpp +++ b/src/script/cpp_api/s_nodemeta.cpp @@ -28,8 +28,7 @@ with this program; if not, write to the Free Software Foundation, Inc., // Return number of accepted items to be moved int ScriptApiNodemeta::nodemeta_inventory_AllowMove( - const MoveAction &ma, int count, - ServerActiveObject *player) + const MoveAction &ma, int count, ServerActiveObject *player) { SCRIPTAPI_PRECHECKHEADER @@ -44,21 +43,23 @@ int ScriptApiNodemeta::nodemeta_inventory_AllowMove( // Push callback function on stack std::string nodename = ndef->get(node).name; - if (!getItemCallback(nodename.c_str(), "allow_metadata_inventory_move", &ma.to_inv.p)) + if (!getItemCallback(nodename.c_str(), "allow_metadata_inventory_move", + &ma.to_inv.p)) return count; // function(pos, from_list, from_index, to_list, to_index, count, player) - push_v3s16(L, ma.to_inv.p); // pos + push_v3s16(L, ma.to_inv.p); // pos lua_pushstring(L, ma.from_list.c_str()); // from_list - lua_pushinteger(L, ma.from_i + 1); // from_index - lua_pushstring(L, ma.to_list.c_str()); // to_list - lua_pushinteger(L, ma.to_i + 1); // to_index - lua_pushinteger(L, count); // count - objectrefGetOrCreate(L, player); // player + lua_pushinteger(L, ma.from_i + 1); // from_index + lua_pushstring(L, ma.to_list.c_str()); // to_list + lua_pushinteger(L, ma.to_i + 1); // to_index + lua_pushinteger(L, count); // count + objectrefGetOrCreate(L, player); // player PCALL_RES(lua_pcall(L, 7, 1, error_handler)); if (!lua_isnumber(L, -1)) throw LuaError("allow_metadata_inventory_move should" - " return a number, guilty node: " + nodename); + " return a number, guilty node: " + + nodename); int num = luaL_checkinteger(L, -1); lua_pop(L, 2); // Pop integer and error handler return num; @@ -66,8 +67,7 @@ int ScriptApiNodemeta::nodemeta_inventory_AllowMove( // Return number of accepted items to be put int ScriptApiNodemeta::nodemeta_inventory_AllowPut( - const MoveAction &ma, const ItemStack &stack, - ServerActiveObject *player) + const MoveAction &ma, const ItemStack &stack, ServerActiveObject *player) { SCRIPTAPI_PRECHECKHEADER @@ -82,19 +82,21 @@ int ScriptApiNodemeta::nodemeta_inventory_AllowPut( // Push callback function on stack std::string nodename = ndef->get(node).name; - if (!getItemCallback(nodename.c_str(), "allow_metadata_inventory_put", &ma.to_inv.p)) + if (!getItemCallback(nodename.c_str(), "allow_metadata_inventory_put", + &ma.to_inv.p)) return stack.count; // Call function(pos, listname, index, stack, player) - push_v3s16(L, ma.to_inv.p); // pos + push_v3s16(L, ma.to_inv.p); // pos lua_pushstring(L, ma.to_list.c_str()); // listname lua_pushinteger(L, ma.to_i + 1); // index - LuaItemStack::create(L, stack); // stack + LuaItemStack::create(L, stack); // stack objectrefGetOrCreate(L, player); // player PCALL_RES(lua_pcall(L, 5, 1, error_handler)); - if(!lua_isnumber(L, -1)) + if (!lua_isnumber(L, -1)) throw LuaError("allow_metadata_inventory_put should" - " return a number, guilty node: " + nodename); + " return a number, guilty node: " + + nodename); int num = luaL_checkinteger(L, -1); lua_pop(L, 2); // Pop integer and error handler return num; @@ -102,8 +104,7 @@ int ScriptApiNodemeta::nodemeta_inventory_AllowPut( // Return number of accepted items to be taken int ScriptApiNodemeta::nodemeta_inventory_AllowTake( - const MoveAction &ma, const ItemStack &stack, - ServerActiveObject *player) + const MoveAction &ma, const ItemStack &stack, ServerActiveObject *player) { SCRIPTAPI_PRECHECKHEADER @@ -118,19 +119,21 @@ int ScriptApiNodemeta::nodemeta_inventory_AllowTake( // Push callback function on stack std::string nodename = ndef->get(node).name; - if (!getItemCallback(nodename.c_str(), "allow_metadata_inventory_take", &ma.from_inv.p)) + if (!getItemCallback(nodename.c_str(), "allow_metadata_inventory_take", + &ma.from_inv.p)) return stack.count; // Call function(pos, listname, index, count, player) - push_v3s16(L, ma.from_inv.p); // pos + push_v3s16(L, ma.from_inv.p); // pos lua_pushstring(L, ma.from_list.c_str()); // listname - lua_pushinteger(L, ma.from_i + 1); // index - LuaItemStack::create(L, stack); // stack - objectrefGetOrCreate(L, player); // player + lua_pushinteger(L, ma.from_i + 1); // index + LuaItemStack::create(L, stack); // stack + objectrefGetOrCreate(L, player); // player PCALL_RES(lua_pcall(L, 5, 1, error_handler)); if (!lua_isnumber(L, -1)) throw LuaError("allow_metadata_inventory_take should" - " return a number, guilty node: " + nodename); + " return a number, guilty node: " + + nodename); int num = luaL_checkinteger(L, -1); lua_pop(L, 2); // Pop integer and error handler return num; @@ -138,8 +141,7 @@ int ScriptApiNodemeta::nodemeta_inventory_AllowTake( // Report moved items void ScriptApiNodemeta::nodemeta_inventory_OnMove( - const MoveAction &ma, int count, - ServerActiveObject *player) + const MoveAction &ma, int count, ServerActiveObject *player) { SCRIPTAPI_PRECHECKHEADER @@ -154,25 +156,25 @@ void ScriptApiNodemeta::nodemeta_inventory_OnMove( // Push callback function on stack std::string nodename = ndef->get(node).name; - if (!getItemCallback(nodename.c_str(), "on_metadata_inventory_move", &ma.from_inv.p)) + if (!getItemCallback(nodename.c_str(), "on_metadata_inventory_move", + &ma.from_inv.p)) return; // function(pos, from_list, from_index, to_list, to_index, count, player) - push_v3s16(L, ma.from_inv.p); // pos + push_v3s16(L, ma.from_inv.p); // pos lua_pushstring(L, ma.from_list.c_str()); // from_list - lua_pushinteger(L, ma.from_i + 1); // from_index - lua_pushstring(L, ma.to_list.c_str()); // to_list - lua_pushinteger(L, ma.to_i + 1); // to_index - lua_pushinteger(L, count); // count - objectrefGetOrCreate(L, player); // player + lua_pushinteger(L, ma.from_i + 1); // from_index + lua_pushstring(L, ma.to_list.c_str()); // to_list + lua_pushinteger(L, ma.to_i + 1); // to_index + lua_pushinteger(L, count); // count + objectrefGetOrCreate(L, player); // player PCALL_RES(lua_pcall(L, 7, 0, error_handler)); - lua_pop(L, 1); // Pop error handler + lua_pop(L, 1); // Pop error handler } // Report put items void ScriptApiNodemeta::nodemeta_inventory_OnPut( - const MoveAction &ma, const ItemStack &stack, - ServerActiveObject *player) + const MoveAction &ma, const ItemStack &stack, ServerActiveObject *player) { SCRIPTAPI_PRECHECKHEADER @@ -191,19 +193,18 @@ void ScriptApiNodemeta::nodemeta_inventory_OnPut( return; // Call function(pos, listname, index, stack, player) - push_v3s16(L, ma.to_inv.p); // pos + push_v3s16(L, ma.to_inv.p); // pos lua_pushstring(L, ma.to_list.c_str()); // listname lua_pushinteger(L, ma.to_i + 1); // index - LuaItemStack::create(L, stack); // stack + LuaItemStack::create(L, stack); // stack objectrefGetOrCreate(L, player); // player PCALL_RES(lua_pcall(L, 5, 0, error_handler)); - lua_pop(L, 1); // Pop error handler + lua_pop(L, 1); // Pop error handler } // Report taken items void ScriptApiNodemeta::nodemeta_inventory_OnTake( - const MoveAction &ma, const ItemStack &stack, - ServerActiveObject *player) + const MoveAction &ma, const ItemStack &stack, ServerActiveObject *player) { SCRIPTAPI_PRECHECKHEADER @@ -218,15 +219,16 @@ void ScriptApiNodemeta::nodemeta_inventory_OnTake( // Push callback function on stack std::string nodename = ndef->get(node).name; - if (!getItemCallback(nodename.c_str(), "on_metadata_inventory_take", &ma.from_inv.p)) + if (!getItemCallback(nodename.c_str(), "on_metadata_inventory_take", + &ma.from_inv.p)) return; // Call function(pos, listname, index, stack, player) - push_v3s16(L, ma.from_inv.p); // pos + push_v3s16(L, ma.from_inv.p); // pos lua_pushstring(L, ma.from_list.c_str()); // listname - lua_pushinteger(L, ma.from_i + 1); // index - LuaItemStack::create(L, stack); // stack - objectrefGetOrCreate(L, player); // player + lua_pushinteger(L, ma.from_i + 1); // index + LuaItemStack::create(L, stack); // stack + objectrefGetOrCreate(L, player); // player PCALL_RES(lua_pcall(L, 5, 0, error_handler)); - lua_pop(L, 1); // Pop error handler + lua_pop(L, 1); // Pop error handler } diff --git a/src/script/cpp_api/s_nodemeta.h b/src/script/cpp_api/s_nodemeta.h index 8c7cdd93e..609898a30 100644 --- a/src/script/cpp_api/s_nodemeta.h +++ b/src/script/cpp_api/s_nodemeta.h @@ -26,9 +26,7 @@ with this program; if not, write to the Free Software Foundation, Inc., struct MoveAction; struct ItemStack; -class ScriptApiNodemeta - : virtual public ScriptApiBase, - public ScriptApiItem +class ScriptApiNodemeta : virtual public ScriptApiBase, public ScriptApiItem { public: ScriptApiNodemeta() = default; @@ -36,28 +34,22 @@ public: // Return number of accepted items to be moved int nodemeta_inventory_AllowMove( - const MoveAction &ma, int count, - ServerActiveObject *player); + const MoveAction &ma, int count, ServerActiveObject *player); // Return number of accepted items to be put - int nodemeta_inventory_AllowPut( - const MoveAction &ma, const ItemStack &stack, + int nodemeta_inventory_AllowPut(const MoveAction &ma, const ItemStack &stack, ServerActiveObject *player); // Return number of accepted items to be taken - int nodemeta_inventory_AllowTake( - const MoveAction &ma, const ItemStack &stack, + int nodemeta_inventory_AllowTake(const MoveAction &ma, const ItemStack &stack, ServerActiveObject *player); // Report moved items void nodemeta_inventory_OnMove( - const MoveAction &ma, int count, - ServerActiveObject *player); + const MoveAction &ma, int count, ServerActiveObject *player); // Report put items - void nodemeta_inventory_OnPut( - const MoveAction &ma, const ItemStack &stack, + void nodemeta_inventory_OnPut(const MoveAction &ma, const ItemStack &stack, ServerActiveObject *player); // Report taken items - void nodemeta_inventory_OnTake( - const MoveAction &ma, const ItemStack &stack, + void nodemeta_inventory_OnTake(const MoveAction &ma, const ItemStack &stack, ServerActiveObject *player); -private: +private: }; diff --git a/src/script/cpp_api/s_player.cpp b/src/script/cpp_api/s_player.cpp index 712120c61..c9ab81db9 100644 --- a/src/script/cpp_api/s_player.cpp +++ b/src/script/cpp_api/s_player.cpp @@ -39,7 +39,8 @@ void ScriptApiPlayer::on_newplayer(ServerActiveObject *player) runCallbacks(1, RUN_CALLBACKS_MODE_FIRST); } -void ScriptApiPlayer::on_dieplayer(ServerActiveObject *player, const PlayerHPChangeReason &reason) +void ScriptApiPlayer::on_dieplayer( + ServerActiveObject *player, const PlayerHPChangeReason &reason) { SCRIPTAPI_PRECHECKHEADER @@ -56,11 +57,8 @@ void ScriptApiPlayer::on_dieplayer(ServerActiveObject *player, const PlayerHPCha } bool ScriptApiPlayer::on_punchplayer(ServerActiveObject *player, - ServerActiveObject *hitter, - float time_from_last_punch, - const ToolCapabilities *toolcap, - v3f dir, - s16 damage) + ServerActiveObject *hitter, float time_from_last_punch, + const ToolCapabilities *toolcap, v3f dir, s16 damage) { SCRIPTAPI_PRECHECKHEADER // Get core.registered_on_punchplayers @@ -77,8 +75,8 @@ bool ScriptApiPlayer::on_punchplayer(ServerActiveObject *player, return readParam(L, -1); } -s32 ScriptApiPlayer::on_player_hpchange(ServerActiveObject *player, - s32 hp_change, const PlayerHPChangeReason &reason) +s32 ScriptApiPlayer::on_player_hpchange(ServerActiveObject *player, s32 hp_change, + const PlayerHPChangeReason &reason) { SCRIPTAPI_PRECHECKHEADER @@ -115,9 +113,7 @@ bool ScriptApiPlayer::on_respawnplayer(ServerActiveObject *player) } bool ScriptApiPlayer::on_prejoinplayer( - const std::string &name, - const std::string &ip, - std::string *reason) + const std::string &name, const std::string &ip, std::string *reason) { SCRIPTAPI_PRECHECKHEADER @@ -163,8 +159,7 @@ void ScriptApiPlayer::on_joinplayer(ServerActiveObject *player, s64 last_login) runCallbacks(2, RUN_CALLBACKS_MODE_FIRST); } -void ScriptApiPlayer::on_leaveplayer(ServerActiveObject *player, - bool timeout) +void ScriptApiPlayer::on_leaveplayer(ServerActiveObject *player, bool timeout) { SCRIPTAPI_PRECHECKHEADER @@ -177,8 +172,7 @@ void ScriptApiPlayer::on_leaveplayer(ServerActiveObject *player, runCallbacks(2, RUN_CALLBACKS_MODE_FIRST); } -void ScriptApiPlayer::on_cheat(ServerActiveObject *player, - const std::string &cheat_type) +void ScriptApiPlayer::on_cheat(ServerActiveObject *player, const std::string &cheat_type) { SCRIPTAPI_PRECHECKHEADER @@ -194,8 +188,7 @@ void ScriptApiPlayer::on_cheat(ServerActiveObject *player, } void ScriptApiPlayer::on_playerReceiveFields(ServerActiveObject *player, - const std::string &formname, - const StringMap &fields) + const std::string &formname, const StringMap &fields) { SCRIPTAPI_PRECHECKHEADER @@ -220,7 +213,8 @@ void ScriptApiPlayer::on_playerReceiveFields(ServerActiveObject *player, runCallbacks(3, RUN_CALLBACKS_MODE_OR_SC); } -void ScriptApiPlayer::on_authplayer(const std::string &name, const std::string &ip, bool is_success) +void ScriptApiPlayer::on_authplayer( + const std::string &name, const std::string &ip, bool is_success) { SCRIPTAPI_PRECHECKHEADER @@ -236,13 +230,12 @@ void ScriptApiPlayer::on_authplayer(const std::string &name, const std::string & } void ScriptApiPlayer::pushMoveArguments( - const MoveAction &ma, int count, - ServerActiveObject *player) + const MoveAction &ma, int count, ServerActiveObject *player) { lua_State *L = getStack(); objectrefGetOrCreate(L, player); // player - lua_pushstring(L, "move"); // action - InvRef::create(L, ma.from_inv); // inventory + lua_pushstring(L, "move"); // action + InvRef::create(L, ma.from_inv); // inventory lua_newtable(L); { // Table containing the action information @@ -261,15 +254,14 @@ void ScriptApiPlayer::pushMoveArguments( } } -void ScriptApiPlayer::pushPutTakeArguments( - const char *method, const InventoryLocation &loc, - const std::string &listname, int index, const ItemStack &stack, - ServerActiveObject *player) +void ScriptApiPlayer::pushPutTakeArguments(const char *method, + const InventoryLocation &loc, const std::string &listname, int index, + const ItemStack &stack, ServerActiveObject *player) { lua_State *L = getStack(); objectrefGetOrCreate(L, player); // player - lua_pushstring(L, method); // action - InvRef::create(L, loc); // inventory + lua_pushstring(L, method); // action + InvRef::create(L, loc); // inventory lua_newtable(L); { // Table containing the action information @@ -286,8 +278,7 @@ void ScriptApiPlayer::pushPutTakeArguments( // Return number of accepted items to be moved int ScriptApiPlayer::player_inventory_AllowMove( - const MoveAction &ma, int count, - ServerActiveObject *player) + const MoveAction &ma, int count, ServerActiveObject *player) { SCRIPTAPI_PRECHECKHEADER @@ -301,8 +292,7 @@ int ScriptApiPlayer::player_inventory_AllowMove( // Return number of accepted items to be put int ScriptApiPlayer::player_inventory_AllowPut( - const MoveAction &ma, const ItemStack &stack, - ServerActiveObject *player) + const MoveAction &ma, const ItemStack &stack, ServerActiveObject *player) { SCRIPTAPI_PRECHECKHEADER @@ -316,8 +306,7 @@ int ScriptApiPlayer::player_inventory_AllowPut( // Return number of accepted items to be taken int ScriptApiPlayer::player_inventory_AllowTake( - const MoveAction &ma, const ItemStack &stack, - ServerActiveObject *player) + const MoveAction &ma, const ItemStack &stack, ServerActiveObject *player) { SCRIPTAPI_PRECHECKHEADER @@ -331,8 +320,7 @@ int ScriptApiPlayer::player_inventory_AllowTake( // Report moved items void ScriptApiPlayer::player_inventory_OnMove( - const MoveAction &ma, int count, - ServerActiveObject *player) + const MoveAction &ma, int count, ServerActiveObject *player) { SCRIPTAPI_PRECHECKHEADER @@ -344,8 +332,7 @@ void ScriptApiPlayer::player_inventory_OnMove( // Report put items void ScriptApiPlayer::player_inventory_OnPut( - const MoveAction &ma, const ItemStack &stack, - ServerActiveObject *player) + const MoveAction &ma, const ItemStack &stack, ServerActiveObject *player) { SCRIPTAPI_PRECHECKHEADER @@ -357,8 +344,7 @@ void ScriptApiPlayer::player_inventory_OnPut( // Report taken items void ScriptApiPlayer::player_inventory_OnTake( - const MoveAction &ma, const ItemStack &stack, - ServerActiveObject *player) + const MoveAction &ma, const ItemStack &stack, ServerActiveObject *player) { SCRIPTAPI_PRECHECKHEADER diff --git a/src/script/cpp_api/s_player.h b/src/script/cpp_api/s_player.h index a337f975b..5a1cf15b8 100644 --- a/src/script/cpp_api/s_player.h +++ b/src/script/cpp_api/s_player.h @@ -51,38 +51,33 @@ public: const PlayerHPChangeReason &reason); void on_playerReceiveFields(ServerActiveObject *player, const std::string &formname, const StringMap &fields); - void on_authplayer(const std::string &name, const std::string &ip, bool is_success); + void on_authplayer( + const std::string &name, const std::string &ip, bool is_success); // Player inventory callbacks // Return number of accepted items to be moved int player_inventory_AllowMove( - const MoveAction &ma, int count, - ServerActiveObject *player); + const MoveAction &ma, int count, ServerActiveObject *player); // Return number of accepted items to be put - int player_inventory_AllowPut( - const MoveAction &ma, const ItemStack &stack, - ServerActiveObject *player); + int player_inventory_AllowPut(const MoveAction &ma, const ItemStack &stack, + ServerActiveObject *player); // Return number of accepted items to be taken - int player_inventory_AllowTake( - const MoveAction &ma, const ItemStack &stack, - ServerActiveObject *player); + int player_inventory_AllowTake(const MoveAction &ma, const ItemStack &stack, + ServerActiveObject *player); // Report moved items void player_inventory_OnMove( - const MoveAction &ma, int count, - ServerActiveObject *player); + const MoveAction &ma, int count, ServerActiveObject *player); // Report put items - void player_inventory_OnPut( - const MoveAction &ma, const ItemStack &stack, - ServerActiveObject *player); + void player_inventory_OnPut(const MoveAction &ma, const ItemStack &stack, + ServerActiveObject *player); // Report taken items - void player_inventory_OnTake( - const MoveAction &ma, const ItemStack &stack, - ServerActiveObject *player); + void player_inventory_OnTake(const MoveAction &ma, const ItemStack &stack, + ServerActiveObject *player); + private: - void pushPutTakeArguments( - const char *method, const InventoryLocation &loc, - const std::string &listname, int index, const ItemStack &stack, - ServerActiveObject *player); - void pushMoveArguments(const MoveAction &ma, - int count, ServerActiveObject *player); + void pushPutTakeArguments(const char *method, const InventoryLocation &loc, + const std::string &listname, int index, const ItemStack &stack, + ServerActiveObject *player); + void pushMoveArguments( + const MoveAction &ma, int count, ServerActiveObject *player); }; diff --git a/src/script/cpp_api/s_security.cpp b/src/script/cpp_api/s_security.cpp index 9d65819c0..21bc7eb0a 100644 --- a/src/script/cpp_api/s_security.cpp +++ b/src/script/cpp_api/s_security.cpp @@ -29,19 +29,20 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include - -#define SECURE_API(lib, name) \ - lua_pushcfunction(L, sl_##lib##_##name); \ +#define SECURE_API(lib, name) \ + lua_pushcfunction(L, sl_##lib##_##name); \ lua_setfield(L, -2, #name); - -static inline void copy_safe(lua_State *L, const char *list[], unsigned len, int from=-2, int to=-1) +static inline void copy_safe(lua_State *L, const char *list[], unsigned len, + int from = -2, int to = -1) { - if (from < 0) from = lua_gettop(L) + from + 1; - if (to < 0) to = lua_gettop(L) + to + 1; + if (from < 0) + from = lua_gettop(L) + from + 1; + if (to < 0) + to = lua_gettop(L) + to + 1; for (unsigned i = 0; i < (len / sizeof(list[0])); i++) { lua_getfield(L, from, list[i]); - lua_setfield(L, to, list[i]); + lua_setfield(L, to, list[i]); } } @@ -50,91 +51,90 @@ static inline void push_original(lua_State *L, const char *lib, const char *func { lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_GLOBALS_BACKUP); lua_getfield(L, -1, lib); - lua_remove(L, -2); // Remove globals_backup + lua_remove(L, -2); // Remove globals_backup lua_getfield(L, -1, func); - lua_remove(L, -2); // Remove lib + lua_remove(L, -2); // Remove lib } - void ScriptApiSecurity::initializeSecurity() { static const char *whitelist[] = { - "assert", - "core", - "collectgarbage", - "DIR_DELIM", - "error", - "getfenv", - "getmetatable", - "ipairs", - "next", - "pairs", - "pcall", - "print", - "rawequal", - "rawget", - "rawset", - "select", - "setfenv", - "setmetatable", - "tonumber", - "tostring", - "type", - "unpack", - "_VERSION", - "xpcall", - // Completely safe libraries - "coroutine", - "string", - "table", - "math", + "assert", + "core", + "collectgarbage", + "DIR_DELIM", + "error", + "getfenv", + "getmetatable", + "ipairs", + "next", + "pairs", + "pcall", + "print", + "rawequal", + "rawget", + "rawset", + "select", + "setfenv", + "setmetatable", + "tonumber", + "tostring", + "type", + "unpack", + "_VERSION", + "xpcall", + // Completely safe libraries + "coroutine", + "string", + "table", + "math", }; static const char *io_whitelist[] = { - "open", - "close", - "flush", - "read", - "type", - "write", + "open", + "close", + "flush", + "read", + "type", + "write", }; static const char *os_whitelist[] = { - "clock", - "date", - "difftime", - "getenv", - "setlocale", - "time", - "tmpname", + "clock", + "date", + "difftime", + "getenv", + "setlocale", + "time", + "tmpname", }; static const char *debug_whitelist[] = { - "gethook", - "traceback", - "getinfo", - "getmetatable", - "setupvalue", - "setmetatable", - "upvalueid", - "sethook", - "debug", - "setlocal", + "gethook", + "traceback", + "getinfo", + "getmetatable", + "setupvalue", + "setmetatable", + "upvalueid", + "sethook", + "debug", + "setlocal", }; static const char *package_whitelist[] = { - "config", - "cpath", - "path", - "searchpath", + "config", + "cpath", + "path", + "searchpath", }; #if USE_LUAJIT static const char *jit_whitelist[] = { - "arch", - "flush", - "off", - "on", - "opt", - "os", - "status", - "version", - "version_num", + "arch", + "flush", + "off", + "on", + "opt", + "os", + "status", + "version", + "version_num", }; #endif m_secure = true; @@ -154,7 +154,6 @@ void ScriptApiSecurity::initializeSecurity() lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_GLOBALS_BACKUP); int old_globals = lua_gettop(L); - // Copy safe base functions lua_getglobal(L, "_G"); copy_safe(L, whitelist, sizeof(whitelist)); @@ -167,21 +166,19 @@ void ScriptApiSecurity::initializeSecurity() SECURE_API(g, require); lua_pop(L, 1); - // Copy safe IO functions lua_getfield(L, old_globals, "io"); lua_newtable(L); copy_safe(L, io_whitelist, sizeof(io_whitelist)); // And replace unsafe ones - //SECURE_API(io, open); + // SECURE_API(io, open); SECURE_API(io, input); SECURE_API(io, output); SECURE_API(io, lines); lua_setglobal(L, "io"); - lua_pop(L, 1); // Pop old IO - + lua_pop(L, 1); // Pop old IO // Copy safe OS functions lua_getfield(L, old_globals, "os"); @@ -193,23 +190,21 @@ void ScriptApiSecurity::initializeSecurity() SECURE_API(os, rename); lua_setglobal(L, "os"); - lua_pop(L, 1); // Pop old OS - + lua_pop(L, 1); // Pop old OS // Copy safe debug functions lua_getfield(L, old_globals, "debug"); lua_newtable(L); copy_safe(L, debug_whitelist, sizeof(debug_whitelist)); lua_setglobal(L, "debug"); - lua_pop(L, 1); // Pop old debug - + lua_pop(L, 1); // Pop old debug // Copy safe package fields lua_getfield(L, old_globals, "package"); lua_newtable(L); copy_safe(L, package_whitelist, sizeof(package_whitelist)); lua_setglobal(L, "package"); - lua_pop(L, 1); // Pop old package + lua_pop(L, 1); // Pop old package #if USE_LUAJIT // Copy safe jit functions, if they exist @@ -219,7 +214,7 @@ void ScriptApiSecurity::initializeSecurity() copy_safe(L, jit_whitelist, sizeof(jit_whitelist)); lua_setglobal(L, "jit"); } - lua_pop(L, 1); // Pop old jit + lua_pop(L, 1); // Pop old jit #endif lua_pop(L, 1); // Pop globals_backup @@ -228,57 +223,49 @@ void ScriptApiSecurity::initializeSecurity() void ScriptApiSecurity::initializeSecurityClient() { static const char *whitelist[] = { - "assert", - "core", - "collectgarbage", - "DIR_DELIM", - "error", - "getfenv", - "ipairs", - "next", - "pairs", - "pcall", - "print", - "rawequal", - "rawget", - "rawset", - "select", - "setfenv", - // getmetatable can be used to escape the sandbox - "setmetatable", - "tonumber", - "tostring", - "type", - "unpack", - "_VERSION", - "xpcall", - // Completely safe libraries - "coroutine", - "string", - "table", - "math", - }; - static const char *os_whitelist[] = { - "clock", - "date", - "difftime", - "time" - }; - static const char *debug_whitelist[] = { - "getinfo", - "traceback" + "assert", + "core", + "collectgarbage", + "DIR_DELIM", + "error", + "getfenv", + "ipairs", + "next", + "pairs", + "pcall", + "print", + "rawequal", + "rawget", + "rawset", + "select", + "setfenv", + // getmetatable can be used to escape the sandbox + "setmetatable", + "tonumber", + "tostring", + "type", + "unpack", + "_VERSION", + "xpcall", + // Completely safe libraries + "coroutine", + "string", + "table", + "math", }; + static const char *os_whitelist[] = {"clock", "date", "difftime", "time"}; + static const char *debug_whitelist[] = {"getinfo", "traceback"}; #if USE_LUAJIT static const char *jit_whitelist[] = { - "arch", - "flush", - "off", - "on", - "opt", - "os", - "status", - "version", - "version_num", + "arch", + "flush", + "off", + "on", + "opt", + "os", + "status", + "version", + "version_num", }; #endif @@ -312,16 +299,14 @@ void ScriptApiSecurity::initializeSecurityClient() lua_newtable(L); copy_safe(L, os_whitelist, sizeof(os_whitelist)); lua_setfield(L, -3, "os"); - lua_pop(L, 1); // Pop old OS - + lua_pop(L, 1); // Pop old OS // Copy safe debug functions lua_getglobal(L, "debug"); lua_newtable(L); copy_safe(L, debug_whitelist, sizeof(debug_whitelist)); lua_setfield(L, -3, "debug"); - lua_pop(L, 1); // Pop old debug - + lua_pop(L, 1); // Pop old debug #if USE_LUAJIT // Copy safe jit functions, if they exist @@ -329,7 +314,7 @@ void ScriptApiSecurity::initializeSecurityClient() lua_newtable(L); copy_safe(L, jit_whitelist, sizeof(jit_whitelist)); lua_setfield(L, -3, "jit"); - lua_pop(L, 1); // Pop old jit + lua_pop(L, 1); // Pop old jit #endif // Set the environment to the one we created earlier @@ -339,9 +324,9 @@ void ScriptApiSecurity::initializeSecurityClient() int ScriptApiSecurity::getThread(lua_State *L) { #if LUA_VERSION_NUM <= 501 - int is_main = lua_pushthread(L); // Push the main thread + int is_main = lua_pushthread(L); // Push the main thread FATAL_ERROR_IF(!is_main, "Security: ScriptApi's Lua state " - "isn't the main Lua thread!"); + "isn't the main Lua thread!"); return lua_gettop(L); #endif return 0; @@ -349,21 +334,21 @@ int ScriptApiSecurity::getThread(lua_State *L) void ScriptApiSecurity::createEmptyEnv(lua_State *L) { - lua_newtable(L); // Create new environment + lua_newtable(L); // Create new environment lua_pushvalue(L, -1); - lua_setfield(L, -2, "_G"); // Create the _G loop + lua_setfield(L, -2, "_G"); // Create the _G loop } void ScriptApiSecurity::setLuaEnv(lua_State *L, int thread) { -#if LUA_VERSION_NUM >= 502 // Lua >= 5.2 +#if LUA_VERSION_NUM >= 502 // Lua >= 5.2 // Set the global environment lua_rawseti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS); -#else // Lua <= 5.1 - // Set the environment of the main thread +#else // Lua <= 5.1 + // Set the environment of the main thread FATAL_ERROR_IF(!lua_setfenv(L, thread), "Security: Unable to set " - "environment of the main Lua thread!"); - lua_pop(L, 1); // Pop thread + "environment of the main Lua thread!"); + lua_pop(L, 1); // Pop thread #endif } @@ -375,7 +360,8 @@ bool ScriptApiSecurity::isSecure(lua_State *L) return secure; } -bool ScriptApiSecurity::safeLoadString(lua_State *L, const std::string &code, const char *chunk_name) +bool ScriptApiSecurity::safeLoadString( + lua_State *L, const std::string &code, const char *chunk_name) { if (code.size() > 0 && code[0] == LUA_SIGNATURE[0]) { lua_pushliteral(L, "Bytecode prohibited when mod security is enabled."); @@ -386,7 +372,8 @@ bool ScriptApiSecurity::safeLoadString(lua_State *L, const std::string &code, co return true; } -bool ScriptApiSecurity::safeLoadFile(lua_State *L, const char *path, const char *display_name) +bool ScriptApiSecurity::safeLoadFile( + lua_State *L, const char *path, const char *display_name) { FILE *fp; char *chunk_name; @@ -411,7 +398,8 @@ bool ScriptApiSecurity::safeLoadFile(lua_State *L, const char *path, const char int c = std::getc(fp); if (c == '#') { // Skip the first line - while ((c = std::getc(fp)) != EOF && c != '\n') {} + while ((c = std::getc(fp)) != EOF && c != '\n') { + } if (c == '\n') std::getc(fp); start = std::ftell(fp); @@ -423,7 +411,7 @@ bool ScriptApiSecurity::safeLoadFile(lua_State *L, const char *path, const char lua_pushfstring(L, "%s: %s", path, strerror(errno)); if (path) { std::fclose(fp); - delete [] chunk_name; + delete[] chunk_name; } return false; } @@ -435,7 +423,7 @@ bool ScriptApiSecurity::safeLoadFile(lua_State *L, const char *path, const char lua_pushfstring(L, "%s: %s", path, strerror(errno)); if (path) { std::fclose(fp); - delete [] chunk_name; + delete[] chunk_name; } return false; } @@ -446,31 +434,31 @@ bool ScriptApiSecurity::safeLoadFile(lua_State *L, const char *path, const char if (num_read != size) { lua_pushliteral(L, "Error reading file to load."); if (path) - delete [] chunk_name; + delete[] chunk_name; return false; } bool result = safeLoadString(L, code, chunk_name); if (path) - delete [] chunk_name; + delete[] chunk_name; return result; } - -bool ScriptApiSecurity::checkPath(lua_State *L, const char *path, - bool write_required, bool *write_allowed) +bool ScriptApiSecurity::checkPath( + lua_State *L, const char *path, bool write_required, bool *write_allowed) { if (write_allowed) *write_allowed = false; - std::string str; // Transient + std::string str; // Transient std::string abs_path = fs::AbsolutePath(path); if (!abs_path.empty()) { // Don't allow accessing the settings file str = fs::AbsolutePath(g_settings_path); - if (str == abs_path) return false; + if (str == abs_path) + return false; } // If we couldn't find the absolute path (path doesn't exist) then @@ -482,15 +470,18 @@ bool ScriptApiSecurity::checkPath(lua_State *L, const char *path, std::string component; cur_path = fs::RemoveLastPathComponent(cur_path, &component); if (component == "..") { - // Parent components can't be allowed or we could allow something like + // Parent components can't be allowed or we could allow something + // like // /home/user/minetest/worlds/foo/noexist/../../../../../../etc/passwd. - // If we have previous non-relative elements in the path we might be - // able to remove them so that things like worlds/foo/noexist/../auth.txt - // could be allowed, but those paths will be interpreted as nonexistent - // by the operating system anyways. + // If we have previous non-relative elements in the path we might + // be able to remove them so that things like + // worlds/foo/noexist/../auth.txt could be allowed, but those + // paths will be interpreted as nonexistent by the operating + // system anyways. return false; } - removed.append(component).append(removed.empty() ? "" : DIR_DELIM + removed); + removed.append(component).append( + removed.empty() ? "" : DIR_DELIM + removed); abs_path = fs::AbsolutePath(cur_path); } if (abs_path.empty()) @@ -504,9 +495,9 @@ bool ScriptApiSecurity::checkPath(lua_State *L, const char *path, lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_SCRIPTAPI); ScriptApiBase *script; #if INDIRECT_SCRIPTAPI_RIDX - script = (ScriptApiBase *) *(void**)(lua_touserdata(L, -1)); + script = (ScriptApiBase *)*(void **)(lua_touserdata(L, -1)); #else - script = (ScriptApiBase *) lua_touserdata(L, -1); + script = (ScriptApiBase *)lua_touserdata(L, -1); #endif lua_pop(L, 1); const IGameDef *gamedef = script->getGameDef(); @@ -520,24 +511,27 @@ bool ScriptApiSecurity::checkPath(lua_State *L, const char *path, // Builtin can access anything if (mod_name == BUILTIN_MOD_NAME) { - if (write_allowed) *write_allowed = true; + if (write_allowed) + *write_allowed = true; return true; } // Allow paths in mod path - // Don't bother if write access isn't important, since it will be handled later + // Don't bother if write access isn't important, since it will be handled + // later if (write_required || write_allowed != NULL) { const ModSpec *mod = gamedef->getModSpec(mod_name); if (mod) { str = fs::AbsolutePath(mod->path); if (!str.empty() && fs::PathStartsWith(abs_path, str)) { - if (write_allowed) *write_allowed = true; + if (write_allowed) + *write_allowed = true; return true; } } } } - lua_pop(L, 1); // Pop mod name + lua_pop(L, 1); // Pop mod name // Allow read-only access to all mod directories if (!write_required) { @@ -564,7 +558,8 @@ bool ScriptApiSecurity::checkPath(lua_State *L, const char *path, } // Allow all other paths in world path if (fs::PathStartsWith(abs_path, str)) { - if (write_allowed) *write_allowed = true; + if (write_allowed) + *write_allowed = true; return true; } } @@ -573,7 +568,6 @@ bool ScriptApiSecurity::checkPath(lua_State *L, const char *path, return false; } - int ScriptApiSecurity::sl_g_dofile(lua_State *L) { int nret = sl_g_loadfile(L); @@ -588,7 +582,6 @@ int ScriptApiSecurity::sl_g_dofile(lua_State *L) return lua_gettop(L) - (top_precall - 1); } - int ScriptApiSecurity::sl_g_load(lua_State *L) { size_t len; @@ -627,12 +620,11 @@ int ScriptApiSecurity::sl_g_load(lua_State *L) return 1; } - int ScriptApiSecurity::sl_g_loadfile(lua_State *L) { #ifndef SERVER lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_SCRIPTAPI); - ScriptApiBase *script = (ScriptApiBase *) lua_touserdata(L, -1); + ScriptApiBase *script = (ScriptApiBase *)lua_touserdata(L, -1); lua_pop(L, 1); // Client implementation @@ -672,7 +664,6 @@ int ScriptApiSecurity::sl_g_loadfile(lua_State *L) return 1; } - int ScriptApiSecurity::sl_g_loadstring(lua_State *L) { const char *chunk_name = "=(load)"; @@ -695,14 +686,12 @@ int ScriptApiSecurity::sl_g_loadstring(lua_State *L) return 1; } - int ScriptApiSecurity::sl_g_require(lua_State *L) { lua_pushliteral(L, "require() is disabled when mod security is on."); return lua_error(L); } - int ScriptApiSecurity::sl_io_open(lua_State *L) { bool with_mode = lua_gettop(L) > 1; @@ -715,8 +704,7 @@ int ScriptApiSecurity::sl_io_open(lua_State *L) luaL_checktype(L, 2, LUA_TSTRING); const char *mode = lua_tostring(L, 2); write_requested = strchr(mode, 'w') != NULL || - strchr(mode, '+') != NULL || - strchr(mode, 'a') != NULL; + strchr(mode, '+') != NULL || strchr(mode, 'a') != NULL; } CHECK_SECURE_PATH_INTERNAL(L, path, write_requested, NULL); @@ -730,7 +718,6 @@ int ScriptApiSecurity::sl_io_open(lua_State *L) return 2; } - int ScriptApiSecurity::sl_io_input(lua_State *L) { if (lua_isstring(L, 1)) { @@ -744,7 +731,6 @@ int ScriptApiSecurity::sl_io_input(lua_State *L) return 1; } - int ScriptApiSecurity::sl_io_output(lua_State *L) { if (lua_isstring(L, 1)) { @@ -758,7 +744,6 @@ int ScriptApiSecurity::sl_io_output(lua_State *L) return 1; } - int ScriptApiSecurity::sl_io_lines(lua_State *L) { if (lua_isstring(L, 1)) { @@ -775,7 +760,6 @@ int ScriptApiSecurity::sl_io_lines(lua_State *L) return lua_gettop(L) - top_precall; } - int ScriptApiSecurity::sl_os_rename(lua_State *L) { luaL_checktype(L, 1, LUA_TSTRING); @@ -793,7 +777,6 @@ int ScriptApiSecurity::sl_os_rename(lua_State *L) return 2; } - int ScriptApiSecurity::sl_os_remove(lua_State *L) { luaL_checktype(L, 1, LUA_TSTRING); @@ -805,4 +788,3 @@ int ScriptApiSecurity::sl_os_remove(lua_State *L) lua_call(L, 1, 2); return 2; } - diff --git a/src/script/cpp_api/s_security.h b/src/script/cpp_api/s_security.h index 73e763548..075950845 100644 --- a/src/script/cpp_api/s_security.h +++ b/src/script/cpp_api/s_security.h @@ -21,22 +21,20 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "cpp_api/s_base.h" - -#define CHECK_SECURE_PATH_INTERNAL(L, path, write_required, ptr) \ - if (!ScriptApiSecurity::checkPath(L, path, write_required, ptr)) { \ - throw LuaError(std::string("Mod security: Blocked attempted ") + \ - (write_required ? "write to " : "read from ") + path); \ +#define CHECK_SECURE_PATH_INTERNAL(L, path, write_required, ptr) \ + if (!ScriptApiSecurity::checkPath(L, path, write_required, ptr)) { \ + throw LuaError(std::string("Mod security: Blocked attempted ") + \ + (write_required ? "write to " : "read from ") + path); \ } -#define CHECK_SECURE_PATH(L, path, write_required) \ - if (ScriptApiSecurity::isSecure(L)) { \ - CHECK_SECURE_PATH_INTERNAL(L, path, write_required, NULL); \ +#define CHECK_SECURE_PATH(L, path, write_required) \ + if (ScriptApiSecurity::isSecure(L)) { \ + CHECK_SECURE_PATH_INTERNAL(L, path, write_required, NULL); \ } -#define CHECK_SECURE_PATH_POSSIBLE_WRITE(L, path, ptr) \ - if (ScriptApiSecurity::isSecure(L)) { \ - CHECK_SECURE_PATH_INTERNAL(L, path, false, ptr); \ +#define CHECK_SECURE_PATH_POSSIBLE_WRITE(L, path, ptr) \ + if (ScriptApiSecurity::isSecure(L)) { \ + CHECK_SECURE_PATH_INTERNAL(L, path, false, ptr); \ } - class ScriptApiSecurity : virtual public ScriptApiBase { public: @@ -51,12 +49,14 @@ public: // Checks if the Lua state has been secured static bool isSecure(lua_State *L); // Loads a string as Lua code safely (doesn't allow bytecode). - static bool safeLoadString(lua_State *L, const std::string &code, const char *chunk_name); + static bool safeLoadString( + lua_State *L, const std::string &code, const char *chunk_name); // Loads a file as Lua code safely (doesn't allow bytecode). - static bool safeLoadFile(lua_State *L, const char *path, const char *display_name = NULL); + static bool safeLoadFile( + lua_State *L, const char *path, const char *display_name = NULL); // Checks if mods are allowed to read (and optionally write) to the path static bool checkPath(lua_State *L, const char *path, bool write_required, - bool *write_allowed=NULL); + bool *write_allowed = NULL); private: // Syntax: "sl_" '_' diff --git a/src/script/cpp_api/s_server.cpp b/src/script/cpp_api/s_server.cpp index 96cb28b28..1cb3f99f1 100644 --- a/src/script/cpp_api/s_server.cpp +++ b/src/script/cpp_api/s_server.cpp @@ -21,10 +21,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "cpp_api/s_internal.h" #include "common/c_converter.h" -bool ScriptApiServer::getAuth(const std::string &playername, - std::string *dst_password, - std::set *dst_privs, - s64 *dst_last_login) +bool ScriptApiServer::getAuth(const std::string &playername, std::string *dst_password, + std::set *dst_privs, s64 *dst_last_login) { SCRIPTAPI_PRECHECKHEADER @@ -54,10 +52,10 @@ bool ScriptApiServer::getAuth(const std::string &playername, throw LuaError("Authentication handler didn't return privilege table"); if (dst_privs) readPrivileges(-1, *dst_privs); - lua_pop(L, 1); // Remove key from privs table + lua_pop(L, 1); // Remove key from privs table s64 last_login; - if(!getintfield(L, -1, "last_login", last_login)) + if (!getintfield(L, -1, "last_login", last_login)) throw LuaError("Authentication handler didn't return last_login"); if (dst_last_login) *dst_last_login = (s64)last_login; @@ -71,7 +69,7 @@ void ScriptApiServer::getAuthHandler() lua_getglobal(L, "core"); lua_getfield(L, -1, "registered_auth_handler"); - if (lua_isnil(L, -1)){ + if (lua_isnil(L, -1)) { lua_pop(L, 1); lua_getfield(L, -1, "builtin_auth_handler"); } @@ -102,8 +100,8 @@ void ScriptApiServer::readPrivileges(int index, std::set &result) } } -void ScriptApiServer::createAuth(const std::string &playername, - const std::string &password) +void ScriptApiServer::createAuth( + const std::string &playername, const std::string &password) { SCRIPTAPI_PRECHECKHEADER @@ -119,8 +117,8 @@ void ScriptApiServer::createAuth(const std::string &playername, lua_pop(L, 1); // Pop error handler } -bool ScriptApiServer::setPassword(const std::string &playername, - const std::string &password) +bool ScriptApiServer::setPassword( + const std::string &playername, const std::string &password) { SCRIPTAPI_PRECHECKHEADER @@ -137,8 +135,7 @@ bool ScriptApiServer::setPassword(const std::string &playername, return lua_toboolean(L, -1); } -bool ScriptApiServer::on_chat_message(const std::string &name, - const std::string &message) +bool ScriptApiServer::on_chat_message(const std::string &name, const std::string &message) { SCRIPTAPI_PRECHECKHEADER @@ -174,8 +171,8 @@ void ScriptApiServer::on_shutdown() runCallbacks(0, RUN_CALLBACKS_MODE_FIRST); } -std::string ScriptApiServer::formatChatMessage(const std::string &name, - const std::string &message) +std::string ScriptApiServer::formatChatMessage( + const std::string &name, const std::string &message) { SCRIPTAPI_PRECHECKHEADER diff --git a/src/script/cpp_api/s_server.h b/src/script/cpp_api/s_server.h index d8639cba7..db574bd56 100644 --- a/src/script/cpp_api/s_server.h +++ b/src/script/cpp_api/s_server.h @@ -22,8 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "cpp_api/s_base.h" #include -class ScriptApiServer - : virtual public ScriptApiBase +class ScriptApiServer : virtual public ScriptApiBase { public: // Calls on_chat_message handlers @@ -37,18 +36,15 @@ public: void on_shutdown(); // Calls core.format_chat_message - std::string formatChatMessage(const std::string &name, - const std::string &message); + std::string formatChatMessage( + const std::string &name, const std::string &message); /* auth */ - bool getAuth(const std::string &playername, - std::string *dst_password, - std::set *dst_privs, - s64 *dst_last_login = nullptr); - void createAuth(const std::string &playername, - const std::string &password); - bool setPassword(const std::string &playername, - const std::string &password); + bool getAuth(const std::string &playername, std::string *dst_password, + std::set *dst_privs, s64 *dst_last_login = nullptr); + void createAuth(const std::string &playername, const std::string &password); + bool setPassword(const std::string &playername, const std::string &password); + private: void getAuthHandler(); void readPrivileges(int index, std::set &result); diff --git a/src/script/lua_api/l_areastore.cpp b/src/script/lua_api/l_areastore.cpp index 908c766b0..d043bfc90 100644 --- a/src/script/lua_api/l_areastore.cpp +++ b/src/script/lua_api/l_areastore.cpp @@ -17,7 +17,6 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ - #include "lua_api/l_areastore.h" #include "lua_api/l_internal.h" #include "common/c_converter.h" @@ -27,8 +26,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "filesys.h" #include -static inline void get_data_and_border_flags(lua_State *L, u8 start_i, - bool *borders, bool *data) +static inline void get_data_and_border_flags( + lua_State *L, u8 start_i, bool *borders, bool *data) { if (!lua_isboolean(L, start_i)) return; @@ -38,8 +37,8 @@ static inline void get_data_and_border_flags(lua_State *L, u8 start_i, *data = lua_toboolean(L, start_i + 1); } -static void push_area(lua_State *L, const Area *a, - bool include_borders, bool include_data) +static void push_area( + lua_State *L, const Area *a, bool include_borders, bool include_data) { if (!include_borders && !include_data) { lua_pushboolean(L, true); @@ -58,8 +57,8 @@ static void push_area(lua_State *L, const Area *a, } } -static inline void push_areas(lua_State *L, const std::vector &areas, - bool borders, bool data) +static inline void push_areas( + lua_State *L, const std::vector &areas, bool borders, bool data) { lua_newtable(L); size_t cnt = areas.size(); @@ -71,8 +70,7 @@ static inline void push_areas(lua_State *L, const std::vector &areas, } // Deserializes value and handles errors -static int deserialization_helper(lua_State *L, AreaStore *as, - std::istream &is) +static int deserialization_helper(lua_State *L, AreaStore *as, std::istream &is) { try { as->deserialize(is); @@ -328,9 +326,9 @@ int LuaAreaStore::create_object(lua_State *L) { NO_MAP_LOCK_REQUIRED; - LuaAreaStore *o = (lua_isstring(L, 1)) ? - new LuaAreaStore(readParam(L, 1)) : - new LuaAreaStore(); + LuaAreaStore *o = (lua_isstring(L, 1)) + ? new LuaAreaStore(readParam(L, 1)) + : new LuaAreaStore(); *(void **)(lua_newuserdata(L, sizeof(void *))) = o; luaL_getmetatable(L, className); @@ -348,7 +346,7 @@ LuaAreaStore *LuaAreaStore::checkobject(lua_State *L, int narg) if (!ud) luaL_typerror(L, narg, className); - return *(LuaAreaStore **)ud; // unbox pointer + return *(LuaAreaStore **)ud; // unbox pointer } void LuaAreaStore::Register(lua_State *L) @@ -360,7 +358,7 @@ void LuaAreaStore::Register(lua_State *L) lua_pushliteral(L, "__metatable"); lua_pushvalue(L, methodtable); - lua_settable(L, metatable); // hide metatable from Lua getmetatable() + lua_settable(L, metatable); // hide metatable from Lua getmetatable() lua_pushliteral(L, "__index"); lua_pushvalue(L, methodtable); @@ -370,27 +368,22 @@ void LuaAreaStore::Register(lua_State *L) lua_pushcfunction(L, gc_object); lua_settable(L, metatable); - lua_pop(L, 1); // drop metatable + lua_pop(L, 1); // drop metatable - luaL_openlib(L, 0, methods, 0); // fill methodtable - lua_pop(L, 1); // drop methodtable + luaL_openlib(L, 0, methods, 0); // fill methodtable + lua_pop(L, 1); // drop methodtable // Can be created from Lua (AreaStore()) lua_register(L, className, create_object); } const char LuaAreaStore::className[] = "AreaStore"; -const luaL_Reg LuaAreaStore::methods[] = { - luamethod(LuaAreaStore, get_area), - luamethod(LuaAreaStore, get_areas_for_pos), - luamethod(LuaAreaStore, get_areas_in_area), - luamethod(LuaAreaStore, insert_area), - luamethod(LuaAreaStore, reserve), - luamethod(LuaAreaStore, remove_area), - luamethod(LuaAreaStore, set_cache_params), - luamethod(LuaAreaStore, to_string), - luamethod(LuaAreaStore, to_file), - luamethod(LuaAreaStore, from_string), - luamethod(LuaAreaStore, from_file), - {0,0} -}; +const luaL_Reg LuaAreaStore::methods[] = {luamethod(LuaAreaStore, get_area), + luamethod(LuaAreaStore, get_areas_for_pos), + luamethod(LuaAreaStore, get_areas_in_area), + luamethod(LuaAreaStore, insert_area), luamethod(LuaAreaStore, reserve), + luamethod(LuaAreaStore, remove_area), + luamethod(LuaAreaStore, set_cache_params), + luamethod(LuaAreaStore, to_string), luamethod(LuaAreaStore, to_file), + luamethod(LuaAreaStore, from_string), luamethod(LuaAreaStore, from_file), + {0, 0}}; diff --git a/src/script/lua_api/l_base.cpp b/src/script/lua_api/l_base.cpp index 011434845..8ae03d6c8 100644 --- a/src/script/lua_api/l_base.cpp +++ b/src/script/lua_api/l_base.cpp @@ -32,9 +32,9 @@ ScriptApiBase *ModApiBase::getScriptApiBase(lua_State *L) lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_SCRIPTAPI); ScriptApiBase *sapi_ptr; #if INDIRECT_SCRIPTAPI_RIDX - sapi_ptr = (ScriptApiBase*) *(void**)(lua_touserdata(L, -1)); + sapi_ptr = (ScriptApiBase *)*(void **)(lua_touserdata(L, -1)); #else - sapi_ptr = (ScriptApiBase*) lua_touserdata(L, -1); + sapi_ptr = (ScriptApiBase *)lua_touserdata(L, -1); #endif lua_pop(L, 1); return sapi_ptr; @@ -93,9 +93,8 @@ std::string ModApiBase::getCurrentModPath(lua_State *L) return mod->path; } - -bool ModApiBase::registerFunction(lua_State *L, const char *name, - lua_CFunction func, int top) +bool ModApiBase::registerFunction( + lua_State *L, const char *name, lua_CFunction func, int top) { // TODO: Check presence first! @@ -126,7 +125,7 @@ int ModApiBase::l_deprecated_function(lua_State *L) // Get parent class to get the wrappers map luaL_checktype(L, 1, LUA_TUSERDATA); void *ud = lua_touserdata(L, 1); - ModApiBase *o = *(ModApiBase**)ud; + ModApiBase *o = *(ModApiBase **)ud; // New function and new function name auto it = o->m_deprecated_wrappers.find(ar.name); @@ -136,12 +135,13 @@ int ModApiBase::l_deprecated_function(lua_State *L) backtrace.append(":").append(std::to_string(ar.currentline)); u64 hash = murmur_hash_64_ua(backtrace.data(), backtrace.length(), 0xBADBABE); - if (std::find(deprecated_logged.begin(), deprecated_logged.end(), hash) - == deprecated_logged.end()) { + if (std::find(deprecated_logged.begin(), deprecated_logged.end(), hash) == + deprecated_logged.end()) { deprecated_logged.emplace_back(hash); - warningstream << "Call to deprecated function '" << ar.name << "', please use '" - << it->second.name << "' at " << backtrace << std::endl; + warningstream << "Call to deprecated function '" << ar.name + << "', please use '" << it->second.name << "' at " + << backtrace << std::endl; if (m_error_deprecated_calls) script_error(L, LUA_ERRRUN, NULL, NULL); @@ -172,8 +172,8 @@ void ModApiBase::markAliasDeprecated(luaL_Reg *reg) // Do not inline struct. Breaks MSVC or is error-prone original_reg.name = last_name; original_reg.func = reg->func; - m_deprecated_wrappers.emplace( - std::pair(reg->name, original_reg)); + m_deprecated_wrappers.emplace(std::pair( + reg->name, original_reg)); reg->func = l_deprecated_function; } diff --git a/src/script/lua_api/l_base.h b/src/script/lua_api/l_base.h index 0cbee7756..bb806d631 100644 --- a/src/script/lua_api/l_base.h +++ b/src/script/lua_api/l_base.h @@ -41,43 +41,44 @@ class Server; class Environment; class ServerInventoryManager; -class ModApiBase : protected LuaHelper { +class ModApiBase : protected LuaHelper +{ public: - static ScriptApiBase* getScriptApiBase(lua_State *L); - static Server* getServer(lua_State *L); + static ScriptApiBase *getScriptApiBase(lua_State *L); + static Server *getServer(lua_State *L); static ServerInventoryManager *getServerInventoryMgr(lua_State *L); - #ifndef SERVER - static Client* getClient(lua_State *L); - static Game* getGame(lua_State *L); - static GUIEngine* getGuiEngine(lua_State *L); - #endif // !SERVER +#ifndef SERVER + static Client *getClient(lua_State *L); + static Game *getGame(lua_State *L); + static GUIEngine *getGuiEngine(lua_State *L); +#endif // !SERVER - static IGameDef* getGameDef(lua_State *L); - static Environment* getEnv(lua_State *L); + static IGameDef *getGameDef(lua_State *L); + static Environment *getEnv(lua_State *L); // When we are not loading the mod, this function returns "." - static std::string getCurrentModPath(lua_State *L); + static std::string getCurrentModPath(lua_State *L); // Get an arbitrary subclass of ScriptApiBase // by using dynamic_cast<> on getScriptApiBase() - template - static T* getScriptApi(lua_State *L) { + template static T *getScriptApi(lua_State *L) + { ScriptApiBase *scriptIface = getScriptApiBase(L); - T *scriptIfaceDowncast = dynamic_cast(scriptIface); + T *scriptIfaceDowncast = dynamic_cast(scriptIface); if (!scriptIfaceDowncast) { - throw LuaError("Requested unavailable ScriptApi - core engine bug!"); + throw LuaError("Requested unavailable ScriptApi - core engine " + "bug!"); } return scriptIfaceDowncast; } - static bool registerFunction(lua_State *L, - const char* name, - lua_CFunction func, - int top); + static bool registerFunction( + lua_State *L, const char *name, lua_CFunction func, int top); static int l_deprecated_function(lua_State *L); static void markAliasDeprecated(luaL_Reg *reg); + private: // = { , } static std::unordered_map m_deprecated_wrappers; diff --git a/src/script/lua_api/l_client.cpp b/src/script/lua_api/l_client.cpp index 9961471ff..1bc4829d4 100644 --- a/src/script/lua_api/l_client.cpp +++ b/src/script/lua_api/l_client.cpp @@ -38,11 +38,12 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "nodedef.h" #include "client/keycode.h" -#define checkCSMRestrictionFlag(flag) \ - ( getClient(L)->checkCSMRestrictionFlag(CSMRestrictionFlags::flag) ) +#define checkCSMRestrictionFlag(flag) \ + (getClient(L)->checkCSMRestrictionFlag(CSMRestrictionFlags::flag)) // Not the same as FlagDesc, which contains an `u32 flag` -struct CSMFlagDesc { +struct CSMFlagDesc +{ const char *name; u64 flag; }; @@ -53,14 +54,12 @@ struct CSMFlagDesc { in network/networkprotocol.h */ const static CSMFlagDesc flagdesc_csm_restriction[] = { - {"load_client_mods", CSM_RF_LOAD_CLIENT_MODS}, - {"chat_messages", CSM_RF_CHAT_MESSAGES}, - {"read_itemdefs", CSM_RF_READ_ITEMDEFS}, - {"read_nodedefs", CSM_RF_READ_NODEDEFS}, - {"lookup_nodes", CSM_RF_LOOKUP_NODES}, - {"read_playerinfo", CSM_RF_READ_PLAYERINFO}, - {NULL, 0} -}; + {"load_client_mods", CSM_RF_LOAD_CLIENT_MODS}, + {"chat_messages", CSM_RF_CHAT_MESSAGES}, + {"read_itemdefs", CSM_RF_READ_ITEMDEFS}, + {"read_nodedefs", CSM_RF_READ_NODEDEFS}, + {"lookup_nodes", CSM_RF_LOOKUP_NODES}, + {"read_playerinfo", CSM_RF_READ_PLAYERINFO}, {NULL, 0}}; // get_current_modname() int ModApiClient::l_get_current_modname(lua_State *L) @@ -422,7 +421,7 @@ int ModApiClient::l_send_damage(lua_State *L) { u16 damage = luaL_checknumber(L, 1); getClient(L)->sendDamage(damage); - return 0; + return 0; } // place_node(pos) @@ -433,7 +432,8 @@ int ModApiClient::l_place_node(lua_State *L) LocalPlayer *player = client->getEnv().getLocalPlayer(); ItemStack selected_item, hand_item; player->getWieldedItem(&selected_item, &hand_item); - const ItemDefinition &selected_def = selected_item.getDefinition(getGameDef(L)->idef()); + const ItemDefinition &selected_def = + selected_item.getDefinition(getGameDef(L)->idef()); v3s16 pos = read_v3s16(L, 1); PointedThing pointed; pointed.type = POINTEDTHING_NODE; @@ -465,7 +465,7 @@ int ModApiClient::l_get_inventory(lua_State *L) InventoryLocation inventory_location; Inventory *inventory; std::string location; - + location = readParam(L, 1); try { @@ -475,7 +475,7 @@ int ModApiClient::l_get_inventory(lua_State *L) } catch (SerializationError &) { lua_pushnil(L); } - + return 1; } @@ -508,17 +508,17 @@ int ModApiClient::l_drop_selected_item(lua_State *L) int ModApiClient::l_get_objects_inside_radius(lua_State *L) { ClientEnvironment &env = getClient(L)->getEnv(); - + v3f pos = checkFloatPos(L, 1); float radius = readParam(L, 2) * BS; - + std::vector objs; env.getActiveObjects(pos, radius, objs); - + int i = 0; lua_createtable(L, objs.size(), 0); for (const auto obj : objs) { - ClientObjectRef::create(L, obj.obj); // TODO: getObjectRefOrCreate + ClientObjectRef::create(L, obj.obj); // TODO: getObjectRefOrCreate lua_rawseti(L, -2, ++i); } return 1; diff --git a/src/script/lua_api/l_clientobject.cpp b/src/script/lua_api/l_clientobject.cpp index d88b538a1..05b3e2850 100644 --- a/src/script/lua_api/l_clientobject.cpp +++ b/src/script/lua_api/l_clientobject.cpp @@ -146,7 +146,7 @@ int ClientObjectRef::l_punch(lua_State *L) { ClientObjectRef *ref = checkobject(L, 1); GenericCAO *gcao = get_generic_cao(ref, L); - PointedThing pointed(gcao->getId(), v3f(0,0,0), v3s16(0,0,0), 0); + PointedThing pointed(gcao->getId(), v3f(0, 0, 0), v3s16(0, 0, 0), 0); getClient(L)->interact(INTERACT_START_DIGGING, pointed); return 0; } @@ -155,7 +155,7 @@ int ClientObjectRef::l_rightclick(lua_State *L) { ClientObjectRef *ref = checkobject(L, 1); GenericCAO *gcao = get_generic_cao(ref, L); - PointedThing pointed(gcao->getId(), v3f(0,0,0), v3s16(0,0,0), 0); + PointedThing pointed(gcao->getId(), v3f(0, 0, 0), v3s16(0, 0, 0), 0); getClient(L)->interact(INTERACT_PLACE, pointed); return 0; } @@ -223,6 +223,5 @@ luaL_Reg ClientObjectRef::methods[] = {luamethod(ClientObjectRef, get_pos), luamethod(ClientObjectRef, get_attach), luamethod(ClientObjectRef, get_nametag), luamethod(ClientObjectRef, get_item_textures), - luamethod(ClientObjectRef, get_max_hp), - luamethod(ClientObjectRef, punch), + luamethod(ClientObjectRef, get_max_hp), luamethod(ClientObjectRef, punch), luamethod(ClientObjectRef, rightclick), {0, 0}}; diff --git a/src/script/lua_api/l_clientobject.h b/src/script/lua_api/l_clientobject.h index 521591444..a4516e047 100644 --- a/src/script/lua_api/l_clientobject.h +++ b/src/script/lua_api/l_clientobject.h @@ -60,7 +60,7 @@ private: // is_player(self) static int l_is_player(lua_State *L); - + // is_local_player(self) static int l_is_local_player(lua_State *L); diff --git a/src/script/lua_api/l_craft.cpp b/src/script/lua_api/l_craft.cpp index 18622ee00..07b45eaff 100644 --- a/src/script/lua_api/l_craft.cpp +++ b/src/script/lua_api/l_craft.cpp @@ -17,7 +17,6 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ - #include "lua_api/l_craft.h" #include "lua_api/l_internal.h" #include "lua_api/l_item.h" @@ -26,46 +25,45 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "server.h" #include "craftdef.h" -struct EnumString ModApiCraft::es_CraftMethod[] = -{ - {CRAFT_METHOD_NORMAL, "normal"}, - {CRAFT_METHOD_COOKING, "cooking"}, - {CRAFT_METHOD_FUEL, "fuel"}, - {0, NULL}, +struct EnumString ModApiCraft::es_CraftMethod[] = { + {CRAFT_METHOD_NORMAL, "normal"}, + {CRAFT_METHOD_COOKING, "cooking"}, + {CRAFT_METHOD_FUEL, "fuel"}, + {0, NULL}, }; // helper for register_craft -bool ModApiCraft::readCraftRecipeShaped(lua_State *L, int index, - int &width, std::vector &recipe) +bool ModApiCraft::readCraftRecipeShaped( + lua_State *L, int index, int &width, std::vector &recipe) { - if(index < 0) + if (index < 0) index = lua_gettop(L) + 1 + index; - if(!lua_istable(L, index)) + if (!lua_istable(L, index)) return false; lua_pushnil(L); int rowcount = 0; - while(lua_next(L, index) != 0){ + while (lua_next(L, index) != 0) { int colcount = 0; // key at index -2 and value at index -1 - if(!lua_istable(L, -1)) + if (!lua_istable(L, -1)) return false; int table2 = lua_gettop(L); lua_pushnil(L); - while(lua_next(L, table2) != 0){ + while (lua_next(L, table2) != 0) { // key at index -2 and value at index -1 - if(!lua_isstring(L, -1)) + if (!lua_isstring(L, -1)) return false; recipe.emplace_back(readParam(L, -1)); // removes value, keeps key for next iteration lua_pop(L, 1); colcount++; } - if(rowcount == 0){ + if (rowcount == 0) { width = colcount; } else { - if(colcount != width) + if (colcount != width) return false; } // removes value, keeps key for next iteration @@ -76,19 +74,19 @@ bool ModApiCraft::readCraftRecipeShaped(lua_State *L, int index, } // helper for register_craft -bool ModApiCraft::readCraftRecipeShapeless(lua_State *L, int index, - std::vector &recipe) +bool ModApiCraft::readCraftRecipeShapeless( + lua_State *L, int index, std::vector &recipe) { - if(index < 0) + if (index < 0) index = lua_gettop(L) + 1 + index; - if(!lua_istable(L, index)) + if (!lua_istable(L, index)) return false; lua_pushnil(L); - while(lua_next(L, index) != 0){ + while (lua_next(L, index) != 0) { // key at index -2 and value at index -1 - if(!lua_isstring(L, -1)) + if (!lua_isstring(L, -1)) return false; recipe.emplace_back(readParam(L, -1)); // removes value, keeps key for next iteration @@ -98,27 +96,27 @@ bool ModApiCraft::readCraftRecipeShapeless(lua_State *L, int index, } // helper for register_craft -bool ModApiCraft::readCraftReplacements(lua_State *L, int index, - CraftReplacements &replacements) +bool ModApiCraft::readCraftReplacements( + lua_State *L, int index, CraftReplacements &replacements) { - if(index < 0) + if (index < 0) index = lua_gettop(L) + 1 + index; - if(!lua_istable(L, index)) + if (!lua_istable(L, index)) return false; lua_pushnil(L); - while(lua_next(L, index) != 0){ + while (lua_next(L, index) != 0) { // key at index -2 and value at index -1 - if(!lua_istable(L, -1)) + if (!lua_istable(L, -1)) return false; lua_rawgeti(L, -1, 1); - if(!lua_isstring(L, -1)) + if (!lua_isstring(L, -1)) return false; std::string replace_from = readParam(L, -1); lua_pop(L, 1); lua_rawgeti(L, -1, 2); - if(!lua_isstring(L, -1)) + if (!lua_isstring(L, -1)) return false; std::string replace_to = readParam(L, -1); lua_pop(L, 1); @@ -132,20 +130,19 @@ bool ModApiCraft::readCraftReplacements(lua_State *L, int index, int ModApiCraft::l_register_craft(lua_State *L) { NO_MAP_LOCK_REQUIRED; - //infostream<<"register_craft"<getWritableCraftDefManager(); + IWritableCraftDefManager *craftdef = getServer(L)->getWritableCraftDefManager(); std::string type = getstringfield_default(L, table, "type", "shaped"); /* CraftDefinitionShaped */ - if(type == "shaped"){ + if (type == "shaped") { std::string output = getstringfield_default(L, table, "output", ""); if (output.empty()) throw LuaError("Crafting definition is missing an output"); @@ -153,20 +150,22 @@ int ModApiCraft::l_register_craft(lua_State *L) int width = 0; std::vector recipe; lua_getfield(L, table, "recipe"); - if(lua_isnil(L, -1)) + if (lua_isnil(L, -1)) throw LuaError("Crafting definition is missing a recipe" - " (output=\"" + output + "\")"); - if(!readCraftRecipeShaped(L, -1, width, recipe)) + " (output=\"" + + output + "\")"); + if (!readCraftRecipeShaped(L, -1, width, recipe)) throw LuaError("Invalid crafting recipe" - " (output=\"" + output + "\")"); + " (output=\"" + + output + "\")"); CraftReplacements replacements; lua_getfield(L, table, "replacements"); - if(!lua_isnil(L, -1)) - { - if(!readCraftReplacements(L, -1, replacements)) + if (!lua_isnil(L, -1)) { + if (!readCraftReplacements(L, -1, replacements)) throw LuaError("Invalid replacements" - " (output=\"" + output + "\")"); + " (output=\"" + + output + "\")"); } CraftDefinition *def = new CraftDefinitionShaped( @@ -176,29 +175,31 @@ int ModApiCraft::l_register_craft(lua_State *L) /* CraftDefinitionShapeless */ - else if(type == "shapeless"){ + else if (type == "shapeless") { std::string output = getstringfield_default(L, table, "output", ""); if (output.empty()) throw LuaError("Crafting definition (shapeless)" - " is missing an output"); + " is missing an output"); std::vector recipe; lua_getfield(L, table, "recipe"); - if(lua_isnil(L, -1)) + if (lua_isnil(L, -1)) throw LuaError("Crafting definition (shapeless)" - " is missing a recipe" - " (output=\"" + output + "\")"); - if(!readCraftRecipeShapeless(L, -1, recipe)) + " is missing a recipe" + " (output=\"" + + output + "\")"); + if (!readCraftRecipeShapeless(L, -1, recipe)) throw LuaError("Invalid crafting recipe" - " (output=\"" + output + "\")"); + " (output=\"" + + output + "\")"); CraftReplacements replacements; lua_getfield(L, table, "replacements"); - if(!lua_isnil(L, -1)) - { - if(!readCraftReplacements(L, -1, replacements)) + if (!lua_isnil(L, -1)) { + if (!readCraftReplacements(L, -1, replacements)) throw LuaError("Invalid replacements" - " (output=\"" + output + "\")"); + " (output=\"" + + output + "\")"); } CraftDefinition *def = new CraftDefinitionShapeless( @@ -208,38 +209,38 @@ int ModApiCraft::l_register_craft(lua_State *L) /* CraftDefinitionToolRepair */ - else if(type == "toolrepair"){ - float additional_wear = getfloatfield_default(L, table, - "additional_wear", 0.0); + else if (type == "toolrepair") { + float additional_wear = + getfloatfield_default(L, table, "additional_wear", 0.0); - CraftDefinition *def = new CraftDefinitionToolRepair( - additional_wear); + CraftDefinition *def = new CraftDefinitionToolRepair(additional_wear); craftdef->registerCraft(def, getServer(L)); } /* CraftDefinitionCooking */ - else if(type == "cooking"){ + else if (type == "cooking") { std::string output = getstringfield_default(L, table, "output", ""); if (output.empty()) throw LuaError("Crafting definition (cooking)" - " is missing an output"); + " is missing an output"); std::string recipe = getstringfield_default(L, table, "recipe", ""); if (recipe.empty()) throw LuaError("Crafting definition (cooking)" - " is missing a recipe" - " (output=\"" + output + "\")"); + " is missing a recipe" + " (output=\"" + + output + "\")"); float cooktime = getfloatfield_default(L, table, "cooktime", 3.0); CraftReplacements replacements; lua_getfield(L, table, "replacements"); - if(!lua_isnil(L, -1)) - { - if(!readCraftReplacements(L, -1, replacements)) + if (!lua_isnil(L, -1)) { + if (!readCraftReplacements(L, -1, replacements)) throw LuaError("Invalid replacements" - " (cooking output=\"" + output + "\")"); + " (cooking output=\"" + + output + "\")"); } CraftDefinition *def = new CraftDefinitionCooking( @@ -249,29 +250,27 @@ int ModApiCraft::l_register_craft(lua_State *L) /* CraftDefinitionFuel */ - else if(type == "fuel"){ + else if (type == "fuel") { std::string recipe = getstringfield_default(L, table, "recipe", ""); if (recipe.empty()) throw LuaError("Crafting definition (fuel)" - " is missing a recipe"); + " is missing a recipe"); float burntime = getfloatfield_default(L, table, "burntime", 1.0); CraftReplacements replacements; lua_getfield(L, table, "replacements"); - if(!lua_isnil(L, -1)) - { - if(!readCraftReplacements(L, -1, replacements)) + if (!lua_isnil(L, -1)) { + if (!readCraftReplacements(L, -1, replacements)) throw LuaError("Invalid replacements" - " (fuel recipe=\"" + recipe + "\")"); + " (fuel recipe=\"" + + recipe + "\")"); } - CraftDefinition *def = new CraftDefinitionFuel( - recipe, burntime, replacements); + CraftDefinition *def = + new CraftDefinitionFuel(recipe, burntime, replacements); craftdef->registerCraft(def, getServer(L)); - } - else - { + } else { throw LuaError("Unknown crafting definition type: \"" + type + "\""); } @@ -287,8 +286,7 @@ int ModApiCraft::l_clear_craft(lua_State *L) int table = 1; // Get the writable craft definition manager from the server - IWritableCraftDefManager *craftdef = - getServer(L)->getWritableCraftDefManager(); + IWritableCraftDefManager *craftdef = getServer(L)->getWritableCraftDefManager(); std::string output = getstringfield_default(L, table, "output", ""); std::string type = getstringfield_default(L, table, "type", "shaped"); @@ -334,7 +332,7 @@ int ModApiCraft::l_clear_craft(lua_State *L) std::string rec = getstringfield_default(L, table, "recipe", ""); if (rec.empty()) throw LuaError("Crafting definition (cooking)" - " is missing a recipe"); + " is missing a recipe"); recipe.push_back(rec); } /* @@ -345,7 +343,7 @@ int ModApiCraft::l_clear_craft(lua_State *L) std::string rec = getstringfield_default(L, table, "recipe", ""); if (rec.empty()) throw LuaError("Crafting definition (fuel)" - " is missing a recipe"); + " is missing a recipe"); recipe.push_back(rec); } else { throw LuaError("Unknown crafting definition type: \"" + type + "\""); @@ -374,15 +372,15 @@ int ModApiCraft::l_get_craft_result(lua_State *L) int input_i = 1; std::string method_s = getstringfield_default(L, input_i, "method", "normal"); - enum CraftMethod method = (CraftMethod)getenumfield(L, input_i, "method", - es_CraftMethod, CRAFT_METHOD_NORMAL); + enum CraftMethod method = (CraftMethod)getenumfield( + L, input_i, "method", es_CraftMethod, CRAFT_METHOD_NORMAL); int width = 1; lua_getfield(L, input_i, "width"); - if(lua_isnumber(L, -1)) + if (lua_isnumber(L, -1)) width = luaL_checkinteger(L, -1); lua_pop(L, 1); lua_getfield(L, input_i, "items"); - std::vector items = read_items(L, -1,getServer(L)); + std::vector items = read_items(L, -1, getServer(L)); lua_pop(L, 1); // items IGameDef *gdef = getServer(L); @@ -417,9 +415,7 @@ int ModApiCraft::l_get_craft_result(lua_State *L) return 2; } - -static void push_craft_recipe(lua_State *L, IGameDef *gdef, - const CraftDefinition *recipe, +static void push_craft_recipe(lua_State *L, IGameDef *gdef, const CraftDefinition *recipe, const CraftOutput &tmpout) { CraftInput input = recipe->getInput(tmpout, gdef); @@ -462,8 +458,7 @@ static void push_craft_recipe(lua_State *L, IGameDef *gdef, } static void push_craft_recipes(lua_State *L, IGameDef *gdef, - const std::vector &recipes, - const CraftOutput &output) + const std::vector &recipes, const CraftOutput &output) { lua_createtable(L, recipes.size(), 0); @@ -472,7 +467,7 @@ static void push_craft_recipes(lua_State *L, IGameDef *gdef, return; } - std::vector::const_iterator it = recipes.begin(); + std::vector::const_iterator it = recipes.begin(); for (unsigned i = 0; it != recipes.end(); ++it) { lua_newtable(L); push_craft_recipe(L, gdef, *it, output); @@ -480,7 +475,6 @@ static void push_craft_recipes(lua_State *L, IGameDef *gdef, } } - // get_craft_recipe(result item) int ModApiCraft::l_get_craft_recipe(lua_State *L) { @@ -489,8 +483,8 @@ int ModApiCraft::l_get_craft_recipe(lua_State *L) std::string item = luaL_checkstring(L, 1); Server *server = getServer(L); CraftOutput output(item, 0); - std::vector recipes = server->cdef() - ->getCraftRecipes(output, server, 1); + std::vector recipes = + server->cdef()->getCraftRecipes(output, server, 1); lua_createtable(L, 1, 0); @@ -512,8 +506,8 @@ int ModApiCraft::l_get_all_craft_recipes(lua_State *L) std::string item = luaL_checkstring(L, 1); Server *server = getServer(L); CraftOutput output(item, 0); - std::vector recipes = server->cdef() - ->getCraftRecipes(output, server); + std::vector recipes = + server->cdef()->getCraftRecipes(output, server); push_craft_recipes(L, server, recipes, output); return 1; diff --git a/src/script/lua_api/l_craft.h b/src/script/lua_api/l_craft.h index 9002b23ef..5ba3d18fe 100644 --- a/src/script/lua_api/l_craft.h +++ b/src/script/lua_api/l_craft.h @@ -26,7 +26,8 @@ with this program; if not, write to the Free Software Foundation, Inc., struct CraftReplacements; -class ModApiCraft : public ModApiBase { +class ModApiCraft : public ModApiBase +{ private: static int l_register_craft(lua_State *L); static int l_get_craft_recipe(lua_State *L); @@ -34,12 +35,12 @@ private: static int l_get_craft_result(lua_State *L); static int l_clear_craft(lua_State *L); - static bool readCraftReplacements(lua_State *L, int index, - CraftReplacements &replacements); - static bool readCraftRecipeShapeless(lua_State *L, int index, + static bool readCraftReplacements( + lua_State *L, int index, CraftReplacements &replacements); + static bool readCraftRecipeShapeless( + lua_State *L, int index, std::vector &recipe); + static bool readCraftRecipeShaped(lua_State *L, int index, int &width, std::vector &recipe); - static bool readCraftRecipeShaped(lua_State *L, int index, - int &width, std::vector &recipe); static struct EnumString es_CraftMethod[]; diff --git a/src/script/lua_api/l_env.cpp b/src/script/lua_api/l_env.cpp index ee384ad10..2c612eb9a 100644 --- a/src/script/lua_api/l_env.cpp +++ b/src/script/lua_api/l_env.cpp @@ -46,18 +46,16 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "client/client.h" #endif -struct EnumString ModApiEnvMod::es_ClearObjectsMode[] = -{ - {CLEAR_OBJECTS_MODE_FULL, "full"}, - {CLEAR_OBJECTS_MODE_QUICK, "quick"}, - {0, NULL}, +struct EnumString ModApiEnvMod::es_ClearObjectsMode[] = { + {CLEAR_OBJECTS_MODE_FULL, "full"}, + {CLEAR_OBJECTS_MODE_QUICK, "quick"}, + {0, NULL}, }; /////////////////////////////////////////////////////////////////////////////// - -void LuaABM::trigger(ServerEnvironment *env, v3s16 p, MapNode n, - u32 active_object_count, u32 active_object_count_wider) +void LuaABM::trigger(ServerEnvironment *env, v3s16 p, MapNode n, u32 active_object_count, + u32 active_object_count_wider) { ServerScripting *scriptIface = env->getScriptIface(); scriptIface->realityCheck(); @@ -77,7 +75,7 @@ void LuaABM::trigger(ServerEnvironment *env, v3s16 p, MapNode n, // Get registered_abms[m_id] lua_pushinteger(L, m_id); lua_gettable(L, -2); - if(lua_isnil(L, -1)) + if (lua_isnil(L, -1)) FATAL_ERROR(""); lua_remove(L, -2); // Remove registered_abms @@ -120,7 +118,8 @@ void LuaLBM::trigger(ServerEnvironment *env, v3s16 p, MapNode n) // Get registered_lbms[m_id] lua_pushinteger(L, m_id); lua_gettable(L, -2); - FATAL_ERROR_IF(lua_isnil(L, -1), "Entry with given id not found in registered_lbms table"); + FATAL_ERROR_IF(lua_isnil(L, -1), + "Entry with given id not found in registered_lbms table"); lua_remove(L, -2); // Remove registered_lbms scriptIface->setOriginFromTable(-1); @@ -176,10 +175,9 @@ int LuaRaycast::create_object(lua_State *L) liquids = readParam(L, 4); } - LuaRaycast *o = new LuaRaycast(core::line3d(pos1, pos2), - objects, liquids); + LuaRaycast *o = new LuaRaycast(core::line3d(pos1, pos2), objects, liquids); - *(void **) (lua_newuserdata(L, sizeof(void *))) = o; + *(void **)(lua_newuserdata(L, sizeof(void *))) = o; luaL_getmetatable(L, className); lua_setmetatable(L, -2); return 1; @@ -193,12 +191,12 @@ LuaRaycast *LuaRaycast::checkobject(lua_State *L, int narg) void *ud = luaL_checkudata(L, narg, className); if (!ud) luaL_typerror(L, narg, className); - return *(LuaRaycast **) ud; + return *(LuaRaycast **)ud; } int LuaRaycast::gc_object(lua_State *L) { - LuaRaycast *o = *(LuaRaycast **) (lua_touserdata(L, 1)); + LuaRaycast *o = *(LuaRaycast **)(lua_touserdata(L, 1)); delete o; return 0; } @@ -235,11 +233,7 @@ void LuaRaycast::Register(lua_State *L) } const char LuaRaycast::className[] = "Raycast"; -const luaL_Reg LuaRaycast::methods[] = -{ - luamethod(LuaRaycast, next), - { 0, 0 } -}; +const luaL_Reg LuaRaycast::methods[] = {luamethod(LuaRaycast, next), {0, 0}}; void LuaEmergeAreaCallback(v3s16 blockpos, EmergeAction action, void *param) { @@ -391,7 +385,7 @@ int ModApiEnvMod::l_get_node_light(lua_State *L) // Do it v3s16 pos = read_v3s16(L, 1); u32 time_of_day = env->getTimeOfDay(); - if(lua_isnumber(L, 2)) + if (lua_isnumber(L, 2)) time_of_day = 24000.0 * lua_tonumber(L, 2); time_of_day %= 24000; u32 dnr = time_to_daynight_ratio(time_of_day, true); @@ -423,7 +417,7 @@ int ModApiEnvMod::l_place_node(lua_State *L) // Don't attempt to load non-loaded area as of now MapNode n_old = env->getMap().getNode(pos); - if(n_old.getContent() == CONTENT_IGNORE){ + if (n_old.getContent() == CONTENT_IGNORE) { lua_pushboolean(L, false); return 1; } @@ -433,7 +427,7 @@ int ModApiEnvMod::l_place_node(lua_State *L) PointedThing pointed; pointed.type = POINTEDTHING_NODE; pointed.node_abovesurface = pos; - pointed.node_undersurface = pos + v3s16(0,-1,0); + pointed.node_undersurface = pos + v3s16(0, -1, 0); // Place it with a NULL placer (appears in Lua as nil) bool success = scriptIfaceItem->item_OnPlace(item, nullptr, pointed); lua_pushboolean(L, success); @@ -452,7 +446,7 @@ int ModApiEnvMod::l_dig_node(lua_State *L) // Don't attempt to load non-loaded area as of now MapNode n = env->getMap().getNode(pos); - if(n.getContent() == CONTENT_IGNORE){ + if (n.getContent() == CONTENT_IGNORE) { lua_pushboolean(L, false); return 1; } @@ -475,7 +469,7 @@ int ModApiEnvMod::l_punch_node(lua_State *L) // Don't attempt to load non-loaded area as of now MapNode n = env->getMap().getNode(pos); - if(n.getContent() == CONTENT_IGNORE){ + if (n.getContent() == CONTENT_IGNORE) { lua_pushboolean(L, false); return 1; } @@ -519,7 +513,7 @@ int ModApiEnvMod::l_set_node_level(lua_State *L) v3s16 pos = read_v3s16(L, 1); u8 level = 1; - if(lua_isnumber(L, 2)) + if (lua_isnumber(L, 2)) level = lua_tonumber(L, 2); MapNode n = env->getMap().getNode(pos); lua_pushnumber(L, n.setLevel(env->getGameDef()->ndef(), level)); @@ -536,7 +530,7 @@ int ModApiEnvMod::l_add_node_level(lua_State *L) v3s16 pos = read_v3s16(L, 1); s16 level = 1; - if(lua_isnumber(L, 2)) + if (lua_isnumber(L, 2)) level = lua_tonumber(L, 2); MapNode n = env->getMap().getNode(pos); lua_pushnumber(L, n.addLevel(env->getGameDef()->ndef(), level)); @@ -550,7 +544,7 @@ int ModApiEnvMod::l_find_nodes_with_meta(lua_State *L) GET_PLAIN_ENV_PTR; std::vector positions = env->getMap().findNodesWithMetadata( - check_v3s16(L, 1), check_v3s16(L, 2)); + check_v3s16(L, 1), check_v3s16(L, 2)); lua_createtable(L, positions.size(), 0); for (size_t i = 0; i != positions.size(); i++) { @@ -596,7 +590,7 @@ int ModApiEnvMod::l_add_entity(lua_State *L) ServerActiveObject *obj = new LuaEntitySAO(env, pos, name, staticdata); int objectid = env->addActiveObject(obj); // If failed to add, return nothing (reads as nil) - if(objectid == 0) + if (objectid == 0) return 0; // If already deleted (can happen in on_activate), return nil @@ -613,10 +607,10 @@ int ModApiEnvMod::l_add_item(lua_State *L) GET_ENV_PTR; // pos - //v3f pos = checkFloatPos(L, 1); + // v3f pos = checkFloatPos(L, 1); // item - ItemStack item = read_item(L, 2,getServer(L)->idef()); - if(item.empty() || !item.isKnown(getServer(L)->idef())) + ItemStack item = read_item(L, 2, getServer(L)->idef()); + if (item.empty() || !item.isKnown(getServer(L)->idef())) return 0; int error_handler = PUSH_ERROR_HANDLER(L); @@ -625,7 +619,7 @@ int ModApiEnvMod::l_add_item(lua_State *L) lua_getglobal(L, "core"); lua_getfield(L, -1, "spawn_item"); lua_remove(L, -2); // Remove core - if(lua_isnil(L, -1)) + if (lua_isnil(L, -1)) return 0; lua_pushvalue(L, 1); lua_pushstring(L, item.getItemString().c_str()); @@ -639,10 +633,10 @@ int ModApiEnvMod::l_add_item(lua_State *L) // get_connected_players() int ModApiEnvMod::l_get_connected_players(lua_State *L) { - ServerEnvironment *env = (ServerEnvironment *) getEnv(L); + ServerEnvironment *env = (ServerEnvironment *)getEnv(L); if (!env) { log_deprecated(L, "Calling get_connected_players() at mod load time" - " is deprecated"); + " is deprecated"); lua_createtable(L, 0, 0); return 1; } @@ -690,7 +684,7 @@ int ModApiEnvMod::l_get_objects_inside_radius(lua_State *L) float radius = readParam(L, 2) * BS; std::vector objs; - auto include_obj_cb = [](ServerActiveObject *obj){ return !obj->isGone(); }; + auto include_obj_cb = [](ServerActiveObject *obj) { return !obj->isGone(); }; env->getObjectsInsideRadius(objs, pos, radius, include_obj_cb); int i = 0; @@ -716,7 +710,7 @@ int ModApiEnvMod::l_set_timeofday(lua_State *L) // This should be set directly in the environment but currently // such changes aren't immediately sent to the clients, so call // the server instead. - //env->setTimeOfDay(timeofday_mh); + // env->setTimeOfDay(timeofday_mh); getServer(L)->setTimeOfDay(timeofday_mh); return 0; } @@ -753,7 +747,7 @@ int ModApiEnvMod::l_get_gametime(lua_State *L) } void ModApiEnvMod::collectNodeIds(lua_State *L, int idx, const NodeDefManager *ndef, - std::vector &filter) + std::vector &filter) { if (lua_istable(L, idx)) { lua_pushnil(L); @@ -825,13 +819,13 @@ int ModApiEnvMod::l_find_nodes_near(lua_State *L) if (Client *client = getClient(L)) radius = client->CSMClampRadius(pos, radius); #endif - + std::vector individual_count; individual_count.resize(filter.size()); - + lua_newtable(L); u32 i = 0; - + for (int d = start_radius; d <= radius; d++) { const std::vector &list = FacePositionCache::getFacePositions(d); for (const v3s16 &posi : list) { @@ -875,13 +869,13 @@ int ModApiEnvMod::l_find_nodes_near_under_air(lua_State *L) if (Client *client = getClient(L)) radius = client->CSMClampRadius(pos, radius); #endif - + std::vector individual_count; individual_count.resize(filter.size()); - + lua_newtable(L); u32 i = 0; - + for (int d = start_radius; d <= radius; d++) { const std::vector &list = FacePositionCache::getFacePositions(d); for (const v3s16 &posi : list) { @@ -929,13 +923,13 @@ int ModApiEnvMod::l_find_nodes_near_under_air_except(lua_State *L) if (Client *client = getClient(L)) radius = client->CSMClampRadius(pos, radius); #endif - + std::vector individual_count; individual_count.resize(filter.size()); - + lua_newtable(L); u32 i = 0; - + for (int d = start_radius; d <= radius; d++) { const std::vector &list = FacePositionCache::getFacePositions(d); for (const v3s16 &posi : list) { @@ -986,7 +980,7 @@ int ModApiEnvMod::l_find_nodes_in_area(lua_State *L) // Volume limit equal to 8 default mapchunks, (80 * 2) ^ 3 = 4,096,000 if ((u64)cube.X * (u64)cube.Y * (u64)cube.Z > 4096000) { luaL_error(L, "find_nodes_in_area(): area volume" - " exceeds allowed value of 4096000"); + " exceeds allowed value of 4096000"); return 0; } @@ -1008,18 +1002,21 @@ int ModApiEnvMod::l_find_nodes_in_area(lua_State *L) v3s16 p; for (p.X = minp.X; p.X <= maxp.X; p.X++) - for (p.Y = minp.Y; p.Y <= maxp.Y; p.Y++) - for (p.Z = minp.Z; p.Z <= maxp.Z; p.Z++) { - content_t c = map.getNode(p).getContent(); + for (p.Y = minp.Y; p.Y <= maxp.Y; p.Y++) + for (p.Z = minp.Z; p.Z <= maxp.Z; p.Z++) { + content_t c = map.getNode(p).getContent(); - auto it = std::find(filter.begin(), filter.end(), c); - if (it != filter.end()) { - // Calculate index of the table and append the position - u32 filt_index = it - filter.begin(); - push_v3s16(L, p); - lua_rawseti(L, base + 1 + filt_index, ++idx[filt_index]); - } - } + auto it = std::find( + filter.begin(), filter.end(), c); + if (it != filter.end()) { + // Calculate index of the table and append + // the position + u32 filt_index = it - filter.begin(); + push_v3s16(L, p); + lua_rawseti(L, base + 1 + filt_index, + ++idx[filt_index]); + } + } // last filter table is at top of stack u32 i = filter.size() - 1; @@ -1043,19 +1040,21 @@ int ModApiEnvMod::l_find_nodes_in_area(lua_State *L) u32 i = 0; v3s16 p; for (p.X = minp.X; p.X <= maxp.X; p.X++) - for (p.Y = minp.Y; p.Y <= maxp.Y; p.Y++) - for (p.Z = minp.Z; p.Z <= maxp.Z; p.Z++) { - content_t c = env->getMap().getNode(p).getContent(); + for (p.Y = minp.Y; p.Y <= maxp.Y; p.Y++) + for (p.Z = minp.Z; p.Z <= maxp.Z; p.Z++) { + content_t c = env->getMap().getNode(p) + .getContent(); - auto it = std::find(filter.begin(), filter.end(), c); - if (it != filter.end()) { - push_v3s16(L, p); - lua_rawseti(L, -2, ++i); + auto it = std::find( + filter.begin(), filter.end(), c); + if (it != filter.end()) { + push_v3s16(L, p); + lua_rawseti(L, -2, ++i); - u32 filt_index = it - filter.begin(); - individual_count[filt_index]++; - } - } + u32 filt_index = it - filter.begin(); + individual_count[filt_index]++; + } + } lua_createtable(L, 0, filter.size()); for (u32 i = 0; i < filter.size(); i++) { @@ -1097,7 +1096,7 @@ int ModApiEnvMod::l_find_nodes_in_area_under_air(lua_State *L) // Volume limit equal to 8 default mapchunks, (80 * 2) ^ 3 = 4,096,000 if ((u64)cube.X * (u64)cube.Y * (u64)cube.Z > 4096000) { luaL_error(L, "find_nodes_in_area_under_air(): area volume" - " exceeds allowed value of 4096000"); + " exceeds allowed value of 4096000"); return 0; } @@ -1108,20 +1107,20 @@ int ModApiEnvMod::l_find_nodes_in_area_under_air(lua_State *L) u32 i = 0; v3s16 p; for (p.X = minp.X; p.X <= maxp.X; p.X++) - for (p.Z = minp.Z; p.Z <= maxp.Z; p.Z++) { - p.Y = minp.Y; - content_t c = map.getNode(p).getContent(); - for (; p.Y <= maxp.Y; p.Y++) { - v3s16 psurf(p.X, p.Y + 1, p.Z); - content_t csurf = map.getNode(psurf).getContent(); - if (c != CONTENT_AIR && csurf == CONTENT_AIR && - CONTAINS(filter, c)) { - push_v3s16(L, p); - lua_rawseti(L, -2, ++i); + for (p.Z = minp.Z; p.Z <= maxp.Z; p.Z++) { + p.Y = minp.Y; + content_t c = map.getNode(p).getContent(); + for (; p.Y <= maxp.Y; p.Y++) { + v3s16 psurf(p.X, p.Y + 1, p.Z); + content_t csurf = map.getNode(psurf).getContent(); + if (c != CONTENT_AIR && csurf == CONTENT_AIR && + CONTAINS(filter, c)) { + push_v3s16(L, p); + lua_rawseti(L, -2, ++i); + } + c = csurf; } - c = csurf; } - } return 1; } @@ -1136,10 +1135,10 @@ int ModApiEnvMod::l_get_perlin(lua_State *L) if (lua_istable(L, 1)) { read_noiseparams(L, 1, ¶ms); } else { - params.seed = luaL_checkint(L, 1); + params.seed = luaL_checkint(L, 1); params.octaves = luaL_checkint(L, 2); params.persist = readParam(L, 3); - params.spread = v3f(1, 1, 1) * readParam(L, 4); + params.spread = v3f(1, 1, 1) * readParam(L, 4); } params.seed += (int)env->getServerMap().getSeed(); @@ -1177,9 +1176,10 @@ int ModApiEnvMod::l_get_voxel_manip(lua_State *L) GET_ENV_PTR; Map *map = &(env->getMap()); - LuaVoxelManip *o = (lua_istable(L, 1) && lua_istable(L, 2)) ? - new LuaVoxelManip(map, read_v3s16(L, 1), read_v3s16(L, 2)) : - new LuaVoxelManip(map); + LuaVoxelManip *o = (lua_istable(L, 1) && lua_istable(L, 2)) + ? new LuaVoxelManip(map, read_v3s16(L, 1), + read_v3s16(L, 2)) + : new LuaVoxelManip(map); *(void **)(lua_newuserdata(L, sizeof(void *))) = o; luaL_getmetatable(L, "VoxelManip"); @@ -1196,8 +1196,8 @@ int ModApiEnvMod::l_clear_objects(lua_State *L) ClearObjectsMode mode = CLEAR_OBJECTS_MODE_QUICK; if (lua_istable(L, 1)) { - mode = (ClearObjectsMode)getenumfield(L, 1, "mode", - ModApiEnvMod::es_ClearObjectsMode, mode); + mode = (ClearObjectsMode)getenumfield( + L, 1, "mode", ModApiEnvMod::es_ClearObjectsMode, mode); } env->clearObjects(mode); @@ -1237,10 +1237,12 @@ int ModApiEnvMod::l_fix_light(lua_State *L) bool success = true; v3s16 blockpos; for (blockpos.X = blockpos1.X; blockpos.X <= blockpos2.X; blockpos.X++) - for (blockpos.Y = blockpos1.Y; blockpos.Y <= blockpos2.Y; blockpos.Y++) - for (blockpos.Z = blockpos1.Z; blockpos.Z <= blockpos2.Z; blockpos.Z++) { - success = success & map.repairBlockLight(blockpos, &modified_blocks); - } + for (blockpos.Y = blockpos1.Y; blockpos.Y <= blockpos2.Y; blockpos.Y++) + for (blockpos.Z = blockpos1.Z; blockpos.Z <= blockpos2.Z; + blockpos.Z++) { + success = success & map.repairBlockLight(blockpos, + &modified_blocks); + } if (!modified_blocks.empty()) { MapEditEvent event; event.type = MEET_OTHER; @@ -1274,10 +1276,10 @@ int ModApiEnvMod::l_load_area(lua_State *L) v3s16 bp2 = getNodeBlockPos(check_v3s16(L, 2)); sortBoxVerticies(bp1, bp2); for (s16 z = bp1.Z; z <= bp2.Z; z++) - for (s16 y = bp1.Y; y <= bp2.Y; y++) - for (s16 x = bp1.X; x <= bp2.X; x++) { - map->emergeBlock(v3s16(x, y, z)); - } + for (s16 y = bp1.Y; y <= bp2.Y; y++) + for (s16 x = bp1.X; x <= bp2.X; x++) { + map->emergeBlock(v3s16(x, y, z)); + } } return 0; @@ -1311,19 +1313,22 @@ int ModApiEnvMod::l_emerge_area(lua_State *L) int args_ref = luaL_ref(L, LUA_REGISTRYINDEX); state = new ScriptCallbackState; - state->script = getServer(L)->getScriptIface(); + state->script = getServer(L)->getScriptIface(); state->callback_ref = callback_ref; - state->args_ref = args_ref; - state->refcount = num_blocks; - state->origin = getScriptApiBase(L)->getOrigin(); + state->args_ref = args_ref; + state->refcount = num_blocks; + state->origin = getScriptApiBase(L)->getOrigin(); } for (s16 z = bpmin.Z; z <= bpmax.Z; z++) - for (s16 y = bpmin.Y; y <= bpmax.Y; y++) - for (s16 x = bpmin.X; x <= bpmax.X; x++) { - emerge->enqueueBlockEmergeEx(v3s16(x, y, z), PEER_ID_INEXISTENT, - BLOCK_EMERGE_ALLOW_GEN | BLOCK_EMERGE_FORCE_QUEUE, callback, state); - } + for (s16 y = bpmin.Y; y <= bpmax.Y; y++) + for (s16 x = bpmin.X; x <= bpmax.X; x++) { + emerge->enqueueBlockEmergeEx(v3s16(x, y, z), + PEER_ID_INEXISTENT, + BLOCK_EMERGE_ALLOW_GEN | + BLOCK_EMERGE_FORCE_QUEUE, + callback, state); + } return 0; } @@ -1345,16 +1350,16 @@ int ModApiEnvMod::l_delete_area(lua_State *L) bool success = true; for (s16 z = bpmin.Z; z <= bpmax.Z; z++) - for (s16 y = bpmin.Y; y <= bpmax.Y; y++) - for (s16 x = bpmin.X; x <= bpmax.X; x++) { - v3s16 bp(x, y, z); - if (map.deleteBlock(bp)) { - env->setStaticForActiveObjectsInBlock(bp, false); - event.modified_blocks.insert(bp); - } else { - success = false; - } - } + for (s16 y = bpmin.Y; y <= bpmax.Y; y++) + for (s16 x = bpmin.X; x <= bpmax.X; x++) { + v3s16 bp(x, y, z); + if (map.deleteBlock(bp)) { + env->setStaticForActiveObjectsInBlock(bp, false); + event.modified_blocks.insert(bp); + } else { + success = false; + } + } map.dispatchEvent(event); lua_pushboolean(L, success); @@ -1367,14 +1372,14 @@ int ModApiEnvMod::l_find_path(lua_State *L) { Environment *env = getEnv(L); - v3s16 pos1 = read_v3s16(L, 1); - v3s16 pos2 = read_v3s16(L, 2); + v3s16 pos1 = read_v3s16(L, 1); + v3s16 pos2 = read_v3s16(L, 2); unsigned int searchdistance = luaL_checkint(L, 3); - unsigned int max_jump = luaL_checkint(L, 4); - unsigned int max_drop = luaL_checkint(L, 5); - PathAlgorithm algo = PA_PLAIN_NP; + unsigned int max_jump = luaL_checkint(L, 4); + unsigned int max_drop = luaL_checkint(L, 5); + PathAlgorithm algo = PA_PLAIN_NP; if (!lua_isnoneornil(L, 6)) { - std::string algorithm = luaL_checkstring(L,6); + std::string algorithm = luaL_checkstring(L, 6); if (algorithm == "A*") algo = PA_PLAIN; @@ -1382,16 +1387,16 @@ int ModApiEnvMod::l_find_path(lua_State *L) if (algorithm == "Dijkstra") algo = PA_DIJKSTRA; } - - std::vector path = get_path(&env->getMap(), env->getGameDef()->ndef(), pos1, pos2, - searchdistance, max_jump, max_drop, algo); + + std::vector path = get_path(&env->getMap(), env->getGameDef()->ndef(), + pos1, pos2, searchdistance, max_jump, max_drop, algo); if (!path.empty()) { lua_createtable(L, path.size(), 0); int top = lua_gettop(L); unsigned int index = 1; for (const v3s16 &i : path) { - lua_pushnumber(L,index); + lua_pushnumber(L, index); push_v3s16(L, i); lua_settable(L, top); index++; @@ -1410,24 +1415,23 @@ int ModApiEnvMod::l_spawn_tree(lua_State *L) v3s16 p0 = read_v3s16(L, 1); treegen::TreeDef tree_def; - std::string trunk,leaves,fruit; + std::string trunk, leaves, fruit; const NodeDefManager *ndef = env->getGameDef()->ndef(); - if(lua_istable(L, 2)) - { + if (lua_istable(L, 2)) { getstringfield(L, 2, "axiom", tree_def.initial_axiom); getstringfield(L, 2, "rules_a", tree_def.rules_a); getstringfield(L, 2, "rules_b", tree_def.rules_b); getstringfield(L, 2, "rules_c", tree_def.rules_c); getstringfield(L, 2, "rules_d", tree_def.rules_d); getstringfield(L, 2, "trunk", trunk); - tree_def.trunknode=ndef->getId(trunk); + tree_def.trunknode = ndef->getId(trunk); getstringfield(L, 2, "leaves", leaves); - tree_def.leavesnode=ndef->getId(leaves); - tree_def.leaves2_chance=0; + tree_def.leavesnode = ndef->getId(leaves); + tree_def.leaves2_chance = 0; getstringfield(L, 2, "leaves2", leaves); if (!leaves.empty()) { - tree_def.leaves2node=ndef->getId(leaves); + tree_def.leaves2node = ndef->getId(leaves); getintfield(L, 2, "leaves2_chance", tree_def.leaves2_chance); } getintfield(L, 2, "angle", tree_def.angle); @@ -1436,22 +1440,22 @@ int ModApiEnvMod::l_spawn_tree(lua_State *L) tree_def.iterations_random_level = 0; getstringfield(L, 2, "trunk_type", tree_def.trunk_type); getboolfield(L, 2, "thin_branches", tree_def.thin_branches); - tree_def.fruit_chance=0; + tree_def.fruit_chance = 0; getstringfield(L, 2, "fruit", fruit); if (!fruit.empty()) { - tree_def.fruitnode=ndef->getId(fruit); - getintfield(L, 2, "fruit_chance",tree_def.fruit_chance); + tree_def.fruitnode = ndef->getId(fruit); + getintfield(L, 2, "fruit_chance", tree_def.fruit_chance); } tree_def.explicit_seed = getintfield(L, 2, "seed", tree_def.seed); - } - else + } else return 0; ServerMap *map = &env->getServerMap(); treegen::error e; - if ((e = treegen::spawn_ltree (map, p0, ndef, tree_def)) != treegen::SUCCESS) { + if ((e = treegen::spawn_ltree(map, p0, ndef, tree_def)) != treegen::SUCCESS) { if (e == treegen::UNBALANCED_BRACKETS) { - luaL_error(L, "spawn_tree(): closing ']' has no matching opening bracket"); + luaL_error(L, "spawn_tree(): closing ']' has no matching opening " + "bracket"); } else { luaL_error(L, "spawn_tree(): unknown error"); } @@ -1493,14 +1497,14 @@ int ModApiEnvMod::l_forceload_free_block(lua_State *L) } // get_translated_string(lang_code, string) -int ModApiEnvMod::l_get_translated_string(lua_State * L) +int ModApiEnvMod::l_get_translated_string(lua_State *L) { GET_ENV_PTR; std::string lang_code = luaL_checkstring(L, 1); std::string string = luaL_checkstring(L, 2); getServer(L)->loadTranslationLanguage(lang_code); - string = wide_to_utf8(translate_string(utf8_to_wide(string), - &(*g_server_translations)[lang_code])); + string = wide_to_utf8(translate_string( + utf8_to_wide(string), &(*g_server_translations)[lang_code])); lua_pushstring(L, string.c_str()); return 1; } diff --git a/src/script/lua_api/l_env.h b/src/script/lua_api/l_env.h index e1b89494b..43388475e 100644 --- a/src/script/lua_api/l_env.h +++ b/src/script/lua_api/l_env.h @@ -23,7 +23,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "serverenvironment.h" #include "raycast.h" -class ModApiEnvMod : public ModApiBase { +class ModApiEnvMod : public ModApiBase +{ private: // set_node(pos, node) // pos = {x=num, y=num, z=num} @@ -126,17 +127,17 @@ private: // find_node_near(pos, radius, nodenames, search_center) -> pos or nil // nodenames: eg. {"ignore", "group:tree"} or "default:dirt" static int l_find_node_near(lua_State *L); - + // find_nodes_near(pos, radius, nodenames, search_center) -> list of positions // nodenames: eg. {"ignore", "group:tree"} or "default:dirt" static int l_find_nodes_near(lua_State *L); - - // find_nodes_near_under_air(pos, radius, nodenames, search_center) -> list of positions - // nodenames: eg. {"ignore", "group:tree"} or "default:dirt" + + // find_nodes_near_under_air(pos, radius, nodenames, search_center) -> list of + // positions nodenames: eg. {"ignore", "group:tree"} or "default:dirt" static int l_find_nodes_near_under_air(lua_State *L); - - // find_nodes_near_under_air(pos, radius, nodenames, search_center) -> list of positions - // nodenames: eg. {"ignore", "group:tree"} or "default:dirt" + + // find_nodes_near_under_air(pos, radius, nodenames, search_center) -> list of + // positions nodenames: eg. {"ignore", "group:tree"} or "default:dirt" static int l_find_nodes_near_under_air_except(lua_State *L); // find_nodes_in_area(minp, maxp, nodenames) -> list of positions @@ -200,12 +201,12 @@ private: static int l_forceload_free_block(lua_State *L); // Get a string translated server side - static int l_get_translated_string(lua_State * L); + static int l_get_translated_string(lua_State *L); /* Helpers */ - static void collectNodeIds(lua_State *L, int idx, - const NodeDefManager *ndef, std::vector &filter); + static void collectNodeIds(lua_State *L, int idx, const NodeDefManager *ndef, + std::vector &filter); public: static void Initialize(lua_State *L, int top); @@ -214,7 +215,8 @@ public: static struct EnumString es_ClearObjectsMode[]; }; -class LuaABM : public ActiveBlockModifier { +class LuaABM : public ActiveBlockModifier +{ private: int m_id; @@ -223,17 +225,18 @@ private: float m_trigger_interval; u32 m_trigger_chance; bool m_simple_catch_up; + public: - LuaABM(lua_State *L, int id, - const std::vector &trigger_contents, + LuaABM(lua_State *L, int id, const std::vector &trigger_contents, const std::vector &required_neighbors, - float trigger_interval, u32 trigger_chance, bool simple_catch_up): - m_id(id), - m_trigger_contents(trigger_contents), - m_required_neighbors(required_neighbors), - m_trigger_interval(trigger_interval), - m_trigger_chance(trigger_chance), - m_simple_catch_up(simple_catch_up) + float trigger_interval, u32 trigger_chance, + bool simple_catch_up) : + m_id(id), + m_trigger_contents(trigger_contents), + m_required_neighbors(required_neighbors), + m_trigger_interval(trigger_interval), + m_trigger_chance(trigger_chance), + m_simple_catch_up(simple_catch_up) { } virtual const std::vector &getTriggerContents() const @@ -244,18 +247,9 @@ public: { return m_required_neighbors; } - virtual float getTriggerInterval() - { - return m_trigger_interval; - } - virtual u32 getTriggerChance() - { - return m_trigger_chance; - } - virtual bool getSimpleCatchUp() - { - return m_simple_catch_up; - } + virtual float getTriggerInterval() { return m_trigger_interval; } + virtual u32 getTriggerChance() { return m_trigger_chance; } + virtual bool getSimpleCatchUp() { return m_simple_catch_up; } virtual void trigger(ServerEnvironment *env, v3s16 p, MapNode n, u32 active_object_count, u32 active_object_count_wider); }; @@ -264,12 +258,11 @@ class LuaLBM : public LoadingBlockModifierDef { private: int m_id; + public: - LuaLBM(lua_State *L, int id, - const std::set &trigger_contents, - const std::string &name, - bool run_at_every_load): - m_id(id) + LuaLBM(lua_State *L, int id, const std::set &trigger_contents, + const std::string &name, bool run_at_every_load) : + m_id(id) { this->run_at_every_load = run_at_every_load; this->trigger_contents = trigger_contents; @@ -297,14 +290,14 @@ private: * Returns the next pointed thing on the ray. */ static int l_next(lua_State *L); + public: //! Constructor with the same arguments as RaycastState. - LuaRaycast( - const core::line3d &shootline, - bool objects_pointable, - bool liquids_pointable) : - state(shootline, objects_pointable, liquids_pointable) - {} + LuaRaycast(const core::line3d &shootline, bool objects_pointable, + bool liquids_pointable) : + state(shootline, objects_pointable, liquids_pointable) + { + } //! Creates a LuaRaycast and leaves it on top of the stack. static int create_object(lua_State *L); @@ -319,7 +312,8 @@ public: static void Register(lua_State *L); }; -struct ScriptCallbackState { +struct ScriptCallbackState +{ ServerScripting *script; int callback_ref; int args_ref; diff --git a/src/script/lua_api/l_http.cpp b/src/script/lua_api/l_http.cpp index 84837e71b..21d000d25 100644 --- a/src/script/lua_api/l_http.cpp +++ b/src/script/lua_api/l_http.cpp @@ -30,9 +30,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include -#define HTTP_API(name) \ - lua_pushstring(L, #name); \ - lua_pushcfunction(L, l_http_##name); \ +#define HTTP_API(name) \ + lua_pushstring(L, #name); \ + lua_pushcfunction(L, l_http_##name); \ lua_settable(L, -3); #if USE_CURL @@ -54,7 +54,8 @@ void ModApiHttp::read_http_fetch_request(lua_State *L, HTTPFetchRequest &req) if (lua_istable(L, 2)) { lua_pushnil(L); while (lua_next(L, 2) != 0) { - req.post_fields[readParam(L, -2)] = readParam(L, -1); + req.post_fields[readParam(L, -2)] = + readParam(L, -1); lua_pop(L, 1); } } else if (lua_isstring(L, 2)) { @@ -73,7 +74,8 @@ void ModApiHttp::read_http_fetch_request(lua_State *L, HTTPFetchRequest &req) lua_pop(L, 1); } -void ModApiHttp::push_http_fetch_result(lua_State *L, HTTPFetchResult &res, bool completed) +void ModApiHttp::push_http_fetch_result( + lua_State *L, HTTPFetchResult &res, bool completed) { lua_newtable(L); setboolfield(L, -1, "succeeded", res.succeeded); @@ -171,15 +173,19 @@ int ModApiHttp::l_request_http_api(lua_State *L) std::string mod_name = readParam(L, -1); std::string http_mods = g_settings->get("secure.http_mods"); - http_mods.erase(std::remove(http_mods.begin(), http_mods.end(), ' '), http_mods.end()); + http_mods.erase(std::remove(http_mods.begin(), http_mods.end(), ' '), + http_mods.end()); std::vector mod_list_http = str_split(http_mods, ','); std::string trusted_mods = g_settings->get("secure.trusted_mods"); - trusted_mods.erase(std::remove(trusted_mods.begin(), trusted_mods.end(), ' '), trusted_mods.end()); + trusted_mods.erase(std::remove(trusted_mods.begin(), trusted_mods.end(), ' '), + trusted_mods.end()); std::vector mod_list_trusted = str_split(trusted_mods, ','); - mod_list_http.insert(mod_list_http.end(), mod_list_trusted.begin(), mod_list_trusted.end()); - if (std::find(mod_list_http.begin(), mod_list_http.end(), mod_name) == mod_list_http.end()) { + mod_list_http.insert(mod_list_http.end(), mod_list_trusted.begin(), + mod_list_trusted.end()); + if (std::find(mod_list_http.begin(), mod_list_http.end(), mod_name) == + mod_list_http.end()) { lua_pushnil(L); return 1; } diff --git a/src/script/lua_api/l_http.h b/src/script/lua_api/l_http.h index de6e51b37..f2c72eafb 100644 --- a/src/script/lua_api/l_http.h +++ b/src/script/lua_api/l_http.h @@ -25,12 +25,14 @@ with this program; if not, write to the Free Software Foundation, Inc., struct HTTPFetchRequest; struct HTTPFetchResult; -class ModApiHttp : public ModApiBase { +class ModApiHttp : public ModApiBase +{ private: #if USE_CURL // Helpers for HTTP fetch functions static void read_http_fetch_request(lua_State *L, HTTPFetchRequest &req); - static void push_http_fetch_result(lua_State *L, HTTPFetchResult &res, bool completed = true); + static void push_http_fetch_result( + lua_State *L, HTTPFetchResult &res, bool completed = true); // http_fetch_sync({url=, timeout=, post_data=}) static int l_http_fetch_sync(lua_State *L); diff --git a/src/script/lua_api/l_internal.h b/src/script/lua_api/l_internal.h index a86eeaf79..378d19cd0 100644 --- a/src/script/lua_api/l_internal.h +++ b/src/script/lua_api/l_internal.h @@ -28,8 +28,12 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "common/c_internal.h" -#define luamethod(class, name) {#name, class::l_##name} -#define luamethod_aliased(class, name, alias) {#name, class::l_##name}, {#alias, class::l_##name} +#define luamethod(class, name) \ + { \ +#name, class ::l_##name \ + } +#define luamethod_aliased(class, name, alias) \ + {#name, class ::l_##name}, { #alias, class ::l_##name } #define API_FCT(name) registerFunction(L, #name, l_##name, top) // For future use @@ -39,32 +43,33 @@ with this program; if not, write to the Free Software Foundation, Inc., /* In debug mode ensure no code tries to retrieve the server env when it isn't * actually available (in CSM) */ #if !defined(SERVER) && !defined(NDEBUG) -#define DEBUG_ASSERT_NO_CLIENTAPI \ - FATAL_ERROR_IF(getClient(L) != nullptr, "Tried " \ - "to retrieve ServerEnvironment on client") +#define DEBUG_ASSERT_NO_CLIENTAPI \ + FATAL_ERROR_IF(getClient(L) != nullptr, \ + "Tried " \ + "to retrieve ServerEnvironment on client") #else #define DEBUG_ASSERT_NO_CLIENTAPI ((void)0) #endif // Retrieve ServerEnvironment pointer as `env` (no map lock) -#define GET_ENV_PTR_NO_MAP_LOCK \ - DEBUG_ASSERT_NO_CLIENTAPI; \ - ServerEnvironment *env = (ServerEnvironment *)getEnv(L); \ - if (env == NULL) \ - return 0 +#define GET_ENV_PTR_NO_MAP_LOCK \ + DEBUG_ASSERT_NO_CLIENTAPI; \ + ServerEnvironment *env = (ServerEnvironment *)getEnv(L); \ + if (env == NULL) \ + return 0 // Retrieve ServerEnvironment pointer as `env` -#define GET_ENV_PTR \ - MAP_LOCK_REQUIRED; \ +#define GET_ENV_PTR \ + MAP_LOCK_REQUIRED; \ GET_ENV_PTR_NO_MAP_LOCK // Retrieve Environment pointer as `env` (no map lock) -#define GET_PLAIN_ENV_PTR_NO_MAP_LOCK \ - Environment *env = (Environment *)getEnv(L); \ - if (env == NULL) \ - return 0 +#define GET_PLAIN_ENV_PTR_NO_MAP_LOCK \ + Environment *env = (Environment *)getEnv(L); \ + if (env == NULL) \ + return 0 // Retrieve Environment pointer as `env` -#define GET_PLAIN_ENV_PTR \ - MAP_LOCK_REQUIRED; \ +#define GET_PLAIN_ENV_PTR \ + MAP_LOCK_REQUIRED; \ GET_PLAIN_ENV_PTR_NO_MAP_LOCK diff --git a/src/script/lua_api/l_inventory.cpp b/src/script/lua_api/l_inventory.cpp index e41b5cb41..d75d96ef3 100644 --- a/src/script/lua_api/l_inventory.cpp +++ b/src/script/lua_api/l_inventory.cpp @@ -29,25 +29,25 @@ with this program; if not, write to the Free Software Foundation, Inc., /* InvRef */ -InvRef* InvRef::checkobject(lua_State *L, int narg) +InvRef *InvRef::checkobject(lua_State *L, int narg) { luaL_checktype(L, narg, LUA_TUSERDATA); void *ud = luaL_checkudata(L, narg, className); - if(!ud) luaL_typerror(L, narg, className); - return *(InvRef**)ud; // unbox pointer + if (!ud) + luaL_typerror(L, narg, className); + return *(InvRef **)ud; // unbox pointer } -Inventory* InvRef::getinv(lua_State *L, InvRef *ref) +Inventory *InvRef::getinv(lua_State *L, InvRef *ref) { return getServerInventoryMgr(L)->getInventory(ref->m_loc); } -InventoryList* InvRef::getlist(lua_State *L, InvRef *ref, - const char *listname) +InventoryList *InvRef::getlist(lua_State *L, InvRef *ref, const char *listname) { NO_MAP_LOCK_REQUIRED; Inventory *inv = getinv(L, ref); - if(!inv) + if (!inv) return NULL; return inv->getList(listname); } @@ -61,7 +61,8 @@ void InvRef::reportInventoryChange(lua_State *L, InvRef *ref) // Exported functions // garbage collector -int InvRef::gc_object(lua_State *L) { +int InvRef::gc_object(lua_State *L) +{ InvRef *o = *(InvRef **)(lua_touserdata(L, 1)); delete o; return 0; @@ -74,7 +75,7 @@ int InvRef::l_is_empty(lua_State *L) InvRef *ref = checkobject(L, 1); const char *listname = luaL_checkstring(L, 2); InventoryList *list = getlist(L, ref, listname); - if(list && list->getUsedSlots() > 0){ + if (list && list->getUsedSlots() > 0) { lua_pushboolean(L, false); } else { lua_pushboolean(L, true); @@ -89,7 +90,7 @@ int InvRef::l_get_size(lua_State *L) InvRef *ref = checkobject(L, 1); const char *listname = luaL_checkstring(L, 2); InventoryList *list = getlist(L, ref, listname); - if(list){ + if (list) { lua_pushinteger(L, list->getSize()); } else { lua_pushinteger(L, 0); @@ -104,7 +105,7 @@ int InvRef::l_get_width(lua_State *L) InvRef *ref = checkobject(L, 1); const char *listname = luaL_checkstring(L, 2); InventoryList *list = getlist(L, ref, listname); - if(list){ + if (list) { lua_pushinteger(L, list->getWidth()); } else { lua_pushinteger(L, 0); @@ -126,23 +127,22 @@ int InvRef::l_set_size(lua_State *L) } Inventory *inv = getinv(L, ref); - if(inv == NULL){ + if (inv == NULL) { lua_pushboolean(L, false); return 1; } - if(newsize == 0){ + if (newsize == 0) { inv->deleteList(listname); reportInventoryChange(L, ref); lua_pushboolean(L, true); return 1; } InventoryList *list = inv->getList(listname); - if(list){ + if (list) { list->setSize(newsize); } else { list = inv->addList(listname, newsize); - if (!list) - { + if (!list) { lua_pushboolean(L, false); return 1; } @@ -160,11 +160,11 @@ int InvRef::l_set_width(lua_State *L) const char *listname = luaL_checkstring(L, 2); int newwidth = luaL_checknumber(L, 3); Inventory *inv = getinv(L, ref); - if(inv == NULL){ + if (inv == NULL) { return 0; } InventoryList *list = inv->getList(listname); - if(list){ + if (list) { list->setWidth(newwidth); } else { return 0; @@ -182,7 +182,7 @@ int InvRef::l_get_stack(lua_State *L) int i = luaL_checknumber(L, 3) - 1; InventoryList *list = getlist(L, ref, listname); ItemStack item; - if(list != NULL && i >= 0 && i < (int) list->getSize()) + if (list != NULL && i >= 0 && i < (int)list->getSize()) item = list->getItem(i); LuaItemStack::create(L, item); return 1; @@ -197,7 +197,7 @@ int InvRef::l_set_stack(lua_State *L) int i = luaL_checknumber(L, 3) - 1; ItemStack newitem = read_item(L, 4, getServer(L)->idef()); InventoryList *list = getlist(L, ref, listname); - if(list != NULL && i >= 0 && i < (int) list->getSize()){ + if (list != NULL && i >= 0 && i < (int)list->getSize()) { list->changeItem(i, newitem); reportInventoryChange(L, ref); lua_pushboolean(L, true); @@ -214,7 +214,7 @@ int InvRef::l_get_list(lua_State *L) InvRef *ref = checkobject(L, 1); const char *listname = luaL_checkstring(L, 2); Inventory *inv = getinv(L, ref); - if(inv){ + if (inv) { push_inventory_list(L, inv, listname); } else { lua_pushnil(L); @@ -229,13 +229,12 @@ int InvRef::l_set_list(lua_State *L) InvRef *ref = checkobject(L, 1); const char *listname = luaL_checkstring(L, 2); Inventory *inv = getinv(L, ref); - if(inv == NULL){ + if (inv == NULL) { return 0; } InventoryList *list = inv->getList(listname); - if(list) - read_inventory_list(L, 3, inv, listname, - getServer(L), list->getSize()); + if (list) + read_inventory_list(L, 3, inv, listname, getServer(L), list->getSize()); else read_inventory_list(L, 3, inv, listname, getServer(L)); reportInventoryChange(L, ref); @@ -251,11 +250,11 @@ int InvRef::l_get_lists(lua_State *L) if (!inv) { return 0; } - std::vector lists = inv->getLists(); - std::vector::iterator iter = lists.begin(); + std::vector lists = inv->getLists(); + std::vector::iterator iter = lists.begin(); lua_createtable(L, 0, lists.size()); for (; iter != lists.end(); iter++) { - const char* name = (*iter)->getName().c_str(); + const char *name = (*iter)->getName().c_str(); lua_pushstring(L, name); push_inventory_list(L, inv, name); lua_rawset(L, -3); @@ -298,9 +297,9 @@ int InvRef::l_add_item(lua_State *L) const char *listname = luaL_checkstring(L, 2); ItemStack item = read_item(L, 3, getServer(L)->idef()); InventoryList *list = getlist(L, ref, listname); - if(list){ + if (list) { ItemStack leftover = list->addItem(item); - if(leftover.count != item.count) + if (leftover.count != item.count) reportInventoryChange(L, ref); LuaItemStack::create(L, leftover); } else { @@ -318,7 +317,7 @@ int InvRef::l_room_for_item(lua_State *L) const char *listname = luaL_checkstring(L, 2); ItemStack item = read_item(L, 3, getServer(L)->idef()); InventoryList *list = getlist(L, ref, listname); - if(list){ + if (list) { lua_pushboolean(L, list->roomForItem(item)); } else { lua_pushboolean(L, false); @@ -326,8 +325,8 @@ int InvRef::l_room_for_item(lua_State *L) return 1; } -// contains_item(self, listname, itemstack or itemstring or table or nil, [match_meta]) -> true/false -// Returns true if the list contains the given count of the given item +// contains_item(self, listname, itemstack or itemstring or table or nil, [match_meta]) -> +// true/false Returns true if the list contains the given count of the given item int InvRef::l_contains_item(lua_State *L) { NO_MAP_LOCK_REQUIRED; @@ -355,9 +354,9 @@ int InvRef::l_remove_item(lua_State *L) const char *listname = luaL_checkstring(L, 2); ItemStack item = read_item(L, 3, getServer(L)->idef()); InventoryList *list = getlist(L, ref, listname); - if(list){ + if (list) { ItemStack removed = list->removeItem(item); - if(!removed.empty()) + if (!removed.empty()) reportInventoryChange(L, ref); LuaItemStack::create(L, removed); } else { @@ -372,7 +371,7 @@ int InvRef::l_get_location(lua_State *L) NO_MAP_LOCK_REQUIRED; InvRef *ref = checkobject(L, 1); const InventoryLocation &loc = ref->m_loc; - switch(loc.type){ + switch (loc.type) { case InventoryLocation::PLAYER: lua_newtable(L); lua_pushstring(L, "player"); @@ -404,9 +403,7 @@ int InvRef::l_get_location(lua_State *L) return 1; } - -InvRef::InvRef(const InventoryLocation &loc): - m_loc(loc) +InvRef::InvRef(const InventoryLocation &loc) : m_loc(loc) { } @@ -443,7 +440,7 @@ void InvRef::Register(lua_State *L) lua_pushliteral(L, "__metatable"); lua_pushvalue(L, methodtable); - lua_settable(L, metatable); // hide metatable from Lua getmetatable() + lua_settable(L, metatable); // hide metatable from Lua getmetatable() lua_pushliteral(L, "__index"); lua_pushvalue(L, methodtable); @@ -453,35 +450,25 @@ void InvRef::Register(lua_State *L) lua_pushcfunction(L, gc_object); lua_settable(L, metatable); - lua_pop(L, 1); // drop metatable + lua_pop(L, 1); // drop metatable - luaL_openlib(L, 0, methods, 0); // fill methodtable - lua_pop(L, 1); // drop methodtable + luaL_openlib(L, 0, methods, 0); // fill methodtable + lua_pop(L, 1); // drop methodtable // Cannot be created from Lua - //lua_register(L, className, create_object); + // lua_register(L, className, create_object); } const char InvRef::className[] = "InvRef"; -const luaL_Reg InvRef::methods[] = { - luamethod(InvRef, is_empty), - luamethod(InvRef, get_size), - luamethod(InvRef, set_size), - luamethod(InvRef, get_width), - luamethod(InvRef, set_width), - luamethod(InvRef, get_stack), - luamethod(InvRef, set_stack), - luamethod(InvRef, get_list), - luamethod(InvRef, set_list), - luamethod(InvRef, get_lists), - luamethod(InvRef, set_lists), - luamethod(InvRef, add_item), - luamethod(InvRef, room_for_item), - luamethod(InvRef, contains_item), - luamethod(InvRef, remove_item), - luamethod(InvRef, get_location), - {0,0} -}; +const luaL_Reg InvRef::methods[] = {luamethod(InvRef, is_empty), + luamethod(InvRef, get_size), luamethod(InvRef, set_size), + luamethod(InvRef, get_width), luamethod(InvRef, set_width), + luamethod(InvRef, get_stack), luamethod(InvRef, set_stack), + luamethod(InvRef, get_list), luamethod(InvRef, set_list), + luamethod(InvRef, get_lists), luamethod(InvRef, set_lists), + luamethod(InvRef, add_item), luamethod(InvRef, room_for_item), + luamethod(InvRef, contains_item), luamethod(InvRef, remove_item), + luamethod(InvRef, get_location), {0, 0}}; // get_inventory(location) int ModApiInventory::l_get_inventory(lua_State *L) @@ -492,7 +479,7 @@ int ModApiInventory::l_get_inventory(lua_State *L) std::string type = luaL_checkstring(L, -1); lua_pop(L, 1); - if(type == "node"){ + if (type == "node") { MAP_LOCK_REQUIRED; lua_getfield(L, 1, "pos"); v3s16 pos = check_v3s16(L, -1); @@ -522,7 +509,6 @@ int ModApiInventory::l_get_inventory(lua_State *L) lua_pushnil(L); return 1; // END NO_MAP_LOCK_REQUIRED; - } // create_detached_inventory_raw(name, [player_name]) @@ -531,7 +517,8 @@ int ModApiInventory::l_create_detached_inventory_raw(lua_State *L) NO_MAP_LOCK_REQUIRED; const char *name = luaL_checkstring(L, 1); std::string player = readParam(L, 2, ""); - if (getServerInventoryMgr(L)->createDetachedInventory(name, getServer(L)->idef(), player) != NULL) { + if (getServerInventoryMgr(L)->createDetachedInventory( + name, getServer(L)->idef(), player) != NULL) { InventoryLocation loc; loc.setDetached(name); InvRef::create(L, loc); diff --git a/src/script/lua_api/l_inventory.h b/src/script/lua_api/l_inventory.h index 94f670c9d..fa8a77ae2 100644 --- a/src/script/lua_api/l_inventory.h +++ b/src/script/lua_api/l_inventory.h @@ -30,7 +30,8 @@ class RemotePlayer; InvRef */ -class InvRef : public ModApiBase { +class InvRef : public ModApiBase +{ private: InventoryLocation m_loc; @@ -39,10 +40,9 @@ private: static InvRef *checkobject(lua_State *L, int narg); - static Inventory* getinv(lua_State *L, InvRef *ref); + static Inventory *getinv(lua_State *L, InvRef *ref); - static InventoryList* getlist(lua_State *L, InvRef *ref, - const char *listname); + static InventoryList *getlist(lua_State *L, InvRef *ref, const char *listname); static void reportInventoryChange(lua_State *L, InvRef *ref); @@ -88,16 +88,17 @@ private: // Returns the leftover stack static int l_add_item(lua_State *L); - // room_for_item(self, listname, itemstack or itemstring or table or nil) -> true/false - // Returns true if the item completely fits into the list + // room_for_item(self, listname, itemstack or itemstring or table or nil) -> + // true/false Returns true if the item completely fits into the list static int l_room_for_item(lua_State *L); - // contains_item(self, listname, itemstack or itemstring or table or nil, [match_meta]) -> true/false - // Returns true if the list contains the given count of the given item name + // contains_item(self, listname, itemstack or itemstring or table or nil, + // [match_meta]) -> true/false Returns true if the list contains the given count + // of the given item name static int l_contains_item(lua_State *L); - // remove_item(self, listname, itemstack or itemstring or table or nil) -> itemstack - // Returns the items that were actually removed + // remove_item(self, listname, itemstack or itemstring or table or nil) -> + // itemstack Returns the items that were actually removed static int l_remove_item(lua_State *L); // get_location() -> location (like get_inventory(location)) @@ -116,7 +117,8 @@ public: static void Register(lua_State *L); }; -class ModApiInventory : public ModApiBase { +class ModApiInventory : public ModApiBase +{ private: static int l_create_detached_inventory_raw(lua_State *L); diff --git a/src/script/lua_api/l_inventoryaction.cpp b/src/script/lua_api/l_inventoryaction.cpp index f3037ba83..f65137465 100644 --- a/src/script/lua_api/l_inventoryaction.cpp +++ b/src/script/lua_api/l_inventoryaction.cpp @@ -43,9 +43,9 @@ int LuaInventoryAction::l_apply(lua_State *L) std::ostringstream os(std::ios::binary); o->m_action->serialize(os); - + std::istringstream is(os.str(), std::ios_base::binary); - + InventoryAction *a = InventoryAction::deSerialize(is); getClient(L)->inventoryAction(a); @@ -69,29 +69,30 @@ int LuaInventoryAction::l_to(lua_State *L) int LuaInventoryAction::l_craft(lua_State *L) { LuaInventoryAction *o = checkobject(L, 1); - + if (o->m_action->getType() != IAction::Craft) return 0; - + std::string locStr; InventoryLocation loc; - + locStr = readParam(L, 2); - + try { loc.deSerialize(locStr); dynamic_cast(o->m_action)->craft_inv = loc; - } catch (SerializationError &) {} - + } catch (SerializationError &) { + } + return 0; } int LuaInventoryAction::l_set_count(lua_State *L) { LuaInventoryAction *o = checkobject(L, 1); - + s16 count = luaL_checkinteger(L, 2); - + switch (o->m_action->getType()) { case IAction::Move: ((IMoveAction *)o->m_action)->count = count; @@ -103,7 +104,7 @@ int LuaInventoryAction::l_set_count(lua_State *L) ((ICraftAction *)o->m_action)->count = count; break; } - + return 0; } @@ -127,23 +128,25 @@ LuaInventoryAction::~LuaInventoryAction() delete m_action; } -void LuaInventoryAction::readFullInventoryLocationInto(lua_State *L, InventoryLocation *loc, std::string *list, s16 *index) +void LuaInventoryAction::readFullInventoryLocationInto( + lua_State *L, InventoryLocation *loc, std::string *list, s16 *index) { try { loc->deSerialize(readParam(L, 2)); std::string l = readParam(L, 3); *list = l; *index = luaL_checkinteger(L, 4) - 1; - } catch (SerializationError &) {} + } catch (SerializationError &) { + } } int LuaInventoryAction::create_object(lua_State *L) { IAction type; std::string typeStr; - + typeStr = readParam(L, 1); - + if (typeStr == "move") type = IAction::Move; else if (typeStr == "drop") @@ -206,11 +209,7 @@ void LuaInventoryAction::Register(lua_State *L) } const char LuaInventoryAction::className[] = "InventoryAction"; -const luaL_Reg LuaInventoryAction::methods[] = { - luamethod(LuaInventoryAction, apply), - luamethod(LuaInventoryAction, from), - luamethod(LuaInventoryAction, to), - luamethod(LuaInventoryAction, craft), - luamethod(LuaInventoryAction, set_count), - {0,0} -}; +const luaL_Reg LuaInventoryAction::methods[] = {luamethod(LuaInventoryAction, apply), + luamethod(LuaInventoryAction, from), luamethod(LuaInventoryAction, to), + luamethod(LuaInventoryAction, craft), + luamethod(LuaInventoryAction, set_count), {0, 0}}; diff --git a/src/script/lua_api/l_inventoryaction.h b/src/script/lua_api/l_inventoryaction.h index c1a96d010..a4cc6cbe5 100644 --- a/src/script/lua_api/l_inventoryaction.h +++ b/src/script/lua_api/l_inventoryaction.h @@ -20,44 +20,46 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "inventorymanager.h" #include "lua_api/l_base.h" -#define GET_MOVE_ACTION \ - LuaInventoryAction *o = checkobject(L, 1); \ - if (o->m_action->getType() == IAction::Craft) \ - return 0; \ +#define GET_MOVE_ACTION \ + LuaInventoryAction *o = checkobject(L, 1); \ + if (o->m_action->getType() == IAction::Craft) \ + return 0; \ MoveAction *act = dynamic_cast(o->m_action); -class LuaInventoryAction : public ModApiBase { +class LuaInventoryAction : public ModApiBase +{ private: InventoryAction *m_action; - - static void readFullInventoryLocationInto(lua_State *L, InventoryLocation *loc, std::string *list, s16 *index); - + + static void readFullInventoryLocationInto(lua_State *L, InventoryLocation *loc, + std::string *list, s16 *index); + static const char className[]; static const luaL_Reg methods[]; - + // Exported functions - + // garbage collector static int gc_object(lua_State *L); // __tostring metamethod static int mt_tostring(lua_State *L); - + // apply(self) static int l_apply(lua_State *L); - + // from(self, location, list, index) static int l_from(lua_State *L); - + // to(self, location, list, index) static int l_to(lua_State *L); - + // craft(self, location) static int l_craft(lua_State *L); // set_count(self, count) static int l_set_count(lua_State *L); - + public: LuaInventoryAction(const IAction &type); ~LuaInventoryAction(); @@ -67,6 +69,6 @@ public: static int create_object(lua_State *L); // Not callable from Lua static int create(lua_State *L, const IAction &type); - static LuaInventoryAction* checkobject(lua_State *L, int narg); + static LuaInventoryAction *checkobject(lua_State *L, int narg); static void Register(lua_State *L); }; diff --git a/src/script/lua_api/l_item.cpp b/src/script/lua_api/l_item.cpp index bea7d122f..9bbf9826b 100644 --- a/src/script/lua_api/l_item.cpp +++ b/src/script/lua_api/l_item.cpp @@ -234,12 +234,9 @@ int LuaItemStack::l_to_table(lua_State *L) NO_MAP_LOCK_REQUIRED; LuaItemStack *o = checkobject(L, 1); const ItemStack &item = o->m_stack; - if(item.empty()) - { + if (item.empty()) { lua_pushnil(L); - } - else - { + } else { lua_newtable(L); lua_pushstring(L, item.name.c_str()); lua_setfield(L, -2, "name"); @@ -314,8 +311,7 @@ int LuaItemStack::l_get_definition(lua_State *L) lua_getfield(L, -1, "registered_items"); luaL_checktype(L, -1, LUA_TTABLE); lua_getfield(L, -1, item.name.c_str()); - if(lua_isnil(L, -1)) - { + if (lua_isnil(L, -1)) { lua_pop(L, 1); lua_getfield(L, -1, "unknown"); } @@ -330,8 +326,7 @@ int LuaItemStack::l_get_tool_capabilities(lua_State *L) NO_MAP_LOCK_REQUIRED; LuaItemStack *o = checkobject(L, 1); ItemStack &item = o->m_stack; - const ToolCapabilities &prop = - item.getToolCapabilities(getGameDef(L)->idef()); + const ToolCapabilities &prop = item.getToolCapabilities(getGameDef(L)->idef()); push_tool_capabilities(L, prop); return 1; } @@ -375,8 +370,8 @@ int LuaItemStack::l_item_fits(lua_State *L) ItemStack newitem = read_item(L, 2, getGameDef(L)->idef()); ItemStack restitem; bool fits = item.itemFits(newitem, &restitem, getGameDef(L)->idef()); - lua_pushboolean(L, fits); // first return value - create(L, restitem); // second return value + lua_pushboolean(L, fits); // first return value + create(L, restitem); // second return value return 2; } @@ -387,7 +382,7 @@ int LuaItemStack::l_take_item(lua_State *L) LuaItemStack *o = checkobject(L, 1); ItemStack &item = o->m_stack; u32 takecount = 1; - if(!lua_isnone(L, 2)) + if (!lua_isnone(L, 2)) takecount = luaL_checkinteger(L, 2); ItemStack taken = item.takeItem(takecount); create(L, taken); @@ -401,23 +396,22 @@ int LuaItemStack::l_peek_item(lua_State *L) LuaItemStack *o = checkobject(L, 1); ItemStack &item = o->m_stack; u32 peekcount = 1; - if(!lua_isnone(L, 2)) + if (!lua_isnone(L, 2)) peekcount = lua_tointeger(L, 2); ItemStack peekaboo = item.peekItem(peekcount); create(L, peekaboo); return 1; } -LuaItemStack::LuaItemStack(const ItemStack &item): - m_stack(item) +LuaItemStack::LuaItemStack(const ItemStack &item) : m_stack(item) { } -const ItemStack& LuaItemStack::getItem() const +const ItemStack &LuaItemStack::getItem() const { return m_stack; } -ItemStack& LuaItemStack::getItem() +ItemStack &LuaItemStack::getItem() { return m_stack; } @@ -476,44 +470,32 @@ void LuaItemStack::Register(lua_State *L) lua_pushcfunction(L, mt_tostring); lua_settable(L, metatable); - lua_pop(L, 1); // drop metatable + lua_pop(L, 1); // drop metatable - luaL_openlib(L, 0, methods, 0); // fill methodtable - lua_pop(L, 1); // drop methodtable + luaL_openlib(L, 0, methods, 0); // fill methodtable + lua_pop(L, 1); // drop methodtable // Can be created from Lua (ItemStack(itemstack or itemstring or table or nil)) lua_register(L, className, create_object); } const char LuaItemStack::className[] = "ItemStack"; -const luaL_Reg LuaItemStack::methods[] = { - luamethod(LuaItemStack, is_empty), - luamethod(LuaItemStack, get_name), - luamethod(LuaItemStack, set_name), - luamethod(LuaItemStack, get_count), - luamethod(LuaItemStack, set_count), - luamethod(LuaItemStack, get_wear), - luamethod(LuaItemStack, set_wear), - luamethod(LuaItemStack, get_meta), - luamethod(LuaItemStack, get_metadata), - luamethod(LuaItemStack, set_metadata), - luamethod(LuaItemStack, get_description), - luamethod(LuaItemStack, clear), - luamethod(LuaItemStack, replace), - luamethod(LuaItemStack, to_string), - luamethod(LuaItemStack, to_table), - luamethod(LuaItemStack, get_stack_max), - luamethod(LuaItemStack, get_free_space), - luamethod(LuaItemStack, is_known), - luamethod(LuaItemStack, get_definition), - luamethod(LuaItemStack, get_tool_capabilities), - luamethod(LuaItemStack, add_wear), - luamethod(LuaItemStack, add_item), - luamethod(LuaItemStack, item_fits), - luamethod(LuaItemStack, take_item), - luamethod(LuaItemStack, peek_item), - {0,0} -}; +const luaL_Reg LuaItemStack::methods[] = {luamethod(LuaItemStack, is_empty), + luamethod(LuaItemStack, get_name), luamethod(LuaItemStack, set_name), + luamethod(LuaItemStack, get_count), luamethod(LuaItemStack, set_count), + luamethod(LuaItemStack, get_wear), luamethod(LuaItemStack, set_wear), + luamethod(LuaItemStack, get_meta), luamethod(LuaItemStack, get_metadata), + luamethod(LuaItemStack, set_metadata), + luamethod(LuaItemStack, get_description), luamethod(LuaItemStack, clear), + luamethod(LuaItemStack, replace), luamethod(LuaItemStack, to_string), + luamethod(LuaItemStack, to_table), luamethod(LuaItemStack, get_stack_max), + luamethod(LuaItemStack, get_free_space), + luamethod(LuaItemStack, is_known), + luamethod(LuaItemStack, get_definition), + luamethod(LuaItemStack, get_tool_capabilities), + luamethod(LuaItemStack, add_wear), luamethod(LuaItemStack, add_item), + luamethod(LuaItemStack, item_fits), luamethod(LuaItemStack, take_item), + luamethod(LuaItemStack, peek_item), {0, 0}}; /* ItemDefinition @@ -527,15 +509,13 @@ int ModApiItemMod::l_register_item_raw(lua_State *L) int table = 1; // Get the writable item and node definition managers from the server - IWritableItemDefManager *idef = - getGameDef(L)->getWritableItemDefManager(); - NodeDefManager *ndef = - getGameDef(L)->getWritableNodeDefManager(); + IWritableItemDefManager *idef = getGameDef(L)->getWritableItemDefManager(); + NodeDefManager *ndef = getGameDef(L)->getWritableNodeDefManager(); // Check if name is defined std::string name; lua_getfield(L, table, "name"); - if(lua_isstring(L, -1)){ + if (lua_isstring(L, -1)) { name = readParam(L, -1); } else { throw LuaError("register_item_raw: name is not defined or not a string"); @@ -552,8 +532,8 @@ int ModApiItemMod::l_register_item_raw(lua_State *L) // Default to having client-side placement prediction for nodes // ("" in item definition sets it off) - if(def.node_placement_prediction == "__default"){ - if(def.type == ITEM_NODE) + if (def.node_placement_prediction == "__default") { + if (def.type == ITEM_NODE) def.node_placement_prediction = name; else def.node_placement_prediction = ""; @@ -573,13 +553,12 @@ int ModApiItemMod::l_register_item_raw(lua_State *L) content_t id = ndef->set(f.name, f); if (id > MAX_REGISTERED_CONTENT) { - throw LuaError("Number of registerable nodes (" - + itos(MAX_REGISTERED_CONTENT+1) - + ") exceeded (" + name + ")"); + throw LuaError("Number of registerable nodes (" + + itos(MAX_REGISTERED_CONTENT + 1) + + ") exceeded (" + name + ")"); } - } - + return 0; /* number of results */ } @@ -589,13 +568,11 @@ int ModApiItemMod::l_unregister_item_raw(lua_State *L) NO_MAP_LOCK_REQUIRED; std::string name = luaL_checkstring(L, 1); - IWritableItemDefManager *idef = - getGameDef(L)->getWritableItemDefManager(); + IWritableItemDefManager *idef = getGameDef(L)->getWritableItemDefManager(); // Unregister the node if (idef->get(name).type == ITEM_NODE) { - NodeDefManager *ndef = - getGameDef(L)->getWritableNodeDefManager(); + NodeDefManager *ndef = getGameDef(L)->getWritableNodeDefManager(); ndef->removeNode(name); } @@ -612,8 +589,7 @@ int ModApiItemMod::l_register_alias_raw(lua_State *L) std::string convert_to = luaL_checkstring(L, 2); // Get the writable item definition manager from the server - IWritableItemDefManager *idef = - getGameDef(L)->getWritableItemDefManager(); + IWritableItemDefManager *idef = getGameDef(L)->getWritableItemDefManager(); idef->registerAlias(name, convert_to); @@ -636,8 +612,8 @@ int ModApiItemMod::l_get_content_id(lua_State *L) content_t content_id; if (alias_name != name) { if (!ndef->getId(alias_name, content_id)) - throw LuaError("Unknown node: " + alias_name + - " (from alias " + name + ")"); + throw LuaError("Unknown node: " + alias_name + " (from alias " + + name + ")"); } else if (!ndef->getId(name, content_id)) { throw LuaError("Unknown node: " + name); } diff --git a/src/script/lua_api/l_item.h b/src/script/lua_api/l_item.h index 98744c071..21110b83a 100644 --- a/src/script/lua_api/l_item.h +++ b/src/script/lua_api/l_item.h @@ -20,9 +20,10 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once #include "lua_api/l_base.h" -#include "inventory.h" // ItemStack +#include "inventory.h" // ItemStack -class LuaItemStack : public ModApiBase { +class LuaItemStack : public ModApiBase +{ private: ItemStack m_stack; @@ -114,8 +115,8 @@ private: // Returns leftover item stack static int l_add_item(lua_State *L); - // item_fits(self, itemstack or itemstring or table or nil) -> true/false, itemstack - // First return value is true iff the new item fits fully into the stack + // item_fits(self, itemstack or itemstring or table or nil) -> true/false, + // itemstack First return value is true iff the new item fits fully into the stack // Second return value is the would-be-left-over item stack static int l_item_fits(lua_State *L); @@ -129,26 +130,27 @@ public: LuaItemStack(const ItemStack &item); ~LuaItemStack() = default; - const ItemStack& getItem() const; - ItemStack& getItem(); + const ItemStack &getItem() const; + ItemStack &getItem(); // LuaItemStack(itemstack or itemstring or table or nil) // Creates an LuaItemStack and leaves it on top of stack static int create_object(lua_State *L); // Not callable from Lua static int create(lua_State *L, const ItemStack &item); - static LuaItemStack* checkobject(lua_State *L, int narg); + static LuaItemStack *checkobject(lua_State *L, int narg); static void Register(lua_State *L); - }; -class ModApiItemMod : public ModApiBase { +class ModApiItemMod : public ModApiBase +{ private: static int l_register_item_raw(lua_State *L); static int l_unregister_item_raw(lua_State *L); static int l_register_alias_raw(lua_State *L); static int l_get_content_id(lua_State *L); static int l_get_name_from_content_id(lua_State *L); + public: static void Initialize(lua_State *L, int top); }; diff --git a/src/script/lua_api/l_itemstackmeta.cpp b/src/script/lua_api/l_itemstackmeta.cpp index d1ba1bda4..5a5c9934a 100644 --- a/src/script/lua_api/l_itemstackmeta.cpp +++ b/src/script/lua_api/l_itemstackmeta.cpp @@ -26,17 +26,17 @@ with this program; if not, write to the Free Software Foundation, Inc., /* NodeMetaRef */ -ItemStackMetaRef* ItemStackMetaRef::checkobject(lua_State *L, int narg) +ItemStackMetaRef *ItemStackMetaRef::checkobject(lua_State *L, int narg) { luaL_checktype(L, narg, LUA_TUSERDATA); void *ud = luaL_checkudata(L, narg, className); if (!ud) luaL_typerror(L, narg, className); - return *(ItemStackMetaRef**)ud; // unbox pointer + return *(ItemStackMetaRef **)ud; // unbox pointer } -Metadata* ItemStackMetaRef::getmeta(bool auto_create) +Metadata *ItemStackMetaRef::getmeta(bool auto_create) { return &istack->metadata; } @@ -68,7 +68,8 @@ int ItemStackMetaRef::l_set_tool_capabilities(lua_State *L) } // garbage collector -int ItemStackMetaRef::gc_object(lua_State *L) { +int ItemStackMetaRef::gc_object(lua_State *L) +{ ItemStackMetaRef *o = *(ItemStackMetaRef **)(lua_touserdata(L, 1)); delete o; return 0; @@ -79,7 +80,7 @@ int ItemStackMetaRef::gc_object(lua_State *L) { void ItemStackMetaRef::create(lua_State *L, ItemStack *istack) { ItemStackMetaRef *o = new ItemStackMetaRef(istack); - //infostream<<"NodeMetaRef::create: o="<metadata.setToolCapabilities(caps); } - void clearToolCapabilities() - { - istack->metadata.clearToolCapabilities(); - } + void clearToolCapabilities() { istack->metadata.clearToolCapabilities(); } // Exported functions static int l_set_tool_capabilities(lua_State *L); // garbage collector static int gc_object(lua_State *L); + public: - ItemStackMetaRef(ItemStack *istack): istack(istack) {} + ItemStackMetaRef(ItemStack *istack) : istack(istack) {} ~ItemStackMetaRef() = default; // Creates an ItemStackMetaRef and leaves it on top of stack diff --git a/src/script/lua_api/l_localplayer.cpp b/src/script/lua_api/l_localplayer.cpp index e40dd7b37..789b3a3f0 100644 --- a/src/script/lua_api/l_localplayer.cpp +++ b/src/script/lua_api/l_localplayer.cpp @@ -66,10 +66,10 @@ int LuaLocalPlayer::l_get_velocity(lua_State *L) int LuaLocalPlayer::l_set_velocity(lua_State *L) { LocalPlayer *player = getobject(L, 1); - + v3f pos = checkFloatPos(L, 2); player->setSpeed(pos); - + return 0; } @@ -91,7 +91,7 @@ int LuaLocalPlayer::l_set_yaw(lua_State *L) g_game->cam_view.camera_yaw = yaw; g_game->cam_view_target.camera_yaw = yaw; } - + return 0; } @@ -125,7 +125,7 @@ int LuaLocalPlayer::l_set_wield_index(lua_State *L) { LocalPlayer *player = getobject(L, 1); u32 index = luaL_checkinteger(L, 2) - 1; - + player->setWieldIndex(index); g_game->processItemSelection(&g_game->runData.new_playeritem); ItemStack selected_item, hand_item; @@ -266,7 +266,7 @@ int LuaLocalPlayer::l_get_control(lua_State *L) LocalPlayer *player = getobject(L, 1); const PlayerControl &c = player->getPlayerControl(); - auto set = [L] (const char *name, bool value) { + auto set = [L](const char *name, bool value) { lua_pushboolean(L, value); lua_setfield(L, -2, name); }; @@ -308,7 +308,7 @@ int LuaLocalPlayer::l_get_pos(lua_State *L) int LuaLocalPlayer::l_set_pos(lua_State *L) { LocalPlayer *player = getobject(L, 1); - + v3f pos = checkFloatPos(L, 2); player->setPosition(pos); getClient(L)->sendPlayerPos(); @@ -522,13 +522,10 @@ void LuaLocalPlayer::Register(lua_State *L) } const char LuaLocalPlayer::className[] = "LocalPlayer"; -const luaL_Reg LuaLocalPlayer::methods[] = { - luamethod(LuaLocalPlayer, get_velocity), +const luaL_Reg LuaLocalPlayer::methods[] = {luamethod(LuaLocalPlayer, get_velocity), luamethod(LuaLocalPlayer, set_velocity), - luamethod(LuaLocalPlayer, get_yaw), - luamethod(LuaLocalPlayer, set_yaw), - luamethod(LuaLocalPlayer, get_hp), - luamethod(LuaLocalPlayer, get_name), + luamethod(LuaLocalPlayer, get_yaw), luamethod(LuaLocalPlayer, set_yaw), + luamethod(LuaLocalPlayer, get_hp), luamethod(LuaLocalPlayer, get_name), luamethod(LuaLocalPlayer, get_wield_index), luamethod(LuaLocalPlayer, set_wield_index), luamethod(LuaLocalPlayer, get_wielded_item), @@ -547,18 +544,14 @@ const luaL_Reg LuaLocalPlayer::methods[] = { luamethod(LuaLocalPlayer, get_last_look_vertical), // luamethod(LuaLocalPlayer, get_control), - luamethod(LuaLocalPlayer, get_breath), - luamethod(LuaLocalPlayer, get_pos), + luamethod(LuaLocalPlayer, get_breath), luamethod(LuaLocalPlayer, get_pos), luamethod(LuaLocalPlayer, set_pos), luamethod(LuaLocalPlayer, get_movement_acceleration), luamethod(LuaLocalPlayer, get_movement_speed), luamethod(LuaLocalPlayer, get_movement), luamethod(LuaLocalPlayer, get_armor_groups), - luamethod(LuaLocalPlayer, hud_add), - luamethod(LuaLocalPlayer, hud_remove), - luamethod(LuaLocalPlayer, hud_change), - luamethod(LuaLocalPlayer, hud_get), + luamethod(LuaLocalPlayer, hud_add), luamethod(LuaLocalPlayer, hud_remove), + luamethod(LuaLocalPlayer, hud_change), luamethod(LuaLocalPlayer, hud_get), luamethod(LuaLocalPlayer, get_object), - {0, 0} -}; + {0, 0}}; diff --git a/src/script/lua_api/l_mainmenu.cpp b/src/script/lua_api/l_mainmenu.cpp index f32c477c2..78a33b755 100644 --- a/src/script/lua_api/l_mainmenu.cpp +++ b/src/script/lua_api/l_mainmenu.cpp @@ -41,7 +41,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "client/renderingengine.h" #include "network/networkprotocol.h" - /******************************************************************************/ std::string ModApiMainMenu::getTextData(lua_State *L, std::string name) { @@ -49,39 +48,39 @@ std::string ModApiMainMenu::getTextData(lua_State *L, std::string name) lua_getfield(L, -1, name.c_str()); - if(lua_isnil(L, -1)) + if (lua_isnil(L, -1)) return ""; return luaL_checkstring(L, -1); } /******************************************************************************/ -int ModApiMainMenu::getIntegerData(lua_State *L, std::string name,bool& valid) +int ModApiMainMenu::getIntegerData(lua_State *L, std::string name, bool &valid) { lua_getglobal(L, "gamedata"); lua_getfield(L, -1, name.c_str()); - if(lua_isnil(L, -1)) { + if (lua_isnil(L, -1)) { valid = false; return -1; - } + } valid = true; return luaL_checkinteger(L, -1); } /******************************************************************************/ -int ModApiMainMenu::getBoolData(lua_State *L, std::string name,bool& valid) +int ModApiMainMenu::getBoolData(lua_State *L, std::string name, bool &valid) { lua_getglobal(L, "gamedata"); lua_getfield(L, -1, name.c_str()); - if(lua_isnil(L, -1)) { + if (lua_isnil(L, -1)) { valid = false; return false; - } + } valid = true; return readParam(L, -1); @@ -90,13 +89,13 @@ int ModApiMainMenu::getBoolData(lua_State *L, std::string name,bool& valid) /******************************************************************************/ int ModApiMainMenu::l_update_formspec(lua_State *L) { - GUIEngine* engine = getGuiEngine(L); + GUIEngine *engine = getGuiEngine(L); sanity_check(engine != NULL); if (engine->m_startgame) return 0; - //read formspec + // read formspec std::string formspec(luaL_checkstring(L, 1)); if (engine->m_formspecgui != 0) { @@ -124,28 +123,28 @@ int ModApiMainMenu::l_set_formspec_prepend(lua_State *L) /******************************************************************************/ int ModApiMainMenu::l_start(lua_State *L) { - GUIEngine* engine = getGuiEngine(L); + GUIEngine *engine = getGuiEngine(L); sanity_check(engine != NULL); - //update c++ gamedata from lua table + // update c++ gamedata from lua table bool valid = false; MainMenuData *data = engine->m_data; - data->selected_world = getIntegerData(L, "selected_world",valid) -1; - data->simple_singleplayer_mode = getBoolData(L,"singleplayer",valid); + data->selected_world = getIntegerData(L, "selected_world", valid) - 1; + data->simple_singleplayer_mode = getBoolData(L, "singleplayer", valid); data->do_reconnect = getBoolData(L, "do_reconnect", valid); if (!data->do_reconnect) { - data->name = getTextData(L,"playername"); - data->password = getTextData(L,"password"); - data->address = getTextData(L,"address"); - data->port = getTextData(L,"port"); + data->name = getTextData(L, "playername"); + data->password = getTextData(L, "password"); + data->address = getTextData(L, "address"); + data->port = getTextData(L, "port"); } - data->serverdescription = getTextData(L,"serverdescription"); - data->servername = getTextData(L,"servername"); + data->serverdescription = getTextData(L, "serverdescription"); + data->servername = getTextData(L, "servername"); - //close menu next time + // close menu next time engine->m_startgame = true; return 0; } @@ -153,7 +152,7 @@ int ModApiMainMenu::l_start(lua_State *L) /******************************************************************************/ int ModApiMainMenu::l_close(lua_State *L) { - GUIEngine* engine = getGuiEngine(L); + GUIEngine *engine = getGuiEngine(L); sanity_check(engine != NULL); engine->m_kill = true; @@ -163,14 +162,14 @@ int ModApiMainMenu::l_close(lua_State *L) /******************************************************************************/ int ModApiMainMenu::l_set_background(lua_State *L) { - GUIEngine* engine = getGuiEngine(L); + GUIEngine *engine = getGuiEngine(L); sanity_check(engine != NULL); std::string backgroundlevel(luaL_checkstring(L, 1)); std::string texturename(luaL_checkstring(L, 2)); bool tile_image = false; - bool retval = false; + bool retval = false; unsigned int minsize = 16; if (!lua_isnone(L, 3)) { @@ -182,36 +181,36 @@ int ModApiMainMenu::l_set_background(lua_State *L) } if (backgroundlevel == "background") { - retval |= engine->setTexture(TEX_LAYER_BACKGROUND, texturename, - tile_image, minsize); + retval |= engine->setTexture( + TEX_LAYER_BACKGROUND, texturename, tile_image, minsize); } if (backgroundlevel == "overlay") { - retval |= engine->setTexture(TEX_LAYER_OVERLAY, texturename, - tile_image, minsize); + retval |= engine->setTexture( + TEX_LAYER_OVERLAY, texturename, tile_image, minsize); } if (backgroundlevel == "header") { - retval |= engine->setTexture(TEX_LAYER_HEADER, texturename, - tile_image, minsize); + retval |= engine->setTexture( + TEX_LAYER_HEADER, texturename, tile_image, minsize); } if (backgroundlevel == "footer") { - retval |= engine->setTexture(TEX_LAYER_FOOTER, texturename, - tile_image, minsize); + retval |= engine->setTexture( + TEX_LAYER_FOOTER, texturename, tile_image, minsize); } - lua_pushboolean(L,retval); + lua_pushboolean(L, retval); return 1; } /******************************************************************************/ int ModApiMainMenu::l_set_clouds(lua_State *L) { - GUIEngine* engine = getGuiEngine(L); + GUIEngine *engine = getGuiEngine(L); sanity_check(engine != NULL); - bool value = readParam(L,1); + bool value = readParam(L, 1); engine->m_clouds_enabled = value; @@ -228,7 +227,7 @@ int ModApiMainMenu::l_get_textlist_index(lua_State *L) /******************************************************************************/ int ModApiMainMenu::l_get_table_index(lua_State *L) { - GUIEngine* engine = getGuiEngine(L); + GUIEngine *engine = getGuiEngine(L); sanity_check(engine != NULL); std::string tablename(luaL_checkstring(L, 1)); @@ -252,20 +251,20 @@ int ModApiMainMenu::l_get_worlds(lua_State *L) unsigned int index = 1; for (const WorldSpec &world : worlds) { - lua_pushnumber(L,index); + lua_pushnumber(L, index); lua_newtable(L); int top_lvl2 = lua_gettop(L); - lua_pushstring(L,"path"); + lua_pushstring(L, "path"); lua_pushstring(L, world.path.c_str()); lua_settable(L, top_lvl2); - lua_pushstring(L,"name"); + lua_pushstring(L, "name"); lua_pushstring(L, world.name.c_str()); lua_settable(L, top_lvl2); - lua_pushstring(L,"gameid"); + lua_pushstring(L, "gameid"); lua_pushstring(L, world.gameid.c_str()); lua_settable(L, top_lvl2); @@ -286,7 +285,7 @@ int ModApiMainMenu::l_get_favorites(lua_State *L) std::vector servers; - if(listtype == "online") { + if (listtype == "online") { servers = ServerList::getOnline(); } else { servers = ServerList::getLocal(); @@ -305,8 +304,8 @@ int ModApiMainMenu::l_get_favorites(lua_State *L) if (!server["clients"].asString().empty()) { std::string clients_raw = server["clients"].asString(); - char* endptr = 0; - int numbervalue = strtol(clients_raw.c_str(), &endptr,10); + char *endptr = 0; + int numbervalue = strtol(clients_raw.c_str(), &endptr, 10); if ((!clients_raw.empty()) && (*endptr == 0)) { lua_pushstring(L, "clients"); @@ -318,8 +317,8 @@ int ModApiMainMenu::l_get_favorites(lua_State *L) if (!server["clients_max"].asString().empty()) { std::string clients_max_raw = server["clients_max"].asString(); - char* endptr = 0; - int numbervalue = strtol(clients_max_raw.c_str(), &endptr,10); + char *endptr = 0; + int numbervalue = strtol(clients_max_raw.c_str(), &endptr, 10); if ((!clients_max_raw.empty()) && (*endptr == 0)) { lua_pushstring(L, "clients_max"); @@ -450,25 +449,22 @@ int ModApiMainMenu::l_delete_favorite(lua_State *L) std::string listtype = "local"; - if (!lua_isnone(L,2)) { - listtype = luaL_checkstring(L,2); + if (!lua_isnone(L, 2)) { + listtype = luaL_checkstring(L, 2); } - if ((listtype != "local") && - (listtype != "online")) + if ((listtype != "local") && (listtype != "online")) return 0; - - if(listtype == "online") { + if (listtype == "online") { servers = ServerList::getOnline(); } else { servers = ServerList::getLocal(); } - int fav_idx = luaL_checkinteger(L,1) -1; + int fav_idx = luaL_checkinteger(L, 1) - 1; - if ((fav_idx >= 0) && - (fav_idx < (int) servers.size())) { + if ((fav_idx >= 0) && (fav_idx < (int)servers.size())) { ServerList::deleteEntry(servers[fav_idx]); } @@ -490,37 +486,37 @@ int ModApiMainMenu::l_get_games(lua_State *L) lua_newtable(L); int top_lvl2 = lua_gettop(L); - lua_pushstring(L, "id"); - lua_pushstring(L, game.id.c_str()); - lua_settable(L, top_lvl2); + lua_pushstring(L, "id"); + lua_pushstring(L, game.id.c_str()); + lua_settable(L, top_lvl2); - lua_pushstring(L, "path"); - lua_pushstring(L, game.path.c_str()); - lua_settable(L, top_lvl2); + lua_pushstring(L, "path"); + lua_pushstring(L, game.path.c_str()); + lua_settable(L, top_lvl2); - lua_pushstring(L, "type"); - lua_pushstring(L, "game"); - lua_settable(L, top_lvl2); + lua_pushstring(L, "type"); + lua_pushstring(L, "game"); + lua_settable(L, top_lvl2); - lua_pushstring(L, "gamemods_path"); - lua_pushstring(L, game.gamemods_path.c_str()); - lua_settable(L, top_lvl2); + lua_pushstring(L, "gamemods_path"); + lua_pushstring(L, game.gamemods_path.c_str()); + lua_settable(L, top_lvl2); - lua_pushstring(L, "name"); - lua_pushstring(L, game.name.c_str()); - lua_settable(L, top_lvl2); + lua_pushstring(L, "name"); + lua_pushstring(L, game.name.c_str()); + lua_settable(L, top_lvl2); - lua_pushstring(L, "author"); - lua_pushstring(L, game.author.c_str()); - lua_settable(L, top_lvl2); + lua_pushstring(L, "author"); + lua_pushstring(L, game.author.c_str()); + lua_settable(L, top_lvl2); - lua_pushstring(L, "release"); + lua_pushstring(L, "release"); lua_pushinteger(L, game.release); - lua_settable(L, top_lvl2); + lua_settable(L, top_lvl2); - lua_pushstring(L, "menuicon_path"); - lua_pushstring(L, game.menuicon_path.c_str()); - lua_settable(L, top_lvl2); + lua_pushstring(L, "menuicon_path"); + lua_pushstring(L, game.menuicon_path.c_str()); + lua_settable(L, top_lvl2); lua_pushstring(L, "addon_mods_paths"); lua_newtable(L); @@ -529,7 +525,7 @@ int ModApiMainMenu::l_get_games(lua_State *L) for (const std::string &addon_mods_path : game.addon_mods_paths) { lua_pushnumber(L, internal_index); lua_pushstring(L, addon_mods_path.c_str()); - lua_settable(L, table2); + lua_settable(L, table2); internal_index++; } lua_settable(L, top_lvl2); @@ -598,13 +594,11 @@ int ModApiMainMenu::l_get_content_info(lua_State *L) /******************************************************************************/ int ModApiMainMenu::l_show_keys_menu(lua_State *L) { - GUIEngine* engine = getGuiEngine(L); + GUIEngine *engine = getGuiEngine(L); sanity_check(engine != NULL); GUIKeyChangeMenu *kmenu = new GUIKeyChangeMenu(RenderingEngine::get_gui_env(), - engine->m_parent, - -1, - engine->m_menumanager, + engine->m_parent, -1, engine->m_menumanager, engine->m_texture_source); kmenu->drop(); return 0; @@ -613,17 +607,14 @@ int ModApiMainMenu::l_show_keys_menu(lua_State *L) /******************************************************************************/ int ModApiMainMenu::l_create_world(lua_State *L) { - const char *name = luaL_checkstring(L, 1); - int gameidx = luaL_checkinteger(L,2) -1; + const char *name = luaL_checkstring(L, 1); + int gameidx = luaL_checkinteger(L, 2) - 1; - std::string path = porting::path_user + DIR_DELIM - "worlds" + DIR_DELIM - + name; + std::string path = porting::path_user + DIR_DELIM "worlds" + DIR_DELIM + name; std::vector games = getAvailableGames(); - if ((gameidx >= 0) && - (gameidx < (int) games.size())) { + if ((gameidx >= 0) && (gameidx < (int)games.size())) { // Create world if it doesn't exist if (!loadGameConfAndInitWorld(path, games[gameidx])) { @@ -642,7 +633,7 @@ int ModApiMainMenu::l_delete_world(lua_State *L) { int world_id = luaL_checkinteger(L, 1) - 1; std::vector worlds = getAvailableWorlds(); - if (world_id < 0 || world_id >= (int) worlds.size()) { + if (world_id < 0 || world_id >= (int)worlds.size()) { lua_pushstring(L, "Invalid world index"); return 1; } @@ -657,12 +648,12 @@ int ModApiMainMenu::l_delete_world(lua_State *L) /******************************************************************************/ int ModApiMainMenu::l_set_topleft_text(lua_State *L) { - GUIEngine* engine = getGuiEngine(L); + GUIEngine *engine = getGuiEngine(L); sanity_check(engine != NULL); std::string text; - if (!lua_isnone(L,1) && !lua_isnil(L,1)) + if (!lua_isnone(L, 1) && !lua_isnil(L, 1)) text = luaL_checkstring(L, 1); engine->setTopleftText(text); @@ -685,12 +676,11 @@ int ModApiMainMenu::l_get_mapgen_names(lua_State *L) return 1; } - /******************************************************************************/ int ModApiMainMenu::l_get_modpath(lua_State *L) { std::string modpath = fs::RemoveRelativePathComponents( - porting::path_user + DIR_DELIM + "mods" + DIR_DELIM); + porting::path_user + DIR_DELIM + "mods" + DIR_DELIM); lua_pushstring(L, modpath.c_str()); return 1; } @@ -699,7 +689,7 @@ int ModApiMainMenu::l_get_modpath(lua_State *L) int ModApiMainMenu::l_get_clientmodpath(lua_State *L) { std::string modpath = fs::RemoveRelativePathComponents( - porting::path_user + DIR_DELIM + "clientmods" + DIR_DELIM); + porting::path_user + DIR_DELIM + "clientmods" + DIR_DELIM); lua_pushstring(L, modpath.c_str()); return 1; } @@ -708,7 +698,7 @@ int ModApiMainMenu::l_get_clientmodpath(lua_State *L) int ModApiMainMenu::l_get_gamepath(lua_State *L) { std::string gamepath = fs::RemoveRelativePathComponents( - porting::path_user + DIR_DELIM + "games" + DIR_DELIM); + porting::path_user + DIR_DELIM + "games" + DIR_DELIM); lua_pushstring(L, gamepath.c_str()); return 1; } @@ -717,7 +707,7 @@ int ModApiMainMenu::l_get_gamepath(lua_State *L) int ModApiMainMenu::l_get_texturepath(lua_State *L) { std::string gamepath = fs::RemoveRelativePathComponents( - porting::path_user + DIR_DELIM + "textures"); + porting::path_user + DIR_DELIM + "textures"); lua_pushstring(L, gamepath.c_str()); return 1; } @@ -725,7 +715,7 @@ int ModApiMainMenu::l_get_texturepath(lua_State *L) int ModApiMainMenu::l_get_texturepath_share(lua_State *L) { std::string gamepath = fs::RemoveRelativePathComponents( - porting::path_share + DIR_DELIM + "textures"); + porting::path_share + DIR_DELIM + "textures"); lua_pushstring(L, gamepath.c_str()); return 1; } @@ -737,7 +727,8 @@ int ModApiMainMenu::l_get_cache_path(lua_State *L) } /******************************************************************************/ -int ModApiMainMenu::l_create_dir(lua_State *L) { +int ModApiMainMenu::l_create_dir(lua_State *L) +{ const char *path = luaL_checkstring(L, 1); if (ModApiMainMenu::mayModifyPath(path)) { @@ -768,38 +759,37 @@ int ModApiMainMenu::l_delete_dir(lua_State *L) /******************************************************************************/ int ModApiMainMenu::l_copy_dir(lua_State *L) { - const char *source = luaL_checkstring(L, 1); - const char *destination = luaL_checkstring(L, 2); + const char *source = luaL_checkstring(L, 1); + const char *destination = luaL_checkstring(L, 2); bool keep_source = true; - if ((!lua_isnone(L,3)) && - (!lua_isnil(L,3))) { - keep_source = readParam(L,3); + if ((!lua_isnone(L, 3)) && (!lua_isnil(L, 3))) { + keep_source = readParam(L, 3); } std::string absolute_destination = fs::RemoveRelativePathComponents(destination); std::string absolute_source = fs::RemoveRelativePathComponents(source); if ((ModApiMainMenu::mayModifyPath(absolute_destination))) { - bool retval = fs::CopyDir(absolute_source,absolute_destination); + bool retval = fs::CopyDir(absolute_source, absolute_destination); if (retval && (!keep_source)) { retval &= fs::RecursiveDelete(absolute_source); } - lua_pushboolean(L,retval); + lua_pushboolean(L, retval); return 1; } - lua_pushboolean(L,false); + lua_pushboolean(L, false); return 1; } /******************************************************************************/ int ModApiMainMenu::l_extract_zip(lua_State *L) { - const char *zipfile = luaL_checkstring(L, 1); - const char *destination = luaL_checkstring(L, 2); + const char *zipfile = luaL_checkstring(L, 1); + const char *destination = luaL_checkstring(L, 2); std::string absolute_destination = fs::RemoveRelativePathComponents(destination); @@ -809,7 +799,7 @@ int ModApiMainMenu::l_extract_zip(lua_State *L) io::IFileSystem *fs = RenderingEngine::get_filesystem(); if (!fs->addFileArchive(zipfile, false, false, io::EFAT_ZIP)) { - lua_pushboolean(L,false); + lua_pushboolean(L, false); return 1; } @@ -818,33 +808,36 @@ int ModApiMainMenu::l_extract_zip(lua_State *L) /**********************************************************************/ /* WARNING this is not threadsafe!! */ /**********************************************************************/ - io::IFileArchive* opened_zip = - fs->getFileArchive(fs->getFileArchiveCount()-1); + io::IFileArchive *opened_zip = + fs->getFileArchive(fs->getFileArchiveCount() - 1); - const io::IFileList* files_in_zip = opened_zip->getFileList(); + const io::IFileList *files_in_zip = opened_zip->getFileList(); unsigned int number_of_files = files_in_zip->getFileCount(); - for (unsigned int i=0; i < number_of_files; i++) { + for (unsigned int i = 0; i < number_of_files; i++) { std::string fullpath = destination; fullpath += DIR_DELIM; fullpath += files_in_zip->getFullFileName(i).c_str(); std::string fullpath_dir = fs::RemoveLastPathComponent(fullpath); if (!files_in_zip->isDirectory(i)) { - if (!fs::PathExists(fullpath_dir) && !fs::CreateAllDirs(fullpath_dir)) { - fs->removeFileArchive(fs->getFileArchiveCount()-1); - lua_pushboolean(L,false); + if (!fs::PathExists(fullpath_dir) && + !fs::CreateAllDirs(fullpath_dir)) { + fs->removeFileArchive( + fs->getFileArchiveCount() - 1); + lua_pushboolean(L, false); return 1; } - io::IReadFile* toread = opened_zip->createAndOpenFile(i); + io::IReadFile *toread = opened_zip->createAndOpenFile(i); - FILE *targetfile = fopen(fullpath.c_str(),"wb"); + FILE *targetfile = fopen(fullpath.c_str(), "wb"); if (targetfile == NULL) { - fs->removeFileArchive(fs->getFileArchiveCount()-1); - lua_pushboolean(L,false); + fs->removeFileArchive( + fs->getFileArchiveCount() - 1); + lua_pushboolean(L, false); return 1; } @@ -853,14 +846,18 @@ int ModApiMainMenu::l_extract_zip(lua_State *L) while (total_read < toread->getSize()) { - unsigned int bytes_read = - toread->read(read_buffer,sizeof(read_buffer)); - if ((bytes_read == 0 ) || - (fwrite(read_buffer, 1, bytes_read, targetfile) != bytes_read)) - { + unsigned int bytes_read = toread->read( + read_buffer, sizeof(read_buffer)); + if ((bytes_read == 0) || + (fwrite(read_buffer, 1, + bytes_read, + targetfile) != + bytes_read)) { fclose(targetfile); - fs->removeFileArchive(fs->getFileArchiveCount()-1); - lua_pushboolean(L,false); + fs->removeFileArchive( + fs->getFileArchiveCount() - + 1); + lua_pushboolean(L, false); return 1; } total_read += bytes_read; @@ -868,25 +865,24 @@ int ModApiMainMenu::l_extract_zip(lua_State *L) fclose(targetfile); } - } - fs->removeFileArchive(fs->getFileArchiveCount()-1); - lua_pushboolean(L,true); + fs->removeFileArchive(fs->getFileArchiveCount() - 1); + lua_pushboolean(L, true); return 1; } - lua_pushboolean(L,false); + lua_pushboolean(L, false); return 1; } /******************************************************************************/ int ModApiMainMenu::l_get_mainmenu_path(lua_State *L) { - GUIEngine* engine = getGuiEngine(L); + GUIEngine *engine = getGuiEngine(L); sanity_check(engine != NULL); - lua_pushstring(L,engine->getScriptDir().c_str()); + lua_pushstring(L, engine->getScriptDir().c_str()); return 1; } @@ -896,25 +892,31 @@ bool ModApiMainMenu::mayModifyPath(const std::string &path) if (fs::PathStartsWith(path, fs::TempPath())) return true; - if (fs::PathStartsWith(path, fs::RemoveRelativePathComponents(porting::path_user + DIR_DELIM "games"))) + if (fs::PathStartsWith(path, fs::RemoveRelativePathComponents(porting::path_user + + DIR_DELIM "games"))) return true; - if (fs::PathStartsWith(path, fs::RemoveRelativePathComponents(porting::path_user + DIR_DELIM "mods"))) + if (fs::PathStartsWith(path, fs::RemoveRelativePathComponents(porting::path_user + + DIR_DELIM "mods"))) return true; - if (fs::PathStartsWith(path, fs::RemoveRelativePathComponents(porting::path_user + DIR_DELIM "textures"))) + if (fs::PathStartsWith(path, + fs::RemoveRelativePathComponents( + porting::path_user + DIR_DELIM "textures"))) return true; - if (fs::PathStartsWith(path, fs::RemoveRelativePathComponents(porting::path_user + DIR_DELIM "worlds"))) + if (fs::PathStartsWith(path, + fs::RemoveRelativePathComponents( + porting::path_user + DIR_DELIM "worlds"))) return true; - if (fs::PathStartsWith(path, fs::RemoveRelativePathComponents(porting::path_cache))) + if (fs::PathStartsWith( + path, fs::RemoveRelativePathComponents(porting::path_cache))) return true; return false; } - /******************************************************************************/ int ModApiMainMenu::l_may_modify_path(lua_State *L) { @@ -927,21 +929,16 @@ int ModApiMainMenu::l_may_modify_path(lua_State *L) /******************************************************************************/ int ModApiMainMenu::l_show_path_select_dialog(lua_State *L) { - GUIEngine* engine = getGuiEngine(L); + GUIEngine *engine = getGuiEngine(L); sanity_check(engine != NULL); - const char *formname= luaL_checkstring(L, 1); - const char *title = luaL_checkstring(L, 2); + const char *formname = luaL_checkstring(L, 1); + const char *title = luaL_checkstring(L, 2); bool is_file_select = readParam(L, 3); - GUIFileSelectMenu* fileOpenMenu = - new GUIFileSelectMenu(RenderingEngine::get_gui_env(), - engine->m_parent, - -1, - engine->m_menumanager, - title, - formname, - is_file_select); + GUIFileSelectMenu *fileOpenMenu = new GUIFileSelectMenu( + RenderingEngine::get_gui_env(), engine->m_parent, -1, + engine->m_menumanager, title, formname, is_file_select); fileOpenMenu->setTextDest(engine->m_buttonhandler); fileOpenMenu->drop(); return 0; @@ -950,34 +947,36 @@ int ModApiMainMenu::l_show_path_select_dialog(lua_State *L) /******************************************************************************/ int ModApiMainMenu::l_download_file(lua_State *L) { - const char *url = luaL_checkstring(L, 1); + const char *url = luaL_checkstring(L, 1); const char *target = luaL_checkstring(L, 2); - //check path + // check path std::string absolute_destination = fs::RemoveRelativePathComponents(target); if (ModApiMainMenu::mayModifyPath(absolute_destination)) { - if (GUIEngine::downloadFile(url,absolute_destination)) { - lua_pushboolean(L,true); + if (GUIEngine::downloadFile(url, absolute_destination)) { + lua_pushboolean(L, true); return 1; } } else { errorstream << "DOWNLOAD denied: " << absolute_destination - << " isn't a allowed path" << std::endl; + << " isn't a allowed path" << std::endl; } - lua_pushboolean(L,false); + lua_pushboolean(L, false); return 1; } /******************************************************************************/ int ModApiMainMenu::l_get_video_drivers(lua_State *L) { - std::vector drivers = RenderingEngine::getSupportedVideoDrivers(); + std::vector drivers = + RenderingEngine::getSupportedVideoDrivers(); lua_newtable(L); for (u32 i = 0; i != drivers.size(); i++) { - const char *name = RenderingEngine::getVideoDriverName(drivers[i]); - const char *fname = RenderingEngine::getVideoDriverFriendlyName(drivers[i]); + const char *name = RenderingEngine::getVideoDriverName(drivers[i]); + const char *fname = + RenderingEngine::getVideoDriverFriendlyName(drivers[i]); lua_newtable(L); lua_pushstring(L, name); @@ -994,8 +993,8 @@ int ModApiMainMenu::l_get_video_drivers(lua_State *L) /******************************************************************************/ int ModApiMainMenu::l_get_video_modes(lua_State *L) { - std::vector > videomodes - = RenderingEngine::getSupportedVideoModes(); + std::vector> videomodes = + RenderingEngine::getSupportedVideoModes(); lua_newtable(L); for (u32 i = 0; i != videomodes.size(); i++) { @@ -1027,24 +1026,24 @@ int ModApiMainMenu::l_get_screen_info(lua_State *L) { lua_newtable(L); int top = lua_gettop(L); - lua_pushstring(L,"density"); - lua_pushnumber(L,RenderingEngine::getDisplayDensity()); + lua_pushstring(L, "density"); + lua_pushnumber(L, RenderingEngine::getDisplayDensity()); lua_settable(L, top); - lua_pushstring(L,"display_width"); - lua_pushnumber(L,RenderingEngine::getDisplaySize().X); + lua_pushstring(L, "display_width"); + lua_pushnumber(L, RenderingEngine::getDisplaySize().X); lua_settable(L, top); - lua_pushstring(L,"display_height"); - lua_pushnumber(L,RenderingEngine::getDisplaySize().Y); + lua_pushstring(L, "display_height"); + lua_pushnumber(L, RenderingEngine::getDisplaySize().Y); lua_settable(L, top); const v2u32 &window_size = RenderingEngine::get_instance()->getWindowSize(); - lua_pushstring(L,"window_width"); + lua_pushstring(L, "window_width"); lua_pushnumber(L, window_size.X); lua_settable(L, top); - lua_pushstring(L,"window_height"); + lua_pushstring(L, "window_height"); lua_pushnumber(L, window_size.Y); lua_settable(L, top); return 1; @@ -1074,12 +1073,12 @@ int ModApiMainMenu::l_open_url(lua_State *L) /******************************************************************************/ int ModApiMainMenu::l_do_async_callback(lua_State *L) { - GUIEngine* engine = getGuiEngine(L); + GUIEngine *engine = getGuiEngine(L); size_t func_length, param_length; - const char* serialized_func_raw = luaL_checklstring(L, 1, &func_length); + const char *serialized_func_raw = luaL_checklstring(L, 1, &func_length); - const char* serialized_param_raw = luaL_checklstring(L, 2, ¶m_length); + const char *serialized_param_raw = luaL_checklstring(L, 2, ¶m_length); sanity_check(serialized_func_raw != NULL); sanity_check(serialized_param_raw != NULL); @@ -1153,8 +1152,8 @@ void ModApiMainMenu::InitializeAsync(lua_State *L, int top) API_FCT(create_dir); API_FCT(delete_dir); API_FCT(copy_dir); - //API_FCT(extract_zip); //TODO remove dependency to GuiEngine + // API_FCT(extract_zip); //TODO remove dependency to GuiEngine API_FCT(may_modify_path); API_FCT(download_file); - //API_FCT(gettext); (gettext lib isn't threadsafe) + // API_FCT(gettext); (gettext lib isn't threadsafe) } diff --git a/src/script/lua_api/l_mainmenu.h b/src/script/lua_api/l_mainmenu.h index 5a16b3bfe..c5875e797 100644 --- a/src/script/lua_api/l_mainmenu.h +++ b/src/script/lua_api/l_mainmenu.h @@ -24,7 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc., class AsyncEngine; /** Implementation of lua api support for mainmenu */ -class ModApiMainMenu: public ModApiBase +class ModApiMainMenu : public ModApiBase { private: @@ -42,7 +42,7 @@ private: * @param name name of variable to read * @return integer value of requested variable */ - static int getIntegerData(lua_State *L, std::string name,bool& valid); + static int getIntegerData(lua_State *L, std::string name, bool &valid); /** * read a bool variable from gamedata table within lua stack @@ -50,7 +50,7 @@ private: * @param name name of variable to read * @return bool value of requested variable */ - static int getBoolData(lua_State *L, std::string name,bool& valid); + static int getBoolData(lua_State *L, std::string name, bool &valid); /** * Checks if a path may be modified. Paths in the temp directory or the user @@ -60,7 +60,7 @@ private: */ static bool mayModifyPath(const std::string &path); - //api calls + // api calls static int l_start(lua_State *L); @@ -80,13 +80,13 @@ private: static int l_gettext(lua_State *L); - //packages + // packages static int l_get_games(lua_State *L); static int l_get_content_info(lua_State *L); - //gui + // gui static int l_show_keys_menu(lua_State *L); @@ -108,7 +108,7 @@ private: static int l_get_screen_info(lua_State *L); - //filesystem + // filesystem static int l_get_mainmenu_path(lua_State *L); @@ -140,7 +140,7 @@ private: static int l_get_video_modes(lua_State *L); - //version compatibility + // version compatibility static int l_get_min_supp_proto(lua_State *L); static int l_get_max_supp_proto(lua_State *L); @@ -148,12 +148,10 @@ private: // other static int l_open_url(lua_State *L); - // async static int l_do_async_callback(lua_State *L); public: - /** * initialize this API module * @param L lua stack to initialize @@ -162,5 +160,4 @@ public: static void Initialize(lua_State *L, int top); static void InitializeAsync(lua_State *L, int top); - }; diff --git a/src/script/lua_api/l_mapgen.cpp b/src/script/lua_api/l_mapgen.cpp index 834938e56..d52191a29 100644 --- a/src/script/lua_api/l_mapgen.cpp +++ b/src/script/lua_api/l_mapgen.cpp @@ -37,81 +37,73 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "settings.h" #include "log.h" -struct EnumString ModApiMapgen::es_BiomeTerrainType[] = -{ - {BIOMETYPE_NORMAL, "normal"}, - {0, NULL}, +struct EnumString ModApiMapgen::es_BiomeTerrainType[] = { + {BIOMETYPE_NORMAL, "normal"}, + {0, NULL}, }; -struct EnumString ModApiMapgen::es_DecorationType[] = -{ - {DECO_SIMPLE, "simple"}, - {DECO_SCHEMATIC, "schematic"}, - {DECO_LSYSTEM, "lsystem"}, - {0, NULL}, +struct EnumString ModApiMapgen::es_DecorationType[] = { + {DECO_SIMPLE, "simple"}, + {DECO_SCHEMATIC, "schematic"}, + {DECO_LSYSTEM, "lsystem"}, + {0, NULL}, }; -struct EnumString ModApiMapgen::es_MapgenObject[] = -{ - {MGOBJ_VMANIP, "voxelmanip"}, - {MGOBJ_HEIGHTMAP, "heightmap"}, - {MGOBJ_BIOMEMAP, "biomemap"}, - {MGOBJ_HEATMAP, "heatmap"}, - {MGOBJ_HUMIDMAP, "humiditymap"}, - {MGOBJ_GENNOTIFY, "gennotify"}, - {0, NULL}, +struct EnumString ModApiMapgen::es_MapgenObject[] = { + {MGOBJ_VMANIP, "voxelmanip"}, + {MGOBJ_HEIGHTMAP, "heightmap"}, + {MGOBJ_BIOMEMAP, "biomemap"}, + {MGOBJ_HEATMAP, "heatmap"}, + {MGOBJ_HUMIDMAP, "humiditymap"}, + {MGOBJ_GENNOTIFY, "gennotify"}, + {0, NULL}, }; -struct EnumString ModApiMapgen::es_OreType[] = -{ - {ORE_SCATTER, "scatter"}, - {ORE_SHEET, "sheet"}, - {ORE_PUFF, "puff"}, - {ORE_BLOB, "blob"}, - {ORE_VEIN, "vein"}, - {ORE_STRATUM, "stratum"}, - {0, NULL}, +struct EnumString ModApiMapgen::es_OreType[] = { + {ORE_SCATTER, "scatter"}, + {ORE_SHEET, "sheet"}, + {ORE_PUFF, "puff"}, + {ORE_BLOB, "blob"}, + {ORE_VEIN, "vein"}, + {ORE_STRATUM, "stratum"}, + {0, NULL}, }; -struct EnumString ModApiMapgen::es_Rotation[] = -{ - {ROTATE_0, "0"}, - {ROTATE_90, "90"}, - {ROTATE_180, "180"}, - {ROTATE_270, "270"}, - {ROTATE_RAND, "random"}, - {0, NULL}, +struct EnumString ModApiMapgen::es_Rotation[] = { + {ROTATE_0, "0"}, + {ROTATE_90, "90"}, + {ROTATE_180, "180"}, + {ROTATE_270, "270"}, + {ROTATE_RAND, "random"}, + {0, NULL}, }; -struct EnumString ModApiMapgen::es_SchematicFormatType[] = -{ - {SCHEM_FMT_HANDLE, "handle"}, - {SCHEM_FMT_MTS, "mts"}, - {SCHEM_FMT_LUA, "lua"}, - {0, NULL}, +struct EnumString ModApiMapgen::es_SchematicFormatType[] = { + {SCHEM_FMT_HANDLE, "handle"}, + {SCHEM_FMT_MTS, "mts"}, + {SCHEM_FMT_LUA, "lua"}, + {0, NULL}, }; ObjDef *get_objdef(lua_State *L, int index, const ObjDefManager *objmgr); -Biome *get_or_load_biome(lua_State *L, int index, - BiomeManager *biomemgr); +Biome *get_or_load_biome(lua_State *L, int index, BiomeManager *biomemgr); Biome *read_biome_def(lua_State *L, int index, const NodeDefManager *ndef); -size_t get_biome_list(lua_State *L, int index, - BiomeManager *biomemgr, std::unordered_set *biome_id_list); +size_t get_biome_list(lua_State *L, int index, BiomeManager *biomemgr, + std::unordered_set *biome_id_list); -Schematic *get_or_load_schematic(lua_State *L, int index, - SchematicManager *schemmgr, StringMap *replace_names); +Schematic *get_or_load_schematic(lua_State *L, int index, SchematicManager *schemmgr, + StringMap *replace_names); Schematic *load_schematic(lua_State *L, int index, const NodeDefManager *ndef, - StringMap *replace_names); -Schematic *load_schematic_from_def(lua_State *L, int index, - const NodeDefManager *ndef, StringMap *replace_names); -bool read_schematic_def(lua_State *L, int index, - Schematic *schem, std::vector *names); + StringMap *replace_names); +Schematic *load_schematic_from_def(lua_State *L, int index, const NodeDefManager *ndef, + StringMap *replace_names); +bool read_schematic_def(lua_State *L, int index, Schematic *schem, + std::vector *names); bool read_deco_simple(lua_State *L, DecoSimple *deco); bool read_deco_schematic(lua_State *L, SchematicManager *schemmgr, DecoSchematic *deco); - /////////////////////////////////////////////////////////////////////////////// ObjDef *get_objdef(lua_State *L, int index, const ObjDefManager *objmgr) @@ -132,8 +124,8 @@ ObjDef *get_objdef(lua_State *L, int index, const ObjDefManager *objmgr) /////////////////////////////////////////////////////////////////////////////// -Schematic *get_or_load_schematic(lua_State *L, int index, - SchematicManager *schemmgr, StringMap *replace_names) +Schematic *get_or_load_schematic(lua_State *L, int index, SchematicManager *schemmgr, + StringMap *replace_names) { if (index < 0) index = lua_gettop(L) + 1 + index; @@ -142,8 +134,7 @@ Schematic *get_or_load_schematic(lua_State *L, int index, if (schem) return schem; - schem = load_schematic(L, index, schemmgr->getNodeDef(), - replace_names); + schem = load_schematic(L, index, schemmgr->getNodeDef(), replace_names); if (!schem) return NULL; @@ -155,9 +146,8 @@ Schematic *get_or_load_schematic(lua_State *L, int index, return schem; } - Schematic *load_schematic(lua_State *L, int index, const NodeDefManager *ndef, - StringMap *replace_names) + StringMap *replace_names) { if (index < 0) index = lua_gettop(L) + 1 + index; @@ -165,8 +155,7 @@ Schematic *load_schematic(lua_State *L, int index, const NodeDefManager *ndef, Schematic *schem = NULL; if (lua_istable(L, index)) { - schem = load_schematic_from_def(L, index, ndef, - replace_names); + schem = load_schematic_from_def(L, index, ndef, replace_names); if (!schem) { delete schem; return NULL; @@ -178,10 +167,10 @@ Schematic *load_schematic(lua_State *L, int index, const NodeDefManager *ndef, std::string filepath = lua_tostring(L, index); if (!fs::IsPathAbsolute(filepath)) - filepath = ModApiBase::getCurrentModPath(L) + DIR_DELIM + filepath; + filepath = ModApiBase::getCurrentModPath(L) + DIR_DELIM + + filepath; - if (!schem->loadSchematicFromFile(filepath, ndef, - replace_names)) { + if (!schem->loadSchematicFromFile(filepath, ndef, replace_names)) { delete schem; return NULL; } @@ -190,9 +179,8 @@ Schematic *load_schematic(lua_State *L, int index, const NodeDefManager *ndef, return schem; } - -Schematic *load_schematic_from_def(lua_State *L, int index, - const NodeDefManager *ndef, StringMap *replace_names) +Schematic *load_schematic_from_def(lua_State *L, int index, const NodeDefManager *ndef, + StringMap *replace_names) { Schematic *schem = SchematicManager::create(SCHEMATIC_NORMAL); @@ -207,7 +195,8 @@ Schematic *load_schematic_from_def(lua_State *L, int index, if (replace_names) { for (size_t i = 0; i != num_nodes; i++) { - StringMap::iterator it = replace_names->find(schem->m_nodenames[i]); + StringMap::iterator it = + replace_names->find(schem->m_nodenames[i]); if (it != replace_names->end()) schem->m_nodenames[i] = it->second; } @@ -219,9 +208,8 @@ Schematic *load_schematic_from_def(lua_State *L, int index, return schem; } - -bool read_schematic_def(lua_State *L, int index, - Schematic *schem, std::vector *names) +bool read_schematic_def(lua_State *L, int index, Schematic *schem, + std::vector *names) { if (!lua_istable(L, index)) return false; @@ -251,19 +239,21 @@ bool read_schematic_def(lua_State *L, int index, //// Read name std::string name; if (!getstringfield(L, -1, "name", name)) - throw LuaError("Schematic data definition with missing name field"); + throw LuaError("Schematic data definition with missing name " + "field"); //// Read param1/prob u8 param1; if (!getintfield(L, -1, "param1", param1) && - !getintfield(L, -1, "prob", param1)) + !getintfield(L, -1, "prob", param1)) param1 = MTSCHEM_PROB_ALWAYS_OLD; //// Read param2 u8 param2 = getintfield_default(L, -1, "param2", 0); //// Find or add new nodename-to-ID mapping - std::unordered_map::iterator it = name_id_map.find(name); + std::unordered_map::iterator it = + name_id_map.find(name); content_t name_index; if (it != name_id_map.end()) { name_index = it->second; @@ -284,14 +274,14 @@ bool read_schematic_def(lua_State *L, int index, if (i != numnodes) { errorstream << "read_schematic_def: incorrect number of " - "nodes provided in raw schematic data (got " << i << - ", expected " << numnodes << ")." << std::endl; + "nodes provided in raw schematic data (got " + << i << ", expected " << numnodes << ")." << std::endl; return false; } //// Get Y-slice probability values (if present) schem->slice_probs = new u8[size.Y]; - for (i = 0; i != (u32) size.Y; i++) + for (i = 0; i != (u32)size.Y; i++) schem->slice_probs[i] = MTSCHEM_PROB_ALWAYS; lua_getfield(L, index, "yslice_prob"); @@ -299,7 +289,8 @@ bool read_schematic_def(lua_State *L, int index, for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { u16 ypos; if (!getintfield(L, -1, "ypos", ypos) || (ypos >= size.Y) || - !getintfield(L, -1, "prob", schem->slice_probs[ypos])) + !getintfield(L, -1, "prob", + schem->slice_probs[ypos])) continue; schem->slice_probs[ypos] >>= 1; @@ -309,7 +300,6 @@ bool read_schematic_def(lua_State *L, int index, return true; } - void read_schematic_replacements(lua_State *L, int index, StringMap *replace_names) { if (index < 0) @@ -323,21 +313,25 @@ void read_schematic_replacements(lua_State *L, int index, StringMap *replace_nam if (lua_istable(L, -1)) { // Old {{"x", "y"}, ...} format lua_rawgeti(L, -1, 1); if (!lua_isstring(L, -1)) - throw LuaError("schematics: replace_from field is not a string"); + throw LuaError("schematics: replace_from field is not a " + "string"); replace_from = lua_tostring(L, -1); lua_pop(L, 1); lua_rawgeti(L, -1, 2); if (!lua_isstring(L, -1)) - throw LuaError("schematics: replace_to field is not a string"); + throw LuaError("schematics: replace_to field is not a " + "string"); replace_to = lua_tostring(L, -1); lua_pop(L, 1); } else { // New {x = "y", ...} format if (!lua_isstring(L, -2)) - throw LuaError("schematics: replace_from field is not a string"); + throw LuaError("schematics: replace_from field is not a " + "string"); replace_from = lua_tostring(L, -2); if (!lua_isstring(L, -1)) - throw LuaError("schematics: replace_to field is not a string"); + throw LuaError("schematics: replace_to field is not a " + "string"); replace_to = lua_tostring(L, -1); } @@ -369,42 +363,41 @@ Biome *get_or_load_biome(lua_State *L, int index, BiomeManager *biomemgr) return biome; } - Biome *read_biome_def(lua_State *L, int index, const NodeDefManager *ndef) { if (!lua_istable(L, index)) return NULL; BiomeType biometype = (BiomeType)getenumfield(L, index, "type", - ModApiMapgen::es_BiomeTerrainType, BIOMETYPE_NORMAL); + ModApiMapgen::es_BiomeTerrainType, BIOMETYPE_NORMAL); Biome *b = BiomeManager::create(biometype); - b->name = getstringfield_default(L, index, "name", ""); - b->depth_top = getintfield_default(L, index, "depth_top", 0); - b->depth_filler = getintfield_default(L, index, "depth_filler", -31000); - b->depth_water_top = getintfield_default(L, index, "depth_water_top", 0); - b->depth_riverbed = getintfield_default(L, index, "depth_riverbed", 0); - b->heat_point = getfloatfield_default(L, index, "heat_point", 0.f); - b->humidity_point = getfloatfield_default(L, index, "humidity_point", 0.f); - b->vertical_blend = getintfield_default(L, index, "vertical_blend", 0); - b->flags = 0; // reserved + b->name = getstringfield_default(L, index, "name", ""); + b->depth_top = getintfield_default(L, index, "depth_top", 0); + b->depth_filler = getintfield_default(L, index, "depth_filler", -31000); + b->depth_water_top = getintfield_default(L, index, "depth_water_top", 0); + b->depth_riverbed = getintfield_default(L, index, "depth_riverbed", 0); + b->heat_point = getfloatfield_default(L, index, "heat_point", 0.f); + b->humidity_point = getfloatfield_default(L, index, "humidity_point", 0.f); + b->vertical_blend = getintfield_default(L, index, "vertical_blend", 0); + b->flags = 0; // reserved b->min_pos = getv3s16field_default( - L, index, "min_pos", v3s16(-31000, -31000, -31000)); + L, index, "min_pos", v3s16(-31000, -31000, -31000)); getintfield(L, index, "y_min", b->min_pos.Y); b->max_pos = getv3s16field_default( - L, index, "max_pos", v3s16(31000, 31000, 31000)); + L, index, "max_pos", v3s16(31000, 31000, 31000)); getintfield(L, index, "y_max", b->max_pos.Y); std::vector &nn = b->m_nodenames; - nn.push_back(getstringfield_default(L, index, "node_top", "")); - nn.push_back(getstringfield_default(L, index, "node_filler", "")); - nn.push_back(getstringfield_default(L, index, "node_stone", "")); - nn.push_back(getstringfield_default(L, index, "node_water_top", "")); - nn.push_back(getstringfield_default(L, index, "node_water", "")); - nn.push_back(getstringfield_default(L, index, "node_river_water", "")); - nn.push_back(getstringfield_default(L, index, "node_riverbed", "")); - nn.push_back(getstringfield_default(L, index, "node_dust", "")); + nn.push_back(getstringfield_default(L, index, "node_top", "")); + nn.push_back(getstringfield_default(L, index, "node_filler", "")); + nn.push_back(getstringfield_default(L, index, "node_stone", "")); + nn.push_back(getstringfield_default(L, index, "node_water_top", "")); + nn.push_back(getstringfield_default(L, index, "node_water", "")); + nn.push_back(getstringfield_default(L, index, "node_river_water", "")); + nn.push_back(getstringfield_default(L, index, "node_riverbed", "")); + nn.push_back(getstringfield_default(L, index, "node_dust", "")); size_t nnames = getstringlistfield(L, index, "node_cave_liquid", &nn); // If no cave liquids defined, set list to "ignore" to trigger old hardcoded @@ -415,17 +408,16 @@ Biome *read_biome_def(lua_State *L, int index, const NodeDefManager *ndef) } b->m_nnlistsizes.push_back(nnames); - nn.push_back(getstringfield_default(L, index, "node_dungeon", "")); - nn.push_back(getstringfield_default(L, index, "node_dungeon_alt", "")); + nn.push_back(getstringfield_default(L, index, "node_dungeon", "")); + nn.push_back(getstringfield_default(L, index, "node_dungeon_alt", "")); nn.push_back(getstringfield_default(L, index, "node_dungeon_stair", "")); ndef->pendNodeResolve(b); return b; } - -size_t get_biome_list(lua_State *L, int index, - BiomeManager *biomemgr, std::unordered_set *biome_id_list) +size_t get_biome_list(lua_State *L, int index, BiomeManager *biomemgr, + std::unordered_set *biome_id_list) { if (index < 0) index = lua_gettop(L) + 1 + index; @@ -444,8 +436,9 @@ size_t get_biome_list(lua_State *L, int index, Biome *biome = get_or_load_biome(L, index, biomemgr); if (!biome) { infostream << "get_biome_list: failed to get biome '" - << (lua_isstring(L, index) ? lua_tostring(L, index) : "") - << "'." << std::endl; + << (lua_isstring(L, index) ? lua_tostring(L, index) + : "") + << "'." << std::endl; return 1; } @@ -463,8 +456,8 @@ size_t get_biome_list(lua_State *L, int index, if (!biome) { fail_count++; infostream << "get_biome_list: failed to get biome '" - << (lua_isstring(L, -1) ? lua_tostring(L, -1) : "") - << "'" << std::endl; + << (lua_isstring(L, -1) ? lua_tostring(L, -1) : "") + << "'" << std::endl; continue; } @@ -499,7 +492,6 @@ int ModApiMapgen::l_get_biome_id(lua_State *L) return 1; } - // get_biome_name(biome_id) // returns the biome name string int ModApiMapgen::l_get_biome_name(lua_State *L) @@ -518,7 +510,6 @@ int ModApiMapgen::l_get_biome_name(lua_State *L) return 1; } - // get_heat(pos) // returns the heat at the position int ModApiMapgen::l_get_heat(lua_State *L) @@ -531,12 +522,11 @@ int ModApiMapgen::l_get_heat(lua_State *L) NoiseParams np_heat_blend; MapSettingsManager *settingsmgr = - getServer(L)->getEmergeManager()->map_settings_mgr; + getServer(L)->getEmergeManager()->map_settings_mgr; - if (!settingsmgr->getMapSettingNoiseParams("mg_biome_np_heat", - &np_heat) || - !settingsmgr->getMapSettingNoiseParams("mg_biome_np_heat_blend", - &np_heat_blend)) + if (!settingsmgr->getMapSettingNoiseParams("mg_biome_np_heat", &np_heat) || + !settingsmgr->getMapSettingNoiseParams( + "mg_biome_np_heat_blend", &np_heat_blend)) return 0; std::string value; @@ -557,7 +547,6 @@ int ModApiMapgen::l_get_heat(lua_State *L) return 1; } - // get_humidity(pos) // returns the humidity at the position int ModApiMapgen::l_get_humidity(lua_State *L) @@ -570,12 +559,12 @@ int ModApiMapgen::l_get_humidity(lua_State *L) NoiseParams np_humidity_blend; MapSettingsManager *settingsmgr = - getServer(L)->getEmergeManager()->map_settings_mgr; + getServer(L)->getEmergeManager()->map_settings_mgr; - if (!settingsmgr->getMapSettingNoiseParams("mg_biome_np_humidity", - &np_humidity) || - !settingsmgr->getMapSettingNoiseParams("mg_biome_np_humidity_blend", - &np_humidity_blend)) + if (!settingsmgr->getMapSettingNoiseParams( + "mg_biome_np_humidity", &np_humidity) || + !settingsmgr->getMapSettingNoiseParams( + "mg_biome_np_humidity_blend", &np_humidity_blend)) return 0; std::string value; @@ -589,15 +578,14 @@ int ModApiMapgen::l_get_humidity(lua_State *L) if (!bmgr) return 0; - float humidity = bmgr->getHumidityAtPosOriginal(pos, np_humidity, - np_humidity_blend, seed); + float humidity = bmgr->getHumidityAtPosOriginal( + pos, np_humidity, np_humidity_blend, seed); lua_pushnumber(L, humidity); return 1; } - // get_biome_data(pos) // returns a table containing the biome id, heat and humidity at the position int ModApiMapgen::l_get_biome_data(lua_State *L) @@ -612,16 +600,15 @@ int ModApiMapgen::l_get_biome_data(lua_State *L) NoiseParams np_humidity_blend; MapSettingsManager *settingsmgr = - getServer(L)->getEmergeManager()->map_settings_mgr; + getServer(L)->getEmergeManager()->map_settings_mgr; - if (!settingsmgr->getMapSettingNoiseParams("mg_biome_np_heat", - &np_heat) || - !settingsmgr->getMapSettingNoiseParams("mg_biome_np_heat_blend", - &np_heat_blend) || - !settingsmgr->getMapSettingNoiseParams("mg_biome_np_humidity", - &np_humidity) || - !settingsmgr->getMapSettingNoiseParams("mg_biome_np_humidity_blend", - &np_humidity_blend)) + if (!settingsmgr->getMapSettingNoiseParams("mg_biome_np_heat", &np_heat) || + !settingsmgr->getMapSettingNoiseParams( + "mg_biome_np_heat_blend", &np_heat_blend) || + !settingsmgr->getMapSettingNoiseParams( + "mg_biome_np_humidity", &np_humidity) || + !settingsmgr->getMapSettingNoiseParams( + "mg_biome_np_humidity_blend", &np_humidity_blend)) return 0; std::string value; @@ -639,8 +626,8 @@ int ModApiMapgen::l_get_biome_data(lua_State *L) if (!heat) return 0; - float humidity = bmgr->getHumidityAtPosOriginal(pos, np_humidity, - np_humidity_blend, seed); + float humidity = bmgr->getHumidityAtPosOriginal( + pos, np_humidity, np_humidity_blend, seed); if (!humidity) return 0; @@ -662,7 +649,6 @@ int ModApiMapgen::l_get_biome_data(lua_State *L) return 1; } - // get_mapgen_object(objectname) // returns the requested object used during map generation int ModApiMapgen::l_get_mapgen_object(lua_State *L) @@ -756,7 +742,7 @@ int ModApiMapgen::l_get_mapgen_object(lua_State *L) return 1; } case MGOBJ_GENNOTIFY: { - std::map >event_map; + std::map> event_map; mg->gennotify.getEvents(event_map); @@ -779,7 +765,6 @@ int ModApiMapgen::l_get_mapgen_object(lua_State *L) return 0; } - // get_spawn_level(x = num, z = num) int ModApiMapgen::l_get_spawn_level(lua_State *L) { @@ -800,18 +785,17 @@ int ModApiMapgen::l_get_spawn_level(lua_State *L) return 1; } - int ModApiMapgen::l_get_mapgen_params(lua_State *L) { NO_MAP_LOCK_REQUIRED; log_deprecated(L, "get_mapgen_params is deprecated; " - "use get_mapgen_setting instead"); + "use get_mapgen_setting instead"); std::string value; MapSettingsManager *settingsmgr = - getServer(L)->getEmergeManager()->map_settings_mgr; + getServer(L)->getEmergeManager()->map_settings_mgr; lua_newtable(L); @@ -841,7 +825,6 @@ int ModApiMapgen::l_get_mapgen_params(lua_State *L) return 1; } - // set_mapgen_params(params) // set mapgen parameters int ModApiMapgen::l_set_mapgen_params(lua_State *L) @@ -849,17 +832,18 @@ int ModApiMapgen::l_set_mapgen_params(lua_State *L) NO_MAP_LOCK_REQUIRED; log_deprecated(L, "set_mapgen_params is deprecated; " - "use set_mapgen_setting instead"); + "use set_mapgen_setting instead"); if (!lua_istable(L, 1)) return 0; MapSettingsManager *settingsmgr = - getServer(L)->getEmergeManager()->map_settings_mgr; + getServer(L)->getEmergeManager()->map_settings_mgr; lua_getfield(L, 1, "mgname"); if (lua_isstring(L, -1)) - settingsmgr->setMapSetting("mg_name", readParam(L, -1), true); + settingsmgr->setMapSetting( + "mg_name", readParam(L, -1), true); lua_getfield(L, 1, "seed"); if (lua_isnumber(L, -1)) @@ -867,18 +851,21 @@ int ModApiMapgen::l_set_mapgen_params(lua_State *L) lua_getfield(L, 1, "water_level"); if (lua_isnumber(L, -1)) - settingsmgr->setMapSetting("water_level", readParam(L, -1), true); + settingsmgr->setMapSetting( + "water_level", readParam(L, -1), true); lua_getfield(L, 1, "chunksize"); if (lua_isnumber(L, -1)) - settingsmgr->setMapSetting("chunksize", readParam(L, -1), true); + settingsmgr->setMapSetting( + "chunksize", readParam(L, -1), true); warn_if_field_exists(L, 1, "flagmask", - "Obsolete: flags field now includes unset flags."); + "Obsolete: flags field now includes unset flags."); lua_getfield(L, 1, "flags"); if (lua_isstring(L, -1)) - settingsmgr->setMapSetting("mg_flags", readParam(L, -1), true); + settingsmgr->setMapSetting( + "mg_flags", readParam(L, -1), true); return 0; } @@ -890,7 +877,7 @@ int ModApiMapgen::l_get_mapgen_setting(lua_State *L) std::string value; MapSettingsManager *settingsmgr = - getServer(L)->getEmergeManager()->map_settings_mgr; + getServer(L)->getEmergeManager()->map_settings_mgr; const char *name = luaL_checkstring(L, 1); if (!settingsmgr->getMapSetting(name, &value)) @@ -907,7 +894,7 @@ int ModApiMapgen::l_get_mapgen_setting_noiseparams(lua_State *L) NoiseParams np; MapSettingsManager *settingsmgr = - getServer(L)->getEmergeManager()->map_settings_mgr; + getServer(L)->getEmergeManager()->map_settings_mgr; const char *name = luaL_checkstring(L, 1); if (!settingsmgr->getMapSettingNoiseParams(name, &np)) @@ -924,21 +911,20 @@ int ModApiMapgen::l_set_mapgen_setting(lua_State *L) NO_MAP_LOCK_REQUIRED; MapSettingsManager *settingsmgr = - getServer(L)->getEmergeManager()->map_settings_mgr; + getServer(L)->getEmergeManager()->map_settings_mgr; - const char *name = luaL_checkstring(L, 1); - const char *value = luaL_checkstring(L, 2); + const char *name = luaL_checkstring(L, 1); + const char *value = luaL_checkstring(L, 2); bool override_meta = readParam(L, 3, false); if (!settingsmgr->setMapSetting(name, value, override_meta)) { - errorstream << "set_mapgen_setting: cannot set '" - << name << "' after initialization" << std::endl; + errorstream << "set_mapgen_setting: cannot set '" << name + << "' after initialization" << std::endl; } return 0; } - // set_mapgen_setting_noiseparams(name, noiseparams, set_default) // set mapgen config values for noise parameters int ModApiMapgen::l_set_mapgen_setting_noiseparams(lua_State *L) @@ -946,28 +932,27 @@ int ModApiMapgen::l_set_mapgen_setting_noiseparams(lua_State *L) NO_MAP_LOCK_REQUIRED; MapSettingsManager *settingsmgr = - getServer(L)->getEmergeManager()->map_settings_mgr; + getServer(L)->getEmergeManager()->map_settings_mgr; const char *name = luaL_checkstring(L, 1); NoiseParams np; if (!read_noiseparams(L, 2, &np)) { errorstream << "set_mapgen_setting_noiseparams: cannot set '" << name - << "'; invalid noiseparams table" << std::endl; + << "'; invalid noiseparams table" << std::endl; return 0; } bool override_meta = readParam(L, 3, false); if (!settingsmgr->setMapSettingNoiseParams(name, &np, override_meta)) { - errorstream << "set_mapgen_setting_noiseparams: cannot set '" - << name << "' after initialization" << std::endl; + errorstream << "set_mapgen_setting_noiseparams: cannot set '" << name + << "' after initialization" << std::endl; } return 0; } - // set_noiseparams(name, noiseparams, set_default) // set global config values for noise parameters int ModApiMapgen::l_set_noiseparams(lua_State *L) @@ -979,7 +964,7 @@ int ModApiMapgen::l_set_noiseparams(lua_State *L) NoiseParams np; if (!read_noiseparams(L, 2, &np)) { errorstream << "set_noiseparams: cannot set '" << name - << "'; invalid noiseparams table" << std::endl; + << "'; invalid noiseparams table" << std::endl; return 0; } @@ -990,7 +975,6 @@ int ModApiMapgen::l_set_noiseparams(lua_State *L) return 0; } - // get_noiseparams(name) int ModApiMapgen::l_get_noiseparams(lua_State *L) { @@ -1006,7 +990,6 @@ int ModApiMapgen::l_get_noiseparams(lua_State *L) return 1; } - // set_gen_notify(flags, {deco_id_table}) int ModApiMapgen::l_set_gen_notify(lua_State *L) { @@ -1024,7 +1007,8 @@ int ModApiMapgen::l_set_gen_notify(lua_State *L) lua_pushnil(L); while (lua_next(L, 2)) { if (lua_isnumber(L, -1)) - emerge->gen_notify_on_deco_ids.insert((u32)lua_tonumber(L, -1)); + emerge->gen_notify_on_deco_ids.insert( + (u32)lua_tonumber(L, -1)); lua_pop(L, 1); } } @@ -1032,7 +1016,6 @@ int ModApiMapgen::l_set_gen_notify(lua_State *L) return 0; } - // get_gen_notify() int ModApiMapgen::l_get_gen_notify(lua_State *L) { @@ -1040,7 +1023,7 @@ int ModApiMapgen::l_get_gen_notify(lua_State *L) EmergeManager *emerge = getServer(L)->getEmergeManager(); push_flags_string(L, flagdesc_gennotify, emerge->gen_notify_on, - emerge->gen_notify_on); + emerge->gen_notify_on); lua_newtable(L); int i = 1; @@ -1051,7 +1034,6 @@ int ModApiMapgen::l_get_gen_notify(lua_State *L) return 2; } - // get_decoration_id(decoration_name) // returns the decoration ID as used in gennotify int ModApiMapgen::l_get_decoration_id(lua_State *L) @@ -1063,7 +1045,7 @@ int ModApiMapgen::l_get_decoration_id(lua_State *L) return 0; const DecorationManager *dmgr = - getServer(L)->getEmergeManager()->getDecorationManager(); + getServer(L)->getEmergeManager()->getDecorationManager(); if (!dmgr) return 0; @@ -1078,7 +1060,6 @@ int ModApiMapgen::l_get_decoration_id(lua_State *L) return 1; } - // register_biome({lots of stuff}) int ModApiMapgen::l_register_biome(lua_State *L) { @@ -1104,7 +1085,6 @@ int ModApiMapgen::l_register_biome(lua_State *L) return 1; } - // register_decoration({lots of stuff}) int ModApiMapgen::l_register_decoration(lua_State *L) { @@ -1113,32 +1093,33 @@ int ModApiMapgen::l_register_decoration(lua_State *L) int index = 1; luaL_checktype(L, index, LUA_TTABLE); - const NodeDefManager *ndef = getServer(L)->getNodeDefManager(); + const NodeDefManager *ndef = getServer(L)->getNodeDefManager(); EmergeManager *emerge = getServer(L)->getEmergeManager(); DecorationManager *decomgr = emerge->getWritableDecorationManager(); - BiomeManager *biomemgr = emerge->getWritableBiomeManager(); + BiomeManager *biomemgr = emerge->getWritableBiomeManager(); SchematicManager *schemmgr = emerge->getWritableSchematicManager(); - enum DecorationType decotype = (DecorationType)getenumfield(L, index, - "deco_type", es_DecorationType, -1); + enum DecorationType decotype = (DecorationType)getenumfield( + L, index, "deco_type", es_DecorationType, -1); Decoration *deco = decomgr->create(decotype); if (!deco) { errorstream << "register_decoration: decoration placement type " - << decotype << " not implemented" << std::endl; + << decotype << " not implemented" << std::endl; return 0; } - deco->name = getstringfield_default(L, index, "name", ""); - deco->fill_ratio = getfloatfield_default(L, index, "fill_ratio", 0.02); - deco->y_min = getintfield_default(L, index, "y_min", -31000); - deco->y_max = getintfield_default(L, index, "y_max", 31000); - deco->nspawnby = getintfield_default(L, index, "num_spawn_by", -1); + deco->name = getstringfield_default(L, index, "name", ""); + deco->fill_ratio = getfloatfield_default(L, index, "fill_ratio", 0.02); + deco->y_min = getintfield_default(L, index, "y_min", -31000); + deco->y_max = getintfield_default(L, index, "y_max", 31000); + deco->nspawnby = getintfield_default(L, index, "num_spawn_by", -1); deco->place_offset_y = getintfield_default(L, index, "place_offset_y", 0); - deco->sidelen = getintfield_default(L, index, "sidelen", 8); + deco->sidelen = getintfield_default(L, index, "sidelen", 8); if (deco->sidelen <= 0) { errorstream << "register_decoration: sidelen must be " - "greater than 0" << std::endl; + "greater than 0" + << std::endl; delete deco; return 0; } @@ -1159,7 +1140,8 @@ int ModApiMapgen::l_register_decoration(lua_State *L) //// Get biomes associated with this decoration (if any) lua_getfield(L, index, "biomes"); if (get_biome_list(L, -1, biomemgr, &deco->biomes)) - infostream << "register_decoration: couldn't get all biomes " << std::endl; + infostream << "register_decoration: couldn't get all biomes " + << std::endl; lua_pop(L, 1); //// Get node name(s) to 'spawn by' @@ -1167,7 +1149,8 @@ int ModApiMapgen::l_register_decoration(lua_State *L) deco->m_nnlistsizes.push_back(nnames); if (nnames == 0 && deco->nspawnby != -1) { errorstream << "register_decoration: no spawn_by nodes defined," - " but num_spawn_by specified" << std::endl; + " but num_spawn_by specified" + << std::endl; } //// Handle decoration type-specific parameters @@ -1200,19 +1183,19 @@ int ModApiMapgen::l_register_decoration(lua_State *L) return 1; } - bool read_deco_simple(lua_State *L, DecoSimple *deco) { int index = 1; int param2; int param2_max; - deco->deco_height = getintfield_default(L, index, "height", 1); + deco->deco_height = getintfield_default(L, index, "height", 1); deco->deco_height_max = getintfield_default(L, index, "height_max", 0); if (deco->deco_height <= 0) { errorstream << "register_decoration: simple decoration height" - " must be greater than 0" << std::endl; + " must be greater than 0" + << std::endl; return false; } @@ -1221,7 +1204,8 @@ bool read_deco_simple(lua_State *L, DecoSimple *deco) if (nnames == 0) { errorstream << "register_decoration: no decoration nodes " - "defined" << std::endl; + "defined" + << std::endl; return false; } @@ -1229,8 +1213,9 @@ bool read_deco_simple(lua_State *L, DecoSimple *deco) param2_max = getintfield_default(L, index, "param2_max", 0); if (param2 < 0 || param2 > 255 || param2_max < 0 || param2_max > 255) { - errorstream << "register_decoration: param2 or param2_max out of bounds (0-255)" - << std::endl; + errorstream << "register_decoration: param2 or param2_max out of bounds " + "(0-255)" + << std::endl; return false; } @@ -1240,13 +1225,12 @@ bool read_deco_simple(lua_State *L, DecoSimple *deco) return true; } - bool read_deco_schematic(lua_State *L, SchematicManager *schemmgr, DecoSchematic *deco) { int index = 1; - deco->rotation = (Rotation)getenumfield(L, index, "rotation", - ModApiMapgen::es_Rotation, ROTATE_0); + deco->rotation = (Rotation)getenumfield( + L, index, "rotation", ModApiMapgen::es_Rotation, ROTATE_0); StringMap replace_names; lua_getfield(L, index, "replacements"); @@ -1262,7 +1246,6 @@ bool read_deco_schematic(lua_State *L, SchematicManager *schemmgr, DecoSchematic return schem != NULL; } - // register_ore({lots of stuff}) int ModApiMapgen::l_register_ore(lua_State *L) { @@ -1273,28 +1256,29 @@ int ModApiMapgen::l_register_ore(lua_State *L) const NodeDefManager *ndef = getServer(L)->getNodeDefManager(); EmergeManager *emerge = getServer(L)->getEmergeManager(); - BiomeManager *bmgr = emerge->getWritableBiomeManager(); - OreManager *oremgr = emerge->getWritableOreManager(); + BiomeManager *bmgr = emerge->getWritableBiomeManager(); + OreManager *oremgr = emerge->getWritableOreManager(); - enum OreType oretype = (OreType)getenumfield(L, index, - "ore_type", es_OreType, ORE_SCATTER); + enum OreType oretype = (OreType)getenumfield( + L, index, "ore_type", es_OreType, ORE_SCATTER); Ore *ore = oremgr->create(oretype); if (!ore) { - errorstream << "register_ore: ore_type " << oretype << " not implemented\n"; + errorstream << "register_ore: ore_type " << oretype + << " not implemented\n"; return 0; } - ore->name = getstringfield_default(L, index, "name", ""); - ore->ore_param2 = (u8)getintfield_default(L, index, "ore_param2", 0); + ore->name = getstringfield_default(L, index, "name", ""); + ore->ore_param2 = (u8)getintfield_default(L, index, "ore_param2", 0); ore->clust_scarcity = getintfield_default(L, index, "clust_scarcity", 1); ore->clust_num_ores = getintfield_default(L, index, "clust_num_ores", 1); - ore->clust_size = getintfield_default(L, index, "clust_size", 0); - ore->noise = NULL; - ore->flags = 0; + ore->clust_size = getintfield_default(L, index, "clust_size", 0); + ore->noise = NULL; + ore->flags = 0; //// Get noise_threshold warn_if_field_exists(L, index, "noise_threshhold", - "Deprecated: new name is \"noise_threshold\"."); + "Deprecated: new name is \"noise_threshold\"."); float nthresh; if (!getfloatfield(L, index, "noise_threshold", nthresh) && @@ -1303,24 +1287,25 @@ int ModApiMapgen::l_register_ore(lua_State *L) ore->nthresh = nthresh; //// Get y_min/y_max - warn_if_field_exists(L, index, "height_min", - "Deprecated: new name is \"y_min\"."); - warn_if_field_exists(L, index, "height_max", - "Deprecated: new name is \"y_max\"."); + warn_if_field_exists( + L, index, "height_min", "Deprecated: new name is \"y_min\"."); + warn_if_field_exists( + L, index, "height_max", "Deprecated: new name is \"y_max\"."); int ymin, ymax; if (!getintfield(L, index, "y_min", ymin) && - !getintfield(L, index, "height_min", ymin)) + !getintfield(L, index, "height_min", ymin)) ymin = -31000; if (!getintfield(L, index, "y_max", ymax) && - !getintfield(L, index, "height_max", ymax)) + !getintfield(L, index, "height_max", ymax)) ymax = 31000; ore->y_min = ymin; ore->y_max = ymax; if (ore->clust_scarcity <= 0 || ore->clust_num_ores <= 0) { errorstream << "register_ore: clust_scarcity and clust_num_ores" - "must be greater than 0" << std::endl; + "must be greater than 0" + << std::endl; delete ore; return 0; } @@ -1340,7 +1325,8 @@ int ModApiMapgen::l_register_ore(lua_State *L) ore->flags |= OREFLAG_USE_NOISE; } else if (ore->NEEDS_NOISE) { errorstream << "register_ore: specified ore type requires valid " - "'noise_params' parameter" << std::endl; + "'noise_params' parameter" + << std::endl; delete ore; return 0; } @@ -1348,54 +1334,54 @@ int ModApiMapgen::l_register_ore(lua_State *L) //// Get type-specific parameters switch (oretype) { - case ORE_SHEET: { - OreSheet *oresheet = (OreSheet *)ore; + case ORE_SHEET: { + OreSheet *oresheet = (OreSheet *)ore; - oresheet->column_height_min = getintfield_default(L, index, - "column_height_min", 1); - oresheet->column_height_max = getintfield_default(L, index, - "column_height_max", ore->clust_size); - oresheet->column_midpoint_factor = getfloatfield_default(L, index, - "column_midpoint_factor", 0.5f); + oresheet->column_height_min = + getintfield_default(L, index, "column_height_min", 1); + oresheet->column_height_max = getintfield_default( + L, index, "column_height_max", ore->clust_size); + oresheet->column_midpoint_factor = getfloatfield_default( + L, index, "column_midpoint_factor", 0.5f); - break; - } - case ORE_PUFF: { - OrePuff *orepuff = (OrePuff *)ore; + break; + } + case ORE_PUFF: { + OrePuff *orepuff = (OrePuff *)ore; - lua_getfield(L, index, "np_puff_top"); - read_noiseparams(L, -1, &orepuff->np_puff_top); - lua_pop(L, 1); + lua_getfield(L, index, "np_puff_top"); + read_noiseparams(L, -1, &orepuff->np_puff_top); + lua_pop(L, 1); - lua_getfield(L, index, "np_puff_bottom"); - read_noiseparams(L, -1, &orepuff->np_puff_bottom); - lua_pop(L, 1); + lua_getfield(L, index, "np_puff_bottom"); + read_noiseparams(L, -1, &orepuff->np_puff_bottom); + lua_pop(L, 1); - break; - } - case ORE_VEIN: { - OreVein *orevein = (OreVein *)ore; + break; + } + case ORE_VEIN: { + OreVein *orevein = (OreVein *)ore; - orevein->random_factor = getfloatfield_default(L, index, - "random_factor", 1.f); + orevein->random_factor = + getfloatfield_default(L, index, "random_factor", 1.f); - break; - } - case ORE_STRATUM: { - OreStratum *orestratum = (OreStratum *)ore; + break; + } + case ORE_STRATUM: { + OreStratum *orestratum = (OreStratum *)ore; - lua_getfield(L, index, "np_stratum_thickness"); - if (read_noiseparams(L, -1, &orestratum->np_stratum_thickness)) - ore->flags |= OREFLAG_USE_NOISE2; - lua_pop(L, 1); + lua_getfield(L, index, "np_stratum_thickness"); + if (read_noiseparams(L, -1, &orestratum->np_stratum_thickness)) + ore->flags |= OREFLAG_USE_NOISE2; + lua_pop(L, 1); - orestratum->stratum_thickness = getintfield_default(L, index, - "stratum_thickness", 8); + orestratum->stratum_thickness = + getintfield_default(L, index, "stratum_thickness", 8); - break; - } - default: - break; + break; + } + default: + break; } ObjDefHandle handle = oremgr->add(ore); @@ -1415,21 +1401,19 @@ int ModApiMapgen::l_register_ore(lua_State *L) return 1; } - // register_schematic({schematic}, replacements={}) int ModApiMapgen::l_register_schematic(lua_State *L) { NO_MAP_LOCK_REQUIRED; SchematicManager *schemmgr = - getServer(L)->getEmergeManager()->getWritableSchematicManager(); + getServer(L)->getEmergeManager()->getWritableSchematicManager(); StringMap replace_names; if (lua_istable(L, 2)) read_schematic_replacements(L, 2, &replace_names); - Schematic *schem = load_schematic(L, 1, schemmgr->getNodeDef(), - &replace_names); + Schematic *schem = load_schematic(L, 1, schemmgr->getNodeDef(), &replace_names); if (!schem) return 0; @@ -1443,55 +1427,48 @@ int ModApiMapgen::l_register_schematic(lua_State *L) return 1; } - // clear_registered_biomes() int ModApiMapgen::l_clear_registered_biomes(lua_State *L) { NO_MAP_LOCK_REQUIRED; - BiomeManager *bmgr = - getServer(L)->getEmergeManager()->getWritableBiomeManager(); + BiomeManager *bmgr = getServer(L)->getEmergeManager()->getWritableBiomeManager(); bmgr->clear(); return 0; } - // clear_registered_decorations() int ModApiMapgen::l_clear_registered_decorations(lua_State *L) { NO_MAP_LOCK_REQUIRED; DecorationManager *dmgr = - getServer(L)->getEmergeManager()->getWritableDecorationManager(); + getServer(L)->getEmergeManager()->getWritableDecorationManager(); dmgr->clear(); return 0; } - // clear_registered_ores() int ModApiMapgen::l_clear_registered_ores(lua_State *L) { NO_MAP_LOCK_REQUIRED; - OreManager *omgr = - getServer(L)->getEmergeManager()->getWritableOreManager(); + OreManager *omgr = getServer(L)->getEmergeManager()->getWritableOreManager(); omgr->clear(); return 0; } - // clear_registered_schematics() int ModApiMapgen::l_clear_registered_schematics(lua_State *L) { NO_MAP_LOCK_REQUIRED; SchematicManager *smgr = - getServer(L)->getEmergeManager()->getWritableSchematicManager(); + getServer(L)->getEmergeManager()->getWritableSchematicManager(); smgr->clear(); return 0; } - // generate_ores(vm, p1, p2, [ore_id]) int ModApiMapgen::l_generate_ores(lua_State *L) { @@ -1501,13 +1478,15 @@ int ModApiMapgen::l_generate_ores(lua_State *L) Mapgen mg; mg.seed = emerge->mgparams->seed; - mg.vm = LuaVoxelManip::checkobject(L, 1)->vm; + mg.vm = LuaVoxelManip::checkobject(L, 1)->vm; mg.ndef = getServer(L)->getNodeDefManager(); - v3s16 pmin = lua_istable(L, 2) ? check_v3s16(L, 2) : - mg.vm->m_area.MinEdge + v3s16(1,1,1) * MAP_BLOCKSIZE; - v3s16 pmax = lua_istable(L, 3) ? check_v3s16(L, 3) : - mg.vm->m_area.MaxEdge - v3s16(1,1,1) * MAP_BLOCKSIZE; + v3s16 pmin = lua_istable(L, 2) ? check_v3s16(L, 2) + : mg.vm->m_area.MinEdge + + v3s16(1, 1, 1) * MAP_BLOCKSIZE; + v3s16 pmax = lua_istable(L, 3) ? check_v3s16(L, 3) + : mg.vm->m_area.MaxEdge - + v3s16(1, 1, 1) * MAP_BLOCKSIZE; sortBoxVerticies(pmin, pmax); u32 blockseed = Mapgen::getBlockSeed(pmin, mg.seed); @@ -1517,7 +1496,6 @@ int ModApiMapgen::l_generate_ores(lua_State *L) return 0; } - // generate_decorations(vm, p1, p2, [deco_id]) int ModApiMapgen::l_generate_decorations(lua_State *L) { @@ -1527,13 +1505,15 @@ int ModApiMapgen::l_generate_decorations(lua_State *L) Mapgen mg; mg.seed = emerge->mgparams->seed; - mg.vm = LuaVoxelManip::checkobject(L, 1)->vm; + mg.vm = LuaVoxelManip::checkobject(L, 1)->vm; mg.ndef = getServer(L)->getNodeDefManager(); - v3s16 pmin = lua_istable(L, 2) ? check_v3s16(L, 2) : - mg.vm->m_area.MinEdge + v3s16(1,1,1) * MAP_BLOCKSIZE; - v3s16 pmax = lua_istable(L, 3) ? check_v3s16(L, 3) : - mg.vm->m_area.MaxEdge - v3s16(1,1,1) * MAP_BLOCKSIZE; + v3s16 pmin = lua_istable(L, 2) ? check_v3s16(L, 2) + : mg.vm->m_area.MinEdge + + v3s16(1, 1, 1) * MAP_BLOCKSIZE; + v3s16 pmax = lua_istable(L, 3) ? check_v3s16(L, 3) + : mg.vm->m_area.MaxEdge - + v3s16(1, 1, 1) * MAP_BLOCKSIZE; sortBoxVerticies(pmin, pmax); u32 blockseed = Mapgen::getBlockSeed(pmin, mg.seed); @@ -1543,7 +1523,6 @@ int ModApiMapgen::l_generate_decorations(lua_State *L) return 0; } - // create_schematic(p1, p2, probability_list, filename, y_slice_prob_list) int ModApiMapgen::l_create_schematic(lua_State *L) { @@ -1561,7 +1540,7 @@ int ModApiMapgen::l_create_schematic(lua_State *L) v3s16 p2 = check_v3s16(L, 2); sortBoxVerticies(p1, p2); - std::vector > prob_list; + std::vector> prob_list; if (lua_istable(L, 3)) { lua_pushnil(L); while (lua_next(L, 3)) { @@ -1570,7 +1549,8 @@ int ModApiMapgen::l_create_schematic(lua_State *L) v3s16 pos = check_v3s16(L, -1); lua_pop(L, 1); - u8 prob = getintfield_default(L, -1, "prob", MTSCHEM_PROB_ALWAYS); + u8 prob = getintfield_default( + L, -1, "prob", MTSCHEM_PROB_ALWAYS); prob_list.emplace_back(pos, prob); } @@ -1578,13 +1558,14 @@ int ModApiMapgen::l_create_schematic(lua_State *L) } } - std::vector > slice_prob_list; + std::vector> slice_prob_list; if (lua_istable(L, 5)) { lua_pushnil(L); while (lua_next(L, 5)) { if (lua_istable(L, -1)) { s16 ypos = getintfield_default(L, -1, "ypos", 0); - u8 prob = getintfield_default(L, -1, "prob", MTSCHEM_PROB_ALWAYS); + u8 prob = getintfield_default( + L, -1, "prob", MTSCHEM_PROB_ALWAYS); slice_prob_list.emplace_back(ypos, prob); } @@ -1594,21 +1575,21 @@ int ModApiMapgen::l_create_schematic(lua_State *L) if (!schem.getSchematicFromMap(map, p1, p2)) { errorstream << "create_schematic: failed to get schematic " - "from map" << std::endl; + "from map" + << std::endl; return 0; } schem.applyProbabilities(p1, &prob_list, &slice_prob_list); schem.saveSchematicToFile(filename, ndef); - actionstream << "create_schematic: saved schematic file '" - << filename << "'." << std::endl; + actionstream << "create_schematic: saved schematic file '" << filename << "'." + << std::endl; lua_pushboolean(L, true); return 1; } - // place_schematic(p, schematic, rotation, // replacements, force_placement, flagstring) int ModApiMapgen::l_place_schematic(lua_State *L) @@ -1656,7 +1637,6 @@ int ModApiMapgen::l_place_schematic(lua_State *L) return 1; } - // place_schematic_on_vmanip(vm, p, schematic, rotation, // replacements, force_placement, flagstring) int ModApiMapgen::l_place_schematic_on_vmanip(lua_State *L) @@ -1699,19 +1679,19 @@ int ModApiMapgen::l_place_schematic_on_vmanip(lua_State *L) read_flags(L, 7, flagdesc_deco, &flags, NULL); bool schematic_did_fit = schem->placeOnVManip( - vm, p, flags, (Rotation)rot, force_placement); + vm, p, flags, (Rotation)rot, force_placement); lua_pushboolean(L, schematic_did_fit); return 1; } - // serialize_schematic(schematic, format, options={...}) int ModApiMapgen::l_serialize_schematic(lua_State *L) { NO_MAP_LOCK_REQUIRED; - const SchematicManager *schemmgr = getServer(L)->getEmergeManager()->getSchematicManager(); + const SchematicManager *schemmgr = + getServer(L)->getEmergeManager()->getSchematicManager(); //// Read options bool use_comments = getboolfield_default(L, 3, "lua_use_comments", false); @@ -1725,7 +1705,8 @@ int ModApiMapgen::l_serialize_schematic(lua_State *L) was_loaded = true; } if (!schem) { - errorstream << "serialize_schematic: failed to get schematic" << std::endl; + errorstream << "serialize_schematic: failed to get schematic" + << std::endl; return 0; } @@ -1742,8 +1723,8 @@ int ModApiMapgen::l_serialize_schematic(lua_State *L) schem->serializeToMts(&os, schem->m_nodenames); break; case SCHEM_FMT_LUA: - schem->serializeToLua(&os, schem->m_nodenames, - use_comments, indent_spaces); + schem->serializeToLua( + &os, schem->m_nodenames, use_comments, indent_spaces); break; default: return 0; @@ -1763,10 +1744,11 @@ int ModApiMapgen::l_read_schematic(lua_State *L) NO_MAP_LOCK_REQUIRED; const SchematicManager *schemmgr = - getServer(L)->getEmergeManager()->getSchematicManager(); + getServer(L)->getEmergeManager()->getSchematicManager(); //// Read options - std::string write_yslice = getstringfield_default(L, 2, "write_yslice_prob", "all"); + std::string write_yslice = + getstringfield_default(L, 2, "write_yslice_prob", "all"); //// Get schematic bool was_loaded = false; @@ -1812,7 +1794,7 @@ int ModApiMapgen::l_read_schematic(lua_State *L) lua_createtable(L, numnodes, 0); // data table for (u32 i = 0; i < numnodes; ++i) { MapNode node = schem->schemdata[i]; - u8 probability = node.param1 & MTSCHEM_PROB_MASK; + u8 probability = node.param1 & MTSCHEM_PROB_MASK; bool force_place = node.param1 & MTSCHEM_FORCE_PLACE; lua_createtable(L, 0, force_place ? 4 : 3); lua_pushstring(L, names[schem->schemdata[i].getContent()].c_str()); @@ -1835,7 +1817,6 @@ int ModApiMapgen::l_read_schematic(lua_State *L) return 1; } - void ModApiMapgen::Initialize(lua_State *L, int top) { API_FCT(get_biome_id); diff --git a/src/script/lua_api/l_mapgen.h b/src/script/lua_api/l_mapgen.h index 0bdc56fc5..ebc49a5ec 100644 --- a/src/script/lua_api/l_mapgen.h +++ b/src/script/lua_api/l_mapgen.h @@ -21,7 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "lua_api/l_base.h" -typedef u16 biome_t; // copy from mg_biome.h to avoid an unnecessary include +typedef u16 biome_t; // copy from mg_biome.h to avoid an unnecessary include class ModApiMapgen : public ModApiBase { diff --git a/src/script/lua_api/l_metadata.cpp b/src/script/lua_api/l_metadata.cpp index 21002e6a7..3c9670c22 100644 --- a/src/script/lua_api/l_metadata.cpp +++ b/src/script/lua_api/l_metadata.cpp @@ -26,12 +26,14 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "server.h" // LUALIB_API -void *luaL_checkudata_is_metadataref(lua_State *L, int ud) { +void *luaL_checkudata_is_metadataref(lua_State *L, int ud) +{ void *p = lua_touserdata(L, ud); - if (p != NULL && // value is a userdata? - lua_getmetatable(L, ud)) { // does it have a metatable? + if (p != NULL && // value is a userdata? + lua_getmetatable(L, ud)) { // does it have a metatable? lua_getfield(L, -1, "metadata_class"); - if (lua_type(L, -1) == LUA_TSTRING) { // does it have a metadata_class field? + if (lua_type(L, -1) == + LUA_TSTRING) { // does it have a metadata_class field? return p; } } @@ -39,14 +41,14 @@ void *luaL_checkudata_is_metadataref(lua_State *L, int ud) { return NULL; } -MetaDataRef* MetaDataRef::checkobject(lua_State *L, int narg) +MetaDataRef *MetaDataRef::checkobject(lua_State *L, int narg) { luaL_checktype(L, narg, LUA_TUSERDATA); void *ud = luaL_checkudata_is_metadataref(L, narg); if (!ud) luaL_typerror(L, narg, "MetaDataRef"); - return *(MetaDataRef**)ud; // unbox pointer + return *(MetaDataRef **)ud; // unbox pointer } // Exported functions diff --git a/src/script/lua_api/l_minimap.cpp b/src/script/lua_api/l_minimap.cpp index 5fba76eb8..1c429bdcb 100644 --- a/src/script/lua_api/l_minimap.cpp +++ b/src/script/lua_api/l_minimap.cpp @@ -17,7 +17,6 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ - #include "lua_api/l_minimap.h" #include "lua_api/l_internal.h" #include "common/c_converter.h" @@ -99,12 +98,11 @@ int LuaMinimap::l_set_mode(lua_State *L) Minimap *m = getobject(ref); s32 mode = lua_tointeger(L, 2); - if (mode < MINIMAP_MODE_OFF || - mode >= MINIMAP_MODE_COUNT) { + if (mode < MINIMAP_MODE_OFF || mode >= MINIMAP_MODE_COUNT) { return 0; } - m->setMinimapMode((MinimapMode) mode); + m->setMinimapMode((MinimapMode)mode); return 1; } @@ -172,15 +170,16 @@ LuaMinimap *LuaMinimap::checkobject(lua_State *L, int narg) if (!ud) luaL_typerror(L, narg, className); - return *(LuaMinimap **)ud; // unbox pointer + return *(LuaMinimap **)ud; // unbox pointer } -Minimap* LuaMinimap::getobject(LuaMinimap *ref) +Minimap *LuaMinimap::getobject(LuaMinimap *ref) { return ref->m_minimap; } -int LuaMinimap::gc_object(lua_State *L) { +int LuaMinimap::gc_object(lua_State *L) +{ LuaMinimap *o = *(LuaMinimap **)(lua_touserdata(L, 1)); delete o; return 0; @@ -195,7 +194,7 @@ void LuaMinimap::Register(lua_State *L) lua_pushliteral(L, "__metatable"); lua_pushvalue(L, methodtable); - lua_settable(L, metatable); // hide metatable from Lua getmetatable() + lua_settable(L, metatable); // hide metatable from Lua getmetatable() lua_pushliteral(L, "__index"); lua_pushvalue(L, methodtable); @@ -205,23 +204,16 @@ void LuaMinimap::Register(lua_State *L) lua_pushcfunction(L, gc_object); lua_settable(L, metatable); - lua_pop(L, 1); // drop metatable + lua_pop(L, 1); // drop metatable - luaL_openlib(L, 0, methods, 0); // fill methodtable - lua_pop(L, 1); // drop methodtable + luaL_openlib(L, 0, methods, 0); // fill methodtable + lua_pop(L, 1); // drop methodtable } const char LuaMinimap::className[] = "Minimap"; -const luaL_Reg LuaMinimap::methods[] = { - luamethod(LuaMinimap, show), - luamethod(LuaMinimap, hide), - luamethod(LuaMinimap, get_pos), - luamethod(LuaMinimap, set_pos), - luamethod(LuaMinimap, get_angle), - luamethod(LuaMinimap, set_angle), - luamethod(LuaMinimap, get_mode), - luamethod(LuaMinimap, set_mode), - luamethod(LuaMinimap, set_shape), - luamethod(LuaMinimap, get_shape), - {0,0} -}; +const luaL_Reg LuaMinimap::methods[] = {luamethod(LuaMinimap, show), + luamethod(LuaMinimap, hide), luamethod(LuaMinimap, get_pos), + luamethod(LuaMinimap, set_pos), luamethod(LuaMinimap, get_angle), + luamethod(LuaMinimap, set_angle), luamethod(LuaMinimap, get_mode), + luamethod(LuaMinimap, set_mode), luamethod(LuaMinimap, set_shape), + luamethod(LuaMinimap, get_shape), {0, 0}}; diff --git a/src/script/lua_api/l_nodemeta.cpp b/src/script/lua_api/l_nodemeta.cpp index 57052cb42..934c08796 100644 --- a/src/script/lua_api/l_nodemeta.cpp +++ b/src/script/lua_api/l_nodemeta.cpp @@ -29,15 +29,16 @@ with this program; if not, write to the Free Software Foundation, Inc., /* NodeMetaRef */ -NodeMetaRef* NodeMetaRef::checkobject(lua_State *L, int narg) +NodeMetaRef *NodeMetaRef::checkobject(lua_State *L, int narg) { luaL_checktype(L, narg, LUA_TUSERDATA); void *ud = luaL_checkudata(L, narg, className); - if(!ud) luaL_typerror(L, narg, className); - return *(NodeMetaRef**)ud; // unbox pointer + if (!ud) + luaL_typerror(L, narg, className); + return *(NodeMetaRef **)ud; // unbox pointer } -Metadata* NodeMetaRef::getmeta(bool auto_create) +Metadata *NodeMetaRef::getmeta(bool auto_create) { if (m_is_local) return m_meta; @@ -64,7 +65,7 @@ void NodeMetaRef::reportMetadataChange(const std::string *name) SANITY_CHECK(!m_is_local); // NOTE: This same code is in rollback_interface.cpp // Inform other things that the metadata has changed - NodeMetadata *meta = dynamic_cast(m_meta); + NodeMetadata *meta = dynamic_cast(m_meta); MapEditEvent event; event.type = MEET_BLOCK_NODE_METADATA_CHANGED; @@ -76,7 +77,8 @@ void NodeMetaRef::reportMetadataChange(const std::string *name) // Exported functions // garbage collector -int NodeMetaRef::gc_object(lua_State *L) { +int NodeMetaRef::gc_object(lua_State *L) +{ NodeMetaRef *o = *(NodeMetaRef **)(lua_touserdata(L, 1)); delete o; return 0; @@ -88,7 +90,7 @@ int NodeMetaRef::l_get_inventory(lua_State *L) MAP_LOCK_REQUIRED; NodeMetaRef *ref = checkobject(L, 1); - ref->getmeta(true); // try to ensure the metadata exists + ref->getmeta(true); // try to ensure the metadata exists InvRef::createNodeMeta(L, ref->m_p); return 1; } @@ -99,7 +101,7 @@ int NodeMetaRef::l_mark_as_private(lua_State *L) MAP_LOCK_REQUIRED; NodeMetaRef *ref = checkobject(L, 1); - NodeMetadata *meta = dynamic_cast(ref->getmeta(true)); + NodeMetadata *meta = dynamic_cast(ref->getmeta(true)); assert(meta); if (lua_istable(L, 2)) { @@ -124,15 +126,15 @@ void NodeMetaRef::handleToTable(lua_State *L, Metadata *_meta) // fields MetaDataRef::handleToTable(L, _meta); - NodeMetadata *meta = (NodeMetadata*) _meta; + NodeMetadata *meta = (NodeMetadata *)_meta; // inventory lua_newtable(L); Inventory *inv = meta->getInventory(); if (inv) { std::vector lists = inv->getLists(); - for(std::vector::const_iterator - i = lists.begin(); i != lists.end(); ++i) { + for (std::vector::const_iterator i = lists.begin(); + i != lists.end(); ++i) { push_inventory_list(L, inv, (*i)->getName().c_str()); lua_setfield(L, -2, (*i)->getName().c_str()); } @@ -147,7 +149,7 @@ bool NodeMetaRef::handleFromTable(lua_State *L, int table, Metadata *_meta) if (!MetaDataRef::handleFromTable(L, table, _meta)) return false; - NodeMetadata *meta = (NodeMetadata*) _meta; + NodeMetadata *meta = (NodeMetadata *)_meta; // inventory Inventory *inv = meta->getInventory(); @@ -167,16 +169,11 @@ bool NodeMetaRef::handleFromTable(lua_State *L, int table, Metadata *_meta) return true; } - -NodeMetaRef::NodeMetaRef(v3s16 p, ServerEnvironment *env): - m_p(p), - m_env(env) +NodeMetaRef::NodeMetaRef(v3s16 p, ServerEnvironment *env) : m_p(p), m_env(env) { } -NodeMetaRef::NodeMetaRef(Metadata *meta): - m_meta(meta), - m_is_local(true) +NodeMetaRef::NodeMetaRef(Metadata *meta) : m_meta(meta), m_is_local(true) { } @@ -185,7 +182,7 @@ NodeMetaRef::NodeMetaRef(Metadata *meta): void NodeMetaRef::create(lua_State *L, v3s16 p, ServerEnvironment *env) { NodeMetaRef *o = new NodeMetaRef(p, env); - //infostream<<"NodeMetaRef::create: o="<(L,2); - f32 e = readParam(L,3); + f32 t = readParam(L, 2); + f32 e = readParam(L, 3); o->m_map->setNodeTimer(NodeTimer(t, e, o->m_p)); return 0; } @@ -51,7 +52,7 @@ int NodeTimerRef::l_start(lua_State *L) { MAP_LOCK_REQUIRED; NodeTimerRef *o = checkobject(L, 1); - f32 t = readParam(L,2); + f32 t = readParam(L, 2); o->m_map->setNodeTimer(NodeTimer(t, 0, o->m_p)); return 0; } @@ -69,7 +70,7 @@ int NodeTimerRef::l_is_started(lua_State *L) MAP_LOCK_REQUIRED; NodeTimerRef *o = checkobject(L, 1); NodeTimer t = o->m_map->getNodeTimer(o->m_p); - lua_pushboolean(L,(t.timeout != 0)); + lua_pushboolean(L, (t.timeout != 0)); return 1; } @@ -78,7 +79,7 @@ int NodeTimerRef::l_get_timeout(lua_State *L) MAP_LOCK_REQUIRED; NodeTimerRef *o = checkobject(L, 1); NodeTimer t = o->m_map->getNodeTimer(o->m_p); - lua_pushnumber(L,t.timeout); + lua_pushnumber(L, t.timeout); return 1; } @@ -87,7 +88,7 @@ int NodeTimerRef::l_get_elapsed(lua_State *L) MAP_LOCK_REQUIRED; NodeTimerRef *o = checkobject(L, 1); NodeTimer t = o->m_map->getNodeTimer(o->m_p); - lua_pushnumber(L,t.elapsed); + lua_pushnumber(L, t.elapsed); return 1; } @@ -110,7 +111,7 @@ void NodeTimerRef::Register(lua_State *L) lua_pushliteral(L, "__metatable"); lua_pushvalue(L, methodtable); - lua_settable(L, metatable); // hide metatable from Lua getmetatable() + lua_settable(L, metatable); // hide metatable from Lua getmetatable() lua_pushliteral(L, "__index"); lua_pushvalue(L, methodtable); @@ -120,22 +121,17 @@ void NodeTimerRef::Register(lua_State *L) lua_pushcfunction(L, gc_object); lua_settable(L, metatable); - lua_pop(L, 1); // drop metatable + lua_pop(L, 1); // drop metatable - luaL_openlib(L, 0, methods, 0); // fill methodtable - lua_pop(L, 1); // drop methodtable + luaL_openlib(L, 0, methods, 0); // fill methodtable + lua_pop(L, 1); // drop methodtable // Cannot be created from Lua - //lua_register(L, className, create_object); + // lua_register(L, className, create_object); } const char NodeTimerRef::className[] = "NodeTimerRef"; -const luaL_Reg NodeTimerRef::methods[] = { - luamethod(NodeTimerRef, start), - luamethod(NodeTimerRef, set), - luamethod(NodeTimerRef, stop), - luamethod(NodeTimerRef, is_started), - luamethod(NodeTimerRef, get_timeout), - luamethod(NodeTimerRef, get_elapsed), - {0,0} -}; +const luaL_Reg NodeTimerRef::methods[] = {luamethod(NodeTimerRef, start), + luamethod(NodeTimerRef, set), luamethod(NodeTimerRef, stop), + luamethod(NodeTimerRef, is_started), luamethod(NodeTimerRef, get_timeout), + luamethod(NodeTimerRef, get_elapsed), {0, 0}}; diff --git a/src/script/lua_api/l_noise.cpp b/src/script/lua_api/l_noise.cpp index 9aeb15709..0e9ece82b 100644 --- a/src/script/lua_api/l_noise.cpp +++ b/src/script/lua_api/l_noise.cpp @@ -30,12 +30,10 @@ with this program; if not, write to the Free Software Foundation, Inc., LuaPerlinNoise */ -LuaPerlinNoise::LuaPerlinNoise(NoiseParams *params) : - np(*params) +LuaPerlinNoise::LuaPerlinNoise(NoiseParams *params) : np(*params) { } - int LuaPerlinNoise::l_get_2d(lua_State *L) { NO_MAP_LOCK_REQUIRED; @@ -46,7 +44,6 @@ int LuaPerlinNoise::l_get_2d(lua_State *L) return 1; } - int LuaPerlinNoise::l_get_3d(lua_State *L) { NO_MAP_LOCK_REQUIRED; @@ -57,7 +54,6 @@ int LuaPerlinNoise::l_get_3d(lua_State *L) return 1; } - int LuaPerlinNoise::create_object(lua_State *L) { NO_MAP_LOCK_REQUIRED; @@ -67,10 +63,10 @@ int LuaPerlinNoise::create_object(lua_State *L) if (lua_istable(L, 1)) { read_noiseparams(L, 1, ¶ms); } else { - params.seed = luaL_checkint(L, 1); + params.seed = luaL_checkint(L, 1); params.octaves = luaL_checkint(L, 2); params.persist = readParam(L, 3); - params.spread = v3f(1, 1, 1) * readParam(L, 4); + params.spread = v3f(1, 1, 1) * readParam(L, 4); } LuaPerlinNoise *o = new LuaPerlinNoise(¶ms); @@ -81,7 +77,6 @@ int LuaPerlinNoise::create_object(lua_State *L) return 1; } - int LuaPerlinNoise::gc_object(lua_State *L) { LuaPerlinNoise *o = *(LuaPerlinNoise **)(lua_touserdata(L, 1)); @@ -89,7 +84,6 @@ int LuaPerlinNoise::gc_object(lua_State *L) return 0; } - LuaPerlinNoise *LuaPerlinNoise::checkobject(lua_State *L, int narg) { NO_MAP_LOCK_REQUIRED; @@ -100,7 +94,6 @@ LuaPerlinNoise *LuaPerlinNoise::checkobject(lua_State *L, int narg) return *(LuaPerlinNoise **)ud; } - void LuaPerlinNoise::Register(lua_State *L) { lua_newtable(L); @@ -129,13 +122,9 @@ void LuaPerlinNoise::Register(lua_State *L) lua_register(L, className, create_object); } - const char LuaPerlinNoise::className[] = "PerlinNoise"; -luaL_Reg LuaPerlinNoise::methods[] = { - luamethod_aliased(LuaPerlinNoise, get_2d, get2d), - luamethod_aliased(LuaPerlinNoise, get_3d, get3d), - {0,0} -}; +luaL_Reg LuaPerlinNoise::methods[] = {luamethod_aliased(LuaPerlinNoise, get_2d, get2d), + luamethod_aliased(LuaPerlinNoise, get_3d, get3d), {0, 0}}; /////////////////////////////////////// /* @@ -153,13 +142,11 @@ LuaPerlinNoiseMap::LuaPerlinNoiseMap(NoiseParams *params, s32 seed, v3s16 size) } } - LuaPerlinNoiseMap::~LuaPerlinNoiseMap() { delete noise; } - int LuaPerlinNoiseMap::l_get_2d_map(lua_State *L) { NO_MAP_LOCK_REQUIRED; @@ -183,7 +170,6 @@ int LuaPerlinNoiseMap::l_get_2d_map(lua_State *L) return 1; } - int LuaPerlinNoiseMap::l_get_2d_map_flat(lua_State *L) { NO_MAP_LOCK_REQUIRED; @@ -209,7 +195,6 @@ int LuaPerlinNoiseMap::l_get_2d_map_flat(lua_State *L) return 1; } - int LuaPerlinNoiseMap::l_get_3d_map(lua_State *L) { NO_MAP_LOCK_REQUIRED; @@ -240,14 +225,13 @@ int LuaPerlinNoiseMap::l_get_3d_map(lua_State *L) return 1; } - int LuaPerlinNoiseMap::l_get_3d_map_flat(lua_State *L) { NO_MAP_LOCK_REQUIRED; LuaPerlinNoiseMap *o = checkobject(L, 1); - v3f p = check_v3f(L, 2); - bool use_buffer = lua_istable(L, 3); + v3f p = check_v3f(L, 2); + bool use_buffer = lua_istable(L, 3); if (!o->m_is3d) return 0; @@ -269,7 +253,6 @@ int LuaPerlinNoiseMap::l_get_3d_map_flat(lua_State *L) return 1; } - int LuaPerlinNoiseMap::l_calc_2d_map(lua_State *L) { NO_MAP_LOCK_REQUIRED; @@ -288,7 +271,7 @@ int LuaPerlinNoiseMap::l_calc_3d_map(lua_State *L) NO_MAP_LOCK_REQUIRED; LuaPerlinNoiseMap *o = checkobject(L, 1); - v3f p = check_v3f(L, 2); + v3f p = check_v3f(L, 2); if (!o->m_is3d) return 0; @@ -299,15 +282,14 @@ int LuaPerlinNoiseMap::l_calc_3d_map(lua_State *L) return 0; } - int LuaPerlinNoiseMap::l_get_map_slice(lua_State *L) { NO_MAP_LOCK_REQUIRED; LuaPerlinNoiseMap *o = checkobject(L, 1); - v3s16 slice_offset = read_v3s16(L, 2); - v3s16 slice_size = read_v3s16(L, 3); - bool use_buffer = lua_istable(L, 4); + v3s16 slice_offset = read_v3s16(L, 2); + v3s16 slice_size = read_v3s16(L, 3); + bool use_buffer = lua_istable(L, 4); Noise *n = o->noise; @@ -316,15 +298,13 @@ int LuaPerlinNoiseMap::l_get_map_slice(lua_State *L) else lua_newtable(L); - write_array_slice_float(L, lua_gettop(L), n->result, - v3u16(n->sx, n->sy, n->sz), - v3u16(slice_offset.X, slice_offset.Y, slice_offset.Z), - v3u16(slice_size.X, slice_size.Y, slice_size.Z)); + write_array_slice_float(L, lua_gettop(L), n->result, v3u16(n->sx, n->sy, n->sz), + v3u16(slice_offset.X, slice_offset.Y, slice_offset.Z), + v3u16(slice_size.X, slice_size.Y, slice_size.Z)); return 1; } - int LuaPerlinNoiseMap::create_object(lua_State *L) { NoiseParams np; @@ -339,7 +319,6 @@ int LuaPerlinNoiseMap::create_object(lua_State *L) return 1; } - int LuaPerlinNoiseMap::gc_object(lua_State *L) { LuaPerlinNoiseMap *o = *(LuaPerlinNoiseMap **)(lua_touserdata(L, 1)); @@ -347,7 +326,6 @@ int LuaPerlinNoiseMap::gc_object(lua_State *L) return 0; } - LuaPerlinNoiseMap *LuaPerlinNoiseMap::checkobject(lua_State *L, int narg) { luaL_checktype(L, narg, LUA_TUSERDATA); @@ -359,7 +337,6 @@ LuaPerlinNoiseMap *LuaPerlinNoiseMap::checkobject(lua_State *L, int narg) return *(LuaPerlinNoiseMap **)ud; } - void LuaPerlinNoiseMap::Register(lua_State *L) { lua_newtable(L); @@ -388,18 +365,15 @@ void LuaPerlinNoiseMap::Register(lua_State *L) lua_register(L, className, create_object); } - const char LuaPerlinNoiseMap::className[] = "PerlinNoiseMap"; luaL_Reg LuaPerlinNoiseMap::methods[] = { - luamethod_aliased(LuaPerlinNoiseMap, get_2d_map, get2dMap), - luamethod_aliased(LuaPerlinNoiseMap, get_2d_map_flat, get2dMap_flat), - luamethod_aliased(LuaPerlinNoiseMap, calc_2d_map, calc2dMap), - luamethod_aliased(LuaPerlinNoiseMap, get_3d_map, get3dMap), - luamethod_aliased(LuaPerlinNoiseMap, get_3d_map_flat, get3dMap_flat), - luamethod_aliased(LuaPerlinNoiseMap, calc_3d_map, calc3dMap), - luamethod_aliased(LuaPerlinNoiseMap, get_map_slice, getMapSlice), - {0,0} -}; + luamethod_aliased(LuaPerlinNoiseMap, get_2d_map, get2dMap), + luamethod_aliased(LuaPerlinNoiseMap, get_2d_map_flat, get2dMap_flat), + luamethod_aliased(LuaPerlinNoiseMap, calc_2d_map, calc2dMap), + luamethod_aliased(LuaPerlinNoiseMap, get_3d_map, get3dMap), + luamethod_aliased(LuaPerlinNoiseMap, get_3d_map_flat, get3dMap_flat), + luamethod_aliased(LuaPerlinNoiseMap, calc_3d_map, calc3dMap), + luamethod_aliased(LuaPerlinNoiseMap, get_map_slice, getMapSlice), {0, 0}}; /////////////////////////////////////// /* @@ -419,22 +393,22 @@ int LuaPseudoRandom::l_next(lua_State *L) if (lua_isnumber(L, 3)) max = luaL_checkinteger(L, 3); if (max < min) { - errorstream<<"PseudoRandom.next(): max="< 32767/5) + if (max - min != 32767 && max - min > 32767 / 5) throw LuaError("PseudoRandom.next() max-min is not 32767" - " and is > 32768/5. This is disallowed due to" - " the bad random distribution the" - " implementation would otherwise make."); + " and is > 32768/5. This is disallowed due to" + " the bad random distribution the" + " implementation would otherwise make."); PseudoRandom &pseudo = o->m_pseudo; int val = pseudo.next(); - val = (val % (max-min+1)) + min; + val = (val % (max - min + 1)) + min; lua_pushinteger(L, val); return 1; } - int LuaPseudoRandom::create_object(lua_State *L) { NO_MAP_LOCK_REQUIRED; @@ -447,7 +421,6 @@ int LuaPseudoRandom::create_object(lua_State *L) return 1; } - int LuaPseudoRandom::gc_object(lua_State *L) { LuaPseudoRandom *o = *(LuaPseudoRandom **)(lua_touserdata(L, 1)); @@ -455,7 +428,6 @@ int LuaPseudoRandom::gc_object(lua_State *L) return 0; } - LuaPseudoRandom *LuaPseudoRandom::checkobject(lua_State *L, int narg) { luaL_checktype(L, narg, LUA_TUSERDATA); @@ -465,7 +437,6 @@ LuaPseudoRandom *LuaPseudoRandom::checkobject(lua_State *L, int narg) return *(LuaPseudoRandom **)ud; } - void LuaPseudoRandom::Register(lua_State *L) { lua_newtable(L); @@ -493,12 +464,8 @@ void LuaPseudoRandom::Register(lua_State *L) lua_register(L, className, create_object); } - const char LuaPseudoRandom::className[] = "PseudoRandom"; -const luaL_Reg LuaPseudoRandom::methods[] = { - luamethod(LuaPseudoRandom, next), - {0,0} -}; +const luaL_Reg LuaPseudoRandom::methods[] = {luamethod(LuaPseudoRandom, next), {0, 0}}; /////////////////////////////////////// /* @@ -517,7 +484,6 @@ int LuaPcgRandom::l_next(lua_State *L) return 1; } - int LuaPcgRandom::l_rand_normal_dist(lua_State *L) { NO_MAP_LOCK_REQUIRED; @@ -531,22 +497,19 @@ int LuaPcgRandom::l_rand_normal_dist(lua_State *L) return 1; } - int LuaPcgRandom::create_object(lua_State *L) { NO_MAP_LOCK_REQUIRED; u64 seed = luaL_checknumber(L, 1); - LuaPcgRandom *o = lua_isnumber(L, 2) ? - new LuaPcgRandom(seed, lua_tointeger(L, 2)) : - new LuaPcgRandom(seed); + LuaPcgRandom *o = lua_isnumber(L, 2) ? new LuaPcgRandom(seed, lua_tointeger(L, 2)) + : new LuaPcgRandom(seed); *(void **)(lua_newuserdata(L, sizeof(void *))) = o; luaL_getmetatable(L, className); lua_setmetatable(L, -2); return 1; } - int LuaPcgRandom::gc_object(lua_State *L) { LuaPcgRandom *o = *(LuaPcgRandom **)(lua_touserdata(L, 1)); @@ -554,7 +517,6 @@ int LuaPcgRandom::gc_object(lua_State *L) return 0; } - LuaPcgRandom *LuaPcgRandom::checkobject(lua_State *L, int narg) { luaL_checktype(L, narg, LUA_TUSERDATA); @@ -564,7 +526,6 @@ LuaPcgRandom *LuaPcgRandom::checkobject(lua_State *L, int narg) return *(LuaPcgRandom **)ud; } - void LuaPcgRandom::Register(lua_State *L) { lua_newtable(L); @@ -592,13 +553,9 @@ void LuaPcgRandom::Register(lua_State *L) lua_register(L, className, create_object); } - const char LuaPcgRandom::className[] = "PcgRandom"; -const luaL_Reg LuaPcgRandom::methods[] = { - luamethod(LuaPcgRandom, next), - luamethod(LuaPcgRandom, rand_normal_dist), - {0,0} -}; +const luaL_Reg LuaPcgRandom::methods[] = {luamethod(LuaPcgRandom, next), + luamethod(LuaPcgRandom, rand_normal_dist), {0, 0}}; /////////////////////////////////////// /* @@ -633,7 +590,8 @@ int LuaSecureRandom::l_next_bytes(lua_State *L) // Refill buffer and copy over the remainder of what was requested o->fillRandBuf(); - memcpy(output_buf + count_remaining, o->m_rand_buf, count - count_remaining); + memcpy(output_buf + count_remaining, o->m_rand_buf, + count - count_remaining); // Update index o->m_rand_idx = count - count_remaining; @@ -644,7 +602,6 @@ int LuaSecureRandom::l_next_bytes(lua_State *L) return 1; } - int LuaSecureRandom::create_object(lua_State *L) { LuaSecureRandom *o = new LuaSecureRandom(); @@ -661,7 +618,6 @@ int LuaSecureRandom::create_object(lua_State *L) return 1; } - int LuaSecureRandom::gc_object(lua_State *L) { LuaSecureRandom *o = *(LuaSecureRandom **)(lua_touserdata(L, 1)); @@ -669,7 +625,6 @@ int LuaSecureRandom::gc_object(lua_State *L) return 0; } - LuaSecureRandom *LuaSecureRandom::checkobject(lua_State *L, int narg) { luaL_checktype(L, narg, LUA_TUSERDATA); @@ -679,7 +634,6 @@ LuaSecureRandom *LuaSecureRandom::checkobject(lua_State *L, int narg) return *(LuaSecureRandom **)ud; } - void LuaSecureRandom::Register(lua_State *L) { lua_newtable(L); @@ -709,6 +663,4 @@ void LuaSecureRandom::Register(lua_State *L) const char LuaSecureRandom::className[] = "SecureRandom"; const luaL_Reg LuaSecureRandom::methods[] = { - luamethod(LuaSecureRandom, next_bytes), - {0,0} -}; + luamethod(LuaSecureRandom, next_bytes), {0, 0}}; diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index e7394133a..5d48ee93d 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -39,16 +39,16 @@ with this program; if not, write to the Free Software Foundation, Inc., ObjectRef */ - -ObjectRef* ObjectRef::checkobject(lua_State *L, int narg) +ObjectRef *ObjectRef::checkobject(lua_State *L, int narg) { luaL_checktype(L, narg, LUA_TUSERDATA); void *ud = luaL_checkudata(L, narg, className); - if (!ud) luaL_typerror(L, narg, className); - return *(ObjectRef**)ud; // unbox pointer + if (!ud) + luaL_typerror(L, narg, className); + return *(ObjectRef **)ud; // unbox pointer } -ServerActiveObject* ObjectRef::getobject(ObjectRef *ref) +ServerActiveObject *ObjectRef::getobject(ObjectRef *ref) { ServerActiveObject *co = ref->m_object; if (co && co->isGone()) @@ -56,24 +56,24 @@ ServerActiveObject* ObjectRef::getobject(ObjectRef *ref) return co; } -LuaEntitySAO* ObjectRef::getluaobject(ObjectRef *ref) +LuaEntitySAO *ObjectRef::getluaobject(ObjectRef *ref) { ServerActiveObject *obj = getobject(ref); if (obj == NULL) return NULL; if (obj->getType() != ACTIVEOBJECT_TYPE_LUAENTITY) return NULL; - return (LuaEntitySAO*)obj; + return (LuaEntitySAO *)obj; } -PlayerSAO* ObjectRef::getplayersao(ObjectRef *ref) +PlayerSAO *ObjectRef::getplayersao(ObjectRef *ref) { ServerActiveObject *obj = getobject(ref); if (obj == NULL) return NULL; if (obj->getType() != ACTIVEOBJECT_TYPE_PLAYER) return NULL; - return (PlayerSAO*)obj; + return (PlayerSAO *)obj; } RemotePlayer *ObjectRef::getplayer(ObjectRef *ref) @@ -87,9 +87,10 @@ RemotePlayer *ObjectRef::getplayer(ObjectRef *ref) // Exported functions // garbage collector -int ObjectRef::gc_object(lua_State *L) { +int ObjectRef::gc_object(lua_State *L) +{ ObjectRef *o = *(ObjectRef **)(lua_touserdata(L, 1)); - //infostream<<"ObjectRef::gc_object: o="<(L, -1))) { + !reason.setTypeFromString( + readParam(L, -1))) { errorstream << "Bad type given!" << std::endl; } lua_pop(L, 1); @@ -287,7 +295,8 @@ int ObjectRef::l_get_inventory(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); ServerActiveObject *co = getobject(ref); - if (co == NULL) return 0; + if (co == NULL) + return 0; // Do it InventoryLocation loc = co->getInventoryLocation(); if (getServerInventoryMgr(L)->getInventory(loc) != NULL) @@ -347,7 +356,8 @@ int ObjectRef::l_set_wielded_item(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); ServerActiveObject *co = getobject(ref); - if (co == NULL) return 0; + if (co == NULL) + return 0; // Do it ItemStack item = read_item(L, 2, getServer(L)->idef()); bool success = co->setWieldedItem(item); @@ -364,7 +374,8 @@ int ObjectRef::l_set_armor_groups(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); ServerActiveObject *co = getobject(ref); - if (co == NULL) return 0; + if (co == NULL) + return 0; // Do it ItemGroupList groups; read_groups(L, 2, groups); @@ -391,8 +402,9 @@ int ObjectRef::l_set_physics_override(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - PlayerSAO *co = (PlayerSAO *) getobject(ref); - if (co == NULL) return 0; + PlayerSAO *co = (PlayerSAO *)getobject(ref); + if (co == NULL) + return 0; // Do it if (lua_istable(L, 2)) { co->m_physics_override_speed = getfloatfield_default( @@ -403,8 +415,8 @@ int ObjectRef::l_set_physics_override(lua_State *L) L, 2, "gravity", co->m_physics_override_gravity); co->m_physics_override_sneak = getboolfield_default( L, 2, "sneak", co->m_physics_override_sneak); - co->m_physics_override_sneak_glitch = getboolfield_default( - L, 2, "sneak_glitch", co->m_physics_override_sneak_glitch); + co->m_physics_override_sneak_glitch = getboolfield_default(L, 2, + "sneak_glitch", co->m_physics_override_sneak_glitch); co->m_physics_override_new_move = getboolfield_default( L, 2, "new_move", co->m_physics_override_new_move); co->m_physics_override_sent = false; @@ -457,7 +469,8 @@ int ObjectRef::l_set_animation(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); ServerActiveObject *co = getobject(ref); - if (co == NULL) return 0; + if (co == NULL) + return 0; // Do it v2f frames = v2f(1, 1); if (!lua_isnil(L, 2)) @@ -484,7 +497,7 @@ int ObjectRef::l_get_animation(lua_State *L) if (co == NULL) return 0; // Do it - v2f frames = v2f(1,1); + v2f frames = v2f(1, 1); float frame_speed = 15; float frame_blend = 0; bool frame_loop = true; @@ -507,9 +520,9 @@ int ObjectRef::l_set_local_animation(lua_State *L) return 0; // Do it v2s32 frames[4]; - for (int i=0;i<4;i++) { - if (!lua_isnil(L, 2+1)) - frames[i] = read_v2s32(L, 2+i); + for (int i = 0; i < 4; i++) { + if (!lua_isnil(L, 2 + 1)) + frames[i] = read_v2s32(L, 2 + i); } float frame_speed = 30; if (!lua_isnil(L, 6)) @@ -559,10 +572,10 @@ int ObjectRef::l_set_eye_offset(lua_State *L) offset_third = read_v3f(L, 3); // Prevent abuse of offset values (keep player always visible) - offset_third.X = rangelim(offset_third.X,-10,10); - offset_third.Z = rangelim(offset_third.Z,-5,5); + offset_third.X = rangelim(offset_third.X, -10, 10); + offset_third.Z = rangelim(offset_third.Z, -5, 5); /* TODO: if possible: improve the camera colision detetion to allow Y <= -1.5) */ - offset_third.Y = rangelim(offset_third.Y,-10,15); //1.5*BS + offset_third.Y = rangelim(offset_third.Y, -10, 15); // 1.5*BS getServer(L)->setPlayerEyeOffset(player, offset_first, offset_third); lua_pushboolean(L, true); @@ -627,7 +640,8 @@ int ObjectRef::l_set_bone_position(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); ServerActiveObject *co = getobject(ref); - if (co == NULL) return 0; + if (co == NULL) + return 0; // Do it std::string bone; if (!lua_isnil(L, 2)) @@ -680,7 +694,8 @@ int ObjectRef::l_set_attach(lua_State *L) return 0; if (co == parent) - throw LuaError("ObjectRef::set_attach: attaching object to itself is not allowed."); + throw LuaError("ObjectRef::set_attach: attaching object to itself is not " + "allowed."); // Do it int parent_id = 0; @@ -851,7 +866,8 @@ int ObjectRef::l_set_velocity(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); LuaEntitySAO *co = getluaobject(ref); - if (co == NULL) return 0; + if (co == NULL) + return 0; v3f pos = checkFloatPos(L, 2); // Do it co->setVelocity(pos); @@ -878,7 +894,8 @@ int ObjectRef::l_get_velocity(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); LuaEntitySAO *co = getluaobject(ref); - if (co == NULL) return 0; + if (co == NULL) + return 0; // Do it v3f v = co->getVelocity(); pushFloatPos(L, v); @@ -891,7 +908,8 @@ int ObjectRef::l_set_acceleration(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); LuaEntitySAO *co = getluaobject(ref); - if (co == NULL) return 0; + if (co == NULL) + return 0; // pos v3f pos = checkFloatPos(L, 2); // Do it @@ -905,7 +923,8 @@ int ObjectRef::l_get_acceleration(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); LuaEntitySAO *co = getluaobject(ref); - if (co == NULL) return 0; + if (co == NULL) + return 0; // Do it v3f v = co->getAcceleration(); pushFloatPos(L, v); @@ -951,7 +970,8 @@ int ObjectRef::l_set_yaw(lua_State *L) ObjectRef *ref = checkobject(L, 1); LuaEntitySAO *co = getluaobject(ref); - if (co == NULL) return 0; + if (co == NULL) + return 0; if (isNaN(L, 2)) throw LuaError("ObjectRef::set_yaw: NaN value is not allowed."); @@ -980,7 +1000,8 @@ int ObjectRef::l_set_texture_mod(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); LuaEntitySAO *co = getluaobject(ref); - if (co == NULL) return 0; + if (co == NULL) + return 0; // Do it std::string mod = luaL_checkstring(L, 2); co->setTextureMod(mod); @@ -993,7 +1014,8 @@ int ObjectRef::l_get_texture_mod(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); LuaEntitySAO *co = getluaobject(ref); - if (co == NULL) return 0; + if (co == NULL) + return 0; // Do it std::string mod = co->getTextureMod(); lua_pushstring(L, mod.c_str()); @@ -1007,9 +1029,10 @@ int ObjectRef::l_set_sprite(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); LuaEntitySAO *co = getluaobject(ref); - if (co == NULL) return 0; + if (co == NULL) + return 0; // Do it - v2s16 p(0,0); + v2s16 p(0, 0); if (!lua_isnil(L, 2)) p = readParam(L, 2); int num_frames = 1; @@ -1032,8 +1055,9 @@ int ObjectRef::l_get_entity_name(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); LuaEntitySAO *co = getluaobject(ref); - log_deprecated(L,"Deprecated call to \"get_entity_name"); - if (co == NULL) return 0; + log_deprecated(L, "Deprecated call to \"get_entity_name"); + if (co == NULL) + return 0; // Do it std::string name = co->getName(); lua_pushstring(L, name.c_str()); @@ -1046,7 +1070,8 @@ int ObjectRef::l_get_luaentity(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); LuaEntitySAO *co = getluaobject(ref); - if (co == NULL) return 0; + if (co == NULL) + return 0; // Do it luaentity_get(L, co->getId()); return 1; @@ -1060,7 +1085,7 @@ int ObjectRef::l_is_player_connected(lua_State *L) NO_MAP_LOCK_REQUIRED; // This method was once added for a bugfix, but never documented log_deprecated(L, "is_player_connected is undocumented and " - "will be removed in a future release"); + "will be removed in a future release"); ObjectRef *ref = checkobject(L, 1); RemotePlayer *player = getplayer(ref); lua_pushboolean(L, (player != NULL && player->getPeerId() != PEER_ID_INEXISTENT)); @@ -1119,13 +1144,14 @@ int ObjectRef::l_get_look_dir(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - PlayerSAO* co = getplayersao(ref); - if (co == NULL) return 0; + PlayerSAO *co = getplayersao(ref); + if (co == NULL) + return 0; // Do it float pitch = co->getRadLookPitchDep(); float yaw = co->getRadYawDep(); - v3f v(std::cos(pitch) * std::cos(yaw), std::sin(pitch), std::cos(pitch) * - std::sin(yaw)); + v3f v(std::cos(pitch) * std::cos(yaw), std::sin(pitch), + std::cos(pitch) * std::sin(yaw)); push_v3f(L, v); return 1; } @@ -1136,12 +1162,13 @@ int ObjectRef::l_get_look_pitch(lua_State *L) { NO_MAP_LOCK_REQUIRED; - log_deprecated(L, - "Deprecated call to get_look_pitch, use get_look_vertical instead"); + log_deprecated(L, "Deprecated call to get_look_pitch, use get_look_vertical " + "instead"); ObjectRef *ref = checkobject(L, 1); - PlayerSAO* co = getplayersao(ref); - if (co == NULL) return 0; + PlayerSAO *co = getplayersao(ref); + if (co == NULL) + return 0; // Do it lua_pushnumber(L, co->getRadLookPitchDep()); return 1; @@ -1153,12 +1180,13 @@ int ObjectRef::l_get_look_yaw(lua_State *L) { NO_MAP_LOCK_REQUIRED; - log_deprecated(L, - "Deprecated call to get_look_yaw, use get_look_horizontal instead"); + log_deprecated(L, "Deprecated call to get_look_yaw, use get_look_horizontal " + "instead"); ObjectRef *ref = checkobject(L, 1); - PlayerSAO* co = getplayersao(ref); - if (co == NULL) return 0; + PlayerSAO *co = getplayersao(ref); + if (co == NULL) + return 0; // Do it lua_pushnumber(L, co->getRadYawDep()); return 1; @@ -1169,8 +1197,9 @@ int ObjectRef::l_get_look_vertical(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - PlayerSAO* co = getplayersao(ref); - if (co == NULL) return 0; + PlayerSAO *co = getplayersao(ref); + if (co == NULL) + return 0; // Do it lua_pushnumber(L, co->getRadLookPitch()); return 1; @@ -1181,8 +1210,9 @@ int ObjectRef::l_get_look_horizontal(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - PlayerSAO* co = getplayersao(ref); - if (co == NULL) return 0; + PlayerSAO *co = getplayersao(ref); + if (co == NULL) + return 0; // Do it lua_pushnumber(L, co->getRadRotation().Y); return 1; @@ -1193,8 +1223,9 @@ int ObjectRef::l_set_look_vertical(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - PlayerSAO* co = getplayersao(ref); - if (co == NULL) return 0; + PlayerSAO *co = getplayersao(ref); + if (co == NULL) + return 0; float pitch = readParam(L, 2) * core::RADTODEG; // Do it co->setLookPitchAndSend(pitch); @@ -1206,8 +1237,9 @@ int ObjectRef::l_set_look_horizontal(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - PlayerSAO* co = getplayersao(ref); - if (co == NULL) return 0; + PlayerSAO *co = getplayersao(ref); + if (co == NULL) + return 0; float yaw = readParam(L, 2) * core::RADTODEG; // Do it co->setPlayerYawAndSend(yaw); @@ -1220,12 +1252,13 @@ int ObjectRef::l_set_look_pitch(lua_State *L) { NO_MAP_LOCK_REQUIRED; - log_deprecated(L, - "Deprecated call to set_look_pitch, use set_look_vertical instead."); + log_deprecated(L, "Deprecated call to set_look_pitch, use set_look_vertical " + "instead."); ObjectRef *ref = checkobject(L, 1); - PlayerSAO* co = getplayersao(ref); - if (co == NULL) return 0; + PlayerSAO *co = getplayersao(ref); + if (co == NULL) + return 0; float pitch = readParam(L, 2) * core::RADTODEG; // Do it co->setLookPitchAndSend(pitch); @@ -1238,12 +1271,13 @@ int ObjectRef::l_set_look_yaw(lua_State *L) { NO_MAP_LOCK_REQUIRED; - log_deprecated(L, - "Deprecated call to set_look_yaw, use set_look_horizontal instead."); + log_deprecated(L, "Deprecated call to set_look_yaw, use set_look_horizontal " + "instead."); ObjectRef *ref = checkobject(L, 1); - PlayerSAO* co = getplayersao(ref); - if (co == NULL) return 0; + PlayerSAO *co = getplayersao(ref); + if (co == NULL) + return 0; float yaw = readParam(L, 2) * core::RADTODEG; // Do it co->setPlayerYawAndSend(yaw); @@ -1259,11 +1293,10 @@ int ObjectRef::l_set_fov(lua_State *L) if (!player) return 0; - player->setFov({ - static_cast(luaL_checknumber(L, 2)), - readParam(L, 3, false), - lua_isnumber(L, 4) ? static_cast(luaL_checknumber(L, 4)) : 0.0f - }); + player->setFov({static_cast(luaL_checknumber(L, 2)), + readParam(L, 3, false), + lua_isnumber(L, 4) ? static_cast(luaL_checknumber(L, 4)) + : 0.0f}); getServer(L)->SendPlayerFov(player->getPeerId()); return 0; @@ -1291,8 +1324,9 @@ int ObjectRef::l_set_breath(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - PlayerSAO* co = getplayersao(ref); - if (co == NULL) return 0; + PlayerSAO *co = getplayersao(ref); + if (co == NULL) + return 0; u16 breath = luaL_checknumber(L, 2); co->setBreath(breath); @@ -1304,22 +1338,23 @@ int ObjectRef::l_get_breath(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - PlayerSAO* co = getplayersao(ref); - if (co == NULL) return 0; + PlayerSAO *co = getplayersao(ref); + if (co == NULL) + return 0; // Do it u16 breath = co->getBreath(); - lua_pushinteger (L, breath); + lua_pushinteger(L, breath); return 1; } // set_attribute(self, attribute, value) int ObjectRef::l_set_attribute(lua_State *L) { - log_deprecated(L, - "Deprecated call to set_attribute, use MetaDataRef methods instead."); + log_deprecated(L, "Deprecated call to set_attribute, use MetaDataRef methods " + "instead."); ObjectRef *ref = checkobject(L, 1); - PlayerSAO* co = getplayersao(ref); + PlayerSAO *co = getplayersao(ref); if (co == NULL) return 0; @@ -1336,11 +1371,11 @@ int ObjectRef::l_set_attribute(lua_State *L) // get_attribute(self, attribute) int ObjectRef::l_get_attribute(lua_State *L) { - log_deprecated(L, - "Deprecated call to get_attribute, use MetaDataRef methods instead."); + log_deprecated(L, "Deprecated call to get_attribute, use MetaDataRef methods " + "instead."); ObjectRef *ref = checkobject(L, 1); - PlayerSAO* co = getplayersao(ref); + PlayerSAO *co = getplayersao(ref); if (co == NULL) return 0; @@ -1355,7 +1390,6 @@ int ObjectRef::l_get_attribute(lua_State *L) return 0; } - // get_meta(self, attribute) int ObjectRef::l_get_meta(lua_State *L) { @@ -1368,14 +1402,14 @@ int ObjectRef::l_get_meta(lua_State *L) return 1; } - // set_inventory_formspec(self, formspec) int ObjectRef::l_set_inventory_formspec(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); RemotePlayer *player = getplayer(ref); - if (player == NULL) return 0; + if (player == NULL) + return 0; std::string formspec = luaL_checkstring(L, 2); player->inventory_formspec = formspec; @@ -1390,7 +1424,8 @@ int ObjectRef::l_get_inventory_formspec(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); RemotePlayer *player = getplayer(ref); - if (player == NULL) return 0; + if (player == NULL) + return 0; std::string formspec = player->inventory_formspec; lua_pushlstring(L, formspec.c_str(), formspec.size()); @@ -1421,7 +1456,7 @@ int ObjectRef::l_get_formspec_prepend(lua_State *L) ObjectRef *ref = checkobject(L, 1); RemotePlayer *player = getplayer(ref); if (player == NULL) - return 0; + return 0; std::string formspec = player->formspec_prepend; lua_pushlstring(L, formspec.c_str(), formspec.size()); @@ -1573,14 +1608,14 @@ int ObjectRef::l_hud_set_flags(lua_State *L) return 0; u32 flags = 0; - u32 mask = 0; + u32 mask = 0; bool flag; const EnumString *esp = es_HudBuiltinElement; for (int i = 0; esp[i].str; i++) { if (getboolfield(L, 2, esp[i].str, flag)) { flags |= esp[i].num * flag; - mask |= esp[i].num; + mask |= esp[i].num; } } if (!getServer(L)->hudSetFlags(player, flags, mask)) @@ -1735,7 +1770,8 @@ int ObjectRef::l_set_sky(lua_State *L) lua_pushnil(L); while (lua_next(L, -2) != 0) { // Key is at index -2 and value at index -1 - skybox_params.textures.emplace_back(readParam(L, -1)); + skybox_params.textures.emplace_back( + readParam(L, -1)); // Removes the value, but keeps the key for iteration lua_pop(L, 1); } @@ -1748,11 +1784,12 @@ int ObjectRef::l_set_sky(lua_State *L) using "regular" or "plain" skybox modes as textures aren't needed. */ - if (skybox_params.textures.size() != 6 && skybox_params.textures.size() > 0) + if (skybox_params.textures.size() != 6 && + skybox_params.textures.size() > 0) throw LuaError("Skybox expects 6 textures!"); - skybox_params.clouds = getboolfield_default(L, 2, - "clouds", skybox_params.clouds); + skybox_params.clouds = getboolfield_default( + L, 2, "clouds", skybox_params.clouds); lua_getfield(L, 2, "sky_color"); if (lua_istable(L, -1)) { @@ -1836,9 +1873,10 @@ int ObjectRef::l_set_sky(lua_State *L) if (lua_istable(L, 4)) { lua_pushnil(L); while (lua_next(L, 4) != 0) { - // Key at index -2, and value at index -1 + // Key at index -2, and value at index -1 if (lua_isstring(L, -1)) - skybox_params.textures.emplace_back(readParam(L, -1)); + skybox_params.textures.emplace_back( + readParam(L, -1)); else skybox_params.textures.emplace_back(""); // Remove the value, keep the key for the next iteration @@ -1878,7 +1916,7 @@ int ObjectRef::l_get_sky(lua_State *L) lua_newtable(L); s16 i = 1; - for (const std::string& texture : skybox_params.textures) { + for (const std::string &texture : skybox_params.textures) { lua_pushlstring(L, texture.c_str(), texture.size()); lua_rawseti(L, -2, i++); } @@ -1896,7 +1934,7 @@ int ObjectRef::l_get_sky_color(lua_State *L) if (!player) return 0; - const SkyboxParams& skybox_params = player->getSkyParams(); + const SkyboxParams &skybox_params = player->getSkyParams(); lua_newtable(L); if (skybox_params.type == "regular") { @@ -1938,25 +1976,20 @@ int ObjectRef::l_set_sun(lua_State *L) SunParams sun_params = player->getSunParams(); - sun_params.visible = getboolfield_default(L, 2, - "visible", sun_params.visible); - sun_params.texture = getstringfield_default(L, 2, - "texture", sun_params.texture); - sun_params.tonemap = getstringfield_default(L, 2, - "tonemap", sun_params.tonemap); - sun_params.sunrise = getstringfield_default(L, 2, - "sunrise", sun_params.sunrise); - sun_params.sunrise_visible = getboolfield_default(L, 2, - "sunrise_visible", sun_params.sunrise_visible); - sun_params.scale = getfloatfield_default(L, 2, - "scale", sun_params.scale); + sun_params.visible = getboolfield_default(L, 2, "visible", sun_params.visible); + sun_params.texture = getstringfield_default(L, 2, "texture", sun_params.texture); + sun_params.tonemap = getstringfield_default(L, 2, "tonemap", sun_params.tonemap); + sun_params.sunrise = getstringfield_default(L, 2, "sunrise", sun_params.sunrise); + sun_params.sunrise_visible = getboolfield_default( + L, 2, "sunrise_visible", sun_params.sunrise_visible); + sun_params.scale = getfloatfield_default(L, 2, "scale", sun_params.scale); getServer(L)->setSun(player, sun_params); lua_pushboolean(L, true); return 1; } -//get_sun(self) +// get_sun(self) int ObjectRef::l_get_sun(lua_State *L) { NO_MAP_LOCK_REQUIRED; @@ -1996,14 +2029,12 @@ int ObjectRef::l_set_moon(lua_State *L) MoonParams moon_params = player->getMoonParams(); - moon_params.visible = getboolfield_default(L, 2, - "visible", moon_params.visible); - moon_params.texture = getstringfield_default(L, 2, - "texture", moon_params.texture); - moon_params.tonemap = getstringfield_default(L, 2, - "tonemap", moon_params.tonemap); - moon_params.scale = getfloatfield_default(L, 2, - "scale", moon_params.scale); + moon_params.visible = getboolfield_default(L, 2, "visible", moon_params.visible); + moon_params.texture = + getstringfield_default(L, 2, "texture", moon_params.texture); + moon_params.tonemap = + getstringfield_default(L, 2, "tonemap", moon_params.tonemap); + moon_params.scale = getfloatfield_default(L, 2, "scale", moon_params.scale); getServer(L)->setMoon(player, moon_params); lua_pushboolean(L, true); @@ -2046,18 +2077,15 @@ int ObjectRef::l_set_stars(lua_State *L) StarParams star_params = player->getStarParams(); - star_params.visible = getboolfield_default(L, 2, - "visible", star_params.visible); - star_params.count = getintfield_default(L, 2, - "count", star_params.count); + star_params.visible = getboolfield_default(L, 2, "visible", star_params.visible); + star_params.count = getintfield_default(L, 2, "count", star_params.count); lua_getfield(L, 2, "star_color"); if (!lua_isnil(L, -1)) read_color(L, -1, &star_params.starcolor); lua_pop(L, 1); - star_params.scale = getfloatfield_default(L, 2, - "scale", star_params.scale); + star_params.scale = getfloatfield_default(L, 2, "scale", star_params.scale); getServer(L)->setStars(player, star_params); lua_pushboolean(L, true); @@ -2100,7 +2128,8 @@ int ObjectRef::l_set_clouds(lua_State *L) CloudParams cloud_params = player->getCloudParams(); - cloud_params.density = getfloatfield_default(L, 2, "density", cloud_params.density); + cloud_params.density = + getfloatfield_default(L, 2, "density", cloud_params.density); lua_getfield(L, 2, "color"); if (!lua_isnil(L, -1)) @@ -2111,8 +2140,9 @@ int ObjectRef::l_set_clouds(lua_State *L) read_color(L, -1, &cloud_params.color_ambient); lua_pop(L, 1); - cloud_params.height = getfloatfield_default(L, 2, "height", cloud_params.height ); - cloud_params.thickness = getfloatfield_default(L, 2, "thickness", cloud_params.thickness); + cloud_params.height = getfloatfield_default(L, 2, "height", cloud_params.height); + cloud_params.thickness = + getfloatfield_default(L, 2, "thickness", cloud_params.thickness); lua_getfield(L, 2, "speed"); if (lua_istable(L, -1)) { @@ -2158,7 +2188,6 @@ int ObjectRef::l_get_clouds(lua_State *L) return 1; } - // override_day_night_ratio(self, brightness=0...1) int ObjectRef::l_override_day_night_ratio(lua_State *L) { @@ -2201,10 +2230,9 @@ int ObjectRef::l_get_day_night_ratio(lua_State *L) return 1; } -ObjectRef::ObjectRef(ServerActiveObject *object): - m_object(object) +ObjectRef::ObjectRef(ServerActiveObject *object) : m_object(object) { - //infostream<<"ObjectRef created for id="<getId()< 1) // deprecated { log_deprecated(L, "Deprecated add_particle call with " - "individual parameters instead of definition"); + "individual parameters instead of definition"); p.pos = check_v3f(L, 1); p.vel = check_v3f(L, 2); p.acc = check_v3f(L, 3); @@ -59,9 +59,7 @@ int ModApiParticles::l_add_particle(lua_State *L) p.texture = luaL_checkstring(L, 7); if (lua_gettop(L) == 8) // only spawn for a single player playername = luaL_checkstring(L, 8); - } - else if (lua_istable(L, 1)) - { + } else if (lua_istable(L, 1)) { lua_getfield(L, 1, "pos"); if (lua_istable(L, -1)) p.pos = check_v3f(L, -1); @@ -71,7 +69,7 @@ int ModApiParticles::l_add_particle(lua_State *L) if (lua_istable(L, -1)) { p.vel = check_v3f(L, -1); log_deprecated(L, "The use of vel is deprecated. " - "Use velocity instead"); + "Use velocity instead"); } lua_pop(L, 1); @@ -84,7 +82,7 @@ int ModApiParticles::l_add_particle(lua_State *L) if (lua_istable(L, -1)) { p.acc = check_v3f(L, -1); log_deprecated(L, "The use of acc is deprecated. " - "Use acceleration instead"); + "Use acceleration instead"); } lua_pop(L, 1); @@ -93,15 +91,15 @@ int ModApiParticles::l_add_particle(lua_State *L) p.acc = check_v3f(L, -1); lua_pop(L, 1); - p.expirationtime = getfloatfield_default(L, 1, "expirationtime", - p.expirationtime); + p.expirationtime = getfloatfield_default( + L, 1, "expirationtime", p.expirationtime); p.size = getfloatfield_default(L, 1, "size", p.size); - p.collisiondetection = getboolfield_default(L, 1, - "collisiondetection", p.collisiondetection); - p.collision_removal = getboolfield_default(L, 1, - "collision_removal", p.collision_removal); - p.object_collision = getboolfield_default(L, 1, - "object_collision", p.object_collision); + p.collisiondetection = getboolfield_default( + L, 1, "collisiondetection", p.collisiondetection); + p.collision_removal = getboolfield_default( + L, 1, "collision_removal", p.collision_removal); + p.object_collision = getboolfield_default( + L, 1, "object_collision", p.object_collision); p.vertical = getboolfield_default(L, 1, "vertical", p.vertical); lua_getfield(L, 1, "animation"); @@ -156,10 +154,10 @@ int ModApiParticles::l_add_particlespawner(lua_State *L) ServerActiveObject *attached = NULL; std::string playername; - if (lua_gettop(L) > 1) //deprecated + if (lua_gettop(L) > 1) // deprecated { log_deprecated(L, "Deprecated add_particlespawner call with " - "individual parameters instead of definition"); + "individual parameters instead of definition"); p.amount = luaL_checknumber(L, 1); p.time = luaL_checknumber(L, 2); p.minpos = check_v3f(L, 3); @@ -176,9 +174,7 @@ int ModApiParticles::l_add_particlespawner(lua_State *L) p.texture = luaL_checkstring(L, 14); if (lua_gettop(L) == 15) // only spawn for a single player playername = luaL_checkstring(L, 15); - } - else if (lua_istable(L, 1)) - { + } else if (lua_istable(L, 1)) { p.amount = getintfield_default(L, 1, "amount", p.amount); p.time = getfloatfield_default(L, 1, "time", p.time); @@ -216,12 +212,12 @@ int ModApiParticles::l_add_particlespawner(lua_State *L) p.maxexptime = getfloatfield_default(L, 1, "maxexptime", p.maxexptime); p.minsize = getfloatfield_default(L, 1, "minsize", p.minsize); p.maxsize = getfloatfield_default(L, 1, "maxsize", p.maxsize); - p.collisiondetection = getboolfield_default(L, 1, - "collisiondetection", p.collisiondetection); - p.collision_removal = getboolfield_default(L, 1, - "collision_removal", p.collision_removal); - p.object_collision = getboolfield_default(L, 1, - "object_collision", p.object_collision); + p.collisiondetection = getboolfield_default( + L, 1, "collisiondetection", p.collisiondetection); + p.collision_removal = getboolfield_default( + L, 1, "collision_removal", p.collision_removal); + p.object_collision = getboolfield_default( + L, 1, "object_collision", p.object_collision); lua_getfield(L, 1, "animation"); p.animation = read_animation_definition(L, -1); @@ -276,4 +272,3 @@ void ModApiParticles::Initialize(lua_State *L, int top) API_FCT(add_particlespawner); API_FCT(delete_particlespawner); } - diff --git a/src/script/lua_api/l_particles.h b/src/script/lua_api/l_particles.h index 122810b6d..8734e5516 100644 --- a/src/script/lua_api/l_particles.h +++ b/src/script/lua_api/l_particles.h @@ -21,7 +21,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "lua_api/l_base.h" -class ModApiParticles : public ModApiBase { +class ModApiParticles : public ModApiBase +{ private: static int l_add_particle(lua_State *L); static int l_add_particlespawner(lua_State *L); diff --git a/src/script/lua_api/l_particles_local.cpp b/src/script/lua_api/l_particles_local.cpp index cc68b13a5..498376d40 100644 --- a/src/script/lua_api/l_particles_local.cpp +++ b/src/script/lua_api/l_particles_local.cpp @@ -49,15 +49,15 @@ int ModApiParticlesLocal::l_add_particle(lua_State *L) p.acc = check_v3f(L, -1); lua_pop(L, 1); - p.expirationtime = getfloatfield_default(L, 1, "expirationtime", - p.expirationtime); + p.expirationtime = + getfloatfield_default(L, 1, "expirationtime", p.expirationtime); p.size = getfloatfield_default(L, 1, "size", p.size); - p.collisiondetection = getboolfield_default(L, 1, - "collisiondetection", p.collisiondetection); - p.collision_removal = getboolfield_default(L, 1, - "collision_removal", p.collision_removal); - p.object_collision = getboolfield_default(L, 1, - "object_collision", p.object_collision); + p.collisiondetection = getboolfield_default( + L, 1, "collisiondetection", p.collisiondetection); + p.collision_removal = getboolfield_default( + L, 1, "collision_removal", p.collision_removal); + p.object_collision = getboolfield_default( + L, 1, "object_collision", p.object_collision); p.vertical = getboolfield_default(L, 1, "vertical", p.vertical); lua_getfield(L, 1, "animation"); @@ -75,7 +75,7 @@ int ModApiParticlesLocal::l_add_particle(lua_State *L) p.node_tile = getintfield_default(L, 1, "node_tile", p.node_tile); ClientEvent *event = new ClientEvent(); - event->type = CE_SPAWN_PARTICLE; + event->type = CE_SPAWN_PARTICLE; event->spawn_particle = new ParticleParameters(p); getClient(L)->pushToEventQueue(event); @@ -126,12 +126,12 @@ int ModApiParticlesLocal::l_add_particlespawner(lua_State *L) p.maxexptime = getfloatfield_default(L, 1, "maxexptime", p.maxexptime); p.minsize = getfloatfield_default(L, 1, "minsize", p.minsize); p.maxsize = getfloatfield_default(L, 1, "maxsize", p.maxsize); - p.collisiondetection = getboolfield_default(L, 1, - "collisiondetection", p.collisiondetection); - p.collision_removal = getboolfield_default(L, 1, - "collision_removal", p.collision_removal); - p.object_collision = getboolfield_default(L, 1, - "object_collision", p.object_collision); + p.collisiondetection = getboolfield_default( + L, 1, "collisiondetection", p.collisiondetection); + p.collision_removal = getboolfield_default( + L, 1, "collision_removal", p.collision_removal); + p.object_collision = getboolfield_default( + L, 1, "object_collision", p.object_collision); lua_getfield(L, 1, "animation"); p.animation = read_animation_definition(L, -1); @@ -151,10 +151,10 @@ int ModApiParticlesLocal::l_add_particlespawner(lua_State *L) u64 id = getClient(L)->getParticleManager()->generateSpawnerId(); auto event = new ClientEvent(); - event->type = CE_ADD_PARTICLESPAWNER; - event->add_particlespawner.p = new ParticleSpawnerParameters(p); + event->type = CE_ADD_PARTICLESPAWNER; + event->add_particlespawner.p = new ParticleSpawnerParameters(p); event->add_particlespawner.attached_id = 0; - event->add_particlespawner.id = id; + event->add_particlespawner.id = id; getClient(L)->pushToEventQueue(event); lua_pushnumber(L, id); @@ -168,7 +168,7 @@ int ModApiParticlesLocal::l_delete_particlespawner(lua_State *L) u32 id = luaL_checknumber(L, 1); ClientEvent *event = new ClientEvent(); - event->type = CE_DELETE_PARTICLESPAWNER; + event->type = CE_DELETE_PARTICLESPAWNER; event->delete_particlespawner.id = id; getClient(L)->pushToEventQueue(event); diff --git a/src/script/lua_api/l_rollback.cpp b/src/script/lua_api/l_rollback.cpp index 482b0cbf5..09da123c3 100644 --- a/src/script/lua_api/l_rollback.cpp +++ b/src/script/lua_api/l_rollback.cpp @@ -23,7 +23,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "server.h" #include "rollback_interface.h" - void push_RollbackNode(lua_State *L, RollbackNode &node) { lua_createtable(L, 0, 3); @@ -35,14 +34,15 @@ void push_RollbackNode(lua_State *L, RollbackNode &node) lua_setfield(L, -2, "param2"); } -// rollback_get_node_actions(pos, range, seconds, limit) -> {{actor, pos, time, oldnode, newnode}, ...} +// rollback_get_node_actions(pos, range, seconds, limit) -> {{actor, pos, time, oldnode, +// newnode}, ...} int ModApiRollback::l_rollback_get_node_actions(lua_State *L) { NO_MAP_LOCK_REQUIRED; v3s16 pos = read_v3s16(L, 1); int range = luaL_checknumber(L, 2); - time_t seconds = (time_t) luaL_checknumber(L, 3); + time_t seconds = (time_t)luaL_checknumber(L, 3); int limit = luaL_checknumber(L, 4); Server *server = getServer(L); IRollbackManager *rollback = server->getRollbackManager(); @@ -50,7 +50,8 @@ int ModApiRollback::l_rollback_get_node_actions(lua_State *L) return 0; } - std::list actions = rollback->getNodeActors(pos, range, seconds, limit); + std::list actions = + rollback->getNodeActors(pos, range, seconds, limit); std::list::iterator iter = actions.begin(); lua_createtable(L, actions.size(), 0); @@ -101,8 +102,8 @@ int ModApiRollback::l_rollback_revert_actions_by(lua_State *L) lua_pushboolean(L, success); lua_createtable(L, log.size(), 0); unsigned long i = 0; - for(std::list::const_iterator iter = log.begin(); - iter != log.end(); ++i, ++iter) { + for (std::list::const_iterator iter = log.begin(); iter != log.end(); + ++i, ++iter) { lua_pushnumber(L, i); lua_pushstring(L, iter->c_str()); lua_settable(L, -3); diff --git a/src/script/lua_api/l_rollback.h b/src/script/lua_api/l_rollback.h index c26ff634e..74d8ce10e 100644 --- a/src/script/lua_api/l_rollback.h +++ b/src/script/lua_api/l_rollback.h @@ -24,7 +24,8 @@ with this program; if not, write to the Free Software Foundation, Inc., class ModApiRollback : public ModApiBase { private: - // rollback_get_node_actions(pos, range, seconds) -> {{actor, pos, time, oldnode, newnode}, ...} + // rollback_get_node_actions(pos, range, seconds) -> {{actor, pos, time, oldnode, + // newnode}, ...} static int l_rollback_get_node_actions(lua_State *L); // rollback_revert_actions_by(actor, seconds) -> bool, log messages diff --git a/src/script/lua_api/l_server.cpp b/src/script/lua_api/l_server.cpp index 64ae924d2..f4362dd7f 100644 --- a/src/script/lua_api/l_server.cpp +++ b/src/script/lua_api/l_server.cpp @@ -56,7 +56,6 @@ int ModApiServer::l_get_server_uptime(lua_State *L) return 1; } - // print(text) int ModApiServer::l_print(lua_State *L) { @@ -116,15 +115,14 @@ int ModApiServer::l_get_player_privs(lua_State *L) int ModApiServer::l_get_player_ip(lua_State *L) { NO_MAP_LOCK_REQUIRED; - const char * name = luaL_checkstring(L, 1); - RemotePlayer *player = dynamic_cast(getEnv(L))->getPlayer(name); - if(player == NULL) - { + const char *name = luaL_checkstring(L, 1); + RemotePlayer *player = + dynamic_cast(getEnv(L))->getPlayer(name); + if (player == NULL) { lua_pushnil(L); // no such player return 1; } - try - { + try { Address addr = getServer(L)->getPeerAddress(player->getPeerId()); std::string ip_str = addr.serializeString(); lua_pushstring(L, ip_str.c_str()); @@ -166,21 +164,19 @@ int ModApiServer::l_get_player_information(lua_State *L) u8 ser_vers, major, minor, patch; std::string vers_string, lang_code; - auto getConInfo = [&] (con::rtt_stat_type type, float *value) -> bool { + auto getConInfo = [&](con::rtt_stat_type type, float *value) -> bool { return server->getClientConInfo(player->getPeerId(), type, value); }; - bool have_con_info = - getConInfo(con::MIN_RTT, &min_rtt) && - getConInfo(con::MAX_RTT, &max_rtt) && - getConInfo(con::AVG_RTT, &avg_rtt) && - getConInfo(con::MIN_JITTER, &min_jitter) && - getConInfo(con::MAX_JITTER, &max_jitter) && - getConInfo(con::AVG_JITTER, &avg_jitter); + bool have_con_info = getConInfo(con::MIN_RTT, &min_rtt) && + getConInfo(con::MAX_RTT, &max_rtt) && + getConInfo(con::AVG_RTT, &avg_rtt) && + getConInfo(con::MIN_JITTER, &min_jitter) && + getConInfo(con::MAX_JITTER, &max_jitter) && + getConInfo(con::AVG_JITTER, &avg_jitter); - bool r = server->getClientInfo(player->getPeerId(), &state, &uptime, - &ser_vers, &prot_vers, &major, &minor, &patch, &vers_string, - &lang_code); + bool r = server->getClientInfo(player->getPeerId(), &state, &uptime, &ser_vers, + &prot_vers, &major, &minor, &patch, &vers_string, &lang_code); if (!r) { dstream << FUNCTION_NAME << ": peer was not found" << std::endl; lua_pushnil(L); // error @@ -190,11 +186,11 @@ int ModApiServer::l_get_player_information(lua_State *L) lua_newtable(L); int table = lua_gettop(L); - lua_pushstring(L,"address"); + lua_pushstring(L, "address"); lua_pushstring(L, addr.serializeString().c_str()); lua_settable(L, table); - lua_pushstring(L,"ip_version"); + lua_pushstring(L, "ip_version"); if (addr.getFamily() == AF_INET) { lua_pushnumber(L, 4); } else if (addr.getFamily() == AF_INET6) { @@ -230,11 +226,11 @@ int ModApiServer::l_get_player_information(lua_State *L) lua_settable(L, table); } - lua_pushstring(L,"connection_uptime"); + lua_pushstring(L, "connection_uptime"); lua_pushnumber(L, uptime); lua_settable(L, table); - lua_pushstring(L,"protocol_version"); + lua_pushstring(L, "protocol_version"); lua_pushnumber(L, prot_vers); lua_settable(L, table); @@ -247,28 +243,28 @@ int ModApiServer::l_get_player_information(lua_State *L) lua_settable(L, table); #ifndef NDEBUG - lua_pushstring(L,"serialization_version"); + lua_pushstring(L, "serialization_version"); lua_pushnumber(L, ser_vers); lua_settable(L, table); - lua_pushstring(L,"major"); + lua_pushstring(L, "major"); lua_pushnumber(L, major); lua_settable(L, table); - lua_pushstring(L,"minor"); + lua_pushstring(L, "minor"); lua_pushnumber(L, minor); lua_settable(L, table); - lua_pushstring(L,"patch"); + lua_pushstring(L, "patch"); lua_pushnumber(L, patch); lua_settable(L, table); - lua_pushstring(L,"version_string"); + lua_pushstring(L, "version_string"); lua_pushstring(L, vers_string.c_str()); lua_settable(L, table); - lua_pushstring(L,"state"); - lua_pushstring(L,ClientInterface::state2Name(state).c_str()); + lua_pushstring(L, "state"); + lua_pushstring(L, ClientInterface::state2Name(state).c_str()); lua_settable(L, table); #endif @@ -287,8 +283,9 @@ int ModApiServer::l_get_ban_list(lua_State *L) int ModApiServer::l_get_ban_description(lua_State *L) { NO_MAP_LOCK_REQUIRED; - const char * ip_or_name = luaL_checkstring(L, 1); - lua_pushstring(L, getServer(L)->getBanDescription(std::string(ip_or_name)).c_str()); + const char *ip_or_name = luaL_checkstring(L, 1); + lua_pushstring(L, + getServer(L)->getBanDescription(std::string(ip_or_name)).c_str()); return 1; } @@ -296,19 +293,21 @@ int ModApiServer::l_get_ban_description(lua_State *L) int ModApiServer::l_ban_player(lua_State *L) { NO_MAP_LOCK_REQUIRED; - const char * name = luaL_checkstring(L, 1); - RemotePlayer *player = dynamic_cast(getEnv(L))->getPlayer(name); + const char *name = luaL_checkstring(L, 1); + RemotePlayer *player = + dynamic_cast(getEnv(L))->getPlayer(name); if (player == NULL) { lua_pushboolean(L, false); // no such player return 1; } - try - { + try { Address addr = getServer(L)->getPeerAddress( - dynamic_cast(getEnv(L))->getPlayer(name)->getPeerId()); + dynamic_cast(getEnv(L)) + ->getPlayer(name) + ->getPeerId()); std::string ip_str = addr.serializeString(); getServer(L)->setIpBanned(ip_str, name); - } catch(const con::PeerNotFoundException &) { + } catch (const con::PeerNotFoundException &) { dstream << FUNCTION_NAME << ": peer was not found" << std::endl; lua_pushboolean(L, false); // error return 1; @@ -328,7 +327,8 @@ int ModApiServer::l_kick_player(lua_State *L) else message.append("."); - RemotePlayer *player = dynamic_cast(getEnv(L))->getPlayer(name); + RemotePlayer *player = + dynamic_cast(getEnv(L))->getPlayer(name); if (player == NULL) { lua_pushboolean(L, false); // No such player return 1; @@ -359,7 +359,7 @@ int ModApiServer::l_remove_player(lua_State *L) int ModApiServer::l_unban_player_or_ip(lua_State *L) { NO_MAP_LOCK_REQUIRED; - const char * ip_or_name = luaL_checkstring(L, 1); + const char *ip_or_name = luaL_checkstring(L, 1); getServer(L)->unsetIpBanned(ip_or_name); lua_pushboolean(L, true); return 1; @@ -373,10 +373,9 @@ int ModApiServer::l_show_formspec(lua_State *L) const char *formname = luaL_checkstring(L, 2); const char *formspec = luaL_checkstring(L, 3); - if(getServer(L)->showFormspec(playername,formspec,formname)) - { + if (getServer(L)->showFormspec(playername, formspec, formname)) { lua_pushboolean(L, true); - }else{ + } else { lua_pushboolean(L, false); } return 1; @@ -480,7 +479,8 @@ int ModApiServer::l_dynamic_add_media(lua_State *L) // Reject adding media before the server has started up if (!getEnv(L)) - throw LuaError("Dynamic media cannot be added before server has started up"); + throw LuaError("Dynamic media cannot be added before server has started " + "up"); std::string filepath = readParam(L, 1); CHECK_SECURE_PATH(L, filepath.c_str(), false); @@ -503,7 +503,7 @@ int ModApiServer::l_notify_authentication_modified(lua_State *L) { NO_MAP_LOCK_REQUIRED; std::string name; - if(lua_isstring(L, 1)) + if (lua_isstring(L, 1)) name = readParam(L, 1); getServer(L)->reportPrivsModified(name); return 0; @@ -529,7 +529,7 @@ int ModApiServer::l_set_last_run_mod(lua_State *L) #ifdef SCRIPTAPI_DEBUG const char *mod = lua_tostring(L, 1); getScriptApiBase(L)->setOriginDirect(mod); - //printf(">>>> last mod set from Lua: %s\n", mod); + // printf(">>>> last mod set from Lua: %s\n", mod); #endif return 0; } diff --git a/src/script/lua_api/l_settings.cpp b/src/script/lua_api/l_settings.cpp index 33eb02392..cc4b23ee4 100644 --- a/src/script/lua_api/l_settings.cpp +++ b/src/script/lua_api/l_settings.cpp @@ -25,23 +25,20 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "noise.h" #include "log.h" - -#define SET_SECURITY_CHECK(L, name) \ - if (o->m_settings == g_settings && ScriptApiSecurity::isSecure(L) && \ - name.compare(0, 7, "secure.") == 0) { \ - throw LuaError("Attempt to set secure setting."); \ +#define SET_SECURITY_CHECK(L, name) \ + if (o->m_settings == g_settings && ScriptApiSecurity::isSecure(L) && \ + name.compare(0, 7, "secure.") == 0) { \ + throw LuaError("Attempt to set secure setting."); \ } LuaSettings::LuaSettings(Settings *settings, const std::string &filename) : - m_settings(settings), - m_filename(filename) + m_settings(settings), m_filename(filename) { } LuaSettings::LuaSettings(const std::string &filename, bool write_allowed) : - m_filename(filename), - m_is_own_settings(true), - m_write_allowed(write_allowed) + m_filename(filename), m_is_own_settings(true), + m_write_allowed(write_allowed) { m_settings = new Settings(); m_settings->readConfigFile(filename.c_str()); @@ -53,9 +50,7 @@ LuaSettings::~LuaSettings() delete m_settings; } - -void LuaSettings::create(lua_State *L, Settings *settings, - const std::string &filename) +void LuaSettings::create(lua_State *L, Settings *settings, const std::string &filename) { LuaSettings *o = new LuaSettings(settings, filename); *(void **)(lua_newuserdata(L, sizeof(void *))) = o; @@ -63,21 +58,19 @@ void LuaSettings::create(lua_State *L, Settings *settings, lua_setmetatable(L, -2); } - // garbage collector -int LuaSettings::gc_object(lua_State* L) +int LuaSettings::gc_object(lua_State *L) { - LuaSettings* o = *(LuaSettings **)(lua_touserdata(L, 1)); + LuaSettings *o = *(LuaSettings **)(lua_touserdata(L, 1)); delete o; return 0; } - // get(self, key) -> value -int LuaSettings::l_get(lua_State* L) +int LuaSettings::l_get(lua_State *L) { NO_MAP_LOCK_REQUIRED; - LuaSettings* o = checkobject(L, 1); + LuaSettings *o = checkobject(L, 1); std::string key = std::string(luaL_checkstring(L, 2)); if (o->m_settings->exists(key)) { @@ -91,10 +84,10 @@ int LuaSettings::l_get(lua_State* L) } // get_bool(self, key) -> boolean -int LuaSettings::l_get_bool(lua_State* L) +int LuaSettings::l_get_bool(lua_State *L) { NO_MAP_LOCK_REQUIRED; - LuaSettings* o = checkobject(L, 1); + LuaSettings *o = checkobject(L, 1); std::string key = std::string(luaL_checkstring(L, 2)); if (o->m_settings->exists(key)) { @@ -153,13 +146,13 @@ int LuaSettings::l_get_flags(lua_State *L) } // set(self, key, value) -int LuaSettings::l_set(lua_State* L) +int LuaSettings::l_set(lua_State *L) { NO_MAP_LOCK_REQUIRED; - LuaSettings* o = checkobject(L, 1); + LuaSettings *o = checkobject(L, 1); std::string key = std::string(luaL_checkstring(L, 2)); - const char* value = luaL_checkstring(L, 3); + const char *value = luaL_checkstring(L, 3); SET_SECURITY_CHECK(L, key); @@ -170,10 +163,10 @@ int LuaSettings::l_set(lua_State* L) } // set_bool(self, key, value) -int LuaSettings::l_set_bool(lua_State* L) +int LuaSettings::l_set_bool(lua_State *L) { NO_MAP_LOCK_REQUIRED; - LuaSettings* o = checkobject(L, 1); + LuaSettings *o = checkobject(L, 1); std::string key = std::string(luaL_checkstring(L, 2)); bool value = readParam(L, 3); @@ -203,10 +196,10 @@ int LuaSettings::l_set_np_group(lua_State *L) } // remove(self, key) -> success -int LuaSettings::l_remove(lua_State* L) +int LuaSettings::l_remove(lua_State *L) { NO_MAP_LOCK_REQUIRED; - LuaSettings* o = checkobject(L, 1); + LuaSettings *o = checkobject(L, 1); std::string key = std::string(luaL_checkstring(L, 2)); @@ -219,16 +212,15 @@ int LuaSettings::l_remove(lua_State* L) } // get_names(self) -> {key1, ...} -int LuaSettings::l_get_names(lua_State* L) +int LuaSettings::l_get_names(lua_State *L) { NO_MAP_LOCK_REQUIRED; - LuaSettings* o = checkobject(L, 1); + LuaSettings *o = checkobject(L, 1); std::vector keys = o->m_settings->getNames(); lua_newtable(L); - for (unsigned int i=0; i < keys.size(); i++) - { + for (unsigned int i = 0; i < keys.size(); i++) { lua_pushstring(L, keys[i].c_str()); lua_rawseti(L, -2, i + 1); } @@ -237,10 +229,10 @@ int LuaSettings::l_get_names(lua_State* L) } // write(self) -> success -int LuaSettings::l_write(lua_State* L) +int LuaSettings::l_write(lua_State *L) { NO_MAP_LOCK_REQUIRED; - LuaSettings* o = checkobject(L, 1); + LuaSettings *o = checkobject(L, 1); if (!o->m_write_allowed) { throw LuaError("Settings: writing " + o->m_filename + @@ -254,10 +246,10 @@ int LuaSettings::l_write(lua_State* L) } // to_table(self) -> {[key1]=value1,...} -int LuaSettings::l_to_table(lua_State* L) +int LuaSettings::l_to_table(lua_State *L) { NO_MAP_LOCK_REQUIRED; - LuaSettings* o = checkobject(L, 1); + LuaSettings *o = checkobject(L, 1); std::vector keys = o->m_settings->getNames(); @@ -270,8 +262,7 @@ int LuaSettings::l_to_table(lua_State* L) return 1; } - -void LuaSettings::Register(lua_State* L) +void LuaSettings::Register(lua_State *L) { lua_newtable(L); int methodtable = lua_gettop(L); @@ -280,7 +271,7 @@ void LuaSettings::Register(lua_State* L) lua_pushliteral(L, "__metatable"); lua_pushvalue(L, methodtable); - lua_settable(L, metatable); // hide metatable from Lua getmetatable() + lua_settable(L, metatable); // hide metatable from Lua getmetatable() lua_pushliteral(L, "__index"); lua_pushvalue(L, methodtable); @@ -290,10 +281,10 @@ void LuaSettings::Register(lua_State* L) lua_pushcfunction(L, gc_object); lua_settable(L, metatable); - lua_pop(L, 1); // drop metatable + lua_pop(L, 1); // drop metatable - luaL_openlib(L, 0, methods, 0); // fill methodtable - lua_pop(L, 1); // drop methodtable + luaL_openlib(L, 0, methods, 0); // fill methodtable + lua_pop(L, 1); // drop methodtable // Can be created from Lua (Settings(filename)) lua_register(L, className, create_object); @@ -301,41 +292,33 @@ void LuaSettings::Register(lua_State* L) // LuaSettings(filename) // Creates a LuaSettings and leaves it on top of the stack -int LuaSettings::create_object(lua_State* L) +int LuaSettings::create_object(lua_State *L) { NO_MAP_LOCK_REQUIRED; bool write_allowed = true; - const char* filename = luaL_checkstring(L, 1); + const char *filename = luaL_checkstring(L, 1); CHECK_SECURE_PATH_POSSIBLE_WRITE(L, filename, &write_allowed); - LuaSettings* o = new LuaSettings(filename, write_allowed); + LuaSettings *o = new LuaSettings(filename, write_allowed); *(void **)(lua_newuserdata(L, sizeof(void *))) = o; luaL_getmetatable(L, className); lua_setmetatable(L, -2); return 1; } -LuaSettings* LuaSettings::checkobject(lua_State* L, int narg) +LuaSettings *LuaSettings::checkobject(lua_State *L, int narg) { NO_MAP_LOCK_REQUIRED; luaL_checktype(L, narg, LUA_TUSERDATA); void *ud = luaL_checkudata(L, narg, className); if (!ud) luaL_typerror(L, narg, className); - return *(LuaSettings**) ud; // unbox pointer + return *(LuaSettings **)ud; // unbox pointer } const char LuaSettings::className[] = "Settings"; -const luaL_Reg LuaSettings::methods[] = { - luamethod(LuaSettings, get), - luamethod(LuaSettings, get_bool), - luamethod(LuaSettings, get_np_group), - luamethod(LuaSettings, get_flags), - luamethod(LuaSettings, set), - luamethod(LuaSettings, set_bool), - luamethod(LuaSettings, set_np_group), - luamethod(LuaSettings, remove), - luamethod(LuaSettings, get_names), - luamethod(LuaSettings, write), - luamethod(LuaSettings, to_table), - {0,0} -}; +const luaL_Reg LuaSettings::methods[] = {luamethod(LuaSettings, get), + luamethod(LuaSettings, get_bool), luamethod(LuaSettings, get_np_group), + luamethod(LuaSettings, get_flags), luamethod(LuaSettings, set), + luamethod(LuaSettings, set_bool), luamethod(LuaSettings, set_np_group), + luamethod(LuaSettings, remove), luamethod(LuaSettings, get_names), + luamethod(LuaSettings, write), luamethod(LuaSettings, to_table), {0, 0}}; diff --git a/src/script/lua_api/l_sound.cpp b/src/script/lua_api/l_sound.cpp index b86eda53e..bd9ec2543 100644 --- a/src/script/lua_api/l_sound.cpp +++ b/src/script/lua_api/l_sound.cpp @@ -23,7 +23,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "common/c_content.h" #include "gui/guiEngine.h" - int ModApiSound::l_sound_play(lua_State *L) { SimpleSoundSpec spec; diff --git a/src/script/lua_api/l_storage.cpp b/src/script/lua_api/l_storage.cpp index cba34fb63..fe3b4d74c 100644 --- a/src/script/lua_api/l_storage.cpp +++ b/src/script/lua_api/l_storage.cpp @@ -53,8 +53,7 @@ void ModApiStorage::Initialize(lua_State *L, int top) API_FCT(get_mod_storage); } -StorageRef::StorageRef(ModMetadata *object): - m_object(object) +StorageRef::StorageRef(ModMetadata *object) : m_object(object) { } @@ -90,7 +89,7 @@ void StorageRef::Register(lua_State *L) lua_pushliteral(L, "__metatable"); lua_pushvalue(L, methodtable); - lua_settable(L, metatable); // hide metatable from Lua getmetatable() + lua_settable(L, metatable); // hide metatable from Lua getmetatable() lua_pushliteral(L, "metadata_class"); lua_pushlstring(L, className, strlen(className)); @@ -108,27 +107,28 @@ void StorageRef::Register(lua_State *L) lua_pushcfunction(L, l_equals); lua_settable(L, metatable); - lua_pop(L, 1); // drop metatable + lua_pop(L, 1); // drop metatable - luaL_openlib(L, 0, methods, 0); // fill methodtable - lua_pop(L, 1); // drop methodtable + luaL_openlib(L, 0, methods, 0); // fill methodtable + lua_pop(L, 1); // drop methodtable } -StorageRef* StorageRef::checkobject(lua_State *L, int narg) +StorageRef *StorageRef::checkobject(lua_State *L, int narg) { luaL_checktype(L, narg, LUA_TUSERDATA); void *ud = luaL_checkudata(L, narg, className); - if (!ud) luaL_typerror(L, narg, className); - return *(StorageRef**)ud; // unbox pointer + if (!ud) + luaL_typerror(L, narg, className); + return *(StorageRef **)ud; // unbox pointer } -ModMetadata* StorageRef::getobject(StorageRef *ref) +ModMetadata *StorageRef::getobject(StorageRef *ref) { ModMetadata *co = ref->m_object; return co; } -Metadata* StorageRef::getmeta(bool auto_create) +Metadata *StorageRef::getmeta(bool auto_create) { return m_object; } @@ -139,17 +139,10 @@ void StorageRef::clearMeta() } const char StorageRef::className[] = "StorageRef"; -const luaL_Reg StorageRef::methods[] = { - luamethod(MetaDataRef, contains), - luamethod(MetaDataRef, get), - luamethod(MetaDataRef, get_string), - luamethod(MetaDataRef, set_string), - luamethod(MetaDataRef, get_int), - luamethod(MetaDataRef, set_int), - luamethod(MetaDataRef, get_float), - luamethod(MetaDataRef, set_float), - luamethod(MetaDataRef, to_table), - luamethod(MetaDataRef, from_table), - luamethod(MetaDataRef, equals), - {0,0} -}; +const luaL_Reg StorageRef::methods[] = {luamethod(MetaDataRef, contains), + luamethod(MetaDataRef, get), luamethod(MetaDataRef, get_string), + luamethod(MetaDataRef, set_string), luamethod(MetaDataRef, get_int), + luamethod(MetaDataRef, set_int), luamethod(MetaDataRef, get_float), + luamethod(MetaDataRef, set_float), luamethod(MetaDataRef, to_table), + luamethod(MetaDataRef, from_table), luamethod(MetaDataRef, equals), + {0, 0}}; diff --git a/src/script/lua_api/l_util.cpp b/src/script/lua_api/l_util.cpp index 4595dc1c1..b77e26886 100644 --- a/src/script/lua_api/l_util.cpp +++ b/src/script/lua_api/l_util.cpp @@ -41,7 +41,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/sha1.h" #include - // log([level,] text) // Writes a line to the logger. // The one-argument version logs to LL_NONE. @@ -65,7 +64,7 @@ int ModApiUtil::l_log(lua_State *L) level = Logger::stringToLevel(name); if (level == LL_MAX) { warningstream << "Tried to log at unknown level '" << name - << "'. Defaulting to \"none\"." << std::endl; + << "'. Defaulting to \"none\"." << std::endl; level = LL_NONE; } } @@ -109,8 +108,10 @@ int ModApiUtil::l_parse_json(lua_State *L) size_t jlen = strlen(jsonstr); if (jlen > 100) { errorstream << "Data (" << jlen - << " bytes) printed to warningstream." << std::endl; - warningstream << "data: \"" << jsonstr << "\"" << std::endl; + << " bytes) printed to warningstream." + << std::endl; + warningstream << "data: \"" << jsonstr << "\"" + << std::endl; } else { errorstream << "data: \"" << jsonstr << "\"" << std::endl; } @@ -121,7 +122,7 @@ int ModApiUtil::l_parse_json(lua_State *L) if (!push_json_value(L, root, nullindex)) { errorstream << "Failed to parse json data, " - << "depth exceeds lua stack limit" << std::endl; + << "depth exceeds lua stack limit" << std::endl; errorstream << "data: \"" << jsonstr << "\"" << std::endl; lua_pushnil(L); } @@ -176,7 +177,7 @@ int ModApiUtil::l_get_hit_params(lua_State *L) std::unordered_map groups; read_groups(L, 1, groups); ToolCapabilities tp = read_tool_capabilities(L, 2); - if(lua_isnoneornil(L, 3)) + if (lua_isnoneornil(L, 3)) push_hit_params(L, getHitParams(groups, &tp)); else push_hit_params(L, getHitParams(groups, &tp, readParam(L, 3))); @@ -228,9 +229,9 @@ int ModApiUtil::l_is_yes(lua_State *L) { NO_MAP_LOCK_REQUIRED; - lua_getglobal(L, "tostring"); // function to be called - lua_pushvalue(L, 1); // 1st argument - lua_call(L, 1, 1); // execute function + lua_getglobal(L, "tostring"); // function to be called + lua_pushvalue(L, 1); // 1st argument + lua_call(L, 1, 1); // execute function std::string str = readParam(L, -1); // get result lua_pop(L, 1); @@ -345,7 +346,7 @@ int ModApiUtil::l_get_dir_list(lua_State *L) { NO_MAP_LOCK_REQUIRED; const char *path = luaL_checkstring(L, 1); - bool list_all = !lua_isboolean(L, 2); // if its not a boolean list all + bool list_all = !lua_isboolean(L, 2); // if its not a boolean list all bool list_dirs = readParam(L, 2); // true: list dirs, false: list files CHECK_SECURE_PATH(L, path, false); @@ -416,12 +417,11 @@ int ModApiUtil::l_request_insecure_environment(lua_State *L) // Check secure.trusted_mods std::string mod_name = readParam(L, -1); std::string trusted_mods = g_settings->get("secure.trusted_mods"); - trusted_mods.erase(std::remove_if(trusted_mods.begin(), - trusted_mods.end(), static_cast(&std::isspace)), + trusted_mods.erase(std::remove_if(trusted_mods.begin(), trusted_mods.end(), + static_cast(&std::isspace)), trusted_mods.end()); std::vector mod_list = str_split(trusted_mods, ','); - if (std::find(mod_list.begin(), mod_list.end(), mod_name) == - mod_list.end()) { + if (std::find(mod_list.begin(), mod_list.end(), mod_name) == mod_list.end()) { return 0; } @@ -463,7 +463,7 @@ int ModApiUtil::l_sha1(lua_State *L) SHA1 ctx; ctx.addBytes(data, size); unsigned char *data_tmpdigest = ctx.getDigest(); - data_sha1.assign((char*) data_tmpdigest, 20); + data_sha1.assign((char *)data_tmpdigest, 20); free(data_tmpdigest); } @@ -527,7 +527,7 @@ void ModApiUtil::InitializeClient(lua_State *L, int top) API_FCT(is_yes); API_FCT(is_nan); - + API_FCT(compress); API_FCT(decompress); @@ -538,7 +538,7 @@ void ModApiUtil::InitializeClient(lua_State *L, int top) API_FCT(get_version); API_FCT(sha1); - + LuaSettings::create(L, g_settings, g_settings_path); lua_setfield(L, top, "settings"); } @@ -571,4 +571,3 @@ void ModApiUtil::InitializeAsync(lua_State *L, int top) LuaSettings::create(L, g_settings, g_settings_path); lua_setfield(L, top, "settings"); } - diff --git a/src/script/lua_api/l_vmanip.cpp b/src/script/lua_api/l_vmanip.cpp index b99b1d98c..17fb830f9 100644 --- a/src/script/lua_api/l_vmanip.cpp +++ b/src/script/lua_api/l_vmanip.cpp @@ -17,7 +17,6 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ - #include "lua_api/l_vmanip.h" #include "lua_api/l_internal.h" #include "common/c_content.h" @@ -63,7 +62,7 @@ int LuaVoxelManip::l_get_data(lua_State *L) NO_MAP_LOCK_REQUIRED; LuaVoxelManip *o = checkobject(L, 1); - bool use_buffer = lua_istable(L, 2); + bool use_buffer = lua_istable(L, 2); MMVManip *vm = o->vm; @@ -117,8 +116,7 @@ int LuaVoxelManip::l_write_to_map(lua_State *L) if (o->is_mapgen_vm || !update_light) { o->vm->blitBackAll(&(o->modified_blocks)); } else { - voxalgo::blit_back_with_light(map, o->vm, - &(o->modified_blocks)); + voxalgo::blit_back_with_light(map, o->vm, &(o->modified_blocks)); } MapEditEvent event; @@ -139,7 +137,7 @@ int LuaVoxelManip::l_get_node_at(lua_State *L) const NodeDefManager *ndef = getServer(L)->getNodeDefManager(); LuaVoxelManip *o = checkobject(L, 1); - v3s16 pos = check_v3s16(L, 2); + v3s16 pos = check_v3s16(L, 2); pushnode(L, o->vm->getNodeNoExNoEmerge(pos), ndef); return 1; @@ -152,8 +150,8 @@ int LuaVoxelManip::l_set_node_at(lua_State *L) const NodeDefManager *ndef = getServer(L)->getNodeDefManager(); LuaVoxelManip *o = checkobject(L, 1); - v3s16 pos = check_v3s16(L, 2); - MapNode n = readnode(L, 3, ndef); + v3s16 pos = check_v3s16(L, 2); + MapNode n = readnode(L, 3, ndef); o->vm->setNodeNoEmerge(pos, n); @@ -171,11 +169,11 @@ int LuaVoxelManip::l_update_liquids(lua_State *L) MMVManip *vm = o->vm; Mapgen mg; - mg.vm = vm; + mg.vm = vm; mg.ndef = ndef; - mg.updateLiquid(&map->m_transforming_liquid, - vm->m_area.MinEdge, vm->m_area.MaxEdge); + mg.updateLiquid(&map->m_transforming_liquid, vm->m_area.MinEdge, + vm->m_area.MaxEdge); return 0; } @@ -187,7 +185,8 @@ int LuaVoxelManip::l_calc_lighting(lua_State *L) LuaVoxelManip *o = checkobject(L, 1); if (!o->is_mapgen_vm) { warningstream << "VoxelManip:calc_lighting called for a non-mapgen " - "VoxelManip object" << std::endl; + "VoxelManip object" + << std::endl; return 0; } @@ -196,10 +195,10 @@ int LuaVoxelManip::l_calc_lighting(lua_State *L) MMVManip *vm = o->vm; v3s16 yblock = v3s16(0, 1, 0) * MAP_BLOCKSIZE; - v3s16 fpmin = vm->m_area.MinEdge; - v3s16 fpmax = vm->m_area.MaxEdge; - v3s16 pmin = lua_istable(L, 2) ? check_v3s16(L, 2) : fpmin + yblock; - v3s16 pmax = lua_istable(L, 3) ? check_v3s16(L, 3) : fpmax - yblock; + v3s16 fpmin = vm->m_area.MinEdge; + v3s16 fpmax = vm->m_area.MaxEdge; + v3s16 pmin = lua_istable(L, 2) ? check_v3s16(L, 2) : fpmin + yblock; + v3s16 pmax = lua_istable(L, 3) ? check_v3s16(L, 3) : fpmax - yblock; bool propagate_shadow = !lua_isboolean(L, 4) || readParam(L, 4); sortBoxVerticies(pmin, pmax); @@ -207,8 +206,8 @@ int LuaVoxelManip::l_calc_lighting(lua_State *L) throw LuaError("Specified voxel area out of VoxelManipulator bounds"); Mapgen mg; - mg.vm = vm; - mg.ndef = ndef; + mg.vm = vm; + mg.ndef = ndef; mg.water_level = emerge->mgparams->water_level; mg.calcLighting(pmin, pmax, fpmin, fpmax, propagate_shadow); @@ -223,7 +222,8 @@ int LuaVoxelManip::l_set_lighting(lua_State *L) LuaVoxelManip *o = checkobject(L, 1); if (!o->is_mapgen_vm) { warningstream << "VoxelManip:set_lighting called for a non-mapgen " - "VoxelManip object" << std::endl; + "VoxelManip object" + << std::endl; return 0; } @@ -231,7 +231,7 @@ int LuaVoxelManip::l_set_lighting(lua_State *L) throw LuaError("VoxelManip:set_lighting called with missing parameter"); u8 light; - light = (getintfield_default(L, 2, "day", 0) & 0x0F); + light = (getintfield_default(L, 2, "day", 0) & 0x0F); light |= (getintfield_default(L, 2, "night", 0) & 0x0F) << 4; MMVManip *vm = o->vm; @@ -280,7 +280,7 @@ int LuaVoxelManip::l_set_light_data(lua_State *L) if (!lua_istable(L, 2)) throw LuaError("VoxelManip:set_light_data called with missing " - "parameter"); + "parameter"); u32 volume = vm->m_area.getVolume(); for (u32 i = 0; i != volume; i++) { @@ -300,7 +300,7 @@ int LuaVoxelManip::l_get_param2_data(lua_State *L) NO_MAP_LOCK_REQUIRED; LuaVoxelManip *o = checkobject(L, 1); - bool use_buffer = lua_istable(L, 2); + bool use_buffer = lua_istable(L, 2); MMVManip *vm = o->vm; @@ -329,7 +329,7 @@ int LuaVoxelManip::l_set_param2_data(lua_State *L) if (!lua_istable(L, 2)) throw LuaError("VoxelManip:set_param2_data called with missing " - "parameter"); + "parameter"); u32 volume = vm->m_area.getVolume(); for (u32 i = 0; i != volume; i++) { @@ -374,8 +374,7 @@ int LuaVoxelManip::l_get_emerged_area(lua_State *L) } LuaVoxelManip::LuaVoxelManip(MMVManip *mmvm, bool is_mg_vm) : - is_mapgen_vm(is_mg_vm), - vm(mmvm) + is_mapgen_vm(is_mg_vm), vm(mmvm) { } @@ -406,9 +405,10 @@ int LuaVoxelManip::create_object(lua_State *L) GET_ENV_PTR; Map *map = &(env->getMap()); - LuaVoxelManip *o = (lua_istable(L, 1) && lua_istable(L, 2)) ? - new LuaVoxelManip(map, check_v3s16(L, 1), check_v3s16(L, 2)) : - new LuaVoxelManip(map); + LuaVoxelManip *o = (lua_istable(L, 1) && lua_istable(L, 2)) + ? new LuaVoxelManip(map, check_v3s16(L, 1), + check_v3s16(L, 2)) + : new LuaVoxelManip(map); *(void **)(lua_newuserdata(L, sizeof(void *))) = o; luaL_getmetatable(L, className); @@ -426,7 +426,7 @@ LuaVoxelManip *LuaVoxelManip::checkobject(lua_State *L, int narg) if (!ud) luaL_typerror(L, narg, className); - return *(LuaVoxelManip **)ud; // unbox pointer + return *(LuaVoxelManip **)ud; // unbox pointer } void LuaVoxelManip::Register(lua_State *L) @@ -438,7 +438,7 @@ void LuaVoxelManip::Register(lua_State *L) lua_pushliteral(L, "__metatable"); lua_pushvalue(L, methodtable); - lua_settable(L, metatable); // hide metatable from Lua getmetatable() + lua_settable(L, metatable); // hide metatable from Lua getmetatable() lua_pushliteral(L, "__index"); lua_pushvalue(L, methodtable); @@ -448,32 +448,28 @@ void LuaVoxelManip::Register(lua_State *L) lua_pushcfunction(L, gc_object); lua_settable(L, metatable); - lua_pop(L, 1); // drop metatable + lua_pop(L, 1); // drop metatable - luaL_openlib(L, 0, methods, 0); // fill methodtable - lua_pop(L, 1); // drop methodtable + luaL_openlib(L, 0, methods, 0); // fill methodtable + lua_pop(L, 1); // drop methodtable // Can be created from Lua (VoxelManip()) lua_register(L, className, create_object); } const char LuaVoxelManip::className[] = "VoxelManip"; -const luaL_Reg LuaVoxelManip::methods[] = { - luamethod(LuaVoxelManip, read_from_map), - luamethod(LuaVoxelManip, get_data), - luamethod(LuaVoxelManip, set_data), - luamethod(LuaVoxelManip, get_node_at), - luamethod(LuaVoxelManip, set_node_at), - luamethod(LuaVoxelManip, write_to_map), - luamethod(LuaVoxelManip, update_map), - luamethod(LuaVoxelManip, update_liquids), - luamethod(LuaVoxelManip, calc_lighting), - luamethod(LuaVoxelManip, set_lighting), - luamethod(LuaVoxelManip, get_light_data), - luamethod(LuaVoxelManip, set_light_data), - luamethod(LuaVoxelManip, get_param2_data), - luamethod(LuaVoxelManip, set_param2_data), - luamethod(LuaVoxelManip, was_modified), - luamethod(LuaVoxelManip, get_emerged_area), - {0,0} -}; +const luaL_Reg LuaVoxelManip::methods[] = {luamethod(LuaVoxelManip, read_from_map), + luamethod(LuaVoxelManip, get_data), luamethod(LuaVoxelManip, set_data), + luamethod(LuaVoxelManip, get_node_at), + luamethod(LuaVoxelManip, set_node_at), + luamethod(LuaVoxelManip, write_to_map), + luamethod(LuaVoxelManip, update_map), + luamethod(LuaVoxelManip, update_liquids), + luamethod(LuaVoxelManip, calc_lighting), + luamethod(LuaVoxelManip, set_lighting), + luamethod(LuaVoxelManip, get_light_data), + luamethod(LuaVoxelManip, set_light_data), + luamethod(LuaVoxelManip, get_param2_data), + luamethod(LuaVoxelManip, set_param2_data), + luamethod(LuaVoxelManip, was_modified), + luamethod(LuaVoxelManip, get_emerged_area), {0, 0}}; diff --git a/src/script/scripting_client.cpp b/src/script/scripting_client.cpp index 729645678..011c93b43 100644 --- a/src/script/scripting_client.cpp +++ b/src/script/scripting_client.cpp @@ -41,8 +41,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "lua_api/l_settings.h" #include "lua_api/l_http.h" -ClientScripting::ClientScripting(Client *client): - ScriptApiBase(ScriptingType::Client) +ClientScripting::ClientScripting(Client *client) : ScriptApiBase(ScriptingType::Client) { setGameDef(client); setGame(g_game); diff --git a/src/script/scripting_client.h b/src/script/scripting_client.h index e162f8bcf..b8dd257a9 100644 --- a/src/script/scripting_client.h +++ b/src/script/scripting_client.h @@ -31,12 +31,11 @@ class LocalPlayer; class Camera; class Minimap; -class ClientScripting: - virtual public ScriptApiBase, - public ScriptApiSecurity, - public ScriptApiClient, - public ScriptApiModChannels, - public ScriptApiCheats +class ClientScripting : virtual public ScriptApiBase, + public ScriptApiSecurity, + public ScriptApiClient, + public ScriptApiModChannels, + public ScriptApiCheats { public: ClientScripting(Client *client); diff --git a/src/script/scripting_mainmenu.cpp b/src/script/scripting_mainmenu.cpp index 0f672f917..835368776 100644 --- a/src/script/scripting_mainmenu.cpp +++ b/src/script/scripting_mainmenu.cpp @@ -34,8 +34,7 @@ extern "C" { #define MAINMENU_NUM_ASYNC_THREADS 4 - -MainMenuScripting::MainMenuScripting(GUIEngine* guiengine): +MainMenuScripting::MainMenuScripting(GUIEngine *guiengine) : ScriptApiBase(ScriptingType::MainMenu) { setGuiEngine(guiengine); @@ -76,7 +75,7 @@ void MainMenuScripting::initializeModApi(lua_State *L, int top) asyncEngine.registerStateInitializer(ModApiHttp::InitializeAsync); // Initialize async environment - //TODO possibly make number of async threads configurable + // TODO possibly make number of async threads configurable asyncEngine.initialize(MAINMENU_NUM_ASYNC_THREADS); } @@ -93,9 +92,8 @@ void MainMenuScripting::step() } /******************************************************************************/ -unsigned int MainMenuScripting::queueAsync(const std::string &serialized_func, - const std::string &serialized_param) +unsigned int MainMenuScripting::queueAsync( + const std::string &serialized_func, const std::string &serialized_param) { return asyncEngine.queueAsyncJob(serialized_func, serialized_param); } - diff --git a/src/script/scripting_mainmenu.h b/src/script/scripting_mainmenu.h index 9e23bdc1b..16d0a0cc0 100644 --- a/src/script/scripting_mainmenu.h +++ b/src/script/scripting_mainmenu.h @@ -27,12 +27,10 @@ with this program; if not, write to the Free Software Foundation, Inc., /* Scripting <-> Main Menu Interface */ /*****************************************************************************/ -class MainMenuScripting - : virtual public ScriptApiBase, - public ScriptApiMainMenu +class MainMenuScripting : virtual public ScriptApiBase, public ScriptApiMainMenu { public: - MainMenuScripting(GUIEngine* guiengine); + MainMenuScripting(GUIEngine *guiengine); // Global step handler to pass back async events void step(); @@ -40,6 +38,7 @@ public: // Pass async events from engine to async threads unsigned int queueAsync(const std::string &serialized_func, const std::string &serialized_params); + private: void initializeModApi(lua_State *L, int top); static void registerLuaClasses(lua_State *L, int top); diff --git a/src/script/scripting_server.cpp b/src/script/scripting_server.cpp index 85411ded4..604607e78 100644 --- a/src/script/scripting_server.cpp +++ b/src/script/scripting_server.cpp @@ -50,8 +50,7 @@ extern "C" { #include "lualib.h" } -ServerScripting::ServerScripting(Server* server): - ScriptApiBase(ScriptingType::Server) +ServerScripting::ServerScripting(Server *server) : ScriptApiBase(ScriptingType::Server) { setGameDef(server); @@ -63,9 +62,12 @@ ServerScripting::ServerScripting(Server* server): if (g_settings->getBool("secure.enable_security")) { initializeSecurity(); } else { - warningstream << "\\!/ Mod security should never be disabled, as it allows any mod to " - << "access the host machine." - << "Mods should use minetest.request_insecure_environment() instead \\!/" << std::endl; + warningstream << "\\!/ Mod security should never be disabled, as it " + "allows any mod to " + << "access the host machine." + << "Mods should use " + "minetest.request_insecure_environment() instead \\!/" + << std::endl; } lua_getglobal(L, "core"); diff --git a/src/script/scripting_server.h b/src/script/scripting_server.h index bf06ab197..b0619f449 100644 --- a/src/script/scripting_server.h +++ b/src/script/scripting_server.h @@ -32,19 +32,18 @@ with this program; if not, write to the Free Software Foundation, Inc., /* Scripting <-> Server Game Interface */ /*****************************************************************************/ -class ServerScripting: - virtual public ScriptApiBase, - public ScriptApiDetached, - public ScriptApiEntity, - public ScriptApiEnv, - public ScriptApiModChannels, - public ScriptApiNode, - public ScriptApiPlayer, - public ScriptApiServer, - public ScriptApiSecurity +class ServerScripting : virtual public ScriptApiBase, + public ScriptApiDetached, + public ScriptApiEntity, + public ScriptApiEnv, + public ScriptApiModChannels, + public ScriptApiNode, + public ScriptApiPlayer, + public ScriptApiServer, + public ScriptApiSecurity { public: - ServerScripting(Server* server); + ServerScripting(Server *server); // use ScriptApiBase::loadMod() to load mods diff --git a/src/serialization.cpp b/src/serialization.cpp index 310604f54..64d5cefcc 100644 --- a/src/serialization.cpp +++ b/src/serialization.cpp @@ -26,29 +26,29 @@ with this program; if not, write to the Free Software Foundation, Inc., /* report a zlib or i/o error */ void zerr(int ret) { - dstream<<"zerr: "; - switch (ret) { - case Z_ERRNO: - if (ferror(stdin)) - dstream<<"error reading stdin"< &data, std::ostream &os, u8 version) { - if(version >= 11) - { - compressZlib(*data ,data.getSize(), os); + if (version >= 11) { + compressZlib(*data, data.getSize(), os); return; } - if(data.getSize() == 0) + if (data.getSize() == 0) return; // Write length (u32) u8 tmp[4]; writeU32(tmp, data.getSize()); - os.write((char*)tmp, 4); + os.write((char *)tmp, 4); // We will be writing 8-bit pairs of more_count and byte u8 more_count = 0; u8 current_byte = data[0]; - for(u32 i=1; i= 11) - { + if (version >= 11) { decompressZlib(is, os); return; } @@ -251,31 +236,28 @@ void decompress(std::istream &is, std::ostream &os, u8 version) // Read length (u32) u8 tmp[4]; - is.read((char*)tmp, 4); + is.read((char *)tmp, 4); u32 len = readU32(tmp); // We will be reading 8-bit pairs of more_count and byte u32 count = 0; - for(;;) - { - u8 more_count=0; - u8 byte=0; + for (;;) { + u8 more_count = 0; + u8 byte = 0; - is.read((char*)&more_count, 1); + is.read((char *)&more_count, 1); - is.read((char*)&byte, 1); + is.read((char *)&byte, 1); - if(is.eof()) + if (is.eof()) throw SerializationError("decompress: stream ended halfway"); - for(s32 i=0; i<(u16)more_count+1; i++) - os.write((char*)&byte, 1); + for (s32 i = 0; i < (u16)more_count + 1; i++) + os.write((char *)&byte, 1); - count += (u16)more_count+1; + count += (u16)more_count + 1; - if(count == len) + if (count == len) break; } } - - diff --git a/src/serialization.h b/src/serialization.h index f399983c4..b53564c78 100644 --- a/src/serialization.h +++ b/src/serialization.h @@ -77,7 +77,8 @@ with this program; if not, write to the Free Software Foundation, Inc., // in memory; conversion just won't work in this direction. #define SER_FMT_VER_LOWEST_WRITE 24 -inline bool ser_ver_supported(s32 v) { +inline bool ser_ver_supported(s32 v) +{ return v >= SER_FMT_VER_LOWEST_READ && v <= SER_FMT_VER_HIGHEST_READ; } @@ -91,5 +92,5 @@ void decompressZlib(std::istream &is, std::ostream &os, size_t limit = 0); // These choose between zlib and a self-made one according to version void compress(const SharedBuffer &data, std::ostream &os, u8 version); -//void compress(const std::string &data, std::ostream &os, u8 version); +// void compress(const std::string &data, std::ostream &os, u8 version); void decompress(std::istream &is, std::ostream &os, u8 version); diff --git a/src/server.cpp b/src/server.cpp index fe2bb3840..b9c68654a 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -70,19 +70,13 @@ with this program; if not, write to the Free Software Foundation, Inc., class ClientNotFoundException : public BaseException { public: - ClientNotFoundException(const char *s): - BaseException(s) - {} + ClientNotFoundException(const char *s) : BaseException(s) {} }; class ServerThread : public Thread { public: - - ServerThread(Server *server): - Thread("Server"), - m_server(server) - {} + ServerThread(Server *server) : Thread("Server"), m_server(server) {} void *run(); @@ -112,13 +106,13 @@ void *ServerThread::run() m_server->Receive(); } catch (con::PeerNotFoundException &e) { - infostream<<"Server: PeerNotFoundException"<setAsyncFatalError(e.what()); } catch (LuaError &e) { - m_server->setAsyncFatalError( - "ServerThread::run Lua: " + std::string(e.what())); + m_server->setAsyncFatalError("ServerThread::run Lua: " + + std::string(e.what())); } } @@ -129,23 +123,27 @@ void *ServerThread::run() v3f ServerSoundParams::getPos(ServerEnvironment *env, bool *pos_exists) const { - if(pos_exists) *pos_exists = false; - switch(type){ + if (pos_exists) + *pos_exists = false; + switch (type) { case SSP_LOCAL: - return v3f(0,0,0); + return v3f(0, 0, 0); case SSP_POSITIONAL: - if(pos_exists) *pos_exists = true; + if (pos_exists) + *pos_exists = true; return pos; case SSP_OBJECT: { - if(object == 0) - return v3f(0,0,0); + if (object == 0) + return v3f(0, 0, 0); ServerActiveObject *sao = env->getActiveObject(object); - if(!sao) - return v3f(0,0,0); - if(pos_exists) *pos_exists = true; - return sao->getBasePosition(); } + if (!sao) + return v3f(0, 0, 0); + if (pos_exists) + *pos_exists = true; + return sao->getBasePosition(); } - return v3f(0,0,0); + } + return v3f(0, 0, 0); } void Server::ShutdownState::reset() @@ -169,10 +167,8 @@ void Server::ShutdownState::tick(float dtime, Server *server) return; // Timed shutdown - static const float shutdown_msg_times[] = - { - 1, 2, 3, 4, 5, 10, 20, 40, 60, 120, 180, 300, 600, 1200, 1800, 3600 - }; + static const float shutdown_msg_times[] = {1, 2, 3, 4, 5, 10, 20, 40, 60, 120, + 180, 300, 600, 1200, 1800, 3600}; // Automated messages if (m_timer < shutdown_msg_times[ARRLEN(shutdown_msg_times) - 1]) { @@ -181,7 +177,8 @@ void Server::ShutdownState::tick(float dtime, Server *server) if (m_timer > t && m_timer - dtime < t) { std::wstring periodicMsg = getShutdownTimerMessage(); - infostream << wide_to_utf8(periodicMsg).c_str() << std::endl; + infostream << wide_to_utf8(periodicMsg).c_str() + << std::endl; server->SendChatMessage(PEER_ID_INEXISTENT, periodicMsg); break; } @@ -199,7 +196,7 @@ std::wstring Server::ShutdownState::getShutdownTimerMessage() const { std::wstringstream ws; ws << L"*** Server shutting down in " - << duration_to_string(myround(m_timer)).c_str() << "."; + << duration_to_string(myround(m_timer)).c_str() << "."; return ws.str(); } @@ -207,32 +204,19 @@ std::wstring Server::ShutdownState::getShutdownTimerMessage() const Server */ -Server::Server( - const std::string &path_world, - const SubgameSpec &gamespec, - bool simple_singleplayer_mode, - Address bind_addr, - bool dedicated, - ChatInterface *iface - ): - m_bind_addr(bind_addr), - m_path_world(path_world), - m_gamespec(gamespec), - m_simple_singleplayer_mode(simple_singleplayer_mode), - m_dedicated(dedicated), - m_async_fatal_error(""), - m_con(std::make_shared(PROTOCOL_ID, - 512, - CONNECTION_TIMEOUT, - m_bind_addr.isIPv6(), - this)), - m_itemdef(createItemDefManager()), - m_nodedef(createNodeDefManager()), - m_craftdef(createCraftDefManager()), - m_thread(new ServerThread(this)), - m_clients(m_con), - m_admin_chat(iface), - m_modchannel_mgr(new ModChannelMgr()) +Server::Server(const std::string &path_world, const SubgameSpec &gamespec, + bool simple_singleplayer_mode, Address bind_addr, bool dedicated, + ChatInterface *iface) : + m_bind_addr(bind_addr), + m_path_world(path_world), m_gamespec(gamespec), + m_simple_singleplayer_mode(simple_singleplayer_mode), + m_dedicated(dedicated), m_async_fatal_error(""), + m_con(std::make_shared(PROTOCOL_ID, 512, + CONNECTION_TIMEOUT, m_bind_addr.isIPv6(), this)), + m_itemdef(createItemDefManager()), m_nodedef(createNodeDefManager()), + m_craftdef(createCraftDefManager()), m_thread(new ServerThread(this)), + m_clients(m_con), m_admin_chat(iface), + m_modchannel_mgr(new ModChannelMgr()) { if (m_path_world.empty()) throw ServerError("Supplied empty world path"); @@ -241,29 +225,30 @@ Server::Server( throw ServerError("Supplied invalid gamespec"); #if USE_PROMETHEUS - m_metrics_backend = std::unique_ptr(createPrometheusMetricsBackend()); + m_metrics_backend = + std::unique_ptr(createPrometheusMetricsBackend()); #else m_metrics_backend = std::unique_ptr(new MetricsBackend()); #endif - m_uptime_counter = m_metrics_backend->addCounter("minetest_core_server_uptime", "Server uptime (in seconds)"); - m_player_gauge = m_metrics_backend->addGauge("minetest_core_player_number", "Number of connected players"); + m_uptime_counter = m_metrics_backend->addCounter( + "minetest_core_server_uptime", "Server uptime (in seconds)"); + m_player_gauge = m_metrics_backend->addGauge( + "minetest_core_player_number", "Number of connected players"); m_timeofday_gauge = m_metrics_backend->addGauge( - "minetest_core_timeofday", - "Time of day value"); + "minetest_core_timeofday", "Time of day value"); m_lag_gauge = m_metrics_backend->addGauge( - "minetest_core_latency", - "Latency value (in seconds)"); + "minetest_core_latency", "Latency value (in seconds)"); - m_aom_buffer_counter = m_metrics_backend->addCounter( - "minetest_core_aom_generated_count", - "Number of active object messages generated"); + m_aom_buffer_counter = + m_metrics_backend->addCounter("minetest_core_aom_generated_count", + "Number of active object messages generated"); - m_packet_recv_counter = m_metrics_backend->addCounter( - "minetest_core_server_packet_recv", - "Processable packets received"); + m_packet_recv_counter = + m_metrics_backend->addCounter("minetest_core_server_packet_recv", + "Processable packets received"); m_packet_recv_processed_counter = m_metrics_backend->addCounter( "minetest_core_server_packet_recv_processed", @@ -277,7 +262,7 @@ Server::~Server() // Send shutdown message SendChatMessage(PEER_ID_INEXISTENT, ChatMessage(CHATMESSAGE_TYPE_ANNOUNCE, - L"*** Server shutting down")); + L"*** Server shutting down")); if (m_env) { MutexAutoLock envlock(m_env_mutex); @@ -296,8 +281,7 @@ Server::~Server() kick_msg = g_settings->get("kick_msg_shutdown"); } m_env->saveLoadedPlayers(true); - m_env->kickAllPlayers(SERVER_ACCESSDENIED_SHUTDOWN, - kick_msg, reconnect); + m_env->kickAllPlayers(SERVER_ACCESSDENIED_SHUTDOWN, kick_msg, reconnect); } actionstream << "Server: Shutting down" << std::endl; @@ -373,11 +357,12 @@ void Server::init() m_modmgr->printUnsatisfiedModsError(); } - //lock environment + // lock environment MutexAutoLock envlock(m_env_mutex); // Create the Map (loads map_meta.txt, overriding configured mapgen params) - ServerMap *servermap = new ServerMap(m_path_world, this, m_emerge, m_metrics_backend.get()); + ServerMap *servermap = new ServerMap( + m_path_world, this, m_emerge, m_metrics_backend.get()); // Initialize scripting infostream << "Server: Initializing Lua" << std::endl; @@ -385,7 +370,8 @@ void Server::init() m_script = new ServerScripting(this); // Must be created before mod loading because we have some inventory creation - m_inventory_mgr = std::unique_ptr(new ServerInventoryManager()); + m_inventory_mgr = std::unique_ptr( + new ServerInventoryManager()); m_script->loadMod(getBuiltinLuaPath() + DIR_DELIM "init.lua", BUILTIN_MOD_NAME); @@ -404,7 +390,8 @@ void Server::init() for (const std::string &path : paths) { TextureOverrideSource override_source(path + DIR_DELIM + "override.txt"); m_nodedef->applyTextureOverrides(override_source.getNodeTileOverrides()); - m_itemdef->applyTextureOverrides(override_source.getItemTextureOverrides()); + m_itemdef->applyTextureOverrides( + override_source.getItemTextureOverrides()); } m_nodedef->setNodeRegistrationStatus(true); @@ -455,8 +442,8 @@ void Server::start() { init(); - infostream << "Starting server on " << m_bind_addr.serializeString() - << "..." << std::endl; + infostream << "Starting server on " << m_bind_addr.serializeString() << "..." + << std::endl; // Stop thread if already running m_thread->stop(); @@ -469,30 +456,32 @@ void Server::start() m_thread->start(); // ASCII art for the win! - std::cerr - << " .__ __ __ " << std::endl - << " _____ |__| ____ _____/ |_ ____ _______/ |_ " << std::endl - << " / \\| |/ \\_/ __ \\ __\\/ __ \\ / ___/\\ __\\" << std::endl - << "| Y Y \\ | | \\ ___/| | \\ ___/ \\___ \\ | | " << std::endl - << "|__|_| /__|___| /\\___ >__| \\___ >____ > |__| " << std::endl - << " \\/ \\/ \\/ \\/ \\/ " << std::endl; + std::cerr << " .__ __ __ " << std::endl + << " _____ |__| ____ _____/ |_ ____ _______/ |_ " << std::endl + << " / \\| |/ \\_/ __ \\ __\\/ __ \\ / ___/\\ __\\" + << std::endl + << "| Y Y \\ | | \\ ___/| | \\ ___/ \\___ \\ | | " + << std::endl + << "|__|_| /__|___| /\\___ >__| \\___ >____ > |__| " << std::endl + << " \\/ \\/ \\/ \\/ \\/ " + << std::endl; actionstream << "World at [" << m_path_world << "]" << std::endl; - actionstream << "Server for gameid=\"" << m_gamespec.id - << "\" listening on " << m_bind_addr.serializeString() << ":" - << m_bind_addr.getPort() << "." << std::endl; + actionstream << "Server for gameid=\"" << m_gamespec.id << "\" listening on " + << m_bind_addr.serializeString() << ":" << m_bind_addr.getPort() + << "." << std::endl; } void Server::stop() { - infostream<<"Server: Stopping and waiting threads"<stop(); - //m_emergethread.setRun(false); + // m_emergethread.setRun(false); m_thread->wait(); - //m_emergethread.stop(); + // m_emergethread.stop(); - infostream<<"Server: Threads stopped"<kickAllPlayers(SERVER_ACCESSDENIED_CRASH, - g_settings->get("kick_msg_crash"), - g_settings->getBool("ask_reconnect_on_crash")); + g_settings->get("kick_msg_crash"), + g_settings->getBool("ask_reconnect_on_crash")); } throw ServerError("AsyncErr: " + async_err); } @@ -530,7 +519,7 @@ void Server::AsyncRunStep(bool initial_step) SendBlocks(dtime); } - if((dtime < 0.001) && !initial_step) + if ((dtime < 0.001) && !initial_step) return; ScopeProfiler sp(g_profiler, "Server::AsyncRunStep()", SPT_AVG); @@ -571,10 +560,10 @@ void Server::AsyncRunStep(bool initial_step) // Figure out and report maximum lag to environment float max_lag = m_env->getMaxLagEstimate(); max_lag *= 0.9998; // Decrease slowly (about half per 5 minutes) - if(dtime > max_lag){ - if(dtime > 0.1 && dtime > max_lag * 2.0) - infostream<<"Server: Maximum lag peaked to "< max_lag) { + if (dtime > 0.1 && dtime > max_lag * 2.0) + infostream << "Server: Maximum lag peaked to " << dtime + << " s" << std::endl; max_lag = dtime; } m_env->reportMaxLagEstimate(max_lag); @@ -583,14 +572,13 @@ void Server::AsyncRunStep(bool initial_step) } static const float map_timer_and_unload_dtime = 2.92; - if(m_map_timer_and_unload_interval.step(dtime, map_timer_and_unload_dtime)) - { + if (m_map_timer_and_unload_interval.step(dtime, map_timer_and_unload_dtime)) { MutexAutoLock lock(m_env_mutex); // Run Map's timers and unload unused data ScopeProfiler sp(g_profiler, "Server: map timer and unload"); m_env->getMap().timerUpdate(map_timer_and_unload_dtime, - g_settings->getFloat("server_unload_unused_data_timeout"), - U32_MAX); + g_settings->getFloat("server_unload_unused_data_timeout"), + U32_MAX); } /* @@ -600,13 +588,14 @@ void Server::AsyncRunStep(bool initial_step) if (!m_admin_chat->command_queue.empty()) { MutexAutoLock lock(m_env_mutex); while (!m_admin_chat->command_queue.empty()) { - ChatEvent *evt = m_admin_chat->command_queue.pop_frontNoEx(); + ChatEvent *evt = m_admin_chat->command_queue + .pop_frontNoEx(); handleChatInterfaceEvent(evt); delete evt; } } - m_admin_chat->outgoing_queue.push_back( - new ChatEventTimeInfo(m_env->getGameTime(), m_env->getTimeOfDay())); + m_admin_chat->outgoing_queue.push_back(new ChatEventTimeInfo( + m_env->getGameTime(), m_env->getTimeOfDay())); } /* @@ -615,15 +604,14 @@ void Server::AsyncRunStep(bool initial_step) /* Transform liquids */ m_liquid_transform_timer += dtime; - if(m_liquid_transform_timer >= m_liquid_transform_every) - { + if (m_liquid_transform_timer >= m_liquid_transform_every) { m_liquid_transform_timer -= m_liquid_transform_every; MutexAutoLock lock(m_env_mutex); ScopeProfiler sp(g_profiler, "Server: liquid transform"); - std::map modified_blocks; + std::map modified_blocks; m_env->getMap().transformLiquids(modified_blocks, m_env); /* @@ -635,24 +623,20 @@ void Server::AsyncRunStep(bool initial_step) } m_clients.step(dtime); - m_lag_gauge->increment((m_lag_gauge->get() > dtime ? -1 : 1) * dtime/100); + m_lag_gauge->increment((m_lag_gauge->get() > dtime ? -1 : 1) * dtime / 100); #if USE_CURL // send masterserver announce { float &counter = m_masterserver_timer; if (!isSingleplayer() && (!counter || counter >= 300.0) && g_settings->getBool("server_announce")) { - ServerList::sendAnnounce(counter ? ServerList::AA_UPDATE : - ServerList::AA_START, - m_bind_addr.getPort(), - m_clients.getPlayerNames(), - m_uptime_counter->get(), - m_env->getGameTime(), - m_lag_gauge->get(), - m_gamespec.id, + ServerList::sendAnnounce(counter ? ServerList::AA_UPDATE + : ServerList::AA_START, + m_bind_addr.getPort(), m_clients.getPlayerNames(), + m_uptime_counter->get(), m_env->getGameTime(), + m_lag_gauge->get(), m_gamespec.id, Mapgen::getMapgenName(m_emerge->mgparams->mgtype), - m_modmgr->getMods(), - m_dedicated); + m_modmgr->getMods(), m_dedicated); counter = 0.01; } counter += dtime; @@ -663,7 +647,8 @@ void Server::AsyncRunStep(bool initial_step) Check added and deleted active objects */ { - //infostream<<"Server: Checking added and deleted active objects"<getFloat("server_map_save_interval"); + m_mod_storage_save_timer = + g_settings->getFloat("server_map_save_interval"); int n = 0; - for (std::unordered_map::const_iterator - it = m_mod_storages.begin(); it != m_mod_storages.end(); ++it) { + for (std::unordered_map::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; + infostream << "Saved " << n << " modified mod storages." + << std::endl; } } @@ -715,16 +704,17 @@ void Server::AsyncRunStep(bool initial_step) // Key = object id // Value = data sent by object - std::unordered_map*> buffered_messages; + std::unordered_map *> + buffered_messages; // Get active object messages from environment ActiveObjectMessage aom(0); u32 aom_count = 0; - for(;;) { + for (;;) { if (!m_env->getActiveObjectMessage(&aom)) break; - std::vector* message_list = nullptr; + std::vector *message_list = nullptr; auto n = buffered_messages.find(aom.id); if (n == buffered_messages.end()) { message_list = new std::vector; @@ -749,33 +739,44 @@ void Server::AsyncRunStep(bool initial_step) PlayerSAO *player = getPlayerSAO(client->peer_id); // Go through all objects in message buffer for (const auto &buffered_message : buffered_messages) { - // If object does not exist or is not known by client, skip it + // If object does not exist or is not known by client, + // skip it u16 id = buffered_message.first; ServerActiveObject *sao = m_env->getActiveObject(id); - if (!sao || client->m_known_objects.find(id) == client->m_known_objects.end()) + if (!sao || client->m_known_objects.find(id) == + client->m_known_objects + .end()) continue; // Get message list of object - std::vector* list = buffered_message.second; + std::vector *list = + buffered_message.second; // Go through every message for (const ActiveObjectMessage &aom : *list) { - // Send position updates to players who do not see the attachment + // Send position updates to players who do not see + // the attachment if (aom.datastring[0] == AO_CMD_UPDATE_POSITION) { if (sao->getId() == player->getId()) continue; - // Do not send position updates for attached players - // as long the parent is known to the client - ServerActiveObject *parent = sao->getParent(); - if (parent && client->m_known_objects.find(parent->getId()) != - client->m_known_objects.end()) + // Do not send position updates for + // attached players as long the parent is + // known to the client + ServerActiveObject *parent = + sao->getParent(); + if (parent && client->m_known_objects.find( + parent->getId()) != + client->m_known_objects + .end()) continue; } // Add full new data to appropriate buffer - std::string &buffer = aom.reliable ? reliable_data : unreliable_data; + std::string &buffer = + aom.reliable ? reliable_data + : unreliable_data; char idbuf[2]; - writeU16((u8*) idbuf, aom.id); + writeU16((u8 *)idbuf, aom.id); // u16 id // std::string data buffer.append(idbuf, sizeof(idbuf)); @@ -791,7 +792,8 @@ void Server::AsyncRunStep(bool initial_step) } if (!unreliable_data.empty()) { - SendActiveObjectMessages(client->peer_id, unreliable_data, false); + SendActiveObjectMessages( + client->peer_id, unreliable_data, false); } } m_clients.unlock(); @@ -810,11 +812,11 @@ void Server::AsyncRunStep(bool initial_step) MutexAutoLock lock(m_env_mutex); // Don't send too many at a time - //u32 count = 0; + // u32 count = 0; // Single change sending is disabled if queue size is not small bool disable_single_change_sending = false; - if(m_unsent_map_edit_queue.size() >= 4) + if (m_unsent_map_edit_queue.size() >= 4) disable_single_change_sending = true; int event_count = m_unsent_map_edit_queue.size(); @@ -825,7 +827,7 @@ void Server::AsyncRunStep(bool initial_step) std::list node_meta_updates; while (!m_unsent_map_edit_queue.empty()) { - MapEditEvent* event = m_unsent_map_edit_queue.front(); + MapEditEvent *event = m_unsent_map_edit_queue.front(); m_unsent_map_edit_queue.pop(); // Players far away from the change are stored here. @@ -849,28 +851,30 @@ void Server::AsyncRunStep(bool initial_step) case MEET_BLOCK_NODE_METADATA_CHANGED: { prof.add("MEET_BLOCK_NODE_METADATA_CHANGED", 1); if (!event->is_private_change) { - // Don't send the change yet. Collect them to eliminate dupes. + // Don't send the change yet. Collect them to + // eliminate dupes. node_meta_updates.remove(event->p); node_meta_updates.push_back(event->p); } if (MapBlock *block = m_env->getMap().getBlockNoCreateNoEx( - getNodeBlockPos(event->p))) { + getNodeBlockPos(event->p))) { block->raiseModified(MOD_STATE_WRITE_NEEDED, - MOD_REASON_REPORT_META_CHANGE); + MOD_REASON_REPORT_META_CHANGE); } break; } case MEET_OTHER: prof.add("MEET_OTHER", 1); - for (const v3s16 &modified_block : event->modified_blocks) { + for (const v3s16 &modified_block : + event->modified_blocks) { m_clients.markBlockposAsNotSent(modified_block); } break; default: prof.add("unknown", 1); warningstream << "Server: Unknown MapEditEvent " - << ((u32)event->type) << std::endl; + << ((u32)event->type) << std::endl; break; } @@ -879,16 +883,19 @@ void Server::AsyncRunStep(bool initial_step) */ if (!far_players.empty()) { // Convert list format to that wanted by SetBlocksNotSent - std::map modified_blocks2; - for (const v3s16 &modified_block : event->modified_blocks) { + std::map modified_blocks2; + for (const v3s16 &modified_block : + event->modified_blocks) { modified_blocks2[modified_block] = - m_env->getMap().getBlockNoCreateNoEx(modified_block); + m_env->getMap().getBlockNoCreateNoEx( + modified_block); } // Set blocks not sent for (const u16 far_player : far_players) { if (RemoteClient *client = getClient(far_player)) - client->SetBlocksNotSent(modified_blocks2); + client->SetBlocksNotSent( + modified_blocks2); } } @@ -927,7 +934,7 @@ void Server::AsyncRunStep(bool initial_step) float &counter = m_savemap_timer; counter += dtime; static thread_local const float save_interval = - g_settings->getFloat("server_map_save_interval"); + g_settings->getFloat("server_map_save_interval"); if (counter >= save_interval) { counter = 0.0; MutexAutoLock lock(m_env_mutex); @@ -963,8 +970,9 @@ void Server::Receive() peer_id = 0; try { /* - In the first iteration *wait* for a packet, afterwards process - all packets that are immediately available (no waiting). + In the first iteration *wait* for a packet, afterwards + process all packets that are immediately available (no + waiting). */ if (first) { m_con->Receive(&pkt); @@ -979,15 +987,18 @@ void Server::Receive() ProcessData(&pkt); m_packet_recv_processed_counter->increment(); } catch (const con::InvalidIncomingDataException &e) { - infostream << "Server::Receive(): InvalidIncomingDataException: what()=" - << e.what() << std::endl; + infostream << "Server::Receive(): InvalidIncomingDataException: " + "what()=" + << e.what() << std::endl; } catch (const SerializationError &e) { infostream << "Server::Receive(): SerializationError: what()=" - << e.what() << std::endl; + << e.what() << std::endl; } catch (const ClientStateError &e) { - errorstream << "ProcessData: peer=" << peer_id << " what()=" - << e.what() << std::endl; - DenyAccess_Legacy(peer_id, L"Your client sent something server didn't expect." + errorstream << "ProcessData: peer=" << peer_id + << " what()=" << e.what() << std::endl; + DenyAccess_Legacy(peer_id, + L"Your client sent something server didn't " + L"expect." L"Try reconnecting or updating your client"); } catch (const con::PeerNotFoundException &e) { // Do nothing @@ -997,16 +1008,18 @@ void Server::Receive() } } -PlayerSAO* Server::StageTwoClientInit(session_t peer_id) +PlayerSAO *Server::StageTwoClientInit(session_t peer_id) { std::string playername; PlayerSAO *playersao = NULL; m_clients.lock(); try { - RemoteClient* client = m_clients.lockedGetClientNoEx(peer_id, CS_InitDone); + RemoteClient *client = + m_clients.lockedGetClientNoEx(peer_id, CS_InitDone); if (client) { playername = client->getName(); - playersao = emergePlayer(playername.c_str(), peer_id, client->net_proto_version); + playersao = emergePlayer(playername.c_str(), peer_id, + client->net_proto_version); } } catch (std::exception &e) { m_clients.unlock(); @@ -1020,13 +1033,16 @@ PlayerSAO* Server::StageTwoClientInit(session_t peer_id) if (!playersao || !player) { if (player && player->getPeerId() != PEER_ID_INEXISTENT) { actionstream << "Server: Failed to emerge player \"" << playername - << "\" (player allocated to an another client)" << std::endl; - DenyAccess_Legacy(peer_id, L"Another client is connected with this " - L"name. If your client closed unexpectedly, try again in " + << "\" (player allocated to an another client)" + << std::endl; + DenyAccess_Legacy(peer_id, + L"Another client is connected with this " + L"name. If your client closed unexpectedly, try " + L"again in " L"a minute."); } else { - errorstream << "Server: " << playername << ": Failed to emerge player" - << std::endl; + errorstream << "Server: " << playername + << ": Failed to emerge player" << std::endl; DenyAccess_Legacy(peer_id, L"Could not allocate player."); } return NULL; @@ -1048,7 +1064,7 @@ PlayerSAO* Server::StageTwoClientInit(session_t peer_id) // Send HP or death screen if (playersao->isDead()) - SendDeathscreen(peer_id, false, v3f(0,0,0)); + SendDeathscreen(peer_id, false, v3f(0, 0, 0)); else SendPlayerHPOrDie(playersao, PlayerHPChangeReason(PlayerHPChangeReason::SET_HP)); @@ -1064,13 +1080,14 @@ PlayerSAO* Server::StageTwoClientInit(session_t peer_id) std::string ip_str = addr.serializeString(); const std::vector &names = m_clients.getPlayerNames(); - actionstream << player->getName() << " [" << ip_str << "] joins game. List of players: "; + actionstream << player->getName() << " [" << ip_str + << "] joins game. List of players: "; for (const std::string &name : names) { actionstream << name << " "; } - actionstream << player->getName() <getName() << std::endl; } return playersao; } @@ -1093,36 +1110,36 @@ void Server::ProcessData(NetworkPacket *pkt) Address address = getPeerAddress(peer_id); std::string addr_s = address.serializeString(); - if(m_banmanager->isIpBanned(addr_s)) { + if (m_banmanager->isIpBanned(addr_s)) { std::string ban_name = m_banmanager->getBanName(addr_s); infostream << "Server: A banned client tried to connect from " - << addr_s << "; banned name was " - << ban_name << std::endl; + << addr_s << "; banned name was " << ban_name + << std::endl; // This actually doesn't seem to transfer to the client - DenyAccess_Legacy(peer_id, L"Your ip is banned. Banned name was " - + utf8_to_wide(ban_name)); + DenyAccess_Legacy( + peer_id, L"Your ip is banned. Banned name was " + + utf8_to_wide(ban_name)); return; } - } - catch(con::PeerNotFoundException &e) { + } catch (con::PeerNotFoundException &e) { /* * no peer for this packet found * most common reason is peer timeout, e.g. peer didn't * respond for some time, your server was overloaded or * things like that. */ - infostream << "Server::ProcessData(): Canceling: peer " - << peer_id << " not found" << std::endl; + infostream << "Server::ProcessData(): Canceling: peer " << peer_id + << " not found" << std::endl; return; } try { - ToServerCommand command = (ToServerCommand) pkt->getCommand(); + ToServerCommand command = (ToServerCommand)pkt->getCommand(); // Command must be handled into ToServerCommandHandler if (command >= TOSERVER_NUM_MSG_TYPES) { - infostream << "Server: Ignoring unknown command " - << command << std::endl; + infostream << "Server: Ignoring unknown command " << command + << std::endl; return; } @@ -1133,10 +1150,11 @@ void Server::ProcessData(NetworkPacket *pkt) u8 peer_ser_ver = getClient(peer_id, CS_InitDone)->serialization_version; - if(peer_ser_ver == SER_FMT_VER_INVALID) { + if (peer_ser_ver == SER_FMT_VER_INVALID) { errorstream << "Server::ProcessData(): Cancelling: Peer" - " serialization format invalid or not initialized." - " Skipping incoming command=" << command << std::endl; + " serialization format invalid or not initialized." + " Skipping incoming command=" + << command << std::endl; return; } @@ -1147,23 +1165,23 @@ void Server::ProcessData(NetworkPacket *pkt) } if (m_clients.getClientState(peer_id) < CS_Active) { - if (command == TOSERVER_PLAYERPOS) return; + if (command == TOSERVER_PLAYERPOS) + return; - errorstream << "Got packet command: " << command << " for peer id " - << peer_id << " but client isn't active yet. Dropping packet " - << std::endl; + errorstream << "Got packet command: " << command + << " for peer id " << peer_id + << " but client isn't active yet. Dropping packet " + << std::endl; return; } handleCommand(pkt); } catch (SendFailedException &e) { errorstream << "Server::ProcessData(): SendFailedException: " - << "what=" << e.what() - << std::endl; + << "what=" << e.what() << std::endl; } catch (PacketError &e) { actionstream << "Server::ProcessData(): PacketError: " - << "what=" << e.what() - << std::endl; + << "what=" << e.what() << std::endl; } } @@ -1181,57 +1199,47 @@ void Server::onMapEditEvent(const MapEditEvent &event) m_unsent_map_edit_queue.push(new MapEditEvent(event)); } -void Server::SetBlocksNotSent(std::map& block) +void Server::SetBlocksNotSent(std::map &block) { std::vector clients = m_clients.getClientIDs(); m_clients.lock(); // Set the modified blocks unsent for all the clients for (const session_t client_id : clients) { - if (RemoteClient *client = m_clients.lockedGetClientNoEx(client_id)) - client->SetBlocksNotSent(block); + if (RemoteClient *client = m_clients.lockedGetClientNoEx(client_id)) + client->SetBlocksNotSent(block); } m_clients.unlock(); } void Server::peerAdded(con::Peer *peer) { - verbosestream<<"Server::peerAdded(): peer->id=" - <id<id=" << peer->id << std::endl; m_peer_change_queue.push(con::PeerChange(con::PEER_ADDED, peer->id, false)); } void Server::deletingPeer(con::Peer *peer, bool timeout) { - verbosestream<<"Server::deletingPeer(): peer->id=" - <id<<", timeout="<id=" << peer->id + << ", timeout=" << timeout << std::endl; m_clients.event(peer->id, CSE_Disconnect); m_peer_change_queue.push(con::PeerChange(con::PEER_REMOVED, peer->id, timeout)); } -bool Server::getClientConInfo(session_t peer_id, con::rtt_stat_type type, float* retval) +bool Server::getClientConInfo(session_t peer_id, con::rtt_stat_type type, float *retval) { - *retval = m_con->getPeerStat(peer_id,type); + *retval = m_con->getPeerStat(peer_id, type); return *retval != -1; } -bool Server::getClientInfo( - session_t peer_id, - ClientState* state, - u32* uptime, - u8* ser_vers, - u16* prot_vers, - u8* major, - u8* minor, - u8* patch, - std::string* vers_string, - std::string* lang_code - ) +bool Server::getClientInfo(session_t peer_id, ClientState *state, u32 *uptime, + u8 *ser_vers, u16 *prot_vers, u8 *major, u8 *minor, u8 *patch, + std::string *vers_string, std::string *lang_code) { *state = m_clients.getClientState(peer_id); m_clients.lock(); - RemoteClient* client = m_clients.lockedGetClientNoEx(peer_id, CS_Invalid); + RemoteClient *client = m_clients.lockedGetClientNoEx(peer_id, CS_Invalid); if (!client) { m_clients.unlock(); @@ -1255,23 +1263,21 @@ bool Server::getClientInfo( void Server::handlePeerChanges() { - while(!m_peer_change_queue.empty()) - { + while (!m_peer_change_queue.empty()) { con::PeerChange c = m_peer_change_queue.front(); m_peer_change_queue.pop(); - verbosestream<<"Server: Handling peer change: " - <<"id="<getCommand()].channel, - pkt, - clientCommandFactoryTable[pkt->getCommand()].reliable); + m_clients.send(peer_id, clientCommandFactoryTable[pkt->getCommand()].channel, pkt, + clientCommandFactoryTable[pkt->getCommand()].reliable); } void Server::SendMovement(session_t peer_id) @@ -1350,7 +1354,7 @@ void Server::SendHP(session_t peer_id, u16 hp) void Server::SendBreath(session_t peer_id, u16 breath) { NetworkPacket pkt(TOCLIENT_BREATH, 2, peer_id); - pkt << (u16) breath; + pkt << (u16)breath; Send(&pkt); } @@ -1369,23 +1373,23 @@ void Server::SendAccessDenied(session_t peer_id, AccessDeniedCode reason, Send(&pkt); } -void Server::SendAccessDenied_Legacy(session_t peer_id,const std::wstring &reason) +void Server::SendAccessDenied_Legacy(session_t peer_id, const std::wstring &reason) { NetworkPacket pkt(TOCLIENT_ACCESS_DENIED_LEGACY, 0, peer_id); pkt << reason; Send(&pkt); } -void Server::SendDeathscreen(session_t peer_id, bool set_camera_point_target, - v3f camera_point_target) +void Server::SendDeathscreen( + session_t peer_id, bool set_camera_point_target, v3f camera_point_target) { NetworkPacket pkt(TOCLIENT_DEATHSCREEN, 1 + sizeof(v3f), peer_id); pkt << set_camera_point_target << camera_point_target; Send(&pkt); } -void Server::SendItemDef(session_t peer_id, - IItemDefManager *itemdef, u16 protocol_version) +void Server::SendItemDef( + session_t peer_id, IItemDefManager *itemdef, u16 protocol_version) { NetworkPacket pkt(TOCLIENT_ITEMDEF, 0, peer_id); @@ -1402,13 +1406,13 @@ void Server::SendItemDef(session_t peer_id, // Make data buffer verbosestream << "Server: Sending item definitions to id(" << peer_id - << "): size=" << pkt.getSize() << std::endl; + << "): size=" << pkt.getSize() << std::endl; Send(&pkt); } -void Server::SendNodeDef(session_t peer_id, - const NodeDefManager *nodedef, u16 protocol_version) +void Server::SendNodeDef( + session_t peer_id, const NodeDefManager *nodedef, u16 protocol_version) { NetworkPacket pkt(TOCLIENT_NODEDEF, 0, peer_id); @@ -1426,7 +1430,7 @@ void Server::SendNodeDef(session_t peer_id, // Make data buffer verbosestream << "Server: Sending node definitions to id(" << peer_id - << "): size=" << pkt.getSize() << std::endl; + << "): size=" << pkt.getSize() << std::endl; Send(&pkt); } @@ -1465,7 +1469,8 @@ void Server::SendChatMessage(session_t peer_id, const ChatMessage &message) NetworkPacket pkt(TOCLIENT_CHAT_MESSAGE, 0, peer_id); u8 version = 1; u8 type = message.type; - pkt << version << type << std::wstring(L"") << message.message << (u64)message.timestamp; + pkt << version << type << std::wstring(L"") << message.message + << (u64)message.timestamp; if (peer_id != PEER_ID_INEXISTENT) { RemotePlayer *player = m_env->getPlayer(peer_id); @@ -1479,12 +1484,12 @@ void Server::SendChatMessage(session_t peer_id, const ChatMessage &message) } void Server::SendShowFormspecMessage(session_t peer_id, const std::string &formspec, - const std::string &formname) + const std::string &formname) { NetworkPacket pkt(TOCLIENT_SHOW_FORMSPEC, 0, peer_id); - if (formspec.empty()){ - //the client should close the formspec - //but make sure there wasn't another one open in meantime + if (formspec.empty()) { + // the client should close the formspec + // but make sure there wasn't another one open in meantime const auto it = m_formspec_state_data.find(peer_id); if (it != m_formspec_state_data.end() && it->second == formname) { m_formspec_state_data.erase(peer_id); @@ -1500,11 +1505,12 @@ void Server::SendShowFormspecMessage(session_t peer_id, const std::string &forms } // Spawns a particle on peer with peer_id -void Server::SendSpawnParticle(session_t peer_id, u16 protocol_version, - const ParticleParameters &p) +void Server::SendSpawnParticle( + session_t peer_id, u16 protocol_version, const ParticleParameters &p) { static thread_local const float radius = - g_settings->getS16("max_block_send_distance") * MAP_BLOCKSIZE * BS; + g_settings->getS16("max_block_send_distance") * MAP_BLOCKSIZE * + BS; if (peer_id == PEER_ID_INEXISTENT) { std::vector clients = m_clients.getClientIDs(); @@ -1544,10 +1550,11 @@ void Server::SendSpawnParticle(session_t peer_id, u16 protocol_version, // Adds a ParticleSpawner on peer with peer_id void Server::SendAddParticleSpawner(session_t peer_id, u16 protocol_version, - const ParticleSpawnerParameters &p, u16 attached_id, u32 id) + const ParticleSpawnerParameters &p, u16 attached_id, u32 id) { static thread_local const float radius = - g_settings->getS16("max_block_send_distance") * MAP_BLOCKSIZE * BS; + g_settings->getS16("max_block_send_distance") * MAP_BLOCKSIZE * + BS; if (peer_id == PEER_ID_INEXISTENT) { std::vector clients = m_clients.getClientIDs(); @@ -1566,12 +1573,13 @@ void Server::SendAddParticleSpawner(session_t peer_id, u16 protocol_version, PlayerSAO *sao = player->getPlayerSAO(); if (!sao) continue; - if (sao->getBasePosition().getDistanceFromSQ(pos) > radius_sq) + if (sao->getBasePosition().getDistanceFromSQ(pos) > + radius_sq) continue; } - SendAddParticleSpawner(client_id, player->protocol_version, - p, attached_id, id); + SendAddParticleSpawner(client_id, player->protocol_version, p, + attached_id, id); } return; } @@ -1579,9 +1587,9 @@ void Server::SendAddParticleSpawner(session_t peer_id, u16 protocol_version, NetworkPacket pkt(TOCLIENT_ADD_PARTICLESPAWNER, 100, peer_id); - pkt << p.amount << p.time << p.minpos << p.maxpos << p.minvel - << p.maxvel << p.minacc << p.maxacc << p.minexptime << p.maxexptime - << p.minsize << p.maxsize << p.collisiondetection; + pkt << p.amount << p.time << p.minpos << p.maxpos << p.minvel << p.maxvel + << p.minacc << p.maxacc << p.minexptime << p.maxexptime << p.minsize + << p.maxsize << p.collisiondetection; pkt.putLongString(p.texture); @@ -1607,17 +1615,16 @@ void Server::SendDeleteParticleSpawner(session_t peer_id, u32 id) Send(&pkt); else m_clients.sendToAll(&pkt); - } void Server::SendHUDAdd(session_t peer_id, u32 id, HudElement *form) { - NetworkPacket pkt(TOCLIENT_HUDADD, 0 , peer_id); + NetworkPacket pkt(TOCLIENT_HUDADD, 0, peer_id); - pkt << id << (u8) form->type << form->pos << form->name << form->scale - << form->text << form->number << form->item << form->dir - << form->align << form->offset << form->world_pos << form->size - << form->z_index << form->text2; + pkt << id << (u8)form->type << form->pos << form->name << form->scale + << form->text << form->number << form->item << form->dir << form->align + << form->offset << form->world_pos << form->size << form->z_index + << form->text2; Send(&pkt); } @@ -1632,32 +1639,32 @@ void Server::SendHUDRemove(session_t peer_id, u32 id) void Server::SendHUDChange(session_t peer_id, u32 id, HudElementStat stat, void *value) { NetworkPacket pkt(TOCLIENT_HUDCHANGE, 0, peer_id); - pkt << id << (u8) stat; + pkt << id << (u8)stat; switch (stat) { - case HUD_STAT_POS: - case HUD_STAT_SCALE: - case HUD_STAT_ALIGN: - case HUD_STAT_OFFSET: - pkt << *(v2f *) value; - break; - case HUD_STAT_NAME: - case HUD_STAT_TEXT: - case HUD_STAT_TEXT2: - pkt << *(std::string *) value; - break; - case HUD_STAT_WORLD_POS: - pkt << *(v3f *) value; - break; - case HUD_STAT_SIZE: - pkt << *(v2s32 *) value; - break; - case HUD_STAT_NUMBER: - case HUD_STAT_ITEM: - case HUD_STAT_DIR: - default: - pkt << *(u32 *) value; - break; + case HUD_STAT_POS: + case HUD_STAT_SCALE: + case HUD_STAT_ALIGN: + case HUD_STAT_OFFSET: + pkt << *(v2f *)value; + break; + case HUD_STAT_NAME: + case HUD_STAT_TEXT: + case HUD_STAT_TEXT2: + pkt << *(std::string *)value; + break; + case HUD_STAT_WORLD_POS: + pkt << *(v3f *)value; + break; + case HUD_STAT_SIZE: + pkt << *(v2s32 *)value; + break; + case HUD_STAT_NUMBER: + case HUD_STAT_ITEM: + case HUD_STAT_DIR: + default: + pkt << *(u32 *)value; + break; } Send(&pkt); @@ -1687,26 +1694,26 @@ void Server::SendSetSky(session_t peer_id, const SkyboxParams ¶ms) // Handle prior clients here if (m_clients.getProtocolVersion(peer_id) < 39) { - pkt << params.bgcolor << params.type << (u16) params.textures.size(); + pkt << params.bgcolor << params.type << (u16)params.textures.size(); - for (const std::string& texture : params.textures) + for (const std::string &texture : params.textures) pkt << texture; pkt << params.clouds; } else { // Handle current clients and future clients - pkt << params.bgcolor << params.type - << params.clouds << params.fog_sun_tint - << params.fog_moon_tint << params.fog_tint_type; + pkt << params.bgcolor << params.type << params.clouds + << params.fog_sun_tint << params.fog_moon_tint + << params.fog_tint_type; if (params.type == "skybox") { - pkt << (u16) params.textures.size(); + pkt << (u16)params.textures.size(); for (const std::string &texture : params.textures) pkt << texture; } else if (params.type == "regular") { pkt << params.sky_color.day_sky << params.sky_color.day_horizon - << params.sky_color.dawn_sky << params.sky_color.dawn_horizon - << params.sky_color.night_sky << params.sky_color.night_horizon - << params.sky_color.indoors; + << params.sky_color.dawn_sky << params.sky_color.dawn_horizon + << params.sky_color.night_sky + << params.sky_color.night_horizon << params.sky_color.indoors; } } @@ -1716,9 +1723,8 @@ void Server::SendSetSky(session_t peer_id, const SkyboxParams ¶ms) void Server::SendSetSun(session_t peer_id, const SunParams ¶ms) { NetworkPacket pkt(TOCLIENT_SET_SUN, 0, peer_id); - pkt << params.visible << params.texture - << params.tonemap << params.sunrise - << params.sunrise_visible << params.scale; + pkt << params.visible << params.texture << params.tonemap << params.sunrise + << params.sunrise_visible << params.scale; Send(&pkt); } @@ -1726,8 +1732,7 @@ void Server::SendSetMoon(session_t peer_id, const MoonParams ¶ms) { NetworkPacket pkt(TOCLIENT_SET_MOON, 0, peer_id); - pkt << params.visible << params.texture - << params.tonemap << params.scale; + pkt << params.visible << params.texture << params.tonemap << params.scale; Send(&pkt); } @@ -1735,8 +1740,7 @@ void Server::SendSetStars(session_t peer_id, const StarParams ¶ms) { NetworkPacket pkt(TOCLIENT_SET_STARS, 0, peer_id); - pkt << params.visible << params.count - << params.starcolor << params.scale; + pkt << params.visible << params.count << params.starcolor << params.scale; Send(&pkt); } @@ -1745,17 +1749,15 @@ void Server::SendCloudParams(session_t peer_id, const CloudParams ¶ms) { NetworkPacket pkt(TOCLIENT_CLOUD_PARAMS, 0, peer_id); pkt << params.density << params.color_bright << params.color_ambient - << params.height << params.thickness << params.speed; + << params.height << params.thickness << params.speed; Send(&pkt); } -void Server::SendOverrideDayNightRatio(session_t peer_id, bool do_override, - float ratio) +void Server::SendOverrideDayNightRatio(session_t peer_id, bool do_override, float ratio) { - NetworkPacket pkt(TOCLIENT_OVERRIDE_DAY_NIGHT_RATIO, - 1 + 2, peer_id); + NetworkPacket pkt(TOCLIENT_OVERRIDE_DAY_NIGHT_RATIO, 1 + 2, peer_id); - pkt << do_override << (u16) (ratio * 65535); + pkt << do_override << (u16)(ratio * 65535); Send(&pkt); } @@ -1767,8 +1769,7 @@ void Server::SendTimeOfDay(session_t peer_id, u16 time, f32 time_speed) if (peer_id == PEER_ID_INEXISTENT) { m_clients.sendToAll(&pkt); - } - else { + } else { Send(&pkt); } } @@ -1779,7 +1780,7 @@ void Server::SendPlayerHP(session_t peer_id) assert(playersao); SendHP(peer_id, playersao->getHP()); - m_script->player_event(playersao,"health_changed"); + m_script->player_event(playersao, "health_changed"); // Send to other clients playersao->sendPunchCommand(); @@ -1806,10 +1807,9 @@ void Server::SendMovePlayer(session_t peer_id) { v3f pos = sao->getBasePosition(); verbosestream << "Server: Sending TOCLIENT_MOVE_PLAYER" - << " pos=(" << pos.X << "," << pos.Y << "," << pos.Z << ")" - << " pitch=" << sao->getLookPitch() - << " yaw=" << sao->getRotation().Y - << std::endl; + << " pos=(" << pos.X << "," << pos.Y << "," << pos.Z << ")" + << " pitch=" << sao->getLookPitch() + << " yaw=" << sao->getRotation().Y << std::endl; } Send(&pkt); @@ -1825,14 +1825,13 @@ void Server::SendPlayerFov(session_t peer_id) Send(&pkt); } -void Server::SendLocalPlayerAnimations(session_t peer_id, v2s32 animation_frames[4], - f32 animation_speed) +void Server::SendLocalPlayerAnimations( + session_t peer_id, v2s32 animation_frames[4], f32 animation_speed) { - NetworkPacket pkt(TOCLIENT_LOCAL_PLAYER_ANIMATIONS, 0, - peer_id); + NetworkPacket pkt(TOCLIENT_LOCAL_PLAYER_ANIMATIONS, 0, peer_id); pkt << animation_frames[0] << animation_frames[1] << animation_frames[2] - << animation_frames[3] << animation_speed; + << animation_frames[3] << animation_speed; Send(&pkt); } @@ -1848,14 +1847,14 @@ void Server::SendPlayerPrivileges(session_t peer_id) { RemotePlayer *player = m_env->getPlayer(peer_id); assert(player); - if(player->getPeerId() == PEER_ID_INEXISTENT) + if (player->getPeerId() == PEER_ID_INEXISTENT) return; std::set privs; m_script->getAuth(player->getName(), NULL, &privs); NetworkPacket pkt(TOCLIENT_PRIVILEGES, 0, peer_id); - pkt << (u16) privs.size(); + pkt << (u16)privs.size(); for (const std::string &priv : privs) { pkt << priv; @@ -1893,18 +1892,20 @@ void Server::SendActiveObjectRemoveAdd(RemoteClient *client, PlayerSAO *playersa { // Radius inside which objects are active static thread_local const s16 radius = - g_settings->getS16("active_object_send_range_blocks") * MAP_BLOCKSIZE; + g_settings->getS16("active_object_send_range_blocks") * + MAP_BLOCKSIZE; // Radius inside which players are active static thread_local const bool is_transfer_limited = - g_settings->exists("unlimited_player_transfer_distance") && - !g_settings->getBool("unlimited_player_transfer_distance"); + g_settings->exists("unlimited_player_transfer_distance") && + !g_settings->getBool("unlimited_player_transfer_distance"); static thread_local const s16 player_transfer_dist = - g_settings->getS16("player_transfer_distance") * MAP_BLOCKSIZE; + g_settings->getS16("player_transfer_distance") * MAP_BLOCKSIZE; - s16 player_radius = player_transfer_dist == 0 && is_transfer_limited ? - radius : player_transfer_dist; + s16 player_radius = player_transfer_dist == 0 && is_transfer_limited + ? radius + : player_transfer_dist; s16 my_radius = MYMIN(radius, playersao->getWantedRange() * MAP_BLOCKSIZE); if (my_radius <= 0) @@ -1912,12 +1913,12 @@ void Server::SendActiveObjectRemoveAdd(RemoteClient *client, PlayerSAO *playersa std::queue removed_objects, added_objects; m_env->getRemovedActiveObjects(playersao, my_radius, player_radius, - client->m_known_objects, removed_objects); + client->m_known_objects, removed_objects); m_env->getAddedActiveObjects(playersao, my_radius, player_radius, - client->m_known_objects, added_objects); + client->m_known_objects, added_objects); int removed_count = removed_objects.size(); - int added_count = added_objects.size(); + int added_count = added_objects.size(); if (removed_objects.empty() && added_objects.empty()) return; @@ -1926,15 +1927,15 @@ void Server::SendActiveObjectRemoveAdd(RemoteClient *client, PlayerSAO *playersa std::string data; // Handle removed objects - writeU16((u8*)buf, removed_objects.size()); + writeU16((u8 *)buf, removed_objects.size()); data.append(buf, 2); while (!removed_objects.empty()) { // Get object u16 id = removed_objects.front(); - ServerActiveObject* obj = m_env->getActiveObject(id); + ServerActiveObject *obj = m_env->getActiveObject(id); // Add to data buffer for sending - writeU16((u8*)buf, id); + writeU16((u8 *)buf, id); data.append(buf, 2); // Remove from known objects @@ -1947,7 +1948,7 @@ void Server::SendActiveObjectRemoveAdd(RemoteClient *client, PlayerSAO *playersa } // Handle added objects - writeU16((u8*)buf, added_objects.size()); + writeU16((u8 *)buf, added_objects.size()); data.append(buf, 2); while (!added_objects.empty()) { // Get object @@ -1956,8 +1957,8 @@ void Server::SendActiveObjectRemoveAdd(RemoteClient *client, PlayerSAO *playersa added_objects.pop(); if (!obj) { - warningstream << FUNCTION_NAME << ": NULL object id=" - << (int)id << std::endl; + warningstream << FUNCTION_NAME << ": NULL object id=" << (int)id + << std::endl; continue; } @@ -1965,13 +1966,13 @@ void Server::SendActiveObjectRemoveAdd(RemoteClient *client, PlayerSAO *playersa u8 type = obj->getSendType(); // Add to data buffer for sending - writeU16((u8*)buf, id); + writeU16((u8 *)buf, id); data.append(buf, 2); - writeU8((u8*)buf, type); + writeU8((u8 *)buf, type); data.append(buf, 1); - data.append(serializeLongString( - obj->getClientInitializationData(client->net_proto_version))); + data.append(serializeLongString(obj->getClientInitializationData( + client->net_proto_version))); // Add to known objects client->m_known_objects.insert(id); @@ -1979,32 +1980,35 @@ void Server::SendActiveObjectRemoveAdd(RemoteClient *client, PlayerSAO *playersa obj->m_known_by_count++; } - NetworkPacket pkt(TOCLIENT_ACTIVE_OBJECT_REMOVE_ADD, data.size(), client->peer_id); + NetworkPacket pkt( + TOCLIENT_ACTIVE_OBJECT_REMOVE_ADD, data.size(), client->peer_id); pkt.putRawString(data.c_str(), data.size()); Send(&pkt); - verbosestream << "Server::SendActiveObjectRemoveAdd: " - << removed_count << " removed, " << added_count << " added, " - << "packet size is " << pkt.getSize() << std::endl; + verbosestream << "Server::SendActiveObjectRemoveAdd: " << removed_count + << " removed, " << added_count << " added, " + << "packet size is " << pkt.getSize() << std::endl; } -void Server::SendActiveObjectMessages(session_t peer_id, const std::string &datas, - bool reliable) +void Server::SendActiveObjectMessages( + session_t peer_id, const std::string &datas, bool reliable) { - NetworkPacket pkt(TOCLIENT_ACTIVE_OBJECT_MESSAGES, - datas.size(), peer_id); + NetworkPacket pkt(TOCLIENT_ACTIVE_OBJECT_MESSAGES, datas.size(), peer_id); pkt.putRawString(datas.c_str(), datas.size()); m_clients.send(pkt.getPeerId(), - reliable ? clientCommandFactoryTable[pkt.getCommand()].channel : 1, + reliable ? clientCommandFactoryTable[pkt.getCommand()].channel + : 1, &pkt, reliable); } void Server::SendCSMRestrictionFlags(session_t peer_id) { NetworkPacket pkt(TOCLIENT_CSM_RESTRICTION_FLAGS, - sizeof(m_csm_restriction_flags) + sizeof(m_csm_restriction_noderange), peer_id); + sizeof(m_csm_restriction_flags) + + sizeof(m_csm_restriction_noderange), + peer_id); pkt << m_csm_restriction_flags << m_csm_restriction_noderange; Send(&pkt); } @@ -2026,28 +2030,28 @@ inline s32 Server::nextSoundId() return ret; } -s32 Server::playSound(const SimpleSoundSpec &spec, - const ServerSoundParams ¶ms, bool ephemeral) +s32 Server::playSound(const SimpleSoundSpec &spec, const ServerSoundParams ¶ms, + bool ephemeral) { // Find out initial position of sound bool pos_exists = false; v3f pos = params.getPos(m_env, &pos_exists); // If position is not found while it should be, cancel sound - if(pos_exists != (params.type != ServerSoundParams::SSP_LOCAL)) + if (pos_exists != (params.type != ServerSoundParams::SSP_LOCAL)) return -1; // Filter destination clients std::vector dst_clients; if (!params.to_player.empty()) { RemotePlayer *player = m_env->getPlayer(params.to_player.c_str()); - if(!player){ - infostream<<"Server::playSound: Player \""<getPeerId() == PEER_ID_INEXISTENT) { - infostream<<"Server::playSound: Player \""<getPeerId()); @@ -2067,7 +2071,7 @@ s32 Server::playSound(const SimpleSoundSpec &spec, continue; if (pos_exists) { - if(sao->getBasePosition().getDistanceFrom(pos) > + if (sao->getBasePosition().getDistanceFrom(pos) > params.max_hear_distance) continue; } @@ -2075,7 +2079,7 @@ s32 Server::playSound(const SimpleSoundSpec &spec, } } - if(dst_clients.empty()) + if (dst_clients.empty()) return -1; // Create the sound @@ -2094,10 +2098,8 @@ s32 Server::playSound(const SimpleSoundSpec &spec, float gain = params.gain * spec.gain; NetworkPacket pkt(TOCLIENT_PLAY_SOUND, 0); - pkt << id << spec.name << gain - << (u8) params.type << pos << params.object - << params.loop << params.fade << params.pitch - << ephemeral; + pkt << id << spec.name << gain << (u8)params.type << pos << params.object + << params.loop << params.fade << params.pitch << ephemeral; bool as_reliable = !ephemeral; @@ -2112,7 +2114,7 @@ void Server::stopSound(s32 handle) { // Get sound reference std::unordered_map::iterator i = - m_playing_sounds.find(handle); + m_playing_sounds.find(handle); if (i == m_playing_sounds.end()) return; ServerPlayingSound &psound = i->second; @@ -2133,7 +2135,7 @@ void Server::fadeSound(s32 handle, float step, float gain) { // Get sound reference std::unordered_map::iterator i = - m_playing_sounds.find(handle); + m_playing_sounds.find(handle); if (i == m_playing_sounds.end()) return; @@ -2175,8 +2177,8 @@ void Server::fadeSound(s32 handle, float step, float gain) } } -void Server::sendRemoveNode(v3s16 p, std::unordered_set *far_players, - float far_d_nodes) +void Server::sendRemoveNode( + v3s16 p, std::unordered_set *far_players, float far_d_nodes) { float maxd = far_d_nodes * BS; v3f p_f = intToFloat(p, BS); @@ -2197,8 +2199,9 @@ void Server::sendRemoveNode(v3s16 p, std::unordered_set *far_players, PlayerSAO *sao = player ? player->getPlayerSAO() : nullptr; // If player is far away, only set modified blocks not sent - if (!client->isBlockSent(block_pos) || (sao && - sao->getBasePosition().getDistanceFrom(p_f) > maxd)) { + if (!client->isBlockSent(block_pos) || + (sao && sao->getBasePosition().getDistanceFrom(p_f) > + maxd)) { if (far_players) far_players->emplace(client_id); else @@ -2221,8 +2224,7 @@ void Server::sendAddNode(v3s16 p, MapNode n, std::unordered_set *far_player v3s16 block_pos = getNodeBlockPos(p); NetworkPacket pkt(TOCLIENT_ADDNODE, 6 + 2 + 1 + 1 + 1); - pkt << p << n.param0 << n.param1 << n.param2 - << (u8) (remove_metadata ? 0 : 1); + pkt << p << n.param0 << n.param1 << n.param2 << (u8)(remove_metadata ? 0 : 1); std::vector clients = m_clients.getClientIDs(); m_clients.lock(); @@ -2236,8 +2238,9 @@ void Server::sendAddNode(v3s16 p, MapNode n, std::unordered_set *far_player PlayerSAO *sao = player ? player->getPlayerSAO() : nullptr; // If player is far away, only set modified blocks not sent - if (!client->isBlockSent(block_pos) || (sao && - sao->getBasePosition().getDistanceFrom(p_f) > maxd)) { + if (!client->isBlockSent(block_pos) || + (sao && sao->getBasePosition().getDistanceFrom(p_f) > + maxd)) { if (far_players) far_players->emplace(client_id); else @@ -2275,8 +2278,9 @@ void Server::sendMetadataChanged(const std::list &meta_updates, float far continue; v3s16 block_pos = getNodeBlockPos(pos); - if (!client->isBlockSent(block_pos) || (player && - player_pos.getDistanceFrom(intToFloat(pos, BS)) > maxd)) { + if (!client->isBlockSent(block_pos) || + (player && player_pos.getDistanceFrom(intToFloat( + pos, BS)) > maxd)) { client->SetBlockNotSent(block_pos); continue; } @@ -2303,8 +2307,8 @@ void Server::sendMetadataChanged(const std::list &meta_updates, float far m_clients.unlock(); } -void Server::SendBlockNoLock(session_t peer_id, MapBlock *block, u8 ver, - u16 net_proto_version) +void Server::SendBlockNoLock( + session_t peer_id, MapBlock *block, u8 ver, u16 net_proto_version) { /* Create a packet with the block in the right format @@ -2325,7 +2329,7 @@ void Server::SendBlockNoLock(session_t peer_id, MapBlock *block, u8 ver, void Server::SendBlocks(float dtime) { MutexAutoLock envlock(m_env_mutex); - //TODO check if one big lock could be faster then multiple small ones + // TODO check if one big lock could be faster then multiple small ones std::vector queue; @@ -2338,13 +2342,14 @@ void Server::SendBlocks(float dtime) m_clients.lock(); for (const session_t client_id : clients) { - RemoteClient *client = m_clients.lockedGetClientNoEx(client_id, CS_Active); + RemoteClient *client = m_clients.lockedGetClientNoEx( + client_id, CS_Active); if (!client) continue; total_sending += client->getSendingCount(); - client->GetNextBlocks(m_env,m_emerge, dtime, queue); + client->GetNextBlocks(m_env, m_emerge, dtime, queue); } m_clients.unlock(); } @@ -2358,8 +2363,12 @@ void Server::SendBlocks(float dtime) // Maximal total count calculation // The per-client block sends is halved with the maximal online users - u32 max_blocks_to_send = (m_env->getPlayerCount() + g_settings->getU32("max_users")) * - g_settings->getU32("max_simultaneous_block_sends_per_client") / 4 + 1; + u32 max_blocks_to_send = + (m_env->getPlayerCount() + g_settings->getU32("max_users")) * + g_settings->getU32("max_simultaneous_block_sends_" + "per_client") / + 4 + + 1; ScopeProfiler sp(g_profiler, "Server::SendBlocks(): Send to clients"); Map &map = m_env->getMap(); @@ -2372,13 +2381,13 @@ void Server::SendBlocks(float dtime) if (!block) continue; - RemoteClient *client = m_clients.lockedGetClientNoEx(block_to_send.peer_id, - CS_Active); + RemoteClient *client = m_clients.lockedGetClientNoEx( + block_to_send.peer_id, CS_Active); if (!client) continue; - SendBlockNoLock(block_to_send.peer_id, block, client->serialization_version, - client->net_proto_version); + SendBlockNoLock(block_to_send.peer_id, block, + client->serialization_version, client->net_proto_version); client->SentBlock(block_to_send.pos); total_sending++; @@ -2405,29 +2414,23 @@ bool Server::SendBlock(session_t peer_id, const v3s16 &blockpos) return true; } -bool Server::addMediaFile(const std::string &filename, - const std::string &filepath, std::string *filedata_to, - std::string *digest_to) +bool Server::addMediaFile(const std::string &filename, const std::string &filepath, + std::string *filedata_to, std::string *digest_to) { // If name contains illegal characters, ignore the file if (!string_allowed(filename, TEXTURENAME_ALLOWED_CHARS)) { - infostream << "Server: ignoring illegal file name: \"" - << filename << "\"" << std::endl; + infostream << "Server: ignoring illegal file name: \"" << filename << "\"" + << std::endl; return false; } // If name is not in a supported format, ignore it - const char *supported_ext[] = { - ".png", ".jpg", ".bmp", ".tga", - ".pcx", ".ppm", ".psd", ".wal", ".rgb", - ".ogg", - ".x", ".b3d", ".md2", ".obj", - // Custom translation file format - ".tr", - NULL - }; + const char *supported_ext[] = {".png", ".jpg", ".bmp", ".tga", ".pcx", ".ppm", + ".psd", ".wal", ".rgb", ".ogg", ".x", ".b3d", ".md2", ".obj", + // Custom translation file format + ".tr", NULL}; if (removeStringEnd(filename, supported_ext).empty()) { infostream << "Server: ignoring unsupported file extension: \"" - << filename << "\"" << std::endl; + << filename << "\"" << std::endl; return false; } // Ok, attempt to load the file and add to cache @@ -2435,8 +2438,8 @@ bool Server::addMediaFile(const std::string &filename, // Read data std::ifstream fis(filepath.c_str(), std::ios_base::binary); if (!fis.good()) { - errorstream << "Server::addMediaFile(): Could not open \"" - << filename << "\" for reading" << std::endl; + errorstream << "Server::addMediaFile(): Could not open \"" << filename + << "\" for reading" << std::endl; return false; } std::string filedata; @@ -2454,12 +2457,12 @@ bool Server::addMediaFile(const std::string &filename, } } if (bad) { - errorstream << "Server::addMediaFile(): Failed to read \"" - << filename << "\"" << std::endl; + errorstream << "Server::addMediaFile(): Failed to read \"" << filename + << "\"" << std::endl; return false; } else if (filedata.empty()) { - errorstream << "Server::addMediaFile(): Empty file \"" - << filepath << "\"" << std::endl; + errorstream << "Server::addMediaFile(): Empty file \"" << filepath << "\"" + << std::endl; return false; } @@ -2468,15 +2471,14 @@ bool Server::addMediaFile(const std::string &filename, unsigned char *digest = sha1.getDigest(); std::string sha1_base64 = base64_encode(digest, 20); - std::string sha1_hex = hex_encode((char*) digest, 20); + std::string sha1_hex = hex_encode((char *)digest, 20); if (digest_to) - *digest_to = std::string((char*) digest, 20); + *digest_to = std::string((char *)digest, 20); free(digest); // Put in list m_media[filename] = MediaInfo(filepath, sha1_base64); - verbosestream << "Server: " << sha1_hex << " is " << filename - << std::endl; + verbosestream << "Server: " << sha1_hex << " is " << filename << std::endl; if (filedata_to) *filedata_to = std::move(filedata); @@ -2491,7 +2493,8 @@ void Server::fillMediaCache() std::vector paths; m_modmgr->getModsMediaPaths(paths); fs::GetRecursiveDirs(paths, m_gamespec.path + DIR_DELIM + "textures"); - fs::GetRecursiveDirs(paths, porting::path_user + DIR_DELIM + "textures" + DIR_DELIM + "server"); + fs::GetRecursiveDirs(paths, porting::path_user + DIR_DELIM + "textures" + + DIR_DELIM + "server"); // Collect media file information from paths into cache for (const std::string &mediapath : paths) { @@ -2505,7 +2508,8 @@ void Server::fillMediaCache() } } - infostream << "Server: " << m_media.size() << " media files collected" << std::endl; + infostream << "Server: " << m_media.size() << " media files collected" + << std::endl; } void Server::sendMediaAnnouncement(session_t peer_id, const std::string &lang_code) @@ -2534,7 +2538,8 @@ void Server::sendMediaAnnouncement(session_t peer_id, const std::string &lang_co Send(&pkt); verbosestream << "Server: Announcing files to id(" << peer_id - << "): count=" << media_sent << " size=" << pkt.getSize() << std::endl; + << "): count=" << media_sent << " size=" << pkt.getSize() + << std::endl; } struct SendableMedia @@ -2543,78 +2548,77 @@ struct SendableMedia std::string path; std::string data; - SendableMedia(const std::string &name_="", const std::string &path_="", - const std::string &data_=""): - name(name_), - path(path_), - data(data_) - {} + SendableMedia(const std::string &name_ = "", const std::string &path_ = "", + const std::string &data_ = "") : + name(name_), + path(path_), data(data_) + { + } }; -void Server::sendRequestedMedia(session_t peer_id, - const std::vector &tosend) +void Server::sendRequestedMedia(session_t peer_id, const std::vector &tosend) { - verbosestream<<"Server::sendRequestedMedia(): " - <<"Sending files to client"< > file_bunches; + std::vector> file_bunches; file_bunches.emplace_back(); u32 file_size_bunch_total = 0; for (const std::string &name : tosend) { if (m_media.find(name) == m_media.end()) { - errorstream<<"Server::sendRequestedMedia(): Client asked for " - <<"unknown file \""<<(name)<<"\""<= bytes_per_bunch) { + if (file_size_bunch_total >= bytes_per_bunch) { file_bunches.emplace_back(); file_size_bunch_total = 0; } - } /* Create and send packets */ @@ -2635,22 +2639,22 @@ void Server::sendRequestedMedia(session_t peer_id, */ NetworkPacket pkt(TOCLIENT_MEDIA, 4 + 0, peer_id); - pkt << num_bunches << i << (u32) file_bunches[i].size(); + pkt << num_bunches << i << (u32)file_bunches[i].size(); for (const SendableMedia &j : file_bunches[i]) { pkt << j.name; pkt.putLongString(j.data); } - verbosestream << "Server::sendRequestedMedia(): bunch " - << i << "/" << num_bunches - << " files=" << file_bunches[i].size() - << " size=" << pkt.getSize() << std::endl; + verbosestream << "Server::sendRequestedMedia(): bunch " << i << "/" + << num_bunches << " files=" << file_bunches[i].size() + << " size=" << pkt.getSize() << std::endl; Send(&pkt); } } -void Server::sendDetachedInventory(Inventory *inventory, const std::string &name, session_t peer_id) +void Server::sendDetachedInventory( + Inventory *inventory, const std::string &name, session_t peer_id) { NetworkPacket pkt(TOCLIENT_DETACHED_INVENTORY, 0, peer_id); pkt << name; @@ -2666,7 +2670,8 @@ void Server::sendDetachedInventory(Inventory *inventory, const std::string &name inventory->setModified(false); const std::string &os_str = os.str(); - pkt << static_cast(os_str.size()); // HACK: to keep compatibility with 5.0.0 clients + pkt << static_cast(os_str.size()); // HACK: to keep compatibility + // with 5.0.0 clients pkt.putRawString(os_str); } @@ -2700,9 +2705,8 @@ void Server::DiePlayer(session_t peer_id, const PlayerHPChangeReason &reason) PlayerSAO *playersao = getPlayerSAO(peer_id); assert(playersao); - infostream << "Server::DiePlayer(): Player " - << playersao->getPlayer()->getName() - << " dies" << std::endl; + infostream << "Server::DiePlayer(): Player " << playersao->getPlayer()->getName() + << " dies" << std::endl; playersao->setHP(0, reason); playersao->clearParentAttachment(); @@ -2711,7 +2715,7 @@ void Server::DiePlayer(session_t peer_id, const PlayerHPChangeReason &reason) m_script->on_dieplayer(playersao, reason); SendPlayerHP(peer_id); - SendDeathscreen(peer_id, false, v3f(0,0,0)); + SendDeathscreen(peer_id, false, v3f(0, 0, 0)); } void Server::RespawnPlayer(session_t peer_id) @@ -2720,8 +2724,7 @@ void Server::RespawnPlayer(session_t peer_id) assert(playersao); infostream << "Server::RespawnPlayer(): Player " - << playersao->getPlayer()->getName() - << " respawns" << std::endl; + << playersao->getPlayer()->getName() << " respawns" << std::endl; playersao->setHP(playersao->accessObjectProperties()->hp_max, PlayerHPChangeReason(PlayerHPChangeReason::RESPAWN)); @@ -2736,16 +2739,14 @@ void Server::RespawnPlayer(session_t peer_id) SendPlayerHP(peer_id); } - void Server::DenySudoAccess(session_t peer_id) { NetworkPacket pkt(TOCLIENT_DENY_SUDO_MODE, 0, peer_id); Send(&pkt); } - -void Server::DenyAccessVerCompliant(session_t peer_id, u16 proto_ver, AccessDeniedCode reason, - const std::string &str_reason, bool reconnect) +void Server::DenyAccessVerCompliant(session_t peer_id, u16 proto_ver, + AccessDeniedCode reason, const std::string &str_reason, bool reconnect) { SendAccessDenied(peer_id, reason, str_reason, reconnect); @@ -2753,7 +2754,6 @@ void Server::DenyAccessVerCompliant(session_t peer_id, u16 proto_ver, AccessDeni DisconnectPeer(peer_id); } - void Server::DenyAccess(session_t peer_id, AccessDeniedCode reason, const std::string &custom_reason) { @@ -2780,7 +2780,7 @@ void Server::DisconnectPeer(session_t peer_id) void Server::acceptAuth(session_t peer_id, bool forSudoMode) { if (!forSudoMode) { - RemoteClient* client = getClient(peer_id, CS_Invalid); + RemoteClient *client = getClient(peer_id, CS_Invalid); NetworkPacket resp_pkt(TOCLIENT_AUTH_ACCEPT, 1 + 6 + 8 + 4, peer_id); @@ -2788,9 +2788,9 @@ void Server::acceptAuth(session_t peer_id, bool forSudoMode) u32 sudo_auth_mechs = client->allowed_auth_mechs; client->allowed_sudo_mechs = sudo_auth_mechs; - resp_pkt << v3f(0,0,0) << (u64) m_env->getServerMap().getSeed() - << g_settings->getFloat("dedicated_server_step") - << sudo_auth_mechs; + resp_pkt << v3f(0, 0, 0) << (u64)m_env->getServerMap().getSeed() + << g_settings->getFloat("dedicated_server_step") + << sudo_auth_mechs; Send(&resp_pkt); m_clients.event(peer_id, CSE_AuthAccept); @@ -2813,8 +2813,9 @@ void Server::DeleteClient(session_t peer_id, ClientDeletionReason reason) /* Clear references to playing sounds */ - for (std::unordered_map::iterator - i = m_playing_sounds.begin(); i != m_playing_sounds.end();) { + for (std::unordered_map::iterator i = + m_playing_sounds.begin(); + i != m_playing_sounds.end();) { ServerPlayingSound &psound = i->second; psound.clients.erase(peer_id); if (psound.clients.empty()) @@ -2838,9 +2839,11 @@ void Server::DeleteClient(session_t peer_id, ClientDeletionReason reason) // inform connected clients const std::string &player_name = player->getName(); - NetworkPacket notice(TOCLIENT_UPDATE_PLAYER_LIST, 0, PEER_ID_INEXISTENT); - // (u16) 1 + std::string represents a vector serialization representation - notice << (u8) PLAYER_LIST_REMOVE << (u16) 1 << player_name; + NetworkPacket notice(TOCLIENT_UPDATE_PLAYER_LIST, 0, + PEER_ID_INEXISTENT); + // (u16) 1 + std::string represents a vector serialization + // representation + notice << (u8)PLAYER_LIST_REMOVE << (u16)1 << player_name; m_clients.sendToAll(¬ice); // run scripts m_script->on_leaveplayer(playersao, reason == CDR_TIMEOUT); @@ -2858,7 +2861,8 @@ void Server::DeleteClient(session_t peer_id, ClientDeletionReason reason) for (const session_t client_id : clients) { // Get player - RemotePlayer *player = m_env->getPlayer(client_id); + RemotePlayer *player = + m_env->getPlayer(client_id); if (!player) continue; @@ -2868,11 +2872,14 @@ void Server::DeleteClient(session_t peer_id, ClientDeletionReason reason) std::string name = player->getName(); actionstream << name << " " - << (reason == CDR_TIMEOUT ? "times out." : "leaves game.") - << " List of players: " << os.str() << std::endl; + << (reason == CDR_TIMEOUT ? "times out." + : "leaves game.") + << " List of players: " << os.str() + << std::endl; if (m_admin_chat) m_admin_chat->outgoing_queue.push_back( - new ChatEventNick(CET_NICK_REMOVE, name)); + new ChatEventNick(CET_NICK_REMOVE, + name)); } } { @@ -2903,8 +2910,8 @@ void Server::UpdateCrafting(RemotePlayer *player) loc.setPlayer(player->getName()); std::vector output_replacements; getCraftingResult(&player->inventory, preview, output_replacements, false, this); - m_env->getScriptIface()->item_CraftPredict(preview, player->getPlayerSAO(), - clist, loc); + m_env->getScriptIface()->item_CraftPredict( + preview, player->getPlayerSAO(), clist, loc); InventoryList *plist = player->inventory.getList("craftpreview"); if (plist && plist->getSize() >= 1) { @@ -2920,11 +2927,17 @@ void Server::handleChatInterfaceEvent(ChatEvent *evt) m_admin_nick = ((ChatEventNick *)evt)->nick; if (!m_script->getAuth(m_admin_nick, NULL, NULL)) { errorstream << "You haven't set up an account." << std::endl - << "Please log in using the client as '" - << m_admin_nick << "' with a secure password." << std::endl - << "Until then, you can't execute admin tasks via the console," << std::endl - << "and everybody can claim the user account instead of you," << std::endl - << "giving them full control over this server." << std::endl; + << "Please log in using the client as '" + << m_admin_nick << "' with a secure password." + << std::endl + << "Until then, you can't execute admin tasks via " + "the console," + << std::endl + << "and everybody can claim the user account instead " + "of you," + << std::endl + << "giving them full control over this server." + << std::endl; } } else { assert(evt->type == CET_CHAT); @@ -2933,11 +2946,10 @@ void Server::handleChatInterfaceEvent(ChatEvent *evt) } std::wstring Server::handleChat(const std::string &name, const std::wstring &wname, - std::wstring wmessage, bool check_shout_priv, RemotePlayer *player) + std::wstring wmessage, bool check_shout_priv, RemotePlayer *player) { // If something goes wrong, this player is to blame - RollbackScopeActor rollback_scope(m_rollback, - std::string("player:") + name); + RollbackScopeActor rollback_scope(m_rollback, std::string("player:") + name); if (g_settings->getBool("strip_color_codes")) wmessage = unescape_enriched(wmessage); @@ -2947,8 +2959,8 @@ std::wstring Server::handleChat(const std::string &name, const std::wstring &wna case RPLAYER_CHATRESULT_FLOODING: { std::wstringstream ws; ws << L"You cannot send more messages. You are limited to " - << g_settings->getFloat("chat_message_limit_per_10sec") - << L" messages per 10 seconds."; + << g_settings->getFloat("chat_message_limit_per_10sec") + << L" messages per 10 seconds."; return ws.str(); } case RPLAYER_CHATRESULT_KICK: @@ -2962,10 +2974,11 @@ std::wstring Server::handleChat(const std::string &name, const std::wstring &wna } } - if (m_max_chatmessage_length > 0 - && wmessage.length() > m_max_chatmessage_length) { - return L"Your message exceed the maximum chat message limit set on the server. " - L"It was refused. Send a shorter message"; + if (m_max_chatmessage_length > 0 && + wmessage.length() > m_max_chatmessage_length) { + return L"Your message exceed the maximum chat message limit set on the " + L"server. " + L"It was refused. Send a shorter message"; } auto message = trim(wide_to_utf8(wmessage)); @@ -2993,8 +3006,8 @@ std::wstring Server::handleChat(const std::string &name, const std::wstring &wna #ifdef __ANDROID__ line += L"<" + wname + L"> " + wmessage; #else - line += narrow_to_wide(m_script->formatChatMessage(name, - wide_to_narrow(wmessage))); + line += narrow_to_wide(m_script->formatChatMessage( + name, wide_to_narrow(wmessage))); #endif } @@ -3017,7 +3030,7 @@ std::wstring Server::handleChat(const std::string &name, const std::wstring &wna */ session_t peer_id_to_avoid_sending = - (player ? player->getPeerId() : PEER_ID_INEXISTENT); + (player ? player->getPeerId() : PEER_ID_INEXISTENT); if (player && player->protocol_version >= 29) peer_id_to_avoid_sending = PEER_ID_INEXISTENT; @@ -3045,8 +3058,8 @@ void Server::handleAdminChat(const ChatEventChat *evt) RemoteClient *Server::getClient(session_t peer_id, ClientState state_min) { - RemoteClient *client = getClientNoEx(peer_id,state_min); - if(!client) + RemoteClient *client = getClientNoEx(peer_id, state_min); + if (!client) throw ClientNotFoundException("Client not found"); return client; @@ -3060,7 +3073,7 @@ std::string Server::getPlayerName(session_t peer_id) { RemotePlayer *player = m_env->getPlayer(peer_id); if (!player) - return "[id="+itos(peer_id)+"]"; + return "[id=" + itos(peer_id) + "]"; return player->getName(); } @@ -3107,11 +3120,12 @@ std::wstring Server::getStatusString() } os << L"}"; - if (m_env && !((ServerMap*)(&m_env->getMap()))->isSavingEnabled()) + if (m_env && !((ServerMap *)(&m_env->getMap()))->isSavingEnabled()) os << std::endl << L"# Server: " << " WARNING: Map saving is disabled."; if (!g_settings->get("motd").empty()) - os << std::endl << L"# Server: " << narrow_to_wide(g_settings->get("motd")); + os << std::endl + << L"# Server: " << narrow_to_wide(g_settings->get("motd")); return os.str(); } @@ -3143,11 +3157,9 @@ void Server::reportPrivsModified(const std::string &name) return; SendPlayerPrivileges(player->getPeerId()); PlayerSAO *sao = player->getPlayerSAO(); - if(!sao) + if (!sao) return; - sao->updatePrivileges( - getPlayerEffectivePrivs(name), - isSingleplayer()); + sao->updatePrivileges(getPlayerEffectivePrivs(name), isSingleplayer()); } } @@ -3204,7 +3216,7 @@ void Server::notifyPlayer(const char *name, const std::wstring &msg) } bool Server::showFormspec(const char *playername, const std::string &formspec, - const std::string &formname) + const std::string &formname) { // m_env will be NULL if the server is initializing if (!m_env) @@ -3230,11 +3242,12 @@ u32 Server::hudAdd(RemotePlayer *player, HudElement *form) return id; } -bool Server::hudRemove(RemotePlayer *player, u32 id) { +bool Server::hudRemove(RemotePlayer *player, u32 id) +{ if (!player) return false; - HudElement* todel = player->removeHud(id); + HudElement *todel = player->removeHud(id); if (!todel) return false; @@ -3263,7 +3276,7 @@ bool Server::hudSetFlags(RemotePlayer *player, u32 flags, u32 mask) player->hud_flags &= ~mask; player->hud_flags |= flags; - PlayerSAO* playersao = player->getPlayerSAO(); + PlayerSAO *playersao = player->getPlayerSAO(); if (!playersao) return false; @@ -3310,8 +3323,8 @@ Address Server::getPeerAddress(session_t peer_id) return m_con->GetPeerAddress(peer_id); } -void Server::setLocalPlayerAnimations(RemotePlayer *player, - v2s32 animation_frames[4], f32 frame_speed) +void Server::setLocalPlayerAnimations( + RemotePlayer *player, v2s32 animation_frames[4], f32 frame_speed) { sanity_check(player); player->setLocalAnimations(animation_frames, frame_speed); @@ -3361,8 +3374,7 @@ void Server::setClouds(RemotePlayer *player, const CloudParams ¶ms) SendCloudParams(player->getPeerId(), params); } -void Server::overrideDayNightRatio(RemotePlayer *player, bool do_override, - float ratio) +void Server::overrideDayNightRatio(RemotePlayer *player, bool do_override, float ratio) { sanity_check(player); player->overrideDayNightRatio(do_override, ratio); @@ -3374,8 +3386,7 @@ void Server::notifyPlayers(const std::wstring &msg) SendChatMessage(PEER_ID_INEXISTENT, ChatMessage(msg)); } -void Server::spawnParticle(const std::string &playername, - const ParticleParameters &p) +void Server::spawnParticle(const std::string &playername, const ParticleParameters &p) { // m_env will be NULL if the server is initializing if (!m_env) @@ -3395,7 +3406,7 @@ void Server::spawnParticle(const std::string &playername, } u32 Server::addParticleSpawner(const ParticleSpawnerParameters &p, - ServerActiveObject *attached, const std::string &playername) + ServerActiveObject *attached, const std::string &playername) { // m_env will be NULL if the server is initializing if (!m_env) @@ -3427,7 +3438,8 @@ void Server::deleteParticleSpawner(const std::string &playername, u32 id) { // m_env will be NULL if the server is initializing if (!m_env) - throw ServerError("Can't delete particle spawners during initialisation!"); + throw ServerError( + "Can't delete particle spawners during initialisation!"); session_t peer_id = PEER_ID_INEXISTENT; if (!playername.empty()) { @@ -3446,7 +3458,7 @@ bool Server::dynamicAddMedia(const std::string &filepath) std::string filename = fs::GetFilenameFromPath(filepath.c_str()); if (m_media.find(filename) != m_media.end()) { errorstream << "Server::dynamicAddMedia(): file \"" << filename - << "\" already exists in media cache" << std::endl; + << "\" already exists in media cache" << std::endl; return false; } @@ -3458,17 +3470,16 @@ bool Server::dynamicAddMedia(const std::string &filepath) // Push file to existing clients NetworkPacket pkt(TOCLIENT_MEDIA_PUSH, 0); - pkt << raw_hash << filename << (bool) true; + pkt << raw_hash << filename << (bool)true; pkt.putLongString(filedata); auto client_ids = m_clients.getClientIDs(CS_DefinitionsSent); for (session_t client_id : client_ids) { /* - The network layer only guarantees ordered delivery inside a channel. - Since the very next packet could be one that uses the media, we have - to push the media over ALL channels to ensure it is processed before - it is used. - In practice this means we have to send it twice: + The network layer only guarantees ordered delivery inside a + channel. Since the very next packet could be one that uses the media, + we have to push the media over ALL channels to ensure it is processed + before it is used. In practice this means we have to send it twice: - channel 1 (HUD) - channel 0 (everything else: e.g. play_sound, object messages) */ @@ -3481,11 +3492,12 @@ bool Server::dynamicAddMedia(const std::string &filepath) // actions: time-reversed list // Return value: success/failure -bool Server::rollbackRevertActions(const std::list &actions, - std::list *log) +bool Server::rollbackRevertActions( + const std::list &actions, std::list *log) { - infostream<<"Server::rollbackRevertActions(len="<getMap()); + infostream << "Server::rollbackRevertActions(len=" << actions.size() << ")" + << std::endl; + ServerMap *map = (ServerMap *)(&m_env->getMap()); // Fail if no actions to handle if (actions.empty()) { @@ -3500,27 +3512,31 @@ bool Server::rollbackRevertActions(const std::list &actions, for (const RollbackAction &action : actions) { num_tried++; bool success = action.applyRevert(map, m_inventory_mgr.get(), this); - if(!success){ + if (!success) { num_failed++; std::ostringstream os; - os<<"Revert of step ("<push_back(os.str()); - }else{ + } else { std::ostringstream os; - os<<"Successfully reverted step ("<push_back(os.str()); } } - infostream<<"Map::rollbackRevertActions(): "< & Server::getMods() const +const std::vector &Server::getMods() const { return m_modmgr->getMods(); } @@ -3600,9 +3616,8 @@ v3f Server::findSpawnPos() for (s32 i = 0; i < 4000 && !is_good; i++) { s32 range = MYMIN(1 + i, range_max); // We're going to try to throw the player to this position - v2s16 nodepos2d = v2s16( - -range + (myrand() % (range * 2)), - -range + (myrand() % (range * 2))); + v2s16 nodepos2d = v2s16(-range + (myrand() % (range * 2)), + -range + (myrand() % (range * 2))); // Get spawn level at point s16 spawn_level = m_emerge->getSpawnLevelAtPoint(nodepos2d); // Continue if MAX_MAP_GENERATION_LIMIT was returned by the mapgen to @@ -3625,9 +3640,10 @@ v3f Server::findSpawnPos() map.emergeBlock(blockpos, true); content_t c = map.getNode(nodepos).getContent(); - // In generated mapblocks allow spawn in all 'airlike' drawtype nodes. - // In ungenerated mapblocks allow spawn in 'ignore' nodes. - if (m_nodedef->get(c).drawtype == NDT_AIRLIKE || c == CONTENT_IGNORE) { + // In generated mapblocks allow spawn in all 'airlike' drawtype + // nodes. In ungenerated mapblocks allow spawn in 'ignore' nodes. + if (m_nodedef->get(c).drawtype == NDT_AIRLIKE || + c == CONTENT_IGNORE) { air_count++; if (air_count >= 2) { // Spawn in lower empty node @@ -3635,10 +3651,12 @@ v3f Server::findSpawnPos() nodeposf = intToFloat(nodepos, BS); // Don't spawn the player outside map boundaries if (objectpos_over_limit(nodeposf)) - // Exit this loop, positions above are probably over limit + // Exit this loop, positions above are + // probably over limit break; - // Good position found, cause an exit from main loop + // Good position found, cause an exit from main + // loop is_good = true; break; } @@ -3659,7 +3677,7 @@ v3f Server::findSpawnPos() void Server::requestShutdown(const std::string &msg, bool reconnect, float delay) { if (delay == 0.0f) { - // No delay, shutdown immediately + // No delay, shutdown immediately m_shutdown_state.is_requested = true; // only print to the infostream, a chat message saying // "Server Shutting Down" is sent when the server destructs. @@ -3676,12 +3694,11 @@ void Server::requestShutdown(const std::string &msg, bool reconnect, float delay // m_shutdown_* are already handled, skip. return; } else if (delay > 0.0f) { - // Positive delay, tell the clients when the server will shut down + // Positive delay, tell the clients when the server will shut down std::wstringstream ws; ws << L"*** Server shutting down in " - << duration_to_string(myround(delay)).c_str() - << "."; + << duration_to_string(myround(delay)).c_str() << "."; infostream << wide_to_utf8(ws.str()).c_str() << std::endl; SendChatMessage(PEER_ID_INEXISTENT, ws.str()); @@ -3690,7 +3707,7 @@ void Server::requestShutdown(const std::string &msg, bool reconnect, float delay m_shutdown_state.trigger(delay, msg, reconnect); } -PlayerSAO* Server::emergePlayer(const char *name, session_t peer_id, u16 proto_version) +PlayerSAO *Server::emergePlayer(const char *name, session_t peer_id, u16 proto_version) { /* Try to get an existing player @@ -3699,7 +3716,7 @@ PlayerSAO* Server::emergePlayer(const char *name, session_t peer_id, u16 proto_v // If player is already connected, cancel if (player && player->getPeerId() != PEER_ID_INEXISTENT) { - infostream<<"emergePlayer(): Player already connected"<getPlayer(peer_id)) { - infostream<<"emergePlayer(): Player with wrong name but same" - " peer_id already exists"<loadPlayer(player, &newplayer, peer_id, isSingleplayer()); + PlayerSAO *playersao = + m_env->loadPlayer(player, &newplayer, peer_id, isSingleplayer()); // Complete init with server parts playersao->finalize(player, getPlayerEffectivePrivs(player->getName())); @@ -3737,7 +3756,7 @@ bool Server::registerModStorage(ModMetadata *storage) { if (m_mod_storages.find(storage->getModName()) != m_mod_storages.end()) { errorstream << "Unable to register same mod storage twice. Storage name: " - << storage->getModName() << std::endl; + << storage->getModName() << std::endl; return false; } @@ -3747,7 +3766,8 @@ bool Server::registerModStorage(ModMetadata *storage) void Server::unregisterModStorage(const std::string &name) { - std::unordered_map::const_iterator it = m_mod_storages.find(name); + std::unordered_map::const_iterator it = + m_mod_storages.find(name); if (it != m_mod_storages.end()) { // Save unconditionaly on unregistration it->second->save(getModStoragePath()); @@ -3757,7 +3777,7 @@ void Server::unregisterModStorage(const std::string &name) void dedicated_server_loop(Server &server, bool &kill) { - verbosestream<<"dedicated_server_loop()"<print(infostream); g_profiler->clear(); } @@ -3796,8 +3815,8 @@ void dedicated_server_loop(Server &server, bool &kill) infostream << "Dedicated server quitting" << std::endl; #if USE_CURL if (g_settings->getBool("server_announce")) - ServerList::sendAnnounce(ServerList::AA_DELETE, - server.m_bind_addr.getPort()); + ServerList::sendAnnounce( + ServerList::AA_DELETE, server.m_bind_addr.getPort()); #endif } @@ -3805,11 +3824,10 @@ void dedicated_server_loop(Server &server, bool &kill) * Mod channels */ - bool Server::joinModChannel(const std::string &channel) { return m_modchannel_mgr->joinChannel(channel, PEER_ID_SERVER) && - m_modchannel_mgr->setChannelState(channel, MODCHANNEL_STATE_READ_WRITE); + m_modchannel_mgr->setChannelState(channel, MODCHANNEL_STATE_READ_WRITE); } bool Server::leaveModChannel(const std::string &channel) @@ -3826,7 +3844,7 @@ bool Server::sendModChannelMessage(const std::string &channel, const std::string return true; } -ModChannel* Server::getModChannel(const std::string &channel) +ModChannel *Server::getModChannel(const std::string &channel) { return m_modchannel_mgr->getModChannel(channel); } @@ -3840,8 +3858,8 @@ void Server::broadcastModChannelMessage(const std::string &channel, if (message.size() > STRING_MAX_LEN) { warningstream << "ModChannel message too long, dropping before sending " - << " (" << message.size() << " > " << STRING_MAX_LEN << ", channel: " - << channel << ")" << std::endl; + << " (" << message.size() << " > " << STRING_MAX_LEN + << ", channel: " << channel << ")" << std::endl; return; } @@ -3876,7 +3894,7 @@ void Server::loadTranslationLanguage(const std::string &lang_code) if (str_ends_with(i.first, suffix)) { std::ifstream t(i.second.path); std::string data((std::istreambuf_iterator(t)), - std::istreambuf_iterator()); + std::istreambuf_iterator()); (*g_server_translations)[lang_code].loadTranslation(data); } diff --git a/src/server.h b/src/server.h index f44716531..51566e9f3 100644 --- a/src/server.h +++ b/src/server.h @@ -28,7 +28,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "inventorymanager.h" #include "content/subgames.h" #include "tileanimation.h" // TileAnimationParams -#include "particles.h" // ParticleParams +#include "particles.h" // ParticleParams #include "network/peerhandler.h" #include "network/address.h" #include "util/numeric.h" @@ -71,7 +71,8 @@ class ServerThread; class ServerModManager; class ServerInventoryManager; -enum ClientDeletionReason { +enum ClientDeletionReason +{ CDR_LEAVE, CDR_TIMEOUT, CDR_DENY @@ -82,17 +83,16 @@ struct MediaInfo std::string path; std::string sha1_digest; - MediaInfo(const std::string &path_="", - const std::string &sha1_digest_=""): - path(path_), - sha1_digest(sha1_digest_) + MediaInfo(const std::string &path_ = "", const std::string &sha1_digest_ = "") : + path(path_), sha1_digest(sha1_digest_) { } }; struct ServerSoundParams { - enum Type { + enum Type + { SSP_LOCAL, SSP_POSITIONAL, SSP_OBJECT @@ -117,22 +117,16 @@ struct ServerPlayingSound std::unordered_set clients; // peer ids }; -class Server : public con::PeerHandler, public MapEventReceiver, - public IGameDef +class Server : public con::PeerHandler, public MapEventReceiver, public IGameDef { public: /* NOTE: Every public method should be thread-safe */ - Server( - const std::string &path_world, - const SubgameSpec &gamespec, - bool simple_singleplayer_mode, - Address bind_addr, - bool dedicated, - ChatInterface *iface = nullptr - ); + Server(const std::string &path_world, const SubgameSpec &gamespec, + bool simple_singleplayer_mode, Address bind_addr, bool dedicated, + ChatInterface *iface = nullptr); ~Server(); DISABLE_CLASS_COPY(Server); @@ -142,40 +136,40 @@ public: // Actual processing is done in an another thread. void step(float dtime); // This is run by ServerThread and does the actual processing - void AsyncRunStep(bool initial_step=false); + void AsyncRunStep(bool initial_step = false); void Receive(); - PlayerSAO* StageTwoClientInit(session_t peer_id); + PlayerSAO *StageTwoClientInit(session_t peer_id); /* * Command Handlers */ - void handleCommand(NetworkPacket* pkt); + void handleCommand(NetworkPacket *pkt); - void handleCommand_Null(NetworkPacket* pkt) {}; - void handleCommand_Deprecated(NetworkPacket* pkt); - void handleCommand_Init(NetworkPacket* pkt); - void handleCommand_Init2(NetworkPacket* pkt); + void handleCommand_Null(NetworkPacket *pkt){}; + void handleCommand_Deprecated(NetworkPacket *pkt); + void handleCommand_Init(NetworkPacket *pkt); + void handleCommand_Init2(NetworkPacket *pkt); void handleCommand_ModChannelJoin(NetworkPacket *pkt); void handleCommand_ModChannelLeave(NetworkPacket *pkt); void handleCommand_ModChannelMsg(NetworkPacket *pkt); - void handleCommand_RequestMedia(NetworkPacket* pkt); - void handleCommand_ClientReady(NetworkPacket* pkt); - void handleCommand_GotBlocks(NetworkPacket* pkt); - void handleCommand_PlayerPos(NetworkPacket* pkt); - void handleCommand_DeletedBlocks(NetworkPacket* pkt); - void handleCommand_InventoryAction(NetworkPacket* pkt); - void handleCommand_ChatMessage(NetworkPacket* pkt); - void handleCommand_Damage(NetworkPacket* pkt); - void handleCommand_PlayerItem(NetworkPacket* pkt); - void handleCommand_Respawn(NetworkPacket* pkt); - void handleCommand_Interact(NetworkPacket* pkt); - void handleCommand_RemovedSounds(NetworkPacket* pkt); - void handleCommand_NodeMetaFields(NetworkPacket* pkt); - void handleCommand_InventoryFields(NetworkPacket* pkt); - void handleCommand_FirstSrp(NetworkPacket* pkt); - void handleCommand_SrpBytesA(NetworkPacket* pkt); - void handleCommand_SrpBytesM(NetworkPacket* pkt); + void handleCommand_RequestMedia(NetworkPacket *pkt); + void handleCommand_ClientReady(NetworkPacket *pkt); + void handleCommand_GotBlocks(NetworkPacket *pkt); + void handleCommand_PlayerPos(NetworkPacket *pkt); + void handleCommand_DeletedBlocks(NetworkPacket *pkt); + void handleCommand_InventoryAction(NetworkPacket *pkt); + void handleCommand_ChatMessage(NetworkPacket *pkt); + void handleCommand_Damage(NetworkPacket *pkt); + void handleCommand_PlayerItem(NetworkPacket *pkt); + void handleCommand_Respawn(NetworkPacket *pkt); + void handleCommand_Interact(NetworkPacket *pkt); + void handleCommand_RemovedSounds(NetworkPacket *pkt); + void handleCommand_NodeMetaFields(NetworkPacket *pkt); + void handleCommand_InventoryFields(NetworkPacket *pkt); + void handleCommand_FirstSrp(NetworkPacket *pkt); + void handleCommand_SrpBytesA(NetworkPacket *pkt); + void handleCommand_SrpBytesM(NetworkPacket *pkt); void ProcessData(NetworkPacket *pkt); @@ -183,8 +177,8 @@ public: void Send(session_t peer_id, NetworkPacket *pkt); // Helper for handleCommand_PlayerPos and handleCommand_Interact - void process_PlayerPos(RemotePlayer *player, PlayerSAO *playersao, - NetworkPacket *pkt); + void process_PlayerPos( + RemotePlayer *player, PlayerSAO *playersao, NetworkPacket *pkt); // Both setter and getter need no envlock, // can be called freely from threads @@ -210,14 +204,14 @@ public: // Returns -1 if failed, sound handle on success // Envlock s32 playSound(const SimpleSoundSpec &spec, const ServerSoundParams ¶ms, - bool ephemeral=false); + bool ephemeral = false); void stopSound(s32 handle); void fadeSound(s32 handle, float step, float gain); // Envlock std::set getPlayerEffectivePrivs(const std::string &name); bool checkPriv(const std::string &name, const std::string &priv); - void reportPrivsModified(const std::string &name=""); // ""=all + void reportPrivsModified(const std::string &name = ""); // ""=all void reportInventoryFormspecModified(const std::string &name); void reportFormspecPrependModified(const std::string &name); @@ -228,21 +222,21 @@ public: void notifyPlayer(const char *name, const std::wstring &msg); void notifyPlayers(const std::wstring &msg); - void spawnParticle(const std::string &playername, - const ParticleParameters &p); + void spawnParticle(const std::string &playername, const ParticleParameters &p); u32 addParticleSpawner(const ParticleSpawnerParameters &p, - ServerActiveObject *attached, const std::string &playername); + ServerActiveObject *attached, const std::string &playername); void deleteParticleSpawner(const std::string &playername, u32 id); bool dynamicAddMedia(const std::string &filepath); ServerInventoryManager *getInventoryMgr() const { return m_inventory_mgr.get(); } - void sendDetachedInventory(Inventory *inventory, const std::string &name, session_t peer_id); + void sendDetachedInventory( + Inventory *inventory, const std::string &name, session_t peer_id); // Envlock and conlock should be locked when using scriptapi - ServerScripting *getScriptIface(){ return m_script; } + ServerScripting *getScriptIface() { return m_script; } // actions: time-reversed list // Return value: success/failure @@ -251,33 +245,35 @@ public: // IGameDef interface // Under envlock - virtual IItemDefManager* getItemDefManager(); - virtual const NodeDefManager* getNodeDefManager(); - virtual ICraftDefManager* getCraftDefManager(); + virtual IItemDefManager *getItemDefManager(); + virtual const NodeDefManager *getNodeDefManager(); + virtual ICraftDefManager *getCraftDefManager(); virtual u16 allocateUnknownNodeId(const std::string &name); IRollbackManager *getRollbackManager() { return m_rollback; } virtual EmergeManager *getEmergeManager() { return m_emerge; } - IWritableItemDefManager* getWritableItemDefManager(); - NodeDefManager* getWritableNodeDefManager(); - IWritableCraftDefManager* getWritableCraftDefManager(); + IWritableItemDefManager *getWritableItemDefManager(); + NodeDefManager *getWritableNodeDefManager(); + IWritableCraftDefManager *getWritableCraftDefManager(); virtual const std::vector &getMods() const; - virtual const ModSpec* getModSpec(const std::string &modname) const; + virtual const ModSpec *getModSpec(const std::string &modname) const; void getModNames(std::vector &modlist); std::string getBuiltinLuaPath(); virtual std::string getWorldPath() const { return m_path_world; } virtual std::string getModStoragePath() const; - inline bool isSingleplayer() - { return m_simple_singleplayer_mode; } + inline bool isSingleplayer() { return m_simple_singleplayer_mode; } inline void setAsyncFatalError(const std::string &error) - { m_async_fatal_error.set(error); } + { + m_async_fatal_error.set(error); + } - bool showFormspec(const char *name, const std::string &formspec, const std::string &formname); - Map & getMap() { return m_env->getMap(); } - ServerEnvironment & getEnv() { return *m_env; } + bool showFormspec(const char *name, const std::string &formspec, + const std::string &formname); + Map &getMap() { return m_env->getMap(); } + ServerEnvironment &getEnv() { return *m_env; } v3f findSpawnPos(); u32 hudAdd(RemotePlayer *player, HudElement *element); @@ -290,8 +286,8 @@ public: Address getPeerAddress(session_t peer_id); - void setLocalPlayerAnimations(RemotePlayer *player, v2s32 animation_frames[4], - f32 frame_speed); + void setLocalPlayerAnimations( + RemotePlayer *player, v2s32 animation_frames[4], f32 frame_speed); void setPlayerEyeOffset(RemotePlayer *player, const v3f &first, const v3f &third); void setSky(RemotePlayer *player, const SkyboxParams ¶ms); @@ -301,24 +297,26 @@ public: void setClouds(RemotePlayer *player, const CloudParams ¶ms); - void overrideDayNightRatio(RemotePlayer *player, bool do_override, float brightness); + void overrideDayNightRatio( + RemotePlayer *player, bool do_override, float brightness); /* con::PeerHandler implementation. */ void peerAdded(con::Peer *peer); void deletingPeer(con::Peer *peer, bool timeout); void DenySudoAccess(session_t peer_id); - void DenyAccessVerCompliant(session_t peer_id, u16 proto_ver, AccessDeniedCode reason, - const std::string &str_reason = "", bool reconnect = false); + void DenyAccessVerCompliant(session_t peer_id, u16 proto_ver, + AccessDeniedCode reason, const std::string &str_reason = "", + bool reconnect = false); void DenyAccess(session_t peer_id, AccessDeniedCode reason, - const std::string &custom_reason = ""); + const std::string &custom_reason = ""); void acceptAuth(session_t peer_id, bool forSudoMode); void DenyAccess_Legacy(session_t peer_id, const std::wstring &reason); void DisconnectPeer(session_t peer_id); bool getClientConInfo(session_t peer_id, con::rtt_stat_type type, float *retval); bool getClientInfo(session_t peer_id, ClientState *state, u32 *uptime, - u8* ser_vers, u16* prot_vers, u8* major, u8* minor, u8* patch, - std::string* vers_string, std::string* lang_code); + u8 *ser_vers, u16 *prot_vers, u8 *major, u8 *minor, u8 *patch, + std::string *vers_string, std::string *lang_code); void printToConsoleOnly(const std::string &text); @@ -336,7 +334,8 @@ public: bool joinModChannel(const std::string &channel); bool leaveModChannel(const std::string &channel); - bool sendModChannelMessage(const std::string &channel, const std::string &message); + bool sendModChannelMessage( + const std::string &channel, const std::string &message); ModChannel *getModChannel(const std::string &channel); // Send block to specific player only @@ -356,20 +355,23 @@ private: friend class RemoteClient; friend class TestServerShutdownState; - struct ShutdownState { + struct ShutdownState + { friend class TestServerShutdownState; - public: - bool is_requested = false; - bool should_reconnect = false; - std::string message; - void reset(); - void trigger(float delay, const std::string &msg, bool reconnect); - void tick(float dtime, Server *server); - std::wstring getShutdownTimerMessage() const; - bool isTimerRunning() const { return m_timer > 0.0f; } - private: - float m_timer = 0.0f; + public: + bool is_requested = false; + bool should_reconnect = false; + std::string message; + + void reset(); + void trigger(float delay, const std::string &msg, bool reconnect); + void tick(float dtime, Server *server); + std::wstring getShutdownTimerMessage() const; + bool isTimerRunning() const { return m_timer > 0.0f; } + + private: + float m_timer = 0.0f; }; void init(); @@ -378,30 +380,30 @@ private: void SendHP(session_t peer_id, u16 hp); void SendBreath(session_t peer_id, u16 breath); void SendAccessDenied(session_t peer_id, AccessDeniedCode reason, - const std::string &custom_reason, bool reconnect = false); + const std::string &custom_reason, bool reconnect = false); void SendAccessDenied_Legacy(session_t peer_id, const std::wstring &reason); void SendDeathscreen(session_t peer_id, bool set_camera_point_target, - v3f camera_point_target); - void SendItemDef(session_t peer_id, IItemDefManager *itemdef, u16 protocol_version); + v3f camera_point_target); + void SendItemDef(session_t peer_id, IItemDefManager *itemdef, + u16 protocol_version); void SendNodeDef(session_t peer_id, const NodeDefManager *nodedef, - u16 protocol_version); + u16 protocol_version); /* mark blocks not sent for all clients */ - void SetBlocksNotSent(std::map& block); - + void SetBlocksNotSent(std::map &block); virtual void SendChatMessage(session_t peer_id, const ChatMessage &message); void SendTimeOfDay(session_t peer_id, u16 time, f32 time_speed); void SendPlayerHP(session_t peer_id); void SendLocalPlayerAnimations(session_t peer_id, v2s32 animation_frames[4], - f32 animation_speed); + f32 animation_speed); void SendEyeOffset(session_t peer_id, v3f first, v3f third); void SendPlayerPrivileges(session_t peer_id); void SendPlayerInventoryFormspec(session_t peer_id); void SendPlayerFormspecPrepend(session_t peer_id); void SendShowFormspecMessage(session_t peer_id, const std::string &formspec, - const std::string &formname); + const std::string &formname); void SendHUDAdd(session_t peer_id, u32 id, HudElement *form); void SendHUDRemove(session_t peer_id, u32 id); void SendHUDChange(session_t peer_id, u32 id, HudElementStat stat, void *value); @@ -428,11 +430,12 @@ private: std::unordered_set *far_players = nullptr, float far_d_nodes = 100, bool remove_metadata = true); - void sendMetadataChanged(const std::list &meta_updates, - float far_d_nodes = 100); + void sendMetadataChanged( + const std::list &meta_updates, float far_d_nodes = 100); // Environment and Connection must be locked when called - void SendBlockNoLock(session_t peer_id, MapBlock *block, u8 ver, u16 net_proto_version); + void SendBlockNoLock(session_t peer_id, MapBlock *block, u8 ver, + u16 net_proto_version); // Sends blocks to clients (locks env and con on its own) void SendBlocks(float dtime); @@ -441,22 +444,22 @@ private: std::string *filedata = nullptr, std::string *digest = nullptr); void fillMediaCache(); void sendMediaAnnouncement(session_t peer_id, const std::string &lang_code); - void sendRequestedMedia(session_t peer_id, - const std::vector &tosend); + void sendRequestedMedia( + session_t peer_id, const std::vector &tosend); // Adds a ParticleSpawner on peer with peer_id (PEER_ID_INEXISTENT == all) void SendAddParticleSpawner(session_t peer_id, u16 protocol_version, - const ParticleSpawnerParameters &p, u16 attached_id, u32 id); + const ParticleSpawnerParameters &p, u16 attached_id, u32 id); void SendDeleteParticleSpawner(session_t peer_id, u32 id); // Spawns particle on peer with peer_id (PEER_ID_INEXISTENT == all) void SendSpawnParticle(session_t peer_id, u16 protocol_version, - const ParticleParameters &p); + const ParticleParameters &p); void SendActiveObjectRemoveAdd(RemoteClient *client, PlayerSAO *playersao); void SendActiveObjectMessages(session_t peer_id, const std::string &datas, - bool reliable = true); + bool reliable = true); void SendCSMRestrictionFlags(session_t peer_id); /* @@ -467,20 +470,20 @@ private: void RespawnPlayer(session_t peer_id); void DeleteClient(session_t peer_id, ClientDeletionReason reason); void UpdateCrafting(RemotePlayer *player); - bool checkInteractDistance(RemotePlayer *player, const f32 d, const std::string &what); + bool checkInteractDistance( + RemotePlayer *player, const f32 d, const std::string &what); void handleChatInterfaceEvent(ChatEvent *evt); // This returns the answer to the sender of wmessage, or "" if there is none std::wstring handleChat(const std::string &name, const std::wstring &wname, - std::wstring wmessage_input, - bool check_shout_priv = false, - RemotePlayer *player = NULL); + std::wstring wmessage_input, bool check_shout_priv = false, + RemotePlayer *player = NULL); void handleAdminChat(const ChatEventChat *evt); // When called, connection mutex should be locked - RemoteClient* getClient(session_t peer_id, ClientState state_min = CS_Active); - RemoteClient* getClientNoEx(session_t peer_id, ClientState state_min = CS_Active); + RemoteClient *getClient(session_t peer_id, ClientState state_min = CS_Active); + RemoteClient *getClientNoEx(session_t peer_id, ClientState state_min = CS_Active); // When called, environment mutex should be locked std::string getPlayerName(session_t peer_id); @@ -574,7 +577,7 @@ private: float m_time_of_day_send_timer = 0.0f; /* - Client interface + Client interface */ ClientInterface m_clients; @@ -609,7 +612,7 @@ private: Queue of map edits from the environment for sending to the clients This is behind m_env_mutex */ - std::queue m_unsent_map_edit_queue; + std::queue m_unsent_map_edit_queue; /* If a non-empty area, map edit events contained within are left unsent. Done at map generation time to speed up editing of the diff --git a/src/server/luaentity_sao.cpp b/src/server/luaentity_sao.cpp index d504c42ca..d04516414 100644 --- a/src/server/luaentity_sao.cpp +++ b/src/server/luaentity_sao.cpp @@ -26,8 +26,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "server.h" #include "serverenvironment.h" -LuaEntitySAO::LuaEntitySAO(ServerEnvironment *env, v3f pos, const std::string &data) - : UnitSAO(env, pos) +LuaEntitySAO::LuaEntitySAO(ServerEnvironment *env, v3f pos, const std::string &data) : + UnitSAO(env, pos) { std::string name; std::string state; @@ -37,8 +37,9 @@ LuaEntitySAO::LuaEntitySAO(ServerEnvironment *env, v3f pos, const std::string &d while (!data.empty()) { // breakable, run for one iteration std::istringstream is(data, std::ios::binary); - // 'version' does not allow to incrementally extend the parameter list thus - // we need another variable to build on top of 'version=1'. Ugly hack but works™ + // 'version' does not allow to incrementally extend the parameter list + // thus we need another variable to build on top of 'version=1'. Ugly hack + // but works™ u8 version2 = 0; u8 version = readU8(is); @@ -69,8 +70,8 @@ LuaEntitySAO::LuaEntitySAO(ServerEnvironment *env, v3f pos, const std::string &d break; } // create object - infostream << "LuaEntitySAO::create(name=\"" << name << "\" state=\"" - << state << "\")" << std::endl; + infostream << "LuaEntitySAO::create(name=\"" << name << "\" state=\"" << state + << "\")" << std::endl; m_init_name = name; m_init_state = state; @@ -81,7 +82,7 @@ LuaEntitySAO::LuaEntitySAO(ServerEnvironment *env, v3f pos, const std::string &d LuaEntitySAO::~LuaEntitySAO() { - if(m_registered){ + if (m_registered) { m_env->getScriptIface()->luaentity_Remove(m_id); } @@ -95,18 +96,15 @@ void LuaEntitySAO::addedToEnvironment(u32 dtime_s) ServerActiveObject::addedToEnvironment(dtime_s); // Create entity from name - m_registered = m_env->getScriptIface()-> - luaentity_Add(m_id, m_init_name.c_str()); + m_registered = m_env->getScriptIface()->luaentity_Add(m_id, m_init_name.c_str()); - if(m_registered){ + if (m_registered) { // Get properties - m_env->getScriptIface()-> - luaentity_GetProperties(m_id, this, &m_prop); + m_env->getScriptIface()->luaentity_GetProperties(m_id, this, &m_prop); // Initialize HP from properties m_hp = m_prop.hp_max; // Activate entity, supplying serialized state - m_env->getScriptIface()-> - luaentity_Activate(m_id, m_init_state, dtime_s); + m_env->getScriptIface()->luaentity_Activate(m_id, m_init_state, dtime_s); } else { m_prop.infotext = m_init_name; } @@ -114,8 +112,7 @@ void LuaEntitySAO::addedToEnvironment(u32 dtime_s) void LuaEntitySAO::step(float dtime, bool send_recommended) { - if(!m_properties_sent) - { + if (!m_properties_sent) { m_properties_sent = true; std::string str = getPropertyPacket(); // create message and add to list @@ -125,8 +122,9 @@ void LuaEntitySAO::step(float dtime, bool send_recommended) // If attached, check that our parent is still there. If it isn't, detach. if (m_attachment_parent_id && !isAttached()) { // This is handled when objects are removed from the map - warningstream << "LuaEntitySAO::step() id=" << m_id << - " is attached to nonexistent parent. This is a bug." << std::endl; + warningstream << "LuaEntitySAO::step() id=" << m_id + << " is attached to nonexistent parent. This is a bug." + << std::endl; clearParentAttachment(); sendPosition(false, true); } @@ -135,29 +133,28 @@ void LuaEntitySAO::step(float dtime, bool send_recommended) collisionMoveResult moveresult, *moveresult_p = nullptr; - // Each frame, parent position is copied if the object is attached, otherwise it's calculated normally - // If the object gets detached this comes into effect automatically from the last known origin - if(isAttached()) - { - v3f pos = m_env->getActiveObject(m_attachment_parent_id)->getBasePosition(); + // Each frame, parent position is copied if the object is attached, otherwise it's + // calculated normally If the object gets detached this comes into effect + // automatically from the last known origin + if (isAttached()) { + v3f pos = m_env->getActiveObject(m_attachment_parent_id) + ->getBasePosition(); m_base_position = pos; - m_velocity = v3f(0,0,0); - m_acceleration = v3f(0,0,0); - } - else - { - if(m_prop.physical){ + m_velocity = v3f(0, 0, 0); + m_acceleration = v3f(0, 0, 0); + } else { + if (m_prop.physical) { aabb3f box = m_prop.collisionbox; box.MinEdge *= BS; box.MaxEdge *= BS; - f32 pos_max_d = BS*0.25; // Distance per iteration + f32 pos_max_d = BS * 0.25; // Distance per iteration v3f p_pos = m_base_position; v3f p_velocity = m_velocity; v3f p_acceleration = m_acceleration; moveresult = collisionMoveSimple(m_env, m_env->getGameDef(), - pos_max_d, box, m_prop.stepheight, dtime, - &p_pos, &p_velocity, p_acceleration, - this, m_prop.collideWithObjects); + pos_max_d, box, m_prop.stepheight, dtime, &p_pos, + &p_velocity, p_acceleration, this, + m_prop.collideWithObjects); moveresult_p = &moveresult; // Apply results @@ -165,22 +162,24 @@ void LuaEntitySAO::step(float dtime, bool send_recommended) m_velocity = p_velocity; m_acceleration = p_acceleration; } else { - m_base_position += dtime * m_velocity + 0.5 * dtime - * dtime * m_acceleration; + m_base_position += dtime * m_velocity + + 0.5 * dtime * dtime * m_acceleration; m_velocity += dtime * m_acceleration; } if (m_prop.automatic_face_movement_dir && - (fabs(m_velocity.Z) > 0.001 || fabs(m_velocity.X) > 0.001)) { - float target_yaw = atan2(m_velocity.Z, m_velocity.X) * 180 / M_PI - + m_prop.automatic_face_movement_dir_offset; + (fabs(m_velocity.Z) > 0.001 || + fabs(m_velocity.X) > 0.001)) { + float target_yaw = + atan2(m_velocity.Z, m_velocity.X) * 180 / M_PI + + m_prop.automatic_face_movement_dir_offset; float max_rotation_per_sec = m_prop.automatic_face_movement_max_rotation_per_sec; if (max_rotation_per_sec > 0) { m_rotation.Y = wrapDegrees_0_360(m_rotation.Y); wrappedApproachShortest(m_rotation.Y, target_yaw, - dtime * max_rotation_per_sec, 360.f); + dtime * max_rotation_per_sec, 360.f); } else { // Negative values of max_rotation_per_sec mean disabled. m_rotation.Y = target_yaw; @@ -188,21 +187,20 @@ void LuaEntitySAO::step(float dtime, bool send_recommended) } } - if(m_registered) { + if (m_registered) { m_env->getScriptIface()->luaentity_Step(m_id, dtime, moveresult_p); } if (!send_recommended) return; - if(!isAttached()) - { + if (!isAttached()) { // TODO: force send when acceleration changes enough? - float minchange = 0.2*BS; - if(m_last_sent_position_timer > 1.0){ - minchange = 0.01*BS; - } else if(m_last_sent_position_timer > 0.2){ - minchange = 0.05*BS; + float minchange = 0.2 * BS; + if (m_last_sent_position_timer > 1.0) { + minchange = 0.01 * BS; + } else if (m_last_sent_position_timer > 0.2) { + minchange = 0.05 * BS; } float move_d = m_base_position.getDistanceFrom(m_last_sent_position); move_d += m_last_sent_move_precision; @@ -224,21 +222,22 @@ std::string LuaEntitySAO::getClientInitializationData(u16 protocol_version) std::ostringstream os(std::ios::binary); // PROTOCOL_VERSION >= 37 - writeU8(os, 1); // version + writeU8(os, 1); // version os << serializeString(""); // name - writeU8(os, 0); // is_player - writeU16(os, getId()); //id + writeU8(os, 0); // is_player + writeU16(os, getId()); // id writeV3F32(os, m_base_position); writeV3F32(os, m_rotation); writeU16(os, m_hp); std::ostringstream msg_os(std::ios::binary); - msg_os << serializeLongString(getPropertyPacket()); // message 1 + msg_os << serializeLongString(getPropertyPacket()); // message 1 msg_os << serializeLongString(generateUpdateArmorGroupsCommand()); // 2 - msg_os << serializeLongString(generateUpdateAnimationCommand()); // 3 + msg_os << serializeLongString(generateUpdateAnimationCommand()); // 3 for (const auto &bone_pos : m_bone_position) { msg_os << serializeLongString(generateUpdateBonePositionCommand( - bone_pos.first, bone_pos.second.X, bone_pos.second.Y)); // m_bone_position.size + bone_pos.first, bone_pos.second.X, + bone_pos.second.Y)); // m_bone_position.size } msg_os << serializeLongString(generateUpdateAttachmentCommand()); // 4 @@ -248,7 +247,7 @@ std::string LuaEntitySAO::getClientInitializationData(u16 protocol_version) if (ServerActiveObject *obj = m_env->getActiveObject(id)) { message_count++; msg_os << serializeLongString(obj->generateUpdateInfantCommand( - id, protocol_version)); + id, protocol_version)); } } @@ -265,19 +264,19 @@ std::string LuaEntitySAO::getClientInitializationData(u16 protocol_version) void LuaEntitySAO::getStaticData(std::string *result) const { - verbosestream<getScriptIface()-> - luaentity_GetStaticdata(m_id); - os<getScriptIface()->luaentity_GetStaticdata(m_id); + os << serializeLongString(state); } else { - os<getWieldedItem(&selected_item, &hand_item); PunchDamageResult result = getPunchDamage( - m_armor_groups, - toolcap, - &tool_item, - time_from_last_punch); + m_armor_groups, toolcap, &tool_item, time_from_last_punch); bool damage_handled = m_env->getScriptIface()->luaentity_Punch(m_id, puncher, - time_from_last_punch, toolcap, dir, result.did_punch ? result.damage : 0); + time_from_last_punch, toolcap, dir, + result.did_punch ? result.damage : 0); if (!damage_handled) { if (result.did_punch) { setHP((s32)getHP() - result.damage, - PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, puncher)); + PlayerHPChangeReason(PlayerHPChangeReason:: + PLAYER_PUNCH, + puncher)); // create message and add to list sendPunchCommand(); @@ -338,11 +335,11 @@ u16 LuaEntitySAO::punch(v3f dir, m_pending_removal = true; } - actionstream << puncher->getDescription() << " (id=" << puncher->getId() << - ", hp=" << puncher->getHP() << ") punched " << - getDescription() << " (id=" << m_id << ", hp=" << m_hp << - "), damage=" << (old_hp - (s32)getHP()) << - (damage_handled ? " (handled by Lua)" : "") << std::endl; + actionstream << puncher->getDescription() << " (id=" << puncher->getId() + << ", hp=" << puncher->getHP() << ") punched " << getDescription() + << " (id=" << m_id << ", hp=" << m_hp + << "), damage=" << (old_hp - (s32)getHP()) + << (damage_handled ? " (handled by Lua)" : "") << std::endl; // TODO: give Lua control over wear return result.wear; @@ -358,7 +355,7 @@ void LuaEntitySAO::rightClick(ServerActiveObject *clicker) void LuaEntitySAO::setPos(const v3f &pos) { - if(isAttached()) + if (isAttached()) return; m_base_position = pos; sendPosition(false, true); @@ -366,10 +363,10 @@ void LuaEntitySAO::setPos(const v3f &pos) void LuaEntitySAO::moveTo(v3f pos, bool continuous) { - if(isAttached()) + if (isAttached()) return; m_base_position = pos; - if(!continuous) + if (!continuous) sendPosition(true, true); } @@ -429,7 +426,6 @@ std::string LuaEntitySAO::getTextureMod() const return m_current_texture_modifier; } - std::string LuaEntitySAO::generateSetTextureModCommand() const { std::ostringstream os(std::ios::binary); @@ -440,8 +436,8 @@ std::string LuaEntitySAO::generateSetTextureModCommand() const return os.str(); } -std::string LuaEntitySAO::generateSetSpriteCommand(v2s16 p, u16 num_frames, - f32 framelength, bool select_horiz_by_yawpitch) +std::string LuaEntitySAO::generateSetSpriteCommand( + v2s16 p, u16 num_frames, f32 framelength, bool select_horiz_by_yawpitch) { std::ostringstream os(std::ios::binary); // command @@ -454,15 +450,11 @@ std::string LuaEntitySAO::generateSetSpriteCommand(v2s16 p, u16 num_frames, return os.str(); } -void LuaEntitySAO::setSprite(v2s16 p, int num_frames, float framelength, - bool select_horiz_by_yawpitch) +void LuaEntitySAO::setSprite( + v2s16 p, int num_frames, float framelength, bool select_horiz_by_yawpitch) { std::string str = generateSetSpriteCommand( - p, - num_frames, - framelength, - select_horiz_by_yawpitch - ); + p, num_frames, framelength, select_horiz_by_yawpitch); // create message and add to list m_messages_out.emplace(getId(), true, str); } @@ -479,38 +471,32 @@ std::string LuaEntitySAO::getPropertyPacket() void LuaEntitySAO::sendPosition(bool do_interpolate, bool is_movement_end) { - // If the object is attached client-side, don't waste bandwidth sending its position to clients - if(isAttached()) + // If the object is attached client-side, don't waste bandwidth sending its + // position to clients + if (isAttached()) return; - m_last_sent_move_precision = m_base_position.getDistanceFrom( - m_last_sent_position); + m_last_sent_move_precision = + m_base_position.getDistanceFrom(m_last_sent_position); m_last_sent_position_timer = 0; m_last_sent_position = m_base_position; m_last_sent_velocity = m_velocity; - //m_last_sent_acceleration = m_acceleration; + // m_last_sent_acceleration = m_acceleration; m_last_sent_rotation = m_rotation; float update_interval = m_env->getSendRecommendedInterval(); - std::string str = generateUpdatePositionCommand( - m_base_position, - m_velocity, - m_acceleration, - m_rotation, - do_interpolate, - is_movement_end, - update_interval - ); + std::string str = generateUpdatePositionCommand(m_base_position, m_velocity, + m_acceleration, m_rotation, do_interpolate, is_movement_end, + update_interval); // create message and add to list m_messages_out.emplace(getId(), false, str); } bool LuaEntitySAO::getCollisionBox(aabb3f *toset) const { - if (m_prop.physical) - { - //update collision box + if (m_prop.physical) { + // update collision box toset->MinEdge = m_prop.collisionbox.MinEdge * BS; toset->MaxEdge = m_prop.collisionbox.MaxEdge * BS; diff --git a/src/server/player_sao.cpp b/src/server/player_sao.cpp index 3d9f08bfa..1aa1a3f9b 100644 --- a/src/server/player_sao.cpp +++ b/src/server/player_sao.cpp @@ -26,11 +26,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "serverenvironment.h" PlayerSAO::PlayerSAO(ServerEnvironment *env_, RemotePlayer *player_, session_t peer_id_, - bool is_singleplayer): - UnitSAO(env_, v3f(0,0,0)), - m_player(player_), - m_peer_id(peer_id_), - m_is_singleplayer(is_singleplayer) + bool is_singleplayer) : + UnitSAO(env_, v3f(0, 0, 0)), + m_player(player_), m_peer_id(peer_id_), m_is_singleplayer(is_singleplayer) { SANITY_CHECK(m_peer_id != PEER_ID_INEXISTENT); @@ -48,7 +46,7 @@ PlayerSAO::PlayerSAO(ServerEnvironment *env_, RemotePlayer *player_, session_t p m_prop.textures.emplace_back("player_back.png"); m_prop.colors.clear(); m_prop.colors.emplace_back(255, 255, 255, 255); - m_prop.spritediv = v2s16(1,1); + m_prop.spritediv = v2s16(1, 1); m_prop.eye_height = 1.625f; // End of default appearance m_prop.is_visible = true; @@ -108,23 +106,24 @@ std::string PlayerSAO::getClientInitializationData(u16 protocol_version) std::ostringstream os(std::ios::binary); // Protocol >= 15 - writeU8(os, 1); // version + writeU8(os, 1); // version os << serializeString(m_player->getName()); // name - writeU8(os, 1); // is_player - writeS16(os, getId()); // id + writeU8(os, 1); // is_player + writeS16(os, getId()); // id writeV3F32(os, m_base_position); writeV3F32(os, m_rotation); writeU16(os, getHP()); std::ostringstream msg_os(std::ios::binary); - msg_os << serializeLongString(getPropertyPacket()); // message 1 + msg_os << serializeLongString(getPropertyPacket()); // message 1 msg_os << serializeLongString(generateUpdateArmorGroupsCommand()); // 2 - msg_os << serializeLongString(generateUpdateAnimationCommand()); // 3 + msg_os << serializeLongString(generateUpdateAnimationCommand()); // 3 for (const auto &bone_pos : m_bone_position) { msg_os << serializeLongString(generateUpdateBonePositionCommand( - bone_pos.first, bone_pos.second.X, bone_pos.second.Y)); // m_bone_position.size + bone_pos.first, bone_pos.second.X, + bone_pos.second.Y)); // m_bone_position.size } - msg_os << serializeLongString(generateUpdateAttachmentCommand()); // 4 + msg_os << serializeLongString(generateUpdateAttachmentCommand()); // 4 msg_os << serializeLongString(generateUpdatePhysicsOverrideCommand()); // 5 int message_count = 5 + m_bone_position.size(); @@ -133,7 +132,7 @@ std::string PlayerSAO::getClientInitializationData(u16 protocol_version) if (ServerActiveObject *obj = m_env->getActiveObject(id)) { message_count++; msg_os << serializeLongString(obj->generateUpdateInfantCommand( - id, protocol_version)); + id, protocol_version)); } } @@ -145,7 +144,7 @@ std::string PlayerSAO::getClientInitializationData(u16 protocol_version) return os.str(); } -void PlayerSAO::getStaticData(std::string * result) const +void PlayerSAO::getStaticData(std::string *result) const { FATAL_ERROR("Obsolete function"); } @@ -164,7 +163,8 @@ void PlayerSAO::step(float dtime, bool send_recommended) // No more breath, damage player if (m_breath == 0) { - PlayerHPChangeReason reason(PlayerHPChangeReason::DROWNING); + PlayerHPChangeReason reason( + PlayerHPChangeReason::DROWNING); setHP(m_hp - c.drowning, reason); m_env->getGameDef()->SendPlayerHPOrDie(this, reason); } @@ -176,7 +176,8 @@ void PlayerSAO::step(float dtime, bool send_recommended) v3s16 p = floatToInt(getEyePosition(), BS); MapNode n = m_env->getMap().getNode(p); const ContentFeatures &c = m_env->getGameDef()->ndef()->get(n); - // If player is alive & not drowning & not in ignore & not immortal, breathe + // If player is alive & not drowning & not in ignore & not immortal, + // breathe if (m_breath < m_prop.breath_max && c.drowning == 0 && n.getContent() != CONTENT_IGNORE && m_hp > 0) setBreath(m_breath + 1); @@ -191,8 +192,9 @@ void PlayerSAO::step(float dtime, bool send_recommended) // Sequence of damage points, starting 0.1 above feet and progressing // upwards in 1 node intervals, stopping below top damage point. for (float dam_height = 0.1f; dam_height < dam_top; dam_height++) { - v3s16 p = floatToInt(m_base_position + - v3f(0.0f, dam_height * BS, 0.0f), BS); + v3s16 p = floatToInt(m_base_position + v3f(0.0f, dam_height * BS, + 0.0f), + BS); MapNode n = m_env->getMap().getNode(p); const ContentFeatures &c = m_env->getGameDef()->ndef()->get(n); if (c.damage_per_second > damage_per_second) { @@ -202,8 +204,8 @@ void PlayerSAO::step(float dtime, bool send_recommended) } // Top damage point - v3s16 ptop = floatToInt(m_base_position + - v3f(0.0f, dam_top * BS, 0.0f), BS); + v3s16 ptop = floatToInt( + m_base_position + v3f(0.0f, dam_top * BS, 0.0f), BS); MapNode ntop = m_env->getMap().getNode(ptop); const ContentFeatures &c = m_env->getGameDef()->ndef()->get(ntop); if (c.damage_per_second > damage_per_second) { @@ -213,7 +215,8 @@ void PlayerSAO::step(float dtime, bool send_recommended) if (damage_per_second != 0 && m_hp > 0) { s32 newhp = (s32)m_hp - (s32)damage_per_second; - PlayerHPChangeReason reason(PlayerHPChangeReason::NODE_DAMAGE, nodename); + PlayerHPChangeReason reason( + PlayerHPChangeReason::NODE_DAMAGE, nodename); setHP(newhp, reason); m_env->getGameDef()->SendPlayerHPOrDie(this, reason); } @@ -230,19 +233,20 @@ void PlayerSAO::step(float dtime, bool send_recommended) // If attached, check that our parent is still there. If it isn't, detach. if (m_attachment_parent_id && !isAttached()) { // This is handled when objects are removed from the map - warningstream << "PlayerSAO::step() id=" << m_id << - " is attached to nonexistent parent. This is a bug." << std::endl; + warningstream << "PlayerSAO::step() id=" << m_id + << " is attached to nonexistent parent. This is a bug." + << std::endl; clearParentAttachment(); setBasePosition(m_last_good_position); m_env->getGameDef()->SendMovePlayer(m_peer_id); } - //dstream<<"PlayerSAO::step: dtime: "<getMaxLagEstimate() * 2.0f; - if(lag_pool_max < LAG_POOL_MIN) + if (lag_pool_max < LAG_POOL_MIN) lag_pool_max = LAG_POOL_MIN; m_dig_pool.setMax(lag_pool_max); m_move_pool.setMax(lag_pool_max); @@ -260,7 +264,8 @@ void PlayerSAO::step(float dtime, bool send_recommended) // If the object gets detached this comes into effect automatically from // the last known origin. if (isAttached()) { - v3f pos = m_env->getActiveObject(m_attachment_parent_id)->getBasePosition(); + v3f pos = m_env->getActiveObject(m_attachment_parent_id) + ->getBasePosition(); m_last_good_position = pos; setBasePosition(pos); } @@ -279,15 +284,9 @@ void PlayerSAO::step(float dtime, bool send_recommended) else pos = m_base_position; - std::string str = generateUpdatePositionCommand( - pos, - v3f(0.0f, 0.0f, 0.0f), - v3f(0.0f, 0.0f, 0.0f), - m_rotation, - true, - false, - update_interval - ); + std::string str = generateUpdatePositionCommand(pos, + v3f(0.0f, 0.0f, 0.0f), v3f(0.0f, 0.0f, 0.0f), m_rotation, + true, false, update_interval); // create message and add to list m_messages_out.emplace(getId(), false, str); } @@ -295,7 +294,8 @@ void PlayerSAO::step(float dtime, bool send_recommended) if (!m_physics_override_sent) { m_physics_override_sent = true; // create message and add to list - m_messages_out.emplace(getId(), true, generateUpdatePhysicsOverrideCommand()); + m_messages_out.emplace( + getId(), true, generateUpdatePhysicsOverrideCommand()); } sendOutdatedData(); @@ -333,11 +333,12 @@ void PlayerSAO::setBasePosition(const v3f &position) void PlayerSAO::setPos(const v3f &pos) { - if(isAttached()) + if (isAttached()) return; // Send mapblock of target location - v3s16 blockpos = v3s16(pos.X / MAP_BLOCKSIZE, pos.Y / MAP_BLOCKSIZE, pos.Z / MAP_BLOCKSIZE); + v3s16 blockpos = v3s16(pos.X / MAP_BLOCKSIZE, pos.Y / MAP_BLOCKSIZE, + pos.Z / MAP_BLOCKSIZE); m_env->getGameDef()->SendBlock(m_peer_id, blockpos); setBasePosition(pos); @@ -350,7 +351,7 @@ void PlayerSAO::setPos(const v3f &pos) void PlayerSAO::moveTo(v3f pos, bool continuous) { - if(isAttached()) + if (isAttached()) return; setBasePosition(pos); @@ -407,10 +408,8 @@ void PlayerSAO::setLookPitchAndSend(const float pitch) m_env->getGameDef()->SendMovePlayer(m_peer_id); } -u16 PlayerSAO::punch(v3f dir, - const ToolCapabilities *toolcap, - ServerActiveObject *puncher, - float time_from_last_punch) +u16 PlayerSAO::punch(v3f dir, const ToolCapabilities *toolcap, + ServerActiveObject *puncher, float time_from_last_punch) { if (!toolcap) return 0; @@ -427,18 +426,17 @@ u16 PlayerSAO::punch(v3f dir, } s32 old_hp = getHP(); - HitParams hitparams = getHitParams(m_armor_groups, toolcap, - time_from_last_punch); + HitParams hitparams = getHitParams(m_armor_groups, toolcap, time_from_last_punch); PlayerSAO *playersao = m_player->getPlayerSAO(); - bool damage_handled = m_env->getScriptIface()->on_punchplayer(playersao, - puncher, time_from_last_punch, toolcap, dir, - hitparams.hp); + bool damage_handled = m_env->getScriptIface()->on_punchplayer(playersao, puncher, + time_from_last_punch, toolcap, dir, hitparams.hp); if (!damage_handled) { setHP((s32)getHP() - (s32)hitparams.hp, - PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, puncher)); + PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, + puncher)); } else { // override client prediction if (puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) { // create message and add to list @@ -446,11 +444,11 @@ u16 PlayerSAO::punch(v3f dir, } } - actionstream << puncher->getDescription() << " (id=" << puncher->getId() << - ", hp=" << puncher->getHP() << ") punched " << - getDescription() << " (id=" << m_id << ", hp=" << m_hp << - "), damage=" << (old_hp - (s32)getHP()) << - (damage_handled ? " (handled by Lua)" : "") << std::endl; + actionstream << puncher->getDescription() << " (id=" << puncher->getId() + << ", hp=" << puncher->getHP() << ") punched " << getDescription() + << " (id=" << m_id << ", hp=" << m_hp + << "), damage=" << (old_hp - (s32)getHP()) + << (damage_handled ? " (handled by Lua)" : "") << std::endl; return hitparams.wear; } @@ -459,10 +457,11 @@ void PlayerSAO::setHP(s32 hp, const PlayerHPChangeReason &reason) { s32 oldhp = m_hp; - //hp = rangelim(hp, 0, m_prop.hp_max); + // hp = rangelim(hp, 0, m_prop.hp_max); if (oldhp != hp) { - s32 hp_change = m_env->getScriptIface()->on_player_hpchange(this, hp - oldhp, reason); + s32 hp_change = m_env->getScriptIface()->on_player_hpchange( + this, hp - oldhp, reason); if (hp_change == 0) return; @@ -577,7 +576,8 @@ bool PlayerSAO::checkMovementCheat() float override_max_H, override_max_V; if (m_max_speed_override_time > 0.0f) { - override_max_H = MYMAX(fabs(m_max_speed_override.X), fabs(m_max_speed_override.Z)); + override_max_H = MYMAX(fabs(m_max_speed_override.X), + fabs(m_max_speed_override.Z)); override_max_V = fabs(m_max_speed_override.Y); } else { override_max_H = override_max_V = 0.0f; @@ -627,8 +627,9 @@ bool PlayerSAO::checkMovementCheat() lag_pool_max = MYMAX(lag_pool_max, LAG_POOL_MIN); if (m_time_from_last_teleport > lag_pool_max) { actionstream << "Server: " << m_player->getName() - << " moved too fast: V=" << d_vert << ", H=" << d_horiz - << "; resetting position." << std::endl; + << " moved too fast: V=" << d_vert + << ", H=" << d_horiz << "; resetting position." + << std::endl; cheated = true; } setBasePosition(m_last_good_position); @@ -638,7 +639,7 @@ bool PlayerSAO::checkMovementCheat() bool PlayerSAO::getCollisionBox(aabb3f *toset) const { - //update collision box + // update collision box toset->MinEdge = m_prop.collisionbox.MinEdge * BS; toset->MaxEdge = m_prop.collisionbox.MaxEdge * BS; diff --git a/src/server/serveractiveobject.cpp b/src/server/serveractiveobject.cpp index 3341dc008..3acf4cece 100644 --- a/src/server/serveractiveobject.cpp +++ b/src/server/serveractiveobject.cpp @@ -23,16 +23,14 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "constants.h" // BS #include "log.h" -ServerActiveObject::ServerActiveObject(ServerEnvironment *env, v3f pos): - ActiveObject(0), - m_env(env), - m_base_position(pos) +ServerActiveObject::ServerActiveObject(ServerEnvironment *env, v3f pos) : + ActiveObject(0), m_env(env), m_base_position(pos) { } float ServerActiveObject::getMinimumSavedMovement() { - return 2.0*BS; + return 2.0 * BS; } ItemStack ServerActiveObject::getWieldedItem(ItemStack *selected, ItemStack *hand) const @@ -49,7 +47,8 @@ bool ServerActiveObject::setWieldedItem(const ItemStack &item) return false; } -std::string ServerActiveObject::generateUpdateInfantCommand(u16 infant_id, u16 protocol_version) +std::string ServerActiveObject::generateUpdateInfantCommand( + u16 infant_id, u16 protocol_version) { std::ostringstream os(std::ios::binary); // command diff --git a/src/server/serveractiveobject.h b/src/server/serveractiveobject.h index 927009aef..7af1c24c4 100644 --- a/src/server/serveractiveobject.h +++ b/src/server/serveractiveobject.h @@ -58,8 +58,7 @@ public: ServerActiveObject(ServerEnvironment *env, v3f pos); virtual ~ServerActiveObject() = default; - virtual ActiveObjectType getSendType() const - { return getType(); } + virtual ActiveObjectType getSendType() const { return getType(); } // Called after id has been set and has been inserted in environment virtual void addedToEnvironment(u32 dtime_s){}; @@ -67,35 +66,31 @@ public: virtual void removingFromEnvironment(){}; // Returns true if object's deletion is the job of the // environment - virtual bool environmentDeletes() const - { return true; } + virtual bool environmentDeletes() const { return true; } // Create a certain type of ServerActiveObject - static ServerActiveObject* create(ActiveObjectType type, - ServerEnvironment *env, u16 id, v3f pos, - const std::string &data); + static ServerActiveObject *create(ActiveObjectType type, ServerEnvironment *env, + u16 id, v3f pos, const std::string &data); /* Some simple getters/setters */ v3f getBasePosition() const { return m_base_position; } - void setBasePosition(v3f pos){ m_base_position = pos; } - ServerEnvironment* getEnv(){ return m_env; } + void setBasePosition(v3f pos) { m_base_position = pos; } + ServerEnvironment *getEnv() { return m_env; } /* Some more dynamic interface */ - virtual void setPos(const v3f &pos) - { setBasePosition(pos); } + virtual void setPos(const v3f &pos) { setBasePosition(pos); } // continuous: if true, object does not stop immediately at pos - virtual void moveTo(v3f pos, bool continuous) - { setBasePosition(pos); } + virtual void moveTo(v3f pos, bool continuous) { setBasePosition(pos); } // If object has moved less than this and data has not changed, // saving to disk may be omitted virtual float getMinimumSavedMovement(); - virtual std::string getDescription(){return "SAO";} + virtual std::string getDescription() { return "SAO"; } /* Step object in time. @@ -107,13 +102,16 @@ public: same time so that the data can be combined in a single packet. */ - virtual void step(float dtime, bool send_recommended){} + virtual void step(float dtime, bool send_recommended) {} /* The return value of this is passed to the client-side object when it is created */ - virtual std::string getClientInitializationData(u16 protocol_version) {return "";} + virtual std::string getClientInitializationData(u16 protocol_version) + { + return ""; + } /* The return value of this is passed to the server-side object @@ -129,59 +127,65 @@ public: Return false in here to never save and instead remove object on unload. getStaticData() will not be called in that case. */ - virtual bool isStaticAllowed() const - {return true;} + virtual bool isStaticAllowed() const { return true; } // Returns tool wear - virtual u16 punch(v3f dir, - const ToolCapabilities *toolcap = nullptr, + virtual u16 punch(v3f dir, const ToolCapabilities *toolcap = nullptr, ServerActiveObject *puncher = nullptr, float time_from_last_punch = 1000000.0f) - { return 0; } - virtual void rightClick(ServerActiveObject *clicker) - {} - virtual void setHP(s32 hp, const PlayerHPChangeReason &reason) - {} - virtual u16 getHP() const - { return 0; } + { + return 0; + } + virtual void rightClick(ServerActiveObject *clicker) {} + virtual void setHP(s32 hp, const PlayerHPChangeReason &reason) {} + virtual u16 getHP() const { return 0; } - virtual void setArmorGroups(const ItemGroupList &armor_groups) - {} + virtual void setArmorGroups(const ItemGroupList &armor_groups) {} virtual const ItemGroupList &getArmorGroups() const - { static ItemGroupList rv; return rv; } - virtual void setPhysicsOverride(float physics_override_speed, float physics_override_jump, float physics_override_gravity) - {} - virtual void setAnimation(v2f frames, float frame_speed, float frame_blend, bool frame_loop) - {} - virtual void getAnimation(v2f *frames, float *frame_speed, float *frame_blend, bool *frame_loop) - {} - virtual void setAnimationSpeed(float frame_speed) - {} + { + static ItemGroupList rv; + return rv; + } + virtual void setPhysicsOverride(float physics_override_speed, + float physics_override_jump, float physics_override_gravity) + { + } + virtual void setAnimation( + v2f frames, float frame_speed, float frame_blend, bool frame_loop) + { + } + virtual void getAnimation(v2f *frames, float *frame_speed, float *frame_blend, + bool *frame_loop) + { + } + virtual void setAnimationSpeed(float frame_speed) {} virtual void setBonePosition(const std::string &bone, v3f position, v3f rotation) - {} - virtual void getBonePosition(const std::string &bone, v3f *position, v3f *lotation) - {} + { + } + virtual void getBonePosition( + const std::string &bone, v3f *position, v3f *lotation) + { + } virtual const std::unordered_set &getAttachmentChildIds() const - { static std::unordered_set rv; return rv; } + { + static std::unordered_set rv; + return rv; + } virtual ServerActiveObject *getParent() const { return nullptr; } - virtual ObjectProperties* accessObjectProperties() - { return NULL; } - virtual void notifyObjectPropertiesModified() - {} + virtual ObjectProperties *accessObjectProperties() { return NULL; } + virtual void notifyObjectPropertiesModified() {} // Inventory and wielded item - virtual Inventory *getInventory() const - { return NULL; } + virtual Inventory *getInventory() const { return NULL; } virtual InventoryLocation getInventoryLocation() const - { return InventoryLocation(); } - virtual void setInventoryModified() - {} - virtual std::string getWieldList() const - { return ""; } - virtual u16 getWieldIndex() const - { return 0; } - virtual ItemStack getWieldedItem(ItemStack *selected, - ItemStack *hand = nullptr) const; + { + return InventoryLocation(); + } + virtual void setInventoryModified() {} + virtual std::string getWieldList() const { return ""; } + virtual u16 getWieldIndex() const { return 0; } + virtual ItemStack getWieldedItem( + ItemStack *selected, ItemStack *hand = nullptr) const; virtual bool setWieldedItem(const ItemStack &item); inline void attachParticleSpawner(u32 id) { @@ -193,7 +197,8 @@ public: } std::string generateUpdateInfantCommand(u16 infant_id, u16 protocol_version); - std::string generateUpdateNametagAttributesCommand(const video::SColor &color) const; + std::string generateUpdateNametagAttributesCommand( + const video::SColor &color) const; void dumpAOMessagesToQueue(std::queue &queue); @@ -227,8 +232,7 @@ public: A getter that unifies the above to answer the question: "Can the environment still interact with this object?" */ - inline bool isGone() const - { return m_pending_removal || m_pending_deactivation; } + inline bool isGone() const { return m_pending_removal || m_pending_deactivation; } /* Whether the object's static data has been stored to a block @@ -238,7 +242,7 @@ public: The block from which the object was loaded from, and in which a copy of the static data resides. */ - v3s16 m_static_block = v3s16(1337,1337,1337); + v3s16 m_static_block = v3s16(1337, 1337, 1337); protected: virtual void onAttach(int parent_id) {} diff --git a/src/serverenvironment.cpp b/src/serverenvironment.cpp index ad2ffc9a4..dbbfd2c63 100644 --- a/src/serverenvironment.cpp +++ b/src/serverenvironment.cpp @@ -52,21 +52,21 @@ with this program; if not, write to the Free Software Foundation, Inc., #define LBM_NAME_ALLOWED_CHARS "abcdefghijklmnopqrstuvwxyz0123456789_:" -// A number that is much smaller than the timeout for particle spawners should/could ever be +// A number that is much smaller than the timeout for particle spawners should/could ever +// be #define PARTICLE_SPAWNER_NO_EXPIRY -1024.f /* ABMWithState */ -ABMWithState::ABMWithState(ActiveBlockModifier *abm_): - abm(abm_) +ABMWithState::ABMWithState(ActiveBlockModifier *abm_) : abm(abm_) { // Initialize timer to random value to spread processing float itv = abm->getTriggerInterval(); - itv = MYMAX(0.001, itv); // No less than 1ms - int minval = MYMAX(-0.51*itv, -60); // Clamp to - int maxval = MYMIN(0.51*itv, 60); // +-60 seconds + itv = MYMAX(0.001, itv); // No less than 1ms + int minval = MYMAX(-0.51 * itv, -60); // Clamp to + int maxval = MYMIN(0.51 * itv, 60); // +-60 seconds timer = myrand_range(minval, maxval); } @@ -89,15 +89,16 @@ void LBMContentMapping::addLBM(LoadingBlockModifierDef *lbm_def, IGameDef *gamed lbm_list.push_back(lbm_def); - for (const std::string &nodeTrigger: lbm_def->trigger_contents) { + for (const std::string &nodeTrigger : lbm_def->trigger_contents) { std::vector c_ids; bool found = nodedef->getIds(nodeTrigger, c_ids); if (!found) { content_t c_id = gamedef->allocateUnknownNodeId(nodeTrigger); if (c_id == CONTENT_IGNORE) { // Seems it can't be allocated. - warningstream << "Could not internalize node name \"" << nodeTrigger - << "\" while loading LBM \"" << lbm_def->name << "\"." << std::endl; + warningstream << "Could not internalize node name \"" + << nodeTrigger << "\" while loading LBM \"" + << lbm_def->name << "\"." << std::endl; continue; } c_ids.push_back(c_id); @@ -109,8 +110,7 @@ void LBMContentMapping::addLBM(LoadingBlockModifierDef *lbm_def, IGameDef *gamed } } -const std::vector * -LBMContentMapping::lookup(content_t c) const +const std::vector *LBMContentMapping::lookup(content_t c) const { lbm_map::const_iterator it = map.find(c); if (it == map.end()) @@ -135,20 +135,19 @@ LBMManager::~LBMManager() void LBMManager::addLBMDef(LoadingBlockModifierDef *lbm_def) { // Precondition, in query mode the map isn't used anymore - FATAL_ERROR_IF(m_query_mode, - "attempted to modify LBMManager in query mode"); + FATAL_ERROR_IF(m_query_mode, "attempted to modify LBMManager in query mode"); if (!string_allowed(lbm_def->name, LBM_NAME_ALLOWED_CHARS)) { throw ModError("Error adding LBM \"" + lbm_def->name + - "\": Does not follow naming conventions: " + "\": Does not follow naming conventions: " "Only characters [a-z0-9_:] are allowed."); } m_lbm_defs[lbm_def->name] = lbm_def; } -void LBMManager::loadIntroductionTimes(const std::string ×, - IGameDef *gamedef, u32 now) +void LBMManager::loadIntroductionTimes( + const std::string ×, IGameDef *gamedef, u32 now) { m_query_mode = true; @@ -170,8 +169,8 @@ void LBMManager::loadIntroductionTimes(const std::string ×, std::string entry = times.substr(idx, idx_new - idx); std::vector components = str_split(entry, '~'); if (components.size() != 2) - throw SerializationError("Introduction times entry \"" - + entry + "\" requires exactly one '~'!"); + throw SerializationError("Introduction times entry \"" + entry + + "\" requires exactly one '~'!"); const std::string &name = components[0]; u32 time = from_string(components[1]); introduction_times[name] = time; @@ -180,12 +179,12 @@ void LBMManager::loadIntroductionTimes(const std::string ×, // Put stuff from introduction_times into m_lbm_lookup for (std::map::const_iterator it = introduction_times.begin(); - it != introduction_times.end(); ++it) { + it != introduction_times.end(); ++it) { const std::string &name = it->first; u32 time = it->second; std::map::iterator def_it = - m_lbm_defs.find(name); + m_lbm_defs.find(name); if (def_it == m_lbm_defs.end()) { // This seems to be an LBM entry for // an LBM we haven't loaded. Discard it. @@ -231,12 +230,13 @@ std::string LBMManager::createIntroductionTimesString() { // Precondition, we must be in query mode FATAL_ERROR_IF(!m_query_mode, - "attempted to query on non fully set up LBMManager"); + "attempted to query on non fully set up LBMManager"); std::ostringstream oss; for (const auto &it : m_lbm_lookup) { u32 time = it.first; - const std::vector &lbm_list = it.second.lbm_list; + const std::vector &lbm_list = + it.second.lbm_list; for (const auto &lbm_def : lbm_list) { // Don't add if the LBM runs at every load, // then introducement time is hardcoded @@ -253,15 +253,15 @@ void LBMManager::applyLBMs(ServerEnvironment *env, MapBlock *block, u32 stamp) { // Precondition, we need m_lbm_lookup to be initialized FATAL_ERROR_IF(!m_query_mode, - "attempted to query on non fully set up LBMManager"); + "attempted to query on non fully set up LBMManager"); v3s16 pos_of_block = block->getPosRelative(); v3s16 pos; MapNode n; content_t c; lbm_lookup_map::const_iterator it = getLBMsIntroducedAfter(stamp); for (; it != m_lbm_lookup.end(); ++it) { - // Cache previous version to speedup lookup which has a very high performance - // penalty on each call + // Cache previous version to speedup lookup which has a very high + // performance penalty on each call content_t previous_c{}; std::vector *lbm_list = nullptr; @@ -271,17 +271,22 @@ void LBMManager::applyLBMs(ServerEnvironment *env, MapBlock *block, u32 stamp) n = block->getNodeNoEx(pos); c = n.getContent(); - // If content_t are not matching perform an LBM lookup + // If content_t are not matching perform an LBM + // lookup if (previous_c != c) { - lbm_list = (std::vector *) - it->second.lookup(c); + lbm_list = (std::vector< + LoadingBlockModifierDef *> + *)it + ->second + .lookup(c); previous_c = c; } if (!lbm_list) continue; for (auto lbmdef : *lbm_list) { - lbmdef->trigger(env, pos + pos_of_block, n); + lbmdef->trigger(env, pos + pos_of_block, + n); } } } @@ -294,10 +299,9 @@ void LBMManager::applyLBMs(ServerEnvironment *env, MapBlock *block, u32 stamp) void fillRadiusBlock(v3s16 p0, s16 r, std::set &list) { v3s16 p; - for(p.X=p0.X-r; p.X<=p0.X+r; p.X++) - for(p.Y=p0.Y-r; p.Y<=p0.Y+r; p.Y++) - for(p.Z=p0.Z-r; p.Z<=p0.Z+r; p.Z++) - { + for (p.X = p0.X - r; p.X <= p0.X + r; p.X++) + for (p.Y = p0.Y - r; p.Y <= p0.Y + r; p.Y++) + for (p.Z = p0.Z - r; p.Z <= p0.Z + r; p.Z++) { // limit to a sphere if (p.getDistanceFrom(p0) <= r) { // Set in list @@ -306,29 +310,24 @@ void fillRadiusBlock(v3s16 p0, s16 r, std::set &list) } } -void fillViewConeBlock(v3s16 p0, - const s16 r, - const v3f camera_pos, - const v3f camera_dir, - const float camera_fov, - std::set &list) +void fillViewConeBlock(v3s16 p0, const s16 r, const v3f camera_pos, const v3f camera_dir, + const float camera_fov, std::set &list) { v3s16 p; const s16 r_nodes = r * BS * MAP_BLOCKSIZE; - for (p.X = p0.X - r; p.X <= p0.X+r; p.X++) - for (p.Y = p0.Y - r; p.Y <= p0.Y+r; p.Y++) - for (p.Z = p0.Z - r; p.Z <= p0.Z+r; p.Z++) { - if (isBlockInSight(p, camera_pos, camera_dir, camera_fov, r_nodes)) { - list.insert(p); - } - } + for (p.X = p0.X - r; p.X <= p0.X + r; p.X++) + for (p.Y = p0.Y - r; p.Y <= p0.Y + r; p.Y++) + for (p.Z = p0.Z - r; p.Z <= p0.Z + r; p.Z++) { + if (isBlockInSight(p, camera_pos, camera_dir, camera_fov, + r_nodes)) { + list.insert(p); + } + } } -void ActiveBlockList::update(std::vector &active_players, - s16 active_block_range, - s16 active_object_range, - std::set &blocks_removed, - std::set &blocks_added) +void ActiveBlockList::update(std::vector &active_players, + s16 active_block_range, s16 active_object_range, + std::set &blocks_removed, std::set &blocks_added) { /* Create the new list @@ -340,18 +339,16 @@ void ActiveBlockList::update(std::vector &active_players, fillRadiusBlock(pos, active_block_range, m_abm_list); fillRadiusBlock(pos, active_block_range, newlist); - s16 player_ao_range = std::min(active_object_range, playersao->getWantedRange()); + s16 player_ao_range = std::min( + active_object_range, playersao->getWantedRange()); // only do this if this would add blocks if (player_ao_range > active_block_range) { - v3f camera_dir = v3f(0,0,1); + v3f camera_dir = v3f(0, 0, 1); camera_dir.rotateYZBy(playersao->getLookPitch()); camera_dir.rotateXZBy(playersao->getRotation().Y); - fillViewConeBlock(pos, - player_ao_range, - playersao->getEyePosition(), - camera_dir, - playersao->getFov(), - newlist); + fillViewConeBlock(pos, player_ao_range, + playersao->getEyePosition(), camera_dir, + playersao->getFov(), newlist); } } @@ -371,7 +368,7 @@ void ActiveBlockList::update(std::vector &active_players, // Go through new list for (v3s16 p : newlist) { // If not on old list, it's been added - if(m_list.find(p) == m_list.end()) + if (m_list.find(p) == m_list.end()) blocks_added.insert(p); } @@ -391,15 +388,11 @@ void ActiveBlockList::update(std::vector &active_players, // Random device to seed pseudo random generators. static std::random_device seed; -ServerEnvironment::ServerEnvironment(ServerMap *map, - ServerScripting *scriptIface, Server *server, - const std::string &path_world): - Environment(server), - m_map(map), - m_script(scriptIface), - m_server(server), - m_path_world(path_world), - m_rgen(seed()) +ServerEnvironment::ServerEnvironment(ServerMap *map, ServerScripting *scriptIface, + Server *server, const std::string &path_world) : + Environment(server), + m_map(map), m_script(scriptIface), m_server(server), + m_path_world(path_world), m_rgen(seed()) { // Determine which database backend to use std::string conf_path = path_world + DIR_DELIM + "world.mt"; @@ -424,7 +417,7 @@ ServerEnvironment::ServerEnvironment(ServerMap *map, if (!conf.updateConfigFile(conf_path.c_str())) { errorstream << "ServerEnvironment::ServerEnvironment(): " - << "Failed to update world.mt!" << std::endl; + << "Failed to update world.mt!" << std::endl; } } else { conf.getNoEx("player_backend", player_backend_name); @@ -437,7 +430,7 @@ ServerEnvironment::ServerEnvironment(ServerMap *map, if (!conf.updateConfigFile(conf_path.c_str())) { errorstream << "ServerEnvironment::ServerEnvironment(): " - << "Failed to update world.mt!" << std::endl; + << "Failed to update world.mt!" << std::endl; } } else { conf.getNoEx("auth_backend", auth_backend_name); @@ -446,16 +439,22 @@ ServerEnvironment::ServerEnvironment(ServerMap *map, if (player_backend_name == "files") { warningstream << "/!\\ You are using old player file backend. " - << "This backend is deprecated and will be removed in a future release /!\\" - << std::endl << "Switching to SQLite3 or PostgreSQL is advised, " - << "please read http://wiki.minetest.net/Database_backends." << std::endl; + << "This backend is deprecated and will be removed in a " + "future release /!\\" + << std::endl + << "Switching to SQLite3 or PostgreSQL is advised, " + << "please read http://wiki.minetest.net/Database_backends." + << std::endl; } if (auth_backend_name == "files") { warningstream << "/!\\ You are using old auth file backend. " - << "This backend is deprecated and will be removed in a future release /!\\" - << std::endl << "Switching to SQLite3 is advised, " - << "please read http://wiki.minetest.net/Database_backends." << std::endl; + << "This backend is deprecated and will be removed in a " + "future release /!\\" + << std::endl + << "Switching to SQLite3 is advised, " + << "please read http://wiki.minetest.net/Database_backends." + << std::endl; } m_player_database = openPlayerDatabase(player_backend_name, path_world, conf); @@ -488,12 +487,12 @@ ServerEnvironment::~ServerEnvironment() delete m_auth_database; } -Map & ServerEnvironment::getMap() +Map &ServerEnvironment::getMap() { return *m_map; } -ServerMap & ServerEnvironment::getServerMap() +ServerMap &ServerEnvironment::getServerMap() { return *m_map; } @@ -507,7 +506,7 @@ RemotePlayer *ServerEnvironment::getPlayer(const session_t peer_id) return NULL; } -RemotePlayer *ServerEnvironment::getPlayer(const char* name) +RemotePlayer *ServerEnvironment::getPlayer(const char *name) { for (RemotePlayer *player : m_players) { if (strcmp(player->getName(), name) == 0) @@ -525,7 +524,8 @@ void ServerEnvironment::addPlayer(RemotePlayer *player) */ // If peer id is non-zero, it has to be unique. if (player->getPeerId() != PEER_ID_INEXISTENT) - FATAL_ERROR_IF(getPlayer(player->getPeerId()) != NULL, "Peer id not unique"); + FATAL_ERROR_IF(getPlayer(player->getPeerId()) != NULL, + "Peer id not unique"); // Name has to be unique. FATAL_ERROR_IF(getPlayer(player->getName()) != NULL, "Player name not unique"); // Add. @@ -535,7 +535,7 @@ void ServerEnvironment::addPlayer(RemotePlayer *player) void ServerEnvironment::removePlayer(RemotePlayer *player) { for (std::vector::iterator it = m_players.begin(); - it != m_players.end(); ++it) { + it != m_players.end(); ++it) { if ((*it) == player) { delete *it; m_players.erase(it); @@ -549,25 +549,29 @@ bool ServerEnvironment::removePlayerFromDatabase(const std::string &name) return m_player_database->removePlayer(name); } -void ServerEnvironment::kickAllPlayers(AccessDeniedCode reason, - const std::string &str_reason, bool reconnect) +void ServerEnvironment::kickAllPlayers( + AccessDeniedCode reason, const std::string &str_reason, bool reconnect) { for (RemotePlayer *player : m_players) { m_server->DenyAccessVerCompliant(player->getPeerId(), - player->protocol_version, reason, str_reason, reconnect); + player->protocol_version, reason, str_reason, reconnect); } } void ServerEnvironment::saveLoadedPlayers(bool force) { for (RemotePlayer *player : m_players) { - if (force || player->checkModified() || (player->getPlayerSAO() && - player->getPlayerSAO()->getMeta().isModified())) { + if (force || player->checkModified() || + (player->getPlayerSAO() && + player->getPlayerSAO() + ->getMeta() + .isModified())) { try { m_player_database->savePlayer(player); } catch (DatabaseException &e) { - errorstream << "Failed to save player " << player->getName() << " exception: " - << e.what() << std::endl; + errorstream << "Failed to save player " + << player->getName() + << " exception: " << e.what() << std::endl; throw; } } @@ -579,14 +583,14 @@ void ServerEnvironment::savePlayer(RemotePlayer *player) try { m_player_database->savePlayer(player); } catch (DatabaseException &e) { - errorstream << "Failed to save player " << player->getName() << " exception: " - << e.what() << std::endl; + errorstream << "Failed to save player " << player->getName() + << " exception: " << e.what() << std::endl; throw; } } PlayerSAO *ServerEnvironment::loadPlayer(RemotePlayer *player, bool *new_player, - session_t peer_id, bool is_singleplayer) + session_t peer_id, bool is_singleplayer) { PlayerSAO *playersao = new PlayerSAO(this, player, peer_id, is_singleplayer); // Create player if it doesn't exist @@ -594,7 +598,7 @@ PlayerSAO *ServerEnvironment::loadPlayer(RemotePlayer *player, bool *new_player, *new_player = true; // Set player position infostream << "Server: Finding spawn place for player \"" - << player->getName() << "\"" << std::endl; + << player->getName() << "\"" << std::endl; playersao->setBasePosition(m_server->findSpawnPos()); // Make sure the player is saved @@ -605,7 +609,8 @@ PlayerSAO *ServerEnvironment::loadPlayer(RemotePlayer *player, bool *new_player, // to the environment if (objectpos_over_limit(playersao->getBasePosition())) { actionstream << "Respawn position for player \"" - << player->getName() << "\" outside limits, resetting" << std::endl; + << player->getName() + << "\" outside limits, resetting" << std::endl; playersao->setBasePosition(m_server->findSpawnPos()); } } @@ -637,16 +642,14 @@ void ServerEnvironment::saveMeta() args.setU64("time_of_day", getTimeOfDay()); args.setU64("last_clear_objects_time", m_last_clear_objects_time); args.setU64("lbm_introduction_times_version", 1); - args.set("lbm_introduction_times", - m_lbm_mgr.createIntroductionTimesString()); + args.set("lbm_introduction_times", m_lbm_mgr.createIntroductionTimesString()); args.setU64("day_count", m_day_count); args.writeLines(ss); - ss<<"EnvArgsEnd\n"; + ss << "EnvArgsEnd\n"; - if(!fs::safeWriteToFile(path, ss.str())) - { - infostream<<"ServerEnvironment::saveMeta(): Failed to write " - < *> m_aabms; + public: - ABMHandler(std::vector &abms, - float dtime_s, ServerEnvironment *env, - bool use_timers): - m_env(env) + ABMHandler(std::vector &abms, float dtime_s, ServerEnvironment *env, + bool use_timers) : + m_env(env) { - if(dtime_s < 0.001) + if (dtime_s < 0.001) return; const NodeDefManager *ndef = env->getGameDef()->ndef(); for (ABMWithState &abmws : abms) { ActiveBlockModifier *abm = abmws.abm; float trigger_interval = abm->getTriggerInterval(); - if(trigger_interval < 0.001) + if (trigger_interval < 0.001) trigger_interval = 0.001; float actual_interval = dtime_s; - if(use_timers){ + if (use_timers) { abmws.timer += dtime_s; - if(abmws.timer < trigger_interval) + if (abmws.timer < trigger_interval) continue; abmws.timer -= trigger_interval; actual_interval = trigger_interval; } float chance = abm->getTriggerChance(); - if(chance == 0) + if (chance == 0) chance = 1; ActiveABM aabm; aabm.abm = abm; if (abm->getSimpleCatchUp()) { float intervals = actual_interval / trigger_interval; - if(intervals == 0) + if (intervals == 0) continue; aabm.chance = chance / intervals; - if(aabm.chance == 0) + if (aabm.chance == 0) aabm.chance = 1; } else { aabm.chance = chance; @@ -777,14 +783,17 @@ public: // Trigger neighbors const std::vector &required_neighbors_s = - abm->getRequiredNeighbors(); - for (const std::string &required_neighbor_s : required_neighbors_s) { - ndef->getIds(required_neighbor_s, aabm.required_neighbors); + abm->getRequiredNeighbors(); + for (const std::string &required_neighbor_s : + required_neighbors_s) { + ndef->getIds(required_neighbor_s, + aabm.required_neighbors); } aabm.check_required_neighbors = !required_neighbors_s.empty(); // Trigger contents - const std::vector &contents_s = abm->getTriggerContents(); + const std::vector &contents_s = + abm->getTriggerContents(); for (const std::string &content_s : contents_s) { std::vector ids; ndef->getIds(content_s, ids); @@ -809,33 +818,33 @@ public: // Returns the number of objects in the block, and also in 'wider' the // number of objects in the block and all its neighbours. The latter // may an estimate if any neighbours are unloaded. - u32 countObjects(MapBlock *block, ServerMap * map, u32 &wider) + u32 countObjects(MapBlock *block, ServerMap *map, u32 &wider) { wider = 0; u32 wider_unknown_count = 0; - for(s16 x=-1; x<=1; x++) - for(s16 y=-1; y<=1; y++) - for(s16 z=-1; z<=1; z++) - { + for (s16 x = -1; x <= 1; x++) + for (s16 y = -1; y <= 1; y++) + for (s16 z = -1; z <= 1; z++) { MapBlock *block2 = map->getBlockNoCreateNoEx( - block->getPos() + v3s16(x,y,z)); - if(block2==NULL){ + block->getPos() + v3s16(x, y, z)); + if (block2 == NULL) { wider_unknown_count++; continue; } - wider += block2->m_static_objects.m_active.size() - + block2->m_static_objects.m_stored.size(); + wider += block2->m_static_objects.m_active + .size() + + block2->m_static_objects.m_stored.size(); } // Extrapolate u32 active_object_count = block->m_static_objects.m_active.size(); - u32 wider_known_count = 3*3*3 - wider_unknown_count; + u32 wider_known_count = 3 * 3 * 3 - wider_unknown_count; wider += wider_unknown_count * wider / wider_known_count; return active_object_count; - } - void apply(MapBlock *block, int &blocks_scanned, int &abms_run, int &blocks_cached) + void apply(MapBlock *block, int &blocks_scanned, int &abms_run, + int &blocks_cached) { - if(m_aabms.empty() || block->isDummy()) + if (m_aabms.empty() || block->isDummy()) return; // Check the content type cache first @@ -861,75 +870,95 @@ public: ServerMap *map = &m_env->getServerMap(); u32 active_object_count_wider; - u32 active_object_count = this->countObjects(block, map, active_object_count_wider); + u32 active_object_count = + this->countObjects(block, map, active_object_count_wider); m_env->m_added_objects = 0; v3s16 p0; - for(p0.X=0; p0.XgetNodeUnsafe(p0); - content_t c = n.getContent(); - // Cache content types as we go - if (!block->contents_cached && !block->do_not_cache_contents) { - block->contents.insert(c); - if (block->contents.size() > 64) { - // Too many different nodes... don't try to cache - block->do_not_cache_contents = true; - block->contents.clear(); - } - } - - if (c >= m_aabms.size() || !m_aabms[c]) - continue; - - v3s16 p = p0 + block->getPosRelative(); - for (ActiveABM &aabm : *m_aabms[c]) { - if (myrand() % aabm.chance != 0) - continue; - - // Check neighbors - if (aabm.check_required_neighbors) { - v3s16 p1; - for(p1.X = p0.X-1; p1.X <= p0.X+1; p1.X++) - for(p1.Y = p0.Y-1; p1.Y <= p0.Y+1; p1.Y++) - for(p1.Z = p0.Z-1; p1.Z <= p0.Z+1; p1.Z++) - { - if(p1 == p0) - continue; - content_t c; - if (block->isValidPosition(p1)) { - // if the neighbor is found on the same map block - // get it straight from there - const MapNode &n = block->getNodeUnsafe(p1); - c = n.getContent(); - } else { - // otherwise consult the map - MapNode n = map->getNode(p1 + block->getPosRelative()); - c = n.getContent(); + for (p0.X = 0; p0.X < MAP_BLOCKSIZE; p0.X++) + for (p0.Y = 0; p0.Y < MAP_BLOCKSIZE; p0.Y++) + for (p0.Z = 0; p0.Z < MAP_BLOCKSIZE; p0.Z++) { + const MapNode &n = block->getNodeUnsafe(p0); + content_t c = n.getContent(); + // Cache content types as we go + if (!block->contents_cached && + !block->do_not_cache_contents) { + block->contents.insert(c); + if (block->contents.size() > 64) { + // Too many different nodes... + // don't try to cache + block->do_not_cache_contents = + true; + block->contents.clear(); } - if (CONTAINS(aabm.required_neighbors, c)) - goto neighbor_found; } - // No required neighbor found - continue; - } - neighbor_found: - abms_run++; - // Call all the trigger variations - aabm.abm->trigger(m_env, p, n); - aabm.abm->trigger(m_env, p, n, - active_object_count, active_object_count_wider); + if (c >= m_aabms.size() || !m_aabms[c]) + continue; - // Count surrounding objects again if the abms added any - if(m_env->m_added_objects > 0) { - active_object_count = countObjects(block, map, active_object_count_wider); - m_env->m_added_objects = 0; + v3s16 p = p0 + block->getPosRelative(); + for (ActiveABM &aabm : *m_aabms[c]) { + if (myrand() % aabm.chance != 0) + continue; + + // Check neighbors + if (aabm.check_required_neighbors) { + v3s16 p1; + for (p1.X = p0.X - 1; + p1.X <= p0.X + 1; + p1.X++) + for (p1.Y = p0.Y - 1; + p1.Y <= + p0.Y + 1; + p1.Y++) + for (p1.Z = p0.Z - + 1; + p1.Z <= + p0.Z + 1; + p1.Z++) { + if (p1 == p0) + continue; + content_t c; + if (block->isValidPosition( + p1)) { + // if the neighbor is found on the same map block + // get it straight from there + const MapNode &n = block->getNodeUnsafe( + p1); + c = n.getContent(); + } else { + // otherwise consult the map + MapNode n = map->getNode( + p1 + + block->getPosRelative()); + c = n.getContent(); + } + if (CONTAINS(aabm.required_neighbors, + c)) + goto neighbor_found; + } + // No required neighbor found + continue; + } + neighbor_found: + + abms_run++; + // Call all the trigger variations + aabm.abm->trigger(m_env, p, n); + aabm.abm->trigger(m_env, p, n, + active_object_count, + active_object_count_wider); + + // Count surrounding objects again if the + // abms added any + if (m_env->m_added_objects > 0) { + active_object_count = countObjects( + block, map, + active_object_count_wider); + m_env->m_added_objects = 0; + } + } } - } - } block->contents_cached = !block->do_not_cache_contents; } }; @@ -973,8 +1002,7 @@ void ServerEnvironment::activateBlock(MapBlock *block, u32 additional_dtime) m_lbm_mgr.applyLBMs(this, block, stamp); // Run node timers - std::vector elapsed_timers = - block->m_node_timers.step((float)dtime_s); + std::vector elapsed_timers = block->m_node_timers.step((float)dtime_s); if (!elapsed_timers.empty()) { MapNode n; for (const NodeTimer &elapsed_timer : elapsed_timers) { @@ -982,7 +1010,7 @@ void ServerEnvironment::activateBlock(MapBlock *block, u32 additional_dtime) v3s16 p = elapsed_timer.position + block->getPosRelative(); if (m_script->node_on_timer(p, n, elapsed_timer.elapsed)) block->setNodeTimer(NodeTimer(elapsed_timer.timeout, 0, - elapsed_timer.position)); + elapsed_timer.position)); } } } @@ -1069,8 +1097,8 @@ bool ServerEnvironment::swapNode(v3s16 p, const MapNode &n) void ServerEnvironment::clearObjects(ClearObjectsMode mode) { infostream << "ServerEnvironment::clearObjects(): " - << "Removing all active objects" << std::endl; - auto cb_removal = [this] (ServerActiveObject *obj, u16 id) { + << "Removing all active objects" << std::endl; + auto cb_removal = [this](ServerActiveObject *obj, u16 id) { if (obj->getType() == ACTIVEOBJECT_TYPE_PLAYER) return false; @@ -1100,28 +1128,28 @@ void ServerEnvironment::clearObjects(ClearObjectsMode mode) // Get list of loaded blocks std::vector loaded_blocks; infostream << "ServerEnvironment::clearObjects(): " - << "Listing all loaded blocks" << std::endl; + << "Listing all loaded blocks" << std::endl; m_map->listAllLoadedBlocks(loaded_blocks); infostream << "ServerEnvironment::clearObjects(): " - << "Done listing all loaded blocks: " - << loaded_blocks.size()< loadable_blocks; if (mode == CLEAR_OBJECTS_MODE_FULL) { infostream << "ServerEnvironment::clearObjects(): " - << "Listing all loadable blocks" << std::endl; + << "Listing all loadable blocks" << std::endl; m_map->listAllLoadableBlocks(loadable_blocks); infostream << "ServerEnvironment::clearObjects(): " - << "Done listing all loadable blocks: " - << loadable_blocks.size() << std::endl; + << "Done listing all loadable blocks: " + << loadable_blocks.size() << std::endl; } else { loadable_blocks = loaded_blocks; } actionstream << "ServerEnvironment::clearObjects(): " - << "Now clearing objects in " << loadable_blocks.size() - << " blocks" << std::endl; + << "Now clearing objects in " << loadable_blocks.size() << " blocks" + << std::endl; // Grab a reference on each loaded block to avoid unloading it for (v3s16 p : loaded_blocks) { @@ -1133,20 +1161,20 @@ void ServerEnvironment::clearObjects(ClearObjectsMode mode) // Remove objects in all loadable blocks u32 unload_interval = U32_MAX; if (mode == CLEAR_OBJECTS_MODE_FULL) { - unload_interval = g_settings->getS32("max_clearobjects_extra_loaded_blocks"); + unload_interval = g_settings->getS32( + "max_clearobjects_extra_loaded_blocks"); unload_interval = MYMAX(unload_interval, 1); } u32 report_interval = loadable_blocks.size() / 10; u32 num_blocks_checked = 0; u32 num_blocks_cleared = 0; u32 num_objs_cleared = 0; - for (auto i = loadable_blocks.begin(); - i != loadable_blocks.end(); ++i) { + for (auto i = loadable_blocks.begin(); i != loadable_blocks.end(); ++i) { v3s16 p = *i; MapBlock *block = m_map->emergeBlock(p, false); if (!block) { errorstream << "ServerEnvironment::clearObjects(): " - << "Failed to emerge block " << PP(p) << std::endl; + << "Failed to emerge block " << PP(p) << std::endl; continue; } u32 num_stored = block->m_static_objects.m_stored.size(); @@ -1155,20 +1183,19 @@ void ServerEnvironment::clearObjects(ClearObjectsMode mode) block->m_static_objects.m_stored.clear(); block->m_static_objects.m_active.clear(); block->raiseModified(MOD_STATE_WRITE_NEEDED, - MOD_REASON_CLEAR_ALL_OBJECTS); + MOD_REASON_CLEAR_ALL_OBJECTS); num_objs_cleared += num_stored + num_active; num_blocks_cleared++; } num_blocks_checked++; - if (report_interval != 0 && - num_blocks_checked % report_interval == 0) { + if (report_interval != 0 && num_blocks_checked % report_interval == 0) { float percent = 100.0 * (float)num_blocks_checked / - loadable_blocks.size(); + loadable_blocks.size(); actionstream << "ServerEnvironment::clearObjects(): " - << "Cleared " << num_objs_cleared << " objects" - << " in " << num_blocks_cleared << " blocks (" - << percent << "%)" << std::endl; + << "Cleared " << num_objs_cleared << " objects" + << " in " << num_blocks_cleared << " blocks (" + << percent << "%)" << std::endl; } if (num_blocks_checked % unload_interval == 0) { m_map->unloadUnreferencedBlocks(); @@ -1186,8 +1213,8 @@ void ServerEnvironment::clearObjects(ClearObjectsMode mode) m_last_clear_objects_time = m_game_time; actionstream << "ServerEnvironment::clearObjects(): " - << "Finished: Cleared " << num_objs_cleared << " objects" - << " in " << num_blocks_cleared << " blocks" << std::endl; + << "Finished: Cleared " << num_objs_cleared << " objects" + << " in " << num_blocks_cleared << " blocks" << std::endl; } void ServerEnvironment::step(float dtime) @@ -1231,13 +1258,14 @@ void ServerEnvironment::step(float dtime) /* Manage active block list */ - if (m_active_blocks_management_interval.step(dtime, m_cache_active_block_mgmt_interval)) { + if (m_active_blocks_management_interval.step( + dtime, m_cache_active_block_mgmt_interval)) { ScopeProfiler sp(g_profiler, "ServerEnv: update active blocks", SPT_AVG); /* Get player block positions */ - std::vector players; - for (RemotePlayer *player: m_players) { + std::vector players; + for (RemotePlayer *player : m_players) { // Ignore disconnected players if (player->getPeerId() == PEER_ID_INEXISTENT) continue; @@ -1260,7 +1288,7 @@ void ServerEnvironment::step(float dtime) std::set blocks_removed; std::set blocks_added; m_active_blocks.update(players, active_block_range, active_object_range, - blocks_removed, blocks_added); + blocks_removed, blocks_added); /* Handle removed blocks @@ -1269,7 +1297,7 @@ void ServerEnvironment::step(float dtime) // Convert active objects that are no more in active blocks to static deactivateFarObjects(false); - for (const v3s16 &p: blocks_removed) { + for (const v3s16 &p : blocks_removed) { MapBlock *block = m_map->getBlockNoCreateNoEx(p); if (!block) continue; @@ -1282,7 +1310,7 @@ void ServerEnvironment::step(float dtime) Handle added blocks */ - for (const v3s16 &p: blocks_added) { + for (const v3s16 &p : blocks_added) { MapBlock *block = m_map->getBlockOrEmerge(p); if (!block) { m_active_blocks.m_list.erase(p); @@ -1297,12 +1325,13 @@ void ServerEnvironment::step(float dtime) /* Mess around in active blocks */ - if (m_active_blocks_nodemetadata_interval.step(dtime, m_cache_nodetimer_interval)) { + if (m_active_blocks_nodemetadata_interval.step( + dtime, m_cache_nodetimer_interval)) { ScopeProfiler sp(g_profiler, "ServerEnv: Run node timers", SPT_AVG); float dtime = m_cache_nodetimer_interval; - for (const v3s16 &p: m_active_blocks.m_list) { + for (const v3s16 &p : m_active_blocks.m_list) { MapBlock *block = m_map->getBlockNoCreateNoEx(p); if (!block) continue; @@ -1314,21 +1343,25 @@ void ServerEnvironment::step(float dtime) block->setTimestampNoChangedFlag(m_game_time); // If time has changed much from the one on disk, // set block to be saved when it is unloaded - if(block->getTimestamp() > block->getDiskTimestamp() + 60) + if (block->getTimestamp() > block->getDiskTimestamp() + 60) block->raiseModified(MOD_STATE_WRITE_AT_UNLOAD, - MOD_REASON_BLOCK_EXPIRED); + MOD_REASON_BLOCK_EXPIRED); // Run node timers - std::vector elapsed_timers = block->m_node_timers.step(dtime); + std::vector elapsed_timers = + block->m_node_timers.step(dtime); if (!elapsed_timers.empty()) { MapNode n; v3s16 p2; - for (const NodeTimer &elapsed_timer: elapsed_timers) { + for (const NodeTimer &elapsed_timer : elapsed_timers) { n = block->getNodeNoEx(elapsed_timer.position); - p2 = elapsed_timer.position + block->getPosRelative(); - if (m_script->node_on_timer(p2, n, elapsed_timer.elapsed)) { + p2 = elapsed_timer.position + + block->getPosRelative(); + if (m_script->node_on_timer(p2, n, + elapsed_timer.elapsed)) { block->setNodeTimer(NodeTimer( - elapsed_timer.timeout, 0, elapsed_timer.position)); + elapsed_timer.timeout, 0, + elapsed_timer.position)); } } } @@ -1336,7 +1369,8 @@ void ServerEnvironment::step(float dtime) } if (m_active_block_modifier_interval.step(dtime, m_cache_abm_interval)) { - ScopeProfiler sp(g_profiler, "SEnv: modify in blocks avg per interval", SPT_AVG); + ScopeProfiler sp(g_profiler, "SEnv: modify in blocks avg per interval", + SPT_AVG); TimeTaker timer("modify in active blocks per interval"); // Initialize handling of ActiveBlockModifiers @@ -1350,7 +1384,8 @@ void ServerEnvironment::step(float dtime) // Shuffle the active blocks so that each block gets an equal chance // of having its ABMs run. - std::copy(m_active_blocks.m_abm_list.begin(), m_active_blocks.m_abm_list.end(), output.begin()); + std::copy(m_active_blocks.m_abm_list.begin(), + m_active_blocks.m_abm_list.end(), output.begin()); std::shuffle(output.begin(), output.end(), m_rgen); int i = 0; @@ -1372,15 +1407,18 @@ void ServerEnvironment::step(float dtime) u32 time_ms = timer.getTimerTime(); if (time_ms > max_time_ms) { - warningstream << "active block modifiers took " - << time_ms << "ms (processed " << i << " of " - << output.size() << " active blocks)" << std::endl; + warningstream << "active block modifiers took " << time_ms + << "ms (processed " << i << " of " + << output.size() << " active blocks)" + << std::endl; break; } } - g_profiler->avg("ServerEnv: active blocks", m_active_blocks.m_abm_list.size()); + g_profiler->avg("ServerEnv: active blocks", + m_active_blocks.m_abm_list.size()); g_profiler->avg("ServerEnv: active blocks cached", blocks_cached); - g_profiler->avg("ServerEnv: active blocks scanned for ABMs", blocks_scanned); + g_profiler->avg("ServerEnv: active blocks scanned for ABMs", + blocks_scanned); g_profiler->avg("ServerEnv: ABMs run", abms_run); timer.stop(true); @@ -1405,7 +1443,7 @@ void ServerEnvironment::step(float dtime) send_recommended = true; } - auto cb_state = [this, dtime, send_recommended] (ServerActiveObject *obj) { + auto cb_state = [this, dtime, send_recommended](ServerActiveObject *obj) { if (obj->isGone()) return; @@ -1428,9 +1466,10 @@ void ServerEnvironment::step(float dtime) Manage particle spawner expiration */ if (m_particle_management_interval.step(dtime, 1.0)) { - for (std::unordered_map::iterator i = m_particle_spawners.begin(); - i != m_particle_spawners.end(); ) { - //non expiring spawners + for (std::unordered_map::iterator i = + m_particle_spawners.begin(); + i != m_particle_spawners.end();) { + // non expiring spawners if (i->second == PARTICLE_SPAWNER_NO_EXPIRY) { ++i; continue; @@ -1501,7 +1540,7 @@ void ServerEnvironment::deleteParticleSpawner(u32 id, bool remove_from_object) u16 ServerEnvironment::addActiveObject(ServerActiveObject *object) { - assert(object); // Pre-condition + assert(object); // Pre-condition m_added_objects++; u16 id = addActiveObjectRaw(object, true, 0); return id; @@ -1512,9 +1551,8 @@ u16 ServerEnvironment::addActiveObject(ServerActiveObject *object) inside a radius around a position */ void ServerEnvironment::getAddedActiveObjects(PlayerSAO *playersao, s16 radius, - s16 player_radius, - std::set ¤t_objects, - std::queue &added_objects) + s16 player_radius, std::set ¤t_objects, + std::queue &added_objects) { f32 radius_f = radius * BS; f32 player_radius_f = player_radius * BS; @@ -1522,8 +1560,8 @@ void ServerEnvironment::getAddedActiveObjects(PlayerSAO *playersao, s16 radius, if (player_radius_f < 0.0f) player_radius_f = 0.0f; - m_ao_manager.getAddedActiveObjectsAroundPos(playersao->getBasePosition(), radius_f, - player_radius_f, current_objects, added_objects); + m_ao_manager.getAddedActiveObjectsAroundPos(playersao->getBasePosition(), + radius_f, player_radius_f, current_objects, added_objects); } /* @@ -1531,9 +1569,8 @@ void ServerEnvironment::getAddedActiveObjects(PlayerSAO *playersao, s16 radius, inside a radius around a position */ void ServerEnvironment::getRemovedActiveObjects(PlayerSAO *playersao, s16 radius, - s16 player_radius, - std::set ¤t_objects, - std::queue &removed_objects) + s16 player_radius, std::set ¤t_objects, + std::queue &removed_objects) { f32 radius_f = radius * BS; f32 player_radius_f = player_radius * BS; @@ -1553,7 +1590,7 @@ void ServerEnvironment::getRemovedActiveObjects(PlayerSAO *playersao, s16 radius if (object == NULL) { infostream << "ServerEnvironment::getRemovedActiveObjects():" - << " object in current_objects is NULL" << std::endl; + << " object in current_objects is NULL" << std::endl; removed_objects.push(id); continue; } @@ -1563,7 +1600,8 @@ void ServerEnvironment::getRemovedActiveObjects(PlayerSAO *playersao, s16 radius continue; } - f32 distance_f = object->getBasePosition().getDistanceFrom(playersao->getBasePosition()); + f32 distance_f = object->getBasePosition().getDistanceFrom( + playersao->getBasePosition()); if (object->getType() == ACTIVEOBJECT_TYPE_PLAYER) { if (distance_f <= player_radius_f || player_radius_f == 0) continue; @@ -1576,7 +1614,7 @@ void ServerEnvironment::getRemovedActiveObjects(PlayerSAO *playersao, s16 radius } void ServerEnvironment::setStaticForActiveObjectsInBlock( - v3s16 blockpos, bool static_exists, v3s16 static_block) + v3s16 blockpos, bool static_exists, v3s16 static_block) { MapBlock *block = m_map->getBlockNoCreateNoEx(blockpos); if (!block) @@ -1588,19 +1626,20 @@ void ServerEnvironment::setStaticForActiveObjectsInBlock( if (!sao) { // If this ever happens, there must be some kind of nasty bug. errorstream << "ServerEnvironment::setStaticForObjectsInBlock(): " - "Object from MapBlock::m_static_objects::m_active not found " - "in m_active_objects"; + "Object from MapBlock::m_static_objects::m_active " + "not found " + "in m_active_objects"; continue; } sao->m_static_exists = static_exists; - sao->m_static_block = static_block; + sao->m_static_block = static_block; } } bool ServerEnvironment::getActiveObjectMessage(ActiveObjectMessage *dest) { - if(m_active_object_messages.empty()) + if (m_active_object_messages.empty()) return false; *dest = std::move(m_active_object_messages.front()); @@ -1609,12 +1648,12 @@ bool ServerEnvironment::getActiveObjectMessage(ActiveObjectMessage *dest) } void ServerEnvironment::getSelectedActiveObjects( - const core::line3d &shootline_on_map, - std::vector &objects) + const core::line3d &shootline_on_map, + std::vector &objects) { std::vector objs; getObjectsInsideRadius(objs, shootline_on_map.start, - shootline_on_map.getLength() + 10.0f, nullptr); + shootline_on_map.getLength() + 10.0f, nullptr); const v3f line_vector = shootline_on_map.getVector(); for (auto obj : objs) { @@ -1626,26 +1665,27 @@ void ServerEnvironment::getSelectedActiveObjects( v3f pos = obj->getBasePosition(); - aabb3f offsetted_box(selection_box.MinEdge + pos, - selection_box.MaxEdge + pos); + aabb3f offsetted_box( + selection_box.MinEdge + pos, selection_box.MaxEdge + pos); v3f current_intersection; v3s16 current_normal; if (boxLineCollision(offsetted_box, shootline_on_map.start, line_vector, - ¤t_intersection, ¤t_normal)) { - objects.emplace_back( - (s16) obj->getId(), current_intersection, current_normal, - (current_intersection - shootline_on_map.start).getLengthSQ()); + ¤t_intersection, ¤t_normal)) { + objects.emplace_back((s16)obj->getId(), current_intersection, + current_normal, + (current_intersection - shootline_on_map.start) + .getLengthSQ()); } } } /* - ************ Private methods ************* -*/ + ************ Private methods ************* + */ -u16 ServerEnvironment::addActiveObjectRaw(ServerActiveObject *object, - bool set_changed, u32 dtime_s) +u16 ServerEnvironment::addActiveObjectRaw( + ServerActiveObject *object, bool set_changed, u32 dtime_s) { if (!m_ao_manager.registerObject(object)) { return 0; @@ -1664,19 +1704,20 @@ u16 ServerEnvironment::addActiveObjectRaw(ServerActiveObject *object, // Add to the block where the object is located in v3s16 blockpos = getNodeBlockPos(floatToInt(objectpos, BS)); MapBlock *block = m_map->emergeBlock(blockpos); - if(block){ + if (block) { block->m_static_objects.m_active[object->getId()] = s_obj; object->m_static_exists = true; object->m_static_block = blockpos; - if(set_changed) + if (set_changed) block->raiseModified(MOD_STATE_WRITE_NEEDED, - MOD_REASON_ADD_ACTIVE_OBJECT_RAW); + MOD_REASON_ADD_ACTIVE_OBJECT_RAW); } else { v3s16 p = floatToInt(objectpos, BS); - errorstream<<"ServerEnvironment::addActiveObjectRaw(): " - <<"could not emerge block for storing id="<getId() - <<" statically (pos="<m_static_block) << std::endl; + warningstream << "ServerEnvironment::" + "removeRemovedObjects(): " + << "id=" << id + << " m_static_exists=true but " + << "static data doesn't actually " + "exist in " + << PP(obj->m_static_block) + << std::endl; } } else { - infostream << "Failed to emerge block from which an object to " - << "be deactivated was loaded from. id=" << id << std::endl; + infostream << "Failed to emerge block from which an " + "object to " + << "be deactivated was loaded from. id=" << id + << std::endl; } } @@ -1757,45 +1807,46 @@ void ServerEnvironment::removeRemovedObjects() static void print_hexdump(std::ostream &o, const std::string &data) { const int linelength = 16; - for(int l=0; ; l++){ + for (int l = 0;; l++) { int i0 = linelength * l; bool at_end = false; int thislinelength = linelength; - if(i0 + thislinelength > (int)data.size()){ + if (i0 + thislinelength > (int)data.size()) { thislinelength = data.size() - i0; at_end = true; } - for(int di=0; di= 32) - o<= 32) + o << data[i]; else - o<<"."; + o << "."; } - o<getPos()) - <<" ("<m_static_objects.m_stored.size() - <<" objects)"<m_static_objects.m_stored.size() > g_settings->getU16("max_objects_per_block")); + verbosestream << "ServerEnvironment::activateObjects(): " + << "activating objects of block " << PP(block->getPos()) << " (" + << block->m_static_objects.m_stored.size() << " objects)" + << std::endl; + bool large_amount = (block->m_static_objects.m_stored.size() > + g_settings->getU16("max_objects_per_block")); if (large_amount) { - errorstream<<"suspiciously large amount of objects detected: " - <m_static_objects.m_stored.size()<<" in " - <getPos()) - <<"; removing all of them."<m_static_objects.m_stored.size() << " in " + << PP(block->getPos()) << "; removing all of them." + << std::endl; // Clear stored list block->m_static_objects.m_stored.clear(); - block->raiseModified(MOD_STATE_WRITE_NEEDED, - MOD_REASON_TOO_MANY_OBJECTS); + block->raiseModified(MOD_STATE_WRITE_NEEDED, MOD_REASON_TOO_MANY_OBJECTS); return; } @@ -1833,22 +1884,24 @@ void ServerEnvironment::activateObjects(MapBlock *block, u32 dtime_s) std::vector new_stored; for (const StaticObject &s_obj : block->m_static_objects.m_stored) { // Create an active object from the data - ServerActiveObject *obj = createSAO((ActiveObjectType) s_obj.type, s_obj.pos, - s_obj.data); + ServerActiveObject *obj = createSAO( + (ActiveObjectType)s_obj.type, s_obj.pos, s_obj.data); // If couldn't create object, store static data back. if (!obj) { - errorstream<<"ServerEnvironment::activateObjects(): " - <<"failed to create active object from static object " - <<"in block "<m_known_by_count > 0 && !force_delete); @@ -1942,33 +1997,45 @@ void ServerEnvironment::deactivateFarObjects(bool _force_delete) if (obj->m_static_block == blockpos_o) stays_in_same_block = true; - MapBlock *block = m_map->emergeBlock(obj->m_static_block, false); + MapBlock *block = m_map->emergeBlock( + obj->m_static_block, false); if (block) { - const auto n = block->m_static_objects.m_active.find(id); + const auto n = block->m_static_objects.m_active + .find(id); if (n != block->m_static_objects.m_active.end()) { StaticObject static_old = n->second; - float save_movem = obj->getMinimumSavedMovement(); + float save_movem = + obj->getMinimumSavedMovement(); if (static_old.data == s_obj.data && - (static_old.pos - objectpos).getLength() < save_movem) + (static_old.pos - objectpos).getLength() < + save_movem) data_changed = false; } else { - warningstream << "ServerEnvironment::deactivateFarObjects(): " - << "id=" << id << " m_static_exists=true but " - << "static data doesn't actually exist in " - << PP(obj->m_static_block) << std::endl; + warningstream << "ServerEnvironment::" + "deactivateFarObjects():" + " " + << "id=" << id + << " m_static_exists=true " + "but " + << "static data doesn't " + "actually exist in " + << PP(obj->m_static_block) + << std::endl; } } } /* - While changes are always saved, blocks are only marked as modified - if the object has moved or different staticdata. (see above) + While changes are always saved, blocks are only marked as + modified if the object has moved or different staticdata. (see + above) */ bool shall_be_written = (!stays_in_same_block || data_changed); - u32 reason = shall_be_written ? MOD_REASON_STATIC_DATA_CHANGED : MOD_REASON_UNKNOWN; + u32 reason = shall_be_written ? MOD_REASON_STATIC_DATA_CHANGED + : MOD_REASON_UNKNOWN; // Delete old static object deleteStaticFromBlock(obj, id, reason, false); @@ -1986,16 +2053,16 @@ void ServerEnvironment::deactivateFarObjects(bool _force_delete) */ if (pending_delete && !force_delete) { verbosestream << "ServerEnvironment::deactivateFarObjects(): " - << "object id=" << id << " is known by clients" - << "; not deleting yet" << std::endl; + << "object id=" << id << " is known by clients" + << "; not deleting yet" << std::endl; obj->m_pending_deactivation = true; return false; } verbosestream << "ServerEnvironment::deactivateFarObjects(): " - << "object id=" << id << " is not known by clients" - << "; deleting" << std::endl; + << "object id=" << id << " is not known by clients" + << "; deleting" << std::endl; // Tell the object about removal obj->removingFromEnvironment(); @@ -2025,8 +2092,10 @@ void ServerEnvironment::deleteStaticFromBlock( block = m_map->emergeBlock(obj->m_static_block, false); if (!block) { if (!no_emerge) - errorstream << "ServerEnv: Failed to emerge block " << PP(obj->m_static_block) - << " when deleting static data of object from it. id=" << id << std::endl; + errorstream << "ServerEnv: Failed to emerge block " + << PP(obj->m_static_block) + << " when deleting static data of object from it. id=" + << id << std::endl; return; } @@ -2037,10 +2106,8 @@ void ServerEnvironment::deleteStaticFromBlock( obj->m_static_exists = false; } -bool ServerEnvironment::saveStaticToBlock( - v3s16 blockpos, u16 store_id, - ServerActiveObject *obj, const StaticObject &s_obj, - u32 mod_reason) +bool ServerEnvironment::saveStaticToBlock(v3s16 blockpos, u16 store_id, + ServerActiveObject *obj, const StaticObject &s_obj, u32 mod_reason) { MapBlock *block = nullptr; try { @@ -2052,16 +2119,19 @@ bool ServerEnvironment::saveStaticToBlock( } if (!block) { - errorstream << "ServerEnv: Failed to emerge block " << PP(obj->m_static_block) - << " when saving static data of object to it. id=" << store_id << std::endl; + errorstream << "ServerEnv: Failed to emerge block " + << PP(obj->m_static_block) + << " when saving static data of object to it. id=" << store_id + << std::endl; return false; } - if (block->m_static_objects.m_stored.size() >= g_settings->getU16("max_objects_per_block")) { + if (block->m_static_objects.m_stored.size() >= + g_settings->getU16("max_objects_per_block")) { warningstream << "ServerEnv: Trying to store id = " << store_id - << " statically but block " << PP(blockpos) - << " already contains " - << block->m_static_objects.m_stored.size() - << " objects." << std::endl; + << " statically but block " << PP(blockpos) + << " already contains " + << block->m_static_objects.m_stored.size() << " objects." + << std::endl; return false; } @@ -2075,8 +2145,8 @@ bool ServerEnvironment::saveStaticToBlock( return true; } -PlayerDatabase *ServerEnvironment::openPlayerDatabase(const std::string &name, - const std::string &savedir, const Settings &conf) +PlayerDatabase *ServerEnvironment::openPlayerDatabase( + const std::string &name, const std::string &savedir, const Settings &conf) { if (name == "sqlite3") @@ -2104,8 +2174,8 @@ PlayerDatabase *ServerEnvironment::openPlayerDatabase(const std::string &name, throw BaseException(std::string("Database backend ") + name + " not supported."); } -bool ServerEnvironment::migratePlayersDatabase(const GameParams &game_params, - const Settings &cmd_args) +bool ServerEnvironment::migratePlayersDatabase( + const GameParams &game_params, const Settings &cmd_args) { std::string migrate_to = cmd_args.get("migrate-players"); Settings world_mt; @@ -2117,21 +2187,22 @@ bool ServerEnvironment::migratePlayersDatabase(const GameParams &game_params, if (!world_mt.exists("player_backend")) { errorstream << "Please specify your current backend in world.mt:" - << std::endl - << " player_backend = {files|sqlite3|leveldb|postgresql}" - << std::endl; + << std::endl + << " player_backend = " + "{files|sqlite3|leveldb|postgresql}" + << std::endl; return false; } std::string backend = world_mt.get("player_backend"); if (backend == migrate_to) { errorstream << "Cannot migrate: new backend is same" - << " as the old one" << std::endl; + << " as the old one" << std::endl; return false; } - const std::string players_backup_path = game_params.world_path + DIR_DELIM - + "players.bak"; + const std::string players_backup_path = + game_params.world_path + DIR_DELIM + "players.bak"; if (backend == "files") { // Create backup directory @@ -2139,15 +2210,15 @@ bool ServerEnvironment::migratePlayersDatabase(const GameParams &game_params, } try { - PlayerDatabase *srcdb = ServerEnvironment::openPlayerDatabase(backend, - game_params.world_path, world_mt); - PlayerDatabase *dstdb = ServerEnvironment::openPlayerDatabase(migrate_to, - game_params.world_path, world_mt); + PlayerDatabase *srcdb = ServerEnvironment::openPlayerDatabase( + backend, game_params.world_path, world_mt); + PlayerDatabase *dstdb = ServerEnvironment::openPlayerDatabase( + migrate_to, game_params.world_path, world_mt); std::vector player_list; srcdb->listPlayers(player_list); for (std::vector::const_iterator it = player_list.begin(); - it != player_list.end(); ++it) { + it != player_list.end(); ++it) { actionstream << "Migrating player " << it->c_str() << std::endl; RemotePlayer player(it->c_str(), NULL); PlayerSAO playerSAO(NULL, &player, 15000, false); @@ -2161,31 +2232,34 @@ bool ServerEnvironment::migratePlayersDatabase(const GameParams &game_params, // For files source, move player files to backup dir if (backend == "files") { - fs::Rename( - game_params.world_path + DIR_DELIM + "players" + DIR_DELIM + (*it), - players_backup_path + DIR_DELIM + (*it)); + fs::Rename(game_params.world_path + DIR_DELIM + + "players" + DIR_DELIM + + (*it), + players_backup_path + DIR_DELIM + (*it)); } } - actionstream << "Successfully migrated " << player_list.size() << " players" - << std::endl; + actionstream << "Successfully migrated " << player_list.size() + << " players" << std::endl; world_mt.set("player_backend", migrate_to); if (!world_mt.updateConfigFile(world_mt_path.c_str())) errorstream << "Failed to update world.mt!" << std::endl; else actionstream << "world.mt updated" << std::endl; - // When migration is finished from file backend, remove players directory if empty + // When migration is finished from file backend, remove players directory + // if empty if (backend == "files") { - fs::DeleteSingleFileOrEmptyDirectory(game_params.world_path + DIR_DELIM - + "players"); + fs::DeleteSingleFileOrEmptyDirectory( + game_params.world_path + DIR_DELIM + "players"); } delete srcdb; delete dstdb; } catch (BaseException &e) { - errorstream << "An error occurred during migration: " << e.what() << std::endl; + errorstream << "An error occurred during migration: " << e.what() + << std::endl; return false; } return true; @@ -2232,19 +2306,22 @@ bool ServerEnvironment::migrateAuthDatabase( backend = world_mt.get("auth_backend"); else warningstream << "No auth_backend found in world.mt, " - "assuming \"files\"." << std::endl; + "assuming \"files\"." + << std::endl; if (backend == migrate_to) { errorstream << "Cannot migrate: new backend is same" - << " as the old one" << std::endl; + << " as the old one" << std::endl; return false; } try { - const std::unique_ptr srcdb(ServerEnvironment::openAuthDatabase( - backend, game_params.world_path, world_mt)); - const std::unique_ptr dstdb(ServerEnvironment::openAuthDatabase( - migrate_to, game_params.world_path, world_mt)); + const std::unique_ptr srcdb( + ServerEnvironment::openAuthDatabase(backend, + game_params.world_path, world_mt)); + const std::unique_ptr dstdb( + ServerEnvironment::openAuthDatabase(migrate_to, + game_params.world_path, world_mt)); std::vector names_list; srcdb->listNames(names_list); @@ -2259,7 +2336,7 @@ bool ServerEnvironment::migrateAuthDatabase( } actionstream << "Successfully migrated " << names_list.size() - << " auth entries" << std::endl; + << " auth entries" << std::endl; world_mt.set("auth_backend", migrate_to); if (!world_mt.updateConfigFile(world_mt_path.c_str())) errorstream << "Failed to update world.mt!" << std::endl; @@ -2275,13 +2352,15 @@ bool ServerEnvironment::migrateAuthDatabase( if (!fs::PathExists(auth_bak_path)) if (fs::Rename(auth_txt_path, auth_bak_path)) actionstream << "Renamed auth.txt to auth.txt.bak" - << std::endl; + << std::endl; else errorstream << "Could not rename auth.txt to " - "auth.txt.bak" << std::endl; + "auth.txt.bak" + << std::endl; else warningstream << "auth.txt.bak already exists, auth.txt " - "not renamed" << std::endl; + "not renamed" + << std::endl; } } catch (BaseException &e) { diff --git a/src/serverenvironment.h b/src/serverenvironment.h index af742e290..d4e6bdad2 100644 --- a/src/serverenvironment.h +++ b/src/serverenvironment.h @@ -70,7 +70,7 @@ public: // This is called usually at interval for 1/chance of the nodes virtual void trigger(ServerEnvironment *env, v3s16 p, MapNode n){}; virtual void trigger(ServerEnvironment *env, v3s16 p, MapNode n, - u32 active_object_count, u32 active_object_count_wider){}; + u32 active_object_count, u32 active_object_count_wider){}; }; struct ABMWithState @@ -95,7 +95,8 @@ struct LoadingBlockModifierDef struct LBMContentMapping { - typedef std::unordered_map> lbm_map; + typedef std::unordered_map> + lbm_map; lbm_map map; std::vector lbm_list; @@ -117,8 +118,7 @@ public: // Don't call this after loadIntroductionTimes() ran. void addLBMDef(LoadingBlockModifierDef *lbm_def); - void loadIntroductionTimes(const std::string ×, - IGameDef *gamedef, u32 now); + void loadIntroductionTimes(const std::string ×, IGameDef *gamedef, u32 now); // Don't call this before loadIntroductionTimes() ran. std::string createIntroductionTimesString(); @@ -147,7 +147,9 @@ private: // after the given time. This is guaranteed to return // valid values for everything lbm_lookup_map::const_iterator getLBMsIntroducedAfter(u32 time) - { return m_lbm_lookup.lower_bound(time); } + { + return m_lbm_lookup.lower_bound(time); + } }; /* @@ -157,19 +159,13 @@ private: class ActiveBlockList { public: - void update(std::vector &active_players, - s16 active_block_range, - s16 active_object_range, - std::set &blocks_removed, - std::set &blocks_added); + void update(std::vector &active_players, s16 active_block_range, + s16 active_object_range, std::set &blocks_removed, + std::set &blocks_added); - bool contains(v3s16 p){ - return (m_list.find(p) != m_list.end()); - } + bool contains(v3s16 p) { return (m_list.find(p) != m_list.end()); } - void clear(){ - m_list.clear(); - } + void clear() { m_list.clear(); } std::set m_list; std::set m_abm_list; @@ -181,13 +177,14 @@ private: /* Operation mode for ServerEnvironment::clearObjects() */ -enum ClearObjectsMode { +enum ClearObjectsMode +{ // Load and go through every mapblock, clearing objects - CLEAR_OBJECTS_MODE_FULL, + CLEAR_OBJECTS_MODE_FULL, // Clear objects immediately in loaded mapblocks; // clear objects in unloaded mapblocks only when the mapblocks are next activated. - CLEAR_OBJECTS_MODE_QUICK, + CLEAR_OBJECTS_MODE_QUICK, }; /* @@ -201,31 +198,28 @@ typedef std::unordered_map ServerActiveObjectMap; class ServerEnvironment : public Environment { public: - ServerEnvironment(ServerMap *map, ServerScripting *scriptIface, - Server *server, const std::string &path_world); + ServerEnvironment(ServerMap *map, ServerScripting *scriptIface, Server *server, + const std::string &path_world); ~ServerEnvironment(); - Map & getMap(); + Map &getMap(); - ServerMap & getServerMap(); + ServerMap &getServerMap(); - //TODO find way to remove this fct! - ServerScripting* getScriptIface() - { return m_script; } + // TODO find way to remove this fct! + ServerScripting *getScriptIface() { return m_script; } - Server *getGameDef() - { return m_server; } + Server *getGameDef() { return m_server; } - float getSendRecommendedInterval() - { return m_recommended_send_interval; } + float getSendRecommendedInterval() { return m_recommended_send_interval; } - void kickAllPlayers(AccessDeniedCode reason, - const std::string &str_reason, bool reconnect); + void kickAllPlayers(AccessDeniedCode reason, const std::string &str_reason, + bool reconnect); // Save players void saveLoadedPlayers(bool force = false); void savePlayer(RemotePlayer *player); PlayerSAO *loadPlayer(RemotePlayer *player, bool *new_player, session_t peer_id, - bool is_singleplayer); + bool is_singleplayer); void addPlayer(RemotePlayer *player); void removePlayer(RemotePlayer *player); bool removePlayerFromDatabase(const std::string &name); @@ -245,7 +239,7 @@ public: ------------------------------------------- */ - ServerActiveObject* getActiveObject(u16 id) + ServerActiveObject *getActiveObject(u16 id) { return m_ao_manager.getActiveObject(id); } @@ -267,25 +261,21 @@ public: Return value: true if succeeded, false if failed. (note: not used, pending removal from engine) */ - //bool addActiveObjectAsStatic(ServerActiveObject *object); + // bool addActiveObjectAsStatic(ServerActiveObject *object); /* Find out what new objects have been added to inside a radius around a position */ - void getAddedActiveObjects(PlayerSAO *playersao, s16 radius, - s16 player_radius, - std::set ¤t_objects, - std::queue &added_objects); + void getAddedActiveObjects(PlayerSAO *playersao, s16 radius, s16 player_radius, + std::set ¤t_objects, std::queue &added_objects); /* Find out what new objects have been removed from inside a radius around a position */ - void getRemovedActiveObjects(PlayerSAO *playersao, s16 radius, - s16 player_radius, - std::set ¤t_objects, - std::queue &removed_objects); + void getRemovedActiveObjects(PlayerSAO *playersao, s16 radius, s16 player_radius, + std::set ¤t_objects, std::queue &removed_objects); /* Get the next message emitted by some active object. @@ -293,16 +283,14 @@ public: */ bool getActiveObjectMessage(ActiveObjectMessage *dest); - virtual void getSelectedActiveObjects( - const core::line3d &shootline_on_map, - std::vector &objects - ); + virtual void getSelectedActiveObjects(const core::line3d &shootline_on_map, + std::vector &objects); /* Activate objects and dynamically modify for the dtime determined from timestamp and additional_dtime */ - void activateBlock(MapBlock *block, u32 additional_dtime=0); + void activateBlock(MapBlock *block, u32 additional_dtime = 0); /* {Active,Loading}BlockModifiers @@ -323,10 +311,12 @@ public: bool swapNode(v3s16 p, const MapNode &n); // Find all active objects inside a radius around a point - void getObjectsInsideRadius(std::vector &objects, const v3f &pos, float radius, + void getObjectsInsideRadius(std::vector &objects, + const v3f &pos, float radius, std::function include_obj_cb) { - return m_ao_manager.getObjectsInsideRadius(pos, radius, objects, include_obj_cb); + return m_ao_manager.getObjectsInsideRadius( + pos, radius, objects, include_obj_cb); } // Clear objects, loading and going through every MapBlock @@ -340,26 +330,29 @@ public: void reportMaxLagEstimate(float f) { m_max_lag_estimate = f; } float getMaxLagEstimate() { return m_max_lag_estimate; } - std::set* getForceloadedBlocks() { return &m_active_blocks.m_forceloaded_list; }; + std::set *getForceloadedBlocks() + { + return &m_active_blocks.m_forceloaded_list; + }; // Sets the static object status all the active objects in the specified block // This is only really needed for deleting blocks from the map - void setStaticForActiveObjectsInBlock(v3s16 blockpos, - bool static_exists, v3s16 static_block=v3s16(0,0,0)); + void setStaticForActiveObjectsInBlock(v3s16 blockpos, bool static_exists, + v3s16 static_block = v3s16(0, 0, 0)); RemotePlayer *getPlayer(const session_t peer_id); - RemotePlayer *getPlayer(const char* name); + RemotePlayer *getPlayer(const char *name); const std::vector getPlayers() const { return m_players; } u32 getPlayerCount() const { return m_players.size(); } - static bool migratePlayersDatabase(const GameParams &game_params, - const Settings &cmd_args); + static bool migratePlayersDatabase( + const GameParams &game_params, const Settings &cmd_args); AuthDatabase *getAuthDatabase() { return m_auth_database; } - static bool migrateAuthDatabase(const GameParams &game_params, - const Settings &cmd_args); -private: + static bool migrateAuthDatabase( + const GameParams &game_params, const Settings &cmd_args); +private: /** * called if env_meta.txt doesn't exist (e.g. new world) */ @@ -412,8 +405,8 @@ private: */ void deleteStaticFromBlock( ServerActiveObject *obj, u16 id, u32 mod_reason, bool no_emerge); - bool saveStaticToBlock(v3s16 blockpos, u16 store_id, - ServerActiveObject *obj, const StaticObject &s_obj, u32 mod_reason); + bool saveStaticToBlock(v3s16 blockpos, u16 store_id, ServerActiveObject *obj, + const StaticObject &s_obj, u32 mod_reason); /* Member variables @@ -422,7 +415,7 @@ private: // The map ServerMap *m_map; // Lua state - ServerScripting* m_script; + ServerScripting *m_script; // Server definition Server *m_server; // Active Object Manager @@ -459,7 +452,7 @@ private: float m_max_lag_estimate = 0.1f; // peer_ids in here should be unique, except that there may be many 0s - std::vector m_players; + std::vector m_players; PlayerDatabase *m_player_database = nullptr; AuthDatabase *m_auth_database = nullptr; @@ -472,5 +465,6 @@ private: std::unordered_map m_particle_spawners; std::unordered_map m_particle_spawner_attachments; - ServerActiveObject* createSAO(ActiveObjectType type, v3f pos, const std::string &data); + ServerActiveObject *createSAO( + ActiveObjectType type, v3f pos, const std::string &data); }; diff --git a/src/serverlist.cpp b/src/serverlist.cpp index 18264e933..a10e59c12 100644 --- a/src/serverlist.cpp +++ b/src/serverlist.cpp @@ -42,12 +42,11 @@ std::string getFilePath() std::string serverlist_file = g_settings->get("serverlist_file"); std::string dir_path = "client" DIR_DELIM "serverlist" DIR_DELIM; - fs::CreateDir(porting::path_user + DIR_DELIM "client"); + fs::CreateDir(porting::path_user + DIR_DELIM "client"); fs::CreateDir(porting::path_user + DIR_DELIM + dir_path); return porting::path_user + DIR_DELIM + dir_path + serverlist_file; } - std::vector getLocal() { std::string path = ServerList::getFilePath(); @@ -65,16 +64,15 @@ std::vector getLocal() return deSerialize(liststring); } - std::vector getOnline() { std::ostringstream geturl; u16 proto_version_min = CLIENT_PROTOCOL_VERSION_MIN; - geturl << g_settings->get("serverlist_url") << - "/list?proto_version_min=" << proto_version_min << - "&proto_version_max=" << CLIENT_PROTOCOL_VERSION_MAX; + geturl << g_settings->get("serverlist_url") + << "/list?proto_version_min=" << proto_version_min + << "&proto_version_max=" << CLIENT_PROTOCOL_VERSION_MAX; Json::Value root = fetchJsonValue(geturl.str(), NULL); std::vector server_list; @@ -97,7 +95,6 @@ std::vector getOnline() return server_list; } - // Delete a server from the local favorites list bool deleteEntry(const ServerListSpec &server) { @@ -157,8 +154,8 @@ std::vector deSerialize(const std::string &liststring) server["port"] = tmp; bool unique = true; for (const ServerListSpec &added : serverlist) { - if (server["name"] == added["name"] - && server["port"] == added["port"]) { + if (server["name"] == added["name"] && + server["port"] == added["port"]) { unique = false; break; } @@ -199,17 +196,11 @@ const std::string serializeJson(const std::vector &serverlist) return fastWriteJson(root); } - #if USE_CURL -void sendAnnounce(AnnounceAction action, - const u16 port, - const std::vector &clients_names, - const double uptime, - const u32 game_time, - const float lag, - const std::string &gameid, - const std::string &mg_name, - const std::vector &mods, +void sendAnnounce(AnnounceAction action, const u16 port, + const std::vector &clients_names, const double uptime, + const u32 game_time, const float lag, const std::string &gameid, + const std::string &mg_name, const std::vector &mods, bool dedicated) { static const char *aa_names[] = {"start", "update", "delete"}; @@ -220,21 +211,24 @@ void sendAnnounce(AnnounceAction action, server["address"] = g_settings->get("server_address"); } if (action != AA_DELETE) { - bool strict_checking = g_settings->getBool("strict_protocol_version_checking"); - server["name"] = g_settings->get("server_name"); - server["description"] = g_settings->get("server_description"); - server["version"] = g_version_string; - server["proto_min"] = strict_checking ? LATEST_PROTOCOL_VERSION : SERVER_PROTOCOL_VERSION_MIN; - server["proto_max"] = strict_checking ? LATEST_PROTOCOL_VERSION : SERVER_PROTOCOL_VERSION_MAX; - server["url"] = g_settings->get("server_url"); - server["creative"] = g_settings->getBool("creative_mode"); - server["damage"] = g_settings->getBool("enable_damage"); - server["password"] = g_settings->getBool("disallow_empty_password"); - server["pvp"] = g_settings->getBool("enable_pvp"); - server["uptime"] = (int) uptime; - server["game_time"] = game_time; - server["clients"] = (int) clients_names.size(); - server["clients_max"] = g_settings->getU16("max_users"); + bool strict_checking = + g_settings->getBool("strict_protocol_version_checking"); + server["name"] = g_settings->get("server_name"); + server["description"] = g_settings->get("server_description"); + server["version"] = g_version_string; + server["proto_min"] = strict_checking ? LATEST_PROTOCOL_VERSION + : SERVER_PROTOCOL_VERSION_MIN; + server["proto_max"] = strict_checking ? LATEST_PROTOCOL_VERSION + : SERVER_PROTOCOL_VERSION_MAX; + server["url"] = g_settings->get("server_url"); + server["creative"] = g_settings->getBool("creative_mode"); + server["damage"] = g_settings->getBool("enable_damage"); + server["password"] = g_settings->getBool("disallow_empty_password"); + server["pvp"] = g_settings->getBool("enable_pvp"); + server["uptime"] = (int)uptime; + server["game_time"] = game_time; + server["clients"] = (int)clients_names.size(); + server["clients_max"] = g_settings->getU16("max_users"); server["clients_list"] = Json::Value(Json::arrayValue); for (const std::string &clients_name : clients_names) { server["clients_list"].append(clients_name); @@ -244,16 +238,18 @@ void sendAnnounce(AnnounceAction action, } if (action == AA_START) { - server["dedicated"] = dedicated; - server["rollback"] = g_settings->getBool("enable_rollback_recording"); - server["mapgen"] = mg_name; - server["privs"] = g_settings->get("default_privs"); - server["can_see_far_names"] = g_settings->getS16("player_transfer_distance") <= 0; - server["mods"] = Json::Value(Json::arrayValue); + server["dedicated"] = dedicated; + server["rollback"] = g_settings->getBool("enable_rollback_recording"); + server["mapgen"] = mg_name; + server["privs"] = g_settings->get("default_privs"); + server["can_see_far_names"] = + g_settings->getS16("player_transfer_distance") <= 0; + server["mods"] = Json::Value(Json::arrayValue); for (const ModSpec &mod : mods) { server["mods"].append(mod.name); } - actionstream << "Announcing to " << g_settings->get("serverlist_url") << std::endl; + actionstream << "Announcing to " << g_settings->get("serverlist_url") + << std::endl; } else if (action == AA_UPDATE) { if (lag) server["lag"] = lag; @@ -268,4 +264,3 @@ void sendAnnounce(AnnounceAction action, #endif } // namespace ServerList - diff --git a/src/serverlist.h b/src/serverlist.h index 2b82b7431..3e2745fc5 100644 --- a/src/serverlist.h +++ b/src/serverlist.h @@ -40,9 +40,15 @@ std::vector deSerializeJson(const std::string &liststring); const std::string serializeJson(const std::vector &serverlist); #if USE_CURL -enum AnnounceAction {AA_START, AA_UPDATE, AA_DELETE}; +enum AnnounceAction +{ + AA_START, + AA_UPDATE, + AA_DELETE +}; void sendAnnounce(AnnounceAction, u16 port, - const std::vector &clients_names = std::vector(), + const std::vector &clients_names = + std::vector(), double uptime = 0, u32 game_time = 0, float lag = 0, const std::string &gameid = "", const std::string &mg_name = "", const std::vector &mods = std::vector(), diff --git a/src/settings.cpp b/src/settings.cpp index 55404319e..4f8b7b617 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -42,16 +42,14 @@ Settings::~Settings() clear(); } - -Settings & Settings::operator += (const Settings &other) +Settings &Settings::operator+=(const Settings &other) { update(other); return *this; } - -Settings & Settings::operator = (const Settings &other) +Settings &Settings::operator=(const Settings &other) { if (&other == this) return *this; @@ -65,7 +63,6 @@ Settings & Settings::operator = (const Settings &other) return *this; } - bool Settings::checkNameValid(const std::string &name) { bool valid = name.find_first_of("=\"{}#") == std::string::npos; @@ -73,20 +70,19 @@ bool Settings::checkNameValid(const std::string &name) valid = std::find_if(name.begin(), name.end(), ::isspace) == name.end(); if (!valid) { - errorstream << "Invalid setting name \"" << name << "\"" - << std::endl; + errorstream << "Invalid setting name \"" << name << "\"" << std::endl; return false; } return true; } - bool Settings::checkValueValid(const std::string &value) { if (value.substr(0, 3) == "\"\"\"" || - value.find("\n\"\"\"") != std::string::npos) { + value.find("\n\"\"\"") != std::string::npos) { errorstream << "Invalid character sequence '\"\"\"' found in" - " setting value!" << std::endl; + " setting value!" + << std::endl; return false; } return true; @@ -117,7 +113,6 @@ std::string Settings::getMultiline(std::istream &is, size_t *num_lines) return value; } - bool Settings::readConfigFile(const char *filename) { std::ifstream is(filename); @@ -127,7 +122,6 @@ bool Settings::readConfigFile(const char *filename) return parseConfigLines(is, ""); } - bool Settings::parseConfigLines(std::istream &is, const std::string &end) { MutexAutoLock lock(m_mutex); @@ -166,7 +160,6 @@ bool Settings::parseConfigLines(std::istream &is, const std::string &end) return end.empty(); } - void Settings::writeLines(std::ostream &os, u32 tab_depth) const { MutexAutoLock lock(m_mutex); @@ -175,9 +168,8 @@ void Settings::writeLines(std::ostream &os, u32 tab_depth) const printEntry(os, setting_it.first, setting_it.second, tab_depth); } - void Settings::printEntry(std::ostream &os, const std::string &name, - const SettingsEntry &entry, u32 tab_depth) + const SettingsEntry &entry, u32 tab_depth) { for (u32 i = 0; i != tab_depth; i++) os << "\t"; @@ -200,9 +192,8 @@ void Settings::printEntry(std::ostream &os, const std::string &name, } } - -bool Settings::updateConfigObject(std::istream &is, std::ostream &os, - const std::string &end, u32 tab_depth) +bool Settings::updateConfigObject( + std::istream &is, std::ostream &os, const std::string &end, u32 tab_depth) { SettingEntries::const_iterator it; std::set present_entries; @@ -227,7 +218,8 @@ bool Settings::updateConfigObject(std::istream &is, std::ostream &os, case SPE_KVPAIR: it = m_settings.find(name); if (it != m_settings.end() && - (it->second.is_group || it->second.value != value)) { + (it->second.is_group || + it->second.value != value)) { printEntry(os, name, it->second, tab_depth); was_modified = true; } else if (it == m_settings.end()) { @@ -246,14 +238,15 @@ bool Settings::updateConfigObject(std::istream &is, std::ostream &os, if (it != m_settings.end() && it->second.is_group) { os << line << "\n"; sanity_check(it->second.group != NULL); - was_modified |= it->second.group->updateConfigObject(is, os, - "}", tab_depth + 1); + was_modified |= it->second.group->updateConfigObject( + is, os, "}", tab_depth + 1); } else if (it == m_settings.end()) { // Remove by skipping was_modified = true; Settings removed_group; // Move 'is' to group end std::stringstream ss; - removed_group.updateConfigObject(is, ss, "}", tab_depth + 1); + removed_group.updateConfigObject( + is, ss, "}", tab_depth + 1); break; } else { printEntry(os, name, it->second, tab_depth); @@ -279,7 +272,6 @@ bool Settings::updateConfigObject(std::istream &is, std::ostream &os, return was_modified; } - bool Settings::updateConfigFile(const char *filename) { MutexAutoLock lock(m_mutex); @@ -294,32 +286,31 @@ bool Settings::updateConfigFile(const char *filename) return true; if (!fs::safeWriteToFile(filename, os.str())) { - errorstream << "Error writing configuration file: \"" - << filename << "\"" << std::endl; + errorstream << "Error writing configuration file: \"" << filename << "\"" + << std::endl; return false; } return true; } - -bool Settings::parseCommandLine(int argc, char *argv[], - std::map &allowed_options) +bool Settings::parseCommandLine( + int argc, char *argv[], std::map &allowed_options) { int nonopt_index = 0; for (int i = 1; i < argc; i++) { std::string arg_name = argv[i]; if (arg_name.substr(0, 2) != "--") { // If option doesn't start with -, read it in as nonoptX - if (arg_name[0] != '-'){ + if (arg_name[0] != '-') { std::string name = "nonopt"; name += itos(nonopt_index); set(name, arg_name); nonopt_index++; continue; } - errorstream << "Invalid command-line parameter \"" - << arg_name << "\": --

      MoB6*+Hpe+~S~mnLcNKYzEQYz;HC7zP#> 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 z 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


      ~o*Dj$>D#L69Ji0oY5Ij&b5qgNwuOc>l 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
        9dh*O+FLYA-5e+LV!SYQtq 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 Wk6ez=tmk8In2!;{G~`I%%KroBKe3en delta 1591711 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%0Djh2Jjx_5%pNUHDkxV}*|u zK34cx;bVo56+Tw@Sm9%Zj}`t6;orz(5M!9eLRM14PGo;W_BUjYlRZxMIN9UU!p8|8 zCw!dnal*$5A18dA@H>Rxq4^z}-=X;(!tW4%M+F<%MJw$g8lTV$e?9(Gi0)h+q6sru zjOQnKeuC#G`11+={4UR@Ckp;g>-bOW_|Iijv5h8<(ix&j8c*tr#*-ADr0^t#C#_{G zjU1sPMBmILk3rbRHx>S-!r!!wZ`#H;ZR4A^@lD(K<^}g(EWB8FvG8Ky#lnl1BD`35 z@qUhRK1Bc3l|Bq*JTqBL1sf6iUl&3&S>|M!lVwhpIa%gpg(p{H!;@`zvJFr6&;N?P zWyRmJ;&0i+w?;9Uc`T!fZ8TvU-?EJEg#Rbr|VQ5kLiFu!c<$TQ`)%v}_F~rHxo^w+w&%X> zxoO$-L*6vcO_MjxHB8&cE)Ki@X&(HJ2fvfga3(UFB~*oI`Uoa52hFEzK3((a4w~+u z>56|>@$V}1U4_0olQKN_-Flii9ikbTA9Il@!a=4_kGWO-*exeN(tre z|NEQSg9pFg8KPNP^kx_nm_;dT*~)&7aVbPU=uQD6u%#c&VL8>*WBMPQ4$58}ZedhmyR5ctC>EMgVw@!Ssu{?K!Cx?t^dtbNWXCNqy^)UcBS z91qct0zK%@Xr{7&74H8>>%6FAKc@Q8`4G+RiV|~&qU~IFHFq(tYVJmMahUcH-JQ^j zA&kRS-L1skRk(t?n^5BJ3n7}9LtjQxjQ=6_Xx>sU*0PmGj)Z9bSf;axN;a_@Tbgfr zU&o4k9V_~Y>3tn5@^!4p*Ri6XIQSK;?wW2$>hb&sj;G1Wb$y2n)an5x87C5o3QUZQx3 z;w6fg*jR~;mF(sq9U;26D}5NscxJNL{olLBi!JQsFzq2)oY0FQjAI5Rl(T^bS~$t2 z5Z%|E0!ALsBA8niqQE4{)u)WfW?!R=l7fZ0dQtK*?kV9XFQOsPHvX-qha*XpKTGo|53}rksSxf~R*+na-LiAvu z2mKj|?DT`e9~Aze@CSuIDEz@AbcASmCYmqTe7W%D!j}tQE_}K0<-%7CU=)*?$1*(?ds&nGd5GS;w#y&R@JM5_{dF_6(rWdSQ# zM;-gIomIB;i>~xxC@N(hh!pnu1 z3ojR5F1%d$L&6^t{*ds86n{wZhZKKE@rQ&zr1(R!ACg@myFzw_>PxP1xA5-EnB_31aF(n>TqDqM>C90IDQld(UDkZ9vs8XUz zi7F+klz3c;$CY?oiN_Dq9-``mUJPV38`(uGr$Y2ZAl<`{{*0uU1*~8lb?oP4h}M~4 zoe6%GO@4@Kl&ev$M!8@2W-w!!&LUQ^p4}Yebcog~xn9ZjO0IXs>s@hr{X8#hV0{(a zXyPcHA$l?k;ZF*GQuvd?pA`P2@F#^oSx+-3xEP`h*1Mq}*1KUMvsuDwHnWFAoDI=a z5xw32Q^KDT{*>^igg+(xDdA5Ee@ggMns3y6qvjh2F@|X@WF#RAINKm)>kb1m}CwdgtF&k27{ z_;bRa6aJj==Y;#>TJ)Uo=Y&5ee4FrXGPcRsCS%*#5N(g>&0xkdokgs|_y3~pCfaVI z?WaRj+l8JCU=)*?$1!Uk9Kl2R{ipn(=naw$Z+ zx>LXiCNT#S?=tZ&6YnzdE)(x+57BQEdNGjE82Gma{_Se_|63FP*2KRx@o!DMJ0h1t ziZJnR6Yn(_#cG- zLHHkp|6x2cSxf~RQTz{z|3UF)#hVpxR=ip9X2qKoZ}$C=*b|)FpY(*q=sFzawp3{4MprvOb4O+wK_iXK{n`#a?R4jraFM1RgEA6xjdE&SOQ{%i|>Ud?9qaEQ(j z9nPXRgWdn(v0j+)un7;Z#Z?^Mj}nK^hv+RO-csVNB3#8=uHvobR8vngCqi_j3q2Xc z7;NImLRM14P7YwpM=pfu?H>5&zeaD5^rD!#EJca8m3X_6BXor5XeN0GKPvp_OcqnY zMt0H4sSv#r5dKboWWOW(9og^5en<8@vfq*Yj_h|n{eS=4gtrNA6W%7gP4hO*+ca;} zyiM~q&D%6@)BOK(=*uuBFpE+u*~D%RaymrEy3msWjAAnLSVk4;ZGJRyl+F;nn?-L1 zGnVNrVioJDrS){*3Zxls}{V8RgIT zUt)^RXn#igv$+&fgw>o~L?v5kC zwJX&=7elrivOR6ec2l;SvR#Syt?+#-eBTP+H{}O;A^Ok(A3ESe2Yl#&4;}EK13q-X zIR~6`z&Qt;bHF(VoO8grrL0BybIPAn{+#mv$PCf>;Y?(<`#-vX2v74AF&d^h5Xs;TMEokbOb+1=$y5Uyyx4_Q$e6mi@86|3CUz z_{YLOUc@TaQ%^I(KNfyb_{9N?VlwksMio1;jf=MNiEVsh8=u(5C!?9l0?OGy11+57 zQiv}3A7YO#6)=KH%%O}mY+)~lX%Epq6M8W)#GxyF7#iY#nH}OTE2#-_s(}_xaw)`7 zcM2H6B<4^?HMOMo`O(I?5Xaf%GlX%>poDTZAUqZx3y*~-!V}@XMiu)ORh$U-EvneJ zsN!S`dpS&dh%*v;p?t<@rm`T!>5LU#tfP+o9OHb5GfkLj!b}rpjz@T=@J!*E!ZU?u z3eOaNnefYmUncxA;g<=&O!#H8FOz+l?8{_dCi^nkS+cWaXAKSMI7@ieOcqnYMt0H4 zsSsZt=s|x*Qp{YIvX-qha)gc$XJ?YfAjU9_g{-88ogCme7t$gAOb&e+#)J@eokb~? zY+^SDIUVB9cA+N&LY!j*IX2KOn|y{ajv16t&ITH2;Ut$r{P|o8DZ+pLi$6b~$<|auCT5vE`+$JZS@r1Q+Q9|J%#ra-cxu_;XNBULPv|U~a$?hflN~^!p>aQ$f3iI9nmBOzSex>j$g}JWqI@@I2vp!t;dZ3C|OLmEu<^ewE@^DSnmWSINFg_Eoa4l6{rzt7P|< z-CK6=^l(2WGLL0cv5h8<(i!5bv*^uW#xk8ntYSU&G;@NBA!MlbAypYutaoE$pL>b0NMyn|=&uBC}b-YBpni*B_-b#06O)zTs278O&Iw z<9cpbh3mP&_1xfkZg4#}TnuslZpiO1zkk{RH=YmifUa2W0IMBfwF68)!1M!5Kfv?@ zcG1eI@ag|zaC}n_`eXW=On;N2HcA@M-*@dzTWf#gWl$|aVUMRd! zcwu{pZ%*jN5XLcs5=?)y>2Egu&4)M};(-yl6jH<#=Chn?>S^Xgh;QjaPX;iCY54wU ze9KBNl(TSP84bej#`W<$5x&~3`yR!zFrkA1XpF2uvK$!7@Tn1S$N z!iNbTCVZIiVJEp1;;(e40F!^kL|-xaR}3_~hMgSXI2S_v)g1aVj0wzg|6eWjqLNMQ z#;U$*RU^94lL3qh@xQHNJ@qtmf{P&@*^Pd9X5?h%v5YFVp}8ku4P2!qe0w_aqZb1i%~Te!f_2nkE4Lrxe2B+(r4K_H&rB9m z!A5q`%Bc{4BhZ8XjKs>v`SWr9e4IbO!=I<`@W34&xWfZ?c;Jq?EM+ZQY2-+V@ATZA zp1ad?cY5wl&)w;{J3V)&=kD~}ou59}5#kA%u_eNgNF`VxU#l9~T`@T>-#c)#$H^p#M(wa}v{QoVZifuH7 z_}ktvtrXLJ$Dz|5I$go(%1!s&^s^!UuHX6EP5j*{YzgrUe?4OuGw{9{%_06?PyF@w z8bUlX3x#Ly3-R{{W61RP9X_i&v(f$sxp@79))3Ej*bgI2_QQ)Io--Gl_|XW~hj^}k zox3T-clTpAheA9rp$O&Y)pIGtKUVO^{%n4496JAKh<~z-D)+x2Kg0{HW`X-z;NkzC z!>JH297s)we_Dt`e`*Vhdg0JTR=21<#P^KG>wCN|nT+k-n?o@>L%g^z^Ee&i`=)U$ z#HF6|J%hN^S?R^@e~Af~tPAn|g6}tF*;rOl8{(h!VI>YsRr-H(DP$V!xDeu%GFBSm=Nm%2Y8<9tRg2&KVl-8>hq!z&21=LLhxnl$ zc=#ccK6D_&6+@_CV~8Kt@?nQOTo&TW{!C;!)f^A;FXv;L)qb~Hu}7w|AjE63$w$_j zZ8Tw!HJu@TGzaHC+U))xJrQD`;l@70jUO}NW4+PtF`IbI79QJ7M~JKZu4*jP(Y$IE z>k(AdjGQV{KHiOftfVc()x%M`T3+=MWLC?p-oqjHU#;~M*7-y(CViraDVXdDQ$0~l zE&CAo#JLc!%f@u;Ot)@Rh=0|C{y5-Q4)~P=e&v8)9YTJM{2KW+@@vLoh#EuGq)ky{ ziW*bYn4-oMHKzErDSmB=Uu*ws?SHNPuZ90w_^*Yp??O)oFp9~{V;Sz)N4c?&a$_Im z#y-l8pUk2+gHgaoxv`IOZsU!|xe(&t01- z?6ce0XSeZhgl`hQN%$t=n}lx?zDf8d;hSoZy-D^a*_&iPE&FNNPs@H<_S3W6|I@;s zu4EItIT+&2GdLgOXS&jdp^RrHi>Y7>dpS&dh_@_Y1?#9|KgW>stej`%JS*o}InT=R z5pDczdW|1~d`27lj5hWeZM-$17Xum1RD}D8Hue#1>?7KE>mkmD__>H&3Ms;Y&pB|L z1GhPFn*+B^;!=pWcc*|6_}%t7?ti-$+m-MS3W>KXQ9FhCET@`U_R+?<5bww)pCOE6 z1|=xBL%AKw?NDxqa&^kpDOWd`u}o(Xt5}ckf5&yrUYrQ=^Q+m+9t1ouV5fkc0(P2i zr|EVow^O;D%I#Ecr*b=$+o{}6b0xa&PS_peLc$6D_4Ip#4mQEAAa|u-@WK}4Sv_)cMT3~aA1Q28ywi+zy=34 zIIzKi4Gw(CfiLyOc`rHdrCFp){aDLZ8aYBoh<9a@#~{WqjfGUOkzKUX9^&67^kN{R znaToIgm|}Y>>k26W>7*o%I#Jzy<5B8ogsc%yO*_lS-Y3Dd)YQ#wvCr<<7L};**0D_ z`JPG4p^P zYHHbs@V&x+C;WH9e>a?o%w`Fz*~}gc^gH2=5waU)H_C35-6*?JcBAY@*^U19U*bmL zjly3Q{;Kd-HGfs}S2cfC^H(*0RrssIUlsnU@Fw9+!kdIQ32zeKB)my@lkg_tO^P?k zZj$|)Y@ZRwuPOeT;;$+Gn&PjeHGfU`Yr{#s{<_hr$W!Hi`(rBt$s-5lg}h=1RO zo(y0VlbOdds@O&oN0Gf>_I}y>W$%}r-Y3}yK z@SX$SbHIBJc+Vdeu!42ev7cj{4{>vMY^m9nnr*4smiz-t;$~ZF_P_rdH}CVJ4Trta z4QqPCn%d#@oZZOZ6W@@B0T$l?IAuU_uVW${nvFNK5hfYH;4Ey zWAM*^k55D_LBL-Zhxp|95dSS<5tVET@hM@a9Q>ZA-;&Y85{>Q2ipC7`x5Pzif1&8_MEWWr9>%O=v#Gfet$y{1Oe5n^@tO@ZybMX3~ zYeEtRQ5%x*}}7he@6Ic zaw$akXJ%o7&zRsdTWRD7=R?x9D}7kV!H|5mKP8mo;2a0%I5@|_IS$ToaE_sJoST#0 z=0_99xe$`i^*}(kA$YjkVvdI7^A7m@RMyZIlJ1`AK9XYQqFndZko;>Nlc@_yk3nn< z$rU{rz$pCL74uj|mHWS-}iPD)X)K8mqjPdkE3bZdc!Nri=&<*Wx(EbMPZ_xe*jtB!j$X&=CytrIpmM)BS(xfEUNP5R$<;^u_N6``uu_`?7*V6dW=QD;{!!iy^tS z8xFhGVYfQ$R)^i{uv;B=YaRPJ#`%y8?MfepGM6$SgRZBjo(>&WOhi3{jS&)#Y+FzaQyzi{Qkd8IN5f-l}%+xretEm z|2KrrkbGOwY2N#tQ6ZT=i&C8R-NEkvyX!+T!xS@2F++(NCj8!54uxdqf{=WF0^am} zZ<$~%DFL!Hww|kivn8rd*gyhGz@MDwzcn+TXv7q?_7{z4fv5YFV(Zo?YL-La>dNY_( zD%lj01&XB?2wmX71seZ%F3V{P$-?O@qLou2`DvgB6S4B2E@3sM@OL~Wi$-JmMGIKL zI_lUTl6!3H9^1Odw(i+L11+3%|My%9Nr{6?3Mpa=^HHRvnp&Dea_gqH~~lUXLSOlFzPGMQyE%Vd_F+IA@Siv;=_r= zhZBhpCz54N9HrBM4`zkr!QKpJEbje5_x|82touP>4<6)nNS1e@C;pAMd<@fA$VzJ1 z$pMaYAte8k!xC19WQEr&`eI`%CNK-%|4vq{^P-OZ9OHaQ{`99Ch7ikU2?A|wxc-^0C05B9^_AGY>~ zz42jhd|1nez42jhe7GYdm6_x*h%rnvNoX?0~yT>+~?YIHqbx| zC%F`o$8ss8h$+lx73pjv@y1)eCw`=0Q=C%o?o#h*}oox|5TeBE*!zOJ5TPH-_Kzv{*SMlqT6LO)hg zgH``Z(60p5Bq&g$K#c-53e=Ran$7ItD4iksbr!uD%vh$gh*fOD27Y~z(;->kg`W7` zdcR9QDfr1H2!3)KO$dHc@P-`vGK^y8vXr%KrI90agybo!eadQ|vf8Jt_9@{{Ind`n ziO+u$pZ_Eq{odz4$;O4O#OFWBZyfR)hy2DNznOqHZPIenE?PtKv^70Fk7ZP`lLH** zLP$1y|7P#sd@&@?Sno3f7=`ye<9*L~-!tC#jNzU!+%txI#&BC~ZHxc@KiM*v69{-# zz_a}r&O~NoJI~tAvzxK@XAf~UBwHhLDWr%g%x5{()UuB@&V}T;Z1NexIA-wa|NZ}5 zIU5lEobcy_Zxg;v_%?6e=FQu^dAm1n_vY>1yxp6(d+&C|wkx*XWZO-){X$4;ZKSp@ z!zjjcwa%z@Mr{MmsCC8;XQX#HWQPfMm|%wqc9>v?33ixZhY5C=V22Vrl&DjpPKi1t z>XfKcqE2|7@H!>x3{WS$PWbb}pO^joU?wn^rL1KujT~|R&z}#;&Ql?IA<%>VjHDQE zdSM0YsAE6JxD=B5?i4Tr#p@NXSG->Fdd2G%e{m`ckny677iGLC<3$-SI=8|9{$JA2 zhoN+Y%Z+OKUUKz+}W>7*o8)%?~li0*wo7k(g zPf(J*(^*6%o7f$a->qaLyJ+Q9NE(%CbVj<-A&m}cw5mpjG&-cwA&m}cbV#E^8XfYg zLtb^rt0OS+t0sQc#IKt8RTIBz;#W=ls)?Hn(3H;*#$kZhO#PavUvtiD3qrE5iknkoX`WX*nN~)?O&kYGbV?X*Egf z3f56aTS$CHki045O&LBTNPI?+ytz9h2Lp>(#d_*VH~Vpdiy`?_H~KN0iL{60P=Z4b zIrNZ24>|OZL;svf9)mFLpQqv9JAYnD4Lh;fKilk|FNEZS>BJ^#cZwq}}=G!vgwz;>D&=HcO z3LjPY=s0FjLb>}tx*;U*2z$pb-|@?L%0trDg`Nyx6q9jpZF@Mx*^vBSL@tFCF@^ao zrpad&FYvpIH{A>#+xfGJWcSrc&h5udn--Z8uIn~s% zk2cPQq`e#c7|ulIv5cyayl=bj&vgIqTkrc7Y(%s7HG983Bps&iFm;Dw9g1}*)}dI3 zVjYTgDAu7^hhjdqNj^~Q1I0d2>;uIHcO~t8%-QViHk~HRN~?=CNL``pA29W zlbOddy!R9D{lt4e@!n55LvksL-gxgN@4aMzO9r@PfJ~51Y7r3^sAO5|=A+xlLTYlLH**LdeL@L3s8sWM|9H zmYpp-dn>ZDk8wU^d?wx1k3J!z>qKU=j4HO##8HHPR_JGies&UT=?ob;rpU3%9IMP3 zi%sSz@j1_YZa)s`wwN^`DE)cUb}wf`$oSW8XxL*of_zAr z;X}fVE0noHnVzFV#upU+!mf~!JCy|??3XUxoznK3hSUavXloH=vm z$h^+`d-cbQ9r1kM&-?Rvw%xmNbg}Ci*u*Z5gxE+2jdajR2aR;l$lW3Kt?eA(bcl^| zb)#I}DF6LW?D`ahWG$f?L0N+Qs3n$lF2uf_%uvQ+@^4%5x2hz@I-|*_lq#08hRy8eScrW$kU|CrI2~f+{r$hO@q-ve9>r9$gl0CeizA#1vF{}_ zl(7_0#(b93N+)}<+U(w>F`OKxB0F1lw(M-#*)_tmg=Y)DQTUC*Zxnu`@Ee8SDEvm@ zHwwQ|_>IDE6rLkIM|h6#9N{^_b1JB#fp%o)$j*^HLG}dM6J$@2Jwf&a*)X1fHwnK<_)WrZ5}qeKPk5g2JmGo5^MvOK&l8>} zJWqI@@E<7tgMo}>BGZ}6Qr57U-R}Pf$3kpUAcYYjc5`3S8BIQ=EMNs|*~&gnhFE?d z2H?5;T&6LHMKrR39US6Jh}|Ol7TLGRzD4#eHNtPHLaSS}x<#v7cB9l}r6wyiIfL;O zF^gJ+PZsXSak0t5Ckww-_^rbII4{i*g%Dz?ht+H>GU7&b@;xz@D z7ieCfd4c8yn)`8F%#Y(@1;Pu27YM&i_-(>(6MmcU+l1dH{5IjY3BOJFZNhJ}l|tc# z!V6^=$}W^$D7#R0q3lB0g|Z6|x&K1pw+p{r`0c`P7k<0&+lAjQ{C45D3%_0X?V1}jkL-rlA?~r}R3jF84*d1LQq$k9t zBr%vQCQ-sX>RC+(dpQweKkQ8!!^vSP6)Xv{sk5kMCF|J6eolqhk0J&#k{XSEq|uKw z`jKgWWZEBXWgjO)Y+4@%VA^S>oo3o;rkz&LYC7oVDCa}$&i)Kz9EFs#0Mp*N7Q^0Y zSbsloYMj&Lr-N|N1w$xtuGQa~B=SxzgRSa->Z5WA;0X$&WasR+NPjt1H> z-8}~}U8$=qm0c>kRCcNCQrV@lOJ$eJF7@X>Vx_|GO)!WtOlAhvETe@j?BRHb&FDoc znISgQ24vGnyzdVzF*cIVtyVRyDyjhoC>k>h!w1*W~(pzI2mI1FQ$o& zAvVhcv!*eJMKrR39UKa=2fTj3>j%DkeI~?a_h&P^L+n9i9#rN*Wgb-KL94D%{2|33 z8puc{GTr?@G}j9UKjh$t1Uw|*p<^L7$ExR8^_-QgV_S$loJTRHe%N#mJLq8tJ?x;l zwl>$+=2o)|TbpZZbNArzM;!i0Dw$+6h1u@^5x-gKH!J;SrQfXdo0WdEayAQDMH|~W z!08ZsG{GQ7q12;FJ*w2BNEa+gA@+C@gUMnN zCDhFGWjU>MqQv7$R4Y+!!fF#%o3Pr1)h4VqVYLaXO;~NhY7S4Y67e)q1FQI5|uW zu_rUhW(rF9L37Lxnqz*@99w8x3vKHsffSa6Se<{Y%V0c3`18N9y5k}C(-~B=GQ|9a zvaz4_XBgusq$9)@`~KoJY-V?e{oH$(_`M~kL+lr}^^1k93bFbuCQ-sX>RFA9`fiSf z*i(CO?$UJkzqFQ}9Oi6@J*|u%#m1gC!P6#qdJ7&{Hj(MfWid@`#IwshyUeq{?8gws zFqv7%_oLU?FW0dx#Fkf3M*}tOzI1Vro)CK`iNToS8B;t{f+?Oc#WStA$7c@UuoWqc z!24Es-wN+r;e9Ji>&LFKUn%jcW_E?x%6=%e()U&xZl&Q?8m?xg=~jC4O6&M_FH*@Q zn<+H1fgK#;Oo*-OOFE;;r<5v|;)+&nW;e$|?Ad^#&t@>5B4$y`O85WlIxno{+5I8b zFq?&}qK)mCw&8S$J(pk*CU|Z#uIV|GJZF;UO!AyTo-@dE25B@%qd^)C(l`+X8x?F+ zuu;KA1sfG?Jm&tN*YJ4_pV#nt4WHNWc@3Y}@cES}_`HJ8E7(*(8S`0AE1m4+M2Nl6 zn>2=#!&EA$qk(q1I7m;3HPMUY7iGUF`$gF=%6?IHOCR^&BD_U-i|`iVEy7!bw+L?$ z-Xgq3c*~g(d#Nw!j3%E_s#wYzHnW>!A=Vm5A%pQ0F^gJOvW{);zjZ&SLhR*;fe3$D z_{*BVtoh5DzpVMon!l|1%bKsXm9>hmRebFvN|;AItLbDfCqnGMdXvU*a+pd5b<{NY z(oPpgI2U5;k{QZarZAg@tfGzWA@+*rUh&*3o_pmWJt5YX#9*>es!gdjrP`EgQ>v|l zZjN$3#MbwB|Lcc&;jY$ewO*_BTCEqpUif<9>xHjZs$HperP`HhFQAP1ET)6I2&ba>#eM4+RI-|*_lq#%!!&;Qspu`3f{?>%QHQ{ef_*)Zh zRAi$f8x`58$VNprDzee)H=YQw*R1}vG=`IdAzw4(YlhskfEDh4lS4LbWgjO)>~)8{ z?vU3V^14G_FJT__tfqr*j)qu=13Dbg;eZYYbU2{H0UZwLu*D8r>@Yxw0Xoiw*c-_V zC7bEg%=Kk4O>Aa2N^BOic?}-?ohg53%HIjtB4CSvEhg+7gL0jl*cD>G??oz8kh67R zh`p7EYkBK*h;2(CcUxPCy&dqn=50aS=Y-fF(rIKvh;_}z!|x1Z9ACcPA7VR(FrPyq z_O4RzD)p{kziah7Q^;UEMa-g>m8@eMGIpK{vG*bdGSdCOH_?mf*u;A_@t#fWGUYB) z?i!29b{*kdi23tjvG;xdecyjyf%g@7UxDuaDBC@bLdsbXVt@4BKW=4Ti0wAiZiDPz zg$?Z9j{p7d*zVII_CbO{Y+wf_{NPN8?dgln>=}*VJ%aZL-qS!kT^yt*#6C=7Fj@HL z&4fsYmVSb>if_*j9D75G?zLkb*H;E)1` z6gZ^7Aq5U8aLAB{wz&U8d%QRvVt?*MDw$+6h1s~XKd+*V?Hu5Ah#gKah*9KGOeITb zW)n&uKEk;W`%5xI8A|~M{EN?j#{Oc$zqHcH-Vi&|P8SF139-K>F_aWFM7%^dPeT_T~Tm|C{i?3ICh$zX|`F@V^QFoAAF0|C>^O zI}u{XdXvU*a+pd5bqGHu{Fv}#!jB36MD{1LKau^3>`!EWBKs5BpUD11cFiZ6A0Np? zrZbntG_jGL9Oi6@{e2A8c;@e>{kv&TnD&HePnh<^p%DAj0iQbHQwMzNfRhTJRQROA zC%1>#KPKb(e|Y{M=iUD)51wjdLx_FW2M>Pc!Oy(ubP=iJzrT(BoC>j?h=CZa$6!4M>oHi5!FmkVV=zDXjD5b${eRxV7WQyF#Lo00l}xhn z@R=%>vWCs<=2(b*;m|J}`h`Q!I`phV&pPz1L(h8dtV7Q_^sGbAI`ph{XCvx!|C;arF=NMgT+E&gWfT$k?@OzUnKk@;TH+NNccs2eyQ+F zgRd}lKRN<+Lrz)PRc&g&5il-``s(7m6mnnXk;+H9YS&inGX?|G+bu`e9tz2d+ zm-U1wEs4QoF$v*m!qbGO2~QKACOl2?%N4&|@yivzT=B~lzg+Rl6~A2Z%M~9`#ZuO| z{{fr5*v+vJeKn9m2IDDW4vT1{gKmy;K12iiGmLQ*QqBTau$Hat<79}g=)(X;kjpgu z`JdPtGK$)^;}2WdV?^FfLaQhbo`D}`Sv{7T_h3cphLm5N_k z&uSDO?76|78|=Bko*Qi7tNOVAt2}s>2e0zrRUW*`gI6hal~PwJb(Kx$Gn?4Okq`~-MJkzOGlkhKriqR0BNBFFknV+(io0E z{~KkPFvElyCd?3?Av{BPhVTsG*Ca8REG8lR8sXOnzee~q!mkm2jqpt2nS&Tb9>r9$ zgl0Cei{l}>wil`H|JqElLp0nW!yV$!Y(~RNm`6SCYIq0T9OZn7zS*B)jH8fp4Dd|@ z_#>OqHx2Mj1ANl}Ba)CkLiPyRBV><|Jwo<~8sQ^^j}SgW_z2XetJug+4s$j{-|EK@#xR)~?*CiWUM!=9 zE$qR1Mp@6O)DT@S^m?J!3%y?G^=)kD0H;Hgm0%E~$P3Z8r!j{`G_rwi3^%$r-aFcR zM=LOThWj6_zzxlq>;{M5;P5dHA2X1VObpR~tYaJdITfO@5$TMkka8BVg0*aAA16cf zojwd;1lIqZG7R$_!+fXB{eQ;=4+Gx1C)a;arHu+vNC6vYCR7j#pxQjTYnEP-6T6&W7lFDP%C7 zB9!=^65m@*2i+Xye2B99GmLR4k*!3w64?zXk=?~XdO~z#5`)RYCUWMx|D5Gsw9<(J zIj2K3A;BOGtq!<#4V#haC*Dy(AcYLZW3vUb+Aus~qJsSw>3F_4j%{k!+Y-V;2j>k!-IDm!5XI|GnBCuP{w?g(@H0MIT0d1td9J!I{M*o za+pem`~P8`7Y(%2#X)*PG&PCAWHE^n=26dTI_Tyo=R@?P{_Ny1XG1itA43?!WM)v! zGFsTe9``@(c!=)oMJkzOGo86CriqORzf<^~!lw(LE`0iUikL+$D_O@j_H!ykcSQ_j zBomQ+m+ZS_-__*)?-G8O@VkWHCA?U8vG8Kei#0FSyjb&M&5JcJ*1UKRir=mH-HP9> z_}z-%J%!l_zgzLUW#29PZrOLsE|Fa#yF_-0?2;PIOEfRhyhQU7%}X>d(fl6G@6r4o z&F|6t9?kEmVkv9b%x;c_s5Fp52DVaaE2XkaWtYk>m0c>kRCcNCdu87%yXIcu_X@vP z_`TM0ujcm(zgPIZ!tWJ6L--8gGlb6&K128n;WLEK5I#fr4B<0`&k#OS_)Ot5WzUp7 zQ}#^RGiA@Tm6@_<%AV=(e~rq7mkBQuUM9Ruc$x4r;bo0%UEmphoxu!XId-3*iq4pDld0@Y%v= z7f{B0meWcndpQxJ2YZvoaB`SR1$8ta`$5?c%6?FGh3pF16|!q8G_TOSLh}mED>SbV zULm|f_(Q@U68@0zhlD>Q{2}2F34ciVL&6^t{*ds8gwGK^NBEqvC_YE=If~Cwe2(IC zWY3X3NA|=1{{P|Y%0*uox;hv>0hq$2yV8sU!#e@yse!XFdM;?B>gV{r^~t zA1m?W48~K$ENWTFI<~Q&Qz2RqF_4i6Um$#e@CA!$Vk0{-;S;i-7=QtvFyIpgd}1E; z81RWN|MmYwH%B=iqS`@>!i2RZtTkb+32RMQYrG-PXBgusq?`q;U@cqO$H@@Y^}&R7Cag1I zoeAsau!u%BAiM4mXF~MTzN91jr?P8)D*UIye=7W^?R0Sv6D~60A`>n$;i4&+aFGcY znQ)N_7j5SNr$h9!1cR`>pXFhDKdWR3&1_;1$3wKZ7peI9PqcWV7t^UiiNz*dY{JDR zTx`OhoABo*{CO6WC}BQI{9K8jEAewBey+q4C6*|$M2RIzEKy=fHOpvW3;Q`0qF+P| zbpO8?i4wn1;umvSOcNW~$>9*y2T~Y;67@>d&to~Qbg~yEo>Jl|cm32DCNl#io>Jl| zC7x2k2T&p(K#7*tSjEy*GEriw5=)g>s>D(ymTqP@$3pb95>G4fbOz%oVivWm#6ORo z-o_!$glJiRhB1yp%2~h)*0R<8FSF)lJt6vKQizs&^Kx%qK9vgUXrP^49N}Duo=IjX zW0`_~UOc1lGYUVmk)7DuGiO8OLn_g)Wc*6Tuevyh^H$1Ond$ylW_uxIrI3{iSw$P$ zIl$=<{W`%QMv+G`l`Nr|O(^(l1%IvJD&eaXT&3Wu0?LrRO7<$*s}x+N;3|LqGxF(_ z=-CX$Q^YK4S;@K(HKftWUQUGQx!#o0%~8&WsIfn`*SLi}91qd+y+|dKYz+ClA)i;| zc}1RAQ2-K=!8L7In?!&EBVe{-D|4Ybq6L3%>;pGgcRi%FC)k9tQ-!TfG0J1+2gzFB#+|gS=!*t+v!^ORculT7c(TJ=fY9qL+ub|Ch%w znHeEk+n01klTRsCEM*Ow+0C&K{nwcgt>YEXyyBTxJoCyb+A!rS-qRLHVFbCH4$=C4 z7<#>5udkWr%N**l)^_V^cUZf_+D+eXigr`Hn#xEfqSULV-C)|^I`Fp}QR+2^z2>mj z9QK+Gy;g!rU$ddtT0^wS?`>K{BO5~WdY$`!y~7KwIudwuheJE8yu-@hu<|zsAn*-s z-%$1qW#16;hLAUeys?8r*u-X=*gT6`R1o=V6O)+G1xO#Z}M^|64Pe$aLnim?k!|lfxl;OPRNXy(R1|VQ+0>7e_c3qHW1kV5Qp{Xvgnw^ZVQU z{@Z^4?V*gNfHLOe^O@1xt#q=N6Cv8(n>2=#!&HQC7rtHicH!H*I7m;3{*Z)i{2_}; zlwccwu#G?1Mwdgo9NOj3E{AqGv&)%X+ueWH0WVI6=$!5RFrQcQhU8Ubu`dy{pRr+0}-}U^vo7lzi5bf+mDqsHl|ITbLrc=dI z*07n~91GEVffO{P|Y%0*uowh_`U<*ci{V(IPd)_%;w9V|GdA7 zHnw9M@7qSVZFJj4w{3LWMz?Kr+eWu-bepEzG~K4@J`$oo`o}+h`H$y9v^$xhjHQ4w z=Cd4U_|QqT+ZjG{5`Ex|4{99pfkQrU$OjJjz#$(vf$ z(SC>RAI3NeanOG6`ES4eZ-*Qx57GY&WF!+g!08YjG~vNPjA1e}sAd^0`20uo@w^ay zT+eDcLUibOi2j^QCwoJ5Sh>SXS;J;_b1X!E38avL5`Qu25tAM<=@FA2nZ(Hu{k0DR z7=cnpl{%`_(NRox|3|g@+bY`Fj`qi_=-46zf8vdw*v==mb6n8zxh%$ke|O;D9r$+# z{(T+W*w3jDoroC7NG38J&z?~J(?AL}+J8EpB4$y`N`!qX>{DT%3Ogz6q_C61P6|6I z?4+=hikxg>BRe_F*%1AsA43?!WDNO_YL;<6M5p>Q%>ADl=S3mqX!e`vi4gVl zCXM0bAiPI-&k~y1#4e6-E<~RT|6KUz!ao=Ox$w`0e=huU;h%RR`*Yc6WS^;GDQnow zZhZbX`ohFtnD`45e_`S;O#Fq3zcBF^CjP?2Uzqrd{hSJs&v`^X=MnjwN91!Jk5L|yQmRXh#uL%2!u&)UF$~5M%h(?GMqvXf-jBneLveu3}{gkK>10^t{EdVwumU<()6!UeW)f$$52Um(1%@V>(P3hyht zukgOY`wH(Xysz-Svir)uQ1*qgFO+?u> zM65CQU$^fXfA3F5q$jmkYRDz~uriUy5lj-^xBthWLO!3?Pe1lrWEaR?|T@ zM>!wjU+s_U{HjfUbt;uC3Gso&?th?P4E*vJ&1_;9M>rSaS0po(vDnHLwsM87TwyC$ z*vb{Qa>ZUwg!rJ|q%oWvijh4?w$FpaeI6u!rRlFU{gtM_()3pza{pJJ3Gu;w(QvSa zg9|CAjt1K4;vhXCepM2KQR*tCu2Sl%g{-2D?HuN8h^O~s2;(VY7PTmmu0(o`7U_pj z;%iELEuGQiQ%V)C;A?Bx%x+GG_>exVrUR=Ua+LES{`LMCCU}^kO2@nag6D*vL*yGSnnKJQDZek+=_!#IH8V)g~F1Nj6iM%|dKp zm@N#mg<%Ic9pc|eFoZEoW(L(PbN}CH@nQ>_eM7U1UZf&CLwJVp4B;7yWhj=h8C%FW z7UI_gQc&y~#ja888pW=uWhLv_#(qwPc&4c{O`U1zOhaduy8p~7ma+!nnZh%LUn~4t z;nxbkR`|8TuN8i+@N0!%EBx9W91Zc|y-8y@IZUO3IvQxFi-Yup_&1XnjKBXU{>@30 zFpqjx)5%^=g!l;IBZQ9-K0^42Vk%ieGn?4Okr2PmAlDh>I)hwikn0RG(jX%ZGSVO; z4KmUoBMmaL*8PuM>BTy>afmY^{;j^GGmb*aS%68tWs+~1_Ca@mB{LkiL*?cWt~~pnPr_> zN@OXKW#TLoXDRV*<-fh3Qz1TD`O(Uc9!);Vk5+#43f8ifeVh#O8+`sNe!~Dpkc(C~ z%t5Ielp52?UQUGgehDDo(#k|ngTg*_Y(@v*%~C6jDy<~wq~BlkP2>7bjMqrRLE z@o{OGcARO)O~th1OgpZbO(FhW6MwghgY<;>_#_517UAP(vydh>vXjFh{=Gm78H}fh zS=6%9{eN$r7uNSZ>-(PdWk(ETH2IWL#ZuO=ncW-<@f(HTDEvm@HwwQ|_>IDE6n>-d z8#l0nLl`orKf@SDA?5Br$CNpy%-O^)j&Lr-CnRHi6DBg9xh%$n6HGY4gcD3S;cSTK z_G1WRn9K~SQ6krXxk}_7MTze#@qHz}uf+GKy8rK2pv3nZu;%Y~agd%6pP0l@#xjN3 zEMyf*OjKf`5;p}>$iOCUvWc5);wGE8Nr{`3xM>^vIThl05d*0i>5B>TO!$L(R?|T@ z8vj7!Ng7Ymcv23mS>wwAAO zO}@tYxlCgoO5`h%uSEV{PK5X^y-8y@c_?v9HOo-q7L(m#vRh0xxi9IACZAHOSjrkU zvzucfers_5w`y^#61R@W+HReNwH0h-A16cnwmuAC1j^o~>}_*cL?av65#qN`rGh#d zXs3&V^n`d(5`)QN5+&}x$kauqo+5mT@F^wi z&V~4$$qZ#I1(Y$L<+RetUQUGg^xmW~oE&VcW_pDeby(MQ>zdxhL3%>`t|Ux-*Bne! zZ2IDpA%1rs=CYWk5HC@>MClTxOY%_qp8OCm_5D)cFZKOW-@iAB!C3#jlW2ASGolcm zVHGoMVunr3u!$KqG1J2{Jv`IHGd(<0=uDfKX%jPB*uox;hj>{pQpv=c%dEL@wM9vhS09pX~em{r~a%gx@FpKH>KXzt5WQv*!DDVa@lQ3-R(~hGNa-1qd$} zUM{>`c)9R$;pM{b7kpVf~cjA1f0GkmFL8K#5xhfR(hz?LzNz?G+m|XDos~ux<^g-s6!t;7UJ^)DP%C7 zB4$y`O4hNB{hSK%$07zYl8H=r|Bub}Vli4jrpRMEv98C?hImy!h9JC3c$M%f;Z?$` zgjWf#5?&?zap8{(e_Z(E!XMud;?-mDzH0BQ_P&}?xDp>;i2LwDyk?2}uW4ozyEwwR z5TBpSP{vX~8S`0AE1m4+M2P>mH)#wfhpALhM+5D2aWKReOh)-92C+KCYyJ0s@!CBh z{-nlFPG>H*@uY1$X&XL75P#A(7A{}~YuU;^PKNkT`Y?bjCQ-sX>T$qNI_Tyo=R>@% zKf@SDp<(OFy%1g}yiRzX@H*i?ok2CrXkiPA{`7c=FX}}qnPfAC*(_ugZEWWNr$hW_ z2~7O6QRGofB}?4@&zim1#4e6-F2olnGnBCuP{w?g(@H0MIT7MN?@bz>{kdnCcy@_r zmngPmI&)b}6C2sdVa|s5FMR$t{tHciq3JI)t=F_((|S$oHLcgQUeo#|C|a**y`uFF zt9RH_4tvUBPbvD8qE9LMl%h{5`jnziDf*P6Pbs=o(WQzm9Zn8Ysj2X#jt1K4;vhXC z{&W(9$zl>E%%h&wbRhg`;mb_8%!JD}vJ(?7GvP1$kwGrg@cb`5|4YyR((^UH?Dpa) z=R!;5N`(ZUw?a6H6U_9B%`vYEnc7P5*qwsU~fA?|Ym@m~)@_^&4;``5C6 zy^;;=;1Fj*d{tlk{U7mFqsga~DweW_&Ftn_h(8-h!L-ljG7Zx{YuaZU5&o?3XN5m2 zyg_(FIP07e zIT7OjnP3p3$fKA_me9;5c45N*G~w!0GRbBNCS2_bSG&U1u5h(0TzxFWe-kj^Z!#E9 z5nulIf4{Nj-Cj3n|M>!wjYx*;caZIIxIvQw4i8V_2phEn`fsCdQn|N^nD_Bbx z2k8m%mLvv~#UyI1qQxp&>RC+(O0+1^qQpx|yrjfSO1vceCE+g#e@XaD!e0{ZlMeBh zgtrQB72YbmRd}oHR@tqxTV=P(Zk63CyH)nfvTI%z{<83wg}*HPW#RtDi}=eXd|CL* zCVbhN*CsQRu@q3oLRQhnb`Eel#Q!V7AVx8n8C0{37Phd5Q|^CV6yoa!GLnf*XD*9r zVk0{_%-Ilsr5{5WgYZ{`zf#Le*0GKKoC@(a*=@4hWVgv~lien}O?I2?wvFz;Z8yh4 ze0?B=48~K$EQGHYzFzqH9US6Jh`0A;7~?3UoCVlk`&zbgke(2KHHpDwQ9v2C_iByx zy=r~0ZpZpID6v6_4I{{98gp1gBOB=EXo&x|H)#wfhpAMsgl0CeizA#1@r}u3lFbxm zyZ?<^Y*b?7Mr>lEO}wVWYf8Lk6R+9CYemeW7Mpm@CSKdde$IsWroN;zntV#BVg+m2 z%05np`0ITbfB|3kzyBJ4-Gr~3@O2ZuZo=11__{T}Zp|GI>~LU*13Mhp;lPelA^wJY zeZzroIPeVzzG3xm)X_jYT^yt*#5Y_0W~<*kmIBI{&vIJb|K?8iayrC+mtYX1$fKA_ zme9;5c5yt!xAbBlBbkV)x0rg%Qr57U-5d+?Hv^`A)6{R~GL1RZEb^t14!SwY`4I2y z&oIVOhzUCv&_Fv%bROYci2pvBp^T+~GUj8#-?y@p!y&#^iLEJQFrFg!zjc-u*1Xl4 zw_5Yo9US6Jh`-erYktd`-?HYnO!$@(Z>?Z0TiHiXh;K_miEY_TVJyt1waUX?=`zTEOZEfFfV>^dA8{*shF@!NpW(L(PqlGQ(;dqGup%uQ&-3-Q~#JftoTTd&U z?Bzs=@9a$)!^vSP6)d5dP3+kYo zL+&2Ad*trf?*8{24)G6d;X`ly&>KJW#t*&m!}%arBt!h{qJAvMHdI@3Gx3+>arQL5ah&sHKq( zx>5Eo2?n9;U-J0!zyJA*wtrd4I<|3$Ga-JYFX@aXpHiw=%38WO$hi>zYcfL_O95pl z`&VWE+J>@!J;3P@_X()DPe8?wDqC}OvKLAmRpMA-h<~E(CszB3w#U8kxT*eLM+2Ke z{L{fWX1(z@~J~UbuFKE;@M9fa?(cr(Tr#Qu?Oov<*ZYRpW4bk{QV#C&*p~s zXV(9j^`F-0v^Skr=(Iwom(ju&gq#-g&t9aGNj6iM%|cdT+JBn%p9fH+#}GZH?HNTL z#ZJf&y=%(6`0EBhT>m1@T>#R zI`Dr3DP%AnzyH6ps15OR!x%?Rp)Z2Y2|6d}oS<{obIy9soec4R^C9y@O>AT*hdCSK|L5@kbNK(|GL1PbqLB^k2#K07FC;=etLdPdqapEc+e4z)8aA_= zVde`+WJj1V#HO+DFkoiuO^okD{^Oq%oX4im7A?&1_;9M>rP}Q8GgrOF>OY#K$n1 z8C0{37PhdTQz4Ov7>G#{CP|niQN>c$usI~YVydsKWh?tQ84^hybaRyRA#p)}&V)qY zz9Dg;fiJ8X>dROPC}Td$X{D3BoCt}2rtD|RexopDKU4NI5`c8S9-ao8me zOX)=_4oh)Zio;SImSRIGwvu8iDYlYgE0^|R03+Q0rMX^A!(o>?>{5qaYHgQV+ojfa z>G_aI?awgAF_jAHXb6eR+StwkPKQKVfDJb76lSxKRkX2_!<-F?n{4N%404&q92U{Y z26k|WGa-@JmvlyxPbpO_rDlyUn?vFUBbmr_=CYV3HsatPIQRz+p5)+3Lm0zkW>CvY z*0GKKoC=AXBL*V-X4yUukhod)&9ZNneY5Nu9|%bJKtSSV;rYVzh35;;7oIOXznWzT z&o@=Rsq#&Ai>Yog)h(vFB_C7WVyatAb&H{HG1M))ITjL=11V%Mo+4)9^Ph>ynokx! zS@X%lCkww-_^rdpVJa2W(Lg(09Hb{C3X&L17LzDpKC5VBI|n!&61N%fHUr*fz}pOX z+YI-An+b0-;cYE!VGqYcqOcdKWRlGkX0wncHnJ1jE401ah2JjxcHy@Rzuoq3uf>43 z8}RmR?B`TS6h#cAW~46@nU1?DT8!`_;YGrWgck|F!}{*9zB|TK#4Lp0A^eUF?BEb* zLSjl^(iu%Yr7Xbqrr6#T+nZv0Q+nM050gUThl9yt5+%%|o>n@sz8_lO)C7YVMII)c zYI{>{Z>sH0wY{l(I35x|>P0G~#M>I_$;SkeFtD(=r%O5hk2g%SxEXr}f=weRta4ohH1~gm*4r1#8*LK2C~=L9q#nXS0x1w6PuADYl)vZRc*=xqB3O6jR9( zn%TrI9DcXMOOpBW|Nko)O95rfXF08OvX>JfagXqOgx@3l9^v;0zeo5z!tW7&kMMhh z-y{4U;ibY$WtYk>m0c>kRLOhg+$-l^IrmO>|M$-DqMBv2u!TLG3W*sJ0~yIgrZbnt zG_jGL9Oi6D%%AP5ErtF!rePkn1){9j4Unaavc$x4r;blu%!)A7K zEF|s=q>#aQikL+$D_O@j_M>>Y;^m5$%PyB)F1uWIx#H!Dmn&Ybc)8F2C(1RyU-2=#!&EA$qk(q1I7m-OJdng-vY12(vLC2tH8mZ+baRyR zAu+o@!x%>)LvvU}V@S+d${IW~$1`(G`><&rHtoZv zeORG~6?#~qhnLezCwn;&5_5Z#=Kkjn_acX>RIr3*HnEE%oC}FZk{QZa3MgYf!XFX- zi10^*KO($Rcx4)+$fKA_WLL_rlwB#i@`(F?v=;*z$wa0zm&G)(ncW-!u7 z^ZPRl;q!&h7d~J3eBtwj&lf&l_|RbwvhOB3CBZXRWD4sDih_O z^*(=9L*iNQd)E6J9Ms^T28TB^hQxDz-MBp@nhesEf{YhryddL+O!xmnHd8{PIgc43 z@t;L(4T;qbTs;r#U+vz0Q_ceRb1Ed(L=0pk6PeCj7SqH=c5;}rA@O2AhA@W7IP1ll zYG0Po!WQ8n&3SMZp#YTV}Hm_ukTmsag(jIwW37Fo;nYi*ZB4~hTk&oIVONI45w!CJPmkCP#>t`7qkK`zsn!y+2lzzz;^CL~_zOFE;; zr<5v|vc~C#CTG+xKj)%l+y+|dKY^E@qg{-2D?R@#~|F4}6iA@OxF^W8j zsYLiD;hThS623|J>%w0b{<`9?EB=NxzA*HQ=HlE*KbZdiQ*_6HUDtK~z~A9Qe*1jBv?dWpNE~t#k(-D# zh;VU8xR8)&E+Rw(L|l__5&=hSPL2qP5OLC++~f#o%!xD+X(ES2I60hiB91X&(we{l zayaKC{5|;NvHQAxzt8vc^?G-mwU6AJrhn7)Z<_v11>aP{uhNs>RPIgX{3<=!uhNri z1~Q7N%wq**?4XWTu7u>bx-o!}_k_{+!NU=kT9qI_lZ>NxksoysB+s(9x>2L<#e)NPAB{2bn?5o2=L44;O$%3d!}!^r4Pcu7u?Gx?$q? zMq;~%7qE(r6zuY)mKH9Dm_k0w zSx*%;oS-cve-P7`VJQ58!UYYcXfQ>CDH?pU!8ebZ;;1Q(W-yNFXnj=cqgo$T`l!-J zl|HI;qtcB^H!9tzbfeP8eD9d=9rL|ozIW{E_Y88(Ajb@H%pk{(xc_4&X);NZNt#U3 zWD>tfPWFrBcd|dc(;m3s^7k*s$apA{>9~XYymX6ClA^U{v z6S7apK5-!=pWMbl{PUm5C)-1Eb65H?k{le~?C@rXH#@x9;mrblv1aZI;GSprT(VW-<0}WL544rn8Q*^(9Z9HlmFJp z#gN?6h29KdEYn!bIx5*m119@>XL>P=@yuiqtJy?#NNz2{XIp)?pw)+4eYn+!Kl0&^ zeE6fmWHFU_te}h?A^EgToVJP6HgU!#&e+5mhn;cQ8Hb&5*cpeNao8D$`CW4InR6le zY#t6i>)`+OKmYgY|EzTXexaOv-Z#$s#(9m-D|BHlE7`!#ko>U~er$yw@8t*rFDB8G zL5#tn|JR8gq%)ey$oju!l(L-zG;t{;U-J9U=)0;p9ud%_$R_Y5&ntrPlUH6Bik>YliOss$!?R~Cc90x-#aI_6$oz= z-X{FhRQi)iHn|jH`cF;o7thIl@tpkWxscqRL{A1WhAHH;ob^=Us@h#uds|5UET%8R z7|%==x&O~rd$EaX>S*OkNbcyy07jBS9!0F965$=fJA{8O{Bz--3;$gB=fXc1{<-YW zWq&UFbJ?HE{#^ED*?t3_>^I;A$(My+7JgayW#N7Uo_u*94O|GxegmHDH{i*B1D^bk zET%G#6_l}qgEZ3~lCN~7AH$izEM#AieMR;a*;iy=k^O~#{xkUt;a~X17Z*YlI@626 zjAtf`Sj{G?siT!EA^K7`1~8Hw@+e{*mF%Md*_~u}lHEymr!1!8@BgAsD=1?J2Wh4~ zMAvksAH$izEQDVp{2Jlc2){=7HNumGCkgi_VMIy7lY}S9PLiD@J4tqu>?GMqvcD|* z%d!i;Ed0yDzbyRAi&?`Kc2m#k5Oof8M?hx*odsMg;93FK`uw#fzt-f}DtGOAs;J=v zZ6WFs)0bh4XC{Ss{yXYY;l*B>xD=xQmrNQXn8<9Fu$FT6(8$FQC3m4WLm106=Ccw* zB=4r4(;JBpT4#tshBj0xjz3}7TV!1d;m?1FdT7+cs(M&e z53A}?i&gcw9HM_p#j5_vs{SdPTnZ_sB1Anmu#-cWvZpD(Vaji$F@lNAW(jL4XAg%t z8=_vF+<&hgq%)dn%x5LSdkOC)yqEA^!mk&8z3}UWUoZT6;nxfId;REo;nxelUikIG zdv`*1Z`r+N_nypLmQjlA-UY&Y3-2xbo5H^-{F|eg#5`6|#tshBOnZp>bfq67$svy- z)=|km8n_Umf9_0Q{H(9{_my$O-VpWk*9HHwDMbI`UemfU0B=YWm?khyV463j2}~21 zCh*2w3Mr-nK{p=ZT!{K7(UU=pVG8*yXFXNaaDuiF-4wh3oBDb&jPcB55vx(`rfTX! zG{9j495%pV0~|KMVK+PMW{2JEu$vurv%_w7*v$^R*}H4E?68|1HZaCn12f6S zAOrpXe~SjXi-FrYKogfj^smYEA%k&DX926&$S!JW;c|%5Q|XWJbm8g3(`_kT@pQ%0 z6;D?@UGZBKzeVv|6u+fF^IJ5(Me|!UzeV#~G`~gjTQt8#^FfLYQf!c7w<>n4Vz(-G zt75k*cB^8yDt4=4w<>n4Vz(-Gt73x{8?4x1#Re-jSh2y14OVQhVg-XW8?4!2&2H1| zHqCC+>^8-2Q|vayZd2?w#cosVHpOmJ>^8-Q#PnqtS*Okh`!a00gNOE z|NMXSts*bhQHk(x3ICSx4B;8VGlXXd&k&v=JVSVf@C@M@!ZU zhwP!Uhsqwhj|MJ;Xjo@@;rZ`qSQb;6#|p~W!9kj757D=~(vRUxU>1v6!xn^pTllvX zAFlXt#fK|CJeyn!DW-zG9N}DuzEhCoOHT$dhACM22$PL4*$9)}ZnE3`{B}RT-Oq3L z^V^rRo+@nZc3ZpM;h7H4>`x|>naeUt+0Fq@h3LCGsdxY1y%3^*OQsJQjAJ?rSVcK| zILz4)jp{@X(ix55QG!Pa9@WI95Zxi~4taNMVK)xC<8+8d2f8zmQA~3GqvwR^d*1v# zhkwuE-xDxq32Slq7>AE>_?^CYr|;eAdw2TYomao7P?kbj3S}vjrBIfsvrL_3>MZ55 zl*>{sOSvrB-&f%K3VeS9{`sHi`-iZS?{|b~Yzk?NU?Q_|@K^_rb?{gRk9F`^2jAu3 zyBvI%gYR!FM_ME(hP`;JX}rmxJ$e@VI0QIxYi)j+;(_HIG}xMs`t43ztLm zgH-yHNjA9@QcMMVIl{RRjZdN{gBZgU@>$M$s*pWi_ITNM%f4H7!QH~|*8FbG@7DZo zYq@(TwsNTK7Ist5=@5A?q#*KO zNHlRUSxjXfD=1?J2WjO>hyn4s>TASxjXf zD=4F2hc9)sawSBQx-o!}-*_G8W283_!Qw&gijGZMfj9LiV;3#FGsi(qI*+F zV-%B^g9-07;k`=StHixZ+^fV?1KxL)A@B2_|DyYHyvU;nQ{HFF`)WBGqG_GzK{}(c z>S=RXMkzZv#HkS7AL!0NtonYdzJDHy+^@*}irjD0_n!;V^d$P=&wq%fk7G7VSW7wk zY2;#vW^|!9);7c1W?0({Tbp5PGq$h~Tbt1qBF}V0Gy9WCHp4$>N;S=!Fhc2)+;&YI2wRSVGE|u zHGS@d5dEw(y%{w=n)4!Vv0vh@rWrNF~uXM$nT0F@(q!12#-}no~npE zRS|irBFdMYFWXZUk*6vmPgO*Z%6?S#qkjD!Ju2K|6_Lj(qIq*zN(tLS^ouFvvz+x* zg~(G9(fn0xWEZuxa5+T3Or<}WWRpuFrEKQ_ONy>v#R301SUiwX_}9T=TU%^vi*0SOtu5Y9BNs#Tt1ig?mF!>1 z{?#;O|4R0+D&7CD_JwH4JgX2|Ahh6IhzgVF$)FHDmWrt#%Osmz3Mr<7y&S>kihQof z=Zbu;Xbe-xXF2PsqJ|T+h3N4D;g1V{T=?U{9~b_(@W+KeF8p!fj|+cX_)_6Zg)bGp zRQOWiONB38MLByo%-InAhmHP+ssCdLW0}T$RGobsY0M9Wf0V+0eK%@Wok ze3|fN!j}pEweVl}Af3@nW-iMpWjhCuy*$vJfsA4jb684=`(M7zi~TfmF+{)VLO({5 zLmow}qmq3za3Mr1I@626WHFU_te}h?9Hg1{5IxZq*-s2-0{;BJ=!wM$e?s^Z!k-ZS zgz%NZR|;P#e5LS}!dEV0HJhlWj#jRO=(pV%fa1SZ{I`n#R`K7;{;lla%KokF-^yMk zdzI`}p2LYe>k+LIzDn~|ny=D)mFBB7U#0mf&3~u)?==6N=D*YYce7CZcWXklTKH<= ztA(!?{-p3Hg+D3#N!d@%MDZsTe^T)$6@OCkClxPvQu8&MuhD#s=4&)xqxl-m*J!>* z_!{ABgs&0)l<=p7KPCJr;ZG_4l;Te*{*>ZRDgKn=PbvPC;%gOOtN2>kYh|yMy;k;G z*=zmx|Iu3EYlW{B{fmFOAS&kQ4*T(17-|7VKX&Hq%net%w`E|DQ6Fd zIUAziccKUBjAa`0S;-c5Q;!lQN|Y#3V!)CtuKxRf$vjq2#tsf*eI@N7de-`$wZ3PE zGl5wwW(~rh75=R7XN8vvFBM)Yyi|Cp>{8jKvP)%`$}X+rT!@}aqNn?RZjcvam_k0w zS%MmVx=s%O`!w4oanp{xgY9jwy$utX#bVa5g?;XSLjxB= z^k1Fn#W2Q`OCcuwFBAUP0h;It(Z&?gFyTfMdTuD%Xu^$3Y}`Q|ty~GwAG$Gsk>rri za@JE&M4L^t*+iSSup8UjY+GAw zYfE=*Ym04dv8^q0u&phMZrSGkx9s<#k&7YvV;6cegt1IxKEnSf{Ex!_DEyDY%Y~N< zFBe`eyj*y>@N(hh!pnu1%PyB)E_m;#(KH|E%rRJ5Izo_|(n!l*|i<-Y^D=*s0i!0fH@E3)@D14jnZNfdA z6nQu)@^Dh*;iPDr;vPq<7zxrC>Upvu* zbWYF~q60B~8OC^KvIr|aun8-Evx!R~s!65~8H{5(3*3Lrst~=^$i)zO%qIGuL5yJv z`7Fl-|5HUxhz{A(p+bsL{*dyAls}~W+eyfHTgKZm-j?yUjJIXHE#qw&Z_9Yw0B;+h z_UeECUu%L|6V#fZ) s5OBdGxq9X5m8(~-KHvS<3$NF%Ub}kj>b0xa?mg>$Pr3J$dr!Id zlzUIP_mq22x%ZTNuP#J~=i;FEvzUqlJ)RRC*}%>aeV|msKt@r<4i3^xL3@afc4Zzb z@P?z_aMT-)dc#paZ}juVehg;LePKW4NpgRscrqD5kj>$eI`2oK2?xfG1^tqEh=MkKO=%gkd!HJp`X|~ShR<4BT!)^>=jEQ4 zdtUDOOCh?DOdt1uA;XJtOve^3tP0V`W3ZKv^I6V%s;CLk#UYGk8uMAn26l2NME^IQ z!<-G#rB3u9ozYBYF3YgNOWWQ5C9N)LbxEsFwE9G=Pn7y(BC}b-TFTjj@K1!d32zhL z))t~qWBM`-1=^KtSF&Bn_W3B;u4KEC?Mk+vq9a6~rMUmk(injQKXc$`4*bl4pE>X| z2Y%+j&m7p{zzzp?2=5Tykw+2hsAL}vTnN$Uo$1A3vY5)e5M3S>qJQ*bI1?zC<;!B$ zAmkrH{vqTaLaqq8BIJsYD?+Xaxw44WY@(VvTDcOUFS;>+k>sG@7Ycr%;1{yLknKU2 z$b&9%=uEE=7lgrHWHFU_te}h?A^wu@eaZK}w3#u#^(Ev7bgRhPZPVdNZWJm$6J^J}cRP@Xo?J3%^$LYc;=C^J_J~R`Y9Z z59~CklG_lFsN5Cwo(}Hzj*hvNt8S za5=DypNV}YAvYm6AvZCZxfm{CxWrBlamxKCIzrqv zg)~Mmk=ZO^E#>T?k@gUOr7Qgy&ID$$m^Dv!O*j$RurPy4G&81Yk z|CBl}tS-gszS@lej3kFVib9;~YErdN)jn1GRP9r>PgVXp<*!rzI_0lZ{yOEaQ~tXB zD1F_<5O;Iv*Bt(}MIr7!nJw(5-u-t!9pbMCx-&4uJ-X1FWt6g=12l0d#Q&6x@P88C z(6(M z4s$ld0}VN_2kDH)6%3rqGWS2Q)QjyLpovQ%{?}yskij^nvw&4>WEZvA+P~UbdMd)x zg{KQo7oNTh+3B*=Wv9zdmwk)uTV&rN`<8L;{}$o52*2e-hzCt#4ogFPt2f^2jkkKE zM=fHHTErf;h;OYA@!*)g3}ZYqS;T5KQB57KTnX`Q-59_~)=|km3L1R55aJ=7>BV5O zn94j>g!o&g`&NH4$tIUVim6~PM>rSaj3jz8h%rndpXIEliW&@?(dPb#nq;U+hMHuk zNrswas7Z#JWT;7onq;U+hMHukNrstZm`R42WLQp!zim6;ww-U=&bJq^mU8xRn6n`s z-ih7}VJy>_&r0_{e1jJ|Im9VCLj0W+(ip)+X0rt0-x2;D;olMd9pNK{kLW=5~GzEofqQo`SABfVe0Rh`g^7xQ$-CeD0HVncPezJLU$^3 zr$ToYQcMLtd#BH4jd%Z9ewgKlS$>%1hgo~M`a{9rPo@tUjAJ?ru!--NqwMz^xftTH zT~K!Ha3(N|BG#ec*cue{*hYL;Z-y||{omz{cP(ZOTi8tl7eYL)Grbthc$6Kd?6^|4 zW0G;ojg(z0fLKt8A_A(dr(h?osL)#E{6EtF7##yCcoF@_s(Y}8`z1-?=|_<&L}r^Fj?+@s&-Sgn_5f-+D+ANs&@A& zcb{_iDR-Z8_bGRua`!2BpK|x@;V{D?K~C?+w7rMQae)zr}%;u#9fP-sR5mhFVcJ>i zsH6rZW-0NY5)bw#lWcM+M2QCrw0Q6UN<65G_P<^BJ+{ zGvYi`<~bnG0eKTJWu7VX)>B0dCotv1sq`lkB_6iwhpqZyB_3AdVI>|`;^9jno@2;4 zX^e3Hb0&uP=RW*%AO5)y|9mn&{PUG;U?+z-g$d?%XE0ezWf7~{#9kaS*CCIj;E+c~ zVbVto`iMavDPbG?X>|XOm^8l|0~m?>^bALwZ_@mAR8h;>5I@?99;7oGWgnf7vX5?H zC(1sm?4!!gQ(~SH^G0CMd9zu*rfUkLw2J*PuF zKVTE{2QrFDm~g%c=bLc83Fph6FMGc1Uv{M*!Viu~3Oc23%yoMFw1?#G*X+zi7D^>#3ra z7A}YQ-&09vG?SUjGD_LbA#B3)AhG8`;>9+xIE$&wV+EV2rjAywg!ot87{D0(|Nn}A zwScu~`>Xvl(;ng_-nhgYmy9HbJd|Cc?2;;!UDATGSN}GyU|E3c?-2ZQc|3>(4{EO^2!hf@dE!f0w z8n_VR6`kpem8}@hOct>kn^<8JD{Nv#D<*uxgij1$Bst_!#5yY3hwPOFdwn^=xe)(0 ziJlB%4AWV_DmJo!SBkv*ug=X zX%F%0uJmI#+OO7rwf3vmQ$-CYP~u4?o>byVC7x8`$y^F4rUC;#X}~89xW<5M`jEjm z{PREY8WXND;hJ*x(8$FQKh=faDDhNXh}UjlCxy zvN9_xv$8TPE3>k)byOm}O!)K5Sx*%;oS-el8)Ew6zzq)E;J^(I+~B|s73`&nOCkQR zWcrZ7IHtS*|60H*HnNLaTDTnIjXg+bG?SUjGB&W2Ls;n_tn?37`iFsxViI#$N(tN8 zPa`(E$woKX=%%axM`P1irZJzD2;U@plkiQ#Hw)h^e6#S)!Z!=wEPS)@&B8Ye-zm?5*RO$s$&>iL3wp?^fYkg;xl#5MCj?LU@Jn3gH#P zD}-0&`$%F$pW*wv-aKu^%hn)*fPi$V~jw02F^o@t1_Zv08f--h+kXB^xlD$jzt}#p@pXIEl%Kh)E;RJ0V{&P%UhN1bNHUG2bf7blZ zn*UkzKWqMH;eQrhExcNIwc^!^S1Vqvc(vlyidQRMt$4NKuPOeT;;$+Gn&Pi1{#t?N zuWA08=C5h~n&z)H(;nj8UFpYgCNPV|2;VJyxA5J`*qo` z%YI$<>#|>${krV`PGf}o|L=)j%w`E|DQ6FdIUC}=o#@RF#xjlhtYiZ_Im9VCLi|Pw z!ru`7hU_A{xL-rf8-;lkpll$N2P5ZoQpEvFErhVSD&ztsn(>`zdivoY? zL3)Vy=a9#8HsXEz8!^#eyD^Ay$ouP3%BbcDmqL6%xdQ{qVkVydj1QDziUai_e$xSO z?&j+2(;==2tYHhjU*r2VzWLTz(Wo^7GqQI*wi~V^-cp9LR{CGUJNFSsmx!rv4Ap78e$b2h|>J7L1Z z!GQT~YXN0k3S z`40-T|3Lc>wD+J;{J}Cx+0Fr)xD?`sWcrZ7IBcfDW*XMAjr}yz9^#{2>4)-1l|QQd zQRR;+e^mLS${%gug8Ogm9OA~l3}ZY>G+JR}F%|6P2ch)-DA ziT;dcGILpmjh(Qu69=%d6E=3z#!f1GQrVNro>bzb5+{{7sl>@W9CrUF&xW|U6Fo>r zc(d?k;ms@Az)tEp9pVoI-5E$0Q&~hY73}2*Hg+nBp4h}Gn>aOve3rAGf~pYz&EbD5 zq=5?|Zs|-f2IGJh2ede#Wi^{{z~4>v_i;?eRDWN^Ms`t43ztLOYGbYa$t0UxoYksq ztFi^H!dkU$)%GK8KhpLiZ9mfXBW*v@_9JaS()J@|KdRvbZ6Q7#(-+~Vg`XCFTKH+< zr-h%EeOmTu*=J;*>4V~D6hEW*nFa3ujOJ%FKco2>&CiZtBC}b-TFTi&BNs#bzg_6f z5XLf%`K)9^h|l@nIo~_yd*^)b+|}>xvX2HXg!q%r$S(Lq z_$R_Y5&ntrPl~BvFGn~R;e=7S^*`F%@ zsp6k1{;A@hUj6&OPc?7Xyj}Bl&D%9^*SuZxcFo&0Z`ZtC^LEWY)BLku3?_@I%wq+@ zKNJ3$@Xu`JGua)oJ7jmr?vULfyF+$|><<6@SM0g1*mGNPhj7nr#h%-WKNs$~t=My0 zvFEnp&xLLw{zsrY1GyC9E6Eh}@g;+COlJYB z*vKwwY2k8+zeuG&nIRE6(~Dt@XC{kS%_jD8gmWSBr6hVXh;dA30joly;7c35*hMWZ zTn>p&sr1J|owCWLkYXwj-br{T;nxViM))@>$M$s;J=vZ6T2qBRffUl0W}7 zkt94xc#`lW;Yq@igeM9AvhXhp|FZDTf$j`s6qA_4QcBpyej2$L64!R2H$xc9H0HCC z4eaDlfiI`%2#GE!q%net%w`E|DQ6FdIU5rHuM<5;XEc+U%Q8yY&H~=XMzGlMiF?|_EHirDV&wbtJzV35hFYv*y z`{37WI6+%T^zgwR{mI0KdgM|_F%|5kiAy2zPs#KlgKjgbE>S^sgOWDo=PKCrb0wc*Gk0Pw_8&>!YEBuBPe&a$&^y*A629w29=COh@c5skp z+C$>{uJmI#6Wssx!mk&8z3}UWUoZT6;nxfAExfnz-okqe?=8Hy@ZQ3E3-2wwxA5M= zdkg=j@NWwLrtoi0U>1v6!xnZ^&*_lpW57P$-G85fjA9aVSV{@o*iR!DL*k#i(3>HQ zWg7EY$p&_Eh*NZgMBfzB7{Nqj_m$mOc3;_jW%n%*-dFey!fz0MgYX-K-yr-3;Wr4s zLHG^AZxDWi@Ee5p6W&jFKjHm^_nXZU)>6(M4s$jn{-qN=NM|&YnaeWw|1YIpZ07(? z2u~B9COl1en(#E?X_}{Lo~C)4=4pzjDSo5kH!6Ok;x{UOqvAIzexu?yDt@ElH!6Ok z;{5}?7)%!X+<*TDFD`_{O`Y+>oBZ%5KfGxw^H@O{J2*%)?IAHBn_LPhrXnP6HvP?} zzj+U)zuEKyO+V1|15H2B^aD-*ufF%MwY0eZf4v+M>8bQ56T#_%(*>ssP8WR3da9`5 z1Z^QPD5fvN7|%==v6@X(Q%5UTLgLnL3}7TVIQd& z#BC|KmfJ=!k=ZO^E#>UtFlR$zhzW<7dWflqn0kn*hspK71Y`)v*vpZS80yfW4jt;yp$;ADJwqKj)S*KiI?SQNd}f%>4BH~li#A-IV|J$pnqm?Tmk=cy_jO1cSe76g|8NyhEjubjl=t!X>g^m z$Qp(zvrL&~$}FqP+Jwzz)p0H)zMn)-1~G;y_l<4kN=V$*jR98 zqLKEH7+24QkoZAodg0(7IQRz+{(*ykP)r4TIf8@7J9xZ<$2)kugFQ``81LZm4!+x8 z-|esOz8n%0QZd{F!~M{3KkPxe`~Tr+CNrOvY+xscI7LTDOiUq-5lmz@Z6Wcan7%0b zBSn9t=#Lcr(P}nPjmdvx^6W_*LFs#xzDMbM3bejQ%X_rEN9%jEzGppE)Nld^#E5_72K>i_;vz8n%i zO{G7XWRpuF#Z<7DBb*C~DONYd>ZT0B=B7*`pXIEliW*MP783Wy^ko?1vAKI~?%vg0 z{ont4tEr=vD>=on%}Sa{hHse`Td(v{C>snSNwj(rz<{P z@#%_BSA2Rt%UO@DOt+QkwldvTX4uLMTPc_!e1`BD!epCNpP@R`DA z4q^;b5I$4*OyM(y&lEmW_)Oss2!BBM1F|2G{ebKTWIs?$1$#NdIrl#+DI{j~MEES> zvxLtQK1=v4;j@H$oGvj-_$=WM3V%@egTfya{-E#&g+HkHgNi?>_=Ac+sQ815&sKc4 zt<1KS**WA<#5(-v|HSNlG;kp#9_mal29w29<{|tc%^%YIAnaUFpYgCNPV|A(5X%PX;lDDJYn~ijD3+f0q}vv~W2j z9!;e`8a}GwqY6H%;G+sYs^FsvKC0lO=R#th@Oi@L37;o?p743X=b3(<>3@+z8Y9@l zVa|rc{7&@X>c9WbAI)UuvW!x;bATpn;+M(vA%k&DX91@8vMK-j^hWnbl#IFW0k{t3VVjY$2qk#({v7|G-7)%yZ-T#t#UaX*u9UP>Y zcI~>-kKs&U7K=loa4J4m=yQcWSLkzvK3C{-g+BL~&pqaIkNMnVKKGbXk16$-QjaP1 zm{N}^_1FdXU!+x$Rz+GBX;mb=NO+O(BH=|QE;_=wka#?ao(y6PQ?Sm*t@Cl~e7q_o zmZp%#2qrR{C9I{KJsjq2Nc=}ZCtrGy&S)kxmt~Z)odYy+DI}I9(}xVkF`WghVk5h# zrG?8O@#|FjlSwwY6jDruZT@<%7pD8Q>6V*rx#^ahZn^1}n{K)A<-(T>^JHA&H(S_E zeMqdxXF2PsqJ|TA?-Pc4Vh9tMh4nw-eNTAb6AC_I`jvkFn^@_MD^G{SZ>{9FzPV~X z`)J@oNc^rdy%pb zv7BN##Yz?{S*&ESlEq4T&@EBmNw>r^T0W!YGg>|){2AfT2!BTSGs2${{*3TvE{4Rq zF4(|28(3!p>ug}1@O8r1317FHdQOML?*rW#$S5W;hozKo_3!_GZ@s^_-rox^5ndv^ zM0knt65%DnON5sQFVVb2^AgQV6n|FnXBB@|@n;o(R`F*Qe^&8l6@OOoXBB_;Y)F)L z!d6OcrPNkRZKc%ne~D7-Ew$d#?Hr(qOCj-GGJVKk9Mf6ADmJoBn`+3>VEB?IV&ny1C;?FDoyyDL*Uhur;8#Ldrm^Ex+H}#wjiT?_8XCR}P z#2l7V!Z!BP$i_&q_A1lS7=M!~OpuB_#fk#t0@dn@5#O7rBkij@?VzW(bwu#L)v3ZyK-)t3|tzz@#kl2z+e=^A? zmqLoEU@u2F7ZQI=!kYg$h%rndpXIEliW*MP782z#eHq4hW>T=om(^^dnmSs!5)xax zF#zFPg>MzURrprnTZL~GzE${E;T6IwgjWc!5MCj?LUx7h3dJiFuTZ?=Qb@dzjIF$2 zD+Mo%^I|#+u$C9B<%M0?$_r;hqOub`NM|&YnaeUt+0Fr)5dNa@7lpql{6*O>%6?Jy zi?Uyo{i5s_Wxv?MW%s`=H6*t6CzEV)DWn+9w`smj^KIus;-w^dG6-9F$yQ#nm6vSg zC0lvPR$j7|mu%%FTiG7dmtl;jp3@=mCuRPm%%85_|DUw^lQw_491>N^R4G%XOqDWK z%2X*+rA(DFRmxN;Q>Dzy%Dk-1%gVg0%*)EWtjx>GysXU2%Dk-1%gVg0%noICD6>PE z9m?#O$<_Pcv6@X(Q%7q^ydwM+;jajPMffYiUlIO_@K=PtBK#HMuLyrd_)g(Fh3^!; zQ}|BdJ7w>by;Js1**j(Ll)dvxNW9vO0q+0RkzVAGhwxX0zbgDy;japRRroI9yM*r& zzDxM7TnZ_s0$bVD#HEnws~z$!Mfi&|Q^djHj_AyM6*OtKMPExcNIweV`; z)kioN60h0HYqs*5t-NL{ui46Lw(^?duPOeT;;;80ozYBYE*C>$PZxSKgt1IxulxUR zE&p4~|8~G$2kc!M5^pH=hEi`R^@dV!DD{R?Zz%PKQg0~rhEn@V*v5VuanN5J^cM&H z#X)~@&|e(%m-(z@13NjyDGEA5Vt)#0j9?VP0iob{7uc@)cj4&-_-m~ z&EM4gP0iob{7uDc6t7XdM)4ZOYZR|hyhiaF;We^rWY@@kOYyf9e@pSV6n{(cx8}P4 zw={oC^S3mAOY^rhe@pX&njh5spymfPKdAXZ%@1mRa3i~@rG?8O@jt2bCzEV)DWsSR z_Hu-CA#o^)o(yvThsH34e3rAGDrz`ETS&Ye)0bh4XC{kSjqtaHzb*W2;kCkRh1U*3 zcCGAM*|oB3W!K8Cm0c^lwm|qh!ru}8j_`Mcza#t|;qM54XA{-bA-qm_o$xy0b;9d} z*9or^UMIXxc%AS%;dQd#mHn>lcV)jT`(4@Z%6?b&yRr-3)%;z}-xXdjyk2;{@Ot6( z!s~_C3$GVmFT7rOz3_VB?+JfT_^>KP>#P@Wa9n3x8ku`@-MX{C&;e*Zh6W-`D(o&EL0`_ig39M&XUZj|o2}{Fv}#!jCC_O!zV3$AljfeoXi=;Z3reWH-rflHDY`Np_R$ zCfQB03z~#C32zd9T=;R}$Aupkeq8u*;m3s^7k*s$@g^>X#EE43kij^nvw&4>WEZux za5*GSrqZ8Gvaywuh3@~P@RPz%3O}j&NzI!zZ`Qn7^JdMPHE-6uS@UMin}s(EZx-Gx z{6pa%3ja{}hr&M;{-NR@ma?4#DE?tbNSsO`jS=|s9}=f#vxE}1v7bgRhQ!~x(2wCv zU>1v6LnXHNH{1K$<&bDmqD6@oC0dkdQKChO76Y~z&|`av76Y~z@b3ovdrz+Z{r}&s z@9)<4ckBE6TFTkOVa|p`YbSb;&S)kxmz8W_Cxf(bt|;YSlO;73a-VH*bg z$bcUiu;8=_PxodBW0}T$On7<=yQ$}NNSq0DXCR}P#2i*oMm2S`awR0rc4Gh|nL<9x zSx*%;?*D9y7neige^cpCCfVdti1qz%1$#My^_{c6bJlmx_RfuCHcMDbIeW0Zb7w>1 zyzQO0z4Nwr-uBMh-g(@5#3#w5F~a?SGSQ3KEX9PM?7)PdnD7%5wwbU^i8dwL zrjXBa*0YORT0-K}PV^uhYyNaHb6G|yN_?usr%HUPM0*MY-GBQiFQ#G>?JH2CU5R!j z+LdTm;xi>av*ypN`LhYkVlivjf=zs86Q5lOiH^?nVlY|A?vULfyF+$|><-x-tz2>c zpLfHApPTUW9P%h)9hC_GT=?h0FURy{7~`4AB34tuUXE}sB>s^^PX;jt+xv&@{bM<{ z_m3(HYJ53CTS#0Hent2fR`!LJePLx^9Kg!HxD>jEWcrZ7IHt3JRcvGzwX|?Kbp28) z>5OF>^I6G;|4-4~2V_~EeH=fgj1cL1zwSs0k&q0Ll*ow8$cT)HhzQNd2$4vMjK~p@ zHxV-;VuVCUMn*klY!;u*;2IV{vCJ z?u^B~7ffX?Q<=+D<`O=a@VSJ~C44U7bNAvXPGSgzdl2qHxCh}LgnN+fu>-qt5bfy4 zq$n4kJie&MJniW|HUf+!?m8_H0JW^`jj6jk6rAXZ}? zGEjgjG@=csF~R<8glmLrVTeUK*c+`9Y>n26K8%Z^yTS{>Sc4SgpcJ)eLKm2x;evTs zhDanL6NRY8A)H|UjR8!FVtT+Ap@;$DCgCRGCgCRGCgJ8VW<;?p1Rxv?}Bo_&~T#xJ|fCLi#c{NlW<0>T#%zJTxrgfAd`!H6h6 z0tW)I8tagO0#u<9g!>TgL-;}pUr6>svKNxQknDwIFC=>**$c^D=p=k0;l70X67HLV z9F(FKP3XcfW<%fOO=e5{&xNR`g+96hEfn$29zyh9A@LV;X);!;fkBF%3Va z;q;?oKPvX4;zd-vh>90c@ggc-M8%7!co7vZqT)qVyoiDqQLsM+`%|z#)9_~+{!GK4 zY4{hQ3XNzJ{o03x58uEOrrP@jNU;BodK{LR8}rPGA62qFn9)UxXqC zDab)7YQe-VXJVH#vCElQ022!cKsXYRj(k*tpZ_ZXga;5FK==~Emk_>$@Fj#VA$$qp zO9)>=_!7dG5Wa-)C4>hO9!Pi~;emt)5*|o+AmM=xXhk2!MOjMrQnHtly_D>wWH06Y zKV>Q5O9@{}_)@}`626r1Ai{$P4@F2p22oEAWi0~l7g9r~I{0diiAsB100a+-* z9vnso260Z5WeWV*|7Bskh(#)LQHDA+qZ=clEEhNs2*Q^WzMSyogfAz2IpNC*UrzXP z!h;D9COnwzV6ubB4kkO8!haU^tYpDJjs=p=`RDTVVxuyF-F zOYzrI{7SA}83P(!`N#gRc7x{^v)Qt8TJQLfv8-8hJL^kY(#RqpV?3Pj_)DAzj> zh}BpJy1TvrRcI6?d>e{UgCpp~8E~)j25!EAn{VLe8@TxfZoYw=Z(v_#-F~>?S70Q;!NyJt{Yy!Ff?4HTYvC;*f^z;MYTBJ^LTYFNnw< zjEZtI;Wv9D1W`!BHk6?b&FIF6D7Wx+6v0sx7)60m6c|N;QPZN_YG5%|As(BNhYAd1 zMwDm^&Hz4?Vi4yX*(!BiQO*~C;fF_leBWfS3>2;W5bCc-xnzKQTu z!cz%PWh$v;r;?pYb}HGaWT%pyN_HyQ4>%di1B~SX#_|A_KS1RVQ27H)kQ%PegX-p-p5p6gv%7fuZKsxeKi3YTy596Y2c4hxJd+{O| zYp?-XD8e2bMh6CQPLwSQ{167hw-COC@GXRIA$$wrTL|Amcsk+fWT%szPIfxk>13yq zolbT-+36kZe>&mmgg-?1Lxev>_(QQsMJ~!j$%w#uY{d@j29wEPG8s%JgULM1WFBTR z53c}|c{mx_D8XJF#YqfdT9mB@7PJ3bS0Nsok%tQGLkoH_Cdwl&n1^MEM4~8<27)3U zrHDr<;!%poqKGVt$fAfWipZjfEQ-jYh%Ac8qKL=n{xQ1G_Jxy^k2}E0$2s{p6+TXd zk5gd|73M4k1?Es-4h7~=U=9W5P+$%V=df@N3qQfaPq6S4Ec^ruKf%IJko^P;KSA~r zWIsXn6J$R@b}reuWIJ;S&m}yU@La-k3C|@wm+o`vK9}yFr28l7{z7qY@2h1>t#wKSlUc zRR0vkKSl9RQT$UoP=h1r#2K6yC0~O-Rw52**p71i@%w-NG4xWj_l&qSceRZi1NI^0p9;rp6BHAoP3^>&ljKyjP7|x_xx#0h*IJP zZ-gKUNyruDZ=r}m3UW}2S~Q^x!As(BNhYIXN3wkjo%4;ql?=|vXBhUF-GB2`Gg1tD3lNiFZC_Ede)DT`ncn#q- zgx3)MdMr|ri!#)q8QmBWWv{@2K&-|(WS{_5Xha+Pzju%qGorjKsxeKi3YTy z596Zn%%4)r#A=yXEfcG4M?WS-dD9(q_$D2`Nr!LJ;F~n~CJnwxgKyH{n&dPsyPoWNvg^-_@-_{;?WBXZ>EP{7oB=2HRpSs&U;tC1yyF31gdzqh z$U!M;(S$AxV@8w)3jqj60@9HWrrFSdR*>y%AbdaJ`w8Dq_8hEon(68T575*HH1z@5ACUb4*&mSo0ofmr{Q=n@knQ|{@MDA@3qUv$ zkdAy*q5-Yw!?-B_a)lRyu?8EEg(B?1VRT>+=R|2yKz0k+Eo8Tl-9mN?*)50I{}w85 zq4E|g|B%W*r1B4`{6i}Lkjg)#@(-!}Ln`O7JB7#Yln=*5X?4LoFqKxO(#lj?nMx~D zX=N&{Or@3VRW%6x~kI?G)W!fqfvmo$Pk9KPLNQvOgyKL@J!Qd?-U5 zn$e9BQ9cnk5Qx=ShYS>;3XN#PX-tUH;RbJnAPPw!yMycwvOCD`AiIO?Ps#q2>`%#d ze!7Mi8<2$}?7?AlVHh)_bXuU|PNvbxG&(a;h-w_d2@GILl+T#PXN$24@z{(!RDfxG z#xy=-8ayedoOEaZPx|m;1)`CRY?NRxj^ZSSFfGczJrRN^Bw-thQG+As#2K6yrAvc9 zm|oX9Fug9O*R=;tpuw&&_WyGiuqU6>;pcStIURmZho95o=XCfv9ez#`pHsx=6wz&f z4!h~Fn-06_u$vCM>9Cs)yXmmI7j$@v>{Dc)BKs8Cr^r4<_9?PYk$uWZ_$kJBY5-HB z^mxD*p@=~Wa!`s|Pb-=KsXYRj(k+20j=o6xG24@@Io-i?j_sVOL#Bg zy@dA?-gicnFOtA$zhJaq)Zhp@@yEIIqMX*?kCli+8n&YxjcCJZOo;NO8@v$$w(U!@ zza;xhvcIfh|G#YJMK?x7=@&QW6gKo$u9n((g)|GEQ%I48=W0zZTy z7OBVu;e&(^52Au^|>4S_2vxqLCpQ8KRLP8X2OIAsQK?k?&~aI~w_p zM!utw?`XvN9i4nvhB`E(8zZ6&3mgc*%jp9uen@Sh0(iSS9nCkdY{Cj4i@eBtAw zPE+l4EBY`l%5SdlLNEybjqu+H|Bdk92>*@n-w6MW@Za6wjSxg33ENPN8Z?7k=kHUZ ziU)iViWsCI2NgJocJyOX)IYhy2P+YWG;BvX>TwJ`7!`HaEO;Ua5m=9{*n!Z7 z;}A|@08^sQ@qjNvLG~Q7=kS)OI*0H%gwJV07ltt-s=I{%5bjR6JK^qxyA$qCxI5wQ zgu4?ym+-lS&n0{=;d2R}OZMCf>;r|*rSQ2F?m^)m6z)OcP7lI82*1!DD-nk@Y)3ij zaSS~e6;+`~g(CS4LRBbIrAU<`)o>&r9r>t416t9CaZxo_cp(^Tz~BE?wJcr~VGjqWWi&29kqR!hc>U?JqA0n_GTd@PXaS-k3$E2uU?(oM-#32m?c$K3b$IydO zQ7@SVPmp~H*_V)g3E7vBeF@o@kbMc+myqqegm7=dy$Sawd_fcFWC5M{90ud^VO&0p zYhjhBzI5eFSH6kJL<#odC{AJs)1qE#fC4V1fJ-UhQVO_~0{mFZ&j%|I&Hno(i@InP z;;|WdsK7q7pciAJ`nzBrmLXEq%Ys2+mr>Ya1%3!aEK-q+GSs0N-53$|a)ASZSk3-l zz782EKouI%hSQi3HNXuGXhk2!MP1?wFHppiHQ0bG6k!jT+!7|YgaQLkU;tC1E;X}6y-mlM96@a2RrCww{K%L!jj_;SLR6TY1A<%BOMJecrc!h;D9 zCOnw%V8Vk553a{C^k7ufD`&wIL5RS5QF&}f<*^-=$97a6+i|Kqx1)v}!U-^@5XN+s zho~zG!MUqh=xP?anuS6M2;C}b*kVxBwGC)RAI3#p=?X6dBL+~I>2h{lMhw+I{v#A>V)H7W{8 zqTb44x3bu+`_O`3jENfEEozJl=81ZHgs8D}80!xv7#oK)YzIZeQp6n;;k<(??%?7( z80{Tfu>-qBjY~x?%20=9us|He-AQqGQrw*scPGWgCmy;GuYVBrlcyn)yEdB9iHq;Qa#L}n71Nn|FGnbgYuC$V4>7vImt_jB?6 zp`s?oh`O;r)RYqJ6?Ibv_7sHp^{67;|tQPWn4`k15DUT|qRNM;@}UN_f+92iFfQuDuJA&zs9QH63q_(n zk_i@kgasct3a0YNsHmB<;E5naU_G{C2m7Dd$%`{MFY2Qj{IL>@`q4COM>*=zhSQ+v zECqgGv{_6fi=rP3MU1H1C~h0YZR5Vb(CJ_3^e^0#O@Y~ksKz1i_kYxE_9}Zy)W<#G zi&ZE_4UT{UAE&@4TrdyI5DB`=rSn`me=-N9s6`XHFpL>dw{N3MI(CwkM-zF+(1TG? zpDJPhpW2I~An>UPQS;s4jSxg3354WRR6a#Ly#^b=;!m@90k8jRU@=x99-EPe3Q>1# z#ST%Qq3CB4LC!P#FePfCGmQ@&qCV@1W}FwbXa#yiea;7=hy~R@Hy~;;#T64$d=Oky z%=OQEV>Pyc4xc}P5m8HA!9+`sDE1qrsXW1Cu%7jm2St7s4uX<3yZ3e($8HZ+L>Ky}@MO$OL(BG=PF@IbKUvt+Pke zHv^H4qoUTOp%#R`wF)fq78kxXCTe{EvcRHm`(r)0=k4>N?puLm90b2k-r-t)PNcp= zLGN%)gM!s42F*4worW{)|9-~5pA-8l!NL86HTohG$1p7F0Sipz0KXm%Q1HQ7U`hv5 za7fg@yCN3#7#H;r*B?s2Uhwrl;!%PTQ4dq-;X<@vg#CY)O5cqJl^-z>3>G_53vPJN z4@F?%qx@Ppnv8m!6ZM~w$Uqh7x`~CGwxR|dqP{N(1J}HNMAT*%P)sw|@Y55undd*$ zW)}G%4u?eLCm||71yPUXqhHj21t1ShrzJ+z4+;E`>sz}qCF(~TP!1Mr3jx=*HG<-f z)70@jqPEWh(`sh|?HvD@%#YJSLnnANMm<5bpDad~s2%HZLR8+CS3m6)wNpVRSoE_B zP}E6(WMDwle-r%geWG^ZbBg~wO4M$qcZ!Bi4T{>6i0x<*^*^CV0tNnuZSM7D|9iQi zmjnOjiBj~5+82vl91`^lM)pM-ICnY_Y|EGH&?IUmA2D(bh~@NELP{@WQ* zhl0>3>USQXtM7_&MATuXFuW2eXcP5&0omX05p{&uBdMVH5hi-pVE@l<1sC$Bp*mVF z>JJK%!MJ}ohDlM!)`LQRbOXUZ)`8>WF{s6usQ=~u|8m`b%S4@+g-CGSPZadiX7q_V zNnw-Z{OpcGIO%kXz^PVIf8mB-4r5T%bABiRum6{d226;0J`7c0@n89M!JA3ybTsNk z{f*Gy82fJ>qRx0>JH|!*og#m45KSzUU_`V(Z9u@Z}3*Sy7|xeSQ* zXFsIkjA*X$s1fagSy+b?qPclt8@OkVJCeciIg_ICriJFd6>Xx;)vyM2AlD-R+~>jb zf7*pEqFqQe7j}uJgy5KHst+nKCYqLucF}ZiWP_>=t}`;wESi}B&hst2W|3h#kb@b~ z9CUk;fmO&tt!Vu1D9w{>PYSu%&&dZ4&I?33sC+&*c`d^qpC?4Sgqttf2EO)o1>Ji$ zinf3v7i6GUG#@(hq40$qTX8DQ5TuK?p5XP_;M_gyP%m2ILDBA|z_M=(Y7Uk zoBmQQS~itt6a2UV7UUsREr;M`HqojqP}IwGyqnSO=J+c+Fd-lnp* zDP&)wXzxUc*5HR8(e_t}*0>CZMLVzxGol^L6z%VH`u9oE4%LYEk3ewm;c#&NU8Z=1 zMcykA?I<}%x%Z!0qBZ&BplI*MU`n)Rjype~!(%LXjGO+&ftGU7KIFz$Zfs?wA2F(r zQZXo68y&XQigvtFw05rhm}@?s7VX4d(LSjVt%KlCIrb?XcWy?9XrFBm?PMeJ_brQT0&yf7(Uk45{j zE4J+`IvrqY1N%h#x=^&iEYZI41(Q8fFWR?EWyl*e@LjrS!<--H{_hiTp8X%;!n0I4 z>Mz<4TsVdww~00$DB6En^uOmto1n0t=x&l?Ci6x6*%fp!Md4G2MEk`ZlcJrg6YYO2 zaDE+TMEjNOY3`Yx5bd`b(Pp-a_WL|A5&l}gF3#$=obA#&MPsZt9}>~UE>;!Y`sBrW3T8YpREkh z?K06Fk(d_!q7KoWo(Dz0I9c?0?x+%dehvumsu2AWco&JjAWw9kPSF?gnfG_~O9}LA z7kyE==>9BnS%Bz^*NA?37|x3xz_EbSqAwW}J&^nOO-c{)#4*vYs1|)0=YkdR-+$Au z+$Q>!U7}yLSo9V1M8BHqLUB#D=wXebU)wMG${nI#$AnffiR*npXgG!5Kq0HSZ*?vD zM88o$GdGTi9^nm+-^4_2ibf%(M32k>1>Bs7I;ZGssANrn=)C2o-%>1kR00_3t#ong zAfhMr{ozZ9z zJ>E&j@q?n@z)h)K z`~U%I2AV~GkS-ofK&$ARHSl^foot>IeTyHmFe-XF_oS}_ndxLc6ohS{(1&OyV+Hv6 zkDhT%^oI$3_@wAtsd#IT=#RK#E6PRBq~c5#$mFI+DeO@?dvr|ntWa=GR-NdNS%^R> z&WOG(7KIoT{V(z0o@}O-O>x=$I(eMMd5=kdoacY^oYgoi`V%4O5Ir{-xi}&ElL4T> z?aRPIP7N72DS94-<#Ao!Inker0oUa(Mw{qQ`+&us=2!uX6_knoS69%$UnfQ1kpm|G zjFS;P(;|8ydtXRJ&$7_7^`aN?xu{X}=lJ|w1?Z|c8N;GK&&?&C_~Y}S=zj|bV=bja zeizhVpy(G^=!G89%eJCZ^qp(KI_0cWen|8eLs3K{6(Ty%cIz)?V@UMM647_D9lH*S zUX=)ry*wuR?o82nYej#BLaJH#RSmVG?}-N2zDAd?O^9Ao4igh3F9d-yztJKGC}j5ZYCM)1rS) zF`sj;o5H%|P$&8+0#9X%-ouD{xUQ#D^#4%se`-YU^~N@w6#f4)Kz<+heW8FxzvyTG zPp=pKG-LWQ3{0S(oBFFTE&5kfKM(^t|2hhsALJL*;IQc56p4Oj9&$k;XC_4db`82j zAL5>&8PUHh6@8duhk5<|I?+e`5s4Ae&t`-7fA!J5qW?fuKXi&dM!;C5=sYr`|JW+} zc!ucz<=B7Ei9W$tf1;boK=5m0$`waM|K)_}=eYlW9%vN(JlFi{jzn;NnqLpU(dkT! z=)a#4L;A${Qys>|n8o-1#;kPIfRnT5AqJHg6vM?I>rsmdG5#EdGBI2QaX2i-1(BE$ z!>vG!IkQkNhI=ENa2UcK*7>X;L(R|24zZj}7SWImdLsP(Otw#(! zPYi<_jcqt7hRJm%i&z`L*S0_QiNU`UVmOAyxM;f=o?P$QDaOU+V$2H{V?KHFd&Tey zKndvnk{mId-i8?7#pn}b0o5-cWI>S_KJH-gg><@*g%(orLIQnVKv%vQpomMk?$RVM z9lt0X6k`z`FRH+p82+2lEXHMtAa8LDXlQYp7?-aWrxD;OMgRu`=wJz9OTtisAu$32 zz`}uS&C(1IzLYM4($Oi#6`a2!7o%bVMlcHocZhMNKe&&7H^2ztz7Vdx ziq2OUC}#gx^ow!z3h+6!MvQABK&4^%Vq6;qCbM#%7}sUuv>2;`L2=g~79+e^j2mLb zSnVywjVylS2Jnk3f~oPdLW6H^jhonln_9$(bb#qbl6^Bp-Aw+?&OtHOYyUu%7b{7P{l87;yn80|ni=0;y=nIWgiHSNuUS?s7#m>cBz?beF*Eg!5vo zC2TE4tsM|!T|79xZcL23$+|lU+`rxvh3FUK9(QE0|MyVkJ&ZVUHQL0u*8|*i?_M!B zDDcN-w2EInuW!n`1=iP2VEs|eR3!YKvwd&7#mk&pBO3g!10tRF*eb}rad^v z{->@2L8)}{0Hb=KNQ^WVNTZ{)b}=6GLO$qtb2^5_*up(qTEOe{1aRF$si26ASTu_9 za2RSZEyh+F+RAjdGMz`5^dsawLSCjblMjSEO5mf_7!xCF9qPq+OppOO-{y@+=e}1M0uOVc*+ed%1_0N{1h>sc10X0^l7>(V4Q#TK&}`&3^0KmRUrJC zWKe9OCwN^rF2=K~P$x!F5Xdd!zyD)Aw@!@b`o-W+H%2iFJs*VuF-p?WD8}C$$P}ZL z&!vBSX7Lv^R+MR+?F<#k5DZ<5fYT7<=47v9Gb{YlC9cuxQN@ z%!u)N0Jh`(00HJ%!Ydh{4Zz zjJN4#-)5W;;~k28hYt98j?qvo#(oOgUn)i;_cv0&fh2IbwZz!PEMrcXj}MvM-EKIQeNnK&v&XC(OinJ>!3ILYEC4~g+_zW(=! z7+tHdPmIq4!9Ab1h|!(E{&zF3Q*?8x7^7nJ#DjhNj|b|-=ruqG|F;rjV)W777hYgW zr}_M4Fu3>2elhx^u}_SzIQ~_J7z1u#;Q?~K4#Ex$h%uOmJ~6&=(!n?E#W#B~BgUCR zw2SfWJaF;16JiXp;82Gc-_hZB1bs&l!>wX`zZykij94fY<1AZvmPw70IeJ2jA6VoE zt{uxoj~G94{zoP>&U)kL#rSUl`#-_BCkUCC662>zF(wr>iSctdvN0;g6a`GxiSY~9 z{=zlq+(B{w3&pS)=W{SA#;;ueD+^9@|8JYcm|-$A^;+~b1p$1(cr!dSwzXh zpqT1nY{Qh8TC$k>8jOf(q>5?ILaCURzmpH+V%jIgbVP}H5er{D`wC#oDlO0Ca{c!meq*4JW76PAuHF|Xy^%6VcsuM;t^TLmh< zu3pSloVeZ>{bGg}iFpGH-B2UuY6BE@W38AG@nYUYK{wH9WDt&sd2@-F{6WUNg)VRD z5i_bx%v=3X0E&s8g%e`lHZEpNwV3`^9Z_fzGmb*;^gy|o@yTM|MTK{r z6Eh(Vbz-jFF6O#u5OR06nCmI}o-pK#nMfmvhsE5$1UGbudEa7iK8eEbrx4zPHIAE;OTG>Ua(h8eycTixPSuq?bR%#*k^mhETVwtGQ=#VpyESf z@*aWtJUJy^CO^Laafbtz;zUV1t1szm0i}@0xdWnEa zM!qXT%ql9b>Jszi8ZmbliTTQAF{=YGB<8E#V(!6flVa9%iupPP?4__bxW1Mtyy*cl z-lTxKdNFyR!K`N*Z}aaznER-F->8`H_#sWq2Bxq-QOrh0et<;}FolD>K3FH_-(69G zF)7W1F$#B7QZ^Zi&cn~TN#;GmetTEzU< zu$V2}`yqe-+id0H)?P6`Y61amDcC3G@jRRt^W&A^rW5I6ezHT%j>BT|V|nwl)yM(U zJIS^G=ANzrF+blbW;f@%x&9PI_fTlBik#CEEgE!y?HOzo^BW7rXchB}2hu<>-%{kabUow_ z8XMXz=64kL-5MNW|A*&^IZXKQgqYv+`TI7|^++_v#5~IaXX$X1B1Zeg{Gmb2F&|Wl z`6Hn}R){$s02=sjrkE2HIB`_WpMpTalks4opX0=w@J0FnB%N6I(Ir6pP<B zScV^xP$ibBpi(R=5-em5i)F7sG7e)%EC=TtC&jvmV;7O*$=9Am7#Hi}6f}#)KkI4D z7wizrIloIRFS_GR0qYVEPU~{U62Q#?T)ZSftUxBg?|{}) zLYLAlKRB?0`1*=9I3m`v81##^JPw^=1@rZlu4ol2BwMVj=gX zVufxO>l!~$L|7Dh+5c+^x^^$liM5id_#M%@j&4`6;41cL)fus_&l4-$z&1>Yb;DM% zR<8!(H&R@LJ4$d~teZ;FBvzz_bg^!xxSP5EW^&e0*qUZ=-7S4$Me*l4TTj`eP zd9COm924s{wjst1gvRuUbvxDGJ|@?AyyK{k~nrh zTXlc6Sjho6DAvYxs1=KEudNgc-$Ze#-Y666fyJP?G+$(aH6E-HYjZrfcZ&!6zlCw~ z?X#86_#ZkURt7g_Q1Qc+Vr^ZC4zV677mMFstw&iPi*Y}eEY`MJ*eBLsyig=oHlf-5 zVm+QFR?Zr+o)FMXF4yKxi1j3|w}&AUtmQl;Rvv%<%gW=Xr=rj;Rz6+iH;eT&0Z(&7 zK{O`C+TjTne}+jEYT){3gE1pkQLkA1)@waijX|-Bz0o4p^GvFQsg*G5zZHN5OY_8f zf%{%KC{|eroNZ$53;-9Er;GI>-SAzjRe_h{#HtJxYnM9)#Hz{_>*W<-^QR=Z5BH@UZtM&5EoxLEZOOrgGp7pKK~ zyI-t*UShpN_&a;VYH*0PKTxbj0uE5XfiP@FEqcT{XdwZ7{dbD_dm1WnM65#wXyzYu z|BqGJi*B(F)BRyOf0x{M8^F*1tRtyn9pU7Abow3v?@fz!bUWzypB!(Zpr#$*#`kA| z;@+PUt9dng#rj|+_ zt)pUnl!P9!+UTf_(YEad6KIp1^YJx+ngDXg8MKPL2J3Oga->l1YJ3BNWzSua)x z)9EMy*Ye)K^(mR1@u(H+Gm87HNUW1SV*T40$_Gw%)ruaVkNcdnrhQ<0O1l3}lVIpU^naA<0Z+ZPK;oq{*kSlmS#00-% zBHsmpYk1$^8jb`-e(!@)kUhdQM!5DY=g+3H|7Tmp8jVJUSU+`raBF{&LaLW@{G zE(VK@N1y@c#rkg=n#G!6)Ds;0iNK$d(1sbYCO2SGte=_O&;4Re(FlL|u=r8E^$X4Y zGA!1)5cdDvHgLiJf>A8ic>%@zN@u@v6W`TZ(+X11B-U?!$iz9ZW@6DP*6%F#`(d#q z5{+X2DF^jp&)SS0v1fZDS!|bJ>=FCVz9<&k)hT>PK$+MVc%d8LKm-(|=a`_g&foFCWt)r-Aou{iBT>0T*$%3W@<4^yw^IGBIT#Q-IvrGg8^y+ii+wwV-p+loKH%Cr81Wq}9>=-3TCsT> z-;U>+cwXP-k2bLrT#$`%_J8eqvDZ$Cy^hZAJ|gyd7QZJ56p+Xs-Ak4Cj*7h@Pwe}` zKv0q^O2xikLy6eQQDSf8^+t+H;TP~GrnqTL?9^>yKfv(^_TZSl#D0kIj0|wW!-Q>R(MM9m&Rl^Cu^$aaqu5zqm=^o7KC!nky=@fn zmsQv+cJ?~4A6HN&m0xIaF5u}MvGlU;m;Wu6T7%d?B^GQMoOB*{#%^brR0>-)C(+F z=7G&(?<8yIfY{|U@}e`H59h_MV4;e`V!uQ;l`g0gdzZi1Ra{)vE%wV)zME@bVSLr@ zU;)1Ew)Z58{Tc<=FxuB?WN*CKZ*Wg78E-O)H_wS(N5OA3h+RJ}_CAj9Tp@(AjAwaXJxuaZca7J7xKiK&G+o~ zw`F1vvCz6$G$7VNwN8M-5zGt-!sndDR?9lN5nqsihW{_&I6gFy<-0m2Cn~s z36F8?M?ntGi#?vg{*Od%O=Ss1s80AzIxbByAVxO}> zvFDn_{vQkcuTE@!*R;>G!1)oee+|GMv8OpVeNgP*LP0Szod4Ym<>C-`@bAAl{?sCl zS+U}n?Tc(Qio+#Z9Di02g?4ect{2Azyms?Lzc}WUio@L#b>f&?AP$dUaa=eHed186 z#i8yKhn6W0-A5b-ImW0s%)R2U$hFRi!@)6r({o(3$|(-MMRs^@69-ShIp%S2J~z$h z=J{3NMz2ut+KbnhP~;`~m=cFK1$plf#{vpja7-LNG1!9%aV#tm2R|uy_|}W#(slUb z^N2Y7G^FC7I2O6VxrPsPv*?I8{He;H0{l;l<1#N$#AQdtv6$nFQ!plu%lUjcp95mh zAdV&e*Z=|p-9gsUARHD)&|>Tp#}(vUF)of}IpSDO0m~_N`J^~_AKBpyrnBHqaa_q1 zt~`WkafGC!MI2XA#8re}#ptdY6UT}b*bIujn#HbWYP`|x2u(weIIf985vIfuwiQF- zxHbaWXcfmw?q69Wj_bJYy4~RYf5$3LUT>gM9O3i8;^B<(hH!Ax>KJj{=z-JXh~Rug zu{ds`vzr)Wq#N4AaWjEyEKu|lNamXJF_z{PjaVO$`XSlom-?u^4Rut+@9i*LdiaoiOTCYj&^noFR7 zwd+6uYX`-#j_h?@yKY7tcRN?}Aq%bISWgkWqwQGF&Fcxe#}hS}5=UYJ_K1U@j63cv z#5r+nSc7`>h~qwrzONFzPO`v)Nqfa{zYDT)QXI(}z%|Ju;@HUb8(GJhLggtf;@A|1 z3eZ_9H>6IBgYVxR52T|{9BGU=tyvro`Xf&qn_ZD1jx8*(r5bdZo&&CVC>Xqch~hHb z!5SI6LH5Jt7!b$SWc0KDkCcfclhDipaXf0^j5xB6isLat9@~s|acpw~0e|sCIJhQz z2PpFKByfFB44TC81cg39XSpdjCypoma8MlEnacJ?It~B@=Y@j5|Lb^)EqJO+9Qj!o z635e-m=Q-o+5b~?|AAS}{U66)k|dc)u5+$)o$H))ooi+$NoF#WWRfJ4nPg^?Ns?qH zla?ft$z*0GvwJd0l1yf1l1V0+Ofs3wOp=*QGRgP({o~PfUFUp0?_X#4eRcg<(5Apc z9xA}GCFKe{?0`T;6jihc`xSU33ygj#(|WW&>J%tuU-23R9vhC;3M?b>vaJd{zCeMJ zB#`Tg!fFMUQ}OaW3Ou0#(lpqvh$nFz*Ru@rp@JX@^5 ziuvH&b0a|`&k?+m%lZ6Z&`6byCI$Y-wD_7{pqfTr$Yc^PFkap)2&_z3;6)QGt|37U zBYlZYFU?S3)i{v+W!A1PP~epzAkZsoLDy@t6?iojbh&mWDDJiKSfRi=;`07M;Pny( z)=vTF-mpN-H`=)VwFG^0lL8wifvVrCQJ`)pwkq)UJg}jjBHo#xKm*4cSj#uq1Mlus zppo;92NZa3kpdeDxRD~?U!}mNd~8tQgA5#2ps7uP4;L!1nHV2Y>_?>vY~eC(`Tzg@ zf6R$y#?-u4flo3O*qVSA1wI`KHg6-?wkq@~@L8<_Ex8JO9>87&w%050g;1ve-)Ikf zITkw<*g@xCEm2@6zHU&Uty}@Vau?w54S`)u;#+RNrLp!xy8MpncXQx-4|LE$vLB`? z&^bzhAF1-kjS6(JhL_cWJ>wPNH`)R}?^0mzJ_UHb5A36(U+L`E%?fl=$Zr(TLj%7P zhgZje{Y47=QLF&poD2LpS^-`O2M)|u;4hEs&+~ZT?>J0RppS(AaPXh0=v3ek=l*31 zhvzHsU$G2j%Q&JyM!y3x`X}O$jH7sb)IJ$UTQc~Px^c`XtdcRP5nVElW$oYzGLD-h zBc@u$@k`MuBQ_mXDl|?oWt^}WO)`eWV+v|z@PVE&RHy_SPMU@FG6FU#&?dw1KwzU3 z`(>D8upB#O1W6Dqmtn;q17$Ko12G%TGHi;n`TcLh-XbGRvM|+!`(!vXuo8P^xGZ+p z%J3W%V55x4c(BJOus;=a8co4M&~02An8e9GDB|RL8N3T_#P61I%48V{lVqGa5dVK$ zNRq^vGETElA!As7EW&0Pr<3UPIvkcUoB+e=@QiG%lrdr=+GU(M4Q(>`wwp2Xkc_hy z$Vg&eQW3gkoV`m1?|>WUu>PDr8N9eP&gK5OOeiH^M&WrQWt>NV)CL*nb6~V3+1Mvz636(an=y&CSEOQtjPyiM zz?B(bLX$H=u~+f4aMgAh852MOSC0UNWwI`lb=RzsF~#BfPuV8p+FUTEsU)A;E8{v& zX0b7As|=oU4W4d|>_w=Pal>qIEpvE$V+=}UOrtQKT8*1{eA7-D)2CsVjGI}XyFkV* z0kCIArHorC{MJ4hGndP_t&kDk)+J*WL1t}~aeJ+d+4(Z=7ytszVZ)tsWz0>NaTnvf zi^}sh$>1GuBcHwbEi&%u50cDFl5wwtEiww&ci%i2^Q&aspC)6$V6e83q8E;o@xVBA z{Qu)(84s?Kv6uoLqO&C=e|V{kqB0qe(EU;p7SmmE5tz(lBzcTAk5TMnhh!|91NJ{Y z1ld>#8Y&rvJZzKk1kF4#5o={E9|Rwhu!HOWBqJ|nb7>+dfS1KaDT(-oy73fUJ+(!GRlYI|8FOMeo`KymUZJp8DB_g`*e7F6CW!kg4ZqqTV{PF|8L!168#OZ4(bc*t8LuaU zP3y;Dql`BOg09|Zkx@&bZ_>$|jB~?y8E?%8!Rl<3%XoV*xGZnC$*AX8eFjRgN5(r@ zAYKD88kkVwyCY@1yIw{kQ)q0G@gCK_S0ZC0mGgGG@je0Gr{nkQWo+USY}zE_gMMIh z({vdh4g}Nqutx^(m>VCl{-bsoTSj1+jE{-M*SC!33K^fIfugoD%}-OXj_bc|os7>G zqCrN>aHOLcyJYa)E#q^h@HxppKOkd!Jj$?H#uqV|fJ$tW(MnOR6yLf*2CwanFK2*c zI~)+7-!C)1qQI}V$k;g&1!(2^?>r>qYZiY^M{RK^Lbr@>xQ^e@*{)d321&jh1g7vU z<81Gb@mM9}yQyHTy9vB|7HY6Z#`nbdem?qSbdbD*!hf(p+#mMJ=qy~yA5``u*R3lB zYh?Vyn0T%?_5?6T#?M2*`JdZm?45^R8NX1(F9h4io_#Ap!N0Qh*De{|B{F_v&u`rJ za2b0@)^kwC@9C(PvEM;1)^h#-Ajuz8-a7##?`0Z)@&ohdCKtMqdur$@phDs&H7wp<)^T+F+uGW5D_UrlDP?W}`=i<`E4t`%Ofh z%p>Q@>^}q*GLISv9v{^qbAW{zU;y&!VFws!v*kgED!oZzgBTJcq!eNH&UOqZsSC zc`{SzEM=|C^C<8<)~1dE0naa&Ihy;U>ttSFqEqG=is2n_^TM4n#~ze<5$DnhWM0fv zE+*GF3L96-^}l4K%uBjtjwkV@T;mBdWnLC9b0QT^q=U2xvyuNs05nHl9Wubz)qnVBszuc3%36g-7Ou3aZ{D(9}Fi0ddQE0^nkeJTi& zO#wHg%gjlXdE;QPVOo{Un-t)uCLm`DQhUw~M&n_6H4pOF`{SwmpaI z-@ZfUcRcux1>fzL$tz)VHywVTf+Fmc*)a;suvg{}Ly?0znVl5eNkN?*GJl*avnv+! zK=Pl)qa1r=?kNF{{9J}zGWSl#N}0c;as7W;E0d>DbKe%3zj7&lorM;e-7NZzihrYs z9`5%v%j7B4{GB59PXwL)kpTAh#({mkwKD${#-mB*0S7B&{zU_SvH#$7w8{LN>HWQ4 zW*^`GH2Vnf4+Z=~b%#tWmiccY8f6~NM3>C}@)gtwR4I7GXlzihUpm$+cw|4!MT>&{ z=b>G}qsCx?f&)y{D0p-N)+snJ4Z9RPhIq#uRIqSRwt|CN6+CvNf`enQS;6CGDj384 zm`OOK;PI;!j7`E81y4xD8U=^sD0t!!q<|t$>{f7So`NTF?4$+-1H=gIRnVXig91zn z;a&EixkJGqv4Z5Z`1jv~p?C#DO$yosvg;HKlOWuyptDjz*GGwh9>sZ7ADN>dZ>tA+ z$sLR)V!MKIOR!tPlNW-7JWB@S`xNBe^I*bQR4I7s5G+zKkv)mzIIUX2VdKER{~bJi zvVy~7u~fk`sO*f{3XZVRqu`k(3XUvL@GLeajl@0$&*pq`0F?@!!?ANH^c=bwwOYY* zhbfpc6q(@hc_d98jS2CoEO)vM~xyB*EoO=yI;@qy`1Apx7(8H0jL> zURk8zWO7|K9Lp5Upozka#R^`Xp&;*82Qvwhxl6%o#)2_TDN*p+Jaj2ImCe`Xpj^SM zbdcbB?q^R|@CFj+2y+#@k#jfhS8!UNf;UZ3aC$N*;%17vd4qzvxgg#xp!DF z!CR9QoH;_l+vslA|0D42{IJc=R`3oU&#@G|lLU9}Rq!r4zKe5t1kY<%@a`Q7<}IRhl2{v%UAH;epshqK_)nM9}}40q~QIt6kL#qHFUp{>%Wj|^uQo=D7c7GJy@&Y z;%N##L}H#DgG)HKWQBqc6TBz}ta)U)f;>Y8ADsjO6%(U)uY!*;rDaPMe0%~nE6A(g z;1jG{zFxs6Sy$>~j)G6MDpyy^g|O zpRM3}nt6jYwRKG3%@GQ2;M#0htzcaMEPR{WdOCWiRKW&D-AKa53I*RAfMqzOAg>LB z8#@(ze-f5}No`65>ozg&4^puZtZ8zv0DBbtFcaGq+&l#NT>s6C;-j&kgDqoFrQpXy zQ3kqfCden_Ku25Y=2MFOv;f@-ZX@}&dIdkDiO(pk#RqXdC*J22zJ0lZUoeF)XsUG} zh|QB)a0l^uLJNL1kn8_dk%BvigT-I7=<78KwxwXRg5OZ>H{9~RZSY&V`<5xS$D;=A z3Vz2FcC&UjMSL%8Rj`93Kcs;*oyq7`@W%=TyK-_s=bs|Qdu!rD1B>TM|b}G1k6e#eILEwDvGzI@m zzjZI%f8r5Sts;Iovb0;=ZVT1 znt)DOCz1H14YC5Gu(pstIzRw}jmBgw0SkD>vdlD)&}@|z90)cAr=wh!MFNY*7F}9h zvO=s4&A}>JHbvVLQ40zWkHBKoVUH|lEb>vv0hg}a8Q3U`XFAKPN4Km9!6O`xG|KXm zPz?%+I+zQR#?8Y@StoP;!$DaiO3*3mObR=5y)0fxS|jt&BI_)QN%Bz% z)|?%OIkJ-bqYC?Fox}QbvO%sWkr{b&lg zfG#g+mo;W6w#mAX;1{lxHMU3=U$L?-+JpnL(iY3QI0FyziKLSb%DRGp=^Vduw5-VjNqEY%GMLiU-@kPuCvU8fHEjUa%DRcb zH%&*Qtm#8A72LmhFe+u`a+}NL$ZeK&3yE)8j0RaVCV=y|CV1Or@S$B}&j_IJ2J33{}NkcK1*qtUg zcPD%14g_7zrGdFUvhJD-n!0PRth}+Hd!DYXyW_xw?k)r0|F!Z7kY6wBo>5pQYo3ox zG|9S`PVdb}ovZ>IbX7o6_w`2(829`{ECpTiS+8~fGFc1Qw_rIA(ILtCuk2Zcy|NaP zYa#0%p!)|X@B#k&f7YT5w8?tV!z|Fv;vt~RhY0jgHde`6LZT%kU9t_`vK~$W(|efd z712=<`yQc*M`&Ux`<7;b0{MQJ_2_JL$SNKK8heas@oL(7jPHM2%UHauTGrz+SSzc9 z4J8CFq2ed#{)sKJmQMuZd@>meWR>!`l%n{)xb+l8@h-VlmV_DDCF|)tS>^pO3q7)) zA-50NeY zB4SlDnQGR(kO7jeWZ#QEmdL6ZfQ4ZFOXILZ)~Z|_l=bplS*r(Q71#e2uH`Ee&@F4t z9Q4ZK_rR>R@tBW2vR*SmlGpaiT9*Ync%7iHAC|>yeCrK5eS;WpYy=5vDeTPwprAKv zWo;OR4YJ-Ejcu}cr`)O|Ufnvb|JzjZcB8C%et7CTWW7VD@38otE?EsFvfibmcdJ0) zMha@A!1uTm8;9eNtoLWh+9c5JraoC8aIR@6ie-IB6Z~z*+DvhqSIhb+0Ti@_%klAI z6gJChCQvg6nknKFrtt|CZrv^GQ#NcH4+3x7FYB{O;2O4&;PZ*tENeRx`(hC4Wwo-m zHBZ);+g0_>a zy-C)01mgp0Yj-*h$l@ir^?k3b4vzgW1}kNC#$hQq|6>xC$?6Ko`pLr*aC{H9dzjA8 z`CyWJXUh78@Bdo+82`RCvVI+iQd!+2uus-+6x9<8emHx2Wc|Jn&9e4S00sOp3|nOJ z(%b4Sz;0Q8GKB+KAi-Z%vJU3R`kV8A6TdG79kTwR@Iw@Ls8!a#C0zf1Np^S&82^8j z3Mm=e6gpxo@)hbg46O3SzdWFK%6mlkjM%+w=`1?f2D_1DOz6d$|RE4606pHJI zGKC6HW^w!wOi}0*4xTa#+Z0Nm>x2yoow`_|#1w^28;BhW4O^(t=>bqUub4w;jKCuB zcm&DM^gwqbDc~%MKWn8zN$feBDI^b9=$y&eq|mwjQKG`oxd#+Vp@JgPe{ zSE1B2^eJ>c)sG&FB84uXxG{MOU6`%VSf+E)5{1$@e(`*T#>FXg30?B)I5a*1tqNUQ zhP?_+Sg+7!n-!W^rO@T9yPSe1WuT={p)2S-oe88DD0C%BucU~{r3zgYkF^SA5aeo- zX3|Y&twPt3a7up=@Y-C3rVhn&^eS{+zCu|^=u+tV8Q88+_DY3rpn#lAg>Gb$H}ZJe zMul#gt5jOjhW=JcZ_Sd;fTa7H~-l3#al2 z)h=`tdO#S5YK0ata$XIG9xPU9afU(R60SSr&1Lv8_xAF+po~mEee${Q|Oru3O&n) z6@wLeE=i$Es(+rvRaE;w##zm=7btKg$6q92&18jMTA|R(mO`s}{0c>{844!4hNfO6 z>8ljAwm+s}t3t0anb+o`U7>Y+|2MR5qe8F8fRVnwSE2PRT;HtF8`Bl4gc3ymqKq-(A!%Ssvm=$3ca&Pp$3k<8w=Jq=7PlU5oaR>Z=}ih zrz*5*utFac4&;x+3N=+J^dX5qM{kf4XeJ&g+eJ`GzH+Mff?^C4&Gk15!u5U+|uy&VeuISTy#N9fO9 zg${7=03-X01b?yVuNH+4reVKAf9GSjLVZOF{X>F(3UE-NLv(Uzi$edh{@Z52}~V z6N!E7TQ2>(M1UAqFH$V1%a*MJ|YuNb!jzOl0qABS8135qFq_0(8qhJp)X2I8z>84u1dJ zK4Tm>ID&;E7NSG;neku(Bl{sA96M_iR>HK z#$p+`KZc}ZO0iS+g&qicA=4N;60Et1VlJYni&&dR$7vLG@hofvMUNYW8R+EtUlJ$# z5=MAQtL*W4*dY5-7F_%OQ_$`EWzSAWtL!^Ce#cVq{=a=kx9m9tpTmgm90e+zn}n^h@1lUa%D|eueA#!$ zVlp_EKNRJ%?@2_F?0Ez6|F^8Ym$moqmR&Fowb&>7zEsS>8rkz%J3pK2KYtl_*l>T2 z>;(ZV24gKeBzxg{*$+&`LD`E~_h1~VWH08}Lv;NRojyddOXz$FjXX^850k7Y6Psi| zLJ>=aS?H4eC~J#-td;#(0t&ePk8P99m&oj8ZL%L28 z^{?!c{XC0!g=<$e%l;n;s;RnqKDuSUFjMwQ8?CZmB-x8CvTNwPhVw7QqfGWH4;y5^ z90wAu9t~o?;$VgBH50K@_N&BwmE^B(k-e5ZYYV6I2c5rGfR*T#y{-tGWWP?e>jz<( z>^GRg8$GgXCtymI-_S>su*T-N1i1$t|D57B=DE8f< zT;_KP-bi)t4Fd-^P6dhHr>ji^v03&9`(J>Q1&(o-L^;eXMF#|Zb_2evRL-#7J6lGUoZQMeA%t( z*e?6aI8gl#g6`;&{naAbJJUeGud8LZQE=OS+20Uj*C=$${ZiYk)%IX%I?aL{Sym+S}A)^GHPW1oGp7V_kZ!x zA$uRk_L2Bk0{==k-NR)6HW(GMd*VTHzmJu@e*l=;AMF3542NX*GW9>n^=Dy=>;vPm zQ}$o8Wgna_`|p9E(!aZ8_vM0){@E(~5S{+ZxqtbAI_!XR|BX^u{ZOp%5e`Ze?l%;b zIIQrIY1poCf7bP1iEf3DV$FczIH+*p(PI=II0&^0AG2KHL8BEumH@}@Qh0DO3KTwW zhQcwq3LhVfJqpKGD}2H_rnqK>PhPEX{CLzUd`be? zn?Qn7Q?XXzM7m6*pwsAh7{`XO{`7QoDLkC>XOQQN9SV<_q41eBcVxDe8RAoxcd16&{_h@CBT^fKJCW zDtsZ6xRBzMZY?`=S;mfChB$G%!sYl@}=rWzXR~9Kec^H`5Rh-YD^9;JangXsSapnw#uOZ$w zdla6Mi``uRYbPmuE!|ID0>*k>0-6=hqWY}as8#rS5?)^gf@bF_d_xj?73Qfld?P`p zO;-3O67$R%p3d5vrEso?Vuf!>1(Tc+k9`W?x?bU#OBKFtzQVH>aQ$x|qVVml3eV<; z>W&Fu(Hsi8lS?pnvBEt2h4WaGN1=IZ6ux_w!ufQRzeVADRwz7= zj#s&1p47tkmn*!$qSJn;RCpn`3->F$XdHO|H~b)h7E{1ObiahrJv>L@qAZ0UA;}{g zUphkJM+YigOjpG#6@IKo;bk1>-}4BU^0$V8LEPd6xB zK40Ny(iN_tu!?mGKT8467BY$z1qwfxt#Bm?pC1bfs!CJ%e*~%~aW&PxutVV&Dd5FU zg=^{+erc7$t0?$oZdWf>_!YWeL&7z?6@GQ6!fQ$T8pqagSzq6%@OlR|3cryI3a?$R z@S9Aoa03;-MF({r4k`Th3We+0^bQ@pQ?78sNQK{JbK?Mo-y4h$g*VduCLV7ZukZ&H z&gZ+~4@W7yc?{_6BLZ)s@Q-5@ZXT`hCvggIWm2D3D!h%vpOt`IEz7z7pI0lqeXhb^ zu(*{#UvhoE>{56KRer_do!oxCNa3~#3V*|-c5PAk+X{u-8x`J7lHKh8ewV@>YZd;1 z1f9Y{g@0^NxGMo1|EXEwpNYLUUg2NT6yBGua5v5T#{0kF9s>6e;CCwAU#0LLB=2Q} zf6i8zk4eLStpvdiT9}7ch5u$^f0u&eeH`zbgRKhxLqa|^4gb@t@S${2Z^9bj#t5N9PFcAF)?XzX@oNbEJbzte4Y21~aiw&QX)FP|g4g zWpa2O=^Q-`n{ZgpzKV~d8I1wQqQ!FJ z`hh~?n83-bKe<3od>oiqe7Bra`1`+;;DJS_68zK&SS}}#MTy(woW{A+O63eA&@iTQ z`bbdh@QGlI!x`fl1m@Rkoe`7doJpZ)Zjdu_3Tow?6$=_Xt5Z(W7_5+Uc7HI5vm4|j z7iP&hhX6fuIvs%ejn&m-Wh- zSSsgo3b?#Q&Lq0xb)Itt!P41tFc`#efVjnqj_$<+RXco50S;G1ydEosY=V5|ATmY&o8UPj*(aj@CC_$r~ zrGu~>-Etlsh1GJ3*;mZ@;yrR6W9?%*OID{OkDPR<&-SW_zJRo1@RDrfB+^vHRQOZD18IqPQ2c|8%tSwC6M8-q~{I<8Ge zhnzR($=Q$!{{2VitpL`_sbj=-R9DAn>j?BV1=Ul)I|SgRlfz3Vr-9_}3i)yxhst@c zKg#56%mN+1zemodJao$WfFw;!plOqw4~e&VmzEiZA?TqRQgp0cFNi5fuLV=oxi4-HUhK};2RSK zAn2|%ux1zgzNOf2mw;m1^RY|LcT9HoIMCSlNnl?`Iy&V1kjVA_VVj)Jc+8gbBME*a zSXZW;pGf{wE=aP6U_Va+h3-wpb~(Rnl(TOFDEe1M-c9G-opOF7&Ts8B=@`+5c|A3xv9iXor-F?=kwqHbI+fPTDhZ1Fq(j)TjgFrfD3Z5 z4u|B983(2@rcdsLqrma830NujA_t4HLv9*Pq|rcHm)whK>S8*-nESll>W(AsB@}rH z6CXcE?xp=uxKZweL0AQXUq--*88|HW@?~--G2%%CzG4wrn;s83NoW0){ES@LE_X5o zO{Rd!^*A8+s${U{s%E(vao|#9FqNxGcs0|zdLGz+HTeoNbLD1kl6y@GsP>w@a;H%F z6pmf%fg-MD;Z!zF?T~xjSa2?D92oEQLs5-xx!Ge-A@>GGd;!_5W`-%AHBznKg263t%aFA`J{9}q&gS?X6mti;=A@%m?w!NA{&$ju z7n&}AF>vpylAGsa1?c*2Hs$lPkiSkYUr=)IX_7mSWb-=Z-kXUHatmglUhaKUuv6}Q zZs$|L{Y7#Yuyz5-3rSemDtF;bFueyxgIJ3S=gWOC5p=sa7F?%?Qcx#%$sEwZ!`v^L zF87gsSSOc{UEQTjXemWJIvY%+m@bPq%YAGDD0~@3E$fy0IOj`u;Cb@MZK&NjnkXxUCcDe7crlCbH zAHTYdOXa@D6gE=8#wNM%6LV96+z-a0o$KGkh7UQoneIPIk-LQsKjyYMN$w{@&@FfC zcDbL{%iTr+pXJJJnI!k~VW5-ktoedV*qSVtXH$0v$G^&vyK^4c+eU%Ah{?BW+;1Il z{99tR6TiKX2kmsxepv2z87RRHxw|c7f`s1>LOM3c?cj2C5WJ&C?hgd}VKyj&SG{g$ z8j9rp*dKGyE4Pcw@)NOsYLvTYBD&=69fy1yTkyMMXdKZatX++HTpTZ}rnf0Fpm3E=Ucbbf$@2ioQSH3}=_9vqHk z=#%^RO0;wR`v%DE%K{btLx6vnz#+zYs7da>JpOmF+`}GPAG7XbH< ztCbgHV>0UH9X|vdKYl58%ZsIm*a~?kjKCV~l{aJ}cFH?(7&td{5Hhe_-bw5~iQ542 z`S*ADqf4GK6RYHzlR$tV34+b?EMYbX7Rts7dG;u~Y zr>&DWY_7c1Dfsj{dBX`le5$-N5>YL0L_e_h%mea9ZjpBu#V6IsJA1LbWY(NB6!Ybc z8Yl1EDe_V{mNH$1UP_a^^U}~DFEt({O+6^@e7ZTGK%)mE4+Oqovb-@A!6%>Ig$`zd z4#)P#B+$UvPI(ux?xM9IP8w^{DE?xaxOh5Bv0dJ{F<8&_za$o!sFyc>0On!8yh~HD zN#2AhXp?stK`&b+Z=%4OiCyw8pNoz1CXEDzToH!?9Fmt#(dq5-t{jGPd6UU8xeR^s zu3}9_3<^1M^&n7XCbyZ3K;UaephDghrZ8onyla!uC~qnWrq;>3ZX%dY)?#dzcl`=^ z+3e3Qk#_@y->^nr4tsKlcO#R!ai_d#K3I1Xo!?X=Z#s_)r|*(?^HO=a1kP=hcMBEH zV8e{*pxaw#$eTF?B)N@Ev*yaXJs@xPK+KnSM-mRno3ljToelElCdj*M0GLW1>+c=~ zV&;#-VR`pd$(uJGUGnZ7!u7wmQ(gfJ?wf~ZdGpid-OshWpTZU_KoeC~%3GL=b@Cp_ zkhh4@K1ipF>11)6yoV@$Nvynw)8rK~wMR0s40OGeq>qxLxLV$0#C@z>-ZJ7mzF1yK z1J|GTZoTEb@}Arwuaw14ZIf4KVkYS5=@^joX#zaWx^g-xUxhw-e8}iMQwk$M&xyRJaqI;veq=dUx8*ZIL&-ybYqze^t9g7V%VSnWVeLASg&Df&$Uyt!ZAhFR#4 z_ZErYS`G@Wqo}%l^4?yGPI>hyXp;BNOnJPY>^1PS@h*AaZRPqmHp+W1UEan-ESASV zIN`ld!c94-m-hkPG$rAnyboEoIToDfsoL8TK(9PrIeX3hz*Ih&EpO{k?2z{<#eI4} z-Zl#SjG{hkme$`J{yst-N0oKWDqxiP@Xp;901%E>!ymIz-IVhy^Zzq5N z?Idb1llL8qzT^1r$@0GE*!Q_;m)DU2j{iWSABxc_kLP=@bGN)7=Yj^hvOv>(x33>obNzoEAn#W?=+4G^dA}`{*Fyn4+vWXUDR2K&?2z|I z8aB)8t(W)b3V8=8;IAZk2WQCpdlC-G>mzXAVtN0}mdCr_-l1N3|5nL6oFk8SyCa&3 zt%@8m5>v54g^?o;E7Fe>{aO?`G8rZ4P^ABGY*FN>aag6ufI*mpCPj|UK&K)DixfGg zKXxiIXr&^@W+^h5$AecWa-5IF*rP~H0yZgf{46X}B(@*&v9(Z<6NVuVyA&CchB~n6 zL>oLlu|bicsaT2~ikvhCTNL4w&Pad+{O(Z1U>#525tCyk&6wL139=_xqlh&Gv$0DN zUW!LT#Iq^RUXQ|kiiAgFIXV?_D8?zn0Y%*DV6jUAkDy++A`zw$p+J8!NEl5(HV7U! z5lru73Ou<=k$4jn9nW-5@h}f+3ZiA#r037igQSI4&9u?!cn8JRgrU(utSlQd_~S9(0OY?k*O4tN|CAi6*+%C z_9`-Z1`a85K^E#28555cid;AhQ$XPt?o?##c<^&@5&14Uph#LV*FTN1UOW$56d9L? z7DXk-M10T^)+#F`>L#Meb(L-87ZYMDI!B`rorkk$JJ8<9k_9FajjLF9&-S znZFhX6uEyKniN?;p@o^CfQ4MP2T1Thqau7kDe@qDAKa|S;tWL|Vp0!nQe;UsIL15d zk%wCqDI&%r6tgr9U0nZ17lR+d;@OHkHW+MLHcgSosqk^SDydZD2_Ga}zEF`T+4v;K zOJmWl$WwC^DPzoKoPV0j@ifJjuTbO}j#p4b#coBOO~DRDR&cwbMv>?86yfg!k;=k@ ziabw+RV1z=@&D+kIssjZyufHz65z$5Aoz={t>N~i1&XYSLAN3=6X@l`imc|?>PAIg z;pgHNrt?aJB5NGfD)MS7dK6i^L6O&{DzYwFk=Kd&dM8s{U!lkw*^1P%xOR^sZ&oX^ zfkkgo#9N0HsavVY+l;n;rXugeDbm2Y1`2(5IXV>KyF-!p=>EOAifm*u?^D30QHp#J zi~Wi;G1U)A_92CACf7%K;P{ptMLxE<{vY=#^2szswkCo5+e}41qq`OYe?DB1?E^sP ztt9`FpkGqd4yLe^$$ULsk+yV2zS*V7E{ghgpCaE4M2{l7cPjFIw<13<*&o>dqpe66 z=YHaLPrM>O6Z>aA|Bw7quE@U4igeS(Z*C9LeWhqsZ>1B8Nv} zKIq`TOl+2~WbBlGgo!%&{ib7w{3Ct&dOd>kv z4_bij@{i@*u^Z$M9*s5fk4utYc-%_)F=Npq|M(pFu^f!uD*uEjVDpet^vOSwbwktS zpOhy*5QnAm4c43724|p6z7-1!2~9+?e0w-rPp7y;6R13RcKJhY6h1Cw~;hp6j7le#$7c%0G`a=he$kC08m%onOd?(Ns3N zQ~m`>SS^3dNUWECVH!H*kDUrOUz9IDjdN+`@-JrX#l7;!mB_y&2FvA-p9F$lIujH; zf!LQ(z-4RXPmD#Q{LACPWG-hye4OhS^5(LC1!LwFtDjD=E2;F#TKSV1-DEagH3Afo zu~`1q$)J!-HfA=40&Uw#k2r4$DYXMxdwppI`Yx`OhTEugC^nKf7A~3gSIS(Upw;`Tn3NzTE2nFI|51 zD0Ikwf%`91(!k1@@?T_RHFQ%`EB~b``KyZMzq~;HYJ$CjH3VBb2sQFw+be$^V_iQ| zKCcP=+Bs;I|7I$fOA-|5I>WK3;o!19I@^|`i{of&IgAclCU=ts& z`tNR$-^jQc56XY9ME*vK+n5h3f1inLS_?MEh1>SX|7;AFV3+(B2f3ij z&zZ*O^U(x?ZD*|88*o_u7jv;%erpV9s+A_b%mTMN=E?tx$$dpkK4JBD7EY4?^$huK zEN)|CTc`YQJQT>^)gNVG;kT(EK>Hf`-=*V_{N3#Po(b^nC%=PBz!zElP8#VfM!WnU zx6AJ$-X7xYStkGI3G(^m)c++Jv%#9eeLUDlHT&q|*NIpqznfyZ3HIAS(7|u@@_S;z zx!+m;JIVIb#eTZv*D(D*7;P_?kY`H2w_85%|M~|6&i|ExTKNYjp-KMVtowVL{5~f6 z51;?}hc?OocN%ucKb$B3zoA&9s5~rFloyLpz6}-aXQNutBLi5ALyGp#K)s?zO~p1v z2aG~Fb}4#vBAOK)n1DJ(kD06JpdnbV=&_TqPtn1A|2sPPu%gEmC>k>W3lu#*4l5Lm zO~pDzPe{i`MTaDTLQWi~C?Cs4hZZS%(g;vgfX4xn7-P_=sJU3tAcY2-6t(gc4N*jB zv!Zs5qTyUcofxprEu=f%sr> zZHivJN>M&ajgD(j^paRiS9E-)qL;F7!bC+c8wv`YNM{qbDtbAWlD4qL6M?)1kT&8=-tyn z1Nk}Fr|3Ndo=1oG4grY@m|6iz?i;D-d>Y}c-RS%(>{axB?%!X*^Unlci_{K`K~O z(WB_Ibn9KQODg|Ks@U&Lo*6Ns>%E z=UnGH=UnGpGLxB1l1V0$NoJDF%p{XZW}9S^BuOU8WRfJ4WG0!JWM-0?%p{p)W|B!} z=KK8q@z`};=X^fzUuXAy?b>xMCE)h?m5N{84-JZcVK646Q}GoRDE`G_PQEw|BwbUa z_?M!nQT$qN*RE0g%bb6?N%89_XdTC2NkxU?*E7!btY5!h@vnNIxD6!OumT;5=XbpE zua)C~;wvX&v*KS*N1fs~GVzUv75~OmCjSP(HxYbOx8mPq^X3tt%eUC{7K^JKFrBw4 zvYKM5cPjp!F^bR}h!`k{0w4j_o1A_a5lt`+bVvOY*%PivM8-=%9`DZQO6$ zqWB*TFybFcw2w=;uUzpzk?<$>{!{@b!mHl+{UxZyLB;>f-r}FvD!!dS?dc#PzZ{PL zh0VWEc?SVIxOTr1@Yl_X@8q_VbDb3N8-ah@s`vw0*rE8}$AHQG-m3V61z<{lq=G`b zrhv)($+Z5=!2!h|iUWWD8UI&*#s5Y3heu(f;{WFH-z@xlHufpLn}q+22gUW!Wlt_x z*K=6${}Sw9y6LsRvHxO_haMTqz%Cj8i$xJSWc0~LtBfO7phZRu|NS2$rdGz0EIx82 z_RHuy6HPLX8iEZn`X!+f2V@*Q3tMIM=N!LqHh4*G9K*h2_Q;6Mk#TGvl;WU_0Xt+I zHv<$iungN}93O-6sFyK_bte=L5mhpV*q9B1pELn$(JN!3;*!AxiceoIW8@Sx%7{=E}H!2uM6lm@ngo7;KR-Jx|7sT%O_^m&=&Z4=ZHcWP?T7 zEXpnbL2h0uBZp3M_R6?r2H2cC8|^Y~T_7Wmy?NVZ+(uWowaS=D(%a)uBV*P=5aW*N zXp)gXPsW`S!9-?Lz-<2gZ{x0+GVY?o0t5SG@I!=gcc+Xw6m-upRLhu4fP3lWUZzu+ z1iHPCB8vJW2hDUs@c*A?3Gqruc7GZuV%~T#*#|7ll`%g7tbcGMGPwQ^t^gw}Wo)Ij zG9DtqLv*)*YrTLD9%kXg4KfxUlJUrN8H-4|C>`5mJj$L&Nw%1Ei@RhzwnWB~SZtE< z_!xA{Sjt477>*Vh%c^8NIS%AmUMAxye*QO}Vx(ns^z>LX%2=^j#xoR zpz2i)*2{Q~MCIHsuaoioY8k6@(I(>srcpt06?Fb0_t#9sDj6@)^-HW<+Xp;;ndV;Z zWMb)P13hHU(Ms{b{X$v$=E_%UgjI`=E>OFpXCm$v_zm6!Iw@ep(=-k=sV5 z^I4&c9b>RU#^+Cy#H%7Rmk{qx{T%+(Ct@oV8JeY?a65Aiksb#RJ>Zo ze#Y}N)&JZkqn(qyJ~w{pmeH|M#;;8>I@ihgZJCS%bn^RD83$Q=kl=q%+#eM7N3V>o z#WMcn+@WNU?5}hX<1p*~UL&J>o{WDu|Ic9=JvB1^9S@2t?#-9+9|io^D^rCs|2Gz$ zGW)EPc|^L*m_DeG$sdiHeJ9I2Dh{0Qw^HWO3FwyDf2Pc124JJi*ts%~^|4pxfK4)w zTPSm2zD%C=%;WdT9K^8`CZR><;CdCCClYK(jm(pl${acv4KhzA;IM3&r^H~r%;D2z zp31pXr^p;J8YDf9L~+Aq@@}I!vOkCs&%XE`nZ`IY%QQ1&S}9m0Gt>|5GHr?s)0~qk z)16W*(@Ox0Bl~6gt7HZQ3rb{02_7ZM8Nx!DqbTM~0-sqclXo-Cgo85A+8{G=xy-X? z%N%W^Qsz0C*e)|^EUIOmI~f#}yiMkLr8375pC=*n{34kt{O{jPAqcN}&9PK@VTH_b zR5ot6%!{_j96tb6GB0N1#oaPf37X2eOGq>!2^4( zCnbZJS5W*Fhh(M`^GXlpGABDSi?524c~zIp43b>U#wi&%EOY7*ER}g}I!Kr~Mdo$G zWM)NWUeEn$6J*{%aW}A+HyF(uSvzB)OkRMRHyxCjJxAuv?9Jg?=P=n@_Q=fLA@kNk znR&Bh-Zm2a_aDufB)xqq_Q{-;2o~PKB=Q;CohdSB3t6CuyLx06w8*@h;B&YX_l&_3 znR7>g^>b&-ytfaS;JqC(3n}Kla+$owG>b>eEa6Ezi)nXAUjd~TG?@Ov3@0pJvN!B<5!{xWkh9IdON=$j(Dt|E9h& zo2c%~1u~m!WqwsDbJr%BUpLEaq1taq-nw7rw{0@Nn=EsWgL;|Y*U0>VKy6IwM=r^a zO)~ct$^40i_Seb$`H;+ZV*J9mf9aChQHDJ-JDJXJeZcCrm?$fzNY;_{vX07> z)o+}vqesf>pCRj*gx>z)Mh%g5W;V9TN+52+Vo=;!BS3R!b;wGjm_*{A9RvRT7whb5S)&JmpraYn zIg_viRGvieq*b!c?FSN`+bk>D#zO3qbsmAwTYyekW2Rxdtn=eQm*=<0N*ROYvMylV z1-Yn~HI`#z*Mj{QrWW(1LDo1QGtn;VA~syqEo(dr$J4?1Hdz;^VZW@@WwI_Ah9az& z#V;ML33SV6L#<1>1pLfzP24H#G73x+m`YlWtji~WguKIMT|t3Yl*mf&i((e0@0N9C zGUm&g%-AOreDWb#SIw5iXF{zE7GKS#t8--WnGkCVm*E-%6h4(>Q`==-OYzr|IFrZM z5$ig>r?Mz2t6tXi4)}hUHWH<%lXU}y-#`P!(^))yg{&Jnc_RU4(CG{+y(v>x_CRp% zW`V#tB+sFkoNcmhnJg=pB68^{w^P=wS=c2jZ@R48`eBExnZ)3CiPr7&P$O#=lbc0R zv$%f;w|DUKzm?Cp^Lu68xmDKeSWK05R}2VLFaTw;?#>29&6x$(-9zPb=g7L3>D)^< zg-d1KS0t-w8XeJ432{m&=Ki&^=Gox(0b)ErtOqD;{svhO7GOWuzmzdQG!fOZ7Etkm z9$62QU?B@0NkpZrMQnVOju#I?tE|T;WC?+n(D~zsWi2Ja6P$a3u9k89Ng8-^i>&4B zU%p$`Q^YRIlJ#^SSu2>riY{5t(9p_a0zEqhZL(IS;()B@R>>--lX41qp3YWR$a-P1 ztcpppUK}rr|KGP>8j9_**6x(`G6~jE@GJSU*5}H4m8rbS{SCz3uv*q@2W3^Z$a=kA z)<*WcF;>r-5 zvKr^&kgU(x_}MO5J6ODf3G7&cJ+eL@g!!^|_QMR2{EHY&#R^$X+;5`TrUqGGP62zG zIoI4F>#HJJyC`H=jjXRJpk*}5WqrexzM0MSZ*@>5>std0L4~{NXgA4sAC~pqOze=g zha&c*p;p%S$yh0CFUR(_$@*a;R?BJ|jE%B>WGX*)%Gx&xb+Ud+LbI&>e6Rh?MEFtP zYUfh67jyELp_qa)kg$Vqvu(w_{+mSIsi69QVzCG$>zRXtvi@b$zm2kb zlVttZ2VDxOT%rFhQ>agcLPtzPw?Z)o72<8U(2?Bln+Mh%H3JO_^&5*?g?L5`^&g0R z3LP_Dq1e$X4#l$Q*c1?OKtIe;=r~8Afi@N@bUfz=v2oBO5d4Jo3Jso&8ih_wP-w_d zg-&AsP*0(g3l%y=3JuRkokFJ$K?%6e>xR&Xc7;w$Rw#}l;ue6v{|TKQRcPb@h2q&5 zPjX`v2w=1+WOB}&uaFgsx!9{vXbjdWWE03Vu9ZPC<)8m(5iutshn^ zbon4KmCH#ysYIbG$aw|v)Bpear!&$kSv+~FLRa+%K{Kc{gNmmNR_K}p5O`|6Lf0-) zD6>?d>zKxMbeENaWjLVF^%JoMjS5Y3z`AL~n%1k(4e6k$>BOI&i%NxVq_`XT_di27 z5_raJ>{RF`AK74W_9&1jdyhgl&jhzQ+~%WR)zA%yE@XcpW4({y_t9aIfpV@f@k@wXa!?_DD;Bzcy+ZSH z6nY?ua}RVW#M^732M2*}AM8=6v`L|d9F!`=Z^1$f2+Ff!=;0cL7S<~CNRdMP7A(Z8 zpwOcuv0tIZ6ug-0``C7cmgFh)I7L3r@ukFmVj|iVTE;EE0Si6J-Y1#f^5tCrr>O3! z4u#4{R<>KArwbKYF;t;v2>cB9S7s>m>{x|X4aXdXo+IFM%?g#%`STS0d@dO8>U=aR z^uiW}D!3FcZc}ItjjT;n=w$n^^QUuoz1uTRASFv3?^{OU?j?FI*B z3cW^>*ET9t$v7)lEA)B{N)+12`Hd9vMgdsAX{|zU<|?##GuEp{+BqU7`1efh6zkSEyzZNc8>;&|U3hg+Ac? z2VCPiI{MH8$G1&Il|mnJ`wwc+IsAC-0 zzoS*5UpFe$IR+~g`Yjcm3LVH+=y#I+PM5q!4EWHb#JgFcKNl)=C_|yYSbumF zmMHW$m#;frp?@gspK67An8LpUz`kCteeY(4{>zrFL9+jsh~iG!eOAamB13k}NZCgw zU>???TXx@6EI_mDqmr=*dt~>U0QU9UFZ<}psF2-14pVVZ_Az6z1jLUWgF4yA4n`)3 zeQbm50mEb$58%Lny|Ry+gnHQnv#?9{@vCGHV#6SU@cTM@@Nle@ec~M1Lr8v-k1p9m z+hw0j@?j1tWuIbUo$TTLu^2tFPc4-_q94}CK5YnQf>@{3so0JqN!&QFIIdUr=>#}^ zIp}_58aBy}9||TEzg4!8gtf9wsyEAJTLUl=6cI8|hz;nJZI@t&?C=Osl*8DaERe@t zEZZ}R`9dYULuE(u&@S7b07e{SV!P}p-A50}K4Y8gQ8q|4>agrHXUa|x=<2M2;5L!5 zCvKK~HieB&L7VJzR>)4G_;V@ZT-GGd#Xi|%vSpuN%!3pbr%>$$BSFv$w#Xh!L1S}3 zunR|nW8=o4PWDBtyNE#J`+_bm=JCY^*eE;I!E)J`Q0OIP=$1WU9`?(=G!@OVCo-jp zC9*HGxc53$5mYtr3M%g?~+mo3FFHY^Nc%0#b@m|fE ztM|#Ck|6sUAz$`Xy1$lK*D`_3dfC@eSXO_q_j+Pq-ywS%&;Rx`uI~-QuwC|auH%g) zm_eW!B)e&>?Cd_+CHv-WvTsSma@n~HWZz1nTPY%sB5q?^Gc#o0PKUQs$SgX)gEjmC zf<1eH?7RM7I*3s~_XV@Sxw{>%|J{3K&mrlY7TNcd%AQNYx#hC&C2%1X7g9)JtL*y- zeqXojqVFIlD&$~ zRyD|eu3mOIT|LkG)fDjp1-)=kb_Mq&Idx4$FRZ0oVW41F|x05;x5)lr1m>Vsc3lE!rjASd zA$vb8X2XYFvbWLgwpAeMN0YD|2W4+h$0pey4@4#wqg!@8$LqNapNv8gI%GExuz`J_ z#({}^x(1!H8>gW{_Gd#d8x*o51*|RJ(J1?K10>%`lAR^8zu@)@Zku93qNZK4zf47? zY@YY+W`caxA0+tdknCMqvcHZ2iM}Q{FR|?w3jJmTn94UCYfS^qeY;Hd?gTLH?P> z?k`j;d<^%G;aKcyg^x|eA%zFbQ~0>aIH>Tz6orpZS9s8H>{a*#&J8BVi99You|we@ z1UYHG!b7R(UZ&f&D6bN{M54sx56vh&4EFE2#jcp2# zn*eSv8V@er_&BiUV)k6TOX1WMv@3jxgKC8*48sED zX%xgu=kVopIjKnDD`K%w;q)O`q%iN}h55Wlcru+%UaIg_1ixxIdKAu>1jc+dw^!5P zlywSU!~JVo6`nc;hZMec2G^hG=Wr%Ld43LGw?W}7f@aNE_Z>bpxQ;Fv0mXvJ*+^t!i)2;SK-Gv_ZW$nIGBUo3O{aR zyTVI3w)B9)Pb^V*84WJmqVSVbz!aAkPvQ%Uo+5FXuvp=z2ZAD2u;7^q;C5v;81u@* z3O`$>@G6R4wOZllnAUTgFQ?%0QiY$-0@rpmMXV<73!_n|aK%D}UmOC8SVKW;HY@y6 zA`^S5N#V5>3csAL@H#fX!ogRletng~uZ~i9Lm$*D{93ufm29j$r10y#3U8!~Hz;Hi zN#EqMY&OuP@LT&8u3Dq;+Zz7GB<79B@J9sQK3`#;DZ?LA?8g*Z zPqKP$Kgj?MHT1;{uG;;kLxt@)5(%7W%XXCM1;T?lfsqp7xu?U?C@1&rea~1xA z(SMPRHietgPzug}nF)$*W^eO!Y*qLx5`RUUUFm36`0J@)LM=Jirtmk#7GG8<-0Fjo zemfE+3h!oYyD93s5!j&c9s_F>{+>(l{XT{FjzOcsKTHCJw~a!z!aq{vk97YdliWvK zUI~YP+NALQiP)j=&rwhquY|+xi@E;4*b4tb;tmpZtW@~dpyUC2yzwvrw_`w z{yp63DOLF2VPN#V1}YW)Z?YT>#s)e6vym;QPcq7}U(OL(=#>+*K+ciFQ6{Hv5=wAL z&QaVRwOdZVRP2>=bO!dw>7NH~kC`PWc4{$STIC!&4()OV5ahU3at02=S~oMPNZCpPm*9NTI56*$T@??&R}YzGQn-}nbmR(PPqMXD*AjsJc^5l$8 zL!+E?xPK0bk_zOUnaKN18?X_0fm9Bh*_HeJqz6n`N_UDzyV z+-x})QQUa;kMEIlG4KC5sjK8%G6E#Iq)E;Mp-axCY?w&!%O=Z7>yPzvE~n~AV^JyR ziaI&zba*8NPUikq?9WJ$bF~Hb@-C@!4MknEM9x&sUrX%FSz!HjG@QlMuIJzXb@(N* zbHj2u)2aH#@nG`|y5sl2PWECsH*c1cvr-N(Je^$JI!#XAY&o}4$V>v>-YMsf;ow}p zfmS(p?vgWmG|F&T&RtVbC8vPPRKWd$O>*w0(7TIE__9aN93Nbtd+78YlHWtcbCXan z=iVsF!p#!C%Wei+H?of@Pq6B5>(}^etfmalO?s)ceo@tS@l5SVB|5;)` zOM%a_f7K+ckA61EY6biI-}c2g&P-u zU~epyvx&+#5$Me-Ihz^ZTS;yw~e&-zDc`)_+Ww^^@g%5`!{1JncFS6#gk|KHVm#aV#kCGyeTQ zXU9-EJk>g%Qz^f6c6M%;^94bg_(o}(2OfVp08`N{r+F6W{;Pa&?yJLcb}^A%9Q%4Y zy5;cV)A?pBR?2A|jP-K9O_Q^m%d?v)d`G@Lod142*Z=#Sa`rNk9~fiXQaL}4K#iPz zqro`$ZIts9-Ty>UKXH6Nh3zNseggfRids4C6wpp#?G(rhPUn}oaym%JhmM?I2ciH> zkr$p$ClmOM`oaNhk!J_K>hAA8m5}%>c1_+1EP z#1XMrD>r61=EyyA02X1d+`db2NbXTn<@RG;zgcLLd-OE9{Rd*3++&0sxv_)6zGFFl zY_8k^Ls2I8xC!9j|96ig>A*s{$4^3!+(89sm3sn<`DBzkn9V0xNU%seQnj5i@aE?rGI><4Ab=e7PgZ5ubvCa*Z8wP5%4eu9c15 zazjbr)+TAVNUmd{U9L+K50PDR{f%;iJh{MI6=k*3FBncOSK$(`IE+`o!~GRA;}SCjDSCb?6%B-e2N z8Upj7B$v;Py4Nzj>xRhXHKlt!d#-PgJ1raJxFH{W{?DCWDECH^+{hGW48n4`H;u#! zx!F9cfwZyMNF$oayV=$3mQ=k9BkTf{_)ip#hLT%%$}UXlVf z-A@toVlf*moYyP&0Xl!6NACPmxev1bK?*1x0FplBqgC#LDd?2@@FKYjIk#|&+(($s zBV49MtXV{HkCOb+!*Ul_$bD=Kd3j!Tm!zXX?&B1(G%EKAI(&l8mr=;F4!KWmkh?rb z?o(-?n6g!JpU#%c+qCYARJqR#2LV?S=h;{=TAp6r=SW^o=j9wPpDXuyNA7A0TfJBA z3lqWnf9?zW1>UGa=9<1pi1sq08)@hb8hv9e*MAdTY^sy{CLO+cK<;J=dCSHw zxm6siVghe3m&;!bm{W1#He|cDL^GZ;NSU1I!FP3*mAoDVTs&7cga1}E%&d5AnD;W)XM#PD40}tDR#^KhdBRimfOSL z9&++})BSfo*T0u;d#mK~g45FwtdhrHL3n*)utMGu2G+@onIZ4U5m<>Pd3}dsr@W&^ zA_EQb`c1+jG|D@g7)RI3>puz|@{aMb6uafc&XRYmhvND225@J%0Kj^ek;GYDcPW34=kLPNRo zYy*4bh3Cj~n2gI>w?>}F<47Df$@5pq3uelTE|+%(aYx01DV)jqGh0>cCD6fHW%3d^ zc{V4{rrXgtXPvwx5+#xRT<)JY0Egv`p|kTDcZ%d)5CbMRwnE;8d*zMWC+{MXj;E>d zJLO&6EH9M_T+%IX!emsUP2QyxerXAsi{(wU!6+uOm=~MgWe4P?&6IaJ2``@pl1{3V z#|us`eY?CXSIC<@46{%!@2WnapsScx#zgFpcl8V$k~gIgUGlDB-PAZ#%e!`zyi9>f zUdQLZyz5wyRV43vU*5F7I4JK1y5i%;vj#cD=m2reeRmf|+QNcXuZC$eTkW_lyLw=5qhu>GBHu zV!gckQo!D#Y!VLu|NetlOpua1dH4HZ#PcS|d%y;xn!jBh&vag?0nRNuDI|lWgP@ zN#0W=FC$Rd7I{yP!!~&<=ynAYd1kG=l^zK6>?(OY%XzCf{u~L*=b%O&&vM@Lo$^+* zZgmNm&I>a@Llvd+UZk)Wo8_%xVlNfb%}eX#tz~3u%jLby6kev&b!>cv`VwF7J&PFo8`QgByR6fCNl2s(T}MP6+mi~$9GFc!-|F?GT`c^}fu zha}>INFJ{Zy^om4M?LaBjswNi6Qh2gyid6OgvSkIx&93`@_4t>Yh-g{mAuaeVlH~+ z?I6JC!$5bRcgfpH@Gl0TQC?FL_RITniM-}fXqWdDjeJGIU4`IMeO(~0h4n47!bCc3w8;CFWS!aaexu{xczl3n4;+^FJ9!S$^dH4T_`(UE$-F-k zt%f}kN?}Ph!!aFzj2E6iK0r8 zBPJ^nGZftC$tu!!lOjiP?5JKv`fXL@=yFAl>8nWbF|~@s=3$2-$8uso8g?pj+!U~R zAOQ!mnJ1#i@kNRZ5=s?0f!h<}!rHGRVa=3i{E8^0DMQz`3ASk!YbJXILP?8SRRUO2rCA&K!VjFfBe06iFaaLX#qAQQ%p`NF-SzInJJi zJ&KH8rpP%CGEuxkk)(9c<++2g8WfPs{p31D&KrpmMaB$4jUwky#WqD!DB^-apnway z6&cGKUMEH_WJ2SJH?B>Qi?%8NsB?HB9~9VIz=W??IgP9HDM$@1=WgNxkZu59w`1Q_FYw{NCrh*Ev!^z z3b#{oK+nh&Tu1P%0bqpJ&qS9Z(@GV&!9uel)5n5t zZY0=@K45CR@ff*@VA+!uxtYK>uTmtZKNc!-%LuGjBzJ})w=#`e4=R$!wDO2~TL~Hz znaScrBkp=ktZnp ziE^|lvW&u>jKzLMmXq|U5=F{rq>N&oo(mdTQK-l>?;I)c5Dt;qT;MP7B#t;mKwioAAE zk;*NKyxyV68x;Bm@i(z%({4rHOhFCTfAd&HHk14<4!lLRRWYE5w@Jp!u1NJzR4DR} zg?dG{Q1!c95`N1Wd6z(2CxJ<9rP%kTgRX0GK>_d2Riswn*asPk)TJr%VIpXFTa_Xo zC3F2hI;6<buQIStf%HmE=bS?td=QpbQZG$2QMk(^Ufks6R(!n1?LDDYPb}^MMiu`k> zB8RdS`D-G275RIhBHe=&`Nu*&>J;h8QRLrPP-O2yMgGf{uSEI(>w^Ln`+dgB@6#*) zhzV$vAG1jQk(sEK&o3GMqqfQKS0tZ59`TRfBfoz#_R2pdLw>A-YWc^GLyi0a#Nd~T z{=hW($Bzba2kn!8!WQ|1Eo_y4;&AZz#18pG;)?mQK>kTKQo!budgKqCE&t@fST27U zfq2I9PZ@w5P}Fb=8qV#h1)$IoV=*0-@=uFmgZ#L8@=qTKj*sO0$X5CB6l+j`u|&Q( zL%vlU!!uWi;^SK{z8|;@KWj#+y{-~Mq z&rFw}K;a2Y?JSB;r0c}(^3SG;v*~{H0{Q1~>>P?o>XBc3E?>?Yh6D1?A0j_x6sBR9 z{0p|oAG=2Wg@Zsr7uLxiw+@~1FG@zG{P6=Z6=m`-jzyLH)N=WkM6p5s1d5wLBbUmt#+vS{`6azM1K@nGwB%P6^Q^b{<sfF^drGRU@!W_n*!^T@Uf6Edake@pVtL5K15_FvxhfVTtqu`l5 zzI~qjS<}%b|BhMm^ND-s9Qm_pC=zCkg*<0?rwYW!Ni!?l_SAUJn#-?;-hx z)8yYbS$~*jP4ee+``|#N$}f$|e~3%>5c?kD+yWYWm}~!V zrTm5Y@*iP;@gt0FQHuOWW5AdeGmXb6>alwHOV-JMT*#5Xl%Pv@%70>w{AH|PMxjsU zf&!M0#a8)GQSeh`;QOPD2A*z~zha2|XKeW^xm~$W{upH|6l9E8R4Kg*TBgZQ6Q*yo+{cNWS2VkDN!ZyLh&Z(1V%OUC#mLB6E2FSp5W z?u$gEfgoQARJ^M{91v{R7WrRCQ3fvC*V{qSUmup=!c$fB{x3}Cmz8J+opmss4&rrm$p1A3-2atkI@7TkUGjg+N4xw3 z#o2t>EB|*Y{(YPLgH(Eu6MvBCk5>6zW%B>*k6!tQ2>us=4%0oa3H`qrd3RsT1Lyyl zg5@|Mzh@$7h^JovUoPdp#Qm3b|E>k+dy`PY_3tIfe}h2*|1Cq40zP&VXfl>#mxBKd zLnf*f^cjV93XT|w6=+lt6Ndt@_sAs71jqYMzzzjRC1R(7emw5CNx{*=Vg>z&sW|9A zR{^g?gJVk2t{^rYTNE6-2t5i0B|WoX5j?eCk9{;HktJF$Pr%PV0|MY*r9wgM#9U8Sm*!(4=7GP)tLs zg7`sTgvKN+K&JxJK^Zu2F##(V`xV$s#$JaW1>y0i1aX~}*sZ{wg|!O2{>TK=iEtZP zrl8m-sb2&F1wKl#LqRkVB<2G{!5MUW#$pAd`hYG+RiInJnM~|Vf}FV(?Fte|p1__2 zrf^m)SbJ7ASerNxT&A-fko@e8*vs`FO`_4;6`V5?1W8Ip4cK%p=g!T?76r+4nN0BH z|G$4g!FdEaFAEfNUZa9B+#f^G^C{x|%?eTmVjP%Q%3%c;WMC;e6^u>8P6ZccbNw%D zQ!vg#E~*qwtpmlfmX`oS(KFT$&q5azmAZ=@c`4Dd_e_lFbnKewjh?n+Bp> zL3V#cQ3m4OOzfLygMa@i$l*Z_!Ea&wxhWuME(vZW$gPzM^4Of0hrJ4TPdB)2y@Htz zNOpT1ICndHX2oC(n9QtFa0zDZR&WQ8`Gt0H2j4gObFo6foqdo9KK~uuNtbsvE0|59 z*%_ewyJC@w9SREARKRrZCdeGR<2~PC4#DTJ?j9ct70k^7lbPG3;NC&lqo9yjg%oh# zFf2yBf+7koYGv#;HYzA7CSb{EtX6RUNX*1`1@qFdM8N}e^Z<|N)7^X?&nNJM=^$9C z1(H8B0!-tfH3}ARITj>g0d^^PcnIbxSU3S)3Le?1U=hVGqOeCPzWC8~3KkQ5F+ms4 z0SO*U#RkyLlJVG#UImY{=5fB8A8%K%bPO65JVBvPbShXj4m9y3%{*DDVEIOLD|l)m z=(sEe6$+kCK%IgW#9G1M{{_#CQt-?)1uH#ND|ohE!KzFgRPY=@pDV>$^e8AVRPekF zI(UAQg4O*|rGQ_r2QSnrs31l~8TKl8(FaAZ*{R^A!65ls@~qvh;AJk!%QXttP2>8n zqsv!Hz^3(c6}%dUEebZ|DtK)mSXfDd*Aqc!8@c}mYu=cxVAD{nRPg39FqO?ISOJQE ziwV7zjm2PsRU@%Y!P~@nd!YjU7%8YO&Qb7A3?^VH)+yLRwOa}lyi4+Tr-MyfxqXk$ z-&=zg1vOK^{hBTX@6QCC*3xlptAYJ)rvfn*=nD%d8hQt(kKn9}xauK)IC z1s@kFs3&RtJnUBR$pUOs(7^GAYP2c%l*ONRD`;fIjafLX;4{wep!gkWU^1VNKnXS} z*f|KfXi)IQM63qIHgT+}QNfp03Ysgp{$Ir^_=*KzmE(YdT}hz2U2Oc?!whgaTIi%D zAG;NNGX^C3hOvIrp@2_M2d!%qd`kh}?oqItJ^TzA?Cw?YU6F!49N)tP_VD<7;(xzY z!QK>f_g=doQ`H3QnW9} z`p!Y4qDKuxxuX3z)~`*`qi3o(dh`KB`_Dy>qQ`LZn62P`YzjyiOQ2&1ArmAzmh}UY zkcV~HtLSm*=v8!JE_Nw;{CI3qbdZgCsKS0lPoUTnh%q=8IapS#=!yM6z!PU-t)fFx zQGs?vPl|$SPg;UnMTb(z&}?viD3drj4g@-RhoZwqVU?o%o+x?>@lIjgDT_e^!#&XC z@YRZ*%AQmI|CZ^DC}!aZ9*j7s=xN;NU5{uSoyBqT^huz@kv7(V4&yUW03OG8DQeJ> zQK_g&M<(}83NV`$wJ6l0>rh{CJhV(voBMV)suc|jQxz=^Z{iNgoCMS<>K39?QExgJ zWrSk^UD zolVZsoI59#>wgX#&e^GG(i}z49fgI8CKD{V0Gky(j}7Nd0h`7Q#9T$sPr`icQ#6Ig zDF+q3fZ!KU%mtN-jva$~MK2r-;$7IG=(sd215I4SL@!#Q=y?A7AJOrQ_+pY=%z;z` z1WIM|B~dIwjI6qStLzG>gu%7*AG* zqSr4{bQ&X?M$j8rc*7w@`57;IV^&6Rf^sgi%iriI&&~M zK69g@w=<#Jixiz@U?yr5y`wKOK;k>P6wUWA4O}ih&=Ae1m^+DmCx8DDy|WxtJ)1zY zSundn(YuBt9~5yH<1Cm665s8EN!(51yD4-|I;s`DhiTnYh8{)d#-R{xir&lld$%cC zI11#sZ!~E5z79o;D5|KK%8Qz~4m>E%MFn;%%Fl$+l7-l>=>0>%l5RxQGk|as;ktD4pNp4A!BuOJllBC62npsOrk|arzBuSFABuSFQ z_qls(e1E@hkDm8A=Q`K>x?b1yKKD8IIrj~^Fqc@*CIVCW>`D3Zd%!H93-cO4cc44- zis3NxKfkwp^OwOs`JT%Fs(!9Oz6DV*1m?^4Jc^%3!1Md%TS&5n!(cg_k?#e>EJ}e@ z^1bK*9a@ag#e-m{d@uC@k}pB%k_B)|zL%%KCi#}~{f}=cEqaB4uWXQyZ?}AWVD!B@ z0B~L&2bIf1^1U`jz5=GHfaC>>D{H2GHIzp@kL0``^1n5Wm7g zI3eE~UErX6MIC_O|M`jz%lD=M)3wZhUtLQ`qA=zfS?aZ@L?9L01|FY0FrOa2gZDq1Iqycn-H*xHgCfB zV|+dy4$I`*EEola@_iD+{C_f-6uS5QNAw`_~k$-mv1K`cg_N;{i+ubpNE&eUF6zD%&&XHTKRUTGyl8C z0E51Xhe3dVJ=s82-_pKs>BzSv+e?DI)8+fF9c-0v9|8Bxl<)hlz?kpp&VH)=AqvL9 zA^8rF@Bp!YB-X+1unKm|_mctm9||)!Kc~Sc`6^6cV8uT9e(42E;jny%2f!BjejNd{ z_DC0?ir*0T+bq~G-%&d8dm>Dg?^q|Gx<97Lcbv{0N9>>Y{+SCKdy*oI!VHlRQdO4I4$3)2|(a~dO(4ErxO5ir}xPBFBShg88*T(`Ob_7rtd6C&KAqB z7+5BMEfY4%A2kpvKUoREL zz#;kLhQMz5>vw@I@;4X^*cdEe=7=TwMG6+ip)fGW_U6$sA-)7OXbf>1(LKG2Ak#2HlSGkwl0*( zpTjbTmR{Hs3gvG{#qIXWe-Zu{A+&uKF#ci^T-+UI!7lkb(B%$@xg-Wid`UhqF6?7Y zd}CmR{Qe%WNq!?9=tv+A5M^e_Z?SG6#HK3_L0tm5lYpv&j1L}?-x~_2YTjn<;`ap&JSIm{aCmrdzPyQ=Mz;5|_bpqmC#n`K+0)_RCf&ownhvmO|8j!P38uQ<0 zIN;O=<=3#|n$7a}B}iZ9^jZ>Ly95r&-;XZzBS}95To(g~xNd{|{nKHR{MX}q{V3QX zKfeR@4=9)ah7`zyZSoIn4=it_m>UnuKd7htH-)2lp=~#@V=!|#m_#>^m48SQER_G2 zY&a?ZP!iorfLmD(8v^*>mI=dQh5W-=A6_c|?Y&^V{3F`IOxPs<|FVEsBZ)Z@+Z}Ph zbc{lHc+?{K?_}jp97lHog58x2C*&VPurVaOI|pXK0r`2v=D&wP_l%N1moDTI@ZNY} znTI`(gYUjnm@fY~!BqM0XRi6Q}|6nKP|G^^pC*m-1qx=sg!fZGs z|D-N}xJk!Q$)NCfD3^b73XFwq@;{8v!;|Hof{-Z)e`FAFa7~Sc2~Z*bqfFDI*rrj` zG;GuM%KunbSSJ7USmu8^l}z6%|KsE2pOFr;3{J{F8{elH^EB1Yi34KIIV}G(?0<%0=H|)&EW)21C4WAC`4fR|&I`|xe?C>r z-yr{UJzz1Ml79gKo~NqkXUo5EIFRgx8S*cpQ;QbJ|6&?Ufdcs#r$Toqk^d#eyhQMq zj>x}+%mdL-H7A{Bi@;&mu z#{So6|7$2N=m)g^_0jULz-Pr6`Bx@`4g2IT%!DoSzmWrF@)r%3|4qR{`B(J<3VCZf zkgJ$~|H;3)z5J_DTf!WzLD`x!@|Vt#|83g!_A>d)OknvAiPsWnZK3?{X25*;%h^}H zUHvZ%j`ek{uiGsD`{|GiRKA`rtzRTRA7uO=p!7g8^n+sge{2tvem=zb{~_i-JLNx3;?sxa z|91iq`wV~ozyHh%`Ol`pVi{@xqW)>|`1B@UK@WBd#Bm$*7+R_%s**hh;PjGbla>%4Ia_3TtH~*sx4S<78MO zBQYK($Y|0Y_R2`=4g_g>P)0IB`H*6ybb>`Pnh%EEGExV^P8k;rf?YDwMgU`4P)LhY zGSU~w;Fq>WxaA%h8Pnjjj8?foz|1t*A)|F)8CkK=3#h)$EE(B-WVFTBcCCyY0_1Fv zabYSDBPlnWOSG>;}SY?3Ay<5Aco&l*x*fV7%?yj z*2xHXP$t714SQr*6J^*vWH|WnrJ=$1g@()iAjyLpWqA07n1)b^jE+O$h>S}o$mm4t zJLLh@cc!zQx69~a0$sX{SeK20?O_>RX=hgwcT0p^prY=qcc=R947!}T<$FM*$6gs% zko1auGI}nTab*IGl+lan>9qnXWLz~}M(-?OO0FjE)l}VQiHvJvVX2J16xVmFjB6Qt zZJ~^QJpVWP(YEVm$mpL2B)@*Di~$V3AsVL17?=a7zHzRMK?J_3r;Nb@D!#b`upH6} z=;SROf$gC-?2&P6e<+eMjLL^CmvI|8Zkr=xI5CIgAHKb>jN1>$7(vVa7X>qAjC6q( z-$BqjPRkgD4!CBHxiao=2Wy#s9-$cH5%2(tA0YUI@jxpd91i8MSH?sFPdqB)A*!F04D$hD z+&vqUvjKq*_l6@frXcE(j)35)aZo7ZQP!sok?|Ovl>r!*1q(2FjjbUY;P? zOvKFGFXKtNF^j>oSU!~iRPhu7W)GFYb>4Uy-#NWtk&I`efr{pK0Jfi{@_ag(PtyDq zGUlZKt)EYh`Rt#6OvZCvWGsk*@Mam$Gx&Lo3kzkuFj~fLX;NLmO_HPVF2wf>JR&5yt!S*syZ6naW0g~*v$UT6to%P zpA3ZcGPZPriExbh{}k0*b6_SA;UF8Go*T!!l0v022R2tiKilojZyDNs|6O1h&dJ#lBO-|A%QfjgZrWnE%uJ zW&Ar{#+hiSTyBtYmX@BS17{B@pn(e1N{2i+ra;sX1!~8_Yz3mDV7vlch68nWC=jzy z0e);0sJlyn*f9#!8x4yUh)aY;3e;~0_%)cJK*PzfDy%^K1O*z+0(K-YpmBF#P+~HW zJdvP$c@${U1+X_cqCiqtC{dtk10Z43Eea(2V4DKXhC`VGDftRCZ?6EC;=lzSEL9*a z9uUm$g97Pk5I&_qOOj{cm@!>}R-@sJ0-00bgaWO5D3CQ$fi?>i$j*kf3bdt?97N_& z#f4dbpmx&~;2A)meGHT;aPdS1Iz&T9Sg61yh6281=nriB$m^$|u%BRl5*k!wY*!#a zfWTMo|8iwb)kSKuo4T~z=_6zFZkEI6RR)pW5>EKoq7*$P}k2d?P^i0q5lzI^`^=u6xC z671TJFbR$+&~Kaq*JUWsAE*AS6u3SGmI9RwmAB zQQb`xFqjzJ)&~Z!11i56aW^Aw2;CmCQ-NE!s}0Y*c{M1flga4W%w%~jww0uCPl z+ZDJy8zuvt7=bT;;wA7uDjb=wz#Tmm7!?K66}S^YcT(Y zak~|`-vrjj6Yqf@fWQgZCNTCv>=SJT9%=vxcxbHxllc1|1Cyqqf&tvPH#c%~yv zRA4S`o;yT=X9@W1Tm|y!UOtu06Hv(f4A`c?b9CsrLIoCZ+$>;>{4I_V^<<zhHsCG?9Kt$;h1>PoDSv=$`@D8nf=b!>>srcQ23Y518#+AK z1K|5U_V-7?d{_r(nE&+*`d|=H&4w%(2}N*Rfe#7(;VA_+4uu^GeAEtTixt=!3)6vEpRH10+d}65bChv?4}1{?IPGWv zEWeC~{;*tuot=O|U!i;#fxo5`U(<=*B>QG6AaYM%IHSO~#lZ4AVsbeT@I66b-xdYF zpQ6Bi8|dy2#QWix0zZZgUTE(@3%e){3j>$~R1jeKlz$uw6CctKy>G-CTuw@L)m6;I@{baT> zfrOcCXX4u$fvr!-%qo`ICcH{!c1I|a*)|(?$joUExv&s6%DgZE@VSt7w(BMHqBs~N zvwb|Qm3eU=*eA0CV=h6wF9#;UF`53QGL0@k5!_ju0sKvJSkqvI%rMXMO=kem0e85} zV46&i3b>}5AykKs$n1!qjwHBri_A{F0pHGxWp*KPms2t?qlm8fcP*6JjU?R=+PxD{ z<>eI6!+@k4J|PXuCIkC5vZ0Kx`fzkzr+%#=BB5FmOWac&$86fvkj z^FN3LH}wFV2D2P|M&`}EWe&-JqcU$<21jHLoiFp&(SU6j%VB$E-ZmLXGTa9w=Mrs> zVBZLioe>9Q^61?h*&b;79UM1z9G5w2j?6m~V3W+z;aOmatm!v z9|pxT`7wd{_->gqrpkPxyUdvbW%8uld=f$Yp46OmK;~0yKSj4@lVkP{nNRzGBIfY- zKbX(3@eHk-+W|}h) zK(Y_nxsmEOu9ErD2-qxhQznc8Y@1HW{FtggJ}UE*D4AQ@0qdX60DQMH4Ow;GQVWNmnUTI90CVrenr4tF+jjwbmVIS zeT{uL2iWckncw6B{(B|>Q}ZqMZx74dyH)0QjQtK#`zU1J5;!LF`woDh{mDSV`}fQI zf!}|aKP;7bfJzT+kojW{Y?OI$0349{(?nqKA(9*-_|LSsqCaes`AZ+zDf4g-m=5g! zm1IY{!$RP=_>H1|BmPl*kJ7#0N&Y)Aey4zA3uOKgj^Skz(30aQJYFjE&+#%(F!012 znSXVHWiolHZT=kvYvGj4Q`2Pr(*O{1dbG@cr@#@JXG}o!nSC8{oLCy5oTTu_@reRM;k~UJgu<6&DA^vg!|q z6S5jil+`dDmdT2rBa2J1)rf=%88BW};~3Z}E0H*fg}~qcY&97qt4WcpBp>9$Mp;dJ zzzSK(CM=QFtO4Mc5(Ogw_05OEDp{%crc&7jonVoyv@Dn_t3?dVhEuZAiPf^VtPFBw z6v%3Y$W|j|WyXYg!6|c-tk%nAWg(yqN!zTKmAzC}+ca4@v@2(^tP2gG%60=}U4)>E zHp*&G7cTAzRMUZgmw>N}EdOL##w=NZ!Lm%cWYKCn4~Xkzz%a;%@LpMNB20p1uv=D; zL_zG{Hd&z&uw7Qij!-J=Qp8+33RcPL)B$G7>P)4b`$G{Ru*+Ckmqh{oT@lyyu&i!~ z?siBPci7hD6nHt~dmtp-zYln z`t|@4T{~Y^KLTIJSbn@{^(XlCh`GK()&PPJKx7|;k2w<76aQu@fkW9j>x)ot*l|J--hkBld^74hpDng(CYss z0>z9(#K?g_LcZ;`?&uBJM^V(MJUAokPGa17g83hf%DbrUu8p$BQ2pH~9otvdJyT?H z_iN?$gM8Q~>s|vE%gQ5Q9u?fjxchuSMfa7+8rL3X0lxPm=Kj$@H^xtp^*{oU^Z~|A z=njZ`Fr3LtfvkxbCeo^h#=tIFljbrv*|0^{V(frOANfD^#=$G0T{*SK9&mmNx*BE#EHd zH4}EqDi|m0b%MT*Kz@E_ttgkZvJbE6I4tW8wu^eodXpHdVr9L>@~wrkiiusk zi}_zYQC3NNS!)PZ>dAV0q^zq)qNKCu1)aX!Fj1NZ;dhp8}A)<#;qaWQZpev~3>6Wg1X z%lbGAux}>#=2fyjq4H18$l5~iPgls=icl`K*0zPRKF7YDBs(bNOA>yGePk0`9U|+ybXX^A zAIbS671sBgWbMzF^}}dc2k6d$gR*{{A?x5kSwFe54iV!J0uLRL^)sR>Vu0WOS`}2p zQ)!DQ($-;?hdBm*W&VEMChJHq*eUBbg8X(!*3mqmJHI3DcY^=EPu8&>ut3%y*}&L8 z_R2aw3=sM!#r#Q8Cvs)|#rj``vQB0K#hg4Y>u>)37wZ)2PMwkU57qp0Qr2k`CIQ0! zO#_1c%bcB|OJ_)W=9H|n-C?tA8L(V7msh*iOxaOvM@^DlyB+M39X(feoeU_J9Ww~_ z%C6fLcF2y+lU*;I#LFVtae^Y*^)WU`gCnvV4uO-h`9+)EXf&LXoiJT?<7mJyF$Ma- zaoJ4}kyIeNX)ib+J9&rfX2r5oh}pbcb}BJ07$ZBapX?Uc(;L8I+2QoVvRih7?Xola z!dls_2-2!Vb|%7FlQ0Wgn?x7~rLwaboSiSbEuz~}RohLnbJAfL5cEO>UdY&XEZgNl zh3t!buuyjUSQrKCWM9npzczQSHkVGj!(7>yBmqvoI2a43W&2mjHrm5M*@0!U&B1U+ zwzXHbjYwy?YaR&&+a8UMB1fESNo*n`C%_)=p3~inp2Nd<}BH8)FWY5EY9%ASB1qytQxX*>DY(c*4 z=ZDH(2rn?`1rje>Ec-<&T0BBK7BdCK zRIoY|=+Np4*(FRvNs;U|Gh~-0!vH|++lkN%5L$-lcN%~R_`QSBwP`R*_PYr%6V}Nt zPi6kgsph>Xm<%UnucOU8D7N26)Owcd8T>&utdPB79MIwqnb(a3-*`~=N4c^$b(j5d zEELGzj1YdJWq&eW_LgWs(5IVZZ|x`hv%a#ob(Q@&{@aJj{(|p+>@SYV-Z4S;msI;D z)qS}H_Q~e5Y42pfPTo#m*)R~c%HBnwT_v)=?ho5#?OD2jrw1VO}Nz8!buFa-E!vVNfcE=eAC(ZE`YQV7oO*TW^+=#r`%_(1tPD zByZbaP7cW~i~$5*lq#ovFPHEZyPITcr?tBb9*wB z$QePR5ye1~|Fwr1a`?rRGm;p0Q0b^tpi86J&)2Zd=y*WDU1f5{kn3&=85;xJ-kcx$PhqNPce*C5vxmufIs&!7KrUSRHD=qqOtRlhh;&f@lRUSa?@h|bG#P$*~V0y(b?k+ZCW zoL5P*ob}f_$tma~=XJWUqLPKMMb64Zm;eXm6!wGJa^6UW>2iuPVH!}#o8)|RFY~|3 zhIMk@@_=PAPQ^uXR>uR*B{4vdH9cUzoKh+-Etm5)$=^OCr;P1)@Zl=wtnDd>E1dIg zzMOJImLHMBH=xcsDu178;6Bh$H)%l1dn|#2ak7vr^ z+f3&ZD*I%coGl35G7S#O`IPQ_x}|k$m46D3kMj z227Q+pMd)*fJdp$4+CMVoC7QmY?JfjXgDtC;37FcWx^RbhuFvOgq@#{%Bdj!FFgNq z4x{8(6ds9&rE-2F=x=#)jwV4bSON(7-2lWMGXar*xG+)9@fe`0KNB+GzKZ>yX4H zq087{v0QJf+|WL`9rwz;G!16T?UV##fFe2%0`_$o4JYJY#{RCUkRO)Y4I@85a=RUt z+nttopAARlUQVLR3EqQck1cYqpaWNw$n7~o?v-(Jdm*3~072iBj?hu3y*)I1MVh&9Matu8w_tsHx zM(!~7-PRk3A0Cd1+eyX`i`@Uk${m>?_l|72qhjRVnF0j76M>@(y!7(eRvumc1o_? zM@aSvQ!|zLQxPx~L5~iCgL0=$fRl2kcVPaf&zAdmceyhNIwN216C>b++?jOdNe_<6 zomC+BDTF*l>t=6}`!xPfQ^lMWa{2Poede&-xd`UVOZVA@z`p$cumG5zdD$=k*2rhtfRzL++${GEeBan9w}|9L3xKx1SuA%|3@noSRwf|2xK!?H z>?O%CU+$U&;P)SH=|s72XF;LdvJ_Y$_Z_N!r=Q%lu`ovNyDZ;DaXB4%FJA7tR6ywa zRJgteP}K+cZAgRtaz9)uccTkS=@9z`j9)mv+^2cGh$KlT@a6;}0#_$Bx{i^{W>Lg-L zmdX8lB=i6GcDbjh`qTlr+|RlEF4sLxz<&k9#pP{F1IXu40qV22pU`b?M( z+Z6Ptz);wrpuw3eXpD!$3I?d&?5Cg=4+OHPm?u#|hixZcL3aw^7wiKE6!fr#Is*0( zfRRC;*H;57*78v_XJ%f4&VVXcDwdIQ1xolx*Pe6GXRpQ`)g zbA1LZQE&iB2P{(X1{=03I4}n&=ElCTQ^7&R9EAO*S*wOGl^p;91USq zPDa4was?lzZ4d8PaLQH%ADN`!R2-&~_)*&RC_<+Z?6FR;QNii7lD}Xs_&D8|;VSq9 zop^#OXD(3i$p$c0!CBoEd@2g?=Lu=>X_&*lImHS-L;Shpng3@A@a!=K=Ow@j1?N-A zb3GJXfa(Pm3O+JfP#wJ_3cj*M!DX8i ze3dGfQ`Bo+6)YfW!A=EVr}Zml!C?hgBB1c3f^YEsZ?FiJZ^9}DyhX6$ECpA$Q?LXP z-17xXdnw41&0yIy1>d3dYxgQx96%1;0S#4)*V0{mXH%S;3w0uu;LU7Av?5kzaG{d`*ts{h9x7*tv(m zd)6!X?Gy#~jt46Eu3W)=sQo?@sD_`+1b>JE{C*_akF(%_f(Jb~t>8~<6+A=-4`KUx zpn?_Yuu8#S(qNf_hsVGP1%F+t;1Oc{HVlq4|3?QY_&WiAXWou2QSgsB3LY<0@XsM| zNWl{>5a7f~1^;5uUv%YU9FXKB!T#<5r3#)Jui!r<`v==;1NJERFM`fYRq!l@oXv;* z^3((7%d6D^%EI!Z8o)T%BCqxUD3%wU40Gl2q|&Q1LS9TXbb)p9>ZZbWd9lOg)yss# z^5V+n)$ap4P7y@|yRPmukXhc^9mZmxjFsq^AH$(h=B_@hyA85;!3*qa%=~ z6+W#fuoZ>1+9EGAUS2qpM44;lwax|xv_2*;t2-(yX%W;O zC(r36&y9kU@`Bhs97DPCIu4O{DRjyLs_I-0$K-V}U^<+Vci9x616|W$k-Toi?6yf> z_t9`#-sLOg_2?(>3c+M~J-aafJ-5iaa)i8IG4ihJ1gP!ZU*6SukT0)K8Zf92Ex)Ec zES1+c8Ax_*G$8ug;nsd0&eUA;kmqQ zm&acQ;SECRO|P~PxDdAH}v8_^EP@jv2>%z-oV?x5&VqnZCZQ9s&*J@W3_EpH5!jY0jG z6+p1NvjIV4>CV_`P$BOg65X?0UM~A`H_E$rn7q6OK&<=lyN`;;CBSrf_b0(NdE*Db zPI-L2=S}D<@4zN!5Mj< z(Ecsu@;=39D}lCdk;m1~+cq4w%ln+E*&YM4<$b{zZqd9iqk*ws^@KU{c46DK7>>#N zItvcT+g%{b+fow(X@&-{JEeo!XZU!{CU#?^%A2=>5Hc z@jr|J><94uF#}5E9i$TnvHdhr-XTODIwH44C&}@>^8V}rh4M}$!z3WVUwwe&Cu4#5Cy&be zd%C<+-C?u5e|o__d8g@E_}?gb|L&J}hCyctcy^OQDpjaf0&G+$sz9OIagYmz3Ps1j zAXuhQon)8@n-z*NVUa?01#=aOWjhxCdhsw6_9+xMQla`8uv{S?c7__{D;#PVt5CyT z3dK)Ss8LVYqfo*Sg&L>AL4^`0DAXhk4k(n=AC4&06!=|esM$D$Qo1YDycg_MC^ZXu z!zy6=f=;kqp)>=wDAdA*a)r2BhSHZR)N)jqm;DN55I6%7t%kuNg)*lo)Vc?dGz(E# z2x&7!p=>&lji9zv-gdk~IZ;4oE^Gkn6>3MN7g6y=MGA2r7`m7)T)aV{4g-O49a!g{ zFvQg`s8DFMLLJj# zt3sDfQm7MxI}x{YS0LtP0+!uac3-E^lwQOIDy&q!wEl}t>wy$G< zfBgIN?|+A`$8i00g$B^Z8?vEPp@E3Hk@nn3fwhL3S7_u~h3;VgsGiLKoqd4dqdUSf zs8HxGg55O>4l6VU`S%?3Oz;gr>J-~$!Bv6Jlz%8KWC&u&mi`hoeIq*`LpRj;QXmT zd*`iFX#Qx0o+J5lCly+NxaaA>!dPJc3$*?P;x5|G{J)6W#f1vJv`V2R1bZ2!FB5oa z7nlRP6nX_AuTb?eR4*F@41RTqLd&U^hn1lM>;+{Cz1~xy6?9?6e1%qyRj810g%t|D zfq&6NVEs*UtV#t0ty<6gztu;fVg?nLD71PqY*DBLg=je;EtmD8p7`U9fZA#~kFh2HO^(E1#hpwI`yV2h%3m88{LqczPg zs@o_lDzDwVoNMY%s$4F=rfwO_HN*SXU0bhn0tTSIZ;t7j;_g>P&YTSvT$PEyvRyoxbEYDX0^z+RipHsYKtE+Sbfn6 zW8)GVcZq7(q0YQ|#r4{Dtd~)@R&;dZm?jCeYc;FYq|U{S8#k&~->uywIy;LfP0k+H zQEgO=>WSl{b<@^XYo~49hwmPlc-x!v#uV2JL?vG}@8#lpS;59QBsa62W-)b})omM7 zx2^4Dx4j}O{epT)^)fmnT$J58?$Xv7SqoaUY~x0EYG)-_ZJRe~p4BRSUi9jEM%W2h zt>1~er1KqjM&;FNl+w0#y9RY@4U0=nXqIrLbHfd_1`OENxL$Pq8ya7dRVS%Va!R|D zhDo8$q3GM2BqVZBG(7u@PUy3!?2;uMUomgYQsvw=ME-GY!@9K6y5_HlH4*D?+Vr2b zY#`Q)&eHzFI-;#L<{Kl<4G4VfT;MhHrFmP{)JuzxX;kZyTFnf{cIwiVd1vlUXg055 zX}#ER+`O0KQyOM3j;YruIXXEes!o*C#Mkg5t7-k(NptbH zuwz7FT%`2MA|s`T-{ydZHotJkx3b9N+Wwz!*`S&T z75ccwdQ*+{kMpd9YvLY@#0^)b%PxGeGHKb(k)16GU!hNG0u8AS^mA2b3px4~Q_82c zYAL^0_WRA{mDWn}SSw{8iwKO!URCAW80&A^Tod<}O5bp`Gv>E{iij9m6Znrxg>U|W zwYn>1w?_8Go&8xSE0ex4GcsaW#F{|(U-WrR_}kc5M{&HdIMO(K@iADP)Rp7DZR@&D za?xKzjF3lWP-Z&+p zd1CN}8xk+6-#Bqdi#o}58eVIsCuCk&b(H*EdA5qmE{YsG#eCSLYn7egOo){?I>+Rl z5o`R}pQ=s`)ko^+YU|Ho4m4U^xH#f`SGBo9kz=RYdCY&#)xME~{mshrNM*W%^q?}r zOOXh7N33y(I8k{3ztJRO9b0YvSwB}wTgen4?H;V*$`C(Qhv2M>IrqE}HO?hFYeM{a zF2q}p)%gB;&RX(SjqlNjmFZHOqL=Khv4$h>{;Kdr+p5h!RUOM!;a6W# z;~Y6NuZ)B!zPiR*6(J&}?A{t{RgG1}d{kro>0CcbA}7u;@9>|i4wxztt1D`pe~UO* zhR9yY0mIbfRqs2*gjG&WBz)O@H6#8w&pNKg8o5%fAt_ld|rqW zPBt83l>+~cI9G)zt+rMkVl|UcHonI9NTqLN5@J@R*H|OHtG1qN|M3y$DSn{Z`6o`Z zmDN>An{Zx;%5+sVKUiZuZvy#qOaBS}sq#puKF&F6=I!2BuUFXX+dZRW!)`5`Tp!yz z;fibQ?E1YE?rq+-k>y2oye6vi=P@y@nlxz~->h>+tFgD6HgdLEI`b3TIdDRtHo%kzH@#hIH`hT8Xi+`?(K*{0%-Vv+$^jzJb^G|6zYOF`j zy?fRysbwwqICx_~WcFv*q>Ee_s}97K=T4Q!BO)RZO0TPl5VhZcX^A z#zw-w?NwVVXSe3;T6q4Pb6KxC#LAwn@r_*UtF8GpajVYHkrE5nQh#K|D&MvLy}4q_ z=G8b?iKv={`8C$_=CSO#nsn!Uvx}HU`nsTI#QBr?e2um8zKHrOo0Yxl$HDU-jmR)cf|i%hb7RZcG`U zsH%O>3;cGg8tZv);F5EnSI+Y-TTaLMr+Yt>t| z>J(E@W3Bp#9Qm+U`e~$huh;CWx+X@V^Q*clDL-}RW8Jwo&q{{W=U%k({qQ;W3fXEW z{zk~ieb!aFOJkK+`4FNmcUo;*bN3Lz3e5X0JMQvuv!vFS)QUWHsCxEL_1e0b)4EZu zX3j%N(HDp6U(mQ^%T@z(eC^w{;J@pd)W0UKnQ1m_c3+C)q@)Z?Y|$dEc1|FsQDP%E zFE%c@e&e`=gj&rS)^6OiUc2T|iJ7sj6QdhlP``Qo%xh9w)k$m^{dr7uYU3txac%0= zj_-fLy!YD1U29)>PgC z)s)dRDbUR5RBK*`oVW|FZWJFMTPvwnY@=2!60>XfY}m3@?QXXXPfSnDY;sjht*F@4 zn3Sa2QE_qCUZ2>cZcZCYA}?d!=>Fsfe}V+25+}u__@tV%;W< z6XP4zOB&m_euGBM8(%rNaf3#UQWLM-us)@BliG0?)opn}X0v+Ty41C@lj`^UW=cw{ zl)4@42gfzbYEkzR=b_4C8%7?Noy)!|qN&mU;p;sB77j>q>u(lxOp$VSFKiEvZMsk14)47J<@pGXc!-Q4~)rA^Et^Q!YS|48L z@9$R|o8v2QfImN0cK4Wl8#cFPdrsP^xbZh?+8dLX)w}injs5B#ulwTe_EfQ$_4HPp z?MB_axx&A%Uu`x!LSQFpn!W!lG~$~B>Y)F+*XB)s|A5+&cFdHbgja4?cML>hU2Amz z`T;dxN@wF<*6`9RbD4%!v9^uztdf94Lr>Q8`claj&$U}L*Rb^l&q-5dMQhF8kJXyx z$TbhD{wpSo=D|9lvQzMz0L7ri-U$uRL|u|QWYxfOLzwW3CzSTCHb132lW$jD z#k0+{k=D;YY0bJlb*5_i53f?sJjvSYXl#SdTVGW09zla5PmP(sfW0 zcq^xc9XdaBG37X@YkLa#q}&i>a6QO$YzafMyK7wzm6JeEl@rg`tn=>~ zR)g60#JqQ9+(`^{By>Gxc}dgqx35ui zU59K<>xizZvtwS$RTVRqt&5wEy&~FmBYN7##{Kc65m)@u8udVFtrAZq!3r@g^eAR) z$}o(9y-ib>b3u(kbVfLb2O-r0Dci~spe;@vpf@0D13o73d0IYGj?EF8BD;mKY8pGO za%^7EK3rn=HFiD1Wgd!5NUT!G!)T@Ik#KN_JYpFJNtmwjxQfL+i4KryNB<~=pwU*K zy9ve4^Dv6W93~n8Euph{5QhApRN>M41dAuYt3c?^EG9T6F~Qf+$jm5QUCr(aMU#wY zRKek4n7w_s%Hi08KpzKzp)}@DnB@r%%0{sj%QpES%*i)zQ->n!hWzWcsT(`k^)IwocbD^3nQ47o;xJbyxcLZBy&p z6DEvQ;h3(}N5kQW=YTm5>yfAjq9#-Qom!zKK7>6a?7el;=4##fm|N zEYXVvKp((#`#4Mipy+QBnkEzsYaBdvNE!VFp_op5goefSh*s7TY7>gh^j<>Y843R& z@C6R@xAPR}TLimA48>K5#j)E6)vCoIF$e*_z#>N|3|HgtLKL3?iuIR_f%e-06lev9 z?+~gJ?L8O*Lk%DgbvMDXc7pi30Tx1HVEnxkqTeSp0o4c!@dH9JrAg@2-hja1Jxc(C zY~|ks;0(w{d4h@GB3fBTEU=LtAzIeY4+%{|UwVY5PmKPE(B+!ALvTP^&~O+=gWjXL@4TxXYDYJghqK-&^~e;?IVPmrDeo_BiLJpK1wLoky1RfO~`l@ z*n<$LQYvoZ`~aE;-I3RyIRt2NoU6zq0&3-P^dvJmN&yN+*nwE?my+C%6Z_@0d4f>% z_q@D@?3H6r3ITCoho+f<&L;(QfHtU)zY{HnKVZ6;0(=>*mZCwopg2qH~+|(DSjxae5spMLp4*djX%j;m5 z;YjGz(0QSYLYIYJA9}yvGSiUv2fbA>|BpAShf5<7s7renRizn{44LLIK(GOTp|y@@ z*W0i_(cIkD9_}d?bte-wJT0q+8`~qzEv>Ddx2@i^?KW$_mPp4k`M9MzRwAD$cxudy z71HSf{JBt1XCsM3EMi9z8B4S44V(X-v?D1O0L#DqUFvWtX%D|K7jYt~?5dkj%cb*Y zS*BK(2#2F}@dhItNgr-HC!N+nec@P3yf+pJN9+q*QlN*r7oUJGf#cDAq=$b}t1d^u zFP(t)-x4i76JYTl2?x;^B^oFWg3%K&UI?EMjhqmTBHFXO9t$xdW~UE*Z(;#^N=m9?Qg5uJSkCqOK^O^)2tV&AO_7=(_9P0V0Gt zUVIc5hVO*zLMwEuo={(C6_n`TJ~8C^pSeYCF;@@OM?A&bw>|y-d`H~B>lVlYG}@|h zT33D84IA%roHNs&=UMQ7s3DJLIN|s@)li{&osj4wp^r;6DbWW4 zXiB2@RZ&RhE~pZIEc7MGLX+s@p)X5Rm*~?0)R5?tp|46>Q=$)sz9CU?Sx`Bw##5nD zjAXeUX$e0X`l2MZCHioHg(K0&LSLxTPJ@;t;~EE7qJIl;@T%w`Fru$iSuA7-PLYad zz~G>^pdTL*CcIDqR&7*s@2Gc{OzAnhpuhUxaKKmDBbm5p|EjVgAVB&Yui4h zv(C%c4G!mbw-vR#5p(L-D8rek)_h%Sr&ciO@2he#wE@b*B*jydOoky1q^N`6NQh&n zyCJ;_lhs+uNUUr3Z~mq2`ZJ$Z`%87j*}ug zL*=}lsM(b)_ND5rxI2JWj~(%;(9c48$O+|*P~y;;|I)XvwXoBCn)N41P( zI?~=1cVF)~ohfjXPNJnfx@x_)Zf*Al?O+=<)&MjGEPHNMA0E%f^i%q5<+mwIkXwJ2JH!*6S^>Tc?cpE zNL&wR6iohPJQzR1uqEiG&#M=ex{9%s9*>5dh>>>;r^_fhZ80rrDeWubUfR)(*eWA! zYk4nTw=!0La-*fT$F`-Ew0Ubfwjv3mf|YGfHo0Ef$eX!jyvefRA7^V?wNvT46xc|l zAJ`O+z#Kek$D1QC8l+dPs5hH>Mq+j{p=th=Ur>8Xburjvv^~{e88#-gT^p>%CfkMl zZ~3icH3hQL44_Zo_OH1Wgn}moT1Igm_dP`MnMA=Q3)%)kF*EZl<$Z>TLnE()I0R!@ z%P@p^zyiHGEy;}S0pjA+pYfGRh_2unhJ%!~5pOgTimB6QX;Q&_S_s9U`B{u}W^blK zTM5O;0Ac`e9kUl84102su?MY4$(-3U3FX>gWcC#1N6W*+0%j+I_F+VscA;IMD8%i6 z65nxlqIFapRB`qUbWR!*%9*zy>=Z zM2Fp^&zKWK?IexnYOLF$KJLmLx47_Oco?UJn1zY*n& z)D=rr`G%BA)E4F*A!VAU5?F=5;T}Puu}y*)w{?OIG75Jwuo9jEg-LXjvmf4z&p@k5 zoiB6=+J{09(y{`J#xq3k#iu~v5*REqo`v}f=sW`rPbsI_vrx3zuMmoE>F<;sf&-0=G(Y>Z35IzoG>3r6{2zx++L zr34PDxTXtSh1gXHQFYhatDRoQ>Fqu0cD0x*Rx0TZMS*roI4kpZ5&}$eebcq2S#Y?{n*5al}+x95aiRX1m6Y_kFC$3|xd$wKQ%g zGU16eLPHO8s*Y>2uu{}oY^D>9=L=d9cQftyA0(N|ct@5yeQNSHd)l6UD*g zlez{9@x5x}O~AwhP)^K3J;uL^^}uGKCdI5Oc6@5mg?@ozJ{b~zj8?#*!1#k{V5oA) z|NJ-Afzp~TT52r9SktwZd^hY?gn11#%krjZOF~n`oWZ;u&LsOc=!2Vk*J%fBH_~m| zDK&hy>+V0xa4*={lWo&s{L?bfB(a#M6ijH5yAq14dwM+#mHB43%`zc9Zl`xOp!7OO zMB5R0(`tDw%DFHQ@^iPTr|zRk$(nPT@pKzAT-|5#7s zprTpnw4Qh3Els*(oa~oxQ*-r=Fnh4Yn#SKlcYsm4Fso|@)7o*$;uwHV{y=q(O z`b@HCt3JG;bE|gH@dmA)$T_fRxU9qmSi*E;nZCUJibNu7d1iA%)~#>xx-Cm>b@u=6 zG%bB}b$g~!wbE`XjRTafDtQegpay;vy0UEGx$jb=p=Sr*2Q%i%Br%JNb-TKbxGS2iwzZ1gYNo&wC`xxe@2q!oUS8|zxHQw=-qa9l z)Gvi;fup7mpPkgs8|ka>F|&HX&7s28v|{RN6rF-nIJ`aWWFxtpvpNrROKl*Pb~Gmq zOPshZ-v8$h)k6bE-M%KP1IlZ*5@7D$aNfD*Qup%9PjPQ>PrkJGh8y-BQj(`{=qhyR zX+3B7Z~Bp1zY;Wo-HNvce46Tv<8lnb3tDrbI=9g-YO*f>pGzs-zij(SPH9d1B~G{F zbax-|RJ*~aknj);1b*wVF+xA^?=HA1J)De_GbwGkN zE>#B{98-b;z%zI+kr!gGZ&AM zn<99U{c5c^M!zOR6MK}!h!&Ls!@hv3!I%FdM8iGo7_^X6oV!b)7_?yJ2t9XlLBc!` z5Ipm4fnq>~<2C!$)d5s0bCS~r*uN-C%d}HU$p7TM>b33>x7cj}kp8pb|;5dQS1vT^TZlD53x=sj<=Rq*x%vD)WCZwW6nq7|*s)H=KBY3f&67NmxL8EcB3n;30K$xXFLdLonQn(rBK{N?8~Yr9A<% zLn2?a*ET6xu=d*nl40*gQ!0*soQm8b-gpvvfgBJPIPmcxDTn*Ys#B1KU6K(;jNB41 z9!LSD^BAjkQ0&cB0gK~ck`Pw&8~`U~ysL(6d-{`-52I{Wm-7@1oAfE_2n@f8h&eul z?lJm7s?Bb}0*yj?!s%B_#+;#N4%~{^UeDB`t?5$*t=RTV+Ab*zFvEj7A~82PNj!JA zBn}wD+$&{qHOE_=CevBiOV9A=8Z{;#a#`31!Y9R8^fNK)tcA?)$&h1!>6l;X(i#(# z(_Pc$+qY?61olqd^McxV`c*cZitFn;>JDeE;*erD;qVvF_1D?E?L<#BmrrM$Iz4Aa zo9+5G*bP>1yux$L4Yrdt-Ne9}c;iuLr{nD0IdV!$J$1M((`c&xj(?~fC8N1KRESM!NH;2Mf9kInA+4N$n@g8gs7lTtJJnr_rgs{|kDy?-a8Ax1L=WHkLOJ>;1`GJ<9Te<^Q)6`YGm<^+z$$}>0#Ts8WFr_ap+`c- z#Yx}*E(2!BzN2V+!=8Vxq7C(RKuF!0N;Rb#)kafm)tVs2jdbMFPLHD}cRIy*+%s%T zYyM;;2$#d2Lwz2&>OSI1o~HK6%>&99FctB3*-nBqj{KW0i&bh71TFu{$_onrr{a% z8fpt{H*{;v4#o&TG0ugJj#X~s-Sh_3ot{<00W7Jy0~phhC7L>mf&6SiT+PK0@d6Yg z3JvN4o(VJ%z$}3E3Y3Rz5^&}%0>vYbH7%TvXbHRz))Y-2+o>pO3eF4|$;D>s7R8Ai z)#*ZdhRr~c#c|rZNEC(H5#-4N+$GSe10{40byGt3hd~&|RRoVg8L|$N#lpoBrCmTm z^_K|ruOLdbb0`boK7a%Ic3OmF0mqM#@R!jutk`p?i~U@)$_zb(Q9%^_C=CiE(83k) zECmx&8dXDHC?9GFwZQJTwR9E|8f`WQ#~*VEe|3j;%3+?Oz9n6ZCEZllsgaXDusX71 zWik$jzTwXN*;OI`<}R%ve4StJ(ysCk z{ywdpd^Hz)rq|tKKz7Bc=VAv6&c(^q8#fLOtzJEH^T_b<;NZrMeev}jO--v>4&l#h zlPyh6?Rw5lc}7#JrM=0&wp-g=ig|XpHC?}Bjbj^y7TECQRONe=+J>xotU6P`sbbBu zLZC)1_83nUMP05Ib-{&aq{X@v$G<^Mb&YJQr`UEVwKl_JClBBb7i3CO389VixXwdI(+1=)Xz0myvMGQTC+E(d$k~%ejFXZHXCkjDg!? zAH?8g4E_#Y?^VQNIm+SBa`ZJPqJmB&1q&rns8qfaXiMTCD_lBUefUVNy+%2F$J-YIg-(2cXmKwm4K5X*3T0Jc8$5;G zij}hP7Qq(>P-zDu1qQSwW+8~LmqJkXl+j63pg8=4s}LxUR9u|Y_Vi-7x7ZT+K z9V)Ff`di5&Q0E2kc)~JRVj>WS8X`65}y7@SW= zYkX?}26s8e=-UD)^j3jZ>ny?^ZjLF={Gfgwr8<*-$}>VVq{R+M)tLL0KtZ&CbC}iy zj03(@+YGxtmKa*UvJC{Tit>&cqnbNexIv)8Jh@OYrDHx29Ei%4-YJO1ej--h++=_S z5HpK;2>V2%@D5z;K*h1&!>_V%BRxa*6v`9(>ipqdk{dS=zNd_S5z!Jkt$-I9h4G6D z5eX^krNvng6AF2^c!s$g*%Kx>W2RLz147s{i8e(oU90bV1P8oi0d-uW7Fl?2pipE? ztvC8RiITA}I{myrAL9s&(MrwvQ7(s3F)>)~%5D-Ipnq`tQ*mRhvN)P9hyeNYz1#!P%?AW=t+i>8Tobtm-mkT{-wjQu`#A+`s4W#L1z z&;X-_n+1BDfYUHK3EQDqh$}PwJy|F_ankm}ErimBQJPS59Tz?Y8)mVU!(A?>9*wAExJbiGcZ+&R1|JrwIL;h{2u8-~J zyHjYDvus$IPJLxdA=ST98?JkU`w{mT+)8^5Y_40QZ0qUWpbmAyHi>L{b%?rw{Fz(B{PiLBjx4b{-1W`z>1H< zok^B$$1+MZ9Nnbuu-00`J+8WS)%j{igVUT^6U*f6)$zR~ zr$KG(Hk)&%W&7v6U)xjaSZ%Ip-L0Q$Wwh&dt!%Mc`;4KMje2*++u%ASbJ>`k%Q_L*K$MCpjYkuSqg%|h2{pMz8?fPYAM9?#CGZ3s zT?3clKX8jSSTa(MVTFyP7IotF$y^*x$X*h=I%4a|cp`eCqpqx9=bQycSTkliGf;Pt zX*64(K782i?Iks|TN%S{bKs>$Ct^0n2ke9e+m+xvSxN(g&s!b3hb5g>z6Fc1kwqG{tqISD&T3u%K=@ znWndxrtQI%YW~KLXlIt{S89W;E9|V=oNs^$e>!WnTkV}*4%lwJyy*5cdyQI?<7P4z zTfmyQ?R(1myvbxc1pB4%=9QTza*5$ zSeR-~K;Q$Sfo~U`g-1vg(Mk>F>q20@05jVt(U!1hk=d445sS}B7L?E~n}mK|qSPH2 zorjo96vrrY_<}@P2tvht7fWN5g-{`G5G*(pCE{6*?v@dYok}&7MWgVOMhgzu2%Y6v zp+-x^&3{6cR@D!buZ&*23??DQZzdGGHCnD0+CN9x{MkhNZ%7;{_ zQ5L?$Xo-3chET-5GKB2Mcgt6(Xl|OBZ%dR?hN2P9mMFI_3BSNTiR}Zjz!hk1SR>@{ z7_~w<4&aORuW26i0sG+(grGZAgWq9O57?$I^t3yNVGC% zdk_Vyfo&tg#2^w5+U~@kP~8)MK)F#M@r>J;7)>lnl;ccl`T1`M6t{eWUT0s592UMN zX?gXDXeUa%scM;o6CCp*%6))~TOnBlT6g?20d!e2F4U}qb;W#sAO!?ISbu`2mbW=e z_+809P{+9{;|F*y!5ro|62>+s>mCDpIj+qELQGaivJwXARk@YPS{(z#qcHdh^#_EQ zxj>>kiy(n#UM*4XVua2;enRvlUf`tgrPfxsrAHLUNibqDTb3w{r=T@5yt%g!ibt|2 zx}AU_2-2@lQJYsSp=zqcXOgEb2HXdSjaw4tH+H{d&ode#uDng6yxfIoalA;c@BW5Wg!8B9XnmV!R;?P$lU@CD!n)c-3A2uhHWK;jzm)40oH z{6*sMBT36kSI`fD?;Vma_l7<|i?jf+;}8{t zt)Nf5Qx-}+dJfqulk6$@XeU}2^n2*wP$g(wKBNI_Yvbpa5MKB({6-*0(hpZ1{TQ}$ zgKT(L);he{9R3gY^JH`RyYBjF?$g-|v|K8--F@56!NHx`glPdgyrP4)0#-=Og`ZF% zF1VOSDN<~tGJYyh+)D`pl^HGFMX(KPfI;HB3FYHSK+KFQ>%|4M>Ou{b-f%lZx z7=!8oMGf*22hmPLb%_?&cZrtk&mjl*Qd!(M*8}(0^PGiPOz_x-wW~3%9_9MEVE>mY zd#w5R?*;ncaRUAip)mi)H7zOk7atp>s;}WWyYBRltV3e5lN(vUNJic;F(k}erc*gLtcQoTa5^BQr3*>kNhx-V1 zstqPknBJm&;QT)*F?T#PJTfsBZT?N7IP?J^gkW($p=i2HOCidfM<_Nc=~B@Cg;4B{ z6u1aYJizl`+=UcW=3hl|q5}zHj$Ba_LB`-O$<76>DDGjvWYX#+~g=3#om!IVV~c1 ziNQ9#?Y(Qfccf!VEE88tUTa! z+m&tgY1b-rY#7RJ7(RGV+t=?VbB3ALZBv6St6^7(nnnL>d-ax5BHITiPjZ>pc%H5A z!xJV(3jQ&|dQ!>6VXGEiJ%@Lt(7a;~<_nOxsZRKD&m;_Hl`9dQ{v1^eyE~5~I#rFQ zdCO5AZS@yRXtFq8;=Fy;|BK^lbIjuJg7NXRPs!paY7n$fpAdb9P#z9J;A0>eO#5!+ z`85tV95t{I&&uLxbwr5v+zA;!FKL6-cp-p7ae@O!U_7`%FD0}|S@JZWM;!JYzSIEF zjNP)ru7iJ8G}*6kG3RU_fc<;AIdaZ9FV8z`3K3}JXgD2BrtM@ZvJx)ci{!6=<7!Pa zqUqS`k2sB`cDJ$7ZEy5C_9yF2BMk?)O;bHNy=}t==PmHAW-Ik+O?&ILeWtr*^N2lc zCZkR)i8nXkT}ph+78(G|{9mLOMCmYc8H^s#e+v{d7Z7;~^gmS;uYRk+FIM3oit!N@ zhy75}E`}xgOaP5Y^pOA>Js}zcw8WPc{EdiV_zNkEaY-DIcu|pPka0qyfjTeplz_AL zHGxx-miUgr6bbH{TI5s)5;J2KLX*UFe4>nKu?#Uf2nhY=IDG`48T5=N4G>n(zk%L> zNy7i*jXlbuB_84xH6Sg^wFptzaj(=9BU_@heOJ&ru*lYSrG8R)wSU!>dW)v;wHvSw zyVk$?O1-0$NEX^6?WtH)*`@6?Hfvpt`rusyEkn87P)j=;Yf~a_F4gO#t<@Pb=HjAj zuCOs@JN<^8&Vd*?8l#}I!6)HS11^bC0B1zkA_vl(FqZjPqfhJ40syS9=?w7|#6}T`d>4m?5+ogm@Uk`^x8$w(@mCQDB~{kieCn z6WRpZX8wd6geJ0*mKIhn=Guv?^W~|2fpTEWn!tLH+D4BE7GjIC5G^MeKZ6H&nYMgG z$iMUD`nk9b5bHuK9pVMBO+Z;{7$>>NaTrO7R@6n%g2Mn*rutZ+i_lzv;t)fJqbgfa z8;f9$#8)IWh%clwu}cSvWBF#TClo~^bqK}{gmMTB^+pB5ZXBMpNr9QeVk6Pw-VU;s zF&azC9HS#GeM+Y!isMNvvycj%0JVZ>_Iuu2P#hF z(b2wX{|eL2eM9htt@<>g3C7J5rqPelG4AWqz8RgoNYYXqAc|WlP?4-*wEQ-S@&%S8 z+9Y+z5^X7=O?`sJULtm=9@1Rq-!4!QN{D*!{1Uqpb-gi|e?vRBh~mVmz7Ti?q1bd3 z9SGW;tpsNQrY<7jHpzmf;bYLk;1(bzhDZ!I82=4ovKH7azb>>lbgF;d+x0@Y=)d>v z`udWh^(GAq7Ms807%22noO%vJsydeF<5*~+3*)77KXpTEH$dR z--ZiLn&4LBx^J6Py>&hCw`RCrs%iIW4eh75^~F!kzB;yH5N?)g@83{VUme?s|Gl<; zJ**L5r6dnF;kG(#DB8&?l)s0nA}x!1Q<+CM+}}!>o61(wTszU?)p0d_7AOCQo^{YO zK3j(N#k%g4ilW@cSc%$pdYvc~bIUNKaC4P8bs&!COBO}Rf{ua+Gk7PVcs_xW1fsYu zLUG>90#dsyb`y%BIBcr}*=GJ5BreXq1LdAz4}q7r)#{`y_RuRhf(F^MjquD1>&Wcc zsPSrPHW5On?jsJ^y&0v7S4!+;azISQk?Sdry@yoT8+ZjA8`pH22Xkz;Ku?1&0j*VX5bGT{uY;pM5J)oP#eO1QuB?F*2{C$sM7ejc_jsj5pQ{x&@n(VIo;WGQ z;vk_V_7hTwQTBIfnBoR{(&8#2=EzqndKwBPcZYLXMA?=&bU{VatX0^@Wkk#4D@+%t z$(6;S62WM}%!1W6OepT#rrL?|fCpn#anJ_VNFjnMewjcqo|KiF0t*uYS8V{wSLQDw z7!Br0vi>1#ee7aHO!wkfYd68Q!+OxniA@)C*Wk$3O7@gs;F_0r5t@abL9vN2Qtok- z9;nr(H~}nx(R#^LDIrTzp9vv8wDzkKrx3g4){1p!2OQu{~{5~ z$<-{x1t7#GLODByt&0w%I0d4?v(5Aj?I9q1H z?N5G9)Tyd^aWN#RtT=xwu^*9Q@s*F{JNR7H;x<_{b0dSC`81(81rC_>%pHW5@UUdf zO%x}tr$V;VLo`czHv2H4I|O@ddude1*vE0$2YM3P+bL+#uRsP^Xdy}{HUROR`ERH{ zVn7kN;^q+w7WJoKZ$0mm|bW1u*xrHRK0MZfYi9>fy;e_yrEcs&QW z1yPTa1u-%P7J_)cKyi?QNJF3pU}Eyt@9X`BHwm z!v`<--~2hFN%Y5PtDG5t_0?mI^Q3+zB_Ng=W`4;++!kc%veG1mmtd^iAvEs%~s=Zj5B& z-tOFPH{Rl20m*}|eQhmjN2j(u+0v8fY~Ht`W%vsE^VM{EP27sc!V!Ploq8wE4rJs~ zfr%sp+Z+jU_W*(UGZV9qP+KA`h3iZ^`g+;!)Kh04Lk^V(2$im*{4J{2X(vGUDC5D= z8G0iVKl^UI z^CDs-`VupmdXtoeG%wh5ti?HXTm+g8)CN)VQFGvvxsK3EEiXPAy5CR#T;JSue`g(h zr*yBo(>=o-a^R9b?;N-*PIoT$bVu|2tADQdqP9^hyhun|nE)pZC`Q1l)_` z->jBFZ(#qCs#!clZ=g%!^?(bIcH%mzFlq^|(fp&57M%lZts&yY!xAR%!01@5*#gBK zN{WA3vzgdo|<_2o9(s=`FyNi8FmNUdc83}v_o6hI#B5A&tB#K^tXClX_ad^dB@(6&1Q#f z3)7`I=+XR(3xiKXqgGJ_t_thu4HD(;HiS;|*o`efnnUY_b+O+H+UgD(qMZWok5d5F z29B|)4Q1*~BIc|npv0m$4JiZ2(dOtGM^~~Dr#>!FF|iV6`6@z}ai4x$fCHFixkQ?q zVeh)Em(Be(z!-^%cKQlJmo>>W51NSbh8?Qo%tKNLj_0Y0^E@%f0fnakLgJT7+1Qe( zxN&d;|5yFxpGn3<%etH2Drp0@xX76fyxW0e_AjF@7mpIH7y_j-kJTcoK+jq{gNSWN z@Ka360?+ci1hq!NE~AUwfT(y5fvC>2FDDLoeIlK|nd?U(Di72orchP%sk4X}7pv$3 zm&x5E@!9YjrS1*uM38Fk4_)Ek{bZ)#|NeO$K0Y&C>>AGculOkJoWAZm>DaBW>xq@% zm`P$-e)6Q6WWG|3JF%vYIP8RuWeq3g<_)JI-=Un5vz)s6HpRi`TjQ?N8iP&a>Q`Q> z!r-ZPCTzXwbk%g>DNh7tpA%B+v&6DxBGUGeG^$AF#Z$+XiH&tKMa_C;-4(zWYh_MOU^T0^qi z)8SdJ!R%~_*|BJArx_EF8Ad{l*QfY_8nvOzIsMr{QAHsH!xkV~MzBD|*t>93@@S-IxwZ(f{*6ch9kx0Co$< z(d%CP#N>$cw)DS-{sDKE9SB_lU*@_VhH*Ic53Qcd;74v?JkP<|fdmZb0K@WI1D+v( zlQduibcY!&M8LSe4$j$h`8Qo$=<&z?sk|Cmc5v2sV0BMmnXX;F?tU zR8R)4hw@TcBWyKB(mE_dK{(>X({|Vn*C~b`HzI~&MA~Dfo{d4PCAO&v%Te_xEJj6J zW4h+Xz!YK~iseBNvuTHR(%!@=u~;nZMxmJ^>pvE?;wvs}i>az-HD;5pX~i27eK|9g zR`u{oOEc1j+G<<5URdTkyUA6QWULqm*v>0yw>jRZMQvq3jVEGlaUDLz12zR{t}AI4 zy6|gZwl}=qcH(Oewt*|bK1ktH8A4ca1m}<=CYXA; zA->~03}8kDIN&a2mJM7LNsI$kjq&A@MF1VYU!noV6Q7hEf}-(|fr<}`cBP;#9bb>D zB;z29mq@g#Hq_eH5)Ftpxk;c89H*aaByEr{3_2J}p=?3;T0wlgXfKm20>v%XTze4Z zAWEoMXt+2Ip#X^e3dtScfKZCn&lwyQMwx&OEb>6UT)dhNto(hK^?WnFhO^GAnZDIISR6bItS^crRXJ)eD{;jK5yQ^34>N8a@ zY210hUAY}jG#2a~+x$IkMiYLb0pl-<%4VOYlHVn&AZ9nyryx3;CwWi__xty@8y$&C zB%K|I4fx>>gFYn1Q$F0gM$_c7Mr;tJ+bjgi%=z+NNjv!)NlQygOgqQsiiuqkPjL7M z(aMXgMu7_Uw~l?4U`(gdeFdY?FJLmk^TTDL%`gjOOyMCju0T_PHV~!x8=*x!qyw$- zZupHuh>ly(?tmN8yI^C-Dp*LM4Qz>F|G{;J;SaAcikq(3A3Hs6WsA{nOF5$NIsNK$ zcEk@TcD_B@nT#jn2lSD>i9h{mzunYhbY5KS&^n8EthRevU^7dH|FIQDQ+4)SlR+u_ zC_|_LB)cCz$LPWqx<**WSW*4hWMUXLl!%Yr!LEY#930+*gT^qn=kRa%-?+yv2eAP} z2kY9KXFje}qIScI*a|zIoG@?w>;+B9gsJP9=2W`W=jss4WQO1C*qZGmy+rqSx8x6O zf=f=FgPZGK9@pY}ESpRYCp;ycT6gHs4%LpEibjTnjs;RRto+eEQlY%3k?dmp^Rl66 zQ##YmTZBTctpqF}2Q(3fI*Tb`nVk;C*VIw?6>)$BTbx^wTO5XG5=Ervn6~^+Stxz` zjnT#T5Q_6W+6^=cE)T94nNU#OGZi5b=I(H{{60j*m6LJ-ff$KR{}mp1kAJ4jgG&d2 zYV$ARGtHhKtMmlz_axz`VGfP$&xvJJBuZ2iiRJgoI&gQOR~By)?6H%8b0WPdb9WM& zrYsN$PkfZc_W`#E<+ET@A=vL4uwT3xA2*x8ya5tH!x{e}xQ4C75u%I}+q)pyl{n3f zzb3!tHJaV)?apW8h4_%y=QX&->J}qYG#Z>0A5FbF{@C2OZMcG?O%4)iD7LIP<=y6X54+|&SHq zmYGzI^=f~7v)Y|XC(U$Qy#<%-+wIu|M3fGE0Vbnmlo&+zustfJnGH8M@pa7|M^b4s z9Wyp*L-FleuX;BL)@&Q80t>M*T4-!6=KAno8}sn>4z&X;dMF29&x9TcfAR0zWaPO` zFr$Xqlnyk6$_}C7$FK%4J^weNi|;2Cv-3Rr{Np|}J|qYZyIuFT^(E;u}c7C#T~ z!k_>sY#~Dq&%oCh95@(X2>B0fHTKlCMo!t$|1S7b&>Q=;{&(x!uTfudR6p<@f8U5P zj56Z>eK*NSY5)xt!965pjai4-ty{Tc5+5Z)Rw`DTAbBd7=w5?JP$k_$7^CeQqXN zxdt(MGL%-Te$Npj9hzU4{|AMbxmOaiOG9F%@>4=psQkk;STij|MIZz+{u`piSQ#A? zw75iy13S@{Pm(Cb93=sBaUA17yD%zT5!RVMuSFy6=p&@1TV$b>*Wp4Z1_gWUSF+H> z56eP1#3ND0%Yc?Rc;Pxe3yh1Coy7hl@)b%^jncF7llW}n#lSOJ$c!P->eh2AWbvZ{ zMS);-mMb=PQI!MkuR@PO?8pCw?5C;|k7^iCjOIZsP%>(W{U*IJgGmKt@m6|7|Ahvc2me+nF2>f=>0+4nEHXF4KSYOuEhdM ztxWw~(Bj?>i7tLjpzvu?4%t{)|0Rj3$B|Z)nac^4i!@`;foMe>U7(Jzf)uPHzk%P0 zC$u_ABkt2rOWImfa!qLpOi1KU4IVInJ*HGm+4R}GzNA6w4V~R)%a!>(a9{KC2+Nx;8 zVV+|I_MzA$gjP07zHo>JVG^SJLxPt}40V*609v65j(CtUn+q{6S0{N`XP^~z{(>xy zOC#F3>k!4um#S??@FKS}Dw7%i4D~biqh%~m;37Mma)O>=*LsL5L;9F(U-kxZA3p%^ z;Z-)o9WIWEuM2Gn?euSarO|`e^HyGIbo5@WhTprfp-n63aK)qvqpz{M(4{we10%`! z#vO@6r+amoST+&vhI{b+7hh>Kmzwiw*K@R#?H)avk2=vvYNOJc({fEMElNA=g`q)= zj+b)-h~wp~X$WwHp1!n9WU6EZ0tgsF$4~T(6)+B4r?81olN_qDCTrFcQ2Wd-$%uRl zH~l!rRxA*XFl?+}AzHlnhjt*0-;P=zpGSel4+%c-MM>J~LX3*zx8Xzl7Nw{)rIfkW z)X7GUq9(88$TgByTwG`fRmaeNdb4%eSt&nWjHYziKDsWuaR z0mkU(S-l(sU>~BTLT-vhj?r0&Ds6H61uC>=1*})9(qw7l3E58wxvOI$i89CSUmGb& zY_X)ph1NPQHyGQM)a!h$FVY(J=qDga0n<`eEY>E_<}*i6B*Y9W8fgPsTAUKJ$18eT zR1UXHfIddiArwciC4J1$GrB=5VB?dgoT$*bUrIDk=*)!@4TxRt5vbVoCE6TxCCoZd zTKRUUKft)E#f!Tz;=v#U>Lye;vROiB-XK}9^X5kRoc_x_wY;uEG0m80a6X{ zwYcW+5Nrp>Z+7~h`>fGhq6LSmEXzowQ@R!L;2NAn(RPfeVY;227dvr7kKkuFwR~Rh z$Q!D;SxeXnHK8>cjh5l+PNG;$Hd}D*RxDRt9ef%YDbkHU1Fc!P_J|rk{*0D z#W;2S8m)J&eaR(<-n>2zU*v>gCu^%EbuWC&(}>o$Bw7o%c6fHKZ=}$aw6(AUt0F}^ z9!cc)$5-U_#ssWG6{;U>4A}SLH>HBvl1agf-;yYgy^P)_P?6FT{e8Ye60;=}apfV2 za_~m>Jymm-+;X3WW+=`+0l&)ZhFGXrmi zWYKu=sP*@tBiS!Gu$D;RX&5VHheZGPS-SaVkt0U5WIF%G?q&LVB9;s6j7~i#SI%@sSRK(1V?4)utDJNsxO*{2_o4|>AUCp`vjIBG~d39;WtlQo(Pho7J-3Y$~mv zV)g`mM#KMfi^ zSc_nB7ZGD|klCr4#n|g4V_u0sO%x{}ngIK{JMe~#Da5BG`1lp*881|YF!H}8EgyFk zwE@2&dPKlCEZ!s0K)5F_Ma75_i=$pS^C6-|-BBpOx|`cbD0T>*U~`86tzc=T>WTsj zvG{XY9D5>C^!UpKS}l&ynJXo2pta|@I2@Zf9^t^|9i%0gP>U*7-vIq9i~mb7PmZdw zkWxO2P&Tnz>)tC_@Z|9`#P}D4vfBWCP|b_;|3%Mg0a9R4n13mAFOhGodd%uZ@?R}0 zgzCHaYeF$c$BWKS0sCSF>I+r)jd(+RL}w0s6SOq|kCVc2g4dvsi= z1#YL`1&9#^mQdX91zHV)KLcoWTk0RgGm%Q-h80&npeVI)qB#C%&_A&IBfXL$6G3z0 zqN|=1Z?FZllEOkJZlX8-bfQ9LKqJt;$H!&ja{R?V(=%)$3I>HMD%~$(T4NA4%kBlG z4Yu-MBzumvsAn%$^D`m`$Mqe*fM}6Jpw;JC#{u5}vuvpUUqk+no-x`YO=17hXN)zQ z8sc?H9X87}7@f&T=XPUTTf#XjdL+Ipx+{8e^g!5)44El6X->Ere564=HCm5?7I^m=TGlj`@gV%-*Mw<b$h&WjlHu!!5?LS<``4CA-W2&vQnufCq12fS!fL zhN^@tV9vk&1vl&OdfupO_y8Q$-nl~uX-qGkObBr-Loq$gfBo}D<5@fM9f^S^)usYn zEe*Gi!dH>I;Bae)=BCw5s`~@5GT@r180_a@_u`v=)dROeJyvC(cgaKPy*cz+km z-hX201FG}<#TSf*u0eNjaOD3-eQ*P2{DYeZ{r|jR)Rj)GMW=8&=&@5rJtli@N;@gf zsybK7&p_Q7Ijx;vOd$g&@Y`?Kr|1MB*eWXCyWg!PT?IBQ38>9F5J*7b^yRo>@s@KC$U7(-5o`k4QT?JZB zBU-La4j!T;IH47GcgUERRGGuGBpODil-cFd7CF8xq06Ke9VKTye-{y>f;cY={$A!~ z`Q88&jnOrNan%b_ann3lAqV!2L@V;WC>qavh<0`u3Ke5EXPpVI{79f;GCT)i3fRQ+ zgyMZPObhQqj{B_wtrkb8MON7xo%r z!HUKiETM}p3em(ab_YB%{&Sr3l8(iPCC~4Xf2X0>&H!PREFN@=Jj_OZ`k&B#VIE&%9Tn?AwULQk+n% zM!-{u#Zo@>417zX1uv%-R3szX{LP{)aSvanXmM;5(LkZI8Ob5g+GS3X@CpuU>915; z5@<^S*D-b)!oqc+@&b)AHBF3{+rT2BSVy8IZzz_SmQwT#$G(8~o92GcD2P(x786%r zF00LwDhuTy>JKQOxa4xVKE{M7!r8JYykllblcG?^<1&}P?P2}J!dBIImJC6R#v@Hi zUhgQz2*JV>h1MJ@bVr^UE?FnyBfkBKWm2wB)PE^W#8{_*#m>B6qQTaz&UKC(@x&Qm zYzhiicwkzx101k2Y-bLkNXs9OW#ZY3BphVnN*27{&MZ8MK40rKb896B>Pte1no$N! zb^5DFTN0@z>|)$)vczJ!*<2%w3k*ZkmjzHc;7oszP^=wAek{cNIzfvI15ldKL@6hT z#nE6QF5)LRA+#t+_Q440Oo@^{gxGn(NBDM8^EyF`+(G?as8pD%wf8lP}D2Yihft(7DK}eun=GK!5wVW z2d0ZC+EN3tSdKP|;>2pS6*DsYMb8 z1YBw*RK_==Q0R^@Qt*g}&uFX{8f=q%0zGW0)GpzGZ7y|4_5r_E0T%=Qag?4dQ>08{ zp&q5umpTO>?AG^In_>yH2@OPp7RCRg_D|JcrXcOVU1v{!vtJ zkWdza`~msAIc@}G5$yA;Byk`hpZ$_#9B724AxRr(`7v^1OT+RRxiR*9Gyh8{WXOa4 zR#{pt+1D&zDW4l)A>Gf?ngA+WckCt9x>dhY-7tltMsX5G4H&?yE)KjR**`01`dgA6 zI}3V+KZCkd_1-8fs8e|F^-@@>nNTu>mC(I#p2(;0h|fICuEFRwiZ?`LI9Ec~t1NAj zi~>rXn~`XsLzeF+6pfB(+34o}aUxoAc`WL<76TUN--cz*-;8gd{T~+~E^QW!#5g7D zv_-NHTF6$31_$gBLiroE&{*?0!BCdA5h^q1GteJk4poBIqQOE)$i(;=BS3MxC&@hp zCIk`#BT}qDXI?}U83Bs=ATylC2y=^eDc?Escs=;&ixIN*92xUD>>yFG;Bu5ua!C&>^;A0-qc z4iar@FY-mPK?vkaQPd2CG7>GJzogD3Y6q{7#G<&_?*~M~+A(8@1|$n+4CW~~>?001 z#V6W%h&o{l+t-fgi-rRJqfjDPx?I8HWI|aJ;ErZy1477=az3bdUK9c$#=e8JkEdm{0_h{5MaN4+b@{i*VfChB!62VD^HM71 z9D$0%JTvDJ3a$=lvB5aJA&KTXIejOG*9mA>2+(0H0AkLhLYtRALo9$1u|Vrb^j1KN z`~jw=^Z(z^#=(=qCJkIb0zU{_HM-%L{c1RA4kuKGGX@+nHzRoFY9zdNjeqTvX1o9E zCrrH*Hw`#`r^I3%v8HrWzE^pj8_zfCjk!$0D5~{YJzuY{YSUW=&-`5Orn?;1Z0%0r zCRoMsR=~sdI#*99u;}8XvxT%#$ZGI$G0inype&xT6Apnb7ibsk9wCuPqGV8{?fd_a zG}n2tLaW|fug}w_u;@gOheUQywItdn{{6-?i+4=oA1D(E?7<02A?K7cogAgRJ_^g-x;xphn zfU&3ss1-!3om(T?Ne;acrDIP>djZtrDkA0soLDKPibigMg_wLdT)5L4h7Cy_RwU|0QVFxSnDK83TWk5;1MY!Bc1 z*8b8taW{7H6^YAVn!I7qX-{X|liVZey_)GZS)KXyyR~h56GtvQ3I2T3jhnWUPTM)x zbEw;I^2@JfcwMwM0xX zDuOQ(LP>!IqWl%!Ge`w5lZ-hP!j1HrkiS1=o!-(laoUk1SDvJ$opiJ<73pv6N;Nd? zzWnsl54!JzKXLz?DXS1EcKW}De_tyk47A`Jhq^ds`Mt~N+L4S;1d8K%WNpRVj z%3yP(o9bFDDS7mHLRkw~U(&)D#H1uP4tmgag%;R~amapNT6}T5p2~zCgx$dfuk{$+ zWX+0eB}O>X@^`Yiy2<`IM`gIVuFRsnUKYpc22`y0uypzF^bF%04nz2jHxTN+ z2nW4*Frk{u@1&!bs=9|_xto;5o)M*|GqxPhG9u;7*kDti8+01W2E>}Pne!9t(IouUHeF@X&fqY!HUr8fx7<>rGY5#p+X&@B3lm}^fELTYqGxXxd~lq{{?4MjL!e^5 z!l=mZ0UuF+W;74OD#&?*eC6+WuOPo7K3~ogxSHv`Q_zacr=aBl1;>55Qe2qnjYP|1 zAbM96zNI+dMuomhJQR~(?lMd74*8#4XIxd`mJns%lbyAZiU;>&;Z{d&cz#0TsdPD;m7-+Fe8)zTY#Ki zs0lVY<9!AFaDrg7-12-z2x4>G4F;#qRk)dGQ#jGyHW2ndG-4e**wvjZ?%AE(x}z~~ z)~E7XR`ZmO&XqaOD`q$2&l<}%b~#F_aLp#YE&U$&(zxD0|68G%^=kdq>C=8{cJL5&Sv2DnBh8-@mK;KWrjXxk*R68!AOYfJAUm%}CsJLkj zXYk|@Dl{rhai7BC$h~uEOCLZErBELF_hWb|`Ts}PcK|qARr{xY={=d=c4sy-+k1s= zEU@$r3er2MNYiJ1AK1GnO6;Nt*2zqg$s}2wnXCm12>2BBS+M|0?_hiUf2ZW;n{oat zuJX(I&OPVcbI(1uH}=x# zS~FrF{|#|3?K7wx3D_*K-@rUaCB*v5@B$VyHzDQv^$Cfi>`zCw`BJ@+q%RwZhW$Ij z`(m3D>sFkbJ@35J_KvKJY#YgrY>KU2y7%;Rr&9x?UH%N7wCEaG(~CPw;HQ~%PZYO$ z?cP0-NyYK6EUeDPw6|sDR9ys9<=wuhcGaof7$T=%kZ02K(l5Hfv zV%t;}%w|Y&)n75Kq{h_BH-T?{65S%W-^{;}D7@Q%L|Fj`{gJ}Dge($P;W3y2mD^tb z2L}u`7T$PTV!2Ica2GXS)Q+}9T}t5#~58u|E1%VR(FIIcaWLnjZM0(2BJiG zOonDW`6ViIT=Gqu7|8)ot{c4oK-i;4)JnhI6w2BNT9&#Y9eMRu3|+ooMbU7t=z0}G zov8#FAfS5<$ua@&WlwAE)etxN1b*{sjopiwxXpEF;iM%;=AxrqS$X`=W~;E~+{8M2 zMP_78d}Gh1&Uk7xw0GCKfjyZ$eVJ{YsXcpYUHFskOZTU8>11b5VxW89kz=^vtS=T% z1qUMOzO%c#JGzE8aCjzN9L6sH#OT8JEQl_Z4U7gr%X<_Wb*ja@+>$VJi+(~EVNv=) zwU#d}BRyvds;9m$C1#vFS-+rLtSG{9JqxF z)bv#bZR?bugeWNF3+A_WWmz+~GPpTJ{ayE@ozJix)_tw;9H5lUcQM6xAK8Y)EV7%Z65# zk(onXIz=k3Z7ZvRp<3bm|4N);-#fIUdpO*C*t+4wKt9{EHoC5ReP3*N@UY-jocVC|D2xqZTK9$BrIb&;klBsxi5VvD?CojLn9ZU9l(|FA$8jE7*JDWrM zR`Eo=h=|%4hs5e3Fr979NFbtAmDl-7H;_f^<3>e!3|q``hf&etw&e>9>IlqJ2}Y@d zXV;meiHz1pElh)Pbi$QOQVCR<*34HKCjaN?C+;sJp_#9lLZm%%wM{h`mA(uqewD=> z|C^z8cg1?X+h(aaXwfHOi=4)-(#bMetuI+jp zqOks{=wRW0-ERV|dl2yG95@U*kGm7dcHY#RPbyQwL$?y%BI|EFOg-o&((w-{7* zjj`WU-(*l}V?>*uIF?o7fQfhs{pH0@{-pR1^NEbM8Li){=)6>JM%odDzczbo=78bg zh<-EQGN>b>)mO>%(1N%+^KC=SBXiMQ&$LoQcerp7b5gM2j1kb$#v0=EG{Yx_a{U*f zIjryt{%%(0sEH}ZUuhRLNfYLnDYXkOP|}2%rV*~)^z)^a%a@5 z48(1UvIO!~wrK@H7>vGe7&$7rah;;A^*{F5nLAyDZxs{QA9>OA^kAa*ic>zA8(X%- z?@4>ZorfHn&1}eQ`qt|7^5tWZ{&@DJ*G9kEWTmHme^PAvrz@>NwWwXjU^nG_*1=#SKX7 z`bYX3Jy9U(BGONbPPf87(pXsS#kzG~C^7`5lKw`P9IFloj{mBw{I8rT1r-`Gx~SzX+etk(`6 z`O2qRAnDksaj}@fjUH!*usyf(+FZJ7D8DgNm~#%{!8@SR@*WhLV|RcRUfoeb}DRiexkyJ1vz6q{G8j zC?2-HfW;kmB$~|i4F-~tc)Y{L$__5XOeA}|@MTX09QqeU=cOw%4>HPIl&O(UvhPiPl+|s`&B+&7 z1`xl04S!-rRT98;3+BV75Qzk0&A%Gd5!Pq^rhL?F+B-hNC}m3Ae%hJRg%}@v*f4Sg z+3LT|C*oZwu62xi=28A_ez#@KJO&Cs>V`GLwD2I_u07XvQQ_B}J!6G?{*nmw^G%?9 zi#$y%(L4p8o$U<45Q)?5JCkUb>VEaHS;Y0 zDBepFo=G*&K5WfA$G=g~7FT33>o>UyA9*Ix=YGvC_w}qPeDIrn!NQ-ON%RL2Zo1ZI zYvG!+6S-qg-j_N2@a^08@6YVtzir#T{(OG>==t>5R}mryf@>1l#39E7Pdl=IC_LEf zr@%T&akW3*nOQbJtR@N4Kniad`=YUAXV+4s7zvq^r2#=J3$kXOS7kLr zWo50I7mNuw$cYJb`|*paCDkf*)}p28PF~Mxhb?1@(rcNRYJ-=tv^HK^jK$2~O`+{} zXwCe?plwmCnSUC-Jp53;|1x}Ssb&fW`6}W&YEIfLxeLcA{RF8d;@JYnsJ^W1b>F7a zwo$$ww1L5d%Od8?;USHE>{|A$TVe9qmbvqF8f@~C5f>$8)P&NQJfq?4h~S)E`7nlg2JAYIP4%M7ZS#i`DpDU4i08SQ$g_#(dP4w zG6yP6)`3cw5wYq_vxF>!Yy~#jspreKVMWPdodU<^i#B9!lbc}a`QGD?w-Imqr_6*z>XoLiXCPMyxQ9Z-YabK+>5CC)Ky>aP3>hb3hB z9<@JlG}Cr4Exi)REULQ*rQe|4LNz)WmCcC=!u1gcUkbl3^7SmWB@Py(heP67m3M z+)=~#DN3|h`Bb!DIgn4Z(E&!u1)XXP;^8?h&H+yy#>5fecf+RrY$l$n>XkSw6tI=j5Gj@sjYB_HvVcZZp;wN zPD^G{mQYSYNS4S#9FS1H0G%i>hXtrEA`xoy^t9+vd!r%dccTTZNaLmr0g?~>0ixw6 z#z{ch>#O$QN|7%n&M~M%z}hNEx|d#cW`Bvxp2jo%aF%43RZ028!|qEQ^t@>VB7 zb*mGhG8z^RA2S@DrEB_?gLDxxIL4%A!+wCYnR=NDrTHOdPj86OkIZQ5i;A}NXS5;x zFcr$HRYE(v+z2Si0MqK5ylo3ITD_cT=``&_GK?GlHVHAiLRpaGp-l+m=@w(ngA_f| z6!#Jf%*r$dna@h|8@E;YLZ5LJ3!-f`Y^qJGA&ra7@*-{L5#svm3=19t3p(*0gZ`b8 z=>`>Q%}VWdfy48(NwfG@MprW}^+Q}W73hpU>tRNDmW5B#%IWs91Smr+=Fa zE=#AimQl(Jd5f#ku49z;YDtW^j*1&cs6@5O!6sN#Y1cD~+f=kSRK^<^HfJLRt-hbp z7^;e+@A%JHJBR|tB(Q|_CkZW|sc0+MGdjCzKCPCv*=Cp~#~l*SZc*aLWFe=thl|_F zXax6}vJfTC2xqq~VEito-Oi|34H2+(DWk}#SzzrhfTDb>c$s0$(Lfm=Vo-KOMGs{( z$bAbOsB$GSCiY+~qS0*rBV620Mrp$_&o54e7~Q3`B*Z;97bCJwzLR0vJ1N?U#Jd@# z;LHxBEcP%;37OpKcmpw>Cj{zQ1>VcFG#|(9poHQvvbu!6n7ml#ylLAp&*ABh7^c9Z zrCXzfRD74~kpwWBk~4{>`%EEhD;4u_M%%)FmiQ_M+V+6~6Z9{?k6s6 zA&?OQm4yW=j?t-KEk>(nh~!WYszy0TjF8bInrrnu;!D4QZ;aMFUU2;!JsEm7=)9ld zg)Vj!qjKsAj8(g%=hLbsj$u?9CIuL_r53kPv}0w>!#p})`;bGng~Z31;<)?J+C5&2 zqst82IwOx@tC8aYt-)SKXHgE8V1Y59IZ+uav6}59MzgJkS^FH@=VV6BT2}E`dVfDSL~o_>wK<3c{)Rt^B4{?L4GOXFJ=R` zDY34U2=OdhDl9?mKoYMm(B{jX^hGgkzHl1)e1meVQ$80M)M5UjxH8`+#_X9mUz0kQ zwk_^RVMh#mAu}SB`#V9)-!km6JGE6|vll5!iMjEQ*^3n=yE4l9&mtN_(*?CVi$d8G zG`Cl6q_djuHLd;U7S}lf_EUnBOGBruxrM_VSOjl$ZEE5>yOyGw=)|} zg;PB{WBIXgtjpK!bGsA%B`bof-9y;nHQkLr>5+6d_UVm|X4C1;^vKA_z7<&73l9vZ zd$U8yus7rNhL+;u>`WYw8%Kh6ODzs(Qd2UuIg4hlD$7|PR!e_F-^{;hav=ts4w)}F z%N4YA2czO_sKo|3n>CAJ2sp%SBt8z8pZK$3!Hys->h~BH{G7b9c#WZz$K*&rYxcE< zmQM;WZN9XWf4jDG{Z&0>}Xd{=jP5#g@l13wizTTib7FF(V zP*l@pK`SpymLeS@T8*$Tex9ZOFY}4qM9%eJyq$l0qbY=w%OV%8+m{y&E0Yo=(wMa{uc=&S(GuunhDv~OmV*zuE!B6uBLAk6+2<)ha^YOCPgW*p(%=vQ)u*BHGGm+tLCGOQYCO_s{sPdAMNa8 z`i0$keTy}3e4Am)^|%sBvmnk-{DVKaSwCs*aS~g7T+w;L&wfHt${CGHv$q&j`+3%X zw!pB&1DHkRY@Ex5e$xCV^A+q!WrR2AApN>D=Jt1K`mMgpd?(wM_`5ptX?%yUq1JlR zWm6s_ntaB#~0odL<{P#;gBIA7(^Gk|8j5YDLX>T)yu)$T#FB?>*hsg@oEV=>KpZXJr{?gaeu;RsbZdPqq zj!Y&Ai?1jPIZ)e*1z%OPb^KBE|C&K%2QQ;lY1p_~-szpz?AHxr+|r=!JzqghL+)?z zZ{pARO$=EY<=>Xv>&};7;?K+a8znk$T5aPH z33VxLpk)(lcFI9ZLpG_1s^Q*N6^H*;FTx@}9p^%2Y$GOb_o6lPg{+5?Cf01-pbon< zI{CMT7EQuKOttbct&zS44~0c~(W%u*=Nl(8qn3qeM@ZX0&`2Rbd&toqzUHUb@NW{> zsPmHjuVRszcKqkotwu%E;|4-I*V^%6PEm4LgVy<1V15+cdOn3*oD8EX*6kBL2@F zRAO4TZrA#QEcbW#H=6(AcC8uIckx@05BS5|@$kqXUVh!>dWGwJ*BhV_Wf&%t7)2g| zLwFqH)94HohH@!i;mGeMlii*8itbOxy-FC!k1MxsI zhJj2b81M#y9{1W6q25p?(77QK3WaGi4FOd!5 z36o$Z?hOR+;mXKhs58{HF%yi0Vxb6_CXzXPh)F*Mu( zo}S?T=(c1kI{NWtfoKNXrZWj#Yo7?=-5^is(Bzt2GCF!fF4m7*o&4!Y8oMBqE9AQ) zqB}CF=&DaH3r6r=G?7m9qUA7v-f><^V4IgObRnf;OZ z#4*B|{js5?>kl+^G^8n!nA|3XyKEPny-UB~#Ff8*TUoPr8%DB8R;=(7Q>g4zAbs;1 zm+vuOID&tDqkcg%8xXz@u`!gx=zyD@+E^^qxKL=Rqi~w@UM1Gko!WBmQ^wRr+wtnB zhSt#66IS+jo>lb4qqQ~r9HU$Tm(rBIR>1RyST>7@;a*@= z*4@yat7$IoMZ-c~S(c9fCHjpwVo?WrvFl3L)vnjO-t4-eaO*j#)rH;1rK}Y=Z9CxJ z7+9Letx!A`$2)kNWy>V}&4c#Nz?Q9})-C6zM$Zbz(0j$BkyHqq8UyKEE}QK>vNxB* zQ(V1~NH&%T3~lf(TNVwv`{Uk}-|KTH`a<139(0M}R4N-ucv4v_8jf^CyR)%K7j>Vp za3T_o#tZK_H??xrP{^0cc7`&W@MKF^n@9tFaaF5ZCE=BJlOD zH@V*C!n_C)jA2Yy=nTbZC+#53(aV85-L!#}6NtgWgBPSWx&wu-3sbA?u0%TJP5a#6 z>Dsd{8&5}~aVrtQnYu3Ax0p)aGB7X@i$``1z=(tKV9;+_@t`LjiFz!{-|2G)&+~U+ zhbXiQ#)Ix)06zo*xj<-WD1|R7H&5Kx;Z6BG!S7}}x9!-uKD=Vb8SzATRXCPRgg1uQ ztcVUT|9oG3$F6nZHCwLThPQ5VAzy!dSz_0&)v0iDB(W?W?~E7zd|_%`|LLwXUFR8( zcnfQb%xE}A&+8PPeowByaQsE7k>qX={YZpIpps#fJlNe}5nv*mmKC zsc^U_oQxhfl3B83H0||emW+;OR;*Z-vC=C>3Y#uZEwOz)-X*?{!M9(N%Pqq{9UZUr z_I3nTkNU;}Bfe!H+ZM--6n%X=!r^2x5rvy1mMz0yxkPeP4qD6BE9~KzjB!K%mudby z2SR7HvFwf^254o;f@nD?&i%mZSx^IOnj#GpKM7(aq&i_+f` ztt=ccT6s6qQbPC)Z7(TNo3dvA&v4*SS;b%#k=fqHu*ha^1tSTK#25{z#4j5b9O8vV z*~e%QYDj8?oCDid-bCzcuQq&Mpu5Z}YYCmhUuwH2<;v<;5ZpM^u;-3lbeseuwg+WX z6n$C8TGFuKw{VKgq4qUKNg0mGTAbT(;0^r3p(L4yO$+QiTUZWY2|&a3F(T=I0q5!|EBi3w%K3%!Qa*qq-G zJ2bCLVo!DM(58M3)6%X%-hNabVl^zofh{UR=#^TuALeP-Nib;zT`p=RV2JrlwGh{e z%9u=w?Kf)DAikh`40$yG|8FhsV3Bvi3ZJ|oHD*(s>gY_kasGmrrD#Yuy5${#WxlId zY=6%UH{5vRmzIY6I+jLW*|Tz+_sG!Fk-pe#JQ?rM(V?Sm#y% zihawPi<;sbO|V!oj2&)|`^m)Zji(UfDRC64eD+xpVrrUcWdz8rbvH|akfhCwvuJ4} zbBHe(w$lX{a7$to9g z8Qd4Uay#ZOQpvp;t2+_OhT=Oz`$rOE5o>wZp5Ttmus?-!oRRL#aCGyg4MMpw)8D^r zS!RxG# zOc-ZzQr%YC8i)kqkw|D)Jf7@H4h$qZed!)liu6}#$kH>-;TGMmeL8h^Z!8ns@jCD2 zul2wEgQ0irJM}z&X9iIt)wd*cWMSwtseyF*o>V#zNk$Sl%a_D+xXDD}q|c;AXs<7| zDjuGwUm>9fN*=_mRkp>=bu*empk>djV3dK_*~4gLUSDF(p(kJm()nPi%kogPtoJ*9Tq)z_IXI6V<_>rqECx1$f7L!;rU zQPkFUw#=}X+Le3-5e3A=f=wv;*CiXE3S<9J!;Uho9CU%X8-l)=Sl_R1OcLQ^IZn;V?nwM;a;; zqxVq8tC(1JX(A4Z#B-|+%3X?}l^-!m2R&F|wnToC3!QB8Z{pUJYlvH~Q9jz4mTh*E zsdZ#5I&`IG;ToHMh}{HL0hE@pWd}4Hwl0HnNyDMPxSKrlFb8fGqRZKA|7u4fy zbi*RFdrpil-i)eHZ{FZ4+2VwrfZYcLUYcXS|ZcK2ZD*3;uI z%-)?!d4f11d3S0_;n}-W`v)TatUtUt9m#~#U(3W2UEb3!zWB`MsTW;zitl5gj>0Q{ zk{ak;63irmbRv!pPW_2z`445&Zgdr1{z))DrWA@tY%INOYQLEDFSTM3A9z8p-nQyPTJL&7rV7@$pokh8~MdKHBrUtrGosn3= zN_YDTH{FxkV$o=Pjk|EyJ*mU&^TM&d$kJ3UIIt>s?4kZpI^Gq}u00~@>#&lc;@zzXOtv>(F1!7txRJxTK*8Dqy{fKR=Ew8PcXESKnX1k$871-Bp5Pr^%_I$EN;6& zxmPSc3(@oq={XrSQi{y;%G2DcOHYBq?HYhGxWN^ZHpoB%xB!wTW@z?em|oB*-1?i8 z54+ZQJ!r#DcOLT=otuY8w`Fr@UH9XC`*!=5uJ*4SjK^|mUq>!**hq3{%WJJW{G;2` z>FpW!_JBLFP=UXZQ>XdrkELrWLE8MIVsH)V7C1V7>Z>O5DZn6n)5L|DAHdsZnAvx5E(g?6k0$xZa?$ zw#Pyg4>zboyXg-bR9XttR>esvndF@))n9UH@O~MfYc3>0G}=P2c4D8}(aM-;k(&r@ ztV&rPHl~%^D=@Uv;#y5mt)?p5I}Kx&Sm5ga8I*4)RrFs5<;+7_>@p}Yw4dhn_>^cW}>0^1feDAYfS$kXmM6))q#pn67BeH29+L2IEZVD zB~YBu7AFktBfOAQq;Q>|JcbxeBPc*_38M4~Gb&zdP_~kwmG>@&PD3T-z?bJM2T7=j zuk=GKcEdp|myt4DL=vbhxGPf~Kd-3zi=d?rgF0x-pEjsmvdMhs_A09RDL6n_t5i@} z#{eR6Nyb*v%v#3N%R%WiTZ)K1{VJg~FBPjc5oOW3^h8)l zC?OUy0l{eH?WQUVy43kW2q}9 z*6S0+9*7wtaHCzXGHt^g#(%FIr~{&VheVC%4PUVj1jde&xqSw9XgAfW*Le;zCt-&y z!EsU;PyCuWP>;dmvJyfTbe8Bqv~oA3DA)EaQV#d5FkD?@gphltgjjk386&`$LOZu# zi__DVT%10LICqrtX=Q9|yEzG83#Sm!;%L4>T%%>ue0h?jpF7qF-CJx-#Y-pc$RKi_~N*ieD-+0gzC!WBJYi~Bh4zw=aD~auBa?>SKoI^A{6iT#G zxy-jJ_5j+tZeJ>)%|dkMz$Ba5=8iK0I=rNKpFw4)FMK3nYd2k~1hfRTw$KuH+iDCL zxg9PwcY>lisF#|rf5@#;j4J-7r2yQTQCojfg?i54JVP-PRjjM%f zS}mxi)g;Pt#JC zpq1Y+V;b~7OGn*HFgaSzX(}+eoZ&nDIThjwESHegUIE(tG|raOC#UNtPv8?;{Nh5( z9!0go5p?2fhCMr}pe1Q+BsRAcrM=P6I&4=;DEf%LxhKSv;&RcKpVTPE5mQ=DW;%}|1ymE z+_9jOe>SvSuYyi(FerDjf=8ga*61`j6}D?jAhO-L)ffY**(=!EGzt7ao;}1Tt1nVe+zczumKCZz3Dsv3(U+ zAa%8d{oL7FC`}@x-AkdxA23SkCkO7zN7=W|F#OIJtF@rCa8lx1>Lj#&uhKr@ zdX86Yg>hX*y;QGH5(`DchA|f>Xr;+$5=X>kKkKWRaZ3mqSs~jJ+PQNL2S;T$5YC}8 zR&1$fr@4P_BNGo8;>X)UR3;3{d*y`jfdHelL5+`uuy%SEc~xdSsk7$uJz}NCw+tWY zCaz%Dt>qLZ??+fsxz95w?~D?(@e3`qm8OtM^L)XnRhQTsL*wT?-l9@@rQxH6lC9>> zXO#7qZis7adPIpIh1{@;)Z^4rMJfF>Ze9O5qgh;U%5Ndn(Ne$|c4S|obP&p@C+syS z#~wk)@luWu6Jt}H-a{%a+@RGJhStHjF7-&Xl1H!__4g~S-qb0!(~COIB0cPb@~y@e zCZ^Nb+z`b}8~ug_+eeJ4DGtR&3C83#`6+OK<`)_PpQA+=`C__lex6YhbnYUdrPQ6r zfeh!%Tba?tDv+kF;#iHV=(omewGa}39;6X{@6svoL0ppjM7@jbFLBuiZXY} zG{`ky;Cp_AJw2v@G2lCtJq1_ODyq*LRC)t$EA?(gAB7570;t1bVTH>K?b9Tn=v&^$ zs2piS(^MM|z~!gr<*o240tes?HMg( zKa(D@cVS-xo>SPkbz@+6B7{v|;X{^%up4T}y5Rc5avn^=Ct{(be=u51^?PF;E9CQ} z{Ju^r9Cn90<4N2-nOg47xW_$fcJIkWQyY%jVa1cd-N&xP9RK=P?C^Xe)*a|u63Aqu z)2n(%c8A<T6h4T07z%dI|$a%z6A>QJub@I>-hfsh<1sryLHxpq+ELnmYmpa@u?rM(1v_D9jz;kOVk z&|;PeCp~Z>HeWOU2G<8%A93AG^ZbQ9sd%p7y(7IT)G^2{X3$;OeMfqa{hH9~STG(A zB)nbNftpCK&&2#4cTVe z7WAZ_Z2A5kq4b-frRkwL2TJSzFHjCLwTIDvGOb*I2fnqQ7K#=olP~wdj(gC)3ZMCU zI^P#}4=>H{bkpNHSQQx#@UAIbn3*5N#*E*6J>9*$XL+FKM&E0_p_t$6&Uj+|+0eFB zJeo*FM`DM2*A0g{Qa7ceo88I6;5X7;cF@Y~4q~Tgmk(!mBb&mpcsv~2ik9Y!U66{T z-O1?cNUV#Vi=+$ScoLKcYCfqY;)W_Y$$9a-|{RUUT8MyW1L495SZs1{d&UJwF>st^+0zd`8uO^j0F#Jyte zhYnPp@CDxklrrSUKZd`t%JD}1gi%WbRGMo-{}9q4sz)qx3lS3zSGq4HK;q zg^mMKiiB7S4sT&vF+U2eVhm(xX04C@B1VR)=-U(}dtd}3 zcLJ#pq9Zs^?L&zFW?H#-4|&T3(j z)kw4)D<>^S+>?Bw0)rv_MWX-<3L4MSd}IwEhCiF>w>ek^zfoe#9zA_5qwiDp^ab3r zb_1i5+sN!5l&KzI^aIL)CS~Cn)dr&<#NR&GDRQmlJ(|Q^gpO(>7NhyUxnbTLhs2If zSBwTaknYi42UwBjt#G58k=5Y3Vsz|Cb%v>rM@;T4+!){8QP{IH6)ar+jV(SqHJFO0 z1~!M&xp*QM9^ACtd*Zf8z)EB~d_D1=-tcgjH;r?K1L`lAyClkfXT;Yy&OG4S@+kM%to^Ef>I}q!MX5&3!Pdw^R z#nO0h&>s%%Uz*GWGRc6|nFyr={?1jaQbVbHI-O4qrDIXVGt0HKP<$YLxa~>y^n!=i z_o;X;d}iQ%*2&p3zLnXv>y&+;puf&tH#&&^B+|9g+c|h$B5_(eg}e$c;G%Y@U*!3j zNv`H`$)U4Iu{btOzKSe9xx!J)sQk&Z2(4^kH2bnm6-+P_mn165$|)jEokO~=NN3@J zd>xC@{+d}(O2QRfC;zc(Uu2Xb&0mObX$EM%+;sfbhu{80opBAWhV_QY^(qTRPh`Qu zUYDfl20j^#K1VF*%r^V1=1fJjMKp$rqBLYfp@P<=k(ukb)E?hbT3OAb^1rRm(d|menXGb z%A09SJbjEQ^ihhmNuD$)zi2&m8=-R_QpPm&MJJ$|U(aX^^LN}%t3P1c4=ZhJ z;~tfUp6DYC%QYlmQ4?b6(qu~@gebnmEHFU@fr*y)Wfz}kRASLW6k;zziw*)PT_(wE z&vx-eVljQSfrT-nbDuD%yoe>}Ee7R1F2Z51z$ghJOCuuMCl^IOr5ql1*-jRpUX<}? z3=5~upIwypa|RV5MBvXG)LGgW2({T1PWaYEiKSVQ*_^arV%mjxdt*%2k7^=G=00z1=hi z)x4&}^BE%tayA{MMZda;?G7mVFd|~h9@d=1KBvmCzcu%5!`P_@BKIP0G`lD|wW(GI{&i8>zb%T|j~G}CFG4(OP-ma_n4)w9hU_D>lG`kdf=?I@ zQXN7oNsJ=^N&J6`i37-7pP`XR=`0S8Pa78E+rk3L7fig+KF=zL`3?8EMfrk{wmO-# zS7$>y#PIxx0Fx@Q(}9o;Eh5C<4eHeSAIgDzqOIIN8SSNu3;ssqqsi-7XGtn^NFO;Z zuc9--&Y4dzF+E7|2#=*0Egi+EOrFpn5)lF`uhMYMoyheI37wFfvIiUfngdPxk7cgdYs;Q`+iWOSjx=3S7Uc{lZE?AZXd)SBg@;af=Hd-8X60?2@ z;va}#r^F9I2)Gr&7)XQ}rS~E5;2$PEpaHgqt!I>m7ks9*w3E;|8D+~`5{7mHzd;B& z-9@7UR1auhW|XE7$im3~8E)b?Ce~0>Vq-SljGDcuWpt}B@%4<75OR&frwMI(7&W8p z+VO-Eckrdz4PHmAUqNVXJEIV-wckdB5W~yUDn^f{kBQACmMXA+QPf(jxa!9lrFkB% zq((Oj8~|#|oqgZ07#yqeGapyHRTj7|2#bJn1esT|g5_kLts}!$fG~qx|yW?d-$MK!A=j{{p?=GvE zbf9g4=(mklGv)XZ?Ddm0ehflRTK~Nl$7p3@eJ`ejk1jm$4DNWn`*oQmxWC}>*JTDb zAD20C3Y+tPVB@52Ll72h(vl~v7MoKFdAwrL8^%o86&5x?}{0+jGF7PC%?}M z=M2g-hKhDDN+(QZYs|? zlwK~!))_&kWz0ex{z{VojOJ^%5%&qSMHYo3&n$jBhtSfiU9NK(*5D;@5bz(G{qgxYJA?n6nF{$gCezVi^aO9<@P1si=?QMQ_zhyI z$7J7-s5y>$fE+aU^B0PiHADwU(5nj4o#u3_gD7~Y(S#fLxXrfK0}Wzp@_0rY!kDH5 zW%CkOt!`ct+Ll$VQp-e?cET9Kq1mU!Jt|W+f)-!PD8)r=Q2+~;I6t|Y)b zWqiM)x@AdF-Lgbh;SCL{sWc>-C*vtAL7M|cl&5W4+ZS2%k$Mt^r}|lx*5mJla-Biw5h}#0-LJQO;1^F-&{6unPZC;_e$q@m3ZJPXnU?xK-T+YZ+00@{3)Ofd#AQi2sdis%x4LPOo4lP5 z-`0`2qXYgZbCA;+n5I;Z!nnX?HJ9*j^ne84msj^}0NVu#z+lR?qqR0 zp7@#L3vY_vUo@}rml)sTe!oXl3p`~9Yx%-#B$Tp0BAjr506OK zTtnv$%PnO-Sl6(@2ysEoT18~WkHf7W9lu%45k{#U@T{p+Wpf#$@^}g*rYyYfwxSAt zf>yEAN6_{(oXILDRj_SMouVu#F-4S97UEDD9w*SEnba!u1q+&_t^dc*{ia?OiygIYe!FX>4yfBfr}4W>JDF>#<9RyD&xf+ zjY~$aqCfXq;b1h`;Z5K+ft(e|#zUzdE1b5X>BI$A@F;(OCONn+9m@3OlKyPuYzuck zCgWirF1+frYvFKLI3D3De;jUX>{@GO3*-o-UFIHT{A@r<1g-Bj@Q2#qd zX^D?S$kqBAS)tYST2*B2EFj%@ zXj*A=jp6QyevR`PMIZJA^v6hA3ay|CGYjn^1g%LcrIoC9T*=GPg(d{T7i34){{Tco zQ)q=;Jz*ex^n}5}`q#&=q@Ol1cS6D;6)H<&+*{SARlp}u30O51#?^1B5afC=w^V}e zuahcnJN6w+D^Z)S8q|Qjj$tZ}O)8G14~7!TGZunQSCp3gn07U?Xw&zp5Y+paN~xb| zP~L(hqLtntD9u>#W-YE=tGS+G>Wg{%aBT}4^TUkNGVin0o7XVx!;14Sl~$iouYn3{ zdN(pE>&LL=?&PEXc3_jo{?MwG$RPFaM7HCba+j$|QJHSjGeK&pjM?z*KKt*#Cbn@qh@ncjef{sra^hHue1k~#TjDSLGm&ch{jE5#9yQ_?u zs6WJ@P>9QQL(3+0Zr(Cqkp2it`o#@K6ln!wl@wWQ9M0L&pM6$7shsa9gfzwEh0AIp zHo$oTFFz&zEws~V!`@+)>31ng?Z#N8xz*6}@Pn7ck;>vGQl>7xgARMMv z8d{ziF7|OsQru-=9{34y^H4=;aAe%5a+9KZHKGb3*}8-kge&FH=F6c?(5eJ6u^K&- zAS|kvD&t3`H&hn888uPNxs9Zj)h}p^yNQug|58-vxoa1@C=eaM-&S*v{zhvAsB+c6 zE}aLx80zroT04x0GchC1ZS(Y?0N6=2(d&#H^CtcTG(Yii{_QY_;0Qtu&?5YzF{_L7 z7d4xi%GKk|edZIs5190GSe%Ja z(kFB~`F}P&J=;p3v}3weeO757ccPjxNE{_mNceoUpSU-V z_Zw+>AT0G&=H#S~xWHm*VyT*ZNKs8@1=VDh=K6VxmMg<*Oe!sn>kv)VtBz9iQCkYR(=b1RGgd9IdD(9D+7lp++|?3R-WqnI|C{rm>U*jT&r<>6*t1ta+?7 z5AtfR@9H~9!0J1-P#v!;qhk%qnv2+V%;~W0=ruzWDroaKMRlUh78bSFDNM)EFw#?W zk3k*rzj?f2Om`2pv^jxM$}ai(gYjEP;7M_5+6%>jzQlGeNi-!?;(z@*A}&hDLisQ+ z_6l+F&CHlkZk&pu3x#%sa(63e4bH46@BdP?Erdi!q$98;clc2haFfx+mS6UOkk)K0UC? zkM$gTqtA+lyiwndp=hK#lsyhND#nt<-I-IpKEKr$=;-oCBj38@%Fdyo&auIc(b0~< zJ?TVdEaCTivfj|LU@Viw3lp()A|2y8rD&#_VCi$Z?L+KYVtY*?jK2w5ddwI@#&zs& z4JfUQ9UPh`8`R+}6B1~NFO7R`b(Z@78Df6sN{Xw#jZsPTA+grexrWx^EP6Ykyr@oL zlWR}U_fc`Ot}6n2lmliNdEhHP)R5ebhdvA)CZP{dX)u@E2x&PM{)Tn43E(1J=>C7n+g49HBU7t zpJpqqC3JELqcWmJ;$6Io(FYWz4g{zzaOHyxOaBDpjib*6i!U0KuT&MZ_#VT8N9M}- z^M;m(Aj;tj2Ia{J;1<)&Pxi z)nDTaFK^h#;VIV&Ud?sY3fnKqZnaOk^wKrE!t1RqXP%io>#QCAy#LTuOE*XNcARzT zan}1+CN^wYzGf&ou=<{)FP_TxCex{KAcF&xsk9e2uP1u@yHn|OG8Olv-`o-Ia32?m zrxOuslC&X4=1Ewz@@I@Cr0T9AHOjX1IHU(ppNKJ=XBeFwhA+!ZAJNL3mM~t=w5IXZ z-$IP_4jmfPKSq|A*akFT`ZoQ>V>Y>N0*A)Q&u0j3N(?6UoFa%x%jmhGjhr?$L(6j8 zX?b==X(dHkP$}a-Fv_&W&KKcZDARtWgFW)|@({two{k2yC}p@Oh|e^PxP?oh2bMcf zv~WiA;ZO!y?RZRTVFy?9# z+6qjs{@_G2jPm|dr(eIFVX8N|vw-T&YQD-aW-k%O)z)x&p6^s1G_-AVTiWeXNRmBh z?0&_6V(0|fsC`Ou%&=ozD$ASPmZzJBR#Hf&t?f6(al2QY|71{yf9SOYPX9ppR^?Gc z?65@fWku&Tqp9$GT4^t3gk3c;izaAM!Y~!*Y+KS#5Q-p={ii~#-7B?PhY(Y@n&KQR zbc2+zF#a?B0YfW&Bm2a-bU+Jeo98I4t=pTl-Anfxz|*-751(rH1&-=yp64je2vNPl zpwgMM5ZakbNTLQW==m7BsM)wU5u)05wsvuP3sMuG@E7DS)JB+5!@U?hfWvGbGN;bYVC0bHyBE@kqc8pq`9R2|gXG=%nkhsPvJ-QDX zDwulJr3I=%Nm$rw^=Dj^^NJ8Z!|CW8dbc&W@_&o#ROGyc6GtMsLio1q`eWm6Ix~f% z@tXsq8`dO3sbC_yF4VX0sNhNa`UWBc16O9e`EWKge57xez1p{PEb4g`|G6Ug%1e(K z9St8kl69wcN8)Sy3-7!wyUvbxFJB(*ai_2_7xJWFHHw?OEF*DK(=nM$F)r7*)L5A3 zZp7}D|7Mgp{EI7zt=7DRQQ7wfLueN^29?oo2%`mtZnP4Xd5yUIgkkh|S~p@wR#V)Q z3UyRf^Kv7K_!f&&5yv1}>C)e_k)$vK&c6UQIyTV+amGzkPS>6I?O$#$RmY7hPRQ6D zbRwT#&>OY_xWK>)!X5H9)60mNU~fF^@lSZp}^|HGgz(e4tJ&VJH1}Fd(94y=h*!?&mQ!IgYj;U zC({Gkll2=CzuO%O24Vp#>H)ns8bit67@h%&MDPewDCY5?Ft4}Zar?Z! zKp^U+e~bG4uz$2O7X+~gg|DH`5yZ~2(2C_O8q%mz54W=`xjE2cA|F0g^wo@Jc*?L% z`x>Ry$>9noo6Xl64m?7#6_ysYu(;PLV_KPQGrn3`(5@YXTIGQBg9UzS9-$>qWN54a}bIF5#|+f3uo5swn!BN*UR+%WA&epl$B?NmMp|dk6hS z`ag{Mxu2sRPa?P~Sv^_h={DMwNkWmnMVl`l%Z%Q|h0yih&(jxEB}U)PC~ckRX)_fQ zBL`TF{DI?n$tH)$mwNe=_wtW4$HB>xej<}Hw3UZ-5L=Wm%y3!F_wyID9pEL0u#)+a z3J%4ZGDad5o-wp}yKyQr`VcdsANdp2;ls+F7DrlITg{I!Dto34S`tsE`RF!#t9cXC zlD=)>t>#CSFXk%SjPt6~$M{EL-Ymch9@Gn+Cb$P%kopQw{WjZSbN7cnWw`g;XfEVG6x4#Gr0uATxi8HC2oz4m<2ERQWK;-K19oCWppuGosN)Jg$#yi^Q9r;-u$Ey zg@c;#v1?7kh>v=RHkEw_6#==>+Jr$Jq7;#8usvw&1(FhBT>HAAmE+VR_CbRR2b zakwNlzw;G&m31ufrJ zxLr|dyzS!dSiqseYJ5de?Rgr4ezg^bTS5?=FCD?3d`&;02H9rcs#jXyY@^>$S`FFC zUWP0t$g9}n4pD5d7{?gKWi=7|*^RU~MO!YXI*ubp$8?RfaXB*57GlycmI@Rd5v-l= zseDnngv#~HEf)F7Kd>nHkUvqY=qFQ*l7Dd5t7uhe)v$_AFN(_4j;V6c!v0pXKA%|4 z)KJtZM1JB?7VyCQ-{gzR>5HKG(j0&BE&Yv_AKNzncB}s9OONR}*1u(RuAoVwl^_fj z6X^dS{H)TdcS{9Lcgz=s(R}$L{sICqcPg`8{}Wvfm?Y$yZZ;RNCsFdH|6xY$U?O<} z3pC|lP_%g0cNBGc7rJ-o8crC6;UhPxftzKx1yS?sc-zi zpsGJH&QJWEMfoBBNco*Zr{<3on3LrW2<7m$= zt0_8Ay3Bek#G3O@Pre*A4Ue}&)d|Io7ezbZh>sAQ3R z3GEl3{91pTUk$%uG{igaO~ag$DkFn)xSC)^rZddvxBLZmOVY(u_(Vg_@09)gXz+VR ziIId3srx@LI$vw6`G7KRaS+-+DqnSKZU^}?1_rc~;ZOQWs~SXKY2km?Lgx8F^DoM- z^@SAjprX#ATkYT>{jKF4{6+N`nT~Axx;Wp%Oe}AglR3px|GJ=17E>eI-wb=Fg&tv) zre)e_zVtL#0Kzysxa|xh`3hpb%W6KR?B;9zxPH?5g6mVic!GbUj9TiNBVqGN!-r#u zhM%XD4_7Cqqg6l1#ZlSm#a8nfMrC1c#%;uq=QBCB}Qpn$u6v-A&UNI44NcnYm#c^e-qxP|Fn3P9gG>hz?*BSn!32 z6kpEC&kyJG=+ zu}Y_Y(P~uOP7X>23M=QMje*r&f~o6$~q1%KDC zzd0j-CTj-=__ub566@K647WqD=r?2-wYyELD!Gx|-`wgZ z-MQuT=K06po!jHXocY$muil*-z9e{QI69ohb!`dnF{?4%dSU2YTYH9sYliw`{?+S4 zxh?B(jyK*J+^{Kl*ai;{bVv3MELqVVSv`7KbmP)cv^&%h?OJmE_19muB9IEN$mF|6 z-pzmR^ZDa(%p&6<-gM#o_vF^&ba*^@dmxasunWl__od=H{h0Cgt9v?I18Q77+=;Gq z=+;(km7;Xxd%H4LGb()xCN@gvAsk~OWuJlNV4IJsX#6yT^27lVV^cMtu2gB;i~7UW?X-OTZK=@mfR7t9U{@`89*`(zBoy30cILH(&{R zaGjxLt4;Vx2;DV6YKVmi&DZziZ`(S!UO$I+_<4!LY0i(b9q$v)2Xs z1-7e59zdw0T5EN~fp_}b!lFW{vFs8MLo?&b{fbgpCM$7m>ry;1cF<0JWcNgmsNJxDF$)mAi6 zkLEhh&)afroKpOu_FbqH;e%CJ0p=?kGT1fR>l(uA>pNXnx~}!O-dH&4cj>{x`VZwY zg&%yVFH!jMxrun;K3wQsxWAd}$7(LtT@zO>?Z2>pXh*a+7VE`ZsCz>GMEvArC=v~y zv@$g|7K;Xwc&9rR?@TQHSkg+QmoDw^AH8f%UtdQgnoVZ+hy58V++JPC@p!6hpOxzDOm-*x zllZ4QYpq-oUbbRyGUZFglAAi-)PXBQ^O2|(qvuws`d_55&lRm3r4H^;L$}fhuEB$c z81r*o72iQVFnJ=Qlsde?7dnie%CIzM$s^&Pf|e{p%&k^XeJE%Ft={%Q0&;Pc9}wS~ z#5zJbnHC`?#P4l_*)@b%b{0DJGqJ>_OH>GT4H2UHSEHS53>WR%JrMJ)A^C#eA8KgX zKc%$FmIbX(WYfcNl3P(r4G;^x#EDSeTVA?`t@a~hA8~x9t+oTMJj2JL)ucCNL8sAuz&)uriB3#g|F=Qqq&K4ncPeVrjaX!2As$kg zMkheU#PvHEmD95jLSC9Gzn4TgxXZA2d{Gv^r#+AI8Vy%fMG_?0YymOhb07IR$k(Rc`aWP?5WLTok~ zb)6h^o$fl*b&l(T!qwd!9fcE*kEg@f(t>|zYe?b4kLNn+ZSFfB&kfs4^E)D$1fEOk zi^Njitbe(`r*o~ZKb=Vpq{Hc?KjH7}@OI<+{38SLq}QM545j@0j&+AlaATuIJhsNW z&bQgU#?u!_Vc$b4a7fQUaH#t%|I)yY!DzSDyVS}Bj*W-B8DD5$AerkArn^^I`6~iL zo#B9Q$eSq~^+Yaids92oW67*Jb?AqzfFhhP%GcM54&^a-z)vDU@oNa~rN->)_$cLe3zQC?8}YjxdaQ`A~|h zKVneHa-|i%Y*0s=s&WLixqUOSu&B0;Ct01C^hjl)rvapSPqkXTwzO)FtW-s&f}sbT zlPf`!(aH}Aok9c9s*$TNY-T_8C59#C!Feh%cdl3v@Jovnjx~Qhv6@!D9>~nU&mcF8}}N`VPR@s;b>dX68(v zGcB3)o15fLZg062Zl!bSz00L{0TnwQPz1YJ$1eKYHJM~)lF7tICc%OY?7#l<>#rbE z1OzEI-nYuxa}x6M-n&n}vu5qJ)?RzRPV*Bv{ZeNMr3KGOxaT!evmLL}Fh{B?Nx3bXDyU@;_ zZ5XqAiog>HG~hsO;!(aNh(mYnMYiZU<`$E9N%?AQDYI|YBq<;#eV*5M1)jY?i75#q*I*%LrQI*`+Sv;Y zE#IuHvq;HeKAmxWlT)Wt?;1)kVL@Xx%Vo(-X zXf@BG`9Dd7n6~^6rF9qEm~Z7m<|tH?7^CRG*kix^Iz-Avj{^!kdk=e3`(^2AqIFT;M(Trjd0yRrlCIdJKv zyR+H5vb(Ola%U=?i$pWUY$}_|6}O$=zLIw9o8G4Ud1mCsZyml1`wE&3mrCIgh848z z=J|T(Q2EQmq4ZNDA#c4`4$aKCi8|JlOkt6ia#|zACD%)7uMUb6#=9z3Id#a* zXzb4<#H^dS=pk_7Fbhw>R_Xy5?~lG&U$XeXrS0q14i6u)B0D_$%}!V?dfYX?KKE$) zBYkO0;FEOBzr3E9PQS@le8Xk!Cpa-XpX_Lhx9zgiIE~qn?Cgl=_cqSD`s&=}m!CC% z{H|Tcw*_-F*BZ0&Y#gqf!plwazelqDc#I?4uZHKN=%tSk^V+LfJDT2qii#|89y05? z0-d|Uaj6PYB$~#%l!*snHh#&EVQUtd0vOwT35iQ=&0fwZ-Jm?qzGz1!Es2Jt`7$$g3>^~c#mmyi^Hs> z!E!1SnpeI_d}gm@?pd5&Y5u~Ry$+~D`-;gRrqJ1!GqFtD8?>BbG>JJUz64YRoV}h= z+Fj}8a08>MMYQPOU{Sh3_sM2y&g?4~rc2bGYQnRxWK_CAguu#eq(}KCMrA+%v=kxK zJlE?^Yce`|tD%*Qh0$`rpfZwm7_Nv95Mzlmf=WA+RTL_YjXCxs!-6}5X|5{0{e@_G zDrA}+aqNwTmPf0Cnj_%oPWX_Uz&f-e7TjkFTE!#>%+^G?I6agtC{_=&xPKa2S!rN_ zbv#0}oRtV|<%>+KyXv5w7AGB5vpCkH9P=)xKEo#y`w#W z57X)rDQHo?RobesyE|TzatR`|4cIHhYnYZUX!Bglt&;~)c>LdK4!MFp6+%c%r<=AO zrO>NtyiQA_TN&=dER9G!0fVq?nlY&!uV<7F+AxbU4`5UeY8|ZI6jCLRc9eOL)mY~# zYUOSkMM5w-{uMqRifG81oou;>$!^yA%Hg_?~-& z7T+@8pzjpl>2&QEJ(SgkgF|);oe`q{6~dcXIKV@vy9V?4|MA$R($JxtP+RfZue3)U z92CmqA;V!jWZ0zMKA7*#_Qulj{0UUA;Sp>`}uT+1ueLEH=*xeRLC4MsEsz{N`C0-@vFE6T+hoajt&b&vkiu;73{ zfh-Y|4;L6zlwhYw}-uoGszTNu3ZXJq`ek;%xqSi($mARz%U&E^1tI zT$ve7K4oY*4%0Q1;3I`JyG$9rGFm^Ce<9fo+IfAH%B(oQQJB_e5%L5c`#qzQn?V23 zj}!6uIR@qD=>@I+#GrhRM$ieFte{kytx|m_aVSe(BMCNysJ@f*w?^F#PiF+G?6XqtVCDfDr^WtIi3i0_ct&o$BNca)D#{fH=2?0!Cnh`BaZo3J5f z_v1pX>I46OQL0iYP{?H5 zwemMhukDa|5mb<{pZKeX57I^zMFxAsf!mqd5u(7xM~bHtBYp;4eo~d>QECs3MXW&M zXI#QAjDCX0kx#-I*t2}+VQ%_6KWkrutNNe%S^J=~d2cJOf*HnJgE)A(m1nBA4W5!c zE1y4naC1e>^|kHt4`S)NyR!l({>1N z)wcR?=-d`~pmpX%?7=dAS^oT+PVO<3t@OH1nbWZPSNf}vz+EzsnS zOe~$wr|p#e*`eU@S=rb?Bs+p9hT9AIJ8gTJZC@7X%7^p5;x~TYzQg%d|3F|x{MNR% zTk&_srVXj}8&)K+9=O$U@_|es(!S}#r+1_~E?$?(wr>a}SLAbeYxzar5G=}TBNOY% zFw>u7>ytzAWtgRB2%SI<3R*%r-4Rr;Bq8(}AKf>qFdQGml&t)Y_6|;*$CdO}!-7vS z3+=3Q#%8!O&7E3R+MRecjhR>oy%v;E+{*2ul|JT+BLNS~h5n^|D6TNY(MT`!r`NFg z9I-Gey}^j`LX**~I6H0ok%%LjZ&zAH$IuJoLLTY3NtC}*4-LME0aoqphB4nFC&f)f z4a%clL3Q?mSa57n#u6?F<>XD!QE5Je@(5Wu*dEcokK|2=r?Up;kRZe}lE?^SW4y8J zjcAXMB~*xp;lSZT`95oCJ$&_As3^S zjc7V!BNq#~zE!BdjQG@LI!<J^n#s4<5n`t@hg@Al*IPcTOwdZ(s&VD zwLwE%V`x3*9{;34Wj2A8nTCLryc)TMvGz#f(Z;ZzJ7qn7J}TaCb|F^3!4)RS0<|B$ zrBC>dHPw(cD-y!7bj^x{ctINl)zuR$F~wW<{>d&77s~(5?B8j$;VV6by!TlV!AGdTXgAnWd17tHJVEl16sPzFm4KH&A!jjddQYw%q zZ)9hf$QIJ#bO0kP^a?m);br_PBib{xEq* zB_Ea=gEsOLy>FiiW&aTNx->|%d}o2s&K6mecA$I#`skmSuNzNv*-SX-vYEtJ$N2el zyU~Vk&lch_X;xI6OzW{W6E`p|q2m3F>JB_Xjpmbo_t3gMi&ftxsB8(Rj*?l-O3JXP zxKX5H+63jMT&}^=kfJo%XIje4Fr#E>5seJ(uF@^K6d!>~mrTf*6iQf#=`GBHvH%|N zPQ%%dJbju`5zXi^{**zbTM{)srYLnlhIaPjjFREyx;UtT{(tgrhDBo6H7vbXpXPcw z4oGyE{e&s5X?R_Wnf;{sCg=TGlv>T8PtqWhRV-9!JZH^*s;NJ3+!X!mK_ie@g-uBN z3AHV=RfM({X4JgAQ$yc~O*yCpuosKT6IoD~wZzVds_n*Z-w~uJ(9t=c%V&{m08sIptV?Vd$1*Xj@0D_`{>} zD?9B#3yDWF@!Xlwlr}`T%U8Vh==|yHwhp#knOs)bx9uZO%ZQItmW0yZ1 zPj`JOiHlT%QOn9(yRygAGdcca@@tplQayd}#CN>!WS*VEn2M*NFad?>rs8OHYoYk+ zWAfP(_@0~XI_z4pslB~pb$cKajM!_}Zr;3R&8AJ+9Xr+~66-c?8fvrbJ^64vk=c|@ z$L&xicWYNy*O`0wW_D-4-7^&EPK1iTJ|^GeSQ$GUKRh#%$qZ)bT6?-!g7cQDgkjVm zqxuv#J;DurKg%fPL);5aqwxX#FGgt|gC`ow2MMkGuc9GdDb(*&c|eA}hE3Fcrd$ zFj)Hv*#SFf$3Yg2Ul5BFS`*QL6^q7O>~QL4T)?v<9Y`$4bu`h<;UmI!e_tpaTZNa( zB9T-SYiF^T-D&srCUb#gCw9|HPj?^gc8L2@z9HO9wGsDH9R^1^!-v$pMp~`!&n_<> zJP9w%ob|t{MDgU0cSeghpOoL|EKA^(yq&p=3d@(Ty>9*B;L2=d-9BsU@{m6n2#tgT z$zXU(#?GhDN}s&l4zydi%PJa&wJ_Gs(d6Kj7K7|L@*gm1PA+57`q1(=PpmS=7x&vPtLcyGsk=#*i4FQQ7`wT zbpsBnqqi~oLv{*^h;rnM(W;~>+{AAT zDp8J59Yl>EYre^7iGMNEVbJC;EPYc=dOGS?h5L_PTDl&Mx*mqhx4N)Q_6S;8#i)$* z!D1A7F#LWwi8=c@^9xB&h5cO~l)^cql~)_ISteeVc#UB%<|g)5wV6grr^_s=q9cjI zuU@k#R<)V1cKmmRXFtDC2uEbA9b#Z!ViR=s3x`ot21I65G+vPu6+*1-9CkOlgh`y;-}d8N-5~tr3YmJFH~?78h3+AD{sT$NEXQ zCx)Jq{n2z9&(;Y#`(@@!w7kQjQ)pB3f?Od#J8FJ`{aUU|S+ipdldnq3z*ed4QAQN|;i_uFlG4(#aR@(#JP+w|L@nrAW(-l#)tEo50w}X$;O4=fubPYN-q#SfwOF2k$ zp_#iTU+hY#D`SEUi&^nA`^D#U;^%!V1VmvKy)3NRZz!sXpceN{8#iL}!;oWeSXH%4wRynw3T?&l(zuXXlm0 zLhbHhl+s*IXjQcDFe<7jRtI=Lis;z{(Jcwy5@-G;P)GK{HqB|x{)&mI4n2NbS*9!=MTBYk#;*t@S3u9W6I`+F5u`y6%Z zZ9K4>{R4kA<4FC<|LIROP{>bI_doJCxxTI`fC%>|{ubr;;am$8#&)yd4=T^5aIlv( z3*DeAb#hW)tZQ`_{iN~^{mFuFD#oAnce)>31%H@P63)X-EeAJ8NzD-a5Xy%Dk1)Kj zo~_D>in=Wtff}@uvXFa<8?mqx(yKhSm_2?`82=~#4g(*qP<`uUl(=Cp@G1Q9ulhrs zQ<yV3Zo3$8BbxG_*~$P)1!sxTp9VZKx34MdZIRN*3|{f~(7!eOf=#Ti~WHM_(iPN6s+Be4_}+(E1H*U=M`-@Lj^PNsCnn)(k zP1^CaosPB8@w;ZXK~h??s3At_ZYS3U*6hC+^*9S?3zKit{XndZa~#%7qz?7XF1K`NT(D2q}oqYP4$6ZqwqT8-Ab; z>Fd@m|D*&r!A}Cp&eegxNl(xSnxZ`B9%J1b7JqB@f?U($fDK9Wsdb25z|+AC-Q0aP z!umtEX4&|S2%|{^$EK6%1qqEPe_JxpG&nc(*iX2{%`aSErx4RfFl@TLQKVNulZ=wF zMN6szt>K1uBDz^=DgH&$xSP<;7kchPxKEy|s5gk=Dk=`Kjc)!(wd|F?M^UeB3T5sA8@>E3j%5zXgT&N*j4p09 z(&;Q2lpA`TM)M={?@HdKq;%3Z~Z*Q_1gUVCA zK~wC;5X01Ly!xzQ)a=sGiYh3r%bs-CyK5T5%H9n!VwaW5M_=_5Vas2|?;NYKia*j^ zrZh43PwHBs33@KBeK#$_CmN_{5?74Wljmf_95Zs}TX#=`XY@OP7F_kKthM zxUrr;Hd9Cvz-nw@l!CE{B^3jY#(+f9E3!*dWUH}J`8>D)-NYz0W3CJ?+G=cOG)2cV znMIv)+`=T%OB6k#Kk;TaR$~jJ%>XN6Ze_G7is*|Dm#yGN&uT+8mrMReF> zh@~b)-@OJE(+lH$j5Z@X3ol7$W505zXVegFv;qzjl-5p~>2 zLwXT0^EryTeV}kZS5e0`u)yarOoo!$7Z&F;N;e^U8DGGt@gEk&YP^imW;_tS7c$zc zPPP?wWu*T>{usxYp4ls-ix_Rj0}%z@!G?9UAs4h72&X_PG>HY7ef7)y?Na_`(ir7_ z8KW`UJLE0&a)#y2RjTiAf<>VcBu3Rk_!|l2{lyiGl83OPYBgQSsIiT9TvsXTrg*Ro z>3=oD=F$WuzD61U$R7KC~_+3Pp!fP^`jy8I~i`)y0CZ`qs_2PwPiKltt>QI)7JbRU-1V=c67zg z_Z`HgI4}3T!uOWqlSg)(T0H!yj;(qFPFH$ax}yXCbj4n_YD3s>`;)OiDidh4;{MJ4 z_U`oB6{q!KeQs+mH`K9<{y9B19EI`n$hP8lV}&t>2N;vdwgd6~b~4bBv*N{PkLpOK_OIw}jawu5WG51dquPsY zM|TW5nOr1C3&Tz2CCt57P2yIXpm#D#H6T^VwIjFnH{Pe;78>ULit4sRb{DJh0Y>2x zx_G7?@&_3vKjg*4vh*q+Vw9Yn7g_2_u7?jZYNGQvI&;)cQE928z*dYIB+5WPqUc|I z50QV@XBqt{P=~zXPawt;O1aGN$C#MT!Lg2GNa(SmDQ-_z;t5U_;^Rz9{V4~LDsnT> zN;8m0c_2jn5kg1N8L*vo2}L+KjZZL4I}Vry{TLE|l2Pie7@gk#|7YCz6w@v$uJLIt zPOVn1GT*xxrE^8xrpMQ!8KSt);NOt14aLbz136!ZugBMq)pztqSbfJ<1>E9P+;Xtv zFlXEDmXZ9RJeIT#A95qxH|wdHt))z6s4IT@s=mHeH*6Ug7&z>(L)#A?ye4UfFFdC8 zK&WR~IP6aatlmATA^-4(bS8D=o-M5%xmbHNaz-K^^QT%e9ogt-Mh^DacIxP4Y-KEY z?(wnMNG#SB-x^9p6YY`WKMr=ZJKbHe_TE(|C8Mp$)<7_o3b!PKsc37fO*WQy{$TlW z(eASz15x#JeKh`yQ5p0QEn)*^0}(f{ndX=Hmo{!tQ2!3>Fyuo7s$tVX8x9L$7l^Zc zcsmS2yAd{T$A8m)4*btN{^u~Z&OZ=cJ#uPp&#rj3-Oj~Nxh6QWX>abdb3&J%w5M(F z-tT0?!T$3j>%z%MM{l$}>2D9L&s?@Fn7e)h{zw5i^9fNW(79tvVTeVQle<7`e?=Sz zyiZ}bZCygBZdXrXPD$G6K|*!^IJI1Ms8OUbn9P$XNqLooc;--ffcerPI$lJazMA<; zIHV0EfAu-o12m3E2-o?X5rUU}M-MXN0Y#ryAw;yd7+Q~L`T_(AEPW-@>Y)@9s;5)L z*HxJYEz4I6>J2iYT;oefnovu008;c|7BewJ{5Y@3vS?*VYv@fJj$XoI;)hDBXF}9!s}!Y` zA_SyTjfAM)`chhS+T$)`N;xrWt1MPCahml2s(MK5p?YQ$*PmqVzQVM!^MdAm{{yrz z{ty24Rpmn&7Kt)NX!#NSg`+2WN*wSSUt^e@i;qE0e2W-OE@wug%7`k#FseubM=_nf zW~Q5H=@)EQP3uI=DT(Pc>SSvZ3jKAbG_hcvN_&U}bpy;oH*JembkjCHNy8nBXww)o z#YvkuNF1hbQIwL_rd@wzP}%_C6hyYl^9GiU49ucYGN`y1qm@50N@uj0R)o+?CaJ7y75W7^tm8H;WQrh`0X2?;OWJDv z^C12!?TM@JCzSLBN)e5Btgz{=Mx9Z!;x8>~8tE!%V`#AA73AEyg`|ORq`w&>*Dqwb zzpg)_@ibjV$?ak42g;uGhc?2#CLIbb*KpgJyoET_dzh9Yyr<3AUQm=$D!OTtA^`?1 z-_YVHf>Hf*arHYHC3`ShMx~%0F^FYtbYH#{*Z3Br6rLp)L8=JaZ!4-#6^X!lmYo_q zJF_Fj)lN~y^h^ReMn-ibYvXSIMnOrsm4n27`awlW8&kWrV-8gSHy=sGl%OZHcWtfeOS4 zl^Y3l`0Z0ZCsY3$!F8zwdJ)xwHtu5<^dhRnIi?+Zx1w~shpLg$lGrK)HTbxopyg*( z2)AY6zp;tZLFWR^?_ctxeETVXlkpi<$aDNd`Svr>G6nw2O+HINmVklT@Q92#LFl`G}U|p$Jhs!q7g>hYMI> zXUa0LoE~F%>P-ffe4ZIkjvAEhBgK`a>!RW~T~+(sU}zofL~YgZYq-rEo7p$3@u-3dvYoI+4!C zQpp~B(`DBs(p}kfTW9Br&{@mZ9qM1UI?{jDhD0=*iZ73)6Fv62Z)CICU|S4_g=02u zmG3_`+m%kVCEH@DP%e4;@txUJGJ0xQG0()j$`n@uye1H-d)^6(WrnqLM6vnC~ zN~77tWjc>FueF(9c$!A_F2h|;FEaP?4#}lR!X|sGDt*IZ6otZVNNx`FEH+MGktkz` zq3TkeCS3~}l=~R*lE!ZhN>zgiaF)9&(KLdr(ZfO?HYZU${?Yg!!-BWXs{0*hP*2rp zvP&F1zBG9%(?S^sjHz$rN~lZil8)>)f=)|6LM+&%s>kn4ah^6XxyogXYDBCM4w`Wi z2N@EHlOle*v6PPi3S)C-jdV6F>Sr%i+_dbe zk%_kN-e%<@Te_`7+NN=Ldps0Ng!6?JnPfbb4J7SID!Fn9(cH!%m%N=A2_++O9Pq(I z*70RATym3kLZMJP5zdA}`2e92E7Xz;9uf<;^y2Qi=(2b^nF@ze5HgO#E%9jLl(?12 zSSfADo=~XIqm%o3+NK_HBb2)aF;wFr*L?2N7!t@n&agxe)7~0?HYktXrGlrCCL+8& z>@xDUI?v^ie8RF;B-+x)m{tbfxF)Z>4Cu+$=!*)|Ko5s0%l*vwFN)IaLKA%i--47~ zAp!g`4qxr_9pO8GD?)ezc#sZR6~DEi^XMZU%l1U#p`@L(bG?Z*`EX}&IFgN|_oRFJ zuUg)-((cHv4`ltH+!Mo%Akm>{XCSmE7MmOySXKz-;_cCuo#|Ca6_4K7+2?HN4qvit zS!P@C(4*T2Y?z%!Cw$39{aq~Rqbw+mZ=Ru9x%xAVN^eDT@qE8lt&7mI1T;FhAf3UH zjGm?(bkJQsg*iOVXr2bZPtu)P6LBWykG^>EPg^>-`|)IZv3G0d;O=e9BO`r>pK51g zR~!*ZwPm7lyFJ>!I+n}_TUxU%mbD_+u{^S3S#-o6cx^V^o9qZ(&wquEWP9=Et)0D2 zmlbZwbmg=DjKAZGY_2VH78YUb69b zm;=13d@|EY=PyTF(5{_2S~^Sw{kS+{0dp$xQ;d>hvMp2y>Ck9Jn`bMgBOVTdf^qc| z9#pK^R0EL*r6nL5j_AP6su*DyA;?oXq}6s4+Ca^Mn0D?x$;Sw#o}}}q5hrt)Io`v8 znjLp&ss{Qma3EjtqR$aZcT~|maZR0{XJT3h;r2XvKQXSkQz(Rjmi`-}$pnZn#*lEj zz(T;#svS=$P6IT`iG84Q4P!W$&ny0vG$)FyAOEBybv|lnPAL$al z)gkWZINxVjgqRZJk!Z|V&`AlTBpT-)!lL#{X1s{Dbf`f+%1t%9PtHBS7v)=1(gjnZ z!o++hd!c$d*VVraBesW#Qbx;wj*DzJCEdQ6VjYENU~={MOiSD!qLiujO+rV1C8#_Z zOz^h|9+Po|8Ji0_akOC}_F&ra!xSYeQ4+(&O}yRE@{GTrlfN*iOt}fIEkhiVO|};> z-1s*W%laD_SFR_~s&6#KNut8El^(;Gg(!6r?Zhra>k*Ca8$~xpJqc1uU!QNlS4bdP z*-Q&~xa%+3QY_!lxzXu7aMTINE-ro`8hiD+;}Yp~B$kS2liP>B`qh@NeeKIxC!2Mg z`>klIyEhuOQt5O$K7^Dcn2BUV$=v03#Eu*ii5xOOi|10Q#yYy#MyN+HecXglOtsOr zL(#3zqZ=Z)pe1PWQpV%1ffrOp<3EPhWA=u0{j}qs(?hkd3=xvF?UL_E5g7A`Qk=Vh zCN(GBFG=jFWzU7w($O+)?P}#f6KBQ}GjQ*hszI-&&Nys<~DpG3zmQ6rp+v6)hk= zLD#-baaT4ckm!Wj&81h9g}CHO87#e`yRo7c;bSBM?NxT#j@E|#Grh!rV4nQL3t`bP+g2A{aI}Bn(6N-t!`cu+PMUw4t$kDkZHSmT%A0aXOL6~ ziESdcY5mn^oqK}V%%B#hvlPNX-!~!AI68`Gb53$e;=34KtcPUCQo9gBSm+E2p)ykB zs+zNvgVwedm-3(p@?%FKCBSObr3yc(?RA?LE)Mrz{DCZq}_b;iYy%wS;S+fDanS()N=n8H_iH6 znFEaup5|BKYV)Lnoamp+^GEW3PaD@YH)15_V~do~fx@&MDv-`cN-fX94Ow5^TB2x| zLB&}e@z!nwi@_P4c*29iBh&%!R8*@>(8;?M)o3qvoY2G*%5W|zv~{<~!Qw0HUqam= z=X--;EcbgcT0X`I!4)))APt?HnV>v5!L)O|N=plfB(R`vX5}s=e~vilQu68I!>D6n zT>F42j=P*0&|+HUT?T!cF8&vELIMp{3iq6XA}=a_^8U`VH;rtMb>?21>x%8$vNf7s z8SZQAzN@n*AMfgkt&Si6+SjhkopHuh*=qKJJ*iYavbHZ(Ec_IcWm;+6T?xmzWu|lv z@PbzUp(s^58i}A)4E>Y?zY8E}?Q4qCsU~Vr-({^3`*3e|`o(7`oAJqmj>WhCs&g>C zV|b^X&2JAz@)7?bpZnk!`-k$2sDZ)~EF2)7>ce>)?|p{<9<&TD(;JMT@|tL)SjXW4ddB57mflFWqL zB5{8#vm3j6V^%g8!Ar=|SZhlvx+WD{7mWK;sYEE3vw3^u$ujQ7RECJIF@{hMY=Ta^ zZl+f{&ma#+B0PtGTj4sC2-E25M4JDd{c=86Ty}kLtT_5`XLooj?Tg&DeYkl4!<{+D z-yaXQ4kWkQ8ofsSAx(6ajQ@G-0X ztF}kCt%>xmNfi1|yg!gWG7`o`zS;g2J;{7ee@8N#$8|*6(=w6vC>HlZeTh_R$gt&v%sX_~OPF26J6)gYC;&aOD&&SQdsuxO&PO+|ZKWjN86$cy&ix@tco! zhKlwjn}T~E&V4y|OmoR>g&xX29oQO8`2Sf1e;g`T|s}KWy_(_!$ z9LnNWsSKPTm4qTRg~!cqrj-<#zLnLZ+<=3ypp}L}pQWLUprdCSlpkMV6wU4&g%>a$ zmmo|FCfr7aMg1#GOV;G6?C~2RL}8j@jWs6`lU`u(2w0M=NS2i;y0X~i$FxlBa`UjC zInuBfFD3TY)W;3VR~B-imcHjiTQaznkG_X!bziGYdmxYrs!yB}%E^nMV-JI`LkS8E z!H{wbJpKX~CxL=c^bZh1Mv|tJ7Fvyk!Wgkv8NYiG2Nuob&(!%tv?=yPM|U$e8FO)y zTa*QL01!fm%Ww2BCheHk^Qbas6@pIw&CoI`9Oi~RC~UBE29%h#B5jg($ly&DbPF>ZyD2#Jv1m|2Ti$DE`EuCOlSE6Jg9BND)0c7dq6n(^22_;8^ssQK|EQ^lN4x29 zCZ_&CW`fz~S@O56>fxebhx zqEE=R?p*VeHyIY(KB>l_(?&+guo9Bku#?9sO8Xcnzhrc76Qd^aP`BR9C>8fls?7S$ zB=+?2%zZ@kmtX^5P>zm*R*q0q2jGHgMrYPqs&G=ogjXr8jwDNc zgw9E5(Kx&rmYvVIMfi! zfJ_A3s`o3cTUD<4 zg~O2xI5;k2j9$66|BO2Wn~aY(sMiwGTP)B6YDk+{j2>8$!*MR^9?cZl&?r<0Z*eCq ziJoXsZ;hU0P%np*JtzfVDUDj0Lor!*Cs(9Rp320F(bGIEn)W$;NfeEE2{q7M4QeXX znUhX-vDqY7Ta2FLVS!MhdYtP)4dc1<4C=Md`HWJ+!ZRh~&OA513kF+EpgDGGRq(wE;6XMIxa?;A)Xj|Cgr=&*Uis54dXu8J-$nePyVcHWAUs1jHQeI z|Js%=)-UYZ)IV@cSGo@mg{=P6$(fT*y7=PLPd|3h>bsY0ai%;Y6-8kPTXebj(yiQ}jzbLN?XBl+nd*aG8g;$>DNB zX$4yQ1Fe`VJj6}6yK+g!S9xfgjIUmj_L?P8=h`LV>kMPr@h=tg@+E1nSCmqC^e|({ zys?z%W?=fho1o(ob7(yBtk9w>L#FgYWufyXLOd;fEA@fw2ZD}D>migobU{nvGlX*f zRNoE0h1%o9eeMS3PJ3O9fOFDQA(f+fWf)Hw%}9?zjCi;%Xa&hQe1K^GPN|%rJQfgC z9JQZ{GtZz<7l_RQ^u8N}c!hGHtb*E`&<3O{Hv_b>PY`iMIw?X~8$oLlglVkAnhOiv z2OPx+mNTWXPZEbQsX$Q^;e_y(zacmd3uY#HL(1w=Nt%Veg%LPCRk_c5Bm?k!-9xf~kUdzH`l$SKb>> zMpmwjBXLCdNOb8jq>#q7|u)Wjk+%L5NaAV zLMS@c5|SRRYVkUMr=Kt!c-~7I$4g#cfq+P?$QcSZ=Rk7ZIr@@PKRRHkWNtNfgn=1`-i_Ql%h8=A!8 zIUllAL3Il>H3NReLL(8JHy&J*qi_ddxN@ z+KGwD@mS!w*Bjbrn^vnxq9wE@An2R-qB3HYqs4vSu;8JTFs_yrCFgSe+?sm>qqG@C zCf=99#D&rWWa#Rs;Um9b0jE~zFK7*fLLvRa8l&vjA@gfw(HeZCvY=zKkWkT^J&dX2 z>n9fa+-Z{o7dP#;6dEm}rxp&AO%5KXb>`l*kg*U?y-QJ=@@xvxjNKk9>7yhh1kbDs z2R;8rQ>i@mtD}-I)~PUGS=xjeb>}Sv&)uTMQ8S~+#Cp^b4d9;UHjr_UVyx3bzo@&O zK<M|VIRhhjt<+}CbP{{~5cbnlL0y?azndl?NkXt0I!z!@NTlFV2YJqL!TkUE7cDv`B ziHUPE?LZYG2?Li=lhlb&PyBRr+E84`XsTOlO{P}9lZq=zyQc9gPbXFWo6yPt3qdG% z^g>(qFGQJEw`i1_8K1*C2_xMiLyXu%mHjHih%+-m%l8v#&H50^5qQEY{Va^ha1Hx4HIS3YZEHx1q5AZXt}cy+VKsFx@QYcCJy7G zxy*eKUqgDQ(P=?21@; zh{wf)36%{wEO8A92AZEKZt`AeSE$075O`&{K;L+QPz0=8w(u8%)}*T^S{(TxgJ8@F zGfAyb*U1uk&)ZGQgaUo@u&+OE*vm{Fi#hjBgL*WblZYzYH!wq3FH)Ix2eqj>N$m=Y zcNJk?k`)S&Elqi3R^Tv8EHT+^CvpJ**Yc$6vebH@ zhH$8zW-Q_Ho+(r^+vi~)&e@R8Kcl*zd!Hfp6jzcIf`srGzUCEzjL8j<-|MyJ-oJ#gxxS7osJU=Ch6*zstZJ z-vst-8kP|=6W45}r6UEr)H3}&F79K#;<1tL4;|<@HoY~S-n#Y9@X8Znk={fuv3z9B zn%tT-oBLvR--h4^)~8plOxx{=j`WICW1*B4T{oQBv?A4)=)t8!J<0x5?~kl(JZRa) zmC^3it?ilO>Cx`t;0Q*pyNb6&ySrMf;wOM)SMJ*Np(Bns{LHVf4|lY7Cp*7eoi6?= z+FjVQE}zP89!agTBbY)8WMexw;u-Hyq#wtT_So2fOi>e~Z#?VMfipDp%&xB{?~o7D z9^_^5?zZK5yiCq#Un$nK$zpQ*$mZQGbf3^jVM9yt<*{xo1N#%%aK0sGCp!+wW_D+O zf63*m!?vA^+L0Izz6KL1tSSd~?Xt7sNW3kGH^@_&RR4hkb|e#SPiEuM=rtSr2h+)p zojdld>F8KvWrk9jmQ18A8cv0h=~c-9o~{Z+R-{v@bbd|Pjz_ob*gX*H-}{>G)Xg1M z@n5m-MBeu0eaAtQGw@t6X@UpUjUrAS{|&^WM&6?ZJ;2W7IRw|;EqN%Lrv>y4Exe3= zq3!R9w7*onPSq~$&jk)Wbg^>D2=)Vn&w}X#;q`Vrn~hw$re|3y3H$9B&gF)?pk&sL zWYaHIvG_o|dw6)WZ@X`oZ$B3IkM*4Zwa)Qf0L4%b^dLMQUZNHL?fLD+wTbSo06jNV zJT}q2!Kt*=yP}EKTzF_G(w}+7+CU<(dRuVkCF{REn+P7ga($%3kGsr%^ zY&6_KkHN=c10PF-l2$HfCByODvXS`F@%L^CStEm1G=z7fWES{7UlR9q(JfV5`0c23 zNE7m@!L9Vf*y?2WNhfUT>Polc$aMEr%lbEG`umsdYYPmY(-Ysha~1A`izP54nv5s5 zb^5#5%R3WY*{;y>eFMY(t&z)9v2F27Kkx!^q>^B1^tu<{XU(X z75LL07$a6i@A9A+yDRz`4~mRL(a(C&CWrs>pcwfm?dM$7k%6tkpLbzREJd_0cu)*9 zl=h1rw5ju#mPEhoL7QrP1*r2<6MuC{;;$`_xB zaFZCbFQ`#s;US)rX$7i%KHjaCsCZ~$VlA%fK`{_lbdu4g|2SgeDG%JNrJ6x`=0=N? zbU;KyJuBn7DUSPVMQ1!5nw9i*LyKcoO%~s1!X8x8e<`kM9~rL;iW=48zU|=vk5?2b zn%2>*{aHqx#f%#s7EQBB|4muVQ#9>c-pnrHkE%wrZz1IFyG%q#rCPL-f5?qP2knwk!wu@0{r$V5w2M0lE zW8fMOU%5&LQwyL)o@~v1A0I<-Hgc;U(_tSxqDQBFj`ST{Jb5{;x%}Q++Ox&mw{)*@ z+J|ConZO;HWOA@=%gzlW7hTkPS>m{sBah7E9mnXdHEUb5!Ss>oqcdN^{hBj5{G~h7 z9f`I`%HP*+TbX2cy7R0AHr#dInn;EH0e>cB+bz-P&UL*B2u#y!Y@ae^h_DZP40T`U6HOX?TjJAItwHRINr^s+d;o^FyW;yTen;4-l=fJJA9i>sx?= zFK?&O;(o*|WJZcie9KFUBNqK07Ib{%Z&aZ(X+r0ItQ>T|<2VMLm~_Cx80NbM`5WD7 zDT9*d2xa|QbCMA5soMO6iA8g?5f$)0MrnnOHytT@zZOc2;RA~PRB3e^#^{wK$`mH3 zASTj%l*#v(O)ns`;?%+qQ7L6?rnRD_xDJJ{fbc$yGi?NMrAz) z4y_#in^8wjdeRml_GmJs@dR1|6vn?{)JzsC`dbfe(@OutC~cNvLr?vl4GlivOH;L8 z5Br@oSGu9b3&VLpOeoLIsoDOQ(ZxE0J!nNV%zLY7e_+&XA5{+j$Ea9X?1S>Hxj!11 z2MkL5Cq`+94i6v|eNbuXIRW&YTHHfQt0TqI{iL%w(x?lAf{tG8rKRm8OkC?Q@U!ej zjL!Yppgg%$`V=vqS+A&THKx_&L#llqYzbd8+oR)072+}F;J7d|p7^K2?n)suuKv{s z@dV9@s=y~1lxIo>tztBc>Y%{R_c#eU{)FMnd%y&pdstRIgqRp355mqf2_=L+$ChAp z?k~!M=7bFG)GkvT4^f4&*^WfC=njQ%ZIg!kiJ#K!&Z9vgx zRfxW9n;CREYINTDB!yN#YEaG~R1~)=WR4j6i#E+cv!^!8=*X0@xD;8%s-UY?lyj*P zqgClcNOM`YXLRn#MYK|xPr0ZAVrq#3|4m^^(kP&?Xmd|9N?8F5F^+5pR(rOyZq#H{7tYIvU#>CU@ic-rU(OBU6KMbwx)D)f3 zwWxjM?8qS`aP2Jyl{tlv6S`sUc?0uowFogKAxVr$F`F4r&ijg&-QImlkoJ2I7QcRb z_qon0yT89suvZ5{w!eE-HtF9RO%8<%iH>EV;dCUDjrFA>+fEIwj7RcUc8Bd%ftF5d zMb{ZaYuDOq)(j-+&hFj^Pv4&{W_O>uvREvxPveJHTR2d-D!6KI@7uy*y0#>qi1|aw zQv$(6B5Wbe2nR5IVc`|tK+=kK6hCrD_ePmYbdUXrAzm=L@ib|P4KXK1lSL8I4K)d; zx`t^ft>7v5@e>HuDR;`GxFriZCt-?cWuwFypv9B3=NT3}izmdj+ZCk+Gz0@>@h?Sb z^Swc*WrSx^*wX7rh-t(|)Tb0@Xy=d(F-m(+fI324zr(=193p60h6(2GOcA1dg<;J8 zK1O;J#xc-9W+Fj{3VpOGpq_*!0ePuexX=9ueBjA4Geymb<<<**iG@U6LGLn!G7GiG zi;U7657P7sBuW`+1}3GypueE*2AT?Y^IE0b6n#uK4hmXx zQF?FA=s3AoY29rvEJRfTHrds~*O?D8G38n8s}rw;5QXvgu;!K#MZyO&nmNp%90f&` z$(%u(USZ9L49Xmo_Fal%DwT5=f#<^uiPfYLgYpH(N*gsOZ=_T-rl`9ZORi~6zTMDz zG@p+XO6ym8GjKJ*I^$n75UFiSC9`A?t@9**qpKl#1B}#TT{;#rtVhTCK~t#L9=4*o z$~We)gj1_LFb3YLO^Q(w1?V)53SCjs<{MXnERU0z+`8SBTjoBWVbpTPuAu_q)=-S} zC-yS8-sWNN)+Y5{#VQhmmQmrDLObmAPRz+S#(UzbpNxn#;u&rti$)e{r=Q}+= zXxlPY6H|%+ZUs{uF6__8;U#PI`J&rXmxfjJS zzSBdTYJNzGb(6oMD;Q0(%fdq9vgNZGjd6fN9G$^$Vbfo*z7Pvl7UHgmG5`^CSW#U6 z$xTAxl?=;%IT%i)m&KNO*G1jkNoO$o)h;7hXk|Wy8p1Lh3_~*%ZMhkcy4_kvsV|_C zk;!q?)01;@XKGVU)Ux_BPX}nVtY=i(1@u)n++Y}cX*U|w)AsA9G72xCEBjC!YiiAJ zGQ^&EH@}%t(t}y31Adfs9N}*ZJ0MHX?8=A@+*Rx?%7^AxAU5+s{cqJmbsA4+plwR~7+6s5Cw07CY27SO>UamEG}R;{T69fFSgvK1!aX-^r!>ua>{M7^RxZsD zT9%$qHs?|Y&FK6gO8Y1V78DzV!=VP1Zd=e@ic&x(TINuHOi_;i1Y>~U&NIyK){kzG zt&=DkWU1ZqxVFUT{2s$d+*kyZ{%;X&?G1*njB%KQGjS8cl(YVWBrHQ>`0hSM|0Z!t z(fx{2Jiz3`47?wr-KQw!2jC!RS-NJk)+YyO=B4j!Lurpw1y-ETa&d<rWs8fbp42(w?;>diIj&IWIv` zzBPaDOJG7}T(DS=^OvN(U`h04OQIKgQOx92#1sd;ut6_c61{jy^pYjfOP541Q`B)A zf(UW>lEhamiC(Gb!b-YoN!qIwr8h~a*-2>_ywUiNOzd?a36Wh0kq^-(u+sOq_OD|W zi_w=aiC(Y8=`3~$-7Q9itp>$YIN(dD*X7lU|V$7GjoK+lt|CLS%B;vV7Mxr~lU5<;Rq zZ1iwtT|jBfR!(LbYOKQW9Y zxnOj%X*Jp9Dd@O|mXcfH>qu895}SIS`j8=hw5jvdI}FMxr?8-LH=LL(!Ba|t)_t=Nc9z`z)S{?pL%V+bnnE9Lxx z5R)aCc-*ZH%IA!$%)g3R2rZ#ZTWct)>lT7eXS6uY^0YWfb*T0^P7sQj-1=(6m^TOs zi-{*0rMnw=K6`AA^q7A&qq0|*43EwPN#(B?mFZ(b4|-5qTw|+E{glvZztYM91E)|$ z4);VZB8s)n@vpGT$Y>n~IlS({t&ixU6KP z&7v(#w9yCKelX{|E*0*M+NpeBdk5YT=#JQS|B8P66|RUR+B-UW&J8Ep?cNiY(cg8w zy}f5%8c(P5p`P~qK&UsDj3o!ciDbBTK|IwJU)I(iDz3h^XH{!{Z}E(4dj_tK#^R|| zHknShC&KAuB9)HO1tl~;&M!O34Y6tgV^6Wb^hzid!!q1M6ft}k`7ZNa<-5*zqwgl) z>wGu+ZuQ;at6=|6-U^Li2h(8j)-NRciyyQ%@#}jAlGY~L z^F)WWXcJoTGao%P>wu14y5oynwryRTY-w$4^+yIn;kb?KFw=hjp@Bd;96bKC6Y+Q6 z>R@oi$|3x%y0Wz;9tm3k{Gc5_xFi_t>hH$izy*GPJQ@j|wV(cyuvc&IJC@_GzvWfw zP3gm0K6ONTB%R)v-v8;AmZQ?n&LH+X1p-dM-|BC*R$-G{B!bs2gSa3jXoce8DE<%j z@9FD|t%&07n5b6lywX4q)Ahx^f*zr#}~#)~S-+mt=EMJn_dVp0D)qYF6{Diorv`M2}8 z1W)_8d;ww^z}Q-8QX>-l#XlzUng>)njfgNagkEY|#c49ck! z+rriQz2+PL!Vyj8?^KxbDx=^0`xv!x(2%zwDDC?hl_qRxt5+*6b!x6k*8B$;r2zk= zhwld&C9#oV8W!^(GVt>r79VDmit{oqGD`K`w4gQr5k}*Fn*zG7_+R?%K}nitHZb?k5i0t7rT;t= zOY_1kvJ!uRQF%PKiBkXBfnfe1^#38>uQudc<=c(TrYHDL#gnS?Lh!47ulK#l_h#Si zzIXWE<9omF!@iICzTo>RLib2p@tzxc+KTZ{^+dzyit-5Kq2i`{(z)WMPxZX&Fnb`m zaoB&>S&3I*%VuO?I1sUVP7MSGB4=H=YGZQ19!hQ=zGQT??YDin4(!-d0z*5#7;HTy zy=C^?OERaVPtII?X7Qm<^{i-3CyR+s<2J2eI27>54n{-0*{-f!U+C~C9u!W-!^I;$ z-Lo@{U8Odf>r=JcKHYOvOYxDrdb*bHyl~}udzn3$+_W-lpK0T*oi=nHhjrm^a~^-C z;)8efEX)6Y$cl%4rswT6h?R~ReR~z&jNtGkHS%L{5#$y#8yWpFqtxNc30`r?uc+zC zWiYKZaV68jeBevBddsKNKh?F%e`zOgRIunO0|rc)CwfT46wo z9YsKdXm0vKaT`0Pgm!YoaF8*`3rveh$9(n3vCz&y6fKQ26i47%r-9`N6*sJT8IH+A zu$0t6TQoNa2o@Ard0<+5kc%5v)XknOMkf^2``haK7%dwPLo9LaIEIlhC}SR^)gEB9 ziW?2fJx3(*zOOYnAh)_@~`*%VgwXan=Gc@$>EIkju3P+FK( z9X*W3+`d~=b}5uzyI4R=e5{%6hC7cgsay3~&V0=fbHEU7>i;&xT%20zNki)qo0^ao zC-x94%^MC zn1xOo2t5CF!$LfoSxla0(58P_({~xvQ{2=}gNo)Xb_uSFK>Q8!O)imDZ+FeDlSJ%( z*3?QRc2O3kw#TsGwO^r~|E57X5Q{Q3w`J%=RUK&tHmhAnqST$2o1bva%ST-}{g!@n zPbf;!GE^W#@^%_^&1XD(XiUwaY%*d{ZtZH_ZyOXF(8*c6#<|-R>hS`-I*|&MWp~oS zsx*ZcS}&hjrKTp(U#@I&rZWNs$v;-56OX@%Rv|vjX!OM= zAsXd6;0U{8k5BQpt-T*ew)U+JE$i$ViVn23+0o(~{k^L%Ue+1UT9|;ySdmmm219SU z-6+XJ7C24y70lrt=DQe0jKY?B*3>fKVjBGkLbDGa|Q5zmF&tUx4a<49YqGh?Uw2M5yw z1O2Iunc$w39UXxTX~+;B#w%dKcxSvPwY#UkKaVR8y=~UE?KDACd|z!@Z}DHLM5OqwNN>n_b60O~CY9;y>+bIB8+zUHzP_&D zb^e}AKGd7H+WVsYS=h2amPv=cx_fux?Dno`CK2fiCE8-Sa4wBg5co@ESFXg7=FIBD zQg-r&8*ttM53FS|S!HML9Bc~*vzG0Th2U0pJ{^yzhwa!E=``vUx9mD;{A=S5;^cL) zRAMlj8)Ba$ZP7-m7-OT`Ih=itf)Pi6>iY<-{7yYl8)_AUY3$?NW>CIlLr_i7Wnu-s zJcWn=E26h-=7Ut6Fg{|b;--%>j72mqPA`0xiCf|zLOjg0<_+h{b11Gb!2^q`jMycz z-j97DQs}gdTWJ)+SB8utv|tpn+*2r8adjQb{e2_g)6}*Vz1M?6N10hJNhF~O65;y; z4_}O>XkHjBN{7k{Q4~*6$9u!uq!Q%}Fo|1HU zzD9)j6%!L<(HVIi43BT9QR=E$;tAvc29*c(NMM-=acQYSf!0nST2claXgS7P&0=AP z7GA_v{^KF0dkA=*Q5pY+S&-S-fl4%8;xZ<~i!oV^x?~Z=m$&9uk%GhUzg6W(4Ondo zl?Dz+NOC2{gxS>a|HH(>0?X)H`x0>E+zZt+F)mDpnZ0DjX%-tZU@G?iYM~@H*4}2e z5Up0A?4xv=4)c2^rl29j#}ly{Pc(1R{()(wp;Fjkm#h7VQF^Rgkb2AmH&=#&jKbVtA?I~yeoy@6{}>h9VgG>I?vF~VNvvApPfAND32_qh z)qBlkava)Kt{_-ljts=PU5)F?IO4*3eUKT8Bf`T~G|7~NZ?pOSnQ2Km9wU^l;&s7! ziPlsc?`xt*)Zj3GVPc9|>_*D?5l?Yc9pd+oDyq(FbtjCU@UVa$%0h;vQc1K(zmf%hN;#Y5S(^x?IJyNOe)6;6U-8wiGoO zmO%wEmOavf{@o=O#}wM~TMfz^?3BZ^24yE!6#btRCt+IA=Ycvb1z^;j!Y>$N9(pMH z4}*GX|LH+d9ZD<7I;p{vbc+7l&^}EY9t3sf5q2OOwd&_-%S=fO^r9i=d;W#^9vDgW z-~qa#ElZ;4r`?3#$INw)uitmL?|k1u-=#?Six*wn zoBi;Wy}^z=u5F}+!tEhEc`&kl>qrO}KVW9Cc>8O6cR3+{s&##!tF0|%*`ZL*Dzvr+ zTUz{=x3sh-{fSKL(Fxn1ZaE^JidVz!Z8-LV7kpdS1-jbXQ&x8(9BvDJEfvU}+1(ls zX7a&gEEDKGGt+8cx@F6jGhcILX4C7O13kgD7hZSWp({d>voE`DFRrNA_qv``Fn?xu zOET!J;otO~ne*F$tMDVW3TK(Be%z@4JcV$oDW7#u!k~Zm7%=HUkzptY+k?Wv6ioqj zT<16Lcu(38dn-5NLD8=&i>yI;dPLD21dQlV>^=+$WTE#}X3YTCJmj!CuoV}EMDPz? zUKAQG-hEr|nhb8RNVW_P6mXjeR(0v(jpDy=>s{esnK!qlWtcDM;N@LB?TbZk`hOTv zyguF1*3l79g*xzjNG^m$fVOBT8V@E~@lLXp=;-KZ9qC>ki3E~9ts`kORq?T{2_m^HQXPuTEjWKw!A43j`hXk z_r7izL$!gVjk2=stwFo^-M97*TpJ7ct)#tq`xU3{IW)3+$HDC_sjzM3Pwn@og1HV0 zw1MTP9=3V&W`Fv)Es1zbV8!xyEYkU=d@vslv<2UOGc^i2$HoV5WQpjWHds?`W`T`y zQ7H{=SNLu$Zv0w%d-2LUde;tMg&fu>4CnFA2hDn~!8IWC-|ZvYY4O}{87NNP(Ywi6 zo*qu8?ELHB5x2vsT-IvK2mH8pC7g`(v_$QmU{7ywWh`VRtZ1%lMLxZJO>))B??xh| zV=}yRID)1B=y8#FcqkgZBj!&;;(=smC+>OiM>|5vSZgSZ_p$9rFdA(2CxY$%ghkh# zw6?~AcF>>6CY*KaT6$8kWVVIWr2gPB>WD`<%NXCzu0*Iz-XLc%6XQ|r8j!0jyV>{K znN}V{Lhu+v!HvcN!HZbAw#mJ$WXMV#($2z6KUB zkjP;`hjax3Q>w#^C%!{y=?q3GU~)UDa2PF1|3!TNA6wS}*k*ODYOYzhTh0cAs3C9&f;ags;b4y(*kpsW@O0Rm(|*e!YIeCPZ} zS8DR!Gf&@t?sv~U_uO;me}Bj>1aIGIEXyyz@a7`*qR=?e3k?U_K*VDvC&`MV6!q<~ zH)pzdH$&yq^MamG!~nGSev`%K=R!|}WgyGhL2uD;(WxdZN#!bqKeRqVcCR0dy_1?ke;b3f?^=+pr518U)h}O zlH-7K`9YzEUXpTz<-iWP4d47vQ+ujS$nvGZ>`d1YnvliZfx&ntUP4_y`v)8W>eWb zGALgL;>5qHEsr*AU(+_Ydg_qy&?`!(R_svKUNL%D<<_T<88*CSOnWT0-FD5XRO4YpiexVT3AgJ^e35am{i@S@5Ulj)6vs@rta6r9FO7nm#-Y5Qn0 zDRrf)<=b+Czvf5m?VQ4+z^6pZC^Y?VlBHcmsFa*Wc#bT;*FtlrZ!5>y9%VX=j+Xz3q-LT5!ERTvRejXL)A<99cM=gH8fuw=b$RfCo4@g3y1xC zco6nmB2R-TfOk(VKs7S;Lu03jLt*~fp<{kuHJW5ND8G)$h5L|E$6q$!6OzmCDG=r{ zIted*iBWECZVj>xtl`J>nVuHaRvR}%!)p*Kf&(Lr2Ol6%bR?Bu>9N=j`rv@{Fl?rL zvud@Pt+k~i$BfvngwAa&?u|4yV#vTl363E$q{toj_hG}m zQ$q34HMOn5lG4VK^1Ak>wuTX-8fG>$%%p#3)iyUpBK3{aYnyATBEuV}MH-vvKgXOu zBQ(2lPUE1;u|wwI-(&R+)Z%GmO-r%$^`Vl6{YF7NV%kOQ~ zk=jIKeYmZ;c4XuB(P%Ol^peqY=}QT9^=vqv%EHN)JjTV=)k#LNDzjuUj3FlCsR8z@ zWYMEv#s`dIezIa6%XUkkFOnS~*Q4ARmHoPYj2awa)YHN@kli>4^Rmf$p1<|2W^@UK z38LUxoqV!vYh<^Dq4*}VT&glKUQwR)^0576N0x42;W88Pa++0=#zG+)6WaR~p@~IT z@UsD*Ue^~^ZtJAr0SxF@PA@{S9;K>GfGX1al6x(E8!fhbl4&Swa6dZ~9MLd6?7tmo zURL~eet|594Vr2%4?D8_R-Y`ZQCjLtcXD#W`qZNi1(ySRc6{Ez>|qqk$_t1&I~s8< zzWaYrDqZ(81t!f-hf=JN)mgp`@LpYZDR!Y>F-EQZ=k$X4o1 zvZ;P8r%+Uu4r%t)Pd-5J0g_vjteBsdOShzR<3`UVM_u(Dg-r9F@D(qiETw^&5zBCu zh`SN(y3ch?O2mtL9R|)$)_iCzYPs<@7buv}drSQd46~zYIpER!`8`9WQpnItK6Z%F zfY_uf&MT1RS0xDT8|uiK7s>q{YSGF~o;p{zl-V~ldpJ!**^K1(6V+_oQV8vS1;irD zrNj3D`G#4ibjr|&NTP+4&YA4P*%L4Q<^36^koGFR>=SF)S-77=%oJFA?z_`L*%IIL z^qudhEwCC4zj7PMN`-Q6>)C1f_yL{QF;j3kNVlaG4lys|VLhscyMROw1tZdXvQ-s6 zfVK2;My-q-2bi8fvj=A?wv&=iQH#6VEj97X-w1MTPD9V0KveGj2z=UK+6(+<3GTWBs zNz3UXt6!kxAci9*f_ej6f>_ShQb1YO2*Ym$lvgVx%99N!azCypzf4Ubspnfpms9Jf z(y=VI!+O4ltoVnlLz8N~Uo*Oh*h!XAoLTFodn*XmTd^3%?VKXKvcZJK#E|X2*ibA1 ztL*NIYF8tub~Vh4=GnV}a~q(}8cI;?!Fd&dmx%Xs%*J|GFPARSGYv_o1*_h$htT&P zV?^j$l%Bd}pPu8OpuI9;;BkcP0(`^lOFRsNS4h9idI~-CTt)+2%@l1{>9+&Qqaxf} z1T}*L+nX#Hmd#_$x1=#y8}d_SD`S)S`MR1@uE@S*sD+s%r#Ve1-4p| zw^d%jV4oPBcW>|}+lnCPHb9*^siw;oTtni=8Pg5(7D0LTLc;9bVs5UtpkA}UwTaO~ zFVMx$L6%RXuj3czmhX-1Xa8Pc#LW)4)zM1v_QRCErI-kMwroeN|XdCEgFH zOi%LmAO%6p^_oWy0Lm92y6Ax_%T<+^JxJbLl-+28Rkwpp7(IZZhbVF0c+>*eYIq7zeve92xfZgg8Y-CzdRh@ZqdxoT`iuwNoVBDlL(0#qf1;7VXF^y8 z9OTD6<;e7`&7x-mbGObXEw8J07?vA_WX_=Bwi17*p zEl0?&+!wbFZz$cqB=^g$!$)|02m2UVpO`&nOkZEi2U|2n8f$i{t=RjJ^7{Iw5#f@u z#?t1ABb!@x3yqo-n%1~i#Q_zKb<@VpF5POcvK_A8W}CgYqqhMMYb?F2I#^paqr5I$ zU*8sdzP7fmu09+oFRz(j9jhoQFRKmGhuN!!jjfE9x3om|t1qhyMNaYfoLEgw&D2V| zApNXxBp3|VR7EQq%PQHLvKc^?*MkxIzU5jD`k{D4fUjoi%XZ@EV18l zvJX=4#FV6PcC)ez0)gDoi-xz(z9c|_nU=SwH}i6o`Ud$0z0)zdWH$Z9Qr0wjNPC8r zylF^VgXAcWEE+!FV;`B`RM|qizcZ&#rd2MYAo)3^t(CMRHYPE1Mq_Af4SB$bhLYx% zl7?_isII!Sq%>03I(~e6O05^b&tmz7jh(m_#LBde>fsVS+do;zi9xPJWT z6K|+#u4`?osvR3EA6gl#su)kNrq&G|I<$%oc}5x|p^Ca8&CLXH}d`d2tPf)@6QCMPH4POxa2Z z5Pj>Y{8}px#lS^|!b~E(_>v+N3J&BMpr8^<2U(Qez{SeI*Fi{CQR-l?jLcYAvJ>K8 zS$0mP4E(wf?S}1oCm8A|yhMh}%q#me;9V)M%vO_Ct6mN=dcWd8zKj@cYVAgi9;zTk zaK&AQN>6+v^DLs+NJWxZDCMU>uaywi6nZrTp_O?M2Ex%}#9@)`vg=v@WRhq0fet^ZRKh7wC&=5ja`@?9-Z zbdQHDk9)DRyNuHMR_b}LkVh-wEkh{~(Wq7Q?%zdWUECF~SiBPEzM}A%q*yN1#&wLg zBj~`DkQIf*Q6COeGVQH~Fe@k(!a*oFaE+rN z`MAg~zspd_iXB&6j1)8|B`>gh`xbsB{!cEV&fz%SGWAf(6vooh{IEIM<$ko+lJoSH z!aDiC@;XIadcC5EnmlWvNrEG+3xwhlo`4QoAc?KtkEcVj@-J%K5~b zSaBk=4@jnbnId|>6*n+S66XK14-B7PA&w>HCc{e1mp88lWQfhJ8K)%0yvt=Zmq@j| zScVac4NzDDx+d^5PZ%UcH_hlst(*Mlt=**5;Zhp0Ye}%eBqforf(`s0&+L${D7U;8 zbbl}f5GPdu8l8bnttD)7UFd*!GamAK6$Taysi~1(w^$}vuhJIb*C$W?$iPD1@Anh> zhL)hq^PQ~I7M71uRX8`Y#Y#R^O_LnSPNFppJEiXVJz+H`O4CQ(trLbc^lBOZsIqKF zCT?a~e)d0h3=Z7p@ba#eQrvE^Q@VjWl!B*1zEcGU?o?UrNR{C($~2(|>Rt~t1X==v z1HEpT?={6ruXxFm2z;F`eoftvz12kr>m8+ahl z7g!PaOW?`C0L{K$3H&|qPHx*-?UO2bculvi*^ynM}p%bEkqg{Sre-+3mWO3Al<35w7xVrrjm|k9$OkMuMU@$R@79_O3(+a z==PQI)$(s*h)C&0po(x+MRRWb>~_zqtgdXJYOJPv0?-1>knTnqGG zpxh}qnaZWx1NY$>mMGysn3N6t38)PU`d?t49to}%corctdW&Qn#e;u1x4?@lLBYEK z8+fI)yAP^JzhdX)7fJYgKxKKzOZy0msc)mgJg7?OiEmM2g($&qzep$PJrCiBEIYux zG@qarM)YW-8WOsvLzcoQp}a&X6ku4E?;fS=M47w&LA1*D4rYdaN4iLbG+&j8ic zT+L2}7ah;&!ksib9o8+cV07TmKzZpCA0g^F8VsTYFLL7ktYzRGctmBHm%@kSHn0$= zS5%IHMVehfnU_(-L75fprC|;Y3CJFVvOB^OJ*A9l>13Oj0}zU_SQKWLTaP^%>d&Yc z-F_FL6slVfh}nSdMCrU5eb$IB5jlXq zR(EuORKk=#SeZ-zxglCUd)lm-xw%KRkILOuJ-j^EzH57RZqDKDmARg?+e2s3LAA;; z^Xh|BTJ{XhYaAD>uc5a>huk`)qHRzl*iafCQ8&8!pvKm+=8BV=tB$;;yZeFCa7%4j zeRZ(pfeC}s>GTb?^kui&Syg2XWuZC2(uT?{X;!>bC{#DKuCZ!VMGd`dT31^>rmUa3}0d`f>S>18io8-9(>NgfWbJC%Dy_+In$ zG1FW4dyefT&n`u-0;h5gB6oYTIZDHK02>`wij$HshG2MNo8xe zwI&!WFO62TMOqq$RZkAj8&k>`eGM)fRx!P7*36lsg7;RJ(IsP3=(X8M*Nk98`SCX| zUHWiIbyHn^Fr2=O{?Sn>`e4Qk_~f>cXn3F6U^v!LTUSO4i?ua$!)u&%~S*0vQ3OC zk8B{2lk^XDQ1%AQ^ySq*ZE*Cenyd8rBPpg8Tcod{r!#6?k3}Eh35ZR)(6+?5xphe zRr=i{+2bH3R$NHLzADtPiHBPd?nmpRp$t{|J3z_N^_vQ+_q!(S+T%SHF07RIRaQ%* zw)?=)wS0_h*Muv=hC01gBw>?)>iCMRYfzh z@HFqR{i>@=n@Wd|OjnI;s~ki%$)^^uW+Nw42mXn&b8NsnDt$k~BIhc~RYnddsBU_( zEEX#T9r%~YA`&V3A)`Cf_?T}#riRo{8Ij|B1mPO;_jQc+(%72Nf79PEOih!RKoj02 zVXtwu#V%o{^C=%XX}4npNAhZ8)9?g=3W^Wlbe5V;h>c zZHUkgSYF;p|E5n2hUtUH;hS5k>%2&4yPD|G`i8o2 zC`^Z{OX+J{t>KdB%5Z&Egs)8Da^t{FecDKAZL zcyaAEP<#fMyGqQUmH2a@vR+B;d@02*YB1EUiGn|Ifm&U;HyGBq#K!xD@^bZY+qw#} zU#hI9oksTzhV{#c(qEHsFFK9IL8Z_Wxr+W`DBMNR-VsXd_ZHw?DB0PNL}Zsx)8nu! zWwz}4)@_b>d0q)8JAI3*Na5Z~R0$1K3oqxw_4y-T_Ik1<|K_lJfeE8V0S9#pgu@Pc zGC@~J5Eay?2rfQQ8Mv#ZVk{>ckWYN2sIKx01F!X?_LbKYSho*s_$X-K49N8X?4T{!RHAJG(`kLlQ(;oFLBZh|C==}+L5A)JcLo`xX7pjSrZ0k*{u5B1x zTVGinDy6rLL&0cy73~>^=!Fg1W~QY(x~90cqOPQ}lD-B=Tj*1($|DCyB6Tg1=3j9@(Szx0e~sFVv9Q}qjh(FZtyK={h7aJ~&?^m{{PJpv2^;$!??D6&&o>OGXf$!&?Z6flo{*7DmG{nQK4@dX)F zGWum6T{cT))fYr|Kzxh?8al6j>RxC}^$=wF!UViTCg{M=^sJJG1svvHq{Z6d)%t6IOpFd!)62^=@ocP{G`h6Dy0v0P)wHeRvuY-`?|aG~!Goe@Np*01Sk35)CyP+IQ=o^@z9XOn4a#-U#TPLV90tr|T)afHa#uzNXwpe# zt|8U1vf4kzk!2L0_FVjH2bH-qEr|B>cp2)S{bxS?(QJV^H+fK)3|LG6y2x z?(PLzsua34wQPG(PzqwmPt{jLw#>M>4uZ+KAYL%rAw}jYn1xleDD=7Rb_pdDN?DxSR}Y_Mo?w z`X{llRti%%F92$GyoL(f_0yokl;GJPhH^{pVssTv;E6W`SyD}9X=b$PC|)7PdvHFi zt5uxc4?Ljkrtm@M?hvE+GJhiCIFNr9T;+G%Us80Sh3!RT3CgsRjwU@eD>kdLmID6v zB{OqB&59klO??agjjM03uPUty(T(n*c>5vs!-m(_)(;!hKoPr+zP}!AYO1Si*d`in zY~=r+{!?Ec&HZk6tf_KDQ)OMKp(*#s?AXMiH0l>WdEE}j$?neSfv()f*|DkR)YZ(& z?XX>AQyIPDpPRE~tfizfx8b1e+PvY>Saf=0s67-MLVGmrEw$xy2kkU3+SXQH5w2Je zYAqj1N7gDzTINjM`f&W4-&$H7E-%@xDcsOjS~;b@YKzMn<~NKe3lFV|w;wQnMZ=Es z8>cqx_-ymw?^jKJ9tQ_mM+kxJJim~eYIt*ZG1Rf zURM^1RPDTT_2|~NZTZBXROu^$y1)>;nK&=73w;9Mlw9obDJ{9Xwu=pm(5_QrGN0d? zHJdK$J!h-fcyC5@+O!F$O_&irJ{%fcc3yCJP37pe&=%G6<{djBI&oqm)Ku48-WsiL z8dly~SKeGTc0zk?RW#)7dfNW`Hy(K4$=eQXt%>e-SjCW3Yj9d)+p4On+HfQi9x*CX zT3b_3Yigx+H8nHn)L&z5Q*Ds0%B!rXqT4`%r9PQD&BS&3dw62z}tfo)e zhD&NH>uRK-Xa~`IFdQQUCt?S~*tVBq5xGkhP`+p!V;=r`AfYsRK#Sln%km`TaG=yU z3FY|_W{mx_Jchex8;s!}Un5t5;?VxGt&30@G#^uhqN`*^YE(4yL3VoOw4kdb^vKb2 z-oX@DZ-^9_M`_zR#2o1`#9I6;qdn`jbc*)SAe!Qo6vJeBWt(K_87#rJq-NQR5V|TN z=xIcZ5e8FIgyMy^It;r>m~O-f(`0x;V2U*o{yN6Kfdd1F22P>j8T->1nUhuOEAVtk zkSE8pM>da~iGzZ(N6y}+bmqv)+ztl}Zp>}kE4D={Yd} zhRhYnTkw(8;Ho;Agzy>-Tx&56q{%N%gfLTJd%cflcvRPO43+n=P<@KB%Q~LbdpBem zg}1ESof!&HQf&!EN1B8bz}!9TTOh2vW=qjRRF=0Z@kUlJ%^U;4RrQ70Eu+#>c6X`5 zx_ZQ_l5EsaTxaL?|CM2uC_h3gX?v*x{&K%T0SEo*%+*U?E zYC_R1$Fv4rnU{Ltf(k$ z2o4R>tL4?zmGzNP$IRcMEz-K(w%avMYTUAs{&y0!6WQV>n=F5Tt-AO$wI5GWcO-A@ zKa=5Z8ikNBf0`9*;)|YPbX6<;4WpbieW1~J8y%b&MF%C051bjefX+yy0~h}x*D^Q~ zl>oKRkTJM!~5o5OUJgEJg9Bjq~?}TSyOWbbssfQox(X)RwXX}8AoS4 zP7a(K=%f!ZTo|}Cw{<2qJ6D~K?X&+(8e>G`@o22FvazwEIXJC8v^8JiTEACIc)M|1 z&}octtU4O4YHw(6j`C&pQT%uGsEKr~RmteVp|UXtjT}6zicT0ehlYhh6;-+BY;40>k1p^}@Mn!0H9u%U8L>(nh88*54%n@B;jIPWYVuJpeHi?`9=2!=2enRM#I>uB@! zh>CvlC->8W*j9tcuj=@&l1lFRX$_714r73kxxFupZGS*j&Fm>t4@!)iRa-Id^w!oZ zTUxf>qV2e7S-7q`PDiHeno4QLTOMf&kKZd=ws&PseO1K{bO%w>klC~ATU*ZZrs+RqT?^F$$fZXY{=N!$rO#_Rr}ERp{g!ExT&s|{I5P*5e?V1#@d6G zbX^HuKHMD4B`%63LLC#iWF3?G54ijRQ#L<|7)Pjim6!# z5{ga-+D2aaMPL=7Gax$?=rEu!8agZYue)QH&FDZ6-LWNPB1L>#0p&efgp>~S{2fzs zx84)G=GcyHz)nLhs+Tyu=DIfU0{H zciFwMyF4wKOg$nb!x6+!IZtz8{iw-yU_pmdP%iZ(E4GnbXb?cCgvZy2LF`Czh|p%_ zO&y+Uv@=lFGlHn8@Gkwm_r+>TybjFjI{eVe`Q-JcK8zUowcj7fBYnm_fHoqZR~1ck z>;1B1wp!*XjE#WJ-Whtb1C$Ken; zqCeFgJGms(fyrCPQT-qH#9l59bsUYS$K+NniQPZE<5>JUPBYQ|u>)hpj^lF|^~J8( zS7lCsjP{|Fi^$~180c9Qr9P9C=9wa)Dx2sy8F`(OJ9ueq`Mw>e;+KSPW~IRoNuCb$ z3{CIrb1b2W4n+2jvs71oDND6G`+=jE#Y%>EoP+e|s;>NndG?y~fS#UP&>uTH)Nww3 zU6A{_KX$X%(Sct{q~PUfF@6dtJ%g|4_(x?s75z-URiS9tW@Xbr!{k@Es-YtT-mJ-< zB-t$he4)Xsd39HbFESJ}=?)LF7b~ixq7F(+p7kq}70<$wMaPBxXRnBjqIMxdmq5r~ zcI&tlNq=KWC7a8DiUoA3g6iOMpjRk^_O2aQ0=)`oWYantBXO;Z?A0bq@9uJ`u5pBI zpReOupm__362AqSuLi6JsRgc6g?0H~3wOPuzUxT!{*F;zdem>KsPGM{8#hE&jY>d9 zywNDAa;O|)wVMnT)dc;$GOR0L_=CyfYbMIzpn;#NaHb5#4I zp`x0U`8Fy{8LhYBKrRh?qPgEb8hbCKOtrrNy37ewQ~8tVGOONNpqtkz!!nvePt>t0b=UBK#i71@#6$jvYEYe9i? zhdveit=I8-fsj9n5xe|Nlj?hEYL_=ObA2&F1MS}}8)@2a7V*lELTvb!%6^WM(pIXs z4HmnI@IQ>0D#v8sG1P4%?;7gb@IB)dRCwQKRJswVGpU9TOn5CiKC#3<4J8Uw;$MbJ znLjKlvs;XhRMw9y!t4FpC|t++*igw%y!R7BKg9@K%CXu|H@tmnsOvYMDXJ&GIxdtl ze{RB}#2S=?G*riN?em4nQkl&XUz)6I_^(X%b4PZqp=!hLG}!) zBnKC%Eq*EVLrm7`6_Z0L1z%eIH4l<$6|0_3uf=k^zYx1f#}&z8md=TP$>D~|ghKLY zH}ofzU?a4Q8Hz@qj2CH{FIO-*!enLHRywKVNQM1Dh-5d)gt0A=91Y@v7C~kd*@VeT zt%)y=0a`e^OC-lKkCz;0NxsbY9m(;AN;DCoEew@5Gy$qm4XQ`Ft}=16=p;j>0eF-D z$4uNVrW6r7Q;_6Thl190lGBQ0CB}$7rmJjW%bH;f-{KbJQ_(3_lESgBphh977ZhJYlOs8(iosE?& zZ{cj`U$z2U~1M6;vx)Er1<8@{CFxjo>-Ac=DPZP!gG>s~InJ_k3)Hr+R zs$Y%WuM029eN4s~?Irg$RC*Svru_`Xjo8Y!zoPm|ck+OOn&xq+t)(OnGzNL!E_qOa zFrWU(TkBvGrusJrJw(yfYFVkS2aTe=M?xGb%F*oE6q8Kf;9RV6kO)q@w&G zW?n)SilY?O4O_MM(Z+!Gz-@qdBt9NvDa0W>Av)Gz@o@?!T-MOjjls?C3|H28 zOU}$q_*?8TzZIQjLO9i}B|h5-WnwF>=p2)k%tg_24VCsH=y`^s%_q-iYjM#uWUCuq z0GJG_F!jw=DykBopYnloLusUzkNSj4b}2>mRJ1nxPD7o^W3r1;t_D5}#UV0Xq?-&7 zgg9f_sr#VTvxZ`dpIiV2ik^AZ$l&5r`NEM;nZn*hjxf6#XO~DyrWe~Yx0Zk9qNMh( zk*QCwrw7y?mzb;@#4a_I?{hSh{zhS*Zn1xh-j@Nbp`Dj+`AWp(<(yse3ZwXxTS)TC z0wp+(aAC+2RG6!fVkb)>os|^PyV_t05!|SP$!iQnNKRf`L=RM*^tUD}u~LYyGZYOw zd3_Osv=EH~zjK8BN(d%zFccS-Xm&Rm>h_8^IcT2Y_lCN|uRj?2T_DUYUCNxxo&6Sf zYx3)671DKtCQ-)X{bRKH76 zy`Vsgakt4LyW~AZyl#WMmt`d`QX`{shy&hd6zCz7er)K4`KM)CGOWP#J&1Bh+U*zSD>L#<`a1VM{_~QgbJge?}&c*fY21Sg0sBPTr3Z zqc3t~%?-R{x4}{-(X+=;2|yBx7aQuz_8Lm&HGf!QC>WA`s0wN=YC*9%ue6b|mKsdX zLllB8GgO61Hj(T{iskxDk9TMQT4AVokTlMfh9WE_|1Yl!*Z-$H4Dk)il|TwTs!0mF zB!F&<0=yyUA87Cm{V$#u!sj95kz zV&&J3LR9&ilO2~7yXGiqF!_eby5a2a#ye6%iPZI*ChT@LZ{=D(h~4BR-$phRUCj9T z@zy^K71w;nQHcvht8&zU#RBhI=5F5a6*6ci_&)O_KcHt?QEo*2rzMf7A@u(;6un3C zL#UFsuUO}!A_YYiVfeQ(I5zW=9~V~CFmZ{_IDN3OB zAXR(?QzeF?)@r??9;*L-W}sQmt&#xKc)GD^+zQNAW3AE{<5%)U!)IkhFLqU~cVd*&L}A0uH0#ALVe>4}FkCI29`w>321 zu~YfCGgwL^m9V{`c=cVKa*m-+ua}yus4jG*cHrz-?R+q%!ps9o?21w~1`;|@P3>r~ zgkACW`HEuRX{Dp8af!7p@2sdUps3Y$Fj|K6stQW`n7NObMDH5V4f`;0NY?9xL%uhZGU!6-{vXrLrAr>1Fjs(jR81 zSm*E}9vL2s>=7pGc7aC{I?kV4aK(x6QLeDr;pp6&HL(wL4I_1o$vBhY)Uk%b9kmXQ zGgNwI$>n%O`BO4^r#Zn;Or~U&FLk0Z$QlNS6RDG6os;chK0GB-r{LMC`ixyZ?<=QS z3Rlh34HY$|nV+F3Pa^3`7Ed)g)8Ksc3#QI83RG0;Y*Yz(m$ooB?4M&O#%Jor=Nbw( zNS#Lv>INKl{24~Rq`}nrmc&IbP?S5d{Ah~WimHNJ6SOc%Lox42@yIV9aS9rVm+CZP zGLv~nmr=M8C#|TzQz123Ys+(N~kX`FjOkVlm6r<)Gj5o(%953b&;XYu2Cvq z2?+S9i`lTLU!kNvk1oimReo(eWLLkSUg{DTw(-`bg=p$GAkJqhwRst5s;%O3OCjr( z(!pI}sOwi(Qo0Zh9p`pf7kko6T?Kkdg7WhV#`(eO)eh?G?MISpj2bGDc z%Yu#TJ3q?0&Qj!~ICXQ^n=tjQR3@pz-Y0i0(90LbA}`WvJtHsk>b?U;cXxmP!y8y4O&b z;yy#AG@`_x3`O%w-OsAA{}*_I@aFx)6OUz$6R8JLnum-?_AkYR4;v9;Th+A$?DMxG2t?W(QK@B_ZuoLVR?aSx*41NE1~{GcZJbQrYnn5$jS{0Ge7lz zChR)eqlO|=4K#mIROeJ0-5$$rzmc~j#O)rpl+Id2>InzU$JZwn_2Yz?;VDNpKQe#X zk>w#c)aN|mUv%IR-xSvFe1J=wTBQ&DxFGb;S`vBfLD1(6l^tX$%=3oIbWvi+3yNw0 zK-0JG{6$LwPlA(B2QDt9t=<~LE#Zc+gMD14%l|52aLL&7VdxH-7 zUN;f95&un5-Z9RPP~R|Fa$@tqzbndiOQuAwpodk+l6?$^IiaJ?l`?<3O>?4cW|{^_9p=suCU94Y># z&-^t$$@)V@acP`cp?vD+nBmP7o#Ge?k*hMNfbgA89p z_A^7N{_`F8=SEChNtB)V?HUu7s4M6fiuzr@1jH{*7Ntvl#r9zbg}Je!YsFa0C{LL1 z`mz~lol(fz0((LtwH}7vpwIlK$zRj9j(w@HzH<~+UBe~exx_tHZDYe{6j zF7@_{qC6?ikEu5)>K|eNLnpBpWD17Pk|Gr44|bLs1)7PhFm#qF>JPx7cV{{4-C3c} z^!`Y3&Ca0FOBjO%J1ZS_YQ~)*V$fBLw$3VhNcHWTx3hZL&#_zSjk;ryS-Ah6|Hig0 z4RzMwXQY4irr446vlc(=`gbpl|GI2cXFYf&NffmF@|&1S(1zUliueLHRn{fDX!agyJ9Vt|gRIUeguj zk0R#TXApV>ztGAnVtM+R5avtAzJ>5C5YIN^uN;akfo@)vt#YxE@q31=3R^=mN-qn$ z>9^_s*U0#!Qm^Cp$Yfii`<&nM@7xaP_KND6Kt<;$x|Y<;%g!}f3_nG-Lx!rnGl{XJ zGVB0Zc4KctV1Tlt?5Xw5GYVWapu#&EidL-Xd_{FIso7Cvrpjcipuxbdz)%p|N0g_BOp|*m zk*=813hNkHRoUGrXvpoWouq11Vi�c%^0U0XvhLQ2X=17anSm^$vE55^@NH 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


        ^8NA?O@I(p8deSIq% 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

        8`iI`1sAs{m- z0W=0wgTl(>@#D|~CNJz9G0t7|u#q^YmkR+{SWpPCVKoSD0DS&LB?hV^o)02SP!CKd zEJZ{%G6M@-2aBPC8Uer;va}TvS2_a+NfaXouoPa5LDvr{2plGZg8J@({U~A_hU){a z?)7h_Z0|RM2tvgOT&Hn`TecX{rC`CI4)P|af-Kn{NL~J3Ju)xEucbw2 znjfVZdh6Cuh8pDb^cY`~i7T~2MWBA9clGcr?^P+lQX+^m1kaFl@ayDKNQ#g1uwRVC zsdNij8Tc9fC<8vZ7l8A}Kv=ISA_DHm8B!Q=MwmZ4&{&Q$LSeBVX~fd9E||#>w$`%) z4a^6EN+(U_e)VW1xC!5|O#i|}Dzk9M(ZNWs#ut>5B-Vwb6oI{FEud0FV?Y$34U{xv z4{t-2PlcjHSfdm()GCXk9O72DNtA4T=Q0B{SlA}|7B zIubXy&h;MZUh#PD-n3WGLUm4aWNQ&L4AU)Q{=w9biNG9>bxn431xBSY2BaZ@qN>iU zEF7?HdgC8~qd&SVmIA}5I5T8Hi8luCCX#Jc6gD*)uHg0g5 zHOND9mR0~&;ZCW*)am(rP9Lk##b_Kfi?o`746AA}v7v4~Clo3(lEiRehGiUP0vUih z$pM2#qk17CCnf>_-wl>4?ox_-%}afVG+M~zz~n!a0B>s$Y$w;U!hTt)S1+qsM99K39F^AkZ`?TPL?fs`C zHk;$un~LC>kUYZcyZ-N;HfwS4sWA-iL1U=zYCt$mcupir^#A6adTA+kXk6IBQie*I zxSbT-AJC{V*_0yyJqDGti3!`B-uCnkZNCaoZlssjl2R0I<7V8_#3*4=-B<}iI^scd zHIni^aGW%($an~%Rn3x$UXF@SD9NNUM<0~J3j*%F-4z zk9xf!^+9CcDU!dD*OvhCaqrjl``wRNDnxP+$3rlF5*#!nNJ)XTUmM8-jhfaC|L4g{q{eEI<7 zThmAQgRu>2!c*L+rGf`iFz>F|=@VJqMiVWrpo|tCSio_J6ytKi6mtKXf17O zE4F+vDn-X+Cs!SoTKSIGSd^r?C%ms6ZkcnLvS~R5*pMaCs|!&t)4}mBL7DiMsOXWn zW8g~|NN_71X(Km)IvWbs1ZH{3O-ITlhk{(V)vg$5EQh{))gsR(m<*b&m#!x%1}Wnn zAeTqTv2QB2udw(b@EfSv5X8^RsW_FA>d19T`4Y%HJyJ7g0a^LfCq3*o!pT7=2@~gp zrD(OGXCzKCFPoFWI3t+{_ThI{za70gF6%zFeoFi}r5a16-d=Mi8Dcr-q9oYY9 zVZ?Gsg7&x82Q2FYc!IW_QQBO^)rxXSVjeUjw3z)!5?*^{^RT8gCly5$qzSF6?RFg zT5nC!QxZxZRj&dM+PvZo0h1}r60g72i)UY0v-`yF(}vr2)$Xb5tMd z32Yc}Oi`Ri29%&2BLcEMgZ?$b95E8v^k07UG=%hy*jfs*gURj~xLeRdu#eFD6KZm- zgiTi(W>mL4PNxCQq}wJV&~Tn!`SMv`4XQhR+{2rD5a1_|Ue}2&0jIRAgpr3wB-UFJ z*dvr9v+`}o;GpLU#?;Zf?-q~O05pfRyQws#ke;OBn+pANCXU?YJ7ZcXgQ!*NWxGi=l)8bI7p17hu)( z+w73WPwu)nF{ENYTRD;E13)anY62P)?89fQ1ebuGl6gyTYi2@70xjT4aifCb4X4)W z4ozV21h=%#xB^l)iRX1TwQMD*Cd-95tk}J?tptnmrI!*bLFi&*nh@`SH?;=?VVdO? zn=G{b7}|gNAK?Xevz#uB?bhhc(g@{3C0l$zgps&~y+^2X2usHAB5!wY7S~4uje&U) z_(tI}q?+QC=j%U96>KCrS7JSX4Mh?HitLxe{QLJu3%e|=au5$CZO}KLIVqBr%Eu>j zkrIyLhC!N7K_WC%<1!Uq0q{j$Jr#UUav~lTqVs&*B;TxJI|ecMTmI?;(M&x;#~o9Y zho)i&p(_FB0ybcElcEX9$ccdEIAXDYLAB*}BkezXK+u<5l(mCDkHiu+|MEL@WQ$dy z07Ea4XDNcP5iNUW#=W`;SyogiD9D|uwqPuVY8Hx&VBMhjnwOzDDQc4$H8n)@BFAwe z2Tzk}q-Em}Xho}->x$qjtI8KSr3#={c3suIyR23e>{YP036jFtK;GZ~MXj{zM$3hr zrIV!M1!1DVFdDQhAiHyTk2f!Mz4=piR09S}rU=Auu&z=J8Z`W@F__ZbJa#RDi~h0y zy+7KR8#(~uAZo7FnrU8d7RR5Q*|~G}Jnr!3qvFBMhsh4715iMaS_Ei6;$Qtp%-V)9 z{Y=6=awEKSG{Zz~G0#sFa0e7@NK|S@nsgWToOS>`mrMy+udWwLgns~jQz}b3U})Dd zeRWCy-G9?=1*p9CYA~|^Yr$LL1Lh^B3D!%sIz!$c!_uBXeO?<+Oe_y)(OB-HIV3_NMz|-#>W@@ z@BSyKm|>u~NK!k<~v5g(X<$z%qAR=@L0_N4;Vh55*^TB6W)06KcuU?t zz21X9Dbv+x?DaZRXwa1rbIl~WxRbSu$=fOa$A-{I_eE#OV3NSL=#!9U5bgFYE*!sg zPGbB}5FZAd&>lh-m!P$Cfu#h3ZMOVjj+!-&8?$_>0@c;AvHAJ2J9@LTolYg}I7Jww zUaPUZE~X5Yi$nHU57BB?HHugTq8p;Bdzu%0?q_l>k3=Y>Ryoij*_@asor^`pgJe9c zNckP`Pwrj6Lrw?SHnopL^#9}!r^}`W75a-V67>pP`GiR8vrXx{{&QKOn!%m|V+g=v z319NZ2@vdEm&NFNyqj4UBGr-$I1GnO@kiKd_I<5?RiKV|6GzuIGM&Z%5gW!myv5+6` zKBq-atkKro6aF8v!_2duBRvsn&CN%p(&f|G7sEkQX#Rj zc@`T2k^dnU%-J#f@DJ~Pm@$Y-fdEIx>G}VXzSOAR~Xi%_mDGps~R=0*k zjU(q3^O_j&1}6B;81Qdt)j{U~duL2FK*}W)* zD3KUVy%IzrE0QGy5E7K-kUC>r1HMBn(=WKD39i)4RzaDD-2+6_nxl-V6vmrxQ+&xp z^-a@qhCv8Vo?$A0B0>OM=TW+V8P;X9x=!4@@gje4cQCY1V>40_o)O7>j4?=+zRrrC zf8h0)xwKq|86jww8(~-^%5c>H?m}`V;GQM%SV!y;or4LgCObAVEk|R7CS;lbe#L&r zijh;ikyG?Ug0J3qo@~Ufq)Oo3Le{A~Obx;LDlL~Mb4D{_)gw_|%&xC{5{3MZ=urSz zX#9UAc#XS&9xN8|RwyKAX7D%0g7?)r1ML>R1A}7tHXSzw!PUzCsrPqc{;$dq*8b6= ztMDt&d&8U0d-Sa5z2O3exnL7IDvE$6%0A@kuc1rcN2CuUh@B6F+_nvz;~%0#%!k|8 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

        HdXStuHt9O=EWr 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�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

        40G=x*fh*ss;-HdEb*2$~rO?VEI z#WbhpJ^kG9EiV-|E zPt{@)FhUWkP3y`qQ5`WxO?~8SfkMayr7e`+5P{2FP6-+J%4FbQqQS0ZQW;4a8nJZ~ zy&s|P7$)2WJ0`d$0XVUQ2TR0LHYXT)zNp1BKaGl+Zc@i@mt46A9uTHQagb+GVWH-t zhpMGWgdqXAWa8g!LwrzzKs|m#H||oyh%As7LOS!V%T3rb!bgRN4w{4#uL(*Y2_|G3OaHN z2{#LE83?N3AjB3dLopfK94`ryD1k302@PtPRn35oWO|6N!Khbp?^Y%8K&>LIM~Wy4 zPFd%}I1ejl@hK$s8oTO1qDd_Bvdje>$Fm$Kh@vdAuC|Ybw;|Va!z-_*vAiAQ2Wo`i^`#Trh`+m#UBplhA|bARXTO!$@HHtxx*|hQzp5gb8=Oh3=H+HC(1~4;-z*dmR$(6(HX0wftaVgQ#3&&CmpaK zLHpw?*d|C3_To+k?*f>=T~r?@myli_xxycV;ylP41ma3GLOLg~Jj@UeYc{iZ>&cT} z87-E^w&zC1c!I!V7(=feqdapRn6eC>|BsN#eJgFalI#|l4H?_C`*T4Mhv0+o$$QB_ zzoTI0W=8bk?%9dVm=wZ?USS~*36CHEzV2gAOvy8cCSeGpLc_AK1#*3KVG_70)tXo% zA9n(e^y7#ch^|TUtN$J47OwIT31Ci9F$?fK?ePWQ_dXNHcG0ZDU2!a@#8n{D>Shr^ z2iX6_lL;C*J;_1S1ECbR%$>Z%^>deJCu_QBZt_ca6iZ#2qfpl zzW+!==AOx9hd+d+J4~e(cI2VrveNRzdp&ELo{PDX$?rz-CpPoTNWR11%~K0a3IKzs z`VMZms22Oj9nD8|DFYPSzoy#+vj0btJ-d`}>NPdSVU|`|d3SSUV>VDsr6iaBYn3k9 zQO??7Sv!2&Y=d=8baG$p`vk_Z3@Ri3^iFcQ(o~;wQ>X;$J>>@R zx=h;&M*&v3Y!BTiz=>0@4GF@`B-~TZGkF8-3+TDiaG|G7z@}k2O>TGkm48D`?kYSF zk_KXNVu2p5;3xm)v3a3&y6dW|wEJolud!fvAda`h0Dw5jwCVCy2E>X{wcvnzS1HtX zYk5#|Q7a^mdIExW-|D+P`TBodEzQ3c8gO-`R`8(|rdbVDw=#2Vut|3n3)#dPk?jni-) z9kF*BH96MWSLZ1+>-4L(H)g>L0Xk7&)8huv?6k6QK7xJ_#&qJ9pFtDyN>Xo19)8jG zn@e}EtKc6)W-YImTa%MlTJAPU>7Zp1uB2#-RYX{i_dyqHX3{&8#mAq6DO1PZX}3E& zZ-isZnyjq%BW-7pZDl|TAolVXGB&p}PXW4)2!rgc$9C^Nb+hr{gL9aanwHO^g{Pkm@Z?AF^KVa zJpJo8bK{htSkU5N<2sMctZDCuJu%8eH6#v}bv>(5q!Y(16Rz<9{_!ey{KxyQMvO&- z*U8*>Q=C4GnNdt=;8zN@K_j%@_~Ml%dPuMi9y~bNK!F%E+eYK+wF)G-gj-vR0{G($ zVjNT;%`B2BH`CVz;`HlKb|=u}%|IeK)6{I#&(msIIyWkd6Yxk`D(3Tw8q)=S4tO%j zId)=x;!N^=J@z$>?+l*b6Bu7;9V8F#$QE|}k2Yipxm@-M)i==35#&Ab^EOEqtnS!laK5DaTTF;$pN*>x2iI%Qa8 z@#!>AkET4ILooMk4Rx7L|l|6psuM8LCs`(6TnfUquJ^-+!+&83@n z2mkUJjNEVFUeZe@m%YiLAc|6HQ7dgPPQViK*IWAfIQTn@{jf%*MTw^ZMl2pj#>Mn6 zjDvXs0>gPU%mw{SdRlZ8Le!&#;c5VhO$Rw&4N8&<<~P2HC>4xgXZ$*L2qL#&r14j} zcF78ckTTTL{z=T{wxUf5Rc4flvlVzX8*brb9pV2K{pE7~hU6JLj$*RMlB&iX>4ZQJ zWuoJf6pZ||WWA8PE+pAkGgdI#@nQxrW3(=T z28Aw{h5x(I>VyRy+2--Qp8!g57*9I+krRVUn~q9l<+al?Ea2VPIT)dRYKa!COCUq5 zyP)3JuQ0M`>VpIZHmT9X?16DBSavYPWJDCKc}4)L8bP`$N*Isnw25&mvY}p84j=W` zZX9**6{oB#tXY0$%dQjV96Frv(UkOpTx|-?V>=YEFYu4Yu_nH@SrSswXu>~!od+SJ zJ;~!<>}-?OCX|f?lUZXeHQ%h<0N5^AI+ic`p6qT1(ba)VB?FxT5AAj!J9Gy>bOHO% zSK#PmZld4uL(gR6^2XX2YOX^CL#e!>qH4KnMH$x@%`K+6Wy=-le&|R-A)}Jb+WD>^ zbW4EKP`JP!;%8llsYep$o`s;E>HVILB~s;d%E6~P^%VTI&EWs}O}#2W@B3#LGb*T; zbTA54RuSfXLKfn1PKK*x_`nk(AvLgdfl)2d0U&4+6F@fMAY-ep3z07+az3gJ(L0_%NhY9D-Gwg2H$B32kV-umJ z*>yt2lS2?|>2$CA7I3MAXRu>8wW_vGX)Y6y9B+mMs*f5G)3 zj~W|$7N*RpsnO?Fyu2;%pYqbz#{D;zpZ=N-9cB_?2!D zFZTTj2J}Mn2h;EOV6yGwhTN8FkMg-%fetyj0cBC&P{RZ&VYim6GUIe%a za2mLWsGj!3TgS1KzldEYzF5k@HIw+s;Zo0QlJekK!j}+XV?v@a0}ylihX&UmZJ1Md z+j{+W?kE}~fV0DFr$I*ocZijd{X_prP&qEA_Xk`^h`cvU3iX`EpkykEijTI32qk!i z;{%DCfQ12ByHki=$$4f2H#K~Lt9@fW4xk)7F(0Dnk;~yNyh|G{sjeTC<&sW~8N=pq zOy!3&#mlip0^N(3T{HsS(*scQw2V3NIuL$c@5t`)DSzsp^=8hTK$Sib+R6jEMg_6pS z&BJ90^#i;K0`SOP9wN^YFH({;7>F5K$j?hO4@DEm*8of}qcp%`cD}Tc-uwk=6Q~a0 zFeRrFj)C+n-Fy%eI7k~~S@8WXG{f+f3ja}|HCO9L*sx{_a;((mYV2S;rrK+GMYMAh zB)fqCXXg;t0Mx>35~WYs_K>W|Fs{SkPrjm(z5Uh;<;!>PF1yJRrAf(ZDYlQJ-dI@~ zqcPr-RA-vq5lEh38F0{(BD(||q8dGvBW9sX5}DDVK{0E zjy%p)noBd#n9<9O10*NX6q`uNqdZ6sR|0W__Y?|53QMz`3UHp>H=Ir|vWfJHcO=zJ zxp@u3O-u)T6Te1=ovQ%R3!_QcxdBti)nMU@hKX>^jJ{!nm#_NiFs|w zg0=l9*DaMstB2FKA-2Uxwc14MBze1yWn^xE9b_~V<`kA4V{5Fa@vnv->=u?~CHz8u zf0+D{<2X`p9K~e)3V3WJT%JowglJV{9!XZK@c>$G=|aUiP2+{`rV%p<{z&NX{u+Xl~)Py9Ty;y4>vE);8~nNl|abl8x)*Fxe0JIQE6i9u88F{f+zh=92dQwIaWFz zE{VK*LxZY8N{&h1U5~ZIg~b4vG>aLRFJv|W9ODR}@z@_K>AZ@R5X59?cp+QHE2mH@ z4hTMiJ)lR!N(}~|oaR!}EpYz>!~@?BKg27y7SKYlJUBb>H*+G$t6)<>ZxB0jmWW6J zkrpypmD1QY%`R)zB8rk3oq%=A=kLJ{bYA&K#J1;tW1?crKHZ3?GxN!>8gU?(_%U>9 zs?eW4mrOO|JX#(Ds2lu6-|JD6J^&ozWw_`T7?$LeT##|^{NS(nc8O%;mODzgB*qd74MlJY=)2YLH& z@;};fF^5$^dO9RHmoX)?sK~WY=OPEL!=s5V`H8+92iGO=r-0*}!@)G&u-%<_2!hgY z$fl&1Ee3tkYR*T4yl$6)yu0VrDL}0Vn0tw<1yU}K%X}r$3N^l--@&n@znhCd@Y@{^ z4n{alh?59u-oqxPua*y9?|MdJJtx2?ZH|xYR;8Sy6WnnS(=25Q{;e7v;{N2)#~r z2{^U7-1f>sU_t_6R1P$5S_1?L#+qhDnz=hN!g=9-21HVXu1Z6a;M0q6+fY2>>OIOH z$0m=fH4S%>(EJ1$fNM#;3g;?Zrxf$3Y3^SI$Y=Hhu;M)A%X;Wn0Y#@eHt;PesUFJ= ziA&P_9V1Q(2V*7q&r_p`g>c7aFjgrNS z0Z~5A7&RbOO4w>pe6CBDS!f}T5%g6kB1^`zyh7b|*L7$WiB(Vxg?@~P)B@&DJ;)W1 zyX5{q(%u73uDiY$-I>#SpEKp0Gks@vcD9$&h}#>B;@Wpq6uCti zxEdd10m>$k2BF^qx9NZ*XJL$@A&-<`1g~M6)rCz+rQE9_4yJmWQ%c4`IK(STPOa0I zNnHSJxy~9)kx%Qwmo=ncd7XnlNq{w&EyxbPMPs?N&4}}Y=@bNT<}{y`Tm- zd|AzRH7tbT5|jYGhCXbFW?HqN8W6k4Y9%QCy3%fx8Ep;QtCe-IG>0&=e$?|Gtd9!~ zRoc99^3sBc_c~hh_z}Pe3?-blrCvxSs)PO1NFBTk!25y$ls{hY;rkxC(s%j>_=2YZ zx(6Ry{7}w%PV+o|mjg(V@=^hT$gS9)GD#hdBK`Q8pE{W}9W4)Lbb^uRonDkft6Ui$ z@l-S2>v++vwtXXivm;k z@dzZ`-y4%{pHKml3806zqGYD6mu9N(D9(&;bBii;(77QOtsG%Mtg^c)|OR+KrCZIY#V4> z4HonzYs3lQKePe6#%>@z#l}}MLIzY`bX&DbE?r^jI0}SS+2h@pjROmRs+CzxTJB0hoo!oSU}GS-YKXRP=J0 zS-1i*1qoxMRFMzRIZs<7l}NNjj}-9?JjS1(Y+dZB#pVY~`L(1%gF-tMyM~x zGc)cEqXtM@YIG*uf7f#Q2sO5?yfrpfc7QlcTPCE*WE1b_Vi10UL9mmaBXSEonhOE} zc=K&qwPr$LYK%#IUV*j=n{mKhW!SGDE)JjaiUu&t9^uG!{uTLB3$>Y!Vv+1yZx zUk(`wd3Q;GNSIABd0nfPYp~)J%zo?>AJ4?s*BmeY?>7cU8x%w^C&8=k@P(Sx*y0GM z1l2Y6r4>sL8cwWqk2XL2XT^l##@|e%^B%mc?&$^XZDa%NJbe-d7}I zeI!t?@%V{xdGJ*&WyL(lgt@)$O|E;>O}7bbUV*2sW?EU;GUzi)qPWz|;?Y@%fq#Z2 zOCoAiTke*XX{grFoTcOkV@2CqGDd`H#uG_WHCTIhA`7mm7E}+*Jhi#F zN!VV`tVr|uZPKpPp+hH6F3mD?tFtq=0*}}W6C6Fw1B#8T4P>M|5(>=k?clI*$=szI zzPhKy;A-M;-HI*%W-R$VuR}ACRxISCKDP#46-(DgwrN%t=r=7Y8zD zBcat20sFL7Ybcq*Hji0jP0Z?!(r@MPN#vIKGn`Q%FyOX-}VFf94w z$0c%q>g+omn6SLEl`F5Dxa(ob-E0Dqpmq#t2TZ3?lT7iD>@lJ6Z1Ie5@&JH3km|NL zcr@dV51?@S5|$WZc_cbFg@@-hZJOYhu7qwGXhQf8lbrSu4Y&5sxJYV) z#oDj6U^^|8IfspT&H-JFPcg+}RW0VL{*cmg>S<_eN4K*4G=!xrjulE|Az!JXYnM&I zSPjKvvH|@!;9#zbU;O$|Zsyx^ee^^TJa-tE>B{U?xN6s0UXB9z5EqHXiSUkAz<)*i z(M+&f-J$|6$65<|;}_p)DEn^oT+S0vH!&Rx5*foE^+GNZreX1(Nwk?SzZ9@_E$hj} z()bc0S?kh7=u9g(a#TtFW__K#jCJM{G$+3EnFTj~^aTi6>vsm$k=zOAs%+GykBM9+!{)^l zw}Tm0;d$ciho{lIEYH zVA2~uS)1_Ui_2TY_@&$oA76J*kiE!jI;WjRV_;|KDSnx+iB_Q1`rr4dV6ZuRI(c0S zXDv}5*Lf&0i7Jmyk6kuL^zm_2a_H`a@VX8a06= z;vp|cC+B^y)UHWo0)zQa_XgALIh&mXI_%JKv^@8p_zWm<_5xnTXWU?spKA4Q;-InS z^hd>V;qLgl`+`PR<9IAorYD05@k8h%5g~$3hWH=u3kI%9f~snC1pYinCt-fzeAwxT zTzm7hvTJS+5Bk&VnOCLFT%GOJr1Uwft_;XS*Q4rWurj)G7=2wILUq?r3dEEYg`8+h zWlEeaeB6Ja3@0EECbi0hV*LCYf(bwzt9F@l#QH_Y)6G#f%gUAvG9hmX@&QPK*C7(& zm6TQhWf@LtT@PJzqfp1%*AE_6c^fKvK%1~wSz(k8Lbl*v&a)Zx$$;7g`UI>tP0kq> znh&<6NFuTHXvg_)sF8EHcgWOJ)QsET1C{2u@sey_jIz~O8%!6tpyN3$?-2KIOP&_knW;Ro;vrtsu~jv3A&gop9l-j*G_ z2yvt<=KZ{yaYAIFGk*{R0vBi&S0`wqfSuJ1gR1})A=^T} zQS}0t88E$+w472U&G1eL z3O^!Xtzwte9HQo_cnZHj4A7Ms=<8X0;u$lPl9-Sptaxu@K3Jg+m#l~DU=mrG z7I+I;uqIJ90tAAbGFRYU=RCt#P5I2HZ}gE&pS%|;v+#bWyPgOgN$Bu zo_gxP{FnFV2VvIUzA--ZXizmVud-N2KgXGEQ<@UbEu)Ed@s_}dUpfebwV#aZ;^`w6 z?4%i2@a=-Nb>3cFoa_q&xdAFyMXTjnlk*VBo2(t9FE^MK%PeAD0Xf=y%^Nw4xH9X4 zZs<1~K6;_%E&!t71z5ezz zcl}Obm#j~VBiYKJu$V6Z&x~%rUn-mOw!L`WEc#%4sba8o8qz*nSV3pF_>RYeBI#$H z#{g56u)FNg1EY_mpo`+abt)G{oXC+tS4dqC`*1k!P-@?Jmn{q#wMts(lC@AWROGC! z)#R$E76p&Xx4CF{)2uV|BB<8SbGBLBIu90#&t)Yhl{PoRUD?VH%mXdA3KWJF$yY#w zDUUs`I}gwQRw=rxs72S*iED2O=GG3`1?c>`7iZHvtgA_F03JDFO)P>b*DDvX6?6&1 zXNjafKY#vM7VvrV3GD4^Rkk=E-;5s%SkQX19~ntW+MS7%Y+gMoE9D~Y*DsGO&7WAV#mC`rYpQkKSHUm{Ti zpp*}988$DcF~Py5ygB~cCjt=SP(@_Y(gq-YVJV5i#Fk{@+!`-kva&JBE_S_pr2j2_ zLnjJ-nM_~7$=-ffXJy6RwX5S?gx9UH^j5ey)3E*5o_~iJWS3Jzz1ww~eSFp+r(DbC=IQ9Che=D&%&ZGIY(Vkg651YL z{kEWwK}9eA&ZOeUpLts#TnsS)2=lD(mJkd>k$@nWbvqC2Hwz?^4iKztozUUb3|aw7 zupm9j$F^-diaLSKZnNGDh3Q__Pt_z)=FFUuHNcl)MIeWbo);>UjWL#yB4PPKa)uj$fDxvhjbsJ=l6CN&b$x4(WG* zB~r4!jIN`hbvC|b(-cq%a=Iv(LaHQdgY(?d!kzQT!qts8oIih_fe8xER#TKp3`F6) z$goc-0xTi#vim_=#uR}1&+xsmsHfQY`F8|E6DkRIA=OoqJ?t?|3^m~nbtOi3gKa|k zoK7`rr$Njs6>ZO-g+qpzk(@% ziYZJ_t<8qG*LKDPabkLU=?((4oANvWuQERZLPR-*ZZn9_oCsR8q9TsDgG3d&~Or9Ox)lTN)`H;^lLK({Z?7gcWNvk=Lloca3^6! zC;;ASVJz+L5A3I;bbcONl^?%&X3~uxzP!(fg_~Tb09`@SRqMOFzg(^#_6bY_6>4Gz zeENys55UXq34z3T8IXQ}D?c8u{C?mMhJXB4Wz$AHFNd@2&AZ+Bofl7+8J6RVLOu&| z4|M|nHjNg~4=@?t5x;%kVsCqDW_)}CqPa6?&`Fq?SzL6=6;z}$AL)G~)Xm@nLwkz2mM}Y6pp_=@Y9*BGV{cLah zq~olvZriqV=P>k!H*emv$NBm^dRzD*fNg+ZSWHn3w{u(17oT`;1w`CG4%A8QT#IX) z%Vv!m^b`epbubxFU~*%8(egfOSs(SDdfn>^R%zGkYDtLP{r=DY_V@R51NiOVa=2_0#vC z|DEqY)8zWhd7$@Nt|(@Sco~W!sMr7Z_y0fre2gCfIVWHc0P&vMN-@GZ9%i3shJCGY>A$lI5u~-7%+=w8cH;__J z*%o9YN}rzChTzl!n9CL*qXNGsQ&%&2^kL){SD30pnwgg3jkpOS0uObyI#<3DOt0NO z1UUQH1T)rQHy5A)X_nxF+HM!&N)28CSE?}s`J5dZTuFpr2-HDPdqL8zL1(Ssg6p24 zBki%wOVMP+-j8tTYJ3P0O8?g+!{Qg-SMU?5u(dyy#^G5jvf2@642JE266VvkP|m8E zWt+iPXkM#(UQwtOawR$PEKMnbz@V&4g)u%!h4}z5mCETXnq-zW2Z)KRF6x0H!{r;2 z`Y=W|P^X7R3!7XemEYF|vS?HL{ZEfOztPCrb-Po*kHLwx&LA`FjEn$+sY60`bX=Gj zcEWHt`dy7IpW#U(%Tj=RB@$2pLvej_ZliZg0W8{8pYcWbV)*&JH{{U^&L>||cbHDv zbB;17Y{3fyFF1@tyVUnHnZUjX8n2a-V5cs-gv~&xgT$pCz)zC3Aqqpc1ca$`{V$7N z{GHpgQv8{p%s6X*2wsS!R^$xGTQWdPIx-0y9eh2gz%ij%1oFC8L-YA-tY$c>ibzTY zT~q|y+rw5VsoOyHhI|6Zh@`E65QNN)AvA-V^43ecoy5rwu{Dw~w8Y0qRvD}}R8}Wo zF+j&AQcnsulR~Hu0ZN>u6!mt0gE;V3WPiE=B@I4GdWu!utd1CCNK+@Rl>jCCjHvcez0G-i+u0`@uv)gmvQl(*-Rd!fv~L*zQw6vEwF&HP1sm+JyD^ zfdhIGDN=hFF0}*2UG!x|r@b<3H*8qKHtg)%HN*i5irNQc(?tg{-t_&T&hb6>_h|9z z$d0l2jxUG8++%gCR_niNunufX&DQ2O?zcup+7QBMCaq%xV#{r9;PNBZH2kpyd^3x^ zrYjfY$I(+u`FQnNNCi&(p`1PSyYIE>FbV{L6iK=Z-7(inw52ELofXKHP>8qT0CZxZ z6)(Y#)Ii_{XGJ9;k-r2W2Z!35hG|K97u3Gr*|YjYIq>4tPlMJ+r_#9R+}>}kAn9nB2|HssFnsfmIMD>frbecx1PrpUf}#L zC0yfO&@?%Wr%Ce}%0B=7=XN1J^|N4d=~bc%=>wDcyewWGKQ`;R&1F!YjDiwGOIRk(JIwx)EupfclqPC85y= z9#tCPS|1{rDv}3Du4c3L*T@$HBKoo~`jB`->sid_fW*jcFo5rJA}C#s4WNCDr&Lc@ z>~vDP#AOo$mx>}eB!mkXaN^)57YkIce{E#se)sw(FI+xS=3y?(uDzCgNYxyu9;(@} zm>E22f$1mN6)O$pInz@LO5+wY#}}(9Kp4}e25A<>6Xn2ESO%3Y_45D@sbUp7p=z!$ z#J^4MBBQ09JkUB2sx`I!I&Q*^vMD)R_pmv+xQXqlG*HfTCuk-ER&*YSOCnr?81#lh zb|0J8E?HW!)$i_j&hZF|ld6#Wk5j06Ff0F)41;0iX+NT`q;?zdG4>jtpbA1(W z6<@A})wSR#XDWx9@`e`|q`s|_^7h-EJ00gvylxU9)CMn4_^=G`6vdqrfYjh>s9|~! zu!f+#d>ku^QhTg1B&0-BoM>jw<#S>~^&>t1A;)>#0_pOdL+ASSzSB5C2q$m{zPBq+ zLqSdaO&PXjrV!E z=hewIpkI%7siLxu1oV~3)}>%LSJc!2_-TR$t~HI3OPF?toqNWvFvFlK9N2{rqJQ#1 z$oY6E9bl9#yy>j~JyEn3|EdrM{g$2WwaJdxhToYzli#}~ePng<@ZrVd%0YJiB!;8w zrEvulE z2p0isO~f*DfXTZKC#-^z%_L24+)k3_6O)mkyd}!*Trj5=(j)cN!$J; zOypvPYc$1lwlJ9HOI2=c;5xji2UsWxrmvS4;?J%+*?HOqI~hLR?@TxaM(J+1Uq$!n z8OM1>es3KPQe_M>z70?xp{ekFAT0IUY%GpOa&GSQc%%E_IhQDv-g(W4IfC@ws#LasDbIZIowi=+Q8On3@VUQw`0OaO^7AOF)s zo)dp|Ff<3Rk!wu7tcXn7WKiz2E-P@d+A>8LHbTm@h!H%%;QD3-GAgrvA8w6XQNTWJRQfBcoPnE89g&PzObNhl6Jrxq~Fm`9A}Mg+=R^< zO{d1>wi(VkFw8Yzgf^3G77y5x?z6-cnK>v2$9s1B{b|t02!w$a47`^ zq<&Z+;M!rwi0dvP1SOmdQ5x3eN&l=9hUpF;f3p*o*Cs7&>xLk65XwMOfD#s9vc`2= z5Q?Sxv{cSd6BTbt0!^T)K|P*J8I1h&Y;lwu3Q{F@dT%(uwTcXMf4*p5JG~$<3pwEl9I{~i^cOZ}V(mYN)c8Bo zp*fmFZUc&lZq*L34s~Pu6@AbwJwMvp+ZIrhPzN9<-ICBG7h5toSMpcmJu_iFG%V2~ zpBp3D(a4g)Nt6SC)I&Tqz?QOW81mteY)iW*?gT%QjDC_cU@t%kmX1u?6<*$}4C%K&K%@favbZ_Lg?socaO>h||ukB1k;pM7-NOI2fgW7s>C zuiK5r*o@znj8K;v{k*N$N9v~qW?~64GVo$a>{vdAwBEJLf)c-DV_2x9iEB(N<@N9* zBpVg7q^C;s`otrsXIwBoBUihr&Tf%Bzs*oXg~vkr(i)2QHpTFt`}imYoKil zChNKf;eClMAtpKX4`Mu1QvSg@`POpSCNNwwt`kWrmZW2dX2u(syA;9}B)B{XbtO%z zR^$PzxLl(bs;-O)u{1A@vmkASxmxnlB*lV3vUMFqnn|(;uqgdCUe_Do0M$7y)DeFp%&&tMGsgl zx>j8B-OjS(EaMeS8EI&!r1RjG04&3}hLFxKfN7AQF>rBdHm#*4AOR7>3v6b`X;nSF z37FJJjFMTEA#DW&58AeB!!OBxqFvicrELx}H!wv-2MhYAn43B38{7@-D8=esXK5nD zASQ`F+Zn6Gr=HF0@rQpVvFS!8UfdDp=Cx4ur!tn>c*ZbVB&SJr(B)J36+aG0kkJ~DpGSWY4fBEyHx<39Z>(_RBtZ?hMtFt!Q@7hN-WaR)uudpwdLBV zc^P7=pyv%hQ!~t@d6^DS5+k0rHWj2>TfqrHH6*+IiZ$8 zyoDDCJz2>c0z1<%|0kh7p(gId93*?rsws1G%krQ?13N=L>mC@>y8Hk4F(6+|0N1o7QJGskS_f5@du&C+%o6{T9k_9TBz>G4o zFGXB7HOuAv1P3@vgF2ewP~!%~EB4G0Q8!LpMD zwu@k60A-hbFs{eLJbNJAaYfS2?p%mKkq!XF0-Z+ePn^0{gW3S;A;4e-@G(&%DPhE- zJOo8dNNmAl0RP?)%zdL+#B+;Mkdd3xzzQtQGx?81@T_Dif<4pDYuQG;^I$mW7Yg8D zm%;ziePo}g6JoWzzUNDE>63LY*)H3wUUpx#Mdx!CE-8v5h{y64us^@K_jPk9m zZt?Ah%C%$F%Cq((w*3g@n!>Mt30?_R)e_OAM?U6;DI@;jkubLg1<0qGY|Y-((FQgI z{6ZC7RRIuf9pz{AaTvIFmw>wZ9DYZqSW*D#AV8t>^zEywXr#2;pZh%r-qZLO4`EUL z9LbG~1cZ?El&mH*x;s%2c2XUbN^{B1|CYSH)~ikm;NQz(IhAk2y~{IE>3X_Y+-TQw zD3c<>1e|}x%fem}AMkn{Q`8yY64(cJ^yH0^5fhPsVkQK#Nu?e~DgGb!BZ5H_Qyl;6 zag)Ud6-;nJZLpFcy{tHgSOiDpGY9N#7DtPoRRS%%kEw-xFu#E-4>Kc-9|*0e08j{Y z)A#^}GLQ^r6*YWAMMFrfd)r)ecVs{hsQ%6VrywE=Tb;w40LH0`84Q8{@csazXlR zqDs|(&iYA3zs22lJ_t!;FhbF=mQ$8xu0_yLw9*jk5w(7Jpz~=b!W^-vL;*B(`UUgX?h@cE&ay*@iZThcv`VXBh&VAs%b{cu^YjtKFeoem2Z|EvK_k!5Hc$66p;q4dK5odXdB zptyxl1?VdU@mc+S_9m~$&ud-td74$SF(J1P_HT2AZ5Of6&;CH+9%(_P7m1j_;9PA8*EmfbOyH#k_5UJ*G;X# z3kK`!hayChM>|Pj2PS2BSV?84n;&DS@Mvknf4RmkgCGg~t6;8~ zVStfq!8piNo;El6IkpZWFF<>2OF_}ov0LMVR&Q3W)@Mtlk}F^%VG!5JXokK3bm&kL z7AJG4%TOToyl(J=U%R1?Yrv8M;phbYJ;~`o6K4$}k&(vlF2vH+N6Y3QIC_wt#(|zH zuocx3`E+qoHd9$_$c)O6Mz;ow3TfUz?I)K8$(>PIL{E-pr}a;H9@LjvK2yk#Cv zuDcUJFFnP`1LKhKK&T2mYpA8CN7^}M&F#f~+zCv}&yZ_VQG-N<))CZoG2n0iLY!gL5N9R^%;!Od8FR5J`nr&s`mrN3f`k);$(fB%Y5&%_YbUm5mY`FHq!LmMWr zk(xT}K%pB`7|QjEna+az4Bz#MiLdYuonCljT`O-L=TlhjStVu7OhGNp3yh>-eK$}* zzf2|}ik9npaMGgmx>;p>$Gc(T^p>hqt+qm(hiIm=oC^7Aq!d}GGxt|h(4jUA_$MHn zv)N*uuLRq%&`9i2yx3iY%G)?p$j8}QBx0LS1JG(DpIFAk|IH@?FNyFz2@=qcX(YRp z;Nu8D>A}Aw`HKfzR$m`KqPdwAvPfHE>CClp!rSTAUc~ZHAk&ObAj=!dxNSQR!(G%K zGeD=q5w*os-OP~6*6>I@HH-0l14g#n1|+0nJ1+fm$Bln=M^=bGaCKO?@`)1Ec>AyD zFJ*0hP=MAX#0C{tM^Fb#SFK7=T}|Hr4-$`X00eNF1@;;WkD5o41bBct1c}{A5pfu$ zJ5nB>`Fc#w|G**(0T;f&af=`MjG0Ts+SkPoFeel3w?rEO-D6B$KRE$^&t$gu{mHjRz*IN|}|hJUpC$5NL*`4vJweOd=-PRttDLpcpc24uv6B`e3DEZOxX` zLv=|HSYS|1O^0FJthuZ==1}6Xg(o~o0pWthb4}TDoos5hH6eYVY zg7rvXtO4>{LJTAVE4Ja^euxw*eom>KYRc6RtA-P^AU)-;y4W7F?IFCro`Jg3252m+ z9E^bs^`!1xx&eJOe9V38AE;u3xgcXiaQuX$O51<3>x0kdR#}25#vjQ7e7w#{TTFe* zWV62Pj5|s#)c~^+Cs7)Yi7KxBx{h32MYV@Zf1Lx85Pcq0?h1}SehDRUqTRe{0kNpG zdhCoUGZum<%-KhA7W_OJ01`z`0wKCvDjiHDAu;_aUMxB0?vJ`c`*=a>;N_lfmP-AX zLK&^`byizhnL`A>9ZHJmoj`b$)`Emo;f7Zx3>C?eqip>>@y_v2XDqlY{902_WcTk+ zhE1?e&5O%CPFrHDF`QV_$^x$!LG{EYYCXHxf(XG0z=XEIXb*zR5OqYKv<`7val)0YVP^G*gD%108N0+eQ{+4K&UNQ16uP-);Dm?+O0#nbE2i| zjeRaQAZdQ@X{xx~4?q-2Y!O64M+%TUC&Ut?LM;73eqi$7rM@|Sq=M6Hgm#K4pe)%Bu&IOVz_?M^OgAeKijwz|LPmMS%1^%CfZn<*xPDh ze3~##Fd(ObsMcSkFCCNwjIYGmJHrx)gb!gvXs;8nV$rApzVv-o10Z>tI*EoaE|~zh z9si*gj{N5397K$&Oxrb~uv(T7u%My~J$4T&(E!WWXQ7V-1PkCylJyy)>?pmx+P&t&(NRh?lgT7A>C+1IuwwMPsQflVU;SL4`7^rj^ofkxU|@_ zHGW~hbmQv>id_8U-QmC#Zr7`lvgX508}`zN;3#}nld_EywP>?&Fm)A*)hs9z3<5xa z?2%*babS-h+;NgoLx@ zUUjSTz@Sny&o!AzDWw|o#-(_@1!OJNh94SuGhmg@i_oF>Q0zzL)-=R39BcRo)HR^S z)9}9{QHJNSsf{F?>ThZf-2~I8k|>1(o!_XvcWWxiA^M;r9S65D$A${Gn%D9YT%$?f z-AqC41gTcq2IxRUW!8P34Ij_ualq|50HJ`gTUuKYQYc2dPC6w@8>&@=(pj0xCTScj zcL0I%ndI<%WgiR=9B>8)KeNFs0iAdoz+<~IXSCfKDy!X;xFFy6;9Gi$& zkA1<_D`Rl1f6YX@JsJWh1*NOfSNRY?7aSrP{DRF6g-$9D-hOmlFu!Ct!n-75kGz9b?ay8JO5O8hi*V>mIGGPjfTRMyG2qhG1qL9(`9>7CF z`i`syzg&nWDWasGw03DRCm|4p0`!xPBTS?<{4Mwt-RaadEvLo)5lQO>DjQD0^)z48 z06B}Ad~bhxG-MI_$|DH1)nX?N@bad*nUjoNd8Q#b)SqwI4y3+vYN2|)2U%=vN(!`V zkQ?gQ>*3qu;(6 zGEgTZy=|KY{A4MV%M1V8#vUb!S|t0>~7*oQyPm8 zyN>V{UWTC77O2(%$@jDyoTm)G&}x(T(q zyb5;_lNCLbzepSqR2dE0NE6T$hA)kh?9M;NbNP*}$0F+6Bnf!1TjPeo$9P=%dRSY% zo;)v|JD?H~px!q!){ocNt?0~-kmXqAtSc}Z6b`wwR;+TeI_FE!FdK$8otb~u0Q)y& zS)Sv;J;Dz~AL8O~em!hLyN%SUsFYAeG`HOS&<)=RcPB81%+t#FdH4xAfb%$@{h*nQ zgnBxD{u^PT?CLms;7jzpYc7I|3I;6hOokpDEGZVoWvJ*3{8gCCN2Sn)iDd>e!9~OT zHdJ}Yv7Q74N`D>p@4H&80hgOciO@-**%G=r0P|qt;1JmD&v2Z<0j=xlCLn{h_x1{V zZq)S~4?w>aZ%M4=mr=oifOPVvl&r+Ve-nCp;p^UAh_v$^R_4I#n!B1mWZ!EqGR&C; zfx9<(T?qqs>@J6S{xHXz(pl)wX%gP}wZQ(+#vLH9>pFMFzy7;0S_4q7k<2wJ$2KNk z@^#jtHkKWr>Zn))wq_ScRcgyzY>Tp`zVY5{%1Ox}RTQs(U`L1B#Sgx7)22Rt!@eDk zCwq?02aW_{0|nF}fne zPG#5>#OW~~yg2$6P_H1$LdO;=uxb_(5Gg>={|!U?Ih>~p5V-2+nrMSeGr z2;*i_p8tI8|+Y3hPk`!E=mkJ{z*8SZgO`rzA|nG|MwQqX29ADrAB{d=V3Ki65g>eqlYe5Vfo;C(Qqq-;O_= ziMYjX1dEgHABv??tu~q#IgOM&nbb+xTi}&7(okk;@HX2NtrXXg%ryUIWKXE&FG@l@ z=tsdY+(Zm94@NMCyGyqr7dRVGovwxyxu9Tgr)Uo~tEv!(ud>@~Z?xEu&1Q8=#!i~? zgtRX0K&1l(|G-iTg<~W3#30nLP)>9UTE%L9jkWq(9x@G0QHzmoy8t%y0-E2K>$R=} zmA`@ksLf25aBP5RE+n4w^b|-Eohtm@5HFc?jZvVy|1^xu`0%7+#eWk<*4jTygB$tD zK}gD3PzpU+VBrFTP7^$N(O>j3Qc29odWMv6H*anTmhgh5rY)6sG|A11*Mw+GiAX0< z-K`dU&SYxW_&1_ZV(1eEKEO5HfH^_aknp=rWR*)amAeO{M<-*v=N?0hXMR>x;>DMW zVqyus{nm~(IvVBFe#_6vl`Mp<(dh%?+y*Wj^5~GT>od7Y8D#qm9HFazh7CmZbvUEuKMC-g57@Z9De>-*Tce)pjl1|85_P9CDep5be~_;K_Oz%YT9L)rE|B0Nd4HXIzp5B`Athpi;G87l8r^rw6faU^d&W1WKVs(qN7Elal<9}{PeaG`~+oK)h%Jt>D(6a|;_wi)M=pH}_ z8A<#M8tQalVCTM&!z7JgsGreYCvgsKfyp+AGo~{p#MXZAki{$D10;wK z4T}iDC#pYbY23qNg5w&@vaqAOxirF?0&Gp8%8HU9tC=$qkPiBvyHKg&c}R0XTo5Xe z4nAI%#-Wagq642SVhSWEg4s%LmdPl@*-jLt1wQU{qS^Y(KxDi1NQGy0wS|r=mzl)z z4pVR!)?K^_R|Zcn#(&<43dcUvV5kC*a7+ea0>YarpGHGhDxpr#H#6YuTZBhSHv!Fi z0O1+^LCryTLB_cvC$3V&_39$kyILZf6C1-T<9Gh47scZh@8rF7QGnTpeFPNQbrh6XO`cGHK2l*@V?D~cJv^@m8nX_n=!*XQP~lJDNagVf`oC zFqEpq-yIDEjR_2_=2%8}sb@P=vGgEYT}~-Q6^*2xCYtN=*XMHqm^N zDV7F|kUcPfzGJDRNrEUfd4-Rl-235ntJT*xoB}EW4~IJm@Ht)*@H=7&1h*0jR3%WB zzan!YFY-RDozn_@bKz<}-VZ1X{^Pi&#_5nA(PL#ks-?7eWIh_srr||Iap|+z_u8-Q zdhS?U*c{c?-fGxYv`}p{D>N{6`W2A3&mPpO#@zDqIp04cra}((Sn0HRRN5KnhCI1= zYQ8x=lv`#_6tMB!fvo%opbbz2@>`3JBE^Sxg@c1me}C{AkfnC*a&RiJFjS_9<#|*i z5`7GYM*B_MqTH1YDoY?=@f#R*0msx!R74ffaKKJGlE&FrQu4AR2himyg+o~`{Ls@$ zwcj_&o}mqXvCKp_L|e>8Dr?J2xqt*7+C z3R53IOiutF{<-*}l_>B<iQ|T2U8EUpvliQtpUl>rJDz2Sm7u&G`I4wd%y+biQR}NMhe1HbeU_1BB0Vy z*`N)zgWYvKPM|2V@eWYwJZvatI7(78$j`(Kh=3ehf)oi%igQJ|j;d5nE_nFVg56&< zzO0#?YrJ$~|N6m=`W{(3HgK%*E23e*!JiKAza6|GtUjbs-z4@U*m zZ$rj@$oA3u+E6^TVI=1>P?J0EILGn&eFsH84KO$4`VxAUj;^AaY2>lVK&80R$m?d% zXb3`%Z%*0hwhP`4NUlMB9x?C$F7v3?Tow$!*SW2I`j2AMCxQkm*yiu3i10P5}Mr^V*EAt5|+1bs00`(nAQSU~~@{d)Ya zOQLKikAh&-x8ywI7=xp1yx+G`6Ml_vsI6;NsZx>jOr|Q|RaT{7i#jMGFh9=Y)Wej@VuSe+&M9(9$HZAz!OVf`HEv}nfxBwN4f%> zd+_Y>rr5TAC_8J;KkNhDe@@)dckR7HEpec|$27N21Be2LVerviWLZp4Ihg{xUzq}k zO#lKSm<#qhEN0S69rGHP6E`g`E~=ZiKvK%(vwK&)Kixeuvp_a~EX#UA4ppn&laPJE zJDC`!U-Sr9p<-7>0buF4nT_AoKhDKlu8Q=vkBiL#e^1)WX_20JQXHsH2^$B+-U(&2 z=G$h&$(7Bkdr>Q;KXKOvJ-+%MWO3kY_3zc~+mhG*1C|BSGhQl2vwH(K zp@>GTDb$jIb&J1ubyQx1OeGCUcQ-=$&>ThHKQN*I7MNm>~cy(T8iN$ND44 z^pjUn9jQwE@?)dc?5W^?YR`65wVQ2|=K~LymU=~Jb#&%}{ zY7$ACtFeZZAZcbKax&E6K>p@^Q{2VxSdt>J6ks%f2=yL^6;h|R-*XRlPtIfyw%L%| zb|b8yr96EI7TW4lmBmW2PeXe`oeP_|p2ZaSpWF&)APzoH2E-1q~o z72?ysF#NS6AX)0Vn_~h!3&)2nUsuTZ;G~msA@gY2gUW_!aJWq4uq6N?bQY11UV;6( z!-kTB*a@)}$fD@I*JRXky%Kb63w@3Nohj3S?GQp$7E_8kG*@FuWY!@_G`dNBIo^L` zl-qcZbZ}Tv7XN8VHinP#h3)b|Mh=X8}jPv|>iFR)lMeXmx zEo&W;@bUQFzm^K|!s$tAErIfWH&f!u!q$O-sXNB|`!|9<+I#J_kR!Y7Ocwfs>OLxh zQ5|fc1}XZ=*mAFi948G*DQ$A+ag_-MsF74_siBqBZ-FwyZLsRJgtVaPs1m#b9<1(+}bA_ z>3L;uWv8^Wky|=wP585;9Z;gsmXaZnkN03bsBmL}sck?oE)S%w0j%ss7Tt5Tjt5HZBCanv36JU1PJlY-^E?g*%kEfKF!B6~M05w<;t(}|)- zJ0NC>FS80{D&*bp&xUVjgu|85c~6e7esxqtm71QxPw)&b)TQtw{{B~1ZO!`58h>@) zEMMvu`BWp;1>x}j(r>j=|RCA07C-wMMbcTxZ(6-6Aq)=_s3Z{MfahZfD}QYK`F>s*_@{2X`Z{mMNX_kC=24wxW-bgj7!%&u|nJRrqz%7&d9@@z0s zVgDfLYKTAcR1(wC(|#F4)eXdIc*NU#cE`il`js^w6ROei+}V^@B`s%nxV%CCc^JzYt zZb8q0a|&wS6lG3AzuxAqW2G}5!1{cx+n9-?L3W4eE(%coFQ(S=hfR*!C`ntlxsI1BJ4r zC>yJ&6RDJ!MzLZbov zDMX#vR9V3DYB2p3ar_)kOUPgY43YwN)P_SlJ{`d zp@!TX|MEf{tt zCN`n-IWq++x1~VZnn#Tc1z#;I=Ok`HZVWW>YF~l|NLDM(4nsGTUa*yt8p@k&&|j{E zDV`XHdY|s`p`Fqo=vMSl9M4ohQvpjw)VWC!;!nDGij3T#WJdIYVF$Zd1`{~I6ZtU| zSSZmTMj!f9@lU>+E5xPyBV+di&2qVQ{djfr=4$m8n_t`tn|rivNFMrHU95`wo(*_Q zMHO90fun`vfb)z3Nq1%ZD~|VTlQ->FNjJnV-o0}$UU>lXB>d9x%iVp>UiZvVYhmHc zh5ugrzhBA1FrIOQKBwl?-EHjQYvXS{5Y-$aI8$}u)$2RQ)}CGQi{GEEz~CI=4G#<| zjyGf!>$9o5Rs7c1L>@O|PdS@0MnP=MMcEUpk9hV!0v0n$xVbFtg*l_!BV z_IkykN%yjYN8kdqdVBg>#&nyYG;`@oSi2#OOQV%rlh=lL?V%_dh2=BmHk`WVSqbQD z+L`cKFZHY?SvsM$rm@i`XA_l=0Yr#v4@Zl}1L9?z?FkirNNz^ceEihI(SWoGR^l*( zVU*Z-BpQU7sfPa7&wK8QhkvkAjGufYTH((_Nbr;v7aolY&0T#DKMak5`GKqGOVRiX zSkxDXqiCpDybBVZV4v{<;&3*8{i9KPoJWXCN5C2)FcO-imgJSVGK6^DJ?TQIhv$l; zpNKdpTfZq;55Tu+6!w>wD;#on6i;qR)YeG*8YAfGFEtI72 z*Ks+ptxFPwu83`Nae!z;Bmt9;9P9`|hNOYcVcZ9mXuyX6I~2bG_5hNY-Cs}I{E`xN zmX=^u4fhTZfki+8p&v6h3?Ddo|9)|HuRO`=eSNb7SJ0QoIVh3Ur7w^)8Fjw|esE7z9eqH+^w4-Fy0 z`F+QZ2|IRJ&E_T#y7Anc&H4wCE_V za;TjP08BwmEdItH)uSgq9jI&nZbJ&gZrkmAzMab@Uq5(@(CJ9GRVpVS32u`d9yAou zr~fg^vUr={O+LHf`h^}3%_>Z)$B>#q-FaY$34GP_85L9mMQ9H~=-T=W>oyv8FWO9m z3||xSXjVZZqBqbAbo`!2H}+*zQQ@nEf(B_dC8I}63@319D3!rV!<5BjPDHctWbm60 z&(8}xd=`qaGJ^vuUDgdNujL%F7FVLr+BN%dLZ<@OdH(^ikkc}*7~FVT(+P9QVq7$g z%NMX?lV-vIO~lRbiU!X9JFJNn8G(p*isBcrjy2+!x-Jl(rS7 zmUSfuF@=*Ve*9SZ$^u*Hg$qL^HHl@NukAE#`X~!<2g1vCk8WzQ!yWD2?~d{*A-?|e zrc2}s>$P?YzBPte-;w4;G6+NSPh=tV%t#RW%NOO)VAqTjC*a?^njW6xKaKLNtixAQ z))4#wn*n=4?CmT92i?!h$YasA;ZYOi5CIZ5;f18hLN`lYhGdE(cBK|;JoGbJTw7O4 zCRdj++8JnvASCz)U2$Ymby@_C+Nf~Q>dKc9p88ED6>ZF>a;;SE1>QL9sJR@7cQ9d9 zn8RB=h$KRNhBEYE}b01HpM zER~yR3v-i06W{RxFm0f!C2U4`?bK3c=Kmt@&Ew>{s&mmkHJ(#-s?JnZr{+`DRo&I| zsP5L#Qnw^)vMfuoHCUEpNgiZ*79J!I*ex4}0LGD_A|H~5SeOJ8aIgV`4Te|*@PK(p z&h!Wd>|FA~C4|^OLh_Ojz3tcvZ;kGff(y>+Ab`1`>kV?s~jjy>K#ly%xIx%r+?Q0z~ctg~LuVK=BOwCw~kz z`G-<@&Hu`uI>mKD+XkINHl*OzPSx)3vtZWl=8?XrV}d^(91W+PWsvf=Q%lmR#W#Xh zg)tlh_#4VqMz{cL5T&pB|M17eASBU|_I)QP4 zitkz2ta|v9VEAEZxMuu8Zoiod^x~Kh=XyGDO&I1p(y8v@OgY^+KOKktzer>hW;xcG zn!7Hg>tV-v7Sjk)GjO<^!iG_SP#QRc$q<}fYwIthzZ9IBMXL}>n>f&uh*_z$1| z1w+pGJC$Rax$g~YiTHB@^!jB4le_xVdBh0;(M zCn!+80qm<_e+`EqCJGm?v2-cs>`Z`Ja1(0rOIR+ewOByji{+8a?z&9v3dEg6Fg7|` zu!3M&j-&^1itdHn9Z}%OKv%S?#FQ6$hx%a*!5R{lIQuQL1h-Df{L&Rp60FYA-X6-u zy%S6BFJOKS>`gE@s9)^wKb9=7eDA(}+Rekd4%vtBh=s#q%i3zf46N~0;PE3ZkEFLa zp`nJ>)94=zkMtskBgl&?8eB9g{>k~-oL~9NmC-q%lzWCC9Fr=>O0Xy3npLPWB&pE2 z7cTLVM|bSNjyY8t+PkY!F&kq$AOhm1b#+{&Pp5m%{tOlTI%YXH9D*jDsHK+$u;#ST z7AU8Q+B1cGqsQsF?dI-6zF4mhUe@y&>o2UH%dF{+jsrm)Xug4(eGP4F`@thIaL!gy z6lX3xt!B9I@apjDO!=J~vlB3xmmkXM*$~3l?2>h#ZQplQ^E@}{mJzTXUyHGM4DHSN zCm-^Pb2p}-y#PthmKc^1&7?jIvy|f?f>viKn59j??+cH$j$+66GmNP#7y)8&1^N~A zO&g&K%8jrN!p_i(c*KUn2v1V7U zTuGi`5~8k6Ot75)?=ofg+e&T|E~~)JJFAB@S=4~OZ%7~rnP7|)H;qEol?r119dDGK3$O09JP+!Tfe5BAEGwsUcwaY5 zowGnFUxZRiVH#qXF03afv$N+_3Pxj~cg2N;{vjpI@j8tC5;Z2t4BfWn~vJxohHbcmAa=#ZUWwi9>Mew5J{{E}B_d(0LFX<+&Og3qEog`O) zL%^k?rRd12lk^1$xao1K`AiAIQ(yvjf}qi&NKbB=+mh<)n%bb^R5GLP+BJ@IB}4l& zYj2}r*6R~@?~fP%@bCNc#tG}=p^6L6Mi6-RryWlUX#^JYcE8zz=?;$xAi@wn(VN2w zddLoRM+>kNu7#6{TrT(N8&<$8yXS=yX?5_6)P{rt8Q9Ii$qzO~ z;aD652!xRh3>zg_|Jgqu4u$>0zs#rn3x1l^dJlRKeT0vYg6VmMf(Mh51--$a4KX{@ z12qH%?lEGq(ii<=!Yg%7F3alX*t#WQw@t@ zmlo-?LWROWVI6;rH%@dz;|Mz45UB+S<~qC6uyF~}0AizGfkddga>IBTX4K5|N_Ty=TAj92 zS_(TjVuqh|d4Yi7;NTr3Zb zv6Kf@dm{L&vD$qEU=;&$#DtG#fZB=Esgbq_d!dimW(fzkV23*H^X5#U(ia%`!J|s9 zTv!R9A=dtvj!z##ZbV?jeSC6?@7UJXfrRLVNlMalxo2l^xBg+pP6c$B zVt@-}Bbv@C7b?YkG>ucxD3mK>VaT8=UTS$NML$BH05yi=lC%TW0c?HYk~t#|O)MNM zngLT=e?vTe!$mnWmqIPsPoG0SkzGsAmV7Q5eR?U4l8R}1JB^xfE6NR2ocqnf{s1ycd)D-3j zyiHF>*HvJI7sl+bjcCOoy(<|22=HUxf=Kk0izZWZ z4T#&J>KHfR8lh~_jvF@cXegn<+C=64;Iu0oLSRx52sb~VSuVz2I^IQgG z5KOJ7kAigiBpe=P&B3uH;ktEUzTje2%%%+2o3gcKVI$g8g+ZM_500t2Qw0$RjOUJD0A zrt7S99FUMJPlpTE6l`}{>!(ICsPUNLL_4FAbW$7KACK=p51ckm!5xAEAd|@HST1&| z7qHWyp#Jr}u-DS?@BL=Mn`?C!PLJSUytRe_ixMPW>gfp$W3vMJ!C1sD8&J{E(=ol@ zK(_>ook56j!|WcGCRox?)38z)j7(ztG6A8wg!47K*FFlnVnpV*?tpF-C@Tmfx?{DO}YE7T?+AyS}1=*Bb>3Kp+@Ml#*6KhOO~wsHZ-2H zkus!`lbLKVX1WnG0fFfXl(p5UYM)Z?9!oA)x0$mkSkR$LBYmRK9CB-43F`f6ObyC( z5^JC)wE$S}qhsF4%6D6#8tnvhrs3)dEL|fGG2{@iuVIh(U^5?AO(=_kmd?(k%ta=p zT2F%)bzhEre`;JfnhHrr^6-24n zSfM|mu=9t}WTsjzM~2n>w`?^!Y^{f&X6)lQXpRAx-xTC5PiDI?90Uld2nC?_-?2QE z6>B}l1-VJF2*7lE8`-7@Kpp-X1OM9LxE))(BD$qB3CS|MW_!>H$oB-)p5S}l18Xyp z*cC<$DwLCY7&b|-N~&I8q>Q8I-u!C*e6lN41$hh^ZzlsUE(ze0K=Svk@4<8dPUJv+ zL9igA$5u_i+A$=>C&+$50FIz-@(0m@BMTa`rDL1_z`W(iOivRGF;9FiU3)K^kpQEV}xKWklqF+wqZHFu;Udp(hA6yRL!0cKb2r{Ca^zv z8iX;t+Qh673{6EMFn1S*I0X*37Ig>1o);S&-nbS=;-EgAm8rx8IOi=|a-|cc;O24K zC4K|DeWEtXjr79Ka%FoV?;qQgb=Mx>U(B1!&0@*IItYs1ij9XNISqcgzHS#EH?2P@ zJ_b6+9E|p4)tR0!4*ZMfXLHp1GQoBt$`l6bR;=_&Yp-&Y^B{afy}m#+T5!>M>yoqj z(v_#pP8AX$b|@Q-z(ZdhLR5m`I4M9hjhOJKRupSG8^^ZhflDI1M~j%v9dlmTLHJ^ zAi__;zLHWaecVL*-`?zH0+SQ|bvwP>92ev6#=|Lf`r`fj_hBV~PFF??UzQH7TD58? zCgW(t2-j8UIo0Ffi@h95o)=5Tr`0G76O@hHdcnO-CJ%?ZRSyys4%lhQP%)Z?W(J6> zG;9J=-;$iOj;DdqB2mzOAcbgxUI;7JHe)pfrlpu(dMO-+r2|S=JsK4@fYR}NK5Du8 zK*0SXj74eTX?ts5sO}!z3N)Y84=)K$q7V(dp?jdq-|{iw_nhyQ=Q4fHwiq~FIKxlE zmZDx(!GJdxX;6KLd!f{)EpueN3I|X^HM}_PB{D0rZpj$k4ulZ|);QYnKhf`$hob&J zE7+2xZM2Oofe)}28s$d%cT$N|99xYQH>zv9ov*{Ja3Yy8yK($rC5(atl6C~!6!;y+ zvD(idP~4JXA#$Mr*~pb1EV*3jMg1pl^y1eLZZ1!2`ycw;=RO6vflg5@qI_x~clX_Q zKa7*|WO7mifT%O#cbNvZ>YsvY1PTzOSljX-(UyIzT2k5(_6_!hsvJs2Yo6 zxu#s+KhP8}C3*{U6WFQO)>@HbCXsQo`FKyO}y1dvx5h!FnQ9mNE?70edh>l<7>vBz)sqo5Il{i zwXNB-F`&8WLM)#}rY6oJeuDEp&UI`$u7_dq{QI*<&yQ}5ggXmiFQbGmx_*15E4Vk* z-TBPJyF3@XJPo|OJH;U(J>>|7Kh7yIOcV*0+L>h5TroL0nKGSSJ&j=-8YeLEFSJf}i|>Q(5g?p{BE;JkTEOV?#}`yw@3bV7lO| zQ~f~*=8rA*^zz_kSDNOPtLVa%mYDB=Hd)IVNb4ZNA2{H3b-^a+#Q`k_OcN=jg031~ zqv_{|N1}@Z!$EiohQN_!-r|4rfLEYvsyN8?JFzR1LUaB_S9o2E`bq|DfkUb~o{B7v z0rnELOhqf&8K(f`m)9GeT37u8pq1gXwpkC@G7)S*w*zehi^mK5s8c!vxR7opdlgK-% zCAn(GsBM93Zk#NaqE627U_RU}rEN?eFr8$3l7iF8gGB$vgI;~jK4>RJy80t-%F*3L zdZ!D9WOU4N#_;zgNa0)Yh?O0P=DoN(bCYlmZ;iZXeHte#%$5fccfeYX`TB;TX}EC8 z!o)-7+Kkf;2Qn$B=fSuQ)=c{&!}xP%9S8bFd%}&p0g}oy%0(;(8MvXjwd3plsm=?_ z<5N(G&tw*Zbn1uhxAk?HcNm9@0(;En zgf)^{G2ud&OP~rgZ2p`E?Aw|eR^(G45c?|yOgH0&3{VGzUx&9$qcw$!ppdax4{VpL%Vh(8xyKNE(S3hdqnvsz?E%VAdv2~?OOfp>!)rZb^wHq7f0E_h;dr{EK@ z1e|0)?ce(`w=k92yB8eJB}=SJYXbw$;9&jv-8Njo?Xk1=__z&xMzi$)ASQ5&(^wUA z#lP{0m)iwdQXz;5laJ7z+_T4~ZGfa5nZ$tR<9Qg#$y=ei>3UfeIz4~33zq#-n*z37 z24I*_OM!1-%d;27-7NZwCXeq%4^V*LfAkJ-sxMo^=CyAy*f$hXR-^$NF&KZ#zyM66 z0V#)EBJSESd7KMYp7wX%=~d?fz*2i59CR9cuGPrp95blFGuxWWB^7Hd3A_ag0kHhi zmYfq-m1%#Ie1{fyF*j^Ot_&v>%a?*-3LnKdPz%GAG|r(?5H5>`FHvSAwj0bwf@2p( zV7MErePe3)X-GGQV_*>{V%4gVfxdtayMmF54f7XK4QpeO28;qwqXnr7yboK<@mL(p zXad4d(AzLQ;h*_bSIWONm`g!XE84ijUTgP-?P$;P<@)yR@Q*S)ye4o(@agf+&T%`E zt()dP@NwKdb_`3JBC>fe&QT`V4{2fNhz9S$IRz8_#`kz#bLQfxxwMv4gCMcOAmfXc6`sLbQf@Pr#A)jn9`KgU-9A%K6k>%$ zF$QD<=X%5OkpvDs{E=%9o4E1U+I=1WLesCLmV3HnE!2v8kw+iArBm^Cz@RGpJY3*_;UIw|G z@iu#PY(fBNON2Pu_^8quDuAv@V@Q^#%5$NiWK8K(V5nS42BGJWEWtflxl8E|Kdaz; zJr;p=uS75$Gaz>zQmc@C(jiR%WfvU>@6iwsRId@;`)wd~i1h<(TIG>;5Us@o5{wEe zkYLm3Z`zaq2-`R8-QOE?mc$^?(yJxYp=$ICpqx^%RBqTTgUbWJg5BRvTKet>I-!Ql zJpgdhd_pwcKlY22dB6Niw|Ff`?kwEyW-MGThxS8Y_ipX_K9FrN!a3Zst$qQF)~Sha zWGaC1jia!H(*Tb%X0FHaeHq3fY2KxO<@-0sJ0S&I zOL70dGVU3s4iiYp0rVUf;P<0>6(F)%0rPDuIoL*GU?ZDrc@;JC^q>C6%0_Qs=^vCHaiesh(g1)3O^BWXP^OwX;`gg25&ve^Pl-W zufolom^HvO0+d0_31ee0B1I_`+2wEA2l@t$3D~(`w;~>MY@Cc?i3aN!Qt_(Nt)qG+ z#e#lzI1ujy-AQr=)`1hx41hZs9|9J>*xaHj3d~)@bTqU9^01ztG_a1EmIp=iSOId1 z__KD5^}sMseVD5~z=dw3;zHnR3M*Gzq%}M5jc+8kO~~<2z$lslwXxa;7e_-lSkA#< zgRRBuMzvP9)>_r>Wyz%t&0V{8Erhj$ueQ@k*ttsV4^95Xp=vXoY(L|57LfBd|56H#QI1@agDiWshb^s<1C?;5b$M zy+4vd$AOoQ?T8h-G8QjgjRWsgJ)IW4^Al*&bE{8rw9z&%9ht89D2@RELxdxSx*4iQ z`eW6o9W>+AAWDQ{)}#RrO&W*rkfj8H=|+;nkgB>E_K}kDfv6Q3fH|OE_}VxfPF>Su zRJXNV$AoXqT?7zWUZuERui?omzGof1a&5sXAnG z8f*3QQlJcA&Oz0zKxzFY`l{{%9A%Dymyfzbt0W`{)qAjMZgJy@V_a#wZlWRfo9$n} z{D&md{JL( z=!L;}`TYKPPoO8ArS*JxLvV43mIt@l(Ba+*Yql{Og~bnOi#Urz2@9g@>8y1Sy9U$l z4id^@0Q27lnA3iX0q7VI1e%N5j)^5@%D-W=o5M+j|AUWvrMbJ*{+Y<4vI_e&|j^7GBOBh2g}iWwQ@Y%JEYXR zhVrp*fW!j#C4vo62G)jQ-X;UN3pfnK;baO{vCBBFf}RhbAy-E9@b{oPUJ_gYDX}Zg zU$7$kFB~qyOfah|M&19@PkPn$nHz^DoKCyTo*J5k1ENYcQ-P)dT=Vpl67g(Zt)@2i ztDUu4ESHlN(F7hFk5~Kd( zr@X-#7kz^MAvSfR>!aWTbZWg&J`awKWSpd3Ohw1xfS|KrDh3!aK!w&uYJqu_WM5)%g+hnTFCui!ixp8 zHSysX%8ca`W;6@ux#PEwHX1`vht7kC*j*371sP6JB7kKRr|D2oQH!Z?4$=ouPYznq zEI=S~5O)2)2(^n0j`@K)u{kyp%OUU9*=ZJFE(ki!;Zn{qwGq8xMqm@%&?v8z3BisK z6#NW~b{g<~9(t*HZhJ*d^SpnuJ6{^TXle>>_xK0ZchHnQSWz<1f7J5Ks_FS^3HE<7 zQ1`>e*#Pie)$wEhzueIw|3CXY-GAgMuYTpwqmP6jFAkE;YdtIYV2)+-r#&p}25#3##uI`3rCl@_ITUqqBobYO1JUU#tMzCq1rH&Pt6Emp0*@Ad zr|NK(Y{BSM+=4GQ$Upeop7DBPSWR?rwLql#$Cemr-`J_90syste#YCmhsgjdR{gW%?`iWq42>~vOXeOv# zu}$*-?{9eHbN3xQXmg;vV=Gi>$c+T`SiUt`h9`oC2ELhw!<fPk?U}HyeU^W)GF2wVIy;f6|Pf4My9Na|9MD zlW*Y?3X*~L20p>N)~l$$74A{y?pR`VMtgfMpwa9hP}tLxmKW{r>M_+493|)zecjz% zU4#4JJv5uFh`*OM!=P%a1ek=76OIGR+cH4qV?&w@sAg)lYHJbV(pTbv(m2*+bkt8ie$Dd)!5C^0<^ z)>J8pL&9Wz98_p9m|0cD6)DJBu~sV%&b(xl^7NtoZp(rvJVW4pLeYEM>|*1V|+VEUMumkrS7w5po5qSYt!~_NZxkkyr`FpN4j!a8C4np zU-u0dyDIuyez>Ga8QV>ExZl3ug0*We`P#1O=}A~)T?b)3m5M1{_)0HgdF`&r5 zgP=xBI0pbrCa|Qf&_CQLOis0DEJ1k+`vGMDULLKWjXicjnq4klwSK)_uVa0mjz+t% z7?~iTVW*QlxXP+vq$g0+&wvhE{66f3{Wq`8l^0K39Co7Hz8rrPeMMFJU5yp3x|v%ZhjHVVP1Uw?@W`00RKb!2=g@+irrV(>0uK@n7AN>Q ziQA>lgdoV-L;r;HGpXdr=}i_40Et^Ga6tUwVHhi#9kwEZK5wbTaBB+)1}qDF_r&6n zlcS3dRY$7$Z&coU;)L_%;f6k_7GUKWQq^GA+TgI8od=U@XD0;e)C`S%#h@vO0fWLN z_!x>6P(cAgVSr{q2Zh-1Zcr(csE?qDqkjFbyxJTDQYn?nf?KEu`|7cY27s-xLyzXI zOdLd*UNZ+u+RWOGyPc^7xThr@lmPrJ=c1L&=#I73v>7f^lyNs?(Nnb2)~oA~XNGp1 z-hj0wSf))Tai#=`a8yX;?r#y?@KhsUd-A~fcGT`)+f-cFTWWCrsl&yL|B1i$^e_Zw zqTT-8e_`b3u@+DVKN=5aaiCGm7qmJKOs!xvQO<1G7YBiut!tHvn$1pn zUMlsHk?1efYQ=%Xd61z2;gZjT?GlZ}aRxr6<2cXAmQ%hU3P+H;6Mm!1TDS` zJr9qBTa-rg7oarm7>-%iqMD(Xv)BTw7g0BzG|57}jvBLTH zxu9dT+aCHTHU_%09*}@@FK3eCdZb@}(Q$(i)f)zvM5t#8618pML~JC1gE;Oum}ub| z*MH?-fdvSzFv?wwB^-TaY|((-xe|~Wg59u0$td3sQ#KPb`4AY6oyqXZSfVpNQXN(& ztLgaYXl_L~a3yRCDqv$m|JJr})(B7a80-L?J;2i|$kljUfnD!JLxqG7%of8?i5-Db zi;>m?3i{?2ju=JgTCUGu|Dl`px6ExaK~1;Jk&C4dT1=;U;cv2K+WZa(@mMACbGvo1 zm07ngbMs>8v?~tw+*&%8(IX0^jiLV+bIY;LLLgPtijb)F;^|cVIS}4pY~atSgO`%W z7Cj1+lyRJsRv}E-6EfWx1^crKFhwE<7d!^ePciCI6AU3bh5s?U57PK_OXBt4Aw?AN zzx3~(H@7pk=teLZVHpd?&LC$K2T^V&CN=nIhXJPqoVlPJM>9@mtP3ZKLyA!@8;Mdf zedV@_u8nGA)4R;`@d}&Wq<7i2?zmBfj-iV6@JA5ze+(tIY(i4R=mIEfbUZD_(IxJ| z>N4(U-@ionjbC}QS4Hs!6Z;)bIlju(qUk8~Q0kSG6^E*V9;iO@oP}!K`b+Fni;J zAaLPk9!)`2Y-XF)7*lA>o8P~5_= zP>Yk%GF~y>q87yO99EqMI=Q>?t`Shsk*><(F*6UR=$aAC6=UfNWXBr0WV#mXtQ(o3 zSkJIgnbnsM0wr%C|LDaFu>rKqy16rB*+z91t`%aIMlo1;EiAxDsai_mphxY^8~I1m zkqiW4XpctexL@@4=|D%;f9S>)*`*lNd2ndpzpb~|85wcGFZgP>HaTf8gK<)OS(o00 z73oI6G9Bjd7=`EhAdO%?_p85?aV|I~ivi;C`xjhrK{rfV*6eY++JH^}o7Rnwf3aXy z^LeYZ-88qCR*l7$O<%tJT{VpRQGxzmLZ&1chdAzvQRyXdJ5_t{o;?mEIwos(ZirqO z4X0ppL<>c=J~KW5qe0bp5ezx(54@MQz%!9?fn{ve6EG}TuI0v}M!vU(NekIp1A+-* zj2mYnEwtLGf0tnTkkcdPA)%Tmi#lQAmA}udfX664);?y2ddlNKj!?n$T6IT9S>{9IQY^y*@5*X*zn1P4PypWK*FKD z9+)V)&yBz>fM(g9HrA{ppf%EQC#d{if~qQa3<&d|=oIcUTa+lMQ7!_Iv7Ug{8&Z3y zA{Gab2L-26zWy1YFDnksf_Nx632l5`x3z>ieUTbf)iy18Du2Lr8yV9*?l@K+^+WF&fO;5s!09ACKDSmWee92&!wfOnt&O0LqG zqZwd5S}ya~Eq4G>r(k>O&5oD+Z-0MvetAm;dhqe?<;(3^xFRp;Q?sG|<-w&d;Y1A- zBy`W9K?ECnA+az1XPk>st+A!VKJc_HAt$s=I-;)U)34!ON_16#tN`%Qw-T)VH#oQ#(l<0 z?>_PO3IFhDWmyKYS=h?`94xrpxlIYsN@b$rZQ(+~HBC%xZk$>=Jw({t_8AFP6w3b?o*ZS~{8U zKMV_byPOA|b&DQ+aCg$QS4E?%28x(o29AaP1=Nd<@>>D}{*N7_xaR&uaCmWS_|Bp5 z+Q8n?-Z|%9I5xQVaxGv1>%l}Eq?jGM2vcU0RbdQ42XEzHKyAVW*m;i~+)-E>8_uqH4vsZ!T2(p-eja8q?C4?b zOJN5CZxBi(58n^4gi@c4L}rJ;^uxMM0)sL%EzSfdFhz}p%?U^&4eq>m&>6TMRyT(7 z>-m!qmX)V%n|{QfGtG-}5(B||+unS@h4_x9H*Q}^$Lq05EziD%85-DtVqo6f zR2kRk$+-6ERAj(ke?g_b-)cpK&7I*ImMfAdJ(|p};a2>&Qa)-qPa7 zMiP**wcv~mdmztFWS0(UY80~0K{!c@V}>PRrt|;8-iGp>5PEys-?O_?+6$&7m_9Q~ zc5YpCy0l(jl^=&j1N_{tSfP(clW zffevh=xc#~or&Lpkw9hpAKF_f&!y2t-?5{x)1HVqMXRhQyAwS)+ocvRCpa!r;GF$g6wAJHcT>WB&w;LDpNmn5riD16LmX3X8FMtECEFfnPkl!T?ihNlc)z-Q`B$q$LzPj(GN+?O%PK}|aIXw3J0~W|%VLm(&1W2^ zW2ip!5FDpme=M#0ryJSWwg1C}N9&NO!ojvBjz8?po1rvWNe)$BUfsV>n2|C2ZW&np z3Azr0MM7tc2RT;)+x;7=y;gWgx(HsZ-r5_D|GMm5OTN8(!vFpvYjUo;aWn^lqzEQz ztp+=E*rEo7h$?ah zxX_`G9j(mxGj~+-)90o1>Bi7dC?A6t;9vwo%3vAP-D1&&9Di4~RLXWmd%J9{YkR}3 zs<1Ve)vBO6V^OVo#=qzGO3zR%1eH!VGdi5fgoAb}EI7aK#6Z&ZND#c=D*G97jlxf$ z|FgX%XGUy5fBVp(Ep!HGL%Re>}S^xCO+qWFI{deJZF5mH&C`t*@kwhm5q1Ju0PY4%H{ZXM)_;yuaWDDz?Q&$ zfhPn16wC%!1&;+k9Xu2KFWAj~KJ=aNJ&}6kzUX4*O!RX6JA`m&^h424N1qbU&qvQh zf3AkqygH(;RS&E0R?es=gU4Fm)Q``pAIHBZ)hDCRtKwg9RrLAbs?c}T!_l8dFIUe* zpF*O%`uF&L7~wzh_NjLGIWqpZ)~$7;0oyV0VggfGpy0c(!ERug9za(OgP0o!18xGc zSBt^GS&EIwG-yjQdDnEzt%V1x4VbALu?TJH*b2zqj>Tvv;P+B^2HAr%iOV3Bcn`!{ z?gMZ5ejH}fpTz?ZL--xY;yl#xNXN&OtCgFSIptpEUCM7M?^b?Wd9QL@`GE34rKy}y zo>9K6d__5>yrTTM@>l4~-atQKwGOHB0aSVF>2G~tOHHk|25JQL2pl?a<24;gF+_A7 zhQG6CI~2cre#;;X$Z;3W0b>FcqkVtJ%=`uOo8~W^-#ou%e(U^2^B2!=o8LaaV}9rS zCG)%HKQRBn`Ag?_&+nPvJAc{yzWM$02j(xIzheH%`R4rZ&Yzfnc>b#SgY#FeDBdr*}b-~8~Fd*(!?Sm6#Ob@n@F|3-oO_*?Jz{`nm0U1!}FgihS=Yrl7K z<+FTe;+|n`JM72xHiR1ym=ELeT0@vy_#p3jWtc+fMOcZjw|!-=%L*;bjQ4MU*4yvo zw}s!e*0xF48?)kj59hi=B82FCQ zeeL^YxXQr1aX%*tW>2#MHzDwzov{Idk#0fQhHyRtdzIf<$z2GO2p+7K_ZA1=lFxLq8(VWSTzw>@M0`r!6aNaN<#^-#QVJlYt*(!SX7U1^@*4yDmEReU-xyCqkbGvkj`sTmt}Lq^=ETiHEX(Q1k6Qc6 zO34KB8=vJpzsviD`7FGb@nt+#wpIByShrg)TEmZQ6~T{J*<#+yZ)~N!XGUxtzcU|R z-+s?$#+PY`*j$8Jg7qxIDuk793-X-=d7@^r7(98x2g6gMr9pjzIPjOhkC%Q1e;d^U5%e5JQmh_)bNTfX`v)JAUVVU;8T4^F8xs;JwVJlcU0#Ufd3h zz(t7>h>~T3h1c3!b6+3;@iWJwytf5H=x=(*eqbNH{mKDe82J6|;qB?#-&+GIGi0PS zZwoRbeha-V5Fkk-Y-@*JT#E>@@&|Cwz_bi+e=cAk1%~4kVgE6bymBxYWCi4vad^M* zI*jLqffZv|m`27|+V2Cr&b1%d<%}S&y#Gys-#MrZ?F`v~v%kc%EbQ%9rjbEHo4ktg zCp1+MOY=hYBNnJ(P0kktqr zONK%_EXS2Wb}cI?E6EBoErX2DtE?cGJ=qX>-Gp~{{963x-R4?9m=0{&#Hmwl2j0tP zd0qHz;l2DWuj1Ro>A=-Wv3b}$!~rJqx)y`J`W;(1L6PURhA>JbL~QZ?d5;- z?p(pF#KMaFZ$2+9Yi{Ag|L#2rBE}Viz+PbIGW<99zaf82@c);L7A81{OeIn!_LhM| zcs;@{1Y#&csfQ3pMgQ%spmK7TZoY%--yi`p;q(7;vM!vwyAf|wJDiUz!^CfR z#|p{NkNbsr%q@Iac+ZwBMv!x-j(di6?LZ78=NdC*_uB|51b*ZETln5-zbE{25ze)q z=R`!lAO*pX%!nDtK!nYL<@4LG?Tkc*OfM_SE1x+GL?8^rD!j6x7a};y?pCc=<6#*B zYn?^tMvz#ASD+$M0cJ{!!%7gdFtEaW&#SCJ5AGL<1@#&^DfmjZk;!`zxPCAxvtf}k zFe{mk-(~hpC;Lafm)CAl+>+9QY?z&dR(a(Zu}j!eR)IBUwctS*42ic^{ta50(1cLug`27$xQ5hi8KKq{Dlc`~pYw;`}^>1Wrg^2?ROaTL_e{vZZ>$9G zWdW?j7y^4u#%CN>p7}5yudE0w%AR03U32(hp&VjHk`am47=hD;y~4zNCtJ#A_Q(N* zs}@{Y3Ffu8edW|)VJx)O;ueBn9CUxj96vCy1uT%YW`$UDKJz{2DZ7*L_@0&EGb_aS ztPG!J&oDhJ$9VkC!dMZ$Bf8|fcDZvR@hD!%mNOCWSrEsV30Vo=vp1NSVH|;g#ULvt zE48qfI2>#_yM8$WE5lymkduHGwPXtz2(=>!tR)jLA@A8z*1ivc?+zin17Quq6ap*B zLivqx*Z@K})AAeBGjF+&FfQ+_d+@`^j7U_+7V;w_%XfU{Jp;e<%J0ln24ZNwzZF5Y zctvye*4hLYL^eeRK6A;C&%E;a$bxqr{^8RwTR; zcMKdf8JFL}2u#Dd!rVAO41CYIBCqTVHjRPl_>I?g^B@J*e#waZ$oBIZLXiE&#QeS# zfeCqCf>8FKd|D|q&rYaw>>>gSfrim}Wmd9meq&N5lh=jyVUe;5|Eu@>&U_amFb?xV z{@mxxovSPB%|c~6c+Z3kY^%KIdsz`#QBF&a8atBryz(1oi44S)>~KE6)%h?e#1Bls zmz<}TfA~_xZGLPh)!f&sH;->n({jKVO$N)KUaJ&N%qx2HkxQY-SXNa3wUA=^&QfLC z2bGp;&TUZr;Ujqe#2Q8S-?$k+`)Z!b_;&oiRDTZHG`;U3iHDoH4XNg*Q0Q|nAh+ZEbbPJ2$+F7+J`aT(6KZoa>VNjB zR#wk_AfV2%ee59FVX`B5Wy54&5Ng?3jL&=7HxgoHr}D};YY>Qz7?=<9=QqZe1A=cE z@lj^N$oyzEk8N;sGAXl@eJsYzfAb{;WX=t5>#CH0)>nMn8K;|g?ESzvR^ZT)j>C95HOfz=@V z6A!XlY!Sh*dqLn-BYI&V0_AstDBts*U?}eyhp3J57>8&~_Aag&(ar1;R*97n-R^J6 z1>LJywj2f7ajc(w&v#6=@R{G4hVMB&xEc}n%6wQh+r?)F(?9l%;wr`m&v&=-y=*h{ zWRUqRtRBl?e=)sm3)9KE$ZOI+^}JH>$HVY{zOYPwKR5KvQ!C(P8E-@xebz`Mx-MrV+fkV*N> z2C$)g$H1Z%7Qt__k$lgFOYr8ii0dEQ2dHY@?!O96a5gB#DvM;&-*gr4nx_T}%||wy zFq6`XC33*MtOP5>n6eVG;(W)-vm#6*`-hM#(=v{16l=lwGQF%7<09VDb1i0O46;WU zQ5G)0F=H`HaZNN2?{xjM_bMu+Q(8%78RuqHbM}DiH%q}(nmNhp@?IqI-*c~0gLZ4{ zG11(A$n~FY1mH0Qub3Gt$$H50iJAE=-u%|Jp6^})V7coB#XK>G*}n4zJe__(*`ip@ zQy=PYZvH{GIdjxKqvh(mjHkK_A;d?mPcTC-T%pkfa%Y8xWme; zk1{>;lK{CeZ{l6qGjgWb{+7#-C)W`ysabz>F{t_u))L<%3p>e^asMe(gQ zwuMtt0%eJUo5vA*cd|8xF755 z^XQki$PDyG^MUWQzd!O7l`uTP#LQe4BC9VeA`2#B=C@X1XVwO48u1fHB+-WKl46!5 zGF_M@ldzuF!_$DnH%L!$CM6`n$GEz0!c?rrTLz=KWoJQ5lPCHxXjlpSmoHKDlFUjZ zVLT>nr?LDqJCJ|jJ#76`CMh_O2{WdW^i5AGD43I9aeV`wquBl_5Dxwv^0j0yvI9g` zzB!C}`od%AyEl&M%`2|0G`$_E=Gi_N2W!U>X;4MifA(&;0cCAsO;{2pWC^g#HcxK= zDOf;No_keoK5G%!)~vMRBj85e>EZ8XAJG^FOx}89ahcNpn-i@jeHI zqJQ4(FUVG*K}YpDLZE=Qc636SoSVE#0Oxo!E@`TBAk@AWP>p*IflSLmmh+hTGd=TW zTE-J#%l%OMMqEiK@|i(Kk_lv?yvj;3p8U?jByh4qOwVs}+VLCX%WJE$uuvmwDGF`A zehffgLKnaMn4&Ld2CVtQiNkx@VrIrT3o9(Y8U7i}Bma?U?qt5K`s-Ff5B0*ab+y)d3){UjSM)-kjawHm|#O5y2VY4}Vks#|~iK z9R3Pm7SPuGkFy~DmjMcDO91b~OZjs>+Vg4i?hIlWpz+ zB6vz~Zu~DS>}N3rpGUozyMG;K-?=@c+We0Rz4`Jtb$=>@I$*wFa(!3#3m_x~R%`DT zffdd}+8{PP{#M%Rpp;s1x*pfATh-%szd&63!y|a)dL$N~R;kDC1)Sf456yi)&_zWFimB%O?EsVINVyS)DcMiGa=x%8 zvITOE@tu610Ab-ahp$on)h_~oH-jzU_X9{p26BvKH^>a+v&f`*W|!Oa{zfe^HsA0o z?+FGRX}$wbV==1!0;W>)wa;R#xzwD4PtlS6*l#Nd|II5f`E>uy^>Fmjf3!Wk%+3PF zDEPZ?KtciuNa|LfHuvpNPsA0NbU-9oHi@zVw`U-Tj}}|w;h#E%G4syq%^A=bo6oA4 z>Zh=cf?FpfY>g-}yP!0=Mu}v0i%R7J+C05S^=p@6Sd2Cb5$q8(>CFuV&d}!B@3p3i z017Nn0LGINo4~#%zUDm_0QP^HebACZWY>t6@-+gfcs`SWV*g62 zhsh*L7yXWBzT>^@US3%ouZ+tfmT~!B4n5xi`s{NFTWbMc3f{{(z6kS?c{0tyrIbk) z)<>2_sFvWy@?;&EUZx>Ck8$|S_p-k2`)2o*mSQ~o9c=2wpo`h#VWG#+4Kgn4A@k+E zY%|ld?|5ZB63sI&x$T9fauiUu?|y*B2364S%)s<3q%n_k)}QVV04+bx?ZyRooY_uy z3+>F#HOL?FkPYZOi^*BWxjt$>`7U>q31qME%fjA}EoMqtW5eGcLj_L5dIxrepdhh% znE77He}mi5_D#y)tO6{tx8!cNc@v;<=gV1aQnB%h1AvEDSDL3^1<4mydYW&nLN|@6 z&7XW5OO*{!G*+o$f$#n4{Q&vXS0Sr?z3|EHpS%oBIsGNI`N%tfeV@vTgAfU!tT?-p zm6d_t_@2*fB(Lm#zLVg^dp4Y)EIU9B7oR0K^E*3BrboKux&CCv314pn!63P*MHhF; z9%CZ*q=NiV{s_2FV$l-g=RdheDQuU8 z@G4&8%8D$k1XJ_7tO&1?RVCxeZ=w;td5_|rL*$GilQWu(dak(UF<8%&6**U3#AMeo zwT#Jo`5gDRfCsjVQCKE>ovmf&vi|a37AW5{J7&YQ{3Zk6vv^Si={Dd`PL%iu+bG#; zZn?jp$&FuYYIQM{th}rNbKyO6615;FLEuVR7silf#r?x@XVg6YB~@(a7PgkrWwfOK z5O|sX$JSsS_=_trEuQ@wtlyKM=vFBevR3pHk6=1HcgaBWwfpp@w_iW88nHh2u9Uy- zRxBc1YhNW%=5h0~@kLI?@9WL4eJIs*4yOF)p2oc4wtjebYa;}54%2Y}OeRoYEe1gQ zhB2=An-mI9HE%kkCb$ui6j!`?_|<~oa0wj59zgpV0Z$Z$JycG{W(wZ3eQam^gO|C@ znfK|<-G7dneFY}*1nyESAHKH>f^g1pm^qA0Cp(vEjppI+xlIGRv)3A}UgQj73=WM% zZld#><~6QZV6pxEt_P*iR_V#+$oFbu132~Ftp9QZy#tPb;EDOi;Za$9dbt5S@<~`e zCLui+LxnDcM!N&JN@II|zf89Ja9e~x7?8`u3g4a3;lZDO#nJr^U^{Mr$a+WpuAfP@t5IX9wQ@WqNO z_T24NUm*I&mVvA~dpjWDxi273EC9_Xrqt#-G=V27*mI^@^dZi9Qq5%;Y`#9uEd{<6 zo3H)~<+gSJc+K?#)FfI!(sA=ksKOI{$e(o8TyIL84si<$Xk?L_dvt#W6sXwgU^jHO zA+k6Dve|#)GUWA>^(a0*jmI}Y^kIjtH{bP{I@uJ>eV@f*&|0?GI>qqcd=YT(fNidE z%5p@_=9c|1)CCn=(D6;3l%h{biNk>|-ZCTEjeKV2vK@SH_$D_meLhvWByH3gV9k0|}PAR8P{fpg|5vF4d0z+nS0 znZ^3m@hPDPr#2X(W)Z_3YaUzMS{VgTL2T}^l@)zF ze4*Oh_gc#T(PHEav~F^*+I;ra)^c$I983QuvO!O$mDYKTgVV`^f-{tNfn47ogh^i1 zmNk{Fw)~Tq1Az8nzb`fid@tv#z%dP;O>JxOG%+mzE8rqDXTYu!TrRQXoI{O*H-w{? zH(zw$!l+_rjs;0@heuk>RdGPXji*>yD8TUCf2mvzTZ9bf(+M8{fWtA-f$g9>?{lQDcdc+A1k|##`8%%!e(J zy~Qh^8K2)|Tp34_2v+lOubbu*OOrJdvHes1z?x5k=hpniAsEkK9I@{$$u46Oc9|?x zRK9s~%0m}`;F(PM&yx8saM&A|lD@%J0JB&1$xb38tsa^Q73$7lR$9}7Q`CHx3bo}vK2BHac!R5 z=8Bka?>K&y3#p)(ns1(Rg>V5&WL`3`CbAs9<1+*AW%u)5rXxy}_cA`~#a`C^V=?T4 zhj+Jl7JOwj_kYLr`*6Z zGEStq4Q$5ukWN+oD{fb^B5P6o=9x=fV6np+AfJK6Sj+$MnnLrg4LoJTdJBL7mG1p9 zxT&l;doAHVxg2EB6W4$#-j7P0ZJ}RKimL+a{NtCPsOR>h;8&DwCH9(xMfRA4NzvmF z0Vph4*!QvoUgdi+<}4L~xpCCR*!ibE0n#JpqiYI+tHTuH3$7e7K8q%{-^*_z2me$A zWG$;>P}b@Hq3eBv{j92dPft?nAvwwUg&a5sPw)f-9Uut_Apbf*0?0o@NDvYT0TV<} zKqa>wiMF-}o4$iHOigGX;JH8 z@`Y86n)!Uz^L&5#!R>jmp8ap_wbx$z|K4lwr&H_NaxDiF{Yqh`rVce(Gopk4Wb@iR zY(=TIZk7NRr_=PB#t-1t%#QUvjrH<>6NhkM;M6S9;lDixVkO7rN; ztJ_Cd1h)rWQq7}z$dZ~ic`-e04dnBlm_Bs{818noo30o;l|4nK^p}q}zkZC7%Qfxm zOeEXgyV$ZZ$z^q#_RA*+ti+sa*PTz@B3X7%}^*Lrc$gV_9>RCY1yD-Rm($#)PmCp5SPi*@m&Z*EXYNmg} zi}$9$l54hR1?~uS3LoRm;qzkh-(C)HC#jc2$XV#%_LhR%Dp&h%K; ze`Cs);-qb+dG)W@i<%pIcO725&PqkS(P8swH!{_2p1gI?9U|6ABJ>CC`V;a0)$=gD zf2Kx*s+%UEXg6N1j3=J!0h+C4#oqAWT1p<83a$62R}4c#VC z`ox;1*I87z5jA&K-}^~_N&Cu4)a(P_7#pwimIv^2Ug5+~(C(hLGi}|nuX;ejgOe8N z%tCz6R^(Z`XG!9jo*<6&Dt*kJ)_esRD5ME#Ps9i(0*PEBEd8tWFoP4JU4`G)-oGYJ z*prUt#Mrr=hnnSTr;8+*V-wJnYZJAEE&(Q*>rR<%*KeVHdBEDN-#mW1$KK}pc0j^m z#5m?uw{{`)M{#7>`1+Yy5>Z)?`PBPZs+pVm#kln2E-vYQx2dEw1t)D`p4Ko zwUblHD6N3$#I?lu(k*nL9z4|+MY@-oUDSGx9b>xosJz!k5c36>$#hvYaXK~=VVH9| zs%{5qk0|b`xkCTzmTkWzrha@9uWc8?(J!vSMo&4>-uHRW1SDSLNwG=*>khYI*(>76 z-9E=2wf)5(5KviCtsm`tGRpot8&W@+Z8y1X;3Sr*QrFb6KwAkEI*Ja}=_{c2=QH#X ztk`>&O}yE3wZs=vz#xqBfj_p7HgMQS=AJx#LC)>DMX5}1~LK}1DM@! z8-u_s@`Y;Y0B0pAtE7kSDN`ypA9m&Y zy5%^9nhkqpsNpb3tK8qd`ujv;aCGo1zC3KNy_(uJuiF)j>P(8Rby=d!k}Z<2y5wz( z${T~%*+JOmY@UnT!O4uM$CzKw zhE?sU*gD0MQos}hy9Z5s22-&UNNX;t`ghY!s#@I=NlzrY3}_BJmfkKIlahpCAr;b^ z!ux?_Fs$h>yJp_Ff_|9IfBd`Sny>sF3(fyJzRt9z8zC6S-cMY!q<^w`eDm~Am>NIv zy|L9bi4aG=QCIoY!s$~YL%n3fXp$E6`f%E8x5G%sVE0dTEWOdk50T`Y z>DGyrjW$YnbT<8Er}1XtPiN}rPlY!2hR}g+FW3)-qg(6pPK){-ZFVF5X6cL0p&8t6 zVWX(|$^#Sa>zt+ykKzXH;_Gm3LJ6 zp7Du^w;D3{h2P_My(6eqqV1o(Z^0~Qe+-TE``4DbJ~)!Lf@@lplALz^;vQD+-Y>Oy zqIvPR?5L5heq zZ!>q-(8BWnw-i(Tpw`b1oRB+vMXftu(bTYsrbLzaXx7i7zevEx4fs z)!>z|h!W5)l9G_Hb_vL|rhbA1jHAB^X{M+W$MDEP+PcM+wzN!Za%{GXoIoRo5Zivw zv&K4t5<++sZ3!DMylg0mBxN#j>nA(18Mc#maRTBQPdv$*#4u&-vMi1)i=$mU?a+8% zIrNmE0m*9wU%d8VGGe%4;)ihJ6cg=W)!1~YoTPCX9NrXL>dJ}jqc1MtykPTq^HgK+ zR`aP7LbYeHQbJ|+-%p=n#*)6gh#B7H=fvjeqZ|x*R!8T=f~22Psr5NL*pibIn-{+@ zy>&FZk#WSJJ6YE+MVDeXTrp%#z;4%XyL&Ye5@8WXW6l z&xS>e5#h3S!$y+aeH(-O!0RjQ8~@o&vxP6N8Ia>-ewjOND#M~~TQ}}RkZ`h)GAjRe z?+1ImwIOeyrerins{^!uGTlD;(*+a59U@D3&^-O}-0)n28@VX#Ic)PYvMxVeQr>;z z)R-;U=7k^B9&(AVGSRT_eVi;*2U8lW&6jQ(v@h>O($DT#IH==S=_EUorBK{V?&dHY zJqgwoXn3(OIy(ZCnnfxTW+M5Ikj-4xoOZwkvg%<{rA0BiX+>Nm6V6z=Bkd4d{Ds@P zflR(5Hgr%P@SI`u7zO8g>Dq)O1G-I2GdxOCw>bKZ7KmXMa@}82pXOv^ixu`pV{>Y) zu622}ub_)saxU66J8ti19)>Q~)1T|qbOvq_^k;%=**9yNJEk}~XN~rgQcZ1tzJ~K3 z`LQLT2(n|rnr8m270sRhCyVGE3vTgq-MoP&5;GH^5;Fej=5>oF>oFxBcLbnm9;9cO z@pLDvx=o{}cW+6~($*tj0;9#M+prK1VcmvB>R~8Mr6-gA3C*K_>%P$|cOYeR#cL8A z>A!l%c%3qiRt0&LwB&^1wOP5pE-clHwfaaS3Ce`A;;tCDP1rE0h55L})lWKMcr=rt zVOZjlM)T!AS#pMPq^-YZDwes>&p7h4yq(!UHdpIey(JmBPBb6+>}>P;^MlJ|Cy^B znpUdOo71jTdeX)*Jbn>e{0tZ358jCtK@TGdA|FqUK+*A=o!W>#Mw7jz?dAhxv%|~P za<8`%5{+9)JICfO5<5Z*&7j0>G=)~__4bMOMV0RgrFreK>E`Rt&$M5@O zA`ge9Q3BlD`T#-FwX>0k_G-_~b$8A-3qLq@b{I}*bxUALNIyBRzAhd;-mnhEKbs%)ktsbB@e1^t(`o4IjYB;zG&+1uCZ0_X+YW)Zat%Uhb3Md@eD zzad4aj}@lkx48MuAI+YpuQcCWw!xNC!Ymu*#8Jm)2f596O}ma_`(y8T-IABVh{$E! z@sH8ZPX4TW6yy>^WL06BWQ6FWGrB8ly{Ux6mswd+MUUHaKkjtNj45M=Bm<=eH_a-& zE;_LBR#)S{^EXVZIGN`XP`mBm$oXCJr@6QmqA0~WUT`n^lyDX^6(yW^3WJ7n8+Oeq zR_80-uk#>SoxFFeUen#WX&1hv1AFU4yPQdK~%yA}IiKE1S#38YBQ=lGAauOFgTUj0l-zkT!y z6c1m$eu2*kaN1k9z0nijy>gTqwAX%Qj;nn$Q4(jv5u)AZ`~mkXesx}_n%-?Auiwq~ zFP9{_V>f8kp7mN_kBu5qyMCcz#VWfis_`EE5fit*QJAre!#cb`6^Y*~A$OC0; zw$R^Bj3a;SlLZrMW0%hMv*&x0Wa{ywklu7Tnj|VRamT6Mb1}O#N8U=5O;@l%&q3vI zT4fEhz>jv#Nk4uqS;rE(ieQV;4@?BR;Z%#T?oP$sDy<8_UC@e^v|&fU!( z(X(iSNqtZntF;f2#w71c+h-QD!se!q=f-v4-~jLLw1*}rXD;#7-5vaoc;{^2BC>G* zKd)#vy$!K0|Hm28SSr5253F3NPd6KXJk|dELSo&xf`fuzs%U=nC*$>W5PM{Je{^i7 zPM}esy^t#CmS3mDP>O-X5&(kU~^O!qtjx`P|Mnz_gKHGa5-R^Hr33GG&WqZT?ZFfVU6@>ybJK|<7v{x-T4z-ph6xP_02*6|M3qq3=&(UvrS_Rwtm$mi&S4nEY?AgLrb7RT}Jb-U?eya0_J zD_eq}UC`+fgmjH0bEUFUhyK!C+-_j$+aB`V>*1evx4}6`x(rpT<;~{P(e-X0^kQar zSAZj;T1lAf;@bP}RI1b)T2))ZBf8-DsX5IB5Qq=AyNa3Sl|P;3h;82|q5Sn{$7ZU^ zCcCKzt`6KCDmF2V=_qYpBVkQ(^s~MR)tWG8o>;VS`Z9go2M|kB%(NNZQn$vXQ49;c z_eaBk&daR}20j%b$xR(%Ih>6*lfM~nr_tzb_S{TAyPuQCBZHv~7znsjs;Ysv`%uJ~ z>vWAh(zI=G@SqGF`csb!0PjdctEtjC)bQZv0v3thMx{NPso_QF3csrwB z6SliAP?ra~3k0PW>t1`^HSy)0(y;Fa3a(4lZz@HYNzob>&`-PRiI>V{Tetp4o z-P#VfWRBu` z&phA1<#MpuP@TKpa-ifU+iP}@t*Vn4$T5UYsvcJs8feUSA{&FgiJ zqNGwK>qgZtyi8S7^9Et5ABrY)OQU^f%r#d$j1mBiyXG3TP1E;#Rh%?qv@xqbbgb|D z6s>ykR#qt%jD9GFf0><{A!F_#mBK>h%}waq(BXv}_IA9y&uQohkwxlcWyu57=89`) zYLu09?MHR*4b;J#R})+h6>Agiij7QRd=;U_pevZAG#7k(;JmCkiV}z+=vGXiPWT`< zcv^36w@;~WaPv-jn6D2`G7Ax4glf7I-11yFTkXFZOY_`q?o4|4Q=R?KjN0+&V}zXeqfOFm+d08i5p|fkGT_q|HTa-B$gTC0`|+(5O3k6A zWDXapYuPUTG+kZ2OmfA0pyZkBHb=wmwFD<#bmE*6?(EWE#NpX&v*wy9-v#SVRM8Nm z{(hO$HY?s}X;yu3oIKYhj=QQ=0c9dEXa+x;Zhn0pTA}+(QL);ZuSj0($u3jC&yUSK zetg zgYudS`R{$yd6_%Nn@LU8N^1xqeIsk3G?x9(UF zmo9Jr!hx64bMjVWU+JxK7p1BRoBxcDs_S@Dfi`p^DWPjXHSaZry76*SWrzbTf&0CP z+7>A7SDyyqm7l6=U2#Sg)?>c**MA6kXbt-9^nt6$gXpL7pw>)1B8bv2#mh^C{cy`# z8NDh>xzb$27P{91A2~?cHVvm^&W*fKR2tv)M#Lr#=(zgU)(*nUXh^WzF;z-yENoU5 zbYv?B%p<=_>P!N<7i3CVtNOO3F5kJvgl-j5=G`|}@7=x6haW;Anw^;T=OOJgT_Nu7+JlSLp;jP5@osk2QyO%a z*Anl#cy6~`gugXWZ8Nt__WbT zeeD@CC^N-u``qW~32ds1Sy$AhF%uGulf*M?(cgH2^ec5t{ev4*KNZWuWuG3Gmrl_t z3zASyM$t4%$;s2-bxA2;WZ~Yl^j^l=@uCSI67T z@wRPdCOneM;8{w)c=g?`&v><;_SH+?4d`@reK`$z&(y@7=Nuh)$;ofc#Z=omJsKrc zvqZ+0nqn}kD@9$e!7${qKX8qsyK^r3bw=QRmNZ_4_9TkECg8+)vNn%%!oB}<#2VIe9x{tij$8Gi5c0W=~=`kJsL0#u&@8mx)&9a6bMX$T| z0d3c|ao)-MBNRcn^F8FR<_tBtgRMdH?8`IVou_JJO63u5^E8Wp*Eh`j)#hCp^>jQjLvU%YrGb7sK+*qTkJ2fr= zr-pPh;Hqf8$-=X<-NS6dhgm2;+QfgF;u<2Vo;-HYO|qRn)b!5zMs*zh%y-ht?I#F z{jI(~ThV@zp5B_?eA!x##tPjQ>|{HcE( z?`B!YqI-FPDmPwR1hvuFtbAd5>;zO!YU~{C(Y0Gg9D35TJC9HtCp4S?sypyah`#ov zZ2VpBF74nSdAnyd^TdzwzHUY=5kexQIWe*7zJ7p5bE`^%W6~zeCfnU8ZkNhd-8<$K z^lPY|?WRfcnCF$^NzYMD+Ud;0ly2$BoV4{H#kCo*y}pHozi5NznVVUwas8_OGzZA- z#!D$dX0fcn?xCJ+=h;FldF!q#J45VYSOzaMbiHANz*Nv!njX8lJ-ms^|J3Sy1H+fl+C9>`i;WH7Ibda9jQKO~ zO|BgIdqHgl+dO#sl=r*4MdYfpczOJX-(pMUoh%v7WcXq~t5zM|6DPxYHO>FbwUwO` z1oa**MRHER@i*l2&{tusE_?fyZ@n@(>*^d{c>wks)T!k>MrVzzZsd`R3ENH2T`zGd z+2q(<3sn%!=AEyM$(jl*LFKX8AFJ$RpR^{5QB95^+b83XQP zi+l33#I>*v*g^f!f_jRZ$J2X>OuOoq@4Qj%I;ws9#boQK!}rH8h4UvLV&p<8`-=~; z2A9S^u+P~&_xYsj&WGK@mQwq$1ya>v<4(@)n$4&1{5gYy%RS$5w^^+Yns4i9weEY43+;c_nSUR4$r_&SPK3 zqo{2b1oE3`5BqtYvTQzm52GLz`_nar+d1Q-u8>i1Hl(Z{DJ2-Q%GF+E+OTF-qbELz zc(`~rRr<2S+U1K3-NUfeD*2Ef7549!?eDO0;65QsZ7(A<5C3Gex20tJ!H6rOPEV?5 zRX+jE!uHvl@H_Mn9V#NPM<#vL&DHnI%OL%wOUMAHE&UeGLg%apK1Ukg;d!5D@1fy+ zt$Wc(?is*;!Xx>PK=tm`s_lqi+w(Zf@7R;FzvAL(?Kg0jn|jTf_}ET-jn`;vH^1It z!yRv4g*k2&fjHn%1m}WmM*N&n^(-C;_{D=$_3XG!ji>RIOI!OeOpHp7^@<(BLEUt= z`O4S0Eqx7#{C_l2)pICcm@m717O&;JhXS4a>G(+rA1x-UdR;P{9oa=7kU;(Hmr2EbLdQGxZv^GS!?V}0*&uBN?!CX*OLpQDgN1^dELx0$b4Q zb$DoQ{4b1ioLp3!c=Dy0($ld3M?Mg5#e}zx7s#Eq7VS{ja?Xc~H2{%qE&&K1oA+p7wMdE#pHL zKuwWe=lv(AHjc{P4(sN`!x1}k{wZJAsL{t89Q6d&u{O=Df_2V`6sLc^;n`8vEq0(mBn1_**83~;R!=&!IG#enpbW{82tmxxK-pC zTgJ7jRo%W({bs}Y8}A?GQG7Q%xomv~Yg*^Z?jd3f{(=Jso~OGADKW*ZD}G(a!CY_P zAx<4^I85-~#hU)QKRNaVf?7k?n5YO;K<9NNCT?V;`kp#XI6|whCwf_x)C+vWvtG;f zxO(BMLx+enNls1QOCLpbti8iSe?2Iv`9p&%n4_<{I9){MtF@}>7G5=nZDw!b<-y-k z64|(J=KqS}jPhT6-bC#OuD35_%|NHrPn)~_Elx#g-Un;bFq?u*idfS48USmYKOQvu z*?i;S{GpVlj?LH)T*Y$|yQXRqQo{^6w65RKY}hw}ElH z6XM+VSL1I`jTA+_E2b2j!`2}S&ewU6!xhB=SMjc4Tuise*6WqOdUD{GJh#Si1uJg) zJ^RM>PMRnv`dEAYM-Fy%zME!SxU0w+`2|>8c!XpAGWmX*?lgBorCOnqH@KE ztE$4t#QKyfjnAsnjq-FK*M+Pa4vcyW+In&Z=zqYlb9~ zjOcbt)u;d^A#(fa8f~h)G<~ZR8}Jgpzm? z5HE3LHnieD;j2TlwO~tL43exQM)R*_yTzi*DP1{=<|^$EN^RKaMm1F>D=d2g z6(JvGI%E2@h*2ELr6i13C82-)N`S7P^UHby%s{~`3rS>I^BHc$tD;*IJZVWcw3d1N zI=rV_k=g+oWbKXY09|QnA!N+hBd6MN#NV7G(NIoGi`$hvtz4Q?r6tFT>0e>UT@bd| zhL;FMH@V3=!aMeWju+Hx0WDoF9)7;R4IvB5>UU8(Y$2SkIn*M0yQcU10rWj)D7plJDAhpOwd*q_bQg=hg6vRoK ziq~l&=C=jwf)nm^Yfn5){VIp(QqI=osZ#Zgq`E#fO2fFOBhR6e+={1NUJa9G!h$P~ z{F{OhNhS~R7azQK!KW6Ua9sM=sdoEu{JdapX6!MS@3(1tsoC_N70tnSGu!^Z8AZ<7 z&>or|+gz)AKZE87r(*|SU>@=nsu0hrZ##!mvcvcvX6gU`pc{-^ICKtbO`k_A;2cQZ z&QSI6RCDOQ@zd79Nw}9w?Ij;f-R6c_@YmFJc!pAdD?wWBW5VryAEXEt zzr=0_|4FIqWwIoWEJ##HJQEtF#i@rcqIvk${OWc76!R&jDf~L;6eF0gQPYSLP^|3h zJU?CfY9=G5u(W+`&)Dnh0mNC2Tz-KO>qp5A8)K$QQfWMDaVeU>BKH>;%YY=f;jqzx1_Bf&&96n?^eD!3!>{67MA(G z-|(7HGFX{ySALlNgD)PPI#1-$eCp01Xq3@t!D9i-zXt9~R9Q~iip3OyDo(N+Jj3Fj zJeW+C5~lGJr;JzRF{ zW+7flSKqz5kBD9B6$vx>;k|h77;j{^u-5X!g>bK+`1G06?bmNzI8|zuiQ6UoG(fvX^n_0uGj3L_=zd$wqzlG*<v~F@k+_)t5Id?z{*rW#T+Z^EUp~~0nPt9bYOI(`+P{5{ zQlT*3>NT~+&C~zyY_sQM`~cty+WfFyS^QNM+-0i$lH=PK7jgsAI%0j$l&(nDv*ooN zbw4`_rMPTC85}g5d)&4|qb1DtU>MJl@y}Vd6MOMJ#Wq6i2-F3v_3Ym)-#t?treYTZ zZjaZ_lW9m3-fN;aW|0gaRW!LcIn|+rvKe%|o3Z@1;S-5Q>yC;o*!Z`Gk8Qx7E z*@vLOR*p@gomDru+|sVynSnmthQ?Fhw)8oJ;5QEJib9B$7thYK*G@CxO|psM#>cRV zbIig<$53x;@(VwudU9oJ_b#5!r%`y6 zH0e}*^XCQb(bNv?vEPx|5Gn_Yn!(yt^=@sQt5h>Yoe#%~{v^dCg*ur<=bVPvD_YQV z@UVDYs_Xv{z{{5&q1#MZ0DPSzgj>5K6t?ZV|9-TS{40b*q7@@LB%IX`9>1Ri=aKtS zegbeKhb3&2x{p>*R=AHi{1s?D8rtcSs320C$@McqUc56>ZQu@{D0|hX@4NrgQuz5d zAv!_7-nBn}=h#Y8TyYWAn!~Q1CZi8}0;NGg zzo~MKXS42(*Is1(k`~PlmTlc%6ozk&m%eOQAZMT?@l9*)__2PJo=;T*$P7FWQN@ui zxB6}d6>~G(yS2F6ZDV$dKC9>dFEaonZJvcnBILIS7n+toWd>lf5)DGse{Y$fYPr* zo}*$dNk3}A4cZHzXW6VmLTQ-jR}3}V`N1GRh^*p4ou`g_y4#5L-fU&O?lY~n=kYJG zH3Huy_4JOZHulcDflYwn)wz$@b?YxU(ao$Xewho$pT9xUj-L;3d9_`lD4JrUz}1t;R|fj>m2pm}NLo}?zesTHS~`s>bU zWp;Isa;e*R;w0VR$8Eggr5&)r$X`{tdi>b2f2-8X(etlHrO-FWX__`2J!aGadU<*1}WZ!}!m;w4Oc>y9|dB`?G9 zvjd;wP(8C+uvdd@;N`GW?6jF)@QQ)IV(5D0d7h<2W2iLMhZ~EL*)smwHe1t1{x-r6 zCxhAM@A7Xl|4;ww9=f+pe^9#m>=!-k;qEKkmGO0&8k}W~J3zfE`|!A|lw};Y>OOBu z-@FLN`;#;-c05lX#eYG!`nf0`X<#zWgkzd_QW*&JLbH@AkcDI8VFh0XFM7{C)|41z(JkPJ^e&l%Z@Sl#g zH4^44h~PJ8N|ps#=1kGQ(xYQJs5gGvb~U!ux4r4V2VP&At)6Fby;jitlE0d&AvS#8 zNMa^r;w9?dy|`P_dTon*)@^g209~xg3#Yc~h7IVZ>m3$p6jPYis@UJ0p`@W{nn^eA z#2d|ca8g4@=dgsGKP?&%A=EEm zx>c~}m0Uh$dQrc~*hAfQu5Bl?l^DhBQ0Hr}=!Y%)H^WVN5^c2neKop!UDC|N@EQ=PBhyRvlZ&Bd?+ zwJW{P{kmtwr9f1t#kQFtgWaQY9Cq{E=*1k0ENb`NhH}92m1?;$?I`{6?(S{1TY60O zP@J=u)Bd8ISEV{VS+nJoX$5QOwh{rI{j02A$RWZAo@XpHcHsh!aMNW8S;m^<}9 zcy9O=hPgI?);j_59R_qy2gz6&dh2K zUcWluQ&UlgqFU!b1QD#I6W$MjV^j6MCO9wbe!y&%OK*b$J4v_^;SkqN)3{x^x|@R+ z5m|ZjiU{q8Qo8SEzjdz8Rh>HVDeC@lbd{O_T%ofz9#eHW@Cq61FSjY$QhcrsRfRZO zV6T1E|Khiz*T#d@thjDpskacUF8Zff zqE#;BYI?8M!tfqt$-ETXSSv(bzR*Kjx4(eTx4dZkH$OCwK$h% zqluZ4f3hnjiQ4zEs7mKbISreR`EdJ!C)!wD4DUv%mG6Iw+c`B3)G29}A7s*WE6O(9 z)UP_>Y780kt8vbG{Ts2zo6XCgV7;_I4}v(i!3x`JG*-$UTchc32o0cg##OlwEM$_H3g#X4s~yzoWTfn&N6- z;{OM|IzlCJF}-u5)1unKKR}puQ*g`Qg)ZHpN8#-#v*7jDv-h-#Z8-^ZE%*ZNeRI`t zI3*8H5w!h%_&(m}o$8g(N?8Ixan!DKD-#vq>OkJm5Qkml;A~MV-JYl!(QW259gH(# zSE9{pzn%7$?{K$zIH3WP`ojRQo|2@$N5NFZn@4^_YyKr0dXKOrRL`2P`3Oa`+#ge` ztJKV*M$1jX_Qvs)~Wm#u9Sc$+X?^9=(6>K6RBgi z_~FHCblo_^|2V^M?R`?k(Zy|_`Ub*pZ>6VoW5H{%{$ybNc;Eg?w+_w1@!G7GjiEf) zC+l|Cq6+0~lSg-aE`&>#ot|N;LkgoL*lzyJg6@?}w+szJx7ml>4*U^YK;D$#9s^4& zAF}@{L-29UhI?1Jfz2xOeA_0AV{f)R{;htkU8ywx%P!tpyI{O-SSYOH*qC4w+53v6 zrd^kPZbr7A5wXS6J!&FHCEDi2_f9uY?qZ{D*F^pFFr{v&Se1wNxxJ{q+Zbz|m)N5P zWRpqlsW^GdLXE)FYrCPgWV(i4t(}z#I#MebI2mTCE)0?NZqdj6N1dAwg++J7{|;2c znpB6md;O{=S*YvwZ)taZ9-9#=`TB1cbfeh<>-LFMKtq-EN+RIYRx+vKL1EaFOR8xN@yRsy|K`9H}=A-DatsRb@AnU!J9( zYG0;Ls>25p6-O2X#%RuB(g>R&EJy~f9+6xbd*&1H`6JlM) z;kIjgJU-mFadzuYPPf-KGJw~w(e1=8>PyY$RozCcg84v_Bhf5lElS6s0yg`?lWC zne5I{dUpQ7WP@EfTQbd=J6LQ!n7!JDn;_)@~Qg~w%XCoH@lfuHO^y%=9~x( zC-+5(C6Cj={thB@Xon+ig?F#XI4xp^c5FJk>7_wCuaY=7?d*MO80K%nFMGdYC&aAGhFoh>O69ddSd6I%*>4 zUVr<`Ynk(JV&YdLWhGz#ny$)`mB>_WbT+?yu-i!-e>ITf#R z>a=M19GU%riMm=B#L!(-d%JEMuTP~+U!)`hk(#^+Y2x||N-|>?Zz_Fw`ZQxoPY}Zx zp^|i1i@85H_i?I}f4yyvawo#3$(~ZX{$gZbP2YPxcFtA#tAKXzB>W>|<7Z1TIZKZ8 z6XeO1(i3Vw_Q$}%2^mw37dsszzaLu`XP8GVgjbt~i&rU1eM$S^GWdV{SK0AFqu^d{ zXj8Wj?x3`7zAg8=yWgmV4x5Gi*0K2oDi2F}REAjt2Yv0=-Kr=q+W|9{I{m9gvGnnM z=nJL%lNb3le~a2z{s)wEB6ht*QP5m!fPE_p+|cJuhyY;Di$Z6?@cW-?c5 znL17F4TG9}+68#NIDFZQ>lU_(?vQS69UIf>@TcYT zYX_%0yS~BnlRN8=wOW2sqRJ@sNZjzNpByEw_R3y&A)q~Z;h=OVr5PM)hL*TVuj2J? zahRu|x>b|vdkm8YP3Z{QJv-1$5onip`O~gh<%?+?^Q2pU<0{4Qqx<;UL~XBTZPp(4 zA1HPzb&+I=^ZmN^tyjH2l>{OL3030tKY^O@aiqgs*QF(YIcqL>Y*2fU;aDyr73~s8 zP9lAA#W6hb(&t19$(8=*ZZxiObZY{~7l$H=T!}T`M-!tT^f*o;*XJ3Yh)Fm_CNUEZ z4e_*h`!W6^WzU^sm1G9hO{$e-)I#=_P}x8uY0ccx9lI~m;B zX77h{Xjl2Q<1lv-5uG|6o6K|pHMwGnRC`@hogYgGhIeL5-JHM+@6E5)l$WRC-Jx?w zjXt3}A8f zmi?%ocKw2{DM+*8HLu(=dr@MApHPT9vEo0Z+V15!wc0(9M3^&}=T!fmIn)s@LEib$ z!l|KNdPFR!noHUjc~I^KsSENHzGCVxHPHoAFk6Wea>;7B zc|M4ZbtVqOr*RTInv8bQP0IG04B7QM3*=^+s#a47#e zJ9Vc|oMzIJHJa#xej!`QdVgiMHfL(zM;xi8yW#J-d#Ns0e0J)}VFCn8Qg_VcGK}aS zsAPIKhkvUVsGh(due-I+ckZ0JT?}IyL-FetVmA{B53UR*HSNKbMr5tY4EZH)2-aUK zYFvFYG2KZ}yIDzgia;Npxk?f>!6Z6L!|*}-%=b87-1K%jqWf5K)`ZSPBq;G^UVfQv zzxR%XgJzbq@-KbBzV@$srKcjniHkemY#F(t^3Q$k&Ft9MSuI!lef;4|Kk5`9!p4NJ z6f~&VwGaOV`z*Y0+3fy-tCxChl@7IgsA$RK1Kx{Fq?odoyl0de&1=cT1THcYqd7LF zRK$+NGSdik@zJ$7WBMdd_N-+VRBBtvy&2rYHtggZZ&&HzK*aXIL%IQ zs&YHx&V7?jp+D)D;PhTxEL=o%a(|$)2v+b0`d&R~{VspGV*HwW!+cnH%Q{Kx$2<;Hq`w?^Z76O%1OCJF4HahLnoTyp%?4phQ+jMpWo z2p=tdmr+g(DovgeV`Xo>s(;|Mudn2?asMbly%&Tc#@c=-H|Ob9m6?Y3Ct^4q(;4vx z?EC`knQ^7Ou4$@j#yPhCysN-kiTRuevcD9sZ&A@NQ1lO9NNu0|W_Nlovue)I`cefg z;WnDs(Z~F8>B@^~GY|3p`Ky20y-nQxR@BFdo6}EaJHJ~oS5@|UQSCRX0N4KFB$DG# z=}3D9B_eLZKEZ_fOEA8T=let2%^&xRHd1p+4>Ywvma5ueE_(bk6bDC}Z1`~~RBQC= z724OXLb=Rs^rrt2E1M%;ZSD8`_?74sanjd)V@t%#Tx2k z^uN!1fysfE4V=FT9*yvI$WxqoWbxiz5L3T4X*N0o76^tseXFJ)+Ae` zoG}ki5SQQ0L%8a{alxmwwr1+m`4+ZY4zPE-@D}njdE0oi2T?7)jcF^Xk|89{gZvx5yY@)mEN;$T#9$4T>H$MP6C4#qD z(@R+(>8Yp}s%zAlCBIx+p-P4SuH+s&WzN1VXT)fO(tfI@4>f3{Q8epaOmKN9&&u4L zQZ$EmTv$6c)qtvi!CzYc?D)6xq1dUR9I!@N?Yh)2ZXsXa z+C>dI$^#m_s?TNiy%gVX&F(F@_I#J!k}inTiY|y}OZSd9_u-iP)Iv_T@1a6W&y0P) zD!qNW90;ap-_G;C+~K)W=O{BlQ;anPV7}Qe?qNN&Be{0y>SO3mf|{unqyDj%G{NZ@ zd-@8VgfxgLf@3N#7Bw6F83nbtCeWjGM$+hdT{E`6?x%;hLTUDWcDn9mNnhIficzho z+aPdXw+0*&?3KnQty#9$=GRB-6%zloF*Q_G%TO#u{$#^%P`ksVJ^#eO-fXy_*OcVf z9rY`b5&g)b`8rR#c_8~bjZC$9WgbEigj5J^WE<3h#eqB%0Y}ko19vYnMw~)ha4yRAil$wK|XNi$?J-cI@;ZOlf`m>4qw_LL@s5CejD8}yP4-z+)-`lPSwZm zdB7@|&f~oP_cE#t`2@g}Py2J-wx)x%{}Soev)Ew4iu&!IbKUr%v8i>H^AYizud1w= zhAFYQ2fhYm-&s?{W$iQE>Z*sG+J%2HHg%F{5v(PesEcb*Zzdn^28^z@K8NZ_H`Z_} z@P;)`%1Bdkk{u-%Kf}`k;wSASn+j!Ea5Yoe;*a^Z4x#Io-4(B!?#WO3)({*OQA8tQ z!*wUzgaW|}P4P<*m|W}o`Sg6UpeYzLDTlg#p&mTL^wZQ&E_92lTYJQ!-SBWV60g1W zqnz{na1UotrIqR9^)GygHZ*zf*!W!`Hkz1zF7(Wj{=rFB!;q$lCNtU-=2SiXPhA>m zUikZo=Ba&boB2Nkbn5E#WzP?245)86hhJf%^`|R%ZOp;seK?Y#vnHOXC)_hp>dt1HExk33pE*c+ zqm+2;>%-ICiqZcB4c}P^??nxY?5f{Kuil(KRpd-4j15st+-3Wcup^P~(Rq3-ZsiSM z9FP;aC}_5-zx#uMxW-Genu7Eq&4d-3&I~K% z;Ryklav+u%%3!Yf_3O2^sCNRS|7KdFe#szvA6&nvV~xr}N9_FBGbEiNjXg}IF_*jD z%ZW#6vhaq*wcUa)r1bUl`tqWGOo;gs1tiVg>7a?_7D1a;yrzN zrUs@~x7K{CtUdS3W2cf-UDjFQWw%pQ2THq4ogA&n=;Hpn+3qofP5z5(-m9A5x)Ubv z=T;Fqi~aepE7#grNaeA139Ndqbv3}_jW;nr@hlxR%o)#ZtK3qpO;r4<_NvCu@*X4e zF{eb?!r)rYUC4o1XmjJwr|QQPX(gP~K6dPSZ&hsxwl6*lp~>H|Q*b6aIjE!7UBWa0 zV{@SSPHqfrK~IPo^^8}_e4KYm?SSf!yY2kCu2P2r8|l*d#~9TGP5!*MS}KxFY!H7n;+>sfWCH2EVy+6T1Xe z%qzd%m!r?9LHI^j1@{)RR9L5}O7pM&r2@i12T zHG92knkV5P+$Ub@32%5Q~&#E|q&x{q$GqSIt9N;uc4cfhEl|sEZw@0kMO_ zFM<#D}9J#(U)Cp8c9tKMgC?QR8_3cF{Y_s==aUV7Rie>H(t z>tJ}{Vv_B;_8+*`Un51|TfgQ%-}c`+*6HA;0!0A1*&-W}U;LK0&wP?$l7;bj-AX87 zs200sJ~=R&q>L(ix;0g`(OKxk{;4umG|`M2DL$0vaY zfO~)s1N(dTM=(DM+zEUF*aF-HcthtJ;FA9zxV8^BG2ssZnH25=48IJxW^Dc~Z@C=H zyMR5wseqT6bjQy^5v~BP0+g1_d<+m@JWVC`Z`ngj=_y&6mMP6s6q8SANnn)NzvVW8 z_{U6w$Gs{3s&+dQ(s~I?RQZ>81*-tmCq4O1<~wm)%9{S#!IIW4{C*F{6GzZb_vyWT zBW6I_*t>syNJs|m2i^}DNziU-`!~8Xf!_nnqeQfy23!oR2Mqr>Ag&F}4Hx~j&j2R? z)^^R+fPU|=bZ4W0VQ+l*i^ND!<34-7qlx{nRo!OJ=?wmY3KG0GGy-G1KbE~ z2i^zVzKM^bH?ocA${x02TK{YW61W@?NBqTrVS-`$UkqFb7%uKEU}N+BX0Ez2Sv5;C?{4+HWWhb zQ$#TT7O?WQc8H|E=2F0{=r3OU4Z99F2e=ft7BCS(`ARS1OB^#MEerapv8y|xdWdX7 zNl??mvz8b+gv8S=gwa#lKl=dhjOisQ$tZqCOsJ#sLWcQya*`R>%=#AfC0>3OLpr{R7yzJAu~gKJp)G#LFKQPRMaxDgd^ib6ABQ!=3>YUWZ42T$-`bxjham_m_SvLLp0 zQ%OY-SGQpb-;|`D)D0JcFP>r2(Ldbu@tWT~e_V6qL*plyUBwVu+Sz2`s(VckjV`H> z6U``GCfeEkc7sC}p21KHvi$|GP@EP<(l6>Z>wWK1OidyJ-m`jg>V!&;Q0a$-LG#~U zM|}a+L7&eXp0HOzwa@KXIDKW(kUKLJ;n|~&W;%FstnlQ+RK!Q?gTF+VdRx$fqA8g$ zWt1>_QoE81j5P~LG=l}HM_z{YqAu|pW~!1jOnlAMw#ppt(KzC62OPRa<6v(;_6art z`k8sBxhKUi&TB-y{!qQ^(B99r`QgEASY_>U?Wz;3FZu|MjV03dX@3sN;q%xD&C@TV z^zq|~dW0?9$WT#4SA>OyN`?#$-y#Ak5%=z_z z19<#2^_K%7DeA9XKU2|sClGv1xk--fT*A5Z@P9`U7D;451S4=^h33c0cQe&Xh;Ojz z8LT_eq^rMOvJFl=`EaOL0^&Pho&|gmaH?QjaU9hJrv%2+e@$=KP1t-9w~Nw4V)E!L z^?iUfz#iTi%w52Gz=WNzICs-+f*%GZ4`XJ$+^W*>L{4I;D$p8||%VxyAKEdh(ka3!QEK@%{Z%oy(QrJwl0 zHGVRnc|Ne+@)uEv$c+#`iR&L?a&5Q-4AV4RMpN^}l>z-_Aj||;e?dA^mcNb!CSl#$ zN5iy7oMA+qbAj7{;D@o$&_4`mk3^(bFVyR)=(Yk^0J_bQgcNI-Fk;Gk7xs04j2JH5 z3^+X%&-lSLOi5@O7W%js6=LP4-TqmIJ)5QBf|Xi2Hto%TeNbjIy6qaolcr#UaGLs_ z-YxIOxBi;GAih<^dVddaIj{wgZ|Kig%M!`VYbH|cCS*p8aD8toT{A0AK)j0p1tksL zwp`;%J9I21?biJzWA}9g^bicR`I=jaNhnpogd`M#y6*+-G_8x4Mrc^sg&lzSGJ8?` z+IKnF@|PjCAWTUrb$bFkRdJKfyRj=x-TL1G*lXCe$e)?Bqn-mS8U1yi3&_U?z_{lC z4wCDEbpYwjSAa-sfU^Mmxzt}XZ8v1NcEcrlHgGi{A?-3@A{X>_Y3UYUQxH#o`$pp# z_IvgMa$=yl5J9(0Sf|zkA`9BZ)9%!Wp1n$}|_eiE?Q zUSJzw?)0=5x2ProBPmh|4S;2U;@4loCSuQSk;|~fX9DI>|C0gZYf8@y+QI85o<%*c z!&WAep4#;j6n)$)u$xfaS7Nsw7|*bnh8xfL`f19HkhuDrds^&#HQUWNEIS!6he|^* zmsSS@Xt;5dl%QKlXz%f@0JujnTN$E0RD_{)$1Vv(s!l>A!D2OA~WH>1$W6~8z zf17XO^qe+#|I@Ns$m*}3Xm9V)N>1-feCxT%{7$rE$g#O8;)1?amJ7<{+ z14&R*dO~GZ&=*5^q-3Vuy_W-T^aOYW{TRa= zM(pdbMrrtYOyXB${~QJ2e(3c#g03d8D)8EoU}Qe~uf1D2~kTGLeq~PWDVB zOi4Th^h<8_+Xt|AtHprKjK;A_7#Hnw~`d;0{Y@-|*( z1-g}ErtwKCS(HoCoyT50qGeo zB)rzSmc1Mqa3Y}Fa+M(?SuHIyS8C!IX7#noCT<8Su?#Z$Sr4QwUG37eW*P>aZY~mz z!i0h0_GIjKChrCmsf>s($b`&T<&9(W*bW#LKj|nv6SgTRy~JNKw!E7giYzF)i@ZTt zYDM~p8g?`n*GZB@btNIiZ{^fqMlE+^TTEuk`k`qYGa%lLfJGWw;>f5tg81@dxL{iA z0dP8n1QW1mOkBGn7RiWVJ-v+nVb*X7%CJQu+zzCtx)r+;7f(hk;xM2DFBqHbL}}nw z;GXswPKf5kvre807$gsxmXH}sMl@Y--vc-&NxfGv#yPt;!>^{ovE-yD=yw)yE}#r> z&(}20j08b}m`OJPj35}c0Wc$GP|z-e3L-d4-nf>gewxXU?t1|$c&U$U6Yzuj|c|d(({0+s`Ntk&L{j2O}m?5d{<21;~)) zz8$b0D>aMoZeRC)^1elpKw(=4AoxQ2JMNqU7)0D`G?aI{)XKRtC*ZTkkBC5ff zRv$}DVJZOq!q*ACJEnMshv%8zu3N#`#UwA{38Z^>)fssid}K)Z@HRk*woK@Ay@htPgBa_&g{!U;@H%K z(-%{>o|SnFi5kpz{scC#-tk-3a$AI-jHX?$@tj~`ozqust|tb6oi z1AfN29vDqu`k^a5(}|yg7|nF}yQF?c$Dw)Y6evzbVW^N8=1#X%Edr_X+YsLEyWPr> z3X+Unjh~H1v{cNHv}H=pq>}lB={C$v7~b75KF?l6NYxRgIKVWjZlgwq=748_zBzri z42cm*uD~v~a1$VtNnF3l;Ug5sS@9hgr?{6jSFfl_1`@eT0d-58GATUtgS}1^aR6>1FS$Y zk^F^ZDuT(6WAk6+J>XZLRH0TVWU z(pZIEkj}iGj+Bm=K{8?zmSb@YOQI4qjzsj63Eko<1*IsBJAhMw;3|5>ZkXvI-TC6e z@-niCNL0H>y0y;%Cj%y?$epe0w-ONF1WZUq#M4ZCP2(9Zu5qNR`!pvpMm9nqi28pR zuou$rUcksEAffjF+W~WXHDEnj0@yUIKNEm7^wYi=NE5d=vu-T4{Eeu%6^Vqd0?q}l z04@hiMDt4Ey58K3d3*1^0aGTWW9>IFTaF2vDVecS%6$CHgqa$)|99YUA|ujCfDvuC zwtqWTiJuQxIj#Mcwk6J6h%iqBeV zxRLcwWbxPbunyBoYK~+^f9s8LEqlZDvo7ebpXSn(zva0FFh@57n*o_Q9+>Ivx)r76 zdjg;+g*||bncK?%8M6pv)cD4^5wJKd{|^94$?%ITeyQWf*R`4{pS^hU*k%_YbMRFD(VF@h;B2FW0>irF~Y$a;($eg(q#~j`Z zSft|VXSf-Xaqaq9{u7qJiM}6@fCLRRM>4UtcS}gZn&KH*rgaA-ZmxA}7e{vzU5DMw z87}Q&EbU)M0>h!b)M7xU3_BYTPo@;5MD$P2O-SO#Q)(utJK>S4VK)I5U7gK|AkpN^ zM8c&N&i20zkP*Q!aU>+s4SJsc$+K&J>Iq{Q*G^+i}wSjUSvDgf6=t0Iz zOu|Y-yOAZPU9hZU4~K}seC{jD!o0oMVS1J?u6Gh9)Je#tjoDJ_Q(dY06*mhJYep)FZqX z(?mrMD3K5|o?v@Ur8Hb=$b__HKpcBd!;+RX+5`H3}uq>sy0Z2})aOVJL0c)%T)`qo!HN(QT;H_o)rB>;0xRKWZ)-Zb~HNvak%oS@NBtxehV?grxTR+&5qHuN&$vPm+P=;@Aw#rOXLROOQc}M6f8Wk=D#>d$)2b)tgg;VWkq?Q^Q?-rr1#Uba!oY* zCV9DUHIKetj#D~HJnh;Vem$@mu>Kk)tx0$MG;M;$OA^kM^tbLBM^>bNC7{1};%mRo z?KC~ipiNZX@vJdmd+)YixeWLi@E+j(fX!3^>;ZNH8vyA@RA!g?9&_ zxiyk*J3B$bGGSjeny~)Pn2ndjWy-o_ybSHq(=TpmN2)b{FtQQNg$Zcd64DBiBc)+C zqFb>mH9Jf3m0ofy4Z}iD`s@2}(=jvp$;f~AD3@GD3X0BL>u18+g$;lN^cUwW-~)h( zn5c=1W0@;XLYgLEg6{&1BOU$37dMi!R}~-J?mXR`i?|3dM-tIAlH$^B z1PMxHEg-&3r4s74d`;LmNz7WVM9&12vi^4ij&rqsG;sq>AURrx-NYnj=r-rNr6I2A=qJ8RR{NjBB3B|B7!X0bk(GwwCj;hKNv#8th&YCa z#@W5yN@kdhOGA2&?1?va_Tzgw@JT+5F1o>X6Ym2|<}AQ4`L{-BO6~z*4V1)4f|Zj4PfpFdk{m4>x2ij1UXo5{WFdN7%`OL}QN}*5enu1vdj2*EV7=V*}RR z^*HAqD_L_bQaVW!7Fjd*tH4$2E9%M6ux#r{$_i-{7TE_s6F+=H1(I9 z{zBr%luv1km*mZ-ps0Z6e?Rkt8w{7Oy}6yJc?kY{v2O#Usk{siebWvP@^=+rIbQsQ z-P83-X#^+I)jrq1)vTGk?8;t9$);ht#WqZH5fB{R;zU$S>qp`39$%6^a8>uO-Af`L z^ODXWvI#p9ly}`u8l8b^4w`4aJY7oqCS@!!q^Mi06rTQur`9Mj!|ZmXZg!N0c3Czo zTxbt{&2V*ghxd+Qev_3T@%Ye}=>`;yr6)auToXi;y0-#OWwcu=+W~RqL%-AZ}FJs@m#lW|9HTWNwu39RA|UfH(?9K`C-U+|alWyA0~q z-)_`|b(`rP|C|c6UfMUvu?Zw6+Kn*ET)l48U391AJ-T4}6!R#i`L?{oHYt1W^7pOU z%BTa0At_NYO)I6Z-MSVY^cTkz(q7(;-5Mm05E{}=t&xsy!{wj$cKNUc=#j`PdIa4@ z+6p)kbutoR7$z~^>UwCpF7wNd*a^{H7?`p%sQ(+y{RPeGA9hV$X=_gcMl;b25xP^K zk`sH!)FH$5pWrtbJU}pX;x%foBJhcbdUjx!L2srY7y1|KNc7-o%858~8%X9VW9`0M z$7WC2L9i2zpcpmcHu_Ba;O(?8OX&t6gUlA}k%M+?gVj(|>Y9dYx7S$-WTeo~B=py` z2Fckj;2gkYZKJmW()f_W?47+MS<;l(t$@{C0w)3bM?A@s30nP9{f!rU=-rIp`M~u+ z);JN@IZn1kl*2W^I^ZlIk?j#~0yY39YWQiu9ld=urt!o-6*vR<0AQ=Kr!sT;zY8!? zr6sOlD_l0u$L`ErXevs31QAvN7K`??0lP38kN!5m>w3GnejlJ{#nrU*ZvbRkdN$0R zfD*7cEgBiC&HAlawgE=8FSY#aeoR~>iQNWB{9M2=@g*i9i^+s!QXCUBOlIxUwCisi z6W6b=ck@*?si)@=um@07b|LXMl8or@Oh;lSsJXT`&7nPl%*%*OWnd65&BjdWuiq(_ zzX6t+HPir$LP>~Vg61%FLO*M^{&Rqp$^^}!Qi>!jZyA=h_?EjBQ~TwB%&!M#=X;AD z650Z2x5g@J?dy6|yF>yK`xqcI&KwL^0)|KQW@IxUgO>l@fTA}o{pS^jNQ&CrnS0G7 zW~G#%iRdp_b7NnMT@c5(+W~WErId!1RtXwEG-SXy(0E&Mv$=WkKTp>$Zsqejz$lUv zO)|1!l7`85*bq-y=?3Q(4hq$L znLy+q{#L-+l(@DTX_&a6e5|roZT;VCRa9`6jJY*oxz#R`k#t+PPXUY+qV@(dA<+z4 z(h|qQGH!&gpP*D^So+13HNgNAPDD*}DS~z(%$R6J#GLgXK)0KOu~YVjrr?Ze!6_$O ziBy!&YG6BHvsNC;OOQrVl+DTJ>0763hgM%P(yJ&G{cH}VXw2&Y^CONNSlr>&xN;eH zQqg}X4D9-c3k!uf^VN}W!y!u{VAC;MA_=Ku5rcx#-)f)4G-m;E6kDo~cIg@}lY;in zuf6@FV~cJ#j-u3*X~S**W>~&-8=iI|p8lrsVL;*71=}>m6~xgz&%b$2UukKipE4*c zDPz%OQ<9NmqRURQF!hDUxDUIRKaiWS7?!WGR{&xKJ2CigC4af8{Xc81`PmAXxCLW= zv?pPs8=jgjzT^WYDUP5$Oq!fH={J&mAFsLe+sD<55vG-lYo6N2)1Lm;IPxY>+C%Hz zy*pVqEM+Nf1i1_Qc|C5@gGTc&cOF-AkhXEHE2b&W($r6Q2e1J!u5{Clr!km!{cLjD zjU#SM3wkA>Z1lIGNFV!rF}d}aM@0(bCT4_WSaS?v~-mWJ_VNE!;p_%@QA zz;58qv2DjnHr$GQGjJ}TU4KVBYjbcUKAKbUbo*{p_W+hI**Xc34P_`+${~~@d)=WZ zo8rlZH0;7o0mLy(yEJucDtqT^(g%NjC@(9s1VljZ09%FT#V2{bjMwB3Ts3iu-Lv@&35#eNFotkJ^WrCF zkCT=)eW}mj*Z;?BRbyJoSNQAF?;?k&e$JvK=wNR?(xXJ+sT&jZpRew>0#B@6Fnye* zBY|b@K3~eZaTkAdyPSh(Bbq=GkpUC1l(zv2{x;xjz*&;~D}yz_IzZvs%5DW@PCC-i z9A$2pKK>g3c_E#9V;M#=@kBfkyOPoG|Lf|`VlBPSGcoH>v71B995#zp#VWEmRn@7P zvp7_7E}o%IjT}XmIEkWUNdellePJ90XrO_h5ewdib^{I&<|=R}h}GRl&-A!)!d z`l1ye0TQ^;@l|dTAOv#JKs$!=KL7sT!!2ICXRY8aPjI0H8z}%xzl~jwRdq?=OPFtR)|IUtBOSWh zf?cp4u{msmDk3mo3kaD*6&M3@wiM4CpA&l4dIrr}GH@n%C!l{VSPg9LPXBX}H^5(B znmkLlKIqsXw$NH2KKa5&r=tM7XQzHIFKRY2wb50pNui)oV?y25gVv;`xTMpOPV3ju zjLPLX>NqXXr~mb@Z<+m_m8E>pyf;3d|A-8qpGN|=lm?9-xcz6VX@&ptzs;?RB|LC) zsS(lJt^@OKbC?~>8l?(D*&Sk#fT9~vk^7U2)Z94_3V+Y6FjYcDloSRoCCQA65d@~#4=>F zeR}GDpB!MWA)s|r5u<_qH`*L6Yheiq& z#kMBWEyv@xr@JQc8xt3$8>>rCALo}KpG}5$4JL_t`}F63Z~lYu=BS-Ir&D`!H!rXA z`g9c0T&v}gtsc3`K9>1g0e)h%NX7|+e#^YbTOEOBk*;9lqwAJ8J;0O*UJEIzqU5wD2j&d8Xj3dvD z%TW~p8rzxQGZi;K$<~!aY3>rcGClNKKHmHf z^H#z65RBoIP_Sx3rmKMn(PdjxyMMN}G#UQA%;`lv5UdBTVu6wNB(4Sv_XI{3YgLM$ z1`WD3qQ}lIpYFNOJBrM%99Y^v{0HV@QjJVPZv;;SbnTI`vWvxPCU}nqe4=#}8{NX1 zetg}!Mcg%fjL`Hi{^72-JTAzJg$?q(HrPe)%P%~%(cUnMbA#=*Yv<1f-wHkm>b}o9@?@8Tj{|G>lX0sqwpkm=T@Y&Fh0M`lHIO^nT0qgwEtN9? zJ+DYWEsh6%j&L>L$VLlX(2oa)18pv-SC&@hhGjvJ6X1k`fCw(WYm;D;vDASqK4T7kU3~kyE0>aj< zhLK~VOz0X_=G8#8WXqN=y%?y74s%G@BTwe#&3w_$HHN9Al6CeZySD4p!4k)*wfX(m1fQ1TA+lLc{p=H0$NLHt_NhMFRi&e7NEB(EqD0yH@Z-2 zdq?4cfOG3sfDvUh-W{w5mjZlu1;ZZD*5>r>t8Kd+2%hfa0h!7j@xwb^NPzj0f%%sG@{9nO*$HyV|IZ) zxw)O}KS7S*bU?|RP%|N1$M_ro!}N~?GAxZugEZ15V=f?vb_mA)bA%|PIwc4-E<1BH zisTqUYhcaJnk}+*jE81Krz^?f-4h5Q*yBY3f*j#8OiZu{#Es||8-)l(;Dj7jI9|G zh&f{%3eIJW4%Q7qpyZ4KppTC>d{A2>#^lIIxz5ZfH$Y{t0dzV>VBCnm5ol0N?Wh`e z2k6fS>%q$|hP{E8w6Bcwof%!T&a_M8<$%l!0RygDaL1pA*$p2RLzItg0rlGgG@?wJ z?-)l{44O5QKo1R%thG_RT!T=A`D`FOqZBgE|F#Pe9t&OyUK~NNN-jruCm{D|;L%`y zBls{l8K{W@9uL^$h@6rB_X4My{*xXhvPZzA=%pUW!Ufy8ELBAds1lxFI9v&2h(}K~ zWPTyI7}Q32Mo*66jVWcUY@#(X%z*ON@T=n*?W5p$z#&77quZ86N1W5|mau6K+ReE2 zPPXotG4duVJK_bi>9M~bN=jEs?O{YIUjELVe6G12Tg4STxFTljj(! z!vWa_P4-go;OxuSb4j>(kP2$Lw63VS5Ut6a3u+#%X0K*mnSGh->aOY0X^7SvLB{!C zL@)x&f!GKX_U?OuW~f6!38YTtSZ7Z!%+dFcYkZKLi_&~}wWeb?uJ_A9U8BL^ zYMcuP&IYdfr?kuynb`U)hL#bhs~X)1jJtEt`kWw#Mz?%xMLILW-0?aV;L&|Fz-RXz z&y^FIU|tE#j|U7M2~GtR&^o@8`N;t9+v6w*GN%KXvFDWpa84JG-oxWa|Adyg5nysE zL&9F3xauDY1ho?2Yaa#~GLmPIoU8o3K(7kExIc5ZHF-2`1GZxG#c^Su5Isj62Gwv7 z2;DZ$cV{HC5zuQ7)Yz`j$e`0PUJJCJN|m;f`C9O1UstqbZ-n@E&`RhDjs$adq^r(% z0;N?My2djB8KY{dOdM>L9gi;1!9QyM?SloUi-wdRG?D$Zzsk-49dnL26sR>BwDQ|S zY~%BTM`uXV{&TA2qQY?O(cE2LLog_MisqcC0ONGfj|GCo2l__zIurPEpx^WhJDY?M zn_07~0HX|W#;7%U^6~(?D0u<(-%%(58Uw>98e_qazKVMM`StX(3~tePR+>< znT>0K_E`&H;4#`YT@NGyk#pOTqg%V^D#dnfUAF%fXy$|nG%!x;NM=>W zw@ogU${1yCBx_t7=K?wu;PU~){xisVBf}6BI)xf9--vH4uYr-rtGNWS&+883a7S+k)-E?m(&cjO!g4Uk~i>oxv*s-mZYW`ObjcNSGuuhDkWk?_# z*C^*#0$JOCJXKi_z8@S2HiFtP-Z8H4UYHL*S7>I0M9H{PQdkSl1bARQ3El`euM>gc z(`7?I5<`=eDu6W}nb7Ayq9(v0CYY;6l?mS!kTb3YoL>yC1n9uIKwbzg1YYl{NOj57 zCfS#kX8-eluyoJXr(od=j!a<(sz=8fPjQ+wKH%%c(l;{KkL7na@oO`-X7_%+w0HLE zpDpd$Tj}eOuq~vNZO7~w4@d%Q1N;I$s`_64q7bD&S&wy7+6Spr`B&Kge> zJ0ULx?4r{j`)B%p{^{=R=#3$tB=*5%y7}+te`Lyg+uX4t$yzOSyfrj?Np zFS1w<%vH$R++{=FIp$8roym`<#-^~#j?0u@X-aa8YmAMgT^)vd)9)g^9J+s zNE`lGnSeSMYNmS$*Ersyv1TfevBm@68dS7F*{fDaa0f@;5Uk6DBiV|w1~C^M3g}#5 zTOJRzCA5_2DsnOK%C-TrspND#s$Dpa-+RGh!SHS`m^T87j|NW!n6A+i0sgUZzMPSa z^+Ung;8buXI30L!ju4mK-XmshEwEv%FB725`G9!c{ur=2ccnsjv`6Tdk zvY%~OTgJG|*8){r4|W8uQB{34z<=2{6V8nCB6PoXdCw9Bl|WPO8n9N<+XBJay4S~% zJcnrPYVdCbO7DiVO?bmUAQRAF_EYI8LX9xW3CExcf^(hHK_f31TZm@P!KJ{SbIQ-V zfvv@(GyHy=f`&L9FlOBnTJH)7Gf-#df->qIeAThG9ULmXZCMTMJ*NU>W5m6KQjgaH zW61EepS?K#Lz1$z$T%9~+78I0sS}%m&>fcp^t*zy!A5XFi|);YlAU*u94dl>p}goE z{q2B38*nk;WIe!h28{GL)N%A%4b@o-=#bmtw3{#{3$17URzP_*(4uIzhofCSn_&Lu z9h$^BJr)pY^875u?d8wz*_VI0G&v%a*2QRDRVgz;eB11=|1f`Ke(~DP(|`Bru9uZs zN$&`VJrq#Yf~s*X5Dil`CxJHu+v1$;%m>{~hQC%P!8wh6!Tx|XFI3RB4ez!;JGgw) z&BNoJ{0G5u(AuqJ&a?g&k=ols31|*pMGR!obu4tBo*e0#0$*e4OxY>*y-VNN@f#`S zKOX+k(!;;;0?A&Buk~ifS{`^Rw#YLl17aWY%~%_)a5sjH%8a&ud-#TG_wb)-zy8a- z%g4X_(_O>ARH_581zP5KU?(0A^sc$Q9~(!9;lp4#I1`)*&`t%%g3|#z^Z^gl`eg8M z5FhjJT&iqTLf!5s1$XcPp5Zmq0d88f^+;FyC3EvT;Ps8At^@+9%Fo4iqbbP#BE-T%4ZJOF{Bdz5x7 z^oFVl8d^yK2_B8zCLytnKp=^%amE_CHJ<8#V2xzD*Ikn} zXNT=pf)3uJ_TPfyqC-JZ5;3ffV*_l7>9#r?;iDPz7~0w%w8d>|V#AvX))`{6EG&^Q+qc|dzL z5H!aP*hrQQa_k~&XlI26#X0H3_IIXj%tB~SK+pb2EXIqYSfP~wbW=K^!y8DPlzm2rG6BOZB1>5^lQ zBQ|6$5pwkL$j<{B_y2kbjksm9ZUtP;9HT)UqLBx4MwQ%LrBt8+(&4;ztRLF+Wh7!W zLrR1hWW*fowUI+}S>s!y(+4yUG<` zh-Ta~O%{!_c{X!L%FiL5fbj+WdaydK1&+4Kxy+#ZhC!K85C~8rgHK-H)G=BET*iIYXTIouP_`JF(S(P~_PK-Z{xk}GZ4mdy|-S;*7~g5sEQW0lTj zj>idu4S*A}ct$qpS+`33SXYM5CP9W6#6UC>296oRZ!irMpFBR=tK(S59m$ykf7V`> zU9kU%_LU)#HhO}b3IJ`r68Tu_Pd9*qtXIwq6eeuL^>vk6#(vXzyhXbAJ9J zl5uz`V5rVy(JEZZTsFsNr~c*AWcTf(*p}+-i~lHZicx8%7jhgdqXAX5stglS+%^5> zcP7J6h;k~net3u5tum*{GHu>ew>#nR*ck=T%MFhF<-lWw{9-}bA#aSI`F|8EMBdln z4<~OuH?p1`M{D&|XI;zPt%1G3&F6tsnyY7ySoR0vkEF4VMfrcjRJf*pe!@vM!=!{?H-yB-IS3G z+!L^W*Env;O#j)jod*{8fK*dA?wEb?X13{}Odk#eyB1sxu8ec)cGNw!9w@mAp{)e& zvR;C9r4v-nm7_6Lm^<##?;Yp1I0cx$>5Z%iaAI5yDBeFpV3Zq>K(r`6y8dvoUJUjH zwnvMi8|kwx5FkhXdD~NFoBfM7mTvy7iy`ftzmv{drZz-3C_bpe`@yk5opjK9Q z#{#YU?LeU)2!y0wbfrsQH{x3hv1Jgt4m@RpEQCmrkhvU<&95CohP?5e;QI?}vfA9{ znG1*w@`8{gnsxbWO9$r!k$H|k8fa04SEMJVU;a;d+r`zTn_sT)>a)oX`raVP3~Q$B zIi3P+1ZdW`Pk(Y@a)gGJtaO6;A~?5xaklHkP5Xx5Mp!-))cO1pn6Hn7{z2dzF9&2f z6d?K2<7f_wOvW?HNRGS)bnM6|+{qvvKL3+)M8cpQ#Etf?U^PIuR*B}<>N9*USiIFa zC0E@B#g4kz2*>MjJH*g`7`z+QQMYrax38H0-934mNCfr=eDKSJBdG(N5}iV(9nl@C z`PJ|g)agW{ZxpvVeOCmz_?W**n*wl1Q5&kJ#BHc^wH}ZGM#wQLaQqhaQs#`ix9U_; z+S=8-b>wefdUYbN}H&rppGfe-(*F?>2V6~(zXXbd%lPHpG(r@ucL z-XhSAAd8EE0D#t7AnMhi0T@|QO4qnb2+Ec>V40ia$=urZ*!8{yAicQ_o|roZ~$ zWOFjI1t|pR?owk?*Sd4B1wz5UI<6J|?ZABs^vnA&^8>+|;8I}2bQc-6Hgp1EJD$aW zw=xQbb2TE&skIyPzHz>om-bo-8u&e#bEcL$iVgKJvSWNK*z1X7u^K3FTZUtWl$|qr zJYby6q9b$XgHe^V&CURrqr27K3+@Wo7f1uXV>1hxJA8JKF~QhDfw|g^))dKrzy!D! z=ptubFzB4BAU<8y6P!7oQBuY`^|calEaKxqIwOA{b^HH!$M7plp&bkq|Dizd90{mv zNIYSJ&9Qb2I`&9d!_j~Z@*ahB*e1umktvss`Ju9E4+Ix{VJV*0$CmyEgNM zGp$2_Fyl2;M@GiF#s^hH2c03%X9C%^17yu<11HliI-(bXFEmxFHw!h+R>^{tr$I?G0Ba?OUdI`LGXDTg($*6MCdit|S=22|;I=7g*X z8X0j}No|oXJu*hl9}V6L$N;tOW&Fk)JoxPfEp=&g}X5n(XIsM*QUGP z*f;y5-(T7_d;j3luBH86NZseI)Eh>js-^V{0ZS+T_}$5~inShy8y*X60Kv6@VtHpX z=dRc@+jU^+;P9<5l9WZPCA3Gyn0PqY2*hChdEm6#=jFhGa+sX#(&(TYJ9j`22P)+D zi2VEqQ_995Bb30}$RJ@n2ay3+5NGE03!OZ})*Ka!vJHUD{QAl%7o7zrLk)s4&INHS zI36^R^2l?bYV^rBJ7cRYSZc zZ?n;q$uWUe!^mN`1s~9jc*b6y)-}!!JtI0F1$yT5A506P0T9Gwv=!mZ9L#HkVW@7m zfeo^FY&QUO>yE=Mzi32vGg2Bsfiyr_TI&Nc92+^S92wu$d>S-RL6c+8NS558OH@Ko;koRV}+)jd3@UBX}clgn^zuaQQgKE*tb7X>&Z|d3Onhh4T-+vCzGEP!*2I zfGd=o@viBibCWezTr?zU3y%bvv7wlg0lZpj6oZ}VjlVzHUMt4&8y8GVip_=L-tg+< zmL_Mcv&wVUK&np`ja@XdXmo35^f~{;uqqJr3>y@^Rptd8dDRTF7lW5ZI*o=x=9Yj? z<&Cgw_Vd5U_p&muRPc(RF$+u?xol?N%6uiLDLgWZHmXbC^I~wBv7wD2of%%aDD7(S zLZBqj(l)3X*fab2)%=exeMRA?EM43<#PnhyPColgZLD%}JQ^qZ`M}7YkskfZSVO|Q ze|U|e<`u4xHKFZ3aO}=jUN#LgZ)~1-C?4LL^t`@*HfYwuqRqYVJ{Z?h^}JLmbMiw^ z%>MchzBxJ4$dGz`xN|`?FuXmpU;SCWH}CfmtU;q=l_{^-MF1CqdK>b}ntXNEM!s5b z(TvS)fb7_@W+(IcuL&%m1tZ|4nKdJ?j`Lmx_$>{YR{~Crazrc6jdONd8+NRBE`2zU zjH0%v0A+JQ)zNz9Yk^F<+vZ=_VH}N(%fanm{Y+dQVi#lcoyYN2ywqL`m{0?Cy(TSc2YMK}qD#E4t+rjSz8$r!T=1Q;+wKPY~h<5 z>9hGy)8qsW< zs@QXD^y0S6H748}>j2Sx$|vcX)0vR@DC6mMTZkC6tZa`Zn6IJ!2DhcfOD zY@-I%Ck=3T&pyXYxo!{i04NtBk>3+=YOSek*IJ;H(NyWx0F7}YtOOSW@*3H`8#TGU zK;P7O6%ZW(`+OnLK#M%G_~g)4#(91_kol#6jv(>KkZmFYwpJZUCy31<3#GvE za|Rh+JQ;$dR|rn!IPRQ`-XMc#i>%q3Z*}&+L_!n=VMGU8ust|Et~qc57-Wp>yD&R)e<# zKJXq4_|m7=<_=VML4ncA(;ntrqL+szG@Ftee0tNnciw`gZFf$0eKNW4L}u#&%PK*$ zUiYHU_P?3e_lx8@JrK5QM$R4&`d9~$l^B!ef~t;DBIt$sD1xK}c5gtgveBRapif-) z1U1Sq1&$iCowqrYpw+-%FgEC;xd|EszOFD{9_M#vWN+(#@F_W++KwP)qaciW7fts2 zgUU2GV;wTyz}v2Ps_~G4$z55Xx$bK(^*=L&Tmu;DYkVV+iol#RGD6SCk-as(=(TJ-!~l(Y^U}ED zKzCy{J%f>9ad@6=)9Mei_cAqzZeBMD6QVBufx0$P%`@D1{>2;|HI_IV_97c8Y7OF zX~4GWmwz+ar29%xU&>0dK3uFWSZmv(GW?jc!ml$_dER(YacE4XFK&D0dj>^9lo&bS z*W~-AJD0aDB%vOSnfeg{eoDb01&}4J~ zzZsE-$ZQV67%n^*8G|9uFFeNjLc(aRQ#s&}j6gmPoX7In^8!ON0$qpP9DVpJC&C2Z z4L%C&yK0;r@o2`!#<{%{l#z@8%ym9HU@bU&^D_ay>Y^F>6Uh0^d{Hif%4eUHo{$^Z zgWml9aqd#xA7~0X_XYyCekeE)xXH3?= zhBpZ`ROMu8-VW5sWUSHk)v16@H22-w(l;L8awc=m2!rjXIy`Xf^3%ab?Y&SQ;~(^4-_fZ)mmCa7KSW+;XC_j1eFVjC9T^ z!wtZiyu)NpPVk)@1;w7;#d~13E1zdJ+s`m z{ih)Xa4=x_jeyLNfD9h6Q+UQypHs5Sfd;30+Bc<_GEpF47=xiJ<62e%sZpaW$+oC; z-wWuXlPRxNTp#IV&`t&$j(C->j||ET8u6}<^JZ5A_$t_KF&YpR4Y2O7j!5XR8934qs z9_iMCel~D%eh~a1XhL`7hyVMApURUgSZfJ*sPy*PE^lYeAvUZ9vb`8+Y-@9-ZVjsH zh%w~`esiQdPH1+5txEwt^NvE%3hUz-oz34+5{&LAP(=aR7REmb9vjC#}m_?6wmf81NUh1BDnzTD|e>D)-TfR?GNFWFVDBl*i&Kk72Cn3HBy+$iIwmD?q4qTn) zj>7HUVPezaSuBf(vInw}7DiqMyVA7=jJU_BuJ2f?L4(N+WD2w5?|9Xt^@Qo2TG zE2=Bp_5DEbS)f>_mcFq*IJI3K4z|@RD=jBy)m3>0R9V%n(FKAgSWx3V(=Yz<{7HDn zpX6FV5}VvWjB+Jjnzh)#Mte;J@_IJFhrxVCde-W5SFkHUv*DiPwVO2V9fY*#qX9vR z1O>HYcJ9fjs6K+VwCb{BY{v)X%ofk?15c&@5!GWV(@~V*weNal#PT zE_~Sv#2gLJ+|~%3Lu>ZTRl$0A(kYLH$W8@XU$mSlG1|la+xE`#{8N=H71V;%JfKw>1 z0e>2rMBvJZ@)na3xP;yv@ZE3x`q!C=OeKs27+D>mn<*5wAL>NrbnJ|FSlk&j72BAe z`n`j*Kl(f`C;$BD&C|a*zU{b9u`l1Q zIJosv&)qAGq6f;F9}qT(r=$LX%YoCdIi2ovIT?+7y1 z6kiCQ4>zhLAmw1f}N!p_+4w{z72SS^zwBdW;Hy zj`G7=K-Zisn3FX+!MpvTIp>(J0R}q~+#fVzJhGHw&-5q%cryLhN9G^C{jBGt*r*e0 zoSjpLZh?6Z#ID(mgX8Z4)&7>iUNS+w5m^5y5Utc+49Fh|(8vN_=UjAeJtSJ9+HHxF z(F64WeZk49~v!Uy_HoJd9k1Ob~r4&<84K{Zc^2Xni@@2v*0n z<4d;YU1)5z&&-8#WgM@KW5S&Oro%(&vw(4E5zl1apg5=@bB{r5xfs#p?L`)tHz*Bm z4Fb7q9EbmqM&NMK(T!06@A1G%la1gsu}ZiDRL>Iv{oa6#Dl`$VQ(W;IftLW}=?ZK$ zctK>1lG0GJ;W@AySt46x|Gz(*{q54-vmbqLY5tQ29p%D#EU-gv;}*{gFhvzl6KBhs zjBcaTSot$=9XyiUcc(^r<=lhOy?-#m5Ky)g4xu?LPyhPjWb=4t`vU5ymjb0@y6K^B z1Z0%lL*rtgWI_~*hBW>lVB`CN-X})~M9}oc@#!Hv2SZ+jFq%tmJ~bH^6Ig+Q5g61~Q( zYEK77P@x9{wvx~0aJXN{!o4q%%Yn|Y*9>V>iMm{@6+jS1@&z6TEr!vN0sUYtK(_gN zH(2wN=k9=rRDLyJj8m1+DCTjQ$ZP33$6tMd@J})@KLbnSW@h(7C6w>?6JMHdv@dVrG3NO;j*VfD}fg)s&*_` z4)|qjvFg_3*<$mqpPjhr@a*C1H|=`MAyqFdox+?BSsUlXR|778|b0{Q;}`m}NU{L?h=`liZ1bU7lNf#jRY%%fk*J zwnv4`2n9G8puy!pi>!@UFf<&7)zHt*zVO-r>;1sZ|J{HKRdimOi~P7f8JOQ1JQ2Ja zxUFsr$TntG1GYg(Y^sr?LB|ed7v}GHM!Bs{L4mE!n;#ia%tr#e(i9ebJrLyHagC;^ zK(;!cW-gJZm!|*u(YuDX7`GI&G7^>w<1Wrl-N#6;fq|QQgm4 z14PZCAgxf+C$3CY9sS2L5(xWFnB2-_=^EP(9^1H z+z3=IKIX40Ijmuh^?1(#dU*~GK9t5WXVwkIdOfJDtMK@^RvG+xC1!h3kB$?U~vmiEtf{>9R^GDJN2gz0ogAcyt`;vnkj;(_j9>eY2Clzcl}@cK8vtJrgWB9n+xPd`5s2Yh6fwxxDsH^ z*m*52u8)m!t#oz7=Hb{Rj3QDrUd{Ph!0bPHbLp1fQi)Gyzr3+D`B?kY8}c0I)|Z28 zfr7CGj*p{bPNoHS-&kJ?_>{wy;BW33ZV)ZYe54sem1UO8%Ke?JN?x^ndF3l1C)UPoWZR=psQHMABa^3` zLei*4xAWpNN${$)SRJ%o(_eiy8U8Jn-Q}S6?XYv%j&vb<60yENXpUmgk*zs8%Z*(J zSfitny)WR0ZfLIz->zX2tRc*{pj)-ADh;yM9v)l9We-dM&YO1j@iV&mbN!i%# z&*`9UjX=)pn!0Lesn+jKrvKTGZaz<{Rs_=BP#J-%ztL_xo9N`sjqgoQ{q1DBiriEZwh`$1F|YJ~-Jl5i5dBfxgC zHldxMn|_U$>|0CYYUpn)y&D#mkLOs zX`si(CxfB2EY<@GXM>SByy)v4hvv{2YgnDE7ip>wJ&AZbXTLml)8v4uxGT<#c*HjU zxA>tP6&uK5QE?2=p`~f6P0Pq6c`s^3V7(kv*IIN&9c(wrL+JBYRy9J&95|~H$3g1o zf;2xCFkG4PjT|*tqrk|S*Y4cU?5*k2o?H5e*gpH>@8)`VH)8Hj-^AIax@c&N=FBe# zcw7UT+R#JyszAP_388a8Mz1bfLcePuP&ghm?rpPQUi;?cSYX;sc|EysyM{eb>+n()}l7ll4~)eSsRnfS0_c$870IJqvjwf zG=Z!KmjVW^2a0D7l{N1JXpv6ehMo-+@_E0aY*iWZXsj_Kp)(6KRTYQ~UhwPw$zDF$ zMHXk(@cDMd9dVH*v<(76pD;q4Q!7+<+z5>-ShJOICv@$ zi2kf2{f4#{aLOjzbQ?6AdMG;cKguJxSZDJ-Pz1s_11_7{XLQS4>$A0>&VqWN!9`a42qPJ zEEp7&Yhz_xqvPXwIc2-91l1c$JlJCIbU?lpF(2L^mw_5GcZ8f2bEqM-R;D`iZp-_> zJ?7OX?`sGF0g>=#q}a$QG5Ty?cJI4|1dZd<8;?&8e3+SN4+m`YVA08k>gB!Ik2D5_ zBI2ic4AdPO+&=x~g?;DSd6z@s$}v)^jbJsfkF=cxPM|uH+U;#OrvKN++lIH^!1dh8 zXDjA=h2dL38+k0?esG(Sm@pe+*BqGx^o{QDrq`HvHS-@JDO?=7~y^rsA$_jdxCe@>t$C>hCVyasJVeZJj8H z|6m-cRJ_r2JITp_%J{xHTAoe6T49`lj*tpCP(a~l_LbCfystp z-0f#N+uzFUjew&(;vfFzWcGUx-*m_H&wqXME$j+%GJKcFbN7t&;vPFt{SA-wTLSXZ zFW68jR_#DRuGhoS%-OcdqI0T$r?0GBWRwNK7+7~~Z9S^ZhqN*Zz#P7=Pp$zj%CIRB zwA+`a&pvSLrg&6bjR8ySJv~(F0h0}{&9gHV6u zbR$Gt#5iHGHs=~Q!tf6(vy-3aPd0v(m+?<=OplIWE(e_2kmEt^uuHb~-+x0B(I zWNC;*YYF?q!0uS1_vM1ta7qpZ=Jy7!I5Lj~qHxYn1c!tD0U42K1>O$?EoatW{bvK^ zreSQ&%qd6%O>G%s6t6W_n(~?=+S9>tY(#ajy;pY?G~=HsBj=n?vS7}<}X&7onG zL&NM1P6a1|i>t`wpDSqWYZ z7?)k)L|hF-E@RcP1@{Cm22O^|$aiEzMO1ha`nc#K%Z&Ap;24v{+V+8DfK+ z=d0~;7f1wbrK~v?n3I67k0e8%T_brsHdJQ_FeCr&8{_GKok#VK1<1%AQ;-aU=8QJ* zbD68C9$E{mR|842!MOx9wbQ~ThvurxsensA-Wq~kgj#k5rGvf7`%FoV`PCtrDfywar`(VeU-Eo0udZt2^k%AJ+O0gByjZHIM;kT0yN+R zN@D;R=Js2GRgJ&D_{Eqvm9FrzWG%?t6jr zd?K*-cLthDv!sjXKQ=s>#Uvm=xFe-@tYA1Y!l?kCA^M!Dzm!`FGWg_92i9wW1eIxc zXDtc{1UOg;2%_6S2J9IcInEeVffoWgAbUE3Uk;p6Ydm@a1l7}kD)yAFd@0brdjgr= z71ZcwnN!{yP+kvq1U39h=GvS5%R!x##x5SZ@p8Z(eRh`anEj{kZAMFW=g0ZVxUNSsGCUUOB@N!W^GeR%zaG36TncLIOy-&sk6l}+{~W=gS#`HC z89p9hav%!TqdHfQKQ51R%!lF;CrYSgXSzGs6~7pdOXSTs&-%ttVN_Y(T!{^2D*J|GS7@beb-DUv!D16 zXg(#3T|MKgL2Y%b6o{NSj5eNZzwsdN=H;R?wb9cePP(Jb9Yf#Fv|~-`Te*B&)hI_cEYsZp~RU zx)JM%EZBOqrjMr8R)Uj(TG?{C_DNenp6r{0vc7QtLyc3^w{qqfY#1YTj3(vwo&X&8 z69JxHe=LxM=Z?|l^=u5klHx@1>&R;d!VGZ4Kx^nOJsb!^20d7(GN(^o0_b4P4&EXk zZLm50zpvl+vLjnb^^vAE4N~SH)q4Z21ddK$sI1I>;y2J0RmnQOk@>m6F=$sc7$)n+ zwwtSF{fuO_}WoXb{s{gNOf&5yd2mbdNBQ0@6Lb7MY#sD zFM|OJXdK)U$i#i{XmBz(GeTLF+*Ujm2pk{%Y=Cz>I5DmTfKDc-W&R!|$Fk5+YXLzD zju$7V0*z*mH4(bGv9jdtIk}Ahj|1a!;BHYxm10lNNM`sU7XoF@2pbtZ9SDq}Hv$UE zOI`+e9H2K4bbPwi7c@GXY?B53^T?(zL8GN)j_J4v=&gW}D}i!;JHU6@2%sZba}b;p z!?xhr5}5UBfKPrCgK^-vrRSh)eP}y*lcMUY-Do`kFtva|g(dk`Xu})a*G$V?dME0NQ%65^Vn(ULDt( z&ZVUiPLSL4wm?7Z4ldh&-Mn)oY|FSS(2$Orr=GLCBf#4eaEji`g+U#TUt0pgr`O_4#`K3lff% z*7(|-F+{$SxvcIE@RUqOE*EnKl~};#$?VqPdLSwK@X|QaL5Jb9A(UDM92AU9f;y=Y z$^u=?t~@lsbxqcNVXtL>D$tP20iVZ$;h&+E>ttY5ZVYRRr^mHs6C`El-<(16uLP=K zZ3`Sray=ocL!Yhc;E~&Gmm5XLumG-(>uV#;TIP6+nS1J83RVL&I5Um{*$8yJ#9UsI zYQSV;YjY?~l^Jc+jvM0w=sCWaxd4G2!yJ%(G9ZWkdQh8=v|s>D70k&!)E{ym%)*6W zUl=1ufI$X~XabZ0$L7ZabSFX}bjX{p1=V5G8c*<*J?#8@)HWh>N*p@N6lINOekdS| zZv;G`D*#(S?v+50<`Sf@O6CH9%>(&DY5zzNAw%dCY5*V4nsYpQMnQ0*D&?8iF*$l5 zPyvuj0P|1wWSAk$22^K+1w;W&)C`%IhL3hnus6`<_|?@%7Xr3-jCd{MdHerrCX||m zGwbYb3%neoX#PU*Vz4uC0U6QADs^?p>Z3aYd(Po)fnf0HdX*%=S2y$J)<6afJQ~P~ zV2wVXIi9`Kq{cS`G^e8u>2ib)F0jtY)Q+Uk1v31p(5_5?aZ&Cbq13Wu6yL}YxY5oB z&j$7xjf|0wtl2Sgws>Q93nD-N9#9!?52{c@YoT2Z+~qYyhCxnL!u+|Q0kG2mC4}Af5#K;Wa(j(Myl?Bu+(CXYRC=)%w@LHfJ7{5DsIiTkP>Q>Ozc&hZtI0{5?hd1-3 z$B)qQz$tLtiYn_7kvl0pet4BLGVCN6$#cSSZQ!A!8HXQ-IX09l+;xu=VRIQWWDcAF zy0LP2+ruReViLqT^;51pQD$JKPkylved=5OFvu6^}B^oj;O#foli zbjTRh*PL?JLNnKmF`{>LMb$1lTLF!7k)6NMx`R=JMp+GUj&yUGFhbS?l@r0f9W+>n zjs8m`t$~n}fHl}Aa<45-PygbsYv(fMqOOfAf!JG2GBt;O*T^Sp{rorzle;@-KVJFf z{FiP-E(JuC@R@))YrW_?Sq+{G(AEPqGH@n1ALv&jIhm5Ru055(1G3>iJqTHhgh80n z(<99YjJPm$?#bZjbmPPM9~sM92S`H;8eO@v7CviD$DAjYJOhLx6&}!p%~nIN96e7~ za^DK*opSz9j|AZ{%vkGjHgg6jdN>J;vNgH`YaG(ah_5Bxs2qJK(2{vS_rv>!-_&>} zUI|Dp;-0KELYv`W=4kCZ;i^73jiPo!!I~Fk+%t}vt+qx#^EYUxu*f9DiE+_@IU49T zdqK{eKBHu+TN*hKh%&g__&n*Qj#FWS;XrU-aBqMvW3)p7!^ZvNh)1qKr)#8BJx<+Y zc%@fo-)4w5#f>TY_k;D|!9c`lx=lvU1&lNsS&wBLg2NZ2?NEBMMly7~#_rrmcKC7U zxlHQJcsAfrD_awU^MTUa+tuLBKoD>-(524?c#?QN_$XKlB#Ea}`GW+!IT%GGKtTl< z0_(TOvC5BSP2SOH%hUA?Lvth?6#+dONaoZC{ctI;MTdgnL(J{7lG&4kfd;`ur(iC7 zb4{ZHu0b$YDfgR7DJ!6}*)?4>YyDtkb320^W7b(dL+bOT!<%AZyE@l1w}temcpTY@ ztARwxNsOFr$M5#n4mWf*g0{n3nb*B=*h$OH-~U~(;_|}5{BqD_ttGt@yf@BA<>B&a z!b6|V&RtuY{Lb1ahJ-CX=H( Date: Mon, 9 Nov 2020 15:22:06 +0100 Subject: [PATCH 207/266] Mark additional locales as broken --- src/CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4724bf431..b8ce69f1d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -677,8 +677,11 @@ endif(BUILD_SERVER) # Blacklisted locales that don't work. # see issue #4638 set(GETTEXT_BLACKLISTED_LOCALES + ar he ky + ms_Arab + th ) option(APPLY_LOCALE_BLACKLIST "Use a blacklist to avoid broken locales" TRUE) From fca4db4184ac054c53446c0960ec578ebc6b5857 Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Tue, 10 Nov 2020 21:02:53 +0100 Subject: [PATCH 208/266] ObjectRef: Re-add legacy code for set_physics_override (#10585) These arguments are used at least by MTG beds and homedecor_common. A deprecation warning is shown to safely remove it in a future release. --- src/script/lua_api/l_object.cpp | 42 ++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index a6a7c05f9..bc59bd55c 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -1413,20 +1413,34 @@ int ObjectRef::l_set_physics_override(lua_State *L) if (playersao == nullptr) return 0; - luaL_checktype(L, 2, LUA_TTABLE); - playersao->m_physics_override_speed = getfloatfield_default( - L, 2, "speed", playersao->m_physics_override_speed); - playersao->m_physics_override_jump = getfloatfield_default( - L, 2, "jump", playersao->m_physics_override_jump); - playersao->m_physics_override_gravity = getfloatfield_default( - L, 2, "gravity", playersao->m_physics_override_gravity); - playersao->m_physics_override_sneak = getboolfield_default( - L, 2, "sneak", playersao->m_physics_override_sneak); - playersao->m_physics_override_sneak_glitch = getboolfield_default( - L, 2, "sneak_glitch", playersao->m_physics_override_sneak_glitch); - playersao->m_physics_override_new_move = getboolfield_default( - L, 2, "new_move", playersao->m_physics_override_new_move); - playersao->m_physics_override_sent = false; + if (lua_istable(L, 2)) { + bool modified = false; + modified |= getfloatfield(L, 2, "speed", playersao->m_physics_override_speed); + modified |= getfloatfield(L, 2, "jump", playersao->m_physics_override_jump); + modified |= getfloatfield(L, 2, "gravity", playersao->m_physics_override_gravity); + modified |= getboolfield(L, 2, "sneak", playersao->m_physics_override_sneak); + modified |= getboolfield(L, 2, "sneak_glitch", playersao->m_physics_override_sneak_glitch); + modified |= getboolfield(L, 2, "new_move", playersao->m_physics_override_new_move); + if (modified) + playersao->m_physics_override_sent = false; + } else { + // old, non-table format + // TODO: Remove this code after version 5.4.0 + log_deprecated(L, "Deprecated use of set_physics_override(num, num, num)"); + + if (!lua_isnil(L, 2)) { + playersao->m_physics_override_speed = lua_tonumber(L, 2); + playersao->m_physics_override_sent = false; + } + if (!lua_isnil(L, 3)) { + playersao->m_physics_override_jump = lua_tonumber(L, 3); + playersao->m_physics_override_sent = false; + } + if (!lua_isnil(L, 4)) { + playersao->m_physics_override_gravity = lua_tonumber(L, 4); + playersao->m_physics_override_sent = false; + } + } return 0; } From be3fe161fc831c9e6da1357dc908ed4a7681c46c Mon Sep 17 00:00:00 2001 From: DS Date: Tue, 10 Nov 2020 21:03:10 +0100 Subject: [PATCH 209/266] Do not set a default for description in itemdef table (#10559) * Do not set a default for description in itemdef table * improve documentation --- builtin/game/register.lua | 4 +--- doc/lua_api.txt | 21 ++++++++++++++++----- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/builtin/game/register.lua b/builtin/game/register.lua index f00b76494..1f94a9dca 100644 --- a/builtin/game/register.lua +++ b/builtin/game/register.lua @@ -118,11 +118,9 @@ function core.register_item(name, itemdef) end itemdef.name = name - -- default description to item name - itemdef.description = itemdef.description or name -- default short_description to first line of description itemdef.short_description = itemdef.short_description or - itemdef.description:gsub("\n.*","") + (itemdef.description or ""):gsub("\n.*","") -- Apply defaults and add to registered_* table if itemdef.type == "node" then diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 985af2f6e..ef283f0c1 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -2023,10 +2023,10 @@ Item metadata only contains a key-value store. Some of the values in the key-value store are handled specially: -* `description`: Set the item stack's description. Defaults to - `idef.description`. -* `short_description`: Set the item stack's short description. Defaults - to `idef.short_description`. +* `description`: Set the item stack's description. + See also: `get_description` in [`ItemStack`] +* `short_description`: Set the item stack's short description. + See also: `get_short_description` in [`ItemStack`] * `color`: A `ColorString`, which sets the stack's color. * `palette_index`: If the item has a palette, this is used to get the current color from the palette. @@ -5983,8 +5983,18 @@ an itemstring, a table or `nil`. stack). * `set_metadata(metadata)`: (DEPRECATED) Returns true. * `get_description()`: returns the description shown in inventory list tooltips. + * The engine uses the same as this function for item descriptions. + * Fields for finding the description, in order: + * `description` in item metadata (See [Item Metadata].) + * `description` in item definition + * item name * `get_short_description()`: returns the short description. * Unlike the description, this does not include new lines. + * The engine uses the same as this function for short item descriptions. + * Fields for finding the short description, in order: + * `short_description` in item metadata (See [Item Metadata].) + * `short_description` in item definition + * first line of the description (See `get_description()`.) * `clear()`: removes all items from the stack, making it empty. * `replace(item)`: replace the contents of this stack. * `item` can also be an itemstring or table. @@ -7085,11 +7095,12 @@ Used by `minetest.register_node`, `minetest.register_craftitem`, and { description = "Steel Axe", -- Can contain new lines. "\n" has to be used as new line character. - -- Defaults to the item's name. + -- See also: `get_description` in [`ItemStack`] short_description = "Steel Axe", -- Must not contain new lines. -- Defaults to the first line of description. + -- See also: `get_short_description` in [`ItemStack`] groups = {}, -- key = name, value = rating; rating = 1..3. From be8d1d2d99ad835b5de7277b7b518c334113e795 Mon Sep 17 00:00:00 2001 From: Lars Date: Sun, 8 Nov 2020 12:30:38 -0800 Subject: [PATCH 210/266] Increase default emerge queue limits and limit enqueue requests for active blocks. --- builtin/settingtypes.txt | 6 +++--- src/defaultsettings.cpp | 6 +++--- src/emerge.cpp | 4 ++++ 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 8eb667bdd..c4dc82dfe 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -2152,15 +2152,15 @@ chunksize (Chunk size) int 5 enable_mapgen_debug_info (Mapgen debug) bool false # Maximum number of blocks that can be queued for loading. -emergequeue_limit_total (Absolute limit of queued blocks to emerge) int 512 +emergequeue_limit_total (Absolute limit of queued blocks to emerge) int 1024 # Maximum number of blocks to be queued that are to be loaded from file. # This limit is enforced per player. -emergequeue_limit_diskonly (Per-player limit of queued blocks load from disk) int 64 +emergequeue_limit_diskonly (Per-player limit of queued blocks load from disk) int 128 # Maximum number of blocks to be queued that are to be generated. # This limit is enforced per player. -emergequeue_limit_generate (Per-player limit of queued blocks to generate) int 64 +emergequeue_limit_generate (Per-player limit of queued blocks to generate) int 128 # Number of emerge threads to use. # Value 0: diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index dc0276733..177955589 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -396,9 +396,9 @@ void set_default_settings(Settings *settings) settings->setDefault("debug_log_level", "action"); settings->setDefault("debug_log_size_max", "50"); settings->setDefault("chat_log_level", "error"); - settings->setDefault("emergequeue_limit_total", "512"); - settings->setDefault("emergequeue_limit_diskonly", "64"); - settings->setDefault("emergequeue_limit_generate", "64"); + settings->setDefault("emergequeue_limit_total", "1024"); + settings->setDefault("emergequeue_limit_diskonly", "128"); + settings->setDefault("emergequeue_limit_generate", "128"); settings->setDefault("num_emerge_threads", "1"); settings->setDefault("secure.enable_security", "true"); settings->setDefault("secure.trusted_mods", ""); diff --git a/src/emerge.cpp b/src/emerge.cpp index 0ac26a682..12e407797 100644 --- a/src/emerge.cpp +++ b/src/emerge.cpp @@ -426,6 +426,10 @@ bool EmergeManager::pushBlockEmergeData( m_qlimit_generate : m_qlimit_diskonly; if (count_peer >= qlimit_peer) return false; + } else { + // limit block enqueue requests for active blocks to 1/2 of total + if (count_peer * 2 >= m_qlimit_total) + return false; } } From adffef2b94f2d9cb3104cbc75e315cda3c0728aa Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Thu, 12 Nov 2020 19:15:41 +0100 Subject: [PATCH 211/266] PlayerSAO: Run on_player_hpchange raw change values (#10478) The callback is only run when a change in HP is to be expected. Following cases will not trigger the callback: * Dead player damaged further * Healing full-health player * Change of 0 HP --- src/server/player_sao.cpp | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/server/player_sao.cpp b/src/server/player_sao.cpp index 9fb53380c..62515d1c9 100644 --- a/src/server/player_sao.cpp +++ b/src/server/player_sao.cpp @@ -458,20 +458,25 @@ u16 PlayerSAO::punch(v3f dir, void PlayerSAO::setHP(s32 hp, const PlayerHPChangeReason &reason) { - s32 oldhp = m_hp; + if (hp == (s32)m_hp) + return; // Nothing to do - hp = rangelim(hp, 0, m_prop.hp_max); + if (m_hp <= 0 && hp < (s32)m_hp) + return; // Cannot take more damage - if (oldhp != hp) { - s32 hp_change = m_env->getScriptIface()->on_player_hpchange(this, hp - oldhp, reason); + { + s32 hp_change = m_env->getScriptIface()->on_player_hpchange(this, hp - m_hp, reason); if (hp_change == 0) return; - hp = rangelim(oldhp + hp_change, 0, m_prop.hp_max); + hp = m_hp + hp_change; } + s32 oldhp = m_hp; + hp = rangelim(hp, 0, m_prop.hp_max); + if (hp < oldhp && isImmortal()) - return; + return; // Do not allow immortal players to be damaged m_hp = hp; From 68139a28eb8b43b3685b81c77258912ffc5e0b8f Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Thu, 12 Nov 2020 19:16:02 +0100 Subject: [PATCH 212/266] Revert "Replace MyEventReceiver KeyList with std::unordered_set" (#10622) This reverts commit 787561b29afdbc78769f68c2f5c4f2cff1b32340. --- src/client/inputhandler.cpp | 48 ++++++++-------- src/client/inputhandler.h | 108 +++++++++++++++++++++++++++++------- src/client/keycode.h | 19 ------- 3 files changed, 111 insertions(+), 64 deletions(-) diff --git a/src/client/inputhandler.cpp b/src/client/inputhandler.cpp index ee3e37ae9..608a405a8 100644 --- a/src/client/inputhandler.cpp +++ b/src/client/inputhandler.cpp @@ -112,7 +112,7 @@ bool MyEventReceiver::OnEvent(const SEvent &event) // Remember whether each key is down or up if (event.EventType == irr::EET_KEY_INPUT_EVENT) { const KeyPress &keyCode = event.KeyInput; - if (keysListenedFor.count(keyCode)) { + if (keysListenedFor[keyCode]) { // If the key is being held down then the OS may // send a continuous stream of keydown events. // In this case, we don't want to let this @@ -120,15 +120,15 @@ bool MyEventReceiver::OnEvent(const SEvent &event) // certain actions to repeat constantly. if (event.KeyInput.PressedDown) { if (!IsKeyDown(keyCode)) { - keyWasDown.insert(keyCode); - keyWasPressed.insert(keyCode); + keyWasDown.set(keyCode); + keyWasPressed.set(keyCode); } - keyIsDown.insert(keyCode); + keyIsDown.set(keyCode); } else { if (IsKeyDown(keyCode)) - keyWasReleased.insert(keyCode); + keyWasReleased.set(keyCode); - keyIsDown.erase(keyCode); + keyIsDown.unset(keyCode); } return true; @@ -153,36 +153,36 @@ bool MyEventReceiver::OnEvent(const SEvent &event) switch (event.MouseInput.Event) { case EMIE_LMOUSE_PRESSED_DOWN: key = "KEY_LBUTTON"; - keyIsDown.insert(key); - keyWasDown.insert(key); - keyWasPressed.insert(key); + keyIsDown.set(key); + keyWasDown.set(key); + keyWasPressed.set(key); break; case EMIE_MMOUSE_PRESSED_DOWN: key = "KEY_MBUTTON"; - keyIsDown.insert(key); - keyWasDown.insert(key); - keyWasPressed.insert(key); + keyIsDown.set(key); + keyWasDown.set(key); + keyWasPressed.set(key); break; case EMIE_RMOUSE_PRESSED_DOWN: key = "KEY_RBUTTON"; - keyIsDown.insert(key); - keyWasDown.insert(key); - keyWasPressed.insert(key); + keyIsDown.set(key); + keyWasDown.set(key); + keyWasPressed.set(key); break; case EMIE_LMOUSE_LEFT_UP: key = "KEY_LBUTTON"; - keyIsDown.erase(key); - keyWasReleased.insert(key); + keyIsDown.unset(key); + keyWasReleased.set(key); break; case EMIE_MMOUSE_LEFT_UP: key = "KEY_MBUTTON"; - keyIsDown.erase(key); - keyWasReleased.insert(key); + keyIsDown.unset(key); + keyWasReleased.set(key); break; case EMIE_RMOUSE_LEFT_UP: key = "KEY_RBUTTON"; - keyIsDown.erase(key); - keyWasReleased.insert(key); + keyIsDown.unset(key); + keyWasReleased.set(key); break; case EMIE_MOUSE_WHEEL: mouse_wheel += event.MouseInput.Wheel; @@ -235,11 +235,7 @@ void RandomInputHandler::step(float dtime) i.counter -= dtime; if (i.counter < 0.0) { i.counter = 0.1 * Rand(1, i.time_max); - KeyPress k = getKeySetting(i.key.c_str()); - if (keydown.count(k)) - keydown.erase(k); - else - keydown.insert(k); + keydown.toggle(getKeySetting(i.key.c_str())); } } { diff --git a/src/client/inputhandler.h b/src/client/inputhandler.h index 885f34e05..def147a82 100644 --- a/src/client/inputhandler.h +++ b/src/client/inputhandler.h @@ -21,9 +21,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irrlichttypes_extrabloated.h" #include "joystick_controller.h" +#include #include "keycode.h" #include "renderingengine.h" -#include #ifdef HAVE_TOUCHSCREENGUI #include "gui/touchscreengui.h" @@ -61,32 +61,98 @@ struct KeyCache InputHandler *handler; }; +class KeyList : private std::list +{ + typedef std::list super; + typedef super::iterator iterator; + typedef super::const_iterator const_iterator; + + virtual const_iterator find(const KeyPress &key) const + { + const_iterator f(begin()); + const_iterator e(end()); + + while (f != e) { + if (*f == key) + return f; + + ++f; + } + + return e; + } + + virtual iterator find(const KeyPress &key) + { + iterator f(begin()); + iterator e(end()); + + while (f != e) { + if (*f == key) + return f; + + ++f; + } + + return e; + } + +public: + void clear() { super::clear(); } + + void set(const KeyPress &key) + { + if (find(key) == end()) + push_back(key); + } + + void unset(const KeyPress &key) + { + iterator p(find(key)); + + if (p != end()) + erase(p); + } + + void toggle(const KeyPress &key) + { + iterator p(this->find(key)); + + if (p != end()) + erase(p); + else + push_back(key); + } + + bool operator[](const KeyPress &key) const { return find(key) != end(); } +}; + class MyEventReceiver : public IEventReceiver { public: // This is the one method that we have to implement virtual bool OnEvent(const SEvent &event); - bool IsKeyDown(const KeyPress &keyCode) const { return keyIsDown.count(keyCode); } + bool IsKeyDown(const KeyPress &keyCode) const { return keyIsDown[keyCode]; } // Checks whether a key was down and resets the state bool WasKeyDown(const KeyPress &keyCode) { - bool b = keyWasDown.count(keyCode); + bool b = keyWasDown[keyCode]; if (b) - keyWasDown.erase(keyCode); + keyWasDown.unset(keyCode); return b; } // Checks whether a key was just pressed. State will be cleared // in the subsequent iteration of Game::processPlayerInteraction - bool WasKeyPressed(const KeyPress &keycode) const { return keyWasPressed.count(keycode); } + bool WasKeyPressed(const KeyPress &keycode) const { return keyWasPressed[keycode]; } // Checks whether a key was just released. State will be cleared // in the subsequent iteration of Game::processPlayerInteraction - bool WasKeyReleased(const KeyPress &keycode) const { return keyWasReleased.count(keycode); } + bool WasKeyReleased(const KeyPress &keycode) const { return keyWasReleased[keycode]; } - void listenForKey(const KeyPress &keyCode) { keysListenedFor.insert(keyCode); } + void listenForKey(const KeyPress &keyCode) { keysListenedFor.set(keyCode); } void dontListenForKeys() { keysListenedFor.clear(); } s32 getMouseWheel() @@ -132,20 +198,24 @@ public: #endif private: - //! The current state of keys - std::unordered_set keyIsDown; + // The current state of keys + KeyList keyIsDown; - //! Whether a key was down - std::unordered_set keyWasDown; + // Whether a key was down + KeyList keyWasDown; - //! Whether a key has just been pressed - std::unordered_set keyWasPressed; + // Whether a key has just been pressed + KeyList keyWasPressed; - //! Whether a key has just been released - std::unordered_set keyWasReleased; + // Whether a key has just been released + KeyList keyWasReleased; - //! List of keys we listen for - std::unordered_set keysListenedFor; + // List of keys we listen for + // TODO perhaps the type of this is not really + // performant as KeyList is designed for few but + // often changing keys, and keysListenedFor is expected + // to change seldomly but contain lots of keys. + KeyList keysListenedFor; }; class InputHandler @@ -277,7 +347,7 @@ public: return true; } - virtual bool isKeyDown(GameKeyType k) { return keydown.count(keycache.key[k]); } + virtual bool isKeyDown(GameKeyType k) { return keydown[keycache.key[k]]; } virtual bool wasKeyDown(GameKeyType k) { return false; } virtual bool wasKeyPressed(GameKeyType k) { return false; } virtual bool wasKeyReleased(GameKeyType k) { return false; } @@ -292,7 +362,7 @@ public: s32 Rand(s32 min, s32 max); private: - std::unordered_set keydown; + KeyList keydown; v2s32 mousepos; v2s32 mousespeed; }; diff --git a/src/client/keycode.h b/src/client/keycode.h index 263b722c7..7036705d1 100644 --- a/src/client/keycode.h +++ b/src/client/keycode.h @@ -24,20 +24,12 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include -class KeyPress; -namespace std -{ - template <> struct hash; -} - /* A key press, consisting of either an Irrlicht keycode or an actual char */ class KeyPress { public: - friend struct std::hash; - KeyPress() = default; KeyPress(const char *name); @@ -63,17 +55,6 @@ protected: std::string m_name = ""; }; -namespace std -{ - template <> struct hash - { - size_t operator()(const KeyPress &key) const - { - return key.Key; - } - }; -} - extern const KeyPress EscapeKey; extern const KeyPress CancelKey; From 8eb2cbac613c92c1a7656ecb542e63fed9023061 Mon Sep 17 00:00:00 2001 From: red-001 Date: Thu, 12 Nov 2020 20:05:47 +0000 Subject: [PATCH 213/266] Fix warnings about an unused variables and implicit conversion (#10586) --- src/gui/guiScrollBar.cpp | 2 +- src/gui/guiScrollBar.h | 1 - src/mapgen/mg_ore.cpp | 6 +++++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/gui/guiScrollBar.cpp b/src/gui/guiScrollBar.cpp index b04ccb9d5..c6a03f3e4 100644 --- a/src/gui/guiScrollBar.cpp +++ b/src/gui/guiScrollBar.cpp @@ -21,7 +21,7 @@ GUIScrollBar::GUIScrollBar(IGUIEnvironment *environment, IGUIElement *parent, s3 is_horizontal(horizontal), is_auto_scaling(auto_scale), dragged_by_slider(false), tray_clicked(false), scroll_pos(0), draw_center(0), thumb_size(0), min_pos(0), max_pos(100), small_step(10), - large_step(50), last_change(0), drag_offset(0), page_size(100), border_size(0) + large_step(50), drag_offset(0), page_size(100), border_size(0) { refreshControls(); setNotClipped(false); diff --git a/src/gui/guiScrollBar.h b/src/gui/guiScrollBar.h index 29493bb99..d18f8e875 100644 --- a/src/gui/guiScrollBar.h +++ b/src/gui/guiScrollBar.h @@ -68,7 +68,6 @@ private: s32 max_pos; s32 small_step; s32 large_step; - u32 last_change; s32 drag_offset; s32 page_size; s32 border_size; diff --git a/src/mapgen/mg_ore.cpp b/src/mapgen/mg_ore.cpp index b50ed6a32..5814f433a 100644 --- a/src/mapgen/mg_ore.cpp +++ b/src/mapgen/mg_ore.cpp @@ -498,7 +498,11 @@ void OreVein::generate(MMVManip *vm, int mapseed, u32 blockseed, } // randval ranges from -1..1 - float randval = (float)pr.next() / (pr.RANDOM_RANGE / 2) - 1.f; + /* + Note: can generate values slightly larger than 1 + but this can't be changed as mapgen must be deterministic accross versions. + */ + float randval = (float)pr.next() / float(pr.RANDOM_RANGE / 2) - 1.f; float noiseval = contour(noise->result[index]); float noiseval2 = contour(noise2->result[index]); if (noiseval * noiseval2 + randval * random_factor < nthresh) From c441baa91b71c48a369178df287eeb91e15ea7d1 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Tue, 10 Nov 2020 21:22:02 +0100 Subject: [PATCH 214/266] Fix overloaded virtual warnings with get/setAttachment() --- src/client/content_cao.cpp | 47 +++++++++++++++++++------------------- src/client/content_cao.h | 12 ++++------ 2 files changed, 28 insertions(+), 31 deletions(-) diff --git a/src/client/content_cao.cpp b/src/client/content_cao.cpp index 7c349244f..c52bc62c5 100644 --- a/src/client/content_cao.cpp +++ b/src/client/content_cao.cpp @@ -460,18 +460,20 @@ void GenericCAO::setChildrenVisible(bool toset) GenericCAO *obj = m_env->getGenericCAO(cao_id); if (obj) { // Check if the entity is forced to appear in first person. - obj->setVisible(obj->isForcedVisible() ? true : toset); + obj->setVisible(obj->m_force_visible ? true : toset); } } } -void GenericCAO::setAttachment(int parent_id, const std::string &bone, v3f position, v3f rotation) +void GenericCAO::setAttachment(int parent_id, const std::string &bone, + v3f position, v3f rotation, bool force_visible) { int old_parent = m_attachment_parent_id; m_attachment_parent_id = parent_id; m_attachment_bone = bone; m_attachment_position = position; m_attachment_rotation = rotation; + m_force_visible = force_visible; ClientActiveObject *parent = m_env->getActiveObject(parent_id); @@ -482,15 +484,30 @@ void GenericCAO::setAttachment(int parent_id, const std::string &bone, v3f posit parent->addAttachmentChild(m_id); } updateAttachments(); + + // Forcibly show attachments if required by set_attach + if (m_force_visible) { + m_is_visible = true; + } else if (!m_is_local_player) { + // Objects attached to the local player should be hidden in first person + m_is_visible = !m_attached_to_local || + m_client->getCamera()->getCameraMode() != CAMERA_MODE_FIRST; + m_force_visible = false; + } else { + // Local players need to have this set, + // otherwise first person attachments fail. + m_is_visible = true; + } } void GenericCAO::getAttachment(int *parent_id, std::string *bone, v3f *position, - v3f *rotation) const + v3f *rotation, bool *force_visible) const { *parent_id = m_attachment_parent_id; *bone = m_attachment_bone; *position = m_attachment_position; *rotation = m_attachment_rotation; + *force_visible = m_force_visible; } void GenericCAO::clearChildAttachments() @@ -509,9 +526,9 @@ void GenericCAO::clearChildAttachments() void GenericCAO::clearParentAttachment() { if (m_attachment_parent_id) - setAttachment(0, "", m_attachment_position, m_attachment_rotation); + setAttachment(0, "", m_attachment_position, m_attachment_rotation, false); else - setAttachment(0, "", v3f(), v3f()); + setAttachment(0, "", v3f(), v3f(), false); } void GenericCAO::addAttachmentChild(int child_id) @@ -1781,25 +1798,9 @@ void GenericCAO::processMessage(const std::string &data) std::string bone = deSerializeString16(is); v3f position = readV3F32(is); v3f rotation = readV3F32(is); - m_force_visible = readU8(is); // Returns false for EOF + bool force_visible = readU8(is); // Returns false for EOF - setAttachment(parent_id, bone, position, rotation); - - // Forcibly show attachments if required by set_attach - if (m_force_visible) - m_is_visible = true; - // localplayer itself can't be attached to localplayer - else if (!m_is_local_player) { - // Objects attached to the local player should be hidden in first - // person provided the forced boolean isn't set. - m_is_visible = !m_attached_to_local || - m_client->getCamera()->getCameraMode() != CAMERA_MODE_FIRST; - m_force_visible = false; - } else { - // Local players need to have this set, - // otherwise first person attachments fail. - m_is_visible = true; - } + setAttachment(parent_id, bone, position, rotation, force_visible); } else if (cmd == AO_CMD_PUNCHED) { u16 result_hp = readU16(is); diff --git a/src/client/content_cao.h b/src/client/content_cao.h index 435fc2931..7c134fb48 100644 --- a/src/client/content_cao.h +++ b/src/client/content_cao.h @@ -111,6 +111,7 @@ private: v3f m_attachment_position; v3f m_attachment_rotation; bool m_attached_to_local = false; + bool m_force_visible = false; int m_anim_frame = 0; int m_anim_num_frames = 1; @@ -126,7 +127,6 @@ private: float m_step_distance_counter = 0.0f; u8 m_last_light = 255; bool m_is_visible = false; - bool m_force_visible = false; s8 m_glow = 0; // Material video::E_MATERIAL_TYPE m_material_type; @@ -218,15 +218,11 @@ public: m_is_visible = toset; } - inline bool isForcedVisible() const - { - return m_force_visible; - } - void setChildrenVisible(bool toset); - void setAttachment(int parent_id, const std::string &bone, v3f position, v3f rotation); + void setAttachment(int parent_id, const std::string &bone, v3f position, + v3f rotation, bool force_visible); void getAttachment(int *parent_id, std::string *bone, v3f *position, - v3f *rotation) const; + v3f *rotation, bool *force_visible) const; void clearChildAttachments(); void clearParentAttachment(); void addAttachmentChild(int child_id); From b504a1aa4bdc56676b4b1c398ebfe98d336f8f6e Mon Sep 17 00:00:00 2001 From: sfan5 Date: Tue, 10 Nov 2020 21:36:58 +0100 Subject: [PATCH 215/266] Fix player sprite visibility in first person closes #10525 --- src/client/content_cao.cpp | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/client/content_cao.cpp b/src/client/content_cao.cpp index c52bc62c5..c645900aa 100644 --- a/src/client/content_cao.cpp +++ b/src/client/content_cao.cpp @@ -1919,13 +1919,23 @@ void GenericCAO::updateMeshCulling() if (!m_is_local_player) return; - // Grab the active player scene node so we know there's - // at least a mesh to occlude from the camera. + const bool hidden = m_client->getCamera()->getCameraMode() == CAMERA_MODE_FIRST; + + if (m_meshnode && m_prop.visual == "upright_sprite") { + u32 buffers = m_meshnode->getMesh()->getMeshBufferCount(); + for (u32 i = 0; i < buffers; i++) { + video::SMaterial &mat = m_meshnode->getMesh()->getMeshBuffer(i)->getMaterial(); + // upright sprite has no backface culling + mat.setFlag(video::EMF_FRONT_FACE_CULLING, hidden); + } + return; + } + irr::scene::ISceneNode *node = getSceneNode(); if (!node) return; - if (m_client->getCamera()->getCameraMode() == CAMERA_MODE_FIRST) { + if (hidden) { // Hide the mesh by culling both front and // back faces. Serious hackyness but it works for our // purposes. This also preserves the skeletal armature. From 61bbdd6807f4c383b7300b4fd5931f5e09dc7205 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Tue, 10 Nov 2020 21:43:06 +0100 Subject: [PATCH 216/266] Copy position for can_dig fixes #10514 --- builtin/game/item.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/builtin/game/item.lua b/builtin/game/item.lua index f680ce0d4..109712b42 100644 --- a/builtin/game/item.lua +++ b/builtin/game/item.lua @@ -551,8 +551,9 @@ function core.node_dig(pos, node, digger) local diggername = user_name(digger) local log = make_log(diggername) local def = core.registered_nodes[node.name] + -- Copy pos because the callback could modify it if def and (not def.diggable or - (def.can_dig and not def.can_dig(pos, digger))) then + (def.can_dig and not def.can_dig(vector.new(pos), digger))) then log("info", diggername .. " tried to dig " .. node.name .. " which is not diggable " .. core.pos_to_string(pos)) From ad58ece18062d4c545432b45d71ce6dbe841746b Mon Sep 17 00:00:00 2001 From: sfan5 Date: Tue, 10 Nov 2020 21:46:30 +0100 Subject: [PATCH 217/266] serverpackethandler: Minor log message fixes closes #10434 --- src/network/serverpackethandler.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/network/serverpackethandler.cpp b/src/network/serverpackethandler.cpp index 95e7c548e..3db4eb286 100644 --- a/src/network/serverpackethandler.cpp +++ b/src/network/serverpackethandler.cpp @@ -1657,19 +1657,18 @@ void Server::handleCommand_SrpBytesM(NetworkPacket* pkt) bool wantSudo = (cstate == CS_Active); - verbosestream << "Server: Received TOCLIENT_SRP_BYTES_M." << std::endl; + verbosestream << "Server: Received TOSERVER_SRP_BYTES_M." << std::endl; if (!((cstate == CS_HelloSent) || (cstate == CS_Active))) { - actionstream << "Server: got SRP _M packet in wrong state " - << cstate << " from " << addr_s - << ". Ignoring." << std::endl; + warningstream << "Server: got SRP_M packet in wrong state " + << cstate << " from " << addr_s << ". Ignoring." << std::endl; return; } if (client->chosen_mech != AUTH_MECHANISM_SRP && client->chosen_mech != AUTH_MECHANISM_LEGACY_PASSWORD) { - actionstream << "Server: got SRP _M packet, while auth" - << "is going on with mech " << client->chosen_mech << " from " + warningstream << "Server: got SRP_M packet, while auth " + "is going on with mech " << client->chosen_mech << " from " << addr_s << " (wantSudo=" << wantSudo << "). Denying." << std::endl; if (wantSudo) { DenySudoAccess(peer_id); @@ -1717,7 +1716,7 @@ void Server::handleCommand_SrpBytesM(NetworkPacket* pkt) std::string checkpwd; // not used, but needed for passing something if (!m_script->getAuth(playername, &checkpwd, NULL)) { - actionstream << "Server: " << playername << + errorstream << "Server: " << playername << " cannot be authenticated (auth handler does not work?)" << std::endl; DenyAccess(peer_id, SERVER_ACCESSDENIED_SERVER_FAIL); From 1780adeea63630e67f4af8b300f6ae710d9af486 Mon Sep 17 00:00:00 2001 From: corarona Date: Sun, 15 Nov 2020 01:09:31 +0100 Subject: [PATCH 218/266] lua-api: fix get/set_pitch --- src/script/lua_api/l_localplayer.cpp | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/script/lua_api/l_localplayer.cpp b/src/script/lua_api/l_localplayer.cpp index 8cd5d01e4..9b089458d 100644 --- a/src/script/lua_api/l_localplayer.cpp +++ b/src/script/lua_api/l_localplayer.cpp @@ -75,10 +75,8 @@ int LuaLocalPlayer::l_set_velocity(lua_State *L) int LuaLocalPlayer::l_get_yaw(lua_State *L) { - LocalPlayer *player = getobject(L, 1); - - lua_pushinteger(L, player->getYaw()); - return 1; + lua_pushnumber(L, wrapDegrees_0_360(g_game->cam_view.camera_yaw)); + return 1; } int LuaLocalPlayer::l_set_yaw(lua_State *L) @@ -97,13 +95,10 @@ int LuaLocalPlayer::l_set_yaw(lua_State *L) int LuaLocalPlayer::l_get_pitch(lua_State *L) { - LocalPlayer *player = getobject(L, 1); - - lua_pushinteger(L, player->getPitch()); - return 1; + lua_pushnumber(L, -wrapDegrees_180(g_game->cam_view.camera_pitch) ); + return 1; } - int LuaLocalPlayer::l_set_pitch(lua_State *L) { LocalPlayer *player = getobject(L, 1); From ee1853e9bc740a521270e2e113bb4215246382c4 Mon Sep 17 00:00:00 2001 From: Wuzzy Date: Sun, 15 Nov 2020 17:37:09 +0100 Subject: [PATCH 219/266] Fix falling image of torchlike if paramtype2="none" (#10612) --- builtin/game/falling.lua | 9 ++++++++- games/devtest/mods/testnodes/drawtypes.lua | 21 ++++++++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/builtin/game/falling.lua b/builtin/game/falling.lua index 4bfcca9e7..8d044beaa 100644 --- a/builtin/game/falling.lua +++ b/builtin/game/falling.lua @@ -84,6 +84,9 @@ core.register_entity(":__builtin:falling_node", { local textures if def.tiles and def.tiles[1] then local tile = def.tiles[1] + if def.drawtype == "torchlike" and def.paramtype2 ~= "wallmounted" then + tile = def.tiles[2] or def.tiles[1] + end if type(tile) == "table" then tile = tile.name end @@ -144,7 +147,11 @@ core.register_entity(":__builtin:falling_node", { -- Rotate entity if def.drawtype == "torchlike" then - self.object:set_yaw(math.pi*0.25) + if def.paramtype2 == "wallmounted" then + self.object:set_yaw(math.pi*0.25) + else + self.object:set_yaw(-math.pi*0.25) + end elseif (node.param2 ~= 0 and (def.wield_image == "" or def.wield_image == nil)) or def.drawtype == "signlike" diff --git a/games/devtest/mods/testnodes/drawtypes.lua b/games/devtest/mods/testnodes/drawtypes.lua index 82d862819..b3ab2b322 100644 --- a/games/devtest/mods/testnodes/drawtypes.lua +++ b/games/devtest/mods/testnodes/drawtypes.lua @@ -145,6 +145,23 @@ minetest.register_node("testnodes:fencelike", { }) minetest.register_node("testnodes:torchlike", { + description = S("Torchlike Drawtype Test Node"), + drawtype = "torchlike", + paramtype = "light", + tiles = { + "testnodes_torchlike_floor.png", + "testnodes_torchlike_ceiling.png", + "testnodes_torchlike_wall.png", + }, + + + walkable = false, + sunlight_propagates = true, + groups = { dig_immediate = 3 }, + inventory_image = fallback_image("testnodes_torchlike_floor.png"), +}) + +minetest.register_node("testnodes:torchlike_wallmounted", { description = S("Wallmounted Torchlike Drawtype Test Node"), drawtype = "torchlike", paramtype = "light", @@ -162,6 +179,8 @@ minetest.register_node("testnodes:torchlike", { inventory_image = fallback_image("testnodes_torchlike_floor.png"), }) + + minetest.register_node("testnodes:signlike", { description = S("Wallmounted Signlike Drawtype Test Node"), drawtype = "signlike", @@ -526,7 +545,7 @@ scale("allfaces_optional_waving", scale("plantlike", S("Double-sized Plantlike Drawtype Test Node"), S("Half-sized Plantlike Drawtype Test Node")) -scale("torchlike", +scale("torchlike_wallmounted", S("Double-sized Wallmounted Torchlike Drawtype Test Node"), S("Half-sized Wallmounted Torchlike Drawtype Test Node")) scale("signlike", From a16e412c9de26beea427cb1f15e42db61c106b68 Mon Sep 17 00:00:00 2001 From: Lejo Date: Sun, 15 Nov 2020 17:38:04 +0100 Subject: [PATCH 220/266] Auto build client appimage (#10561) * Replace ubuntu:14.04 with ubuntu 18:04 * Auto build client appimage Co-authored-by: sfan5 --- .gitlab-ci.yml | 82 ++++++++++++++++++++++++++++----------------- AppImageBuilder.yml | 51 ++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+), 31 deletions(-) create mode 100644 AppImageBuilder.yml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d03b7b601..c3e120375 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -36,7 +36,7 @@ variables: - cp misc/debpkg-control build/deb/minetest/DEBIAN/control - cp -Rp artifact/minetest/usr build/deb/minetest/ script: - - git clone $MINETEST_GAME_REPO build/deb/minetest/usr/share/minetest/games/minetest + - git clone $MINETEST_GAME_REPO build/deb/minetest/usr/share/minetest/games/minetest_game - rm -Rf build/deb/minetest/usr/share/minetest/games/minetest/.git - sed -i 's/DATEPLACEHOLDER/'$(date +%y.%m.%d)'/g' build/deb/minetest/DEBIAN/control - sed -i 's/LEVELDB_PLACEHOLDER/'$LEVELDB_PKG'/g' build/deb/minetest/DEBIAN/control @@ -142,36 +142,6 @@ deploy:debian-10: ## Ubuntu ## -# Trusty - -build:ubuntu-14.04: - extends: .build_template - image: ubuntu:trusty - before_script: - - echo "deb http://ppa.launchpad.net/ubuntu-toolchain-r/test/ubuntu trusty main" > /etc/apt/sources.list.d/uptodate-toolchain.list - - apt-key adv --keyserver keyserver.ubuntu.com --recv BA9EF27F - - apt-get update -y - - apt-get -y install build-essential gcc-6 g++-6 libirrlicht-dev cmake libbz2-dev libpng-dev libjpeg-dev libxxf86vm-dev libgl1-mesa-dev libsqlite3-dev libogg-dev libvorbis-dev libopenal-dev libcurl4-gnutls-dev libfreetype6-dev zlib1g-dev libgmp-dev libjsoncpp-dev - variables: - CC: gcc-6 - CXX: g++-6 - -package:ubuntu-14.04: - extends: .debpkg_template - image: ubuntu:trusty - dependencies: - - build:ubuntu-14.04 - variables: - LEVELDB_PKG: libleveldb1 - -deploy:ubuntu-14.04: - extends: .debpkg_install - image: ubuntu:trusty - dependencies: - - package:ubuntu-14.04 - variables: - LEVELDB_PKG: libleveldb1 - # Xenial build:ubuntu-16.04: @@ -197,6 +167,31 @@ deploy:ubuntu-16.04: variables: LEVELDB_PKG: libleveldb1v5 +# Bionic + +build:ubuntu-18.04: + extends: .build_template + image: ubuntu:bionic + before_script: + - apt-get update -y + - apt-get -y install build-essential libirrlicht-dev cmake libbz2-dev libpng-dev libjpeg-dev libxxf86vm-dev libgl1-mesa-dev libsqlite3-dev libogg-dev libvorbis-dev libopenal-dev libcurl4-gnutls-dev libfreetype6-dev zlib1g-dev libgmp-dev libjsoncpp-dev + +package:ubuntu-18.04: + extends: .debpkg_template + image: ubuntu:bionic + dependencies: + - build:ubuntu-18.04 + variables: + LEVELDB_PKG: libleveldb1v5 + +deploy:ubuntu-18.04: + extends: .debpkg_install + image: ubuntu:bionic + dependencies: + - package:ubuntu-18.04 + variables: + LEVELDB_PKG: libleveldb1v5 + ## ## Fedora ## @@ -308,3 +303,28 @@ pages: only: - master +package:appimage-client: + stage: package + image: appimagecrafters/appimage-builder + dependencies: + - build:ubuntu-18.04 + before_script: + - apt-get update -y + - apt-get install -y git wget + # Collect files + - mkdir AppDir + - cp -a artifact/minetest/usr/ AppDir/usr/ + - rm AppDir/usr/bin/minetestserver + - cp -R clientmods AppDir/usr/share/minetest + script: + - git clone $MINETEST_GAME_REPO AppDir/usr/share/minetest/games/minetest_game + - rm -Rf AppDir/usr/share/minetest/games/minetest/.git + - export VERSION=$CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA + # Remove PrefersNonDefaultGPU property due to validation errors + - sed -i '/PrefersNonDefaultGPU/d' AppDir/usr/share/applications/net.minetest.minetest.desktop + - appimage-builder --skip-test + artifacts: + when: on_success + expire_in: 90 day + paths: + - ./*.AppImage diff --git a/AppImageBuilder.yml b/AppImageBuilder.yml new file mode 100644 index 000000000..9ecad5d8e --- /dev/null +++ b/AppImageBuilder.yml @@ -0,0 +1,51 @@ +version: 1 + +AppDir: + path: ./AppDir + + app_info: + id: minetest + name: Minetest + icon: minetest + version: !ENV ${VERSION} + exec: usr/bin/minetest + exec_args: $@ + runtime: + env: + APPDIR_LIBRARY_PATH: $APPDIR/usr/lib/x86_64-linux-gnu + + apt: + arch: amd64 + sources: + - sourceline: deb http://archive.ubuntu.com/ubuntu/ bionic main universe + key_url: 'http://keyserver.ubuntu.com/pks/lookup?op=get&search=0x3b4fe6acc0b21f32' + - sourceline: deb http://archive.ubuntu.com/ubuntu/ bionic-updates main universe + - sourceline: deb http://archive.ubuntu.com/ubuntu/ bionic-backports main universe + - sourceline: deb http://archive.ubuntu.com/ubuntu/ bionic-security main universe + + include: + - libirrlicht1.8 + - libxxf86vm1 + - libgl1-mesa-glx + - libsqlite3-0 + - libogg0 + - libvorbis0a + - libopenal1 + - libcurl3-gnutls + - libfreetype6 + - zlib1g + - libgmp10 + - libjsoncpp1 + + files: + exclude: + - usr/share/man + - usr/share/doc/*/README.* + - usr/share/doc/*/changelog.* + - usr/share/doc/*/NEWS.* + - usr/share/doc/*/TODO.* + +AppImage: + update-information: None + sign-key: None + arch: x86_64 From 2f6393f49d5ebf21abfaa7bff876b8c0cf4ca191 Mon Sep 17 00:00:00 2001 From: Lars Date: Thu, 12 Nov 2020 10:37:34 -0800 Subject: [PATCH 221/266] Increase limit for simultaneous blocks sent per client and the meshgen cache. --- builtin/settingtypes.txt | 4 ++-- src/defaultsettings.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index c4dc82dfe..dd4914201 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -747,7 +747,7 @@ mesh_generation_interval (Mapblock mesh generation delay) int 0 0 50 # Size of the MapBlock cache of the mesh generator. Increasing this will # increase the cache hit %, reducing the data being copied from the main # thread, thus reducing jitter. -meshgen_block_cache_size (Mapblock mesh generator's MapBlock cache size in MB) int 20 0 1000 +meshgen_block_cache_size (Mapblock mesh generator's MapBlock cache size in MB) int 40 0 1000 # Enables minimap. enable_minimap (Minimap) bool true @@ -1037,7 +1037,7 @@ ipv6_server (IPv6 server) bool false # Maximum number of blocks that are simultaneously sent per client. # The maximum total count is calculated dynamically: # max_total = ceil((#clients + max_users) * per_client / 4) -max_simultaneous_block_sends_per_client (Maximum simultaneous block sends per client) int 40 +max_simultaneous_block_sends_per_client (Maximum simultaneous block sends per client) int 128 # To reduce lag, block transfers are slowed down when a player is building something. # This determines how long they are slowed down after placing or removing a node. diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 177955589..42e7fc16b 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -42,7 +42,7 @@ void set_default_settings(Settings *settings) settings->setDefault("mute_sound", "false"); settings->setDefault("enable_mesh_cache", "false"); settings->setDefault("mesh_generation_interval", "0"); - settings->setDefault("meshgen_block_cache_size", "20"); + settings->setDefault("meshgen_block_cache_size", "40"); settings->setDefault("enable_vbo", "true"); settings->setDefault("free_move", "false"); settings->setDefault("pitch_move", "false"); @@ -343,7 +343,7 @@ void set_default_settings(Settings *settings) settings->setDefault("port", "30000"); settings->setDefault("strict_protocol_version_checking", "false"); settings->setDefault("player_transfer_distance", "0"); - settings->setDefault("max_simultaneous_block_sends_per_client", "40"); + settings->setDefault("max_simultaneous_block_sends_per_client", "128"); settings->setDefault("time_send_interval", "5"); settings->setDefault("default_game", "minetest"); From b65db98bd556bd77c9d6a63b1a661381abcdd17a Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein Date: Fri, 20 Nov 2020 12:04:41 +0100 Subject: [PATCH 222/266] Added OnlyTracePlayers --- builtin/client/cheats/init.lua | 1 + builtin/settingtypes.txt | 2 ++ src/client/render/core.cpp | 4 ++++ src/defaultsettings.cpp | 1 + 4 files changed, 8 insertions(+) diff --git a/builtin/client/cheats/init.lua b/builtin/client/cheats/init.lua index 6f3046f3c..2c67e0ebf 100644 --- a/builtin/client/cheats/init.lua +++ b/builtin/client/cheats/init.lua @@ -8,6 +8,7 @@ core.cheats = { ["CrystalPvP"] = "crystal_pvp", ["AutoTotem"] = "autototem", ["ThroughWalls"] = "dont_point_nodes", + ["OnlyTracePlayers"] = "only_trace_players", }, ["Movement"] = { ["Freecam"] = "freecam", diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index ce7d301dd..4006cc62b 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -2351,3 +2351,5 @@ enable_node_esp (NodeESP) bool false enable_node_tracers (NodeTracers) bool false node_esp_nodes (NodeESP Nodes) string + +only_trace_players (OnlyTracePlayers) bool false diff --git a/src/client/render/core.cpp b/src/client/render/core.cpp index cb4f1deb7..1fad4af52 100644 --- a/src/client/render/core.cpp +++ b/src/client/render/core.cpp @@ -80,6 +80,8 @@ void RenderingCore::draw(video::SColor _skycolor, bool _show_hud, bool _show_min void RenderingCore::drawTracersAndESP() { + bool only_trace_players = g_settings->getBool("only_trace_players"); + ClientEnvironment &env = client->getEnv(); Camera *camera = client->getCamera(); @@ -105,6 +107,8 @@ void RenderingCore::drawTracersAndESP() GenericCAO *obj = dynamic_cast(cao); if (! obj) continue; + if (only_trace_players && ! obj->isPlayer()) + continue; aabb3f box; if (! obj->getSelectionBox(&box)) continue; diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 4c3d311db..75e02f0c9 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -134,6 +134,7 @@ void set_default_settings(Settings *settings) settings->setDefault("enable_node_esp", "false"); settings->setDefault("enable_node_tracers", "false"); settings->setDefault("node_esp_nodes", ""); + settings->setDefault("only_trace_players", "false"); // Keymap settings->setDefault("remote_port", "30000"); From 872dce5020c5d05597a251c5ce63ebde256f2b64 Mon Sep 17 00:00:00 2001 From: Paramat Date: Fri, 20 Nov 2020 16:11:19 +0000 Subject: [PATCH 223/266] Move Mapgen V7 river generation into the main generation loop (#10639) All terrain generation now occurs in one loop, instead of rivers being carved afterwards in a separate loop. Fixes the removal of nodes added by mods in 'register on generated'. Avoids other problems and reduces the chance of future bugs. Mapchunk generation time is reduced. Also fixes a code mistake which resulted in river channel generation being disabled at floatland altitudes even when floatlands were disabled. --- src/mapgen/mapgen_v7.cpp | 79 ++++++++++++++++------------------------ src/mapgen/mapgen_v7.h | 2 +- 2 files changed, 32 insertions(+), 49 deletions(-) diff --git a/src/mapgen/mapgen_v7.cpp b/src/mapgen/mapgen_v7.cpp index a1fe25ab6..6b0779e9f 100644 --- a/src/mapgen/mapgen_v7.cpp +++ b/src/mapgen/mapgen_v7.cpp @@ -342,10 +342,6 @@ void MapgenV7::makeChunk(BlockMakeData *data) // Generate base and mountain terrain s16 stone_surface_max_y = generateTerrain(); - // Generate rivers - if (spflags & MGV7_RIDGES) - generateRidgeTerrain(); - // Create heightmap updateHeightmap(node_min, node_max); @@ -467,6 +463,23 @@ bool MapgenV7::getMountainTerrainFromMap(int idx_xyz, int idx_xz, s16 y) } +bool MapgenV7::getRiverChannelFromMap(int idx_xyz, int idx_xz, s16 y) +{ + // Maximum width of river channel. Creates the vertical canyon walls + float width = 0.2f; + float absuwatern = std::fabs(noise_ridge_uwater->result[idx_xz]) * 2.0f; + if (absuwatern > width) + return false; + + float altitude = y - water_level; + float height_mod = (altitude + 17.0f) / 2.5f; + float width_mod = width - absuwatern; + float nridge = noise_ridge->result[idx_xyz] * std::fmax(altitude, 0.0f) / 7.0f; + + return nridge + width_mod * height_mod >= 0.6f; +} + + bool MapgenV7::getFloatlandTerrainFromMap(int idx_xyz, float float_offset) { return noise_floatland->result[idx_xyz] + floatland_density - float_offset >= 0.0f; @@ -521,6 +534,15 @@ int MapgenV7::generateTerrain() } } + // 'Generate rivers in this mapchunk' bool for + // simplification of condition checks in y-loop. + bool gen_rivers = (spflags & MGV7_RIDGES) && node_max.Y >= water_level - 16 && + !gen_floatlands; + if (gen_rivers) { + noise_ridge->perlinMap3D(node_min.X, node_min.Y - 1, node_min.Z); + noise_ridge_uwater->perlinMap2D(node_min.X, node_min.Z); + } + //// Place nodes const v3s16 &em = vm->m_area.getExtent(); s16 stone_surface_max_y = -MAX_MAP_GENERATION_LIMIT; @@ -544,10 +566,13 @@ int MapgenV7::generateTerrain() if (vm->m_data[vi].getContent() != CONTENT_IGNORE) continue; - if (y <= surface_y) { + bool is_river_channel = gen_rivers && + getRiverChannelFromMap(index3d, index2d, y); + if (y <= surface_y && !is_river_channel) { vm->m_data[vi] = n_stone; // Base terrain } else if ((spflags & MGV7_MOUNTAINS) && - getMountainTerrainFromMap(index3d, index2d, y)) { + getMountainTerrainFromMap(index3d, index2d, y) && + !is_river_channel) { vm->m_data[vi] = n_stone; // Mountain terrain if (y > stone_surface_max_y) stone_surface_max_y = y; @@ -569,45 +594,3 @@ int MapgenV7::generateTerrain() return stone_surface_max_y; } - - -void MapgenV7::generateRidgeTerrain() -{ - if (node_max.Y < water_level - 16 || - (node_max.Y >= floatland_ymin && node_min.Y <= floatland_ymax)) - return; - - noise_ridge->perlinMap3D(node_min.X, node_min.Y - 1, node_min.Z); - noise_ridge_uwater->perlinMap2D(node_min.X, node_min.Z); - - MapNode n_water(c_water_source); - MapNode n_air(CONTENT_AIR); - u32 index3d = 0; - float width = 0.2f; - - for (s16 z = node_min.Z; z <= node_max.Z; z++) - for (s16 y = node_min.Y - 1; y <= node_max.Y + 1; y++) { - u32 vi = vm->m_area.index(node_min.X, y, z); - for (s16 x = node_min.X; x <= node_max.X; x++, index3d++, vi++) { - u32 index2d = (z - node_min.Z) * csize.X + (x - node_min.X); - float uwatern = noise_ridge_uwater->result[index2d] * 2.0f; - if (std::fabs(uwatern) > width) - continue; - // Optimises, but also avoids removing nodes placed by mods in - // 'on-generated', when generating outside mapchunk. - content_t c = vm->m_data[vi].getContent(); - if (c != c_stone) - continue; - - float altitude = y - water_level; - float height_mod = (altitude + 17.0f) / 2.5f; - float width_mod = width - std::fabs(uwatern); - float nridge = noise_ridge->result[index3d] * - std::fmax(altitude, 0.0f) / 7.0f; - if (nridge + width_mod * height_mod < 0.6f) - continue; - - vm->m_data[vi] = (y > water_level) ? n_air : n_water; - } - } -} diff --git a/src/mapgen/mapgen_v7.h b/src/mapgen/mapgen_v7.h index 4020cd935..5db10a304 100644 --- a/src/mapgen/mapgen_v7.h +++ b/src/mapgen/mapgen_v7.h @@ -94,10 +94,10 @@ public: float baseTerrainLevelFromMap(int index); bool getMountainTerrainAtPoint(s16 x, s16 y, s16 z); bool getMountainTerrainFromMap(int idx_xyz, int idx_xz, s16 y); + bool getRiverChannelFromMap(int idx_xyz, int idx_xz, s16 y); bool getFloatlandTerrainFromMap(int idx_xyz, float float_offset); int generateTerrain(); - void generateRidgeTerrain(); private: s16 mount_zero_level; From 43bc3a124541d014d7a2678d72bf3b54ff2d6e97 Mon Sep 17 00:00:00 2001 From: MoNTE48 Date: Sun, 22 Nov 2020 14:21:08 +0100 Subject: [PATCH 224/266] Fix Visual Studio build in Actions --- .github/workflows/build.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ae359f5d8..71fdf3652 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -221,8 +221,8 @@ jobs: name: VS 2019 ${{ matrix.config.arch }}-${{ matrix.type }} runs-on: windows-2019 env: - VCPKG_VERSION: c7ab9d3110813979a873b2dbac630a9ab79850dc -# 2020.04 + VCPKG_VERSION: 0bf3923f9fab4001c00f0f429682a0853b5749e0 +# 2020.11 vcpkg_packages: irrlicht zlib curl[winssl] openal-soft libvorbis libogg sqlite3 freetype luajit strategy: fail-fast: false @@ -248,7 +248,7 @@ jobs: uses: actions/checkout@v2 - name: Restore from cache and run vcpkg - uses: lukka/run-vcpkg@v2 + uses: lukka/run-vcpkg@v5 with: vcpkgArguments: ${{env.vcpkg_packages}} vcpkgDirectory: '${{ github.workspace }}\vcpkg' From 4dd5ecfc552819ad24557df75b441fab18c0c96a Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein Date: Mon, 23 Nov 2020 13:26:51 +0100 Subject: [PATCH 225/266] Added setpitch & setyaw commands; AutoSprint --- builtin/client/chatcommands.lua | 43 +++++++++++++++++++----------- builtin/client/cheats/init.lua | 1 + builtin/client/cheats/movement.lua | 27 ++++++++++--------- builtin/settingtypes.txt | 2 ++ src/defaultsettings.cpp | 1 + 5 files changed, 46 insertions(+), 28 deletions(-) diff --git a/builtin/client/chatcommands.lua b/builtin/client/chatcommands.lua index 8090b2bef..ec8fed652 100644 --- a/builtin/client/chatcommands.lua +++ b/builtin/client/chatcommands.lua @@ -134,21 +134,6 @@ core.register_chatcommand("set", { end, }) -core.register_chatcommand("findnodes", { - description = "Scan for one or multible nodes in a radius around you", - param = " [,...]", - func = function(param) - local radius = tonumber(param:split(" ")[1]) - local nodes = param:split(" ")[2]:split(",") - local pos = core.localplayer:get_pos() - local fpos = core.find_node_near(pos, radius, nodes, true) - if fpos then - return true, "Found " .. table.concat(nodes, " or ") .. " at " .. core.pos_to_string(fpos) - end - return false, "None of " .. table.concat(nodes, " or ") .. " found in a radius of " .. tostring(radius) - end, -}) - core.register_chatcommand("place", { params = ",,", description = "Place wielded item", @@ -174,3 +159,31 @@ core.register_chatcommand("dig", { return false, pos end, }) + +core.register_chatcommand("setyaw", { + params = "", + description = "Set your yaw", + func = function(param) + local yaw = tonumber(param) + if yaw then + core.localplayer:set_yaw(yaw) + return true + else + return false, "Invalid usage (See /help setyaw)" + end + end +}) + +core.register_chatcommand("setpitch", { + params = "", + description = "Set your pitch", + func = function(param) + local pitch = tonumber(param) + if pitch then + core.localplayer:set_pitch(pitch) + return true + else + return false, "Invalid usage (See /help setpitch)" + end + end +}) diff --git a/builtin/client/cheats/init.lua b/builtin/client/cheats/init.lua index 2c67e0ebf..2307c7aec 100644 --- a/builtin/client/cheats/init.lua +++ b/builtin/client/cheats/init.lua @@ -18,6 +18,7 @@ core.cheats = { ["Jesus"] = "jesus", ["NoSlow"] = "no_slow", ["AutoSneak"] = "autosneak", + ["AutoSprint"] = "autosprint", }, ["Render"] = { ["Xray"] = "xray", diff --git a/builtin/client/cheats/movement.lua b/builtin/client/cheats/movement.lua index bd9b995ad..9737662b2 100644 --- a/builtin/client/cheats/movement.lua +++ b/builtin/client/cheats/movement.lua @@ -1,14 +1,15 @@ --- autosneak +local function register_keypress_cheat(cheat, keyname) + local was_enabled = false + core.register_globalstep(function() + if core.settings:get_bool(cheat) then + was_enabled = true + core.set_keypress(keyname, true) + elseif was_enabled then + was_enabled = false + core.set_keypress(keyname, false) + end + end) +end -local autosneak_was_enabled = false - -core.register_globalstep(function() - if core.settings:get_bool("autosneak") then - core.set_keypress("sneak", true) - autosneak_was_enabled = true - elseif autosneak_was_enabled then - autosneak_was_enabled = false - core.set_keypress("sneak", false) - end -end) - +register_keypress_cheat("autosneak", "sneak") +register_keypress_cheat("autosprint", "special1") diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 4006cc62b..6e683c025 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -2353,3 +2353,5 @@ enable_node_tracers (NodeTracers) bool false node_esp_nodes (NodeESP Nodes) string only_trace_players (OnlyTracePlayers) bool false + +autosprint (AutoSprint) bool false diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 75e02f0c9..6775fdcd4 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -135,6 +135,7 @@ void set_default_settings(Settings *settings) settings->setDefault("enable_node_tracers", "false"); settings->setDefault("node_esp_nodes", ""); settings->setDefault("only_trace_players", "false"); + settings->setDefault("autosprint", "false"); // Keymap settings->setDefault("remote_port", "30000"); From 82216e1476dff509ba0c83bfabf5e4ec6e1075b2 Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein Date: Mon, 23 Nov 2020 19:10:56 +0100 Subject: [PATCH 226/266] LocalPlayer:set_physics_override; minetest.register_on_recieve_physics_override --- builtin/client/register.lua | 1 + doc/client_lua_api.txt | 15 ++++++++++ src/client/content_cao.cpp | 6 ++++ src/script/common/c_content.cpp | 23 +++++++++++++++ src/script/common/c_content.h | 2 ++ src/script/cpp_api/s_client.cpp | 16 +++++++++++ src/script/cpp_api/s_client.h | 1 + src/script/lua_api/l_localplayer.cpp | 43 ++++++++++++++++------------ src/script/lua_api/l_localplayer.h | 1 + 9 files changed, 89 insertions(+), 19 deletions(-) diff --git a/builtin/client/register.lua b/builtin/client/register.lua index 071220a43..c6374ab41 100644 --- a/builtin/client/register.lua +++ b/builtin/client/register.lua @@ -94,3 +94,4 @@ core.registered_on_item_use, core.register_on_item_use = make_registration() core.registered_on_modchannel_message, core.register_on_modchannel_message = make_registration() core.registered_on_modchannel_signal, core.register_on_modchannel_signal = make_registration() core.registered_on_inventory_open, core.register_on_inventory_open = make_registration() +core.registered_on_recieve_physics_override, core.register_on_recieve_physics_override = make_registration() diff --git a/doc/client_lua_api.txt b/doc/client_lua_api.txt index 2802ff6c1..dd7bb3808 100644 --- a/doc/client_lua_api.txt +++ b/doc/client_lua_api.txt @@ -746,6 +746,10 @@ Call these functions only at load time! * Called when the local player open inventory * Newest functions are called first * If any function returns true, inventory doesn't open +* `minetest.register_on_recieve_physics_override(function(override))` + * Called when recieving physics_override from server + * Newest functions are called first + * If any function returns true, the physics override does not change ### Setting-related * `minetest.settings`: Settings object containing all of the settings from the @@ -1223,6 +1227,17 @@ Methods: } ``` +* `set_physics_override(override_table)` + * `override_table` is a table with the following fields: + * `speed`: multiplier to default walking speed value (default: `1`) + * `jump`: multiplier to default jump value (default: `1`) + * `gravity`: multiplier to default gravity value (default: `1`) + * `sneak`: whether player can sneak (default: `true`) + * `sneak_glitch`: whether player can use the new move code replications + of the old sneak side-effects: sneak ladders and 2 node sneak jump + (default: `false`) + * `new_move`: use new move/sneak code. When `false` the exact old code + is used for the specific old sneak behaviour (default: `true`) * `get_override_pos()` * returns override position * `get_last_pos()` diff --git a/src/client/content_cao.cpp b/src/client/content_cao.cpp index cf671d5ca..aed576372 100644 --- a/src/client/content_cao.cpp +++ b/src/client/content_cao.cpp @@ -47,6 +47,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include #include "client/shader.h" +#include "script/scripting_client.h" class Settings; struct ToolCapabilities; @@ -1673,6 +1674,11 @@ void GenericCAO::processMessage(const std::string &data) if(m_is_local_player) { + Client *client = m_env->getGameDef(); + + if (client->modsLoaded() && client->getScript()->on_recieve_physics_override(override_speed, override_jump, override_gravity, sneak, sneak_glitch, new_move)) + return; + LocalPlayer *player = m_env->getLocalPlayer(); player->physics_override_speed = override_speed; player->physics_override_jump = override_jump; diff --git a/src/script/common/c_content.cpp b/src/script/common/c_content.cpp index 210363417..78c46f0e0 100644 --- a/src/script/common/c_content.cpp +++ b/src/script/common/c_content.cpp @@ -2096,3 +2096,26 @@ void push_collision_move_result(lua_State *L, const collisionMoveResult &res) lua_setfield(L, -2, "collisions"); /**/ } + +void push_physics_override(lua_State *L, float speed, float jump, float gravity, bool sneak, bool sneak_glitch, bool new_move) +{ + lua_createtable(L, 0, 6); + + lua_pushnumber(L, speed); + lua_setfield(L, -2, "speed"); + + lua_pushnumber(L, jump); + lua_setfield(L, -2, "jump"); + + lua_pushnumber(L, gravity); + lua_setfield(L, -2, "gravity"); + + lua_pushboolean(L, sneak); + lua_setfield(L, -2, "sneak"); + + lua_pushboolean(L, sneak_glitch); + lua_setfield(L, -2, "sneak_glitch"); + + lua_pushboolean(L, new_move); + lua_setfield(L, -2, "new_move"); +} diff --git a/src/script/common/c_content.h b/src/script/common/c_content.h index 5a8bf6700..eeef71c36 100644 --- a/src/script/common/c_content.h +++ b/src/script/common/c_content.h @@ -202,3 +202,5 @@ void push_hud_element (lua_State *L, HudElement *elem); HudElementStat read_hud_change (lua_State *L, HudElement *elem, void **value); void push_collision_move_result(lua_State *L, const collisionMoveResult &res); + +void push_physics_override (lua_State *L, float speed, float jump, float gravity, bool sneak, bool sneak_glitch, bool new_move); diff --git a/src/script/cpp_api/s_client.cpp b/src/script/cpp_api/s_client.cpp index dd9019d4d..981b08537 100644 --- a/src/script/cpp_api/s_client.cpp +++ b/src/script/cpp_api/s_client.cpp @@ -221,6 +221,22 @@ bool ScriptApiClient::on_item_use(const ItemStack &item, const PointedThing &poi return readParam(L, -1); } +bool ScriptApiClient::on_recieve_physics_override(float speed, float jump, float gravity, bool sneak, bool sneak_glitch, bool new_move) +{ + SCRIPTAPI_PRECHECKHEADER + + // Get core.registered_on_recieve_physics_override + lua_getglobal(L, "core"); + lua_getfield(L, -1, "registered_on_recieve_physics_override"); + + // Push data + push_physics_override(L, speed, jump, gravity, sneak, sneak_glitch, new_move); + + // Call functions + runCallbacks(1, RUN_CALLBACKS_MODE_OR); + return readParam(L, -1); +} + bool ScriptApiClient::on_inventory_open(Inventory *inventory) { SCRIPTAPI_PRECHECKHEADER diff --git a/src/script/cpp_api/s_client.h b/src/script/cpp_api/s_client.h index 177dce3ea..2ad3bcfad 100644 --- a/src/script/cpp_api/s_client.h +++ b/src/script/cpp_api/s_client.h @@ -57,6 +57,7 @@ public: bool on_punchnode(v3s16 p, MapNode node); bool on_placenode(const PointedThing &pointed, const ItemDefinition &item); bool on_item_use(const ItemStack &item, const PointedThing &pointed); + bool on_recieve_physics_override(float override_speed, float override_jump, float override_gravity, bool sneak, bool sneak_glitch, bool new_move); bool on_inventory_open(Inventory *inventory); void open_enderchest(); diff --git a/src/script/lua_api/l_localplayer.cpp b/src/script/lua_api/l_localplayer.cpp index 9b089458d..1cb6ff156 100644 --- a/src/script/lua_api/l_localplayer.cpp +++ b/src/script/lua_api/l_localplayer.cpp @@ -225,28 +225,32 @@ int LuaLocalPlayer::l_get_physics_override(lua_State *L) { LocalPlayer *player = getobject(L, 1); - lua_newtable(L); - lua_pushnumber(L, player->physics_override_speed); - lua_setfield(L, -2, "speed"); - - lua_pushnumber(L, player->physics_override_jump); - lua_setfield(L, -2, "jump"); - - lua_pushnumber(L, player->physics_override_gravity); - lua_setfield(L, -2, "gravity"); - - lua_pushboolean(L, player->physics_override_sneak); - lua_setfield(L, -2, "sneak"); - - lua_pushboolean(L, player->physics_override_sneak_glitch); - lua_setfield(L, -2, "sneak_glitch"); - - lua_pushboolean(L, player->physics_override_new_move); - lua_setfield(L, -2, "new_move"); - + push_physics_override(L, player->physics_override_speed, player->physics_override_jump, player->physics_override_gravity, player->physics_override_sneak, player->physics_override_sneak_glitch, player->physics_override_new_move); + return 1; } +// set_physics_override(self, override) +int LuaLocalPlayer::l_set_physics_override(lua_State *L) +{ + LocalPlayer *player = getobject(L, 1); + + player->physics_override_speed = getfloatfield_default( + L, 2, "speed", player->physics_override_speed); + player->physics_override_jump = getfloatfield_default( + L, 2, "jump", player->physics_override_jump); + player->physics_override_gravity = getfloatfield_default( + L, 2, "gravity", player->physics_override_gravity); + player->physics_override_sneak = getboolfield_default( + L, 2, "sneak", player->physics_override_sneak); + player->physics_override_sneak_glitch = getboolfield_default( + L, 2, "sneak_glitch", player->physics_override_sneak_glitch); + player->physics_override_new_move = getboolfield_default( + L, 2, "new_move", player->physics_override_new_move); + + return 0; +} + int LuaLocalPlayer::l_get_last_pos(lua_State *L) { LocalPlayer *player = getobject(L, 1); @@ -561,6 +565,7 @@ const luaL_Reg LuaLocalPlayer::methods[] = { luamethod(LuaLocalPlayer, is_climbing), luamethod(LuaLocalPlayer, swimming_vertical), luamethod(LuaLocalPlayer, get_physics_override), + luamethod(LuaLocalPlayer, set_physics_override), // TODO: figure our if these are useful in any way luamethod(LuaLocalPlayer, get_last_pos), luamethod(LuaLocalPlayer, get_last_velocity), diff --git a/src/script/lua_api/l_localplayer.h b/src/script/lua_api/l_localplayer.h index c436c3b11..33e23d178 100644 --- a/src/script/lua_api/l_localplayer.h +++ b/src/script/lua_api/l_localplayer.h @@ -74,6 +74,7 @@ private: static int l_swimming_vertical(lua_State *L); static int l_get_physics_override(lua_State *L); + static int l_set_physics_override(lua_State *L); static int l_get_override_pos(lua_State *L); From 78273027bf4bbee7488c76c6d65f85381ec7c0ba Mon Sep 17 00:00:00 2001 From: Pierre-Yves Rollo Date: Wed, 16 Sep 2020 17:10:17 +0200 Subject: [PATCH 227/266] Add sound to press event of some formspecs elements (#10402) --- doc/lua_api.txt | 5 +++++ src/client/game.cpp | 12 +++++------ src/gui/StyleSpec.h | 3 +++ src/gui/guiEngine.cpp | 1 + src/gui/guiFormSpecMenu.cpp | 40 ++++++++++++++++++++++++++++++++++--- src/gui/guiFormSpecMenu.h | 6 +++++- 6 files changed, 57 insertions(+), 10 deletions(-) diff --git a/doc/lua_api.txt b/doc/lua_api.txt index ef283f0c1..27f871618 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -2843,11 +2843,14 @@ Some types may inherit styles from parent types. * noclip - boolean, set to true to allow the element to exceed formspec bounds. * padding - rect, adds space between the edges of the button and the content. This value is relative to bgimg_middle. + * sound - a sound to be played when clicked. * textcolor - color, default white. * checkbox * noclip - boolean, set to true to allow the element to exceed formspec bounds. + * sound - a sound to be played when clicked. * dropdown * noclip - boolean, set to true to allow the element to exceed formspec bounds. + * sound - a sound to be played when clicked. * field, pwdfield, textarea * border - set to false to hide the textbox background and border. Default true. * font - Sets font type. See button `font` property for more information. @@ -2874,10 +2877,12 @@ Some types may inherit styles from parent types. * fgimg_pressed - image when pressed. Defaults to fgimg when not provided. * This is deprecated, use states instead. * NOTE: The parameters of any given image_button will take precedence over fgimg/fgimg_pressed + * sound - a sound to be played when clicked. * scrollbar * noclip - boolean, set to true to allow the element to exceed formspec bounds. * tabheader * noclip - boolean, set to true to allow the element to exceed formspec bounds. + * sound - a sound to be played when clicked. * textcolor - color. Default white. * table, textlist * font - Sets font type. See button `font` property for more information. diff --git a/src/client/game.cpp b/src/client/game.cpp index 5c38e027d..b7bb0a330 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -2071,7 +2071,7 @@ void Game::openInventory() TextDest *txt_dst = new TextDestPlayerInventory(client); auto *&formspec = m_game_ui->updateFormspec(""); GUIFormSpecMenu::create(formspec, client, &input->joystick, fs_src, - txt_dst, client->getFormspecPrepend()); + txt_dst, client->getFormspecPrepend(), sound); formspec->setFormSpec(fs_src->getForm(), inventoryloc); } @@ -2603,7 +2603,7 @@ void Game::handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation auto *&formspec = m_game_ui->updateFormspec(*(event->show_formspec.formname)); GUIFormSpecMenu::create(formspec, client, &input->joystick, - fs_src, txt_dst, client->getFormspecPrepend()); + fs_src, txt_dst, client->getFormspecPrepend(), sound); } delete event->show_formspec.formspec; @@ -2616,7 +2616,7 @@ void Game::handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrienta LocalFormspecHandler *txt_dst = new LocalFormspecHandler(*event->show_formspec.formname, client); GUIFormSpecMenu::create(m_game_ui->getFormspecGUI(), client, &input->joystick, - fs_src, txt_dst, client->getFormspecPrepend()); + fs_src, txt_dst, client->getFormspecPrepend(), sound); delete event->show_formspec.formspec; delete event->show_formspec.formname; @@ -3336,7 +3336,7 @@ bool Game::nodePlacement(const ItemDefinition &selected_def, auto *&formspec = m_game_ui->updateFormspec(""); GUIFormSpecMenu::create(formspec, client, &input->joystick, fs_src, - txt_dst, client->getFormspecPrepend()); + txt_dst, client->getFormspecPrepend(), sound); formspec->setFormSpec(meta->getString("formspec"), inventoryloc); return false; @@ -4108,7 +4108,7 @@ void Game::showDeathFormspec() auto *&formspec = m_game_ui->getFormspecGUI(); GUIFormSpecMenu::create(formspec, client, &input->joystick, - fs_src, txt_dst, client->getFormspecPrepend()); + fs_src, txt_dst, client->getFormspecPrepend(), sound); formspec->setFocus("btn_respawn"); } @@ -4242,7 +4242,7 @@ void Game::showPauseMenu() auto *&formspec = m_game_ui->getFormspecGUI(); GUIFormSpecMenu::create(formspec, client, &input->joystick, - fs_src, txt_dst, client->getFormspecPrepend()); + fs_src, txt_dst, client->getFormspecPrepend(), sound); formspec->setFocus("btn_continue"); formspec->doPause = true; } diff --git a/src/gui/StyleSpec.h b/src/gui/StyleSpec.h index 36ad51a89..f2844ce28 100644 --- a/src/gui/StyleSpec.h +++ b/src/gui/StyleSpec.h @@ -54,6 +54,7 @@ public: COLORS, BORDERCOLORS, BORDERWIDTHS, + SOUND, NUM_PROPERTIES, NONE }; @@ -116,6 +117,8 @@ public: return BORDERCOLORS; } else if (name == "borderwidths") { return BORDERWIDTHS; + } else if (name == "sound") { + return SOUND; } else { return NONE; } diff --git a/src/gui/guiEngine.cpp b/src/gui/guiEngine.cpp index 4a13f0b11..c5ad5c323 100644 --- a/src/gui/guiEngine.cpp +++ b/src/gui/guiEngine.cpp @@ -170,6 +170,7 @@ GUIEngine::GUIEngine(JoystickController *joystick, m_menumanager, NULL /* &client */, m_texture_source, + m_sound_manager, m_formspecgui, m_buttonhandler, "", diff --git a/src/gui/guiFormSpecMenu.cpp b/src/gui/guiFormSpecMenu.cpp index 039b28e79..632b15992 100644 --- a/src/gui/guiFormSpecMenu.cpp +++ b/src/gui/guiFormSpecMenu.cpp @@ -48,6 +48,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "settings.h" #include "client/client.h" #include "client/fontengine.h" +#include "client/sound.h" #include "util/hex.h" #include "util/numeric.h" #include "util/string.h" // for parseColorString() @@ -95,11 +96,13 @@ inline u32 clamp_u8(s32 value) GUIFormSpecMenu::GUIFormSpecMenu(JoystickController *joystick, gui::IGUIElement *parent, s32 id, IMenuManager *menumgr, - Client *client, ISimpleTextureSource *tsrc, IFormSource *fsrc, TextDest *tdst, + Client *client, ISimpleTextureSource *tsrc, ISoundManager *sound_manager, + IFormSource *fsrc, TextDest *tdst, const std::string &formspecPrepend, bool remap_dbl_click): GUIModalMenu(RenderingEngine::get_gui_env(), parent, id, menumgr, remap_dbl_click), m_invmgr(client), m_tsrc(tsrc), + m_sound_manager(sound_manager), m_client(client), m_formspec_prepend(formspecPrepend), m_form_src(fsrc), @@ -143,11 +146,12 @@ GUIFormSpecMenu::~GUIFormSpecMenu() void GUIFormSpecMenu::create(GUIFormSpecMenu *&cur_formspec, Client *client, JoystickController *joystick, IFormSource *fs_src, TextDest *txt_dest, - const std::string &formspecPrepend) + const std::string &formspecPrepend, ISoundManager *sound_manager) { if (cur_formspec == nullptr) { cur_formspec = new GUIFormSpecMenu(joystick, guiroot, -1, &g_menumgr, - client, client->getTextureSource(), fs_src, txt_dest, formspecPrepend); + client, client->getTextureSource(), sound_manager, fs_src, + txt_dest, formspecPrepend); cur_formspec->doPause = false; /* @@ -614,6 +618,9 @@ void GUIFormSpecMenu::parseCheckbox(parserData* data, const std::string &element data->current_parent, spec.fid, spec.flabel.c_str()); auto style = getDefaultStyleForElement("checkbox", name); + + spec.sound = style.get(StyleSpec::Property::SOUND, ""); + e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); if (spec.fname == m_focused_element) { @@ -1020,6 +1027,9 @@ void GUIFormSpecMenu::parseButton(parserData* data, const std::string &element, data->current_parent, spec.fid, spec.flabel.c_str()); auto style = getStyleForElement(type, name, (type != "button") ? "button" : ""); + + spec.sound = style[StyleSpec::STATE_DEFAULT].get(StyleSpec::Property::SOUND, ""); + e->setStyles(style); if (spec.fname == m_focused_element) { @@ -1381,6 +1391,9 @@ void GUIFormSpecMenu::parseDropDown(parserData* data, const std::string &element e->setSelected(stoi(str_initial_selection)-1); auto style = getDefaultStyleForElement("dropdown", name); + + spec.sound = style.get(StyleSpec::Property::SOUND, ""); + e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); m_fields.push_back(spec); @@ -1747,6 +1760,10 @@ void GUIFormSpecMenu::parseHyperText(parserData *data, const std::string &elemen ); spec.ftype = f_HyperText; + + auto style = getDefaultStyleForElement("hypertext", spec.fname); + spec.sound = style.get(StyleSpec::Property::SOUND, ""); + GUIHyperText *e = new GUIHyperText(spec.flabel.c_str(), Environment, data->current_parent, spec.fid, rect, m_client, m_tsrc); e->drop(); @@ -1999,6 +2016,8 @@ void GUIFormSpecMenu::parseImageButton(parserData* data, const std::string &elem auto style = getStyleForElement("image_button", spec.fname); + spec.sound = style[StyleSpec::STATE_DEFAULT].get(StyleSpec::Property::SOUND, ""); + // Override style properties with values specified directly in the element if (!image_name.empty()) style[StyleSpec::STATE_DEFAULT].set(StyleSpec::FGIMG, image_name); @@ -2107,6 +2126,9 @@ void GUIFormSpecMenu::parseTabHeader(parserData* data, const std::string &elemen e->setTabHeight(geom.Y); auto style = getDefaultStyleForElement("tabheader", name); + + spec.sound = style.get(StyleSpec::Property::SOUND, ""); + e->setNotClipped(style.getBool(StyleSpec::NOCLIP, true)); for (const std::string &button : buttons) { @@ -2195,6 +2217,9 @@ void GUIFormSpecMenu::parseItemImageButton(parserData* data, const std::string & item_name, m_client); auto style = getStyleForElement("item_image_button", spec_btn.fname, "image_button"); + + spec_btn.sound = style[StyleSpec::STATE_DEFAULT].get(StyleSpec::Property::SOUND, ""); + e_btn->setStyles(style); if (spec_btn.fname == m_focused_element) { @@ -4486,6 +4511,8 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) for (GUIFormSpecMenu::FieldSpec &s : m_fields) { if ((s.ftype == f_TabHeader) && (s.fid == event.GUIEvent.Caller->getID())) { + if (!s.sound.empty() && m_sound_manager) + m_sound_manager->playSound(s.sound, false, 1.0f); s.send = true; acceptInput(); s.send = false; @@ -4529,6 +4556,9 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) continue; if (s.ftype == f_Button || s.ftype == f_CheckBox) { + if (!s.sound.empty() && m_sound_manager) + m_sound_manager->playSound(s.sound, false, 1.0f); + s.send = true; if (s.is_exit) { if (m_allowclose) { @@ -4551,6 +4581,8 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) s2.send = false; } } + if (!s.sound.empty() && m_sound_manager) + m_sound_manager->playSound(s.sound, false, 1.0f); s.send = true; acceptInput(quit_mode_no); @@ -4567,6 +4599,8 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) acceptInput(quit_mode_no); s.fdefault = L""; } else if (s.ftype == f_Unknown || s.ftype == f_HyperText) { + if (!s.sound.empty() && m_sound_manager) + m_sound_manager->playSound(s.sound, false, 1.0f); s.send = true; acceptInput(); s.send = false; diff --git a/src/gui/guiFormSpecMenu.h b/src/gui/guiFormSpecMenu.h index c5d662a69..53076e3bd 100644 --- a/src/gui/guiFormSpecMenu.h +++ b/src/gui/guiFormSpecMenu.h @@ -39,6 +39,7 @@ class InventoryManager; class ISimpleTextureSource; class Client; class GUIScrollContainer; +class ISoundManager; typedef enum { f_Button, @@ -127,6 +128,7 @@ class GUIFormSpecMenu : public GUIModalMenu int priority; core::rect rect; gui::ECURSOR_ICON fcursor_icon; + std::string sound; }; struct TooltipSpec @@ -151,6 +153,7 @@ public: IMenuManager *menumgr, Client *client, ISimpleTextureSource *tsrc, + ISoundManager *sound_manager, IFormSource* fs_src, TextDest* txt_dst, const std::string &formspecPrepend, @@ -160,7 +163,7 @@ public: static void create(GUIFormSpecMenu *&cur_formspec, Client *client, JoystickController *joystick, IFormSource *fs_src, TextDest *txt_dest, - const std::string &formspecPrepend); + const std::string &formspecPrepend, ISoundManager *sound_manager); void setFormSpec(const std::string &formspec_string, const InventoryLocation ¤t_inventory_location) @@ -293,6 +296,7 @@ protected: InventoryManager *m_invmgr; ISimpleTextureSource *m_tsrc; + ISoundManager *m_sound_manager; Client *m_client; std::string m_formspec_string; From 7d327def8219f74d51127a739c9cdf8100b82c6d Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein Date: Tue, 24 Nov 2020 09:48:16 +0100 Subject: [PATCH 228/266] Improved AutoSneak --- builtin/client/cheats/movement.lua | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/builtin/client/cheats/movement.lua b/builtin/client/cheats/movement.lua index 9737662b2..907990cce 100644 --- a/builtin/client/cheats/movement.lua +++ b/builtin/client/cheats/movement.lua @@ -1,15 +1,18 @@ -local function register_keypress_cheat(cheat, keyname) +local function register_keypress_cheat(cheat, keyname, condition) local was_enabled = false core.register_globalstep(function() - if core.settings:get_bool(cheat) then - was_enabled = true + local is_active = core.settings:get_bool(cheat) + local condition_true = (not condition or condition()) + if is_active and condition_true then core.set_keypress(keyname, true) elseif was_enabled then - was_enabled = false core.set_keypress(keyname, false) end + was_enabled = is_active and condition_true end) end -register_keypress_cheat("autosneak", "sneak") +register_keypress_cheat("autosneak", "sneak", function() + return core.localplayer:is_touching_ground() +end) register_keypress_cheat("autosprint", "special1") From 598e9bdbce675289b00ad3cd544d74f7ad8659cc Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein Date: Tue, 24 Nov 2020 09:49:04 +0100 Subject: [PATCH 229/266] Update Credits --- builtin/mainmenu/tab_credits.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/builtin/mainmenu/tab_credits.lua b/builtin/mainmenu/tab_credits.lua index 274c8faa5..617823319 100644 --- a/builtin/mainmenu/tab_credits.lua +++ b/builtin/mainmenu/tab_credits.lua @@ -22,6 +22,7 @@ local dragonfire_team = { "emilia [Core Developer]", "oneplustwo [Developer]", "joshia_wi [Developer]", + "Code-Sploit [Developer]", "DerZombiiie [User Support]", } From 843239c0bab3dd55c7fb4f52556b5548d28b3c09 Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein Date: Tue, 24 Nov 2020 13:20:30 +0100 Subject: [PATCH 230/266] Added Speed/Jump/Gravity Override --- builtin/client/cheats/init.lua | 3 +++ builtin/client/cheats/movement.lua | 35 +++++++++++++++++++++++++----- builtin/settingtypes.txt | 12 ++++++++++ src/defaultsettings.cpp | 6 +++++ 4 files changed, 50 insertions(+), 6 deletions(-) diff --git a/builtin/client/cheats/init.lua b/builtin/client/cheats/init.lua index 2307c7aec..69dbac02a 100644 --- a/builtin/client/cheats/init.lua +++ b/builtin/client/cheats/init.lua @@ -19,6 +19,9 @@ core.cheats = { ["NoSlow"] = "no_slow", ["AutoSneak"] = "autosneak", ["AutoSprint"] = "autosprint", + ["SpeedOverride"] = "override_speed", + ["JumpOverride"] = "override_jump", + ["GravityOverride"] = "override_gravity", }, ["Render"] = { ["Xray"] = "xray", diff --git a/builtin/client/cheats/movement.lua b/builtin/client/cheats/movement.lua index 907990cce..33a46fca0 100644 --- a/builtin/client/cheats/movement.lua +++ b/builtin/client/cheats/movement.lua @@ -1,14 +1,13 @@ local function register_keypress_cheat(cheat, keyname, condition) - local was_enabled = false + local was_active = false core.register_globalstep(function() - local is_active = core.settings:get_bool(cheat) - local condition_true = (not condition or condition()) - if is_active and condition_true then + local is_active = core.settings:get_bool(cheat) and (not condition or condition()) + if is_active then core.set_keypress(keyname, true) - elseif was_enabled then + elseif was_active then core.set_keypress(keyname, false) end - was_enabled = is_active and condition_true + was_active = is_active end) end @@ -16,3 +15,27 @@ register_keypress_cheat("autosneak", "sneak", function() return core.localplayer:is_touching_ground() end) register_keypress_cheat("autosprint", "special1") + +local legit_override + +local function get_override_factor(name) + if core.settings:get_bool("override_" .. name) then + return tonumber(core.settings:get("override_" .. name .. "_factor")) or 1 + else + return 1.0 + end +end + +core.register_globalstep(function() + if not legit_override then return end + local override = table.copy(legit_override) + override.speed = override.speed * get_override_factor("speed") + override.jump = override.jump * get_override_factor("jump") + override.gravity = override.gravity * get_override_factor("gravity") + core.localplayer:set_physics_override(override) +end) + +core.register_on_recieve_physics_override(function(override) + legit_override = override + return true +end) diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 6e683c025..f91603ff4 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -2355,3 +2355,15 @@ node_esp_nodes (NodeESP Nodes) string only_trace_players (OnlyTracePlayers) bool false autosprint (AutoSprint) bool false + +override_speed (SpeedOverride) bool false + +override_jump (JumpOverride) bool false + +override_gravity (GravityOverride) bool false + +override_speed_factor (SpeedOverride Factor) float 1.2 + +override_jump_factor (JumpOverride Factor) float 2.0 + +override_gravity_factor (GravityOverride) float 0.8 diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 6775fdcd4..bb9c47dc8 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -136,6 +136,12 @@ void set_default_settings(Settings *settings) settings->setDefault("node_esp_nodes", ""); settings->setDefault("only_trace_players", "false"); settings->setDefault("autosprint", "false"); + settings->setDefault("override_speed", "false"); + settings->setDefault("override_jump", "false"); + settings->setDefault("override_gravity", "false"); + settings->setDefault("override_speed_factor", "1.2"); + settings->setDefault("override_jump_factor", "2.0"); + settings->setDefault("override_gravity_factor", "0.9"); // Keymap settings->setDefault("remote_port", "30000"); From f9c6324666939a349935411a4ad540768215bdaa Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein Date: Tue, 24 Nov 2020 13:38:11 +0100 Subject: [PATCH 231/266] Added JetPack and AutoHit (-> Credits to Code-Sploit and cora) --- builtin/client/cheats/init.lua | 2 ++ builtin/settingtypes.txt | 4 ++++ src/client/game.cpp | 2 +- src/client/localplayer.cpp | 4 ++-- src/defaultsettings.cpp | 2 ++ 5 files changed, 11 insertions(+), 3 deletions(-) diff --git a/builtin/client/cheats/init.lua b/builtin/client/cheats/init.lua index 69dbac02a..b8facae9d 100644 --- a/builtin/client/cheats/init.lua +++ b/builtin/client/cheats/init.lua @@ -9,6 +9,7 @@ core.cheats = { ["AutoTotem"] = "autototem", ["ThroughWalls"] = "dont_point_nodes", ["OnlyTracePlayers"] = "only_trace_players", + ["AutoHit"] = "autohit", }, ["Movement"] = { ["Freecam"] = "freecam", @@ -22,6 +23,7 @@ core.cheats = { ["SpeedOverride"] = "override_speed", ["JumpOverride"] = "override_jump", ["GravityOverride"] = "override_gravity", + ["JetPack"] = "jetpack", }, ["Render"] = { ["Xray"] = "xray", diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index f91603ff4..477b059c8 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -2367,3 +2367,7 @@ override_speed_factor (SpeedOverride Factor) float 1.2 override_jump_factor (JumpOverride Factor) float 2.0 override_gravity_factor (GravityOverride) float 0.8 + +jetpack (JetPack) bool false + +autohit (AutoHit) bool false diff --git a/src/client/game.cpp b/src/client/game.cpp index 93ec186c6..88607d1d8 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -2811,7 +2811,7 @@ void Game::handlePointingAtObject(const PointedThing &pointed, m_game_ui->setInfoText(infotext); - if (input->getLeftState()) { + if (input->getLeftState() || g_settings->getBool("autohit")) { bool do_punch = false; bool do_punch_damage = false; diff --git a/src/client/localplayer.cpp b/src/client/localplayer.cpp index 00195cd02..0da76845a 100644 --- a/src/client/localplayer.cpp +++ b/src/client/localplayer.cpp @@ -611,14 +611,14 @@ void LocalPlayer::applyControl(float dtime, Environment *env) else speedV.Y = movement_speed_walk; } - } else if (m_can_jump) { + } else if (m_can_jump || g_settings->getBool("jetpack")) { /* NOTE: The d value in move() affects jump height by raising the height at which the jump speed is kept at its starting value */ v3f speedJ = getSpeed(); - if (speedJ.Y >= -0.5f * BS) { + if (speedJ.Y >= -0.5f * BS || g_settings->getBool("jetpack")) { speedJ.Y = movement_speed_jump * physics_override_jump; setSpeed(speedJ); m_client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::PLAYER_JUMP)); diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index bb9c47dc8..8addb72eb 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -142,6 +142,8 @@ void set_default_settings(Settings *settings) settings->setDefault("override_speed_factor", "1.2"); settings->setDefault("override_jump_factor", "2.0"); settings->setDefault("override_gravity_factor", "0.9"); + settings->setDefault("jetpack", "false"); + settings->setDefault("autohit", "false"); // Keymap settings->setDefault("remote_port", "30000"); From 3d74e17cc24b2a3027d12c96940d92048c7bfe4e Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein Date: Tue, 24 Nov 2020 13:54:57 +0100 Subject: [PATCH 232/266] Added AutoSlip (-> Credit to Code-Sploit) --- builtin/client/cheats/init.lua | 1 + builtin/settingtypes.txt | 2 ++ src/client/localplayer.cpp | 2 +- src/defaultsettings.cpp | 1 + 4 files changed, 5 insertions(+), 1 deletion(-) diff --git a/builtin/client/cheats/init.lua b/builtin/client/cheats/init.lua index b8facae9d..4f4d33d86 100644 --- a/builtin/client/cheats/init.lua +++ b/builtin/client/cheats/init.lua @@ -24,6 +24,7 @@ core.cheats = { ["JumpOverride"] = "override_jump", ["GravityOverride"] = "override_gravity", ["JetPack"] = "jetpack", + ["AntiSlip"] = "antislip", }, ["Render"] = { ["Xray"] = "xray", diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 477b059c8..498e3f035 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -2371,3 +2371,5 @@ override_gravity_factor (GravityOverride) float 0.8 jetpack (JetPack) bool false autohit (AutoHit) bool false + +antislip (AntiSlip) bool false diff --git a/src/client/localplayer.cpp b/src/client/localplayer.cpp index 0da76845a..3b52b18e1 100644 --- a/src/client/localplayer.cpp +++ b/src/client/localplayer.cpp @@ -1102,7 +1102,7 @@ float LocalPlayer::getSlipFactor(Environment *env, const v3f &speedH) Map *map = &env->getMap(); const ContentFeatures &f = nodemgr->get(map->getNode(getStandingNodePos())); int slippery = 0; - if (f.walkable) + if (f.walkable && ! g_settings->getBool("antislip")) slippery = itemgroup_get(f.groups, "slippery"); if (slippery >= 1) { diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 8addb72eb..ec1b915ca 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -144,6 +144,7 @@ void set_default_settings(Settings *settings) settings->setDefault("override_gravity_factor", "0.9"); settings->setDefault("jetpack", "false"); settings->setDefault("autohit", "false"); + settings->setDefault("antislip", "false"); // Keymap settings->setDefault("remote_port", "30000"); From 50629cc6a1830783580811cedec1fd8ab559f40b Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein Date: Tue, 24 Nov 2020 14:24:32 +0100 Subject: [PATCH 233/266] Improved Scaffold --- builtin/client/cheats/world.lua | 6 +++++- src/defaultsettings.cpp | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/builtin/client/cheats/world.lua b/builtin/client/cheats/world.lua index 6cbdd67fc..1a86e0703 100644 --- a/builtin/client/cheats/world.lua +++ b/builtin/client/cheats/world.lua @@ -17,7 +17,11 @@ core.register_globalstep(function(dtime) local nodes_per_tick = tonumber(minetest.settings:get("nodes_per_tick")) or 8 if item and item:get_count() > 0 and def and def.node_placement_prediction ~= "" then if core.settings:get_bool("scaffold") then - core.place_node(vector.add(pos, {x = 0, y = -0.6, z = 0})) + local p = vector.round(vector.add(pos, {x = 0, y = -0.6, z = 0})) + local node = minetest.get_node_or_nil(p) + if not node or minetest.get_node_def(node.name).buildable_to then + core.place_node(p) + end elseif core.settings:get_bool("scaffold_plus") then local z = pos.z local positions = { diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index ec1b915ca..bfcee01f8 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -53,7 +53,7 @@ void set_default_settings(Settings *settings) settings->setDefault("screenshot_quality", "0"); settings->setDefault("client_unload_unused_data_timeout", "600"); settings->setDefault("client_mapblock_limit", "5000"); - settings->setDefault("enable_build_where_you_stand", "false"); + settings->setDefault("enable_build_where_you_stand", "true"); settings->setDefault("curl_timeout", "5000"); settings->setDefault("curl_parallel_limit", "8"); settings->setDefault("curl_file_download_timeout", "300000"); From fb4d54ee30ee9b8394bac18418b88adb0f9cd953 Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein Date: Tue, 24 Nov 2020 15:01:52 +0100 Subject: [PATCH 234/266] Added minetest.register_on_play_sound --- builtin/client/register.lua | 1 + doc/client_lua_api.txt | 4 ++++ src/network/clientpackethandler.cpp | 7 ++++++- src/script/cpp_api/s_client.cpp | 16 ++++++++++++++++ src/script/cpp_api/s_client.h | 1 + 5 files changed, 28 insertions(+), 1 deletion(-) diff --git a/builtin/client/register.lua b/builtin/client/register.lua index c6374ab41..ef48f2ee6 100644 --- a/builtin/client/register.lua +++ b/builtin/client/register.lua @@ -95,3 +95,4 @@ core.registered_on_modchannel_message, core.register_on_modchannel_message = mak core.registered_on_modchannel_signal, core.register_on_modchannel_signal = make_registration() core.registered_on_inventory_open, core.register_on_inventory_open = make_registration() core.registered_on_recieve_physics_override, core.register_on_recieve_physics_override = make_registration() +core.registered_on_play_sound, core.register_on_play_sound = make_registration() diff --git a/doc/client_lua_api.txt b/doc/client_lua_api.txt index dd7bb3808..ea6f4e13f 100644 --- a/doc/client_lua_api.txt +++ b/doc/client_lua_api.txt @@ -750,6 +750,10 @@ Call these functions only at load time! * Called when recieving physics_override from server * Newest functions are called first * If any function returns true, the physics override does not change +* `minetest.register_on_sound_play(function(SimpleSoundSpec))` + * Called when recieving a play sound command from server + * Newest functions are called first + * If any function returns true, the sound does not play ### Setting-related * `minetest.settings`: Settings object containing all of the settings from the diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index f0fb09fad..a3f1e668d 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -833,7 +833,12 @@ void Client::handleCommand_PlaySound(NetworkPacket* pkt) *pkt >> pitch; *pkt >> ephemeral; } catch (PacketError &e) {}; - + + SimpleSoundSpec sound_spec(name, gain, fade, pitch); + + if (m_mods_loaded && m_script->on_play_sound(sound_spec)) + return; + // Start playing int client_id = -1; switch(type) { diff --git a/src/script/cpp_api/s_client.cpp b/src/script/cpp_api/s_client.cpp index 981b08537..cf7df5b5d 100644 --- a/src/script/cpp_api/s_client.cpp +++ b/src/script/cpp_api/s_client.cpp @@ -237,6 +237,22 @@ bool ScriptApiClient::on_recieve_physics_override(float speed, float jump, float return readParam(L, -1); } +bool ScriptApiClient::on_play_sound(SimpleSoundSpec spec) +{ + SCRIPTAPI_PRECHECKHEADER + + // Get core.registered_on_play_sound + lua_getglobal(L, "core"); + lua_getfield(L, -1, "registered_on_play_sound"); + + // Push data + push_soundspec(L, spec); + + // Call functions + runCallbacks(1, RUN_CALLBACKS_MODE_OR); + return readParam(L, -1); +} + bool ScriptApiClient::on_inventory_open(Inventory *inventory) { SCRIPTAPI_PRECHECKHEADER diff --git a/src/script/cpp_api/s_client.h b/src/script/cpp_api/s_client.h index 2ad3bcfad..8db253d56 100644 --- a/src/script/cpp_api/s_client.h +++ b/src/script/cpp_api/s_client.h @@ -58,6 +58,7 @@ public: bool on_placenode(const PointedThing &pointed, const ItemDefinition &item); bool on_item_use(const ItemStack &item, const PointedThing &pointed); bool on_recieve_physics_override(float override_speed, float override_jump, float override_gravity, bool sneak, bool sneak_glitch, bool new_move); + bool on_play_sound(SimpleSoundSpec spec); bool on_inventory_open(Inventory *inventory); void open_enderchest(); From 8dc70ebb936277f740dffe9a11b5ee0fecc9de8e Mon Sep 17 00:00:00 2001 From: Lars Date: Tue, 24 Nov 2020 09:23:18 -0800 Subject: [PATCH 235/266] Fix camera panning glitches (partially revert 10489.) --- src/client/clientmap.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/client/clientmap.cpp b/src/client/clientmap.cpp index c8561def6..09072858a 100644 --- a/src/client/clientmap.cpp +++ b/src/client/clientmap.cpp @@ -124,7 +124,10 @@ void ClientMap::updateDrawList() const v3f camera_position = m_camera_position; const v3f camera_direction = m_camera_direction; - const f32 camera_fov = m_camera_fov; + + // Use a higher fov to accomodate faster camera movements. + // Blocks are cropped better when they are drawn. + const f32 camera_fov = m_camera_fov * 1.1f; v3s16 cam_pos_nodes = floatToInt(camera_position, BS); v3s16 p_blocks_min; From eaec3645b08e517e709b15089ad09d4978d69297 Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein Date: Thu, 26 Nov 2020 18:44:29 +0100 Subject: [PATCH 236/266] Added ClientObjectRef:get_hp() --- src/client/content_cao.h | 5 +++++ src/script/lua_api/l_clientobject.cpp | 9 +++++++++ src/script/lua_api/l_clientobject.h | 3 +++ 3 files changed, 17 insertions(+) diff --git a/src/client/content_cao.h b/src/client/content_cao.h index 56ba8e0ec..75882a074 100644 --- a/src/client/content_cao.h +++ b/src/client/content_cao.h @@ -178,6 +178,11 @@ public: { return m_velocity; } + + inline const u16 getHp() const + { + return m_hp; + } const bool isImmortal(); diff --git a/src/script/lua_api/l_clientobject.cpp b/src/script/lua_api/l_clientobject.cpp index 05b3e2850..70151ba40 100644 --- a/src/script/lua_api/l_clientobject.cpp +++ b/src/script/lua_api/l_clientobject.cpp @@ -133,6 +133,14 @@ int ClientObjectRef::l_get_item_textures(lua_State *L) return 1; } +int ClientObjectRef::l_get_hp(lua_State *L) +{ + ClientObjectRef *ref = checkobject(L, 1); + GenericCAO *gcao = get_generic_cao(ref, L); + lua_pushnumber(L, gcao->getHp()); + return 1; +} + int ClientObjectRef::l_get_max_hp(lua_State *L) { ClientObjectRef *ref = checkobject(L, 1); @@ -223,5 +231,6 @@ luaL_Reg ClientObjectRef::methods[] = {luamethod(ClientObjectRef, get_pos), luamethod(ClientObjectRef, get_attach), luamethod(ClientObjectRef, get_nametag), luamethod(ClientObjectRef, get_item_textures), + luamethod(ClientObjectRef, get_hp), luamethod(ClientObjectRef, get_max_hp), luamethod(ClientObjectRef, punch), luamethod(ClientObjectRef, rightclick), {0, 0}}; diff --git a/src/script/lua_api/l_clientobject.h b/src/script/lua_api/l_clientobject.h index a4516e047..d622dc3b2 100644 --- a/src/script/lua_api/l_clientobject.h +++ b/src/script/lua_api/l_clientobject.h @@ -77,6 +77,9 @@ private: static int l_get_item_textures(lua_State *L); // get_hp(self) + static int l_get_hp(lua_State *L); + + // get_max_hp(self) static int l_get_max_hp(lua_State *L); // punch(self) From a65251a7a88e51f2d0039d0a4c032f9090a13025 Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein Date: Thu, 26 Nov 2020 18:46:34 +0100 Subject: [PATCH 237/266] Fixed glowing GenericCAOs being rendered completely back when Fullbright is enabled --- src/client/content_cao.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/client/content_cao.cpp b/src/client/content_cao.cpp index aed576372..e1cebf461 100644 --- a/src/client/content_cao.cpp +++ b/src/client/content_cao.cpp @@ -811,9 +811,6 @@ void GenericCAO::updateLight(u32 day_night_ratio) u8 light_at_pos = 0; bool pos_ok = false; - - if (g_settings->getBool("fullbright")) - light_at_pos = 255; v3s16 pos[3]; u16 npos = getLightPosition(pos); @@ -830,6 +827,8 @@ void GenericCAO::updateLight(u32 day_night_ratio) light_at_pos = blend_light(day_night_ratio, LIGHT_SUN, 0); u8 light = decode_light(light_at_pos + m_glow); + if (g_settings->getBool("fullbright")) + light = 255; if (light != m_last_light) { m_last_light = light; setNodeLight(light); From f1349be5422ab11b488309bee89fdcf868e860bb Mon Sep 17 00:00:00 2001 From: Lars Date: Wed, 25 Nov 2020 17:16:41 -0800 Subject: [PATCH 238/266] Avoid marking map blocks dirty upon deserialization. --- src/mapblock.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/mapblock.cpp b/src/mapblock.cpp index 5fb6b221b..8bfecd755 100644 --- a/src/mapblock.cpp +++ b/src/mapblock.cpp @@ -533,7 +533,7 @@ void MapBlock::deSerialize(std::istream &is, u8 version, bool disk) // Timestamp TRACESTREAM(<<"MapBlock::deSerialize "<= 17) { - setTimestamp(readU32(is)); + setTimestampNoChangedFlag(readU32(is)); m_disk_timestamp = m_timestamp; } else { - setTimestamp(BLOCK_TIMESTAMP_UNDEFINED); + setTimestampNoChangedFlag(BLOCK_TIMESTAMP_UNDEFINED); } // Dynamically re-set ids based on node names From 095f82692d3d1b8b190178d640f0a166d1851f60 Mon Sep 17 00:00:00 2001 From: numzero Date: Sun, 22 Nov 2020 09:13:16 +0300 Subject: [PATCH 239/266] Batch cloud drawing --- src/client/clouds.cpp | 47 +++++++++++++++++++++---------------------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/src/client/clouds.cpp b/src/client/clouds.cpp index 887a62f25..ccc94cc88 100644 --- a/src/client/clouds.cpp +++ b/src/client/clouds.cpp @@ -170,8 +170,9 @@ void Clouds::render() // Read noise - bool *grid = new bool[m_cloud_radius_i * 2 * m_cloud_radius_i * 2]; - + std::vector grid(m_cloud_radius_i * 2 * m_cloud_radius_i * 2); // vector is broken + std::vector vertices; + vertices.reserve(16 * m_cloud_radius_i * m_cloud_radius_i); for(s16 zi = -m_cloud_radius_i; zi < m_cloud_radius_i; zi++) { u32 si = (zi + m_cloud_radius_i) * m_cloud_radius_i * 2 + m_cloud_radius_i; @@ -195,12 +196,7 @@ void Clouds::render() { s16 zi = zi0; s16 xi = xi0; - // Draw from front to back (needed for transparency) - /*if(zi <= 0) - zi = -m_cloud_radius_i - zi; - if(xi <= 0) - xi = -m_cloud_radius_i - xi;*/ - // Draw from back to front + // Draw from back to front for proper transparency if(zi >= 0) zi = m_cloud_radius_i - zi - 1; if(xi >= 0) @@ -220,17 +216,10 @@ void Clouds::render() video::S3DVertex(0,0,0, 0,0,0, c_top, 0, 0) }; - /*if(zi <= 0 && xi <= 0){ - v[0].Color.setBlue(255); - v[1].Color.setBlue(255); - v[2].Color.setBlue(255); - v[3].Color.setBlue(255); - }*/ - - f32 rx = cloud_size / 2.0f; + const f32 rx = cloud_size / 2.0f; // if clouds are flat, the top layer should be at the given height - f32 ry = m_enable_3d ? m_params.thickness * BS : 0.0f; - f32 rz = cloud_size / 2; + const f32 ry = m_enable_3d ? m_params.thickness * BS : 0.0f; + const f32 rz = cloud_size / 2; for(int i=0; idrawVertexPrimitiveList(v, 4, indices, 2, - video::EVT_STANDARD, scene::EPT_TRIANGLES, video::EIT_16BIT); + vertices.push_back(vertex); + } } } - - delete[] grid; + int quad_count = vertices.size() / 4; + std::vector indices; + indices.reserve(quad_count * 6); + for (int k = 0; k < quad_count; k++) { + indices.push_back(4 * k + 0); + indices.push_back(4 * k + 1); + indices.push_back(4 * k + 2); + indices.push_back(4 * k + 2); + indices.push_back(4 * k + 3); + indices.push_back(4 * k + 0); + } + driver->drawVertexPrimitiveList(vertices.data(), vertices.size(), indices.data(), 2 * quad_count, + video::EVT_STANDARD, scene::EPT_TRIANGLES, video::EIT_16BIT); // Restore fog settings driver->setFog(fog_color, fog_type, fog_start, fog_end, fog_density, From 89cc5bf53730d72744d45f92fc11dc9ab6c232c9 Mon Sep 17 00:00:00 2001 From: numzero Date: Sun, 22 Nov 2020 16:43:38 +0300 Subject: [PATCH 240/266] Don't evaluate things many times --- src/client/clouds.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/client/clouds.cpp b/src/client/clouds.cpp index ccc94cc88..253dee8b9 100644 --- a/src/client/clouds.cpp +++ b/src/client/clouds.cpp @@ -341,14 +341,13 @@ void Clouds::step(float dtime) void Clouds::update(const v3f &camera_p, const video::SColorf &color_diffuse) { + video::SColorf ambient(m_params.color_ambient); + video::SColorf bright(m_params.color_bright); m_camera_pos = camera_p; - m_color.r = MYMIN(MYMAX(color_diffuse.r * m_params.color_bright.getRed(), - m_params.color_ambient.getRed()), 255) / 255.0f; - m_color.g = MYMIN(MYMAX(color_diffuse.g * m_params.color_bright.getGreen(), - m_params.color_ambient.getGreen()), 255) / 255.0f; - m_color.b = MYMIN(MYMAX(color_diffuse.b * m_params.color_bright.getBlue(), - m_params.color_ambient.getBlue()), 255) / 255.0f; - m_color.a = m_params.color_bright.getAlpha() / 255.0f; + m_color.r = core::clamp(color_diffuse.r * bright.r, ambient.r, 1.0f); + m_color.g = core::clamp(color_diffuse.g * bright.g, ambient.g, 1.0f); + m_color.b = core::clamp(color_diffuse.b * bright.b, ambient.b, 1.0f); + m_color.a = bright.a; // is the camera inside the cloud mesh? m_camera_inside_cloud = false; // default From 3077afc0a2039cd4c8d64d9df62ed9b2ba6463dc Mon Sep 17 00:00:00 2001 From: numzero Date: Sun, 22 Nov 2020 16:29:31 +0300 Subject: [PATCH 241/266] Store stars in a single static mesh buffer --- src/client/sky.cpp | 199 ++++++++++++++++++++------------------------- src/client/sky.h | 8 +- 2 files changed, 92 insertions(+), 115 deletions(-) diff --git a/src/client/sky.cpp b/src/client/sky.cpp index 2e0cbca86..29a0545ab 100644 --- a/src/client/sky.cpp +++ b/src/client/sky.cpp @@ -1,6 +1,7 @@ /* Minetest Copyright (C) 2010-2013 celeron55, Perttu Ahola +Copyright (C) 2020 numzero, Lobachevskiy Vitaliy 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 @@ -34,16 +35,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "config.h" using namespace irr::core; -Sky::Sky(s32 id, ITextureSource *tsrc) : - scene::ISceneNode(RenderingEngine::get_scene_manager()->getRootSceneNode(), - RenderingEngine::get_scene_manager(), id) -{ - setAutomaticCulling(scene::EAC_OFF); - m_box.MaxEdge.set(0, 0, 0); - m_box.MinEdge.set(0, 0, 0); - - // Create material - +static video::SMaterial baseMaterial() { video::SMaterial mat; mat.Lighting = false; #if ENABLE_GLES @@ -56,14 +48,29 @@ Sky::Sky(s32 id, ITextureSource *tsrc) : mat.TextureLayer[0].TextureWrapU = video::ETC_CLAMP_TO_EDGE; mat.TextureLayer[0].TextureWrapV = video::ETC_CLAMP_TO_EDGE; mat.BackfaceCulling = false; + return mat; +}; - m_materials[0] = mat; +Sky::Sky(s32 id, ITextureSource *tsrc) : + scene::ISceneNode(RenderingEngine::get_scene_manager()->getRootSceneNode(), + RenderingEngine::get_scene_manager(), id) +{ + setAutomaticCulling(scene::EAC_OFF); + m_box.MaxEdge.set(0, 0, 0); + m_box.MinEdge.set(0, 0, 0); - m_materials[1] = mat; + // Create materials + + m_materials[0] = baseMaterial(); + m_materials[0].MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; + m_materials[0].Lighting = true; + m_materials[0].ColorMaterial = video::ECM_NONE; + + m_materials[1] = baseMaterial(); //m_materials[1].MaterialType = video::EMT_TRANSPARENT_VERTEX_ALPHA; m_materials[1].MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; - m_materials[2] = mat; + m_materials[2] = baseMaterial(); m_materials[2].setTexture(0, tsrc->getTextureForMesh("sunrisebg.png")); m_materials[2].MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; //m_materials[2].MaterialType = video::EMT_TRANSPARENT_ADD_COLOR; @@ -80,7 +87,7 @@ Sky::Sky(s32 id, ITextureSource *tsrc) : tsrc->getTexture(m_moon_params.tonemap) : NULL; if (m_sun_texture) { - m_materials[3] = mat; + m_materials[3] = baseMaterial(); m_materials[3].setTexture(0, m_sun_texture); m_materials[3].MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; // Disables texture filtering @@ -92,7 +99,7 @@ Sky::Sky(s32 id, ITextureSource *tsrc) : m_materials[3].Lighting = true; } if (m_moon_texture) { - m_materials[4] = mat; + m_materials[4] = baseMaterial(); m_materials[4].setTexture(0, m_moon_texture); m_materials[4].MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; // Disables texture filtering @@ -105,7 +112,7 @@ Sky::Sky(s32 id, ITextureSource *tsrc) : } for (int i = 5; i < 11; i++) { - m_materials[i] = mat; + m_materials[i] = baseMaterial(); m_materials[i].Lighting = true; m_materials[i].MaterialType = video::EMT_SOLID; } @@ -673,13 +680,12 @@ void Sky::draw_moon(video::IVideoDriver *driver, float moonsize, const video::SC c = video::SColor(255, 255, 255, 255); draw_sky_body(vertices, -d, d, c); place_sky_body(vertices, -90, wicked_time_of_day * 360 - 90); - driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2); + driver->drawIndexedTriangleList(&vertices[0], 4, indices, 2); } } void Sky::draw_stars(video::IVideoDriver * driver, float wicked_time_of_day) { - driver->setMaterial(m_materials[1]); // Tune values so that stars first appear just after the sun // disappears over the horizon, and disappear just before the sun // appears over the horizon. @@ -687,87 +693,19 @@ void Sky::draw_stars(video::IVideoDriver * driver, float wicked_time_of_day) // to time 4000. float tod = wicked_time_of_day < 0.5f ? wicked_time_of_day : (1.0f - wicked_time_of_day); - float starbrightness = clamp((0.25f - fabsf(tod)) * 20.0f, 0.0f, 1.0f); - - float f = starbrightness; - float d = (0.006 / 2) * m_star_params.scale; - - video::SColor starcolor = m_star_params.starcolor; - starcolor.setAlpha(f * m_star_params.starcolor.getAlpha()); - - // Stars are only drawn when not fully transparent - if (m_star_params.starcolor.getAlpha() < 1) + float starbrightness = (0.25f - fabsf(tod)) * 20.0f; + int alpha = clamp(starbrightness * m_star_params.starcolor.getAlpha(), 0, 255); + if (!alpha) // Stars are only drawn when not fully transparent return; -#if ENABLE_GLES - u16 *indices = new u16[m_star_params.count * 3]; - video::S3DVertex *vertices = - new video::S3DVertex[m_star_params.count * 3]; - for (u32 i = 0; i < m_star_params.count; i++) { - indices[i * 3 + 0] = i * 3 + 0; - indices[i * 3 + 1] = i * 3 + 1; - indices[i * 3 + 2] = i * 3 + 2; - v3f r = m_stars[i]; - core::CMatrix4 a; - a.buildRotateFromTo(v3f(0, 1, 0), r); - v3f p = v3f(-d, 1, -d); - v3f p1 = v3f(d, 1, 0); - v3f p2 = v3f(-d, 1, d); - a.rotateVect(p); - a.rotateVect(p1); - a.rotateVect(p2); - p.rotateXYBy(wicked_time_of_day * 360 - 90); - p1.rotateXYBy(wicked_time_of_day * 360 - 90); - p2.rotateXYBy(wicked_time_of_day * 360 - 90); - vertices[i * 3 + 0].Pos = p; - vertices[i * 3 + 0].Color = starcolor; - vertices[i * 3 + 1].Pos = p1; - vertices[i * 3 + 1].Color = starcolor; - vertices[i * 3 + 2].Pos = p2; - vertices[i * 3 + 2].Color = starcolor; - } - driver->drawIndexedTriangleList(vertices, m_star_params.count * 3, - indices, m_star_params.count); - delete[] indices; - delete[] vertices; -#else - u16 *indices = new u16[m_star_params.count * 4]; - video::S3DVertex *vertices = - new video::S3DVertex[m_star_params.count * 4]; - for (u32 i = 0; i < m_star_params.count; i++) { - indices[i * 4 + 0] = i * 4 + 0; - indices[i * 4 + 1] = i * 4 + 1; - indices[i * 4 + 2] = i * 4 + 2; - indices[i * 4 + 3] = i * 4 + 3; - v3f r = m_stars[i]; - core::CMatrix4 a; - a.buildRotateFromTo(v3f(0, 1, 0), r); - v3f p = v3f(-d, 1, -d); - v3f p1 = v3f(d, 1, -d); - v3f p2 = v3f(d, 1, d); - v3f p3 = v3f(-d, 1, d); - a.rotateVect(p); - a.rotateVect(p1); - a.rotateVect(p2); - a.rotateVect(p3); - p.rotateXYBy(wicked_time_of_day * 360 - 90); - p1.rotateXYBy(wicked_time_of_day * 360 - 90); - p2.rotateXYBy(wicked_time_of_day * 360 - 90); - p3.rotateXYBy(wicked_time_of_day * 360 - 90); - vertices[i * 4 + 0].Pos = p; - vertices[i * 4 + 0].Color = starcolor; - vertices[i * 4 + 1].Pos = p1; - vertices[i * 4 + 1].Color = starcolor; - vertices[i * 4 + 2].Pos = p2; - vertices[i * 4 + 2].Color = starcolor; - vertices[i * 4 + 3].Pos = p3; - vertices[i * 4 + 3].Color = starcolor; - } - driver->drawVertexPrimitiveList(vertices, m_star_params.count * 4, - indices, m_star_params.count, video::EVT_STANDARD, - scene::EPT_QUADS, video::EIT_16BIT); - delete[] indices; - delete[] vertices; -#endif + + m_materials[0].DiffuseColor = video::SColor(alpha, 0, 0, 0); + m_materials[0].EmissiveColor = m_star_params.starcolor; + auto sky_rotation = core::matrix4().setRotationAxisRadians(2.0f * M_PI * (wicked_time_of_day - 0.25f), v3f(0.0f, 0.0f, 1.0f)); + auto world_matrix = driver->getTransform(video::ETS_WORLD); + driver->setTransform(video::ETS_WORLD, world_matrix * sky_rotation); + driver->setMaterial(m_materials[0]); + driver->drawMeshBuffer(m_stars.get()); + driver->setTransform(video::ETS_WORLD, world_matrix); } void Sky::draw_sky_body(std::array &vertices, float pos_1, float pos_2, const video::SColor &c) @@ -822,7 +760,7 @@ void Sky::setSunTexture(std::string sun_texture, m_sun_texture = tsrc->getTextureForMesh(m_sun_params.texture); if (m_sun_texture) { - m_materials[3] = m_materials[0]; + m_materials[3] = baseMaterial(); m_materials[3].setTexture(0, m_sun_texture); m_materials[3].MaterialType = video:: EMT_TRANSPARENT_ALPHA_CHANNEL; @@ -870,7 +808,7 @@ void Sky::setMoonTexture(std::string moon_texture, m_moon_texture = tsrc->getTextureForMesh(m_moon_params.texture); if (m_moon_texture) { - m_materials[4] = m_materials[0]; + m_materials[4] = baseMaterial(); m_materials[4].setTexture(0, m_moon_texture); m_materials[4].MaterialType = video:: EMT_TRANSPARENT_ALPHA_CHANNEL; @@ -892,21 +830,56 @@ void Sky::setStarCount(u16 star_count, bool force_update) // Allow force updating star count at game init. if (m_star_params.count != star_count || force_update) { m_star_params.count = star_count; - m_stars.clear(); - // Rebuild the stars surrounding the camera - for (u16 i = 0; i < star_count; i++) { - v3f star = v3f( - myrand_range(-10000, 10000), - myrand_range(-10000, 10000), - myrand_range(-10000, 10000) - ); - - star.normalize(); - m_stars.emplace_back(star); - } + updateStars(); } } +void Sky::updateStars() { + m_stars.reset(new scene::SMeshBuffer()); + // Stupid IrrLicht doesn’t allow non-indexed rendering, and indexed quad + // rendering is slow due to lack of hardware support. So as indices are + // 16-bit and there are 4 vertices per star... the limit is 2^16/4 = 0x4000. + // That should be well enough actually. + if (m_star_params.count > 0x4000) { + warningstream << "Requested " << m_star_params.count << " stars but " << 0x4000 << " is the max\n"; + m_star_params.count = 0x4000; + } + m_stars->Vertices.reallocate(4 * m_star_params.count); + m_stars->Indices.reallocate(6 * m_star_params.count); + + float d = (0.006 / 2) * m_star_params.scale; + for (u16 i = 0; i < m_star_params.count; i++) { + v3f r = v3f( + myrand_range(-10000, 10000), + myrand_range(-10000, 10000), + myrand_range(-10000, 10000) + ); + core::CMatrix4 a; + a.buildRotateFromTo(v3f(0, 1, 0), r); + v3f p = v3f(-d, 1, -d); + v3f p1 = v3f(d, 1, -d); + v3f p2 = v3f(d, 1, d); + v3f p3 = v3f(-d, 1, d); + a.rotateVect(p); + a.rotateVect(p1); + a.rotateVect(p2); + a.rotateVect(p3); + m_stars->Vertices.push_back(video::S3DVertex(p, {}, {}, {})); + m_stars->Vertices.push_back(video::S3DVertex(p1, {}, {}, {})); + m_stars->Vertices.push_back(video::S3DVertex(p2, {}, {}, {})); + m_stars->Vertices.push_back(video::S3DVertex(p3, {}, {}, {})); + } + for (u16 i = 0; i < m_star_params.count; i++) { + m_stars->Indices.push_back(i * 4 + 0); + m_stars->Indices.push_back(i * 4 + 1); + m_stars->Indices.push_back(i * 4 + 2); + m_stars->Indices.push_back(i * 4 + 2); + m_stars->Indices.push_back(i * 4 + 3); + m_stars->Indices.push_back(i * 4 + 0); + } + m_stars->setHardwareMappingHint(scene::EHM_STATIC); +} + void Sky::setSkyColors(const SkyColor &sky_color) { m_sky_params.sky_color = sky_color; @@ -936,7 +909,7 @@ void Sky::addTextureToSkybox(std::string texture, int material_id, // Keep a list of texture names handy. m_sky_params.textures.emplace_back(texture); video::ITexture *result = tsrc->getTextureForMesh(texture); - m_materials[material_id+5] = m_materials[0]; + m_materials[material_id+5] = baseMaterial(); m_materials[material_id+5].setTexture(0, result); m_materials[material_id+5].MaterialType = video::EMT_SOLID; } diff --git a/src/client/sky.h b/src/client/sky.h index 3227e8f59..176545015 100644 --- a/src/client/sky.h +++ b/src/client/sky.h @@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include "camera.h" #include "irrlichttypes_extrabloated.h" +#include "irr_ptr.h" #include "skyparams.h" #pragma once @@ -77,7 +78,7 @@ public: void setStarsVisible(bool stars_visible) { m_star_params.visible = stars_visible; } void setStarCount(u16 star_count, bool force_update); void setStarColor(video::SColor star_color) { m_star_params.starcolor = star_color; } - void setStarScale(f32 star_scale) { m_star_params.scale = star_scale; } + void setStarScale(f32 star_scale) { m_star_params.scale = star_scale; updateStars(); } bool getCloudsVisible() const { return m_clouds_visible && m_clouds_enabled; } const video::SColorf &getCloudColor() const { return m_cloudcolor_f; } @@ -178,13 +179,16 @@ private: bool m_default_tint = true; - std::vector m_stars; + irr_ptr m_stars; video::ITexture *m_sun_texture; video::ITexture *m_moon_texture; video::ITexture *m_sun_tonemap; video::ITexture *m_moon_tonemap; + void updateStars(); + void updateStarsColor(video::SColor color); + void draw_sun(video::IVideoDriver *driver, float sunsize, const video::SColor &suncolor, const video::SColor &suncolor2, float wicked_time_of_day); void draw_moon(video::IVideoDriver *driver, float moonsize, const video::SColor &mooncolor, From 560627eef8c02f8201e639c75fcd5301d3a33077 Mon Sep 17 00:00:00 2001 From: numzero Date: Sun, 22 Nov 2020 16:41:36 +0300 Subject: [PATCH 242/266] Reuse seed when updating stars The only currently relevant parameter is scale which can now be changed without resetting stars position --- src/client/sky.cpp | 8 +++++--- src/client/sky.h | 1 + 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/client/sky.cpp b/src/client/sky.cpp index 29a0545ab..cc9fb7d36 100644 --- a/src/client/sky.cpp +++ b/src/client/sky.cpp @@ -830,6 +830,7 @@ void Sky::setStarCount(u16 star_count, bool force_update) // Allow force updating star count at game init. if (m_star_params.count != star_count || force_update) { m_star_params.count = star_count; + m_seed = (u64)myrand() << 32 | myrand(); updateStars(); } } @@ -847,12 +848,13 @@ void Sky::updateStars() { m_stars->Vertices.reallocate(4 * m_star_params.count); m_stars->Indices.reallocate(6 * m_star_params.count); + PcgRandom rgen(m_seed); float d = (0.006 / 2) * m_star_params.scale; for (u16 i = 0; i < m_star_params.count; i++) { v3f r = v3f( - myrand_range(-10000, 10000), - myrand_range(-10000, 10000), - myrand_range(-10000, 10000) + rgen.range(-10000, 10000), + rgen.range(-10000, 10000), + rgen.range(-10000, 10000) ); core::CMatrix4 a; a.buildRotateFromTo(v3f(0, 1, 0), r); diff --git a/src/client/sky.h b/src/client/sky.h index 176545015..9f859f961 100644 --- a/src/client/sky.h +++ b/src/client/sky.h @@ -179,6 +179,7 @@ private: bool m_default_tint = true; + u64 m_seed = 0; irr_ptr m_stars; video::ITexture *m_sun_texture; From d7cf40a0ce996985cff20a156c56437f8b64c772 Mon Sep 17 00:00:00 2001 From: numzero Date: Sun, 22 Nov 2020 16:44:00 +0300 Subject: [PATCH 243/266] Replace TriangleFan as poorly supported --- src/client/sky.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/client/sky.cpp b/src/client/sky.cpp index cc9fb7d36..dda59dd11 100644 --- a/src/client/sky.cpp +++ b/src/client/sky.cpp @@ -209,7 +209,7 @@ void Sky::render() const f32 t = 1.0f; const f32 o = 0.0f; - static const u16 indices[4] = {0, 1, 2, 3}; + static const u16 indices[6] = {0, 1, 2, 0, 2, 3}; video::S3DVertex vertices[4]; driver->setMaterial(m_materials[1]); @@ -251,7 +251,7 @@ void Sky::render() vertex.Pos.rotateXZBy(180); } } - driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2); + driver->drawIndexedTriangleList(&vertices[0], 4, indices, 2); } } @@ -277,7 +277,7 @@ void Sky::render() // Switch from -Z (south) to +Z (north) vertex.Pos.rotateXZBy(-180); } - driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2); + driver->drawIndexedTriangleList(&vertices[0], 4, indices, 2); } } @@ -308,7 +308,7 @@ void Sky::render() // Switch from -Z (south) to -X (west) vertex.Pos.rotateXZBy(-90); } - driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2); + driver->drawIndexedTriangleList(&vertices[0], 4, indices, 2); } // Draw sun @@ -344,7 +344,7 @@ void Sky::render() // Switch from -Z (south) to +Z (north) vertex.Pos.rotateXZBy(-180); } - driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2); + driver->drawIndexedTriangleList(&vertices[0], 4, indices, 2); } // Draw bottom far cloudy fog thing in front of sun, moon and stars @@ -353,7 +353,7 @@ void Sky::render() vertices[1] = video::S3DVertex( 1, -1.0, -1, 0, 1, 0, c, o, t); vertices[2] = video::S3DVertex( 1, -1.0, 1, 0, 1, 0, c, o, o); vertices[3] = video::S3DVertex(-1, -1.0, 1, 0, 1, 0, c, t, o); - driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2); + driver->drawIndexedTriangleList(&vertices[0], 4, indices, 2); } } } @@ -597,7 +597,7 @@ void Sky::draw_sun(video::IVideoDriver *driver, float sunsize, const video::SCol * wicked_time_of_day: current time of day, to know where should be the sun in the sky */ { - static const u16 indices[4] = {0, 1, 2, 3}; + static const u16 indices[] = {0, 1, 2, 0, 2, 3}; std::array vertices; if (!m_sun_texture) { driver->setMaterial(m_materials[1]); @@ -615,7 +615,7 @@ void Sky::draw_sun(video::IVideoDriver *driver, float sunsize, const video::SCol for (int i = 0; i < 4; i++) { draw_sky_body(vertices, -sunsizes[i], sunsizes[i], colors[i]); place_sky_body(vertices, 90, wicked_time_of_day * 360 - 90); - driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2); + driver->drawIndexedTriangleList(&vertices[0], 4, indices, 2); } } else { driver->setMaterial(m_materials[3]); @@ -627,7 +627,7 @@ void Sky::draw_sun(video::IVideoDriver *driver, float sunsize, const video::SCol c = video::SColor(255, 255, 255, 255); draw_sky_body(vertices, -d, d, c); place_sky_body(vertices, 90, wicked_time_of_day * 360 - 90); - driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2); + driver->drawIndexedTriangleList(&vertices[0], 4, indices, 2); } } @@ -644,7 +644,7 @@ void Sky::draw_moon(video::IVideoDriver *driver, float moonsize, const video::SC * the sky */ { - static const u16 indices[4] = {0, 1, 2, 3}; + static const u16 indices[] = {0, 1, 2, 0, 2, 3}; std::array vertices; if (!m_moon_texture) { driver->setMaterial(m_materials[1]); @@ -668,7 +668,7 @@ void Sky::draw_moon(video::IVideoDriver *driver, float moonsize, const video::SC for (int i = 0; i < 4; i++) { draw_sky_body(vertices, moonsizes_1[i], moonsizes_2[i], colors[i]); place_sky_body(vertices, -90, wicked_time_of_day * 360 - 90); - driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2); + driver->drawIndexedTriangleList(&vertices[0], 4, indices, 2); } } else { driver->setMaterial(m_materials[4]); From be59668f4743bb3bf85b37a188ffc1759601c152 Mon Sep 17 00:00:00 2001 From: numzero Date: Sun, 22 Nov 2020 17:36:59 +0300 Subject: [PATCH 244/266] Allow missing shaders --- src/client/shader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/shader.cpp b/src/client/shader.cpp index 4f6430579..1cec20d2c 100644 --- a/src/client/shader.cpp +++ b/src/client/shader.cpp @@ -577,7 +577,6 @@ ShaderInfo generate_shader(const std::string &name, u8 material_type, u8 drawtyp shaderinfo.name = name; shaderinfo.material_type = material_type; shaderinfo.drawtype = drawtype; - shaderinfo.material = video::EMT_SOLID; switch (material_type) { case TILE_MATERIAL_OPAQUE: case TILE_MATERIAL_LIQUID_OPAQUE: @@ -598,6 +597,7 @@ ShaderInfo generate_shader(const std::string &name, u8 material_type, u8 drawtyp shaderinfo.base_material = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; break; } + shaderinfo.material = shaderinfo.base_material; bool enable_shaders = g_settings->getBool("enable_shaders"); if (!enable_shaders) From cdcf7dca7c9afb329d49f2016964f77ac379ed67 Mon Sep 17 00:00:00 2001 From: numzero Date: Sun, 22 Nov 2020 18:25:41 +0300 Subject: [PATCH 245/266] Sky: support GLES2 IrrLicht built-in shader is broken, have to write my own --- client/shaders/stars_shader/opengl_fragment.glsl | 6 ++++++ client/shaders/stars_shader/opengl_vertex.glsl | 4 ++++ src/client/game.cpp | 8 +++++++- src/client/sky.cpp | 15 ++++++++------- src/client/sky.h | 8 ++++++-- 5 files changed, 31 insertions(+), 10 deletions(-) create mode 100644 client/shaders/stars_shader/opengl_fragment.glsl create mode 100644 client/shaders/stars_shader/opengl_vertex.glsl diff --git a/client/shaders/stars_shader/opengl_fragment.glsl b/client/shaders/stars_shader/opengl_fragment.glsl new file mode 100644 index 000000000..a9ed741bf --- /dev/null +++ b/client/shaders/stars_shader/opengl_fragment.glsl @@ -0,0 +1,6 @@ +uniform vec4 starColor; + +void main(void) +{ + gl_FragColor = starColor; +} diff --git a/client/shaders/stars_shader/opengl_vertex.glsl b/client/shaders/stars_shader/opengl_vertex.glsl new file mode 100644 index 000000000..77c401f34 --- /dev/null +++ b/client/shaders/stars_shader/opengl_vertex.glsl @@ -0,0 +1,4 @@ +void main(void) +{ + gl_Position = mWorldViewProj * inVertexPosition; +} diff --git a/src/client/game.cpp b/src/client/game.cpp index b7bb0a330..2001f0487 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -424,6 +424,7 @@ class GameGlobalShaderConstantSetter : public IShaderConstantSetter CachedVertexShaderSetting m_animation_timer_vertex; CachedPixelShaderSetting m_animation_timer_pixel; CachedPixelShaderSetting m_day_light; + CachedPixelShaderSetting m_star_color; CachedPixelShaderSetting m_eye_position_pixel; CachedVertexShaderSetting m_eye_position_vertex; CachedPixelShaderSetting m_minimap_yaw; @@ -456,6 +457,7 @@ public: m_animation_timer_vertex("animationTimer"), m_animation_timer_pixel("animationTimer"), m_day_light("dayLight"), + m_star_color("starColor"), m_eye_position_pixel("eyePosition"), m_eye_position_vertex("eyePosition"), m_minimap_yaw("yawVec"), @@ -507,6 +509,10 @@ public: sunlight.b }; m_day_light.set(dnc, services); + video::SColorf star_color = m_sky->getCurrentStarColor(); + float clr[4] = {star_color.r, star_color.g, star_color.b, star_color.a}; + m_star_color.set(clr, services); + u32 animation_timer = porting::getTimeMs() % 1000000; float animation_timer_f = (float)animation_timer / 100000.f; m_animation_timer_vertex.set(&animation_timer_f, services); @@ -1363,7 +1369,7 @@ bool Game::createClient(const GameStartData &start_data) /* Skybox */ - sky = new Sky(-1, texture_src); + sky = new Sky(-1, texture_src, shader_src); scsf->setSky(sky); skybox = NULL; // This is used/set later on in the main run loop diff --git a/src/client/sky.cpp b/src/client/sky.cpp index dda59dd11..3fc5a95b4 100644 --- a/src/client/sky.cpp +++ b/src/client/sky.cpp @@ -51,7 +51,7 @@ static video::SMaterial baseMaterial() { return mat; }; -Sky::Sky(s32 id, ITextureSource *tsrc) : +Sky::Sky(s32 id, ITextureSource *tsrc, IShaderSource *ssrc) : scene::ISceneNode(RenderingEngine::get_scene_manager()->getRootSceneNode(), RenderingEngine::get_scene_manager(), id) { @@ -59,10 +59,12 @@ Sky::Sky(s32 id, ITextureSource *tsrc) : m_box.MaxEdge.set(0, 0, 0); m_box.MinEdge.set(0, 0, 0); + m_enable_shaders = g_settings->getBool("enable_shaders"); + // Create materials m_materials[0] = baseMaterial(); - m_materials[0].MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; + m_materials[0].MaterialType = ssrc->getShaderInfo(ssrc->getShader("stars_shader", TILE_MATERIAL_ALPHA, 0)).material; m_materials[0].Lighting = true; m_materials[0].ColorMaterial = video::ECM_NONE; @@ -694,12 +696,11 @@ void Sky::draw_stars(video::IVideoDriver * driver, float wicked_time_of_day) float tod = wicked_time_of_day < 0.5f ? wicked_time_of_day : (1.0f - wicked_time_of_day); float starbrightness = (0.25f - fabsf(tod)) * 20.0f; - int alpha = clamp(starbrightness * m_star_params.starcolor.getAlpha(), 0, 255); - if (!alpha) // Stars are only drawn when not fully transparent + m_star_color = m_star_params.starcolor; + m_star_color.a = clamp(starbrightness * m_star_color.a, 0.0f, 1.0f); + if (m_star_color.a <= 0.0f) // Stars are only drawn when not fully transparent return; - - m_materials[0].DiffuseColor = video::SColor(alpha, 0, 0, 0); - m_materials[0].EmissiveColor = m_star_params.starcolor; + m_materials[0].DiffuseColor = m_materials[0].EmissiveColor = m_star_color.toSColor(); auto sky_rotation = core::matrix4().setRotationAxisRadians(2.0f * M_PI * (wicked_time_of_day - 0.25f), v3f(0.0f, 0.0f, 1.0f)); auto world_matrix = driver->getTransform(video::ETS_WORLD); driver->setTransform(video::ETS_WORLD, world_matrix * sky_rotation); diff --git a/src/client/sky.h b/src/client/sky.h index 9f859f961..10e1cd976 100644 --- a/src/client/sky.h +++ b/src/client/sky.h @@ -22,6 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "camera.h" #include "irrlichttypes_extrabloated.h" #include "irr_ptr.h" +#include "shader.h" #include "skyparams.h" #pragma once @@ -35,7 +36,7 @@ class Sky : public scene::ISceneNode { public: //! constructor - Sky(s32 id, ITextureSource *tsrc); + Sky(s32 id, ITextureSource *tsrc, IShaderSource *ssrc); virtual void OnRegisterSceneNode(); @@ -102,6 +103,8 @@ public: void clearSkyboxTextures() { m_sky_params.textures.clear(); } void addTextureToSkybox(std::string texture, int material_id, ITextureSource *tsrc); + const video::SColorf &getCurrentStarColor() const { return m_star_color; } + private: aabb3f m_box; video::SMaterial m_materials[SKY_MATERIAL_COUNT]; @@ -155,6 +158,7 @@ private: bool m_clouds_enabled = true; // Initialised to true, reset only by set_sky API bool m_directional_colored_fog; bool m_in_clouds = true; // Prevent duplicating bools to remember old values + bool m_enable_shaders = false; video::SColorf m_bgcolor_bright_f = video::SColorf(1.0f, 1.0f, 1.0f, 1.0f); video::SColorf m_skycolor_bright_f = video::SColorf(1.0f, 1.0f, 1.0f, 1.0f); @@ -181,6 +185,7 @@ private: u64 m_seed = 0; irr_ptr m_stars; + video::SColorf m_star_color; video::ITexture *m_sun_texture; video::ITexture *m_moon_texture; @@ -188,7 +193,6 @@ private: video::ITexture *m_moon_tonemap; void updateStars(); - void updateStarsColor(video::SColor color); void draw_sun(video::IVideoDriver *driver, float sunsize, const video::SColor &suncolor, const video::SColor &suncolor2, float wicked_time_of_day); From c158e20e5beab1037c905fe96b2a56baccddaec7 Mon Sep 17 00:00:00 2001 From: numzero Date: Sun, 22 Nov 2020 19:49:38 +0300 Subject: [PATCH 246/266] Provide fallback star color for GLES 2 with MT shaders disabled --- src/client/sky.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/client/sky.cpp b/src/client/sky.cpp index 3fc5a95b4..0fccf067c 100644 --- a/src/client/sky.cpp +++ b/src/client/sky.cpp @@ -849,6 +849,7 @@ void Sky::updateStars() { m_stars->Vertices.reallocate(4 * m_star_params.count); m_stars->Indices.reallocate(6 * m_star_params.count); + video::SColor fallback_color = m_star_params.starcolor; // used on GLES 2 “without shaders” PcgRandom rgen(m_seed); float d = (0.006 / 2) * m_star_params.scale; for (u16 i = 0; i < m_star_params.count; i++) { @@ -867,10 +868,10 @@ void Sky::updateStars() { a.rotateVect(p1); a.rotateVect(p2); a.rotateVect(p3); - m_stars->Vertices.push_back(video::S3DVertex(p, {}, {}, {})); - m_stars->Vertices.push_back(video::S3DVertex(p1, {}, {}, {})); - m_stars->Vertices.push_back(video::S3DVertex(p2, {}, {}, {})); - m_stars->Vertices.push_back(video::S3DVertex(p3, {}, {}, {})); + m_stars->Vertices.push_back(video::S3DVertex(p, {}, fallback_color, {})); + m_stars->Vertices.push_back(video::S3DVertex(p1, {}, fallback_color, {})); + m_stars->Vertices.push_back(video::S3DVertex(p2, {}, fallback_color, {})); + m_stars->Vertices.push_back(video::S3DVertex(p3, {}, fallback_color, {})); } for (u16 i = 0; i < m_star_params.count; i++) { m_stars->Indices.push_back(i * 4 + 0); From 8689e00fca2cf55594d53f4e112f0d7b6676c8b0 Mon Sep 17 00:00:00 2001 From: numzero Date: Mon, 23 Nov 2020 01:04:31 +0300 Subject: [PATCH 247/266] Fix style --- src/client/sky.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/client/sky.cpp b/src/client/sky.cpp index 0fccf067c..180d43066 100644 --- a/src/client/sky.cpp +++ b/src/client/sky.cpp @@ -35,7 +35,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "config.h" using namespace irr::core; -static video::SMaterial baseMaterial() { +static video::SMaterial baseMaterial() +{ video::SMaterial mat; mat.Lighting = false; #if ENABLE_GLES @@ -836,7 +837,8 @@ void Sky::setStarCount(u16 star_count, bool force_update) } } -void Sky::updateStars() { +void Sky::updateStars() +{ m_stars.reset(new scene::SMeshBuffer()); // Stupid IrrLicht doesn’t allow non-indexed rendering, and indexed quad // rendering is slow due to lack of hardware support. So as indices are From 868749b4f8e898be0c01f892ea78d859d054c17e Mon Sep 17 00:00:00 2001 From: sfan5 Date: Thu, 26 Nov 2020 22:17:11 +0100 Subject: [PATCH 248/266] Return star color calculation to what it previously was --- src/client/sky.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/sky.cpp b/src/client/sky.cpp index 180d43066..9a2614eda 100644 --- a/src/client/sky.cpp +++ b/src/client/sky.cpp @@ -698,7 +698,7 @@ void Sky::draw_stars(video::IVideoDriver * driver, float wicked_time_of_day) float tod = wicked_time_of_day < 0.5f ? wicked_time_of_day : (1.0f - wicked_time_of_day); float starbrightness = (0.25f - fabsf(tod)) * 20.0f; m_star_color = m_star_params.starcolor; - m_star_color.a = clamp(starbrightness * m_star_color.a, 0.0f, 1.0f); + m_star_color.a *= clamp(starbrightness, 0.0f, 1.0f); if (m_star_color.a <= 0.0f) // Stars are only drawn when not fully transparent return; m_materials[0].DiffuseColor = m_materials[0].EmissiveColor = m_star_color.toSColor(); From 9bb381ebd387cd783da8d582949bf284a29d9b3a Mon Sep 17 00:00:00 2001 From: sfan5 Date: Thu, 26 Nov 2020 22:19:46 +0100 Subject: [PATCH 249/266] Change typedef to normal definitions in GUI code --- src/gui/guiEngine.h | 10 +++++----- src/gui/guiFormSpecMenu.h | 18 +++++++++--------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/gui/guiEngine.h b/src/gui/guiEngine.h index e5b3edce7..eef1ad8aa 100644 --- a/src/gui/guiEngine.h +++ b/src/gui/guiEngine.h @@ -29,22 +29,22 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/enriched_string.h" /******************************************************************************/ -/* Typedefs and macros */ +/* Structs and macros */ /******************************************************************************/ /** texture layer ids */ -typedef enum { +enum texture_layer { TEX_LAYER_BACKGROUND = 0, TEX_LAYER_OVERLAY, TEX_LAYER_HEADER, TEX_LAYER_FOOTER, TEX_LAYER_MAX -} texture_layer; +}; -typedef struct { +struct image_definition { video::ITexture *texture = nullptr; bool tile; unsigned int minsize; -} image_definition; +}; /******************************************************************************/ /* forward declarations */ diff --git a/src/gui/guiFormSpecMenu.h b/src/gui/guiFormSpecMenu.h index 53076e3bd..37106cb65 100644 --- a/src/gui/guiFormSpecMenu.h +++ b/src/gui/guiFormSpecMenu.h @@ -41,7 +41,7 @@ class Client; class GUIScrollContainer; class ISoundManager; -typedef enum { +enum FormspecFieldType { f_Button, f_Table, f_TabHeader, @@ -53,13 +53,13 @@ typedef enum { f_HyperText, f_AnimatedImage, f_Unknown -} FormspecFieldType; +}; -typedef enum { +enum FormspecQuitMode { quit_mode_no, quit_mode_accept, quit_mode_cancel -} FormspecQuitMode; +}; struct TextDest { @@ -356,7 +356,7 @@ private: JoystickController *m_joystick; bool m_show_debug = false; - typedef struct { + struct parserData { bool explicit_size; bool real_coordinates; u8 simple_field_count; @@ -384,16 +384,16 @@ private: // used to restore table selection/scroll/treeview state std::unordered_map table_dyndata; - } parserData; + }; - typedef struct { + struct fs_key_pending { bool key_up; bool key_down; bool key_enter; bool key_escape; - } fs_key_pendig; + }; - fs_key_pendig current_keys_pending; + fs_key_pending current_keys_pending; std::string current_field_enter_pending = ""; std::vector m_hovered_item_tooltips; From f1d72d212a0661588be27003069abf4bd8092e55 Mon Sep 17 00:00:00 2001 From: Lars Date: Sun, 15 Nov 2020 22:58:57 -0800 Subject: [PATCH 250/266] Avoid generating the same chunk more than once with multiple emerge threads. --- src/emerge.h | 1 - src/map.cpp | 5 ++++- src/map.h | 1 + src/mapgen/mapgen_carpathian.cpp | 6 ------ src/mapgen/mapgen_flat.cpp | 6 ------ src/mapgen/mapgen_fractal.cpp | 6 ------ src/mapgen/mapgen_singlenode.cpp | 6 ------ src/mapgen/mapgen_v5.cpp | 6 ------ src/mapgen/mapgen_v6.cpp | 6 ------ src/mapgen/mapgen_v7.cpp | 6 ------ src/mapgen/mapgen_valleys.cpp | 6 ------ 11 files changed, 5 insertions(+), 50 deletions(-) diff --git a/src/emerge.h b/src/emerge.h index 6f204666d..da845e243 100644 --- a/src/emerge.h +++ b/src/emerge.h @@ -52,7 +52,6 @@ struct BlockMakeData { u64 seed = 0; v3s16 blockpos_min; v3s16 blockpos_max; - v3s16 blockpos_requested; UniqueQueue transforming_liquid; const NodeDefManager *nodedef = nullptr; diff --git a/src/map.cpp b/src/map.cpp index ef7eddb39..37b6e9b6b 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -1345,6 +1345,9 @@ bool ServerMap::initBlockMake(v3s16 blockpos, BlockMakeData *data) v3s16 bpmin = EmergeManager::getContainingChunk(blockpos, csize); v3s16 bpmax = bpmin + v3s16(1, 1, 1) * (csize - 1); + if (!m_chunks_in_progress.insert(bpmin).second) + return false; + bool enable_mapgen_debug_info = m_emerge->enable_mapgen_debug_info; EMERGE_DBG_OUT("initBlockMake(): " PP(bpmin) " - " PP(bpmax)); @@ -1360,7 +1363,6 @@ bool ServerMap::initBlockMake(v3s16 blockpos, BlockMakeData *data) data->seed = getSeed(); data->blockpos_min = bpmin; data->blockpos_max = bpmax; - data->blockpos_requested = blockpos; data->nodedef = m_nodedef; /* @@ -1482,6 +1484,7 @@ void ServerMap::finishBlockMake(BlockMakeData *data, NOTE: Will be saved later. */ //save(MOD_STATE_WRITE_AT_UNLOAD); + m_chunks_in_progress.erase(bpmin); } MapSector *ServerMap::createSector(v2s16 p2d) diff --git a/src/map.h b/src/map.h index b28f34db3..3bc30c482 100644 --- a/src/map.h +++ b/src/map.h @@ -423,6 +423,7 @@ private: // Chunks core::map m_chunks; #endif + std::set m_chunks_in_progress; /* Metadata is re-written on disk only if this is true. diff --git a/src/mapgen/mapgen_carpathian.cpp b/src/mapgen/mapgen_carpathian.cpp index 74ed263ba..b3a0bd270 100644 --- a/src/mapgen/mapgen_carpathian.cpp +++ b/src/mapgen/mapgen_carpathian.cpp @@ -260,12 +260,6 @@ void MapgenCarpathian::makeChunk(BlockMakeData *data) // Pre-conditions assert(data->vmanip); assert(data->nodedef); - assert(data->blockpos_requested.X >= data->blockpos_min.X && - data->blockpos_requested.Y >= data->blockpos_min.Y && - data->blockpos_requested.Z >= data->blockpos_min.Z); - assert(data->blockpos_requested.X <= data->blockpos_max.X && - data->blockpos_requested.Y <= data->blockpos_max.Y && - data->blockpos_requested.Z <= data->blockpos_max.Z); this->generating = true; this->vm = data->vmanip; diff --git a/src/mapgen/mapgen_flat.cpp b/src/mapgen/mapgen_flat.cpp index d62548014..342455029 100644 --- a/src/mapgen/mapgen_flat.cpp +++ b/src/mapgen/mapgen_flat.cpp @@ -209,12 +209,6 @@ void MapgenFlat::makeChunk(BlockMakeData *data) // Pre-conditions assert(data->vmanip); assert(data->nodedef); - assert(data->blockpos_requested.X >= data->blockpos_min.X && - data->blockpos_requested.Y >= data->blockpos_min.Y && - data->blockpos_requested.Z >= data->blockpos_min.Z); - assert(data->blockpos_requested.X <= data->blockpos_max.X && - data->blockpos_requested.Y <= data->blockpos_max.Y && - data->blockpos_requested.Z <= data->blockpos_max.Z); this->generating = true; this->vm = data->vmanip; diff --git a/src/mapgen/mapgen_fractal.cpp b/src/mapgen/mapgen_fractal.cpp index 3b6bbd6c1..fabb1b2b1 100644 --- a/src/mapgen/mapgen_fractal.cpp +++ b/src/mapgen/mapgen_fractal.cpp @@ -209,12 +209,6 @@ void MapgenFractal::makeChunk(BlockMakeData *data) // Pre-conditions assert(data->vmanip); assert(data->nodedef); - assert(data->blockpos_requested.X >= data->blockpos_min.X && - data->blockpos_requested.Y >= data->blockpos_min.Y && - data->blockpos_requested.Z >= data->blockpos_min.Z); - assert(data->blockpos_requested.X <= data->blockpos_max.X && - data->blockpos_requested.Y <= data->blockpos_max.Y && - data->blockpos_requested.Z <= data->blockpos_max.Z); //TimeTaker t("makeChunk"); diff --git a/src/mapgen/mapgen_singlenode.cpp b/src/mapgen/mapgen_singlenode.cpp index cade9e7a8..5382423fa 100644 --- a/src/mapgen/mapgen_singlenode.cpp +++ b/src/mapgen/mapgen_singlenode.cpp @@ -50,12 +50,6 @@ void MapgenSinglenode::makeChunk(BlockMakeData *data) // Pre-conditions assert(data->vmanip); assert(data->nodedef); - assert(data->blockpos_requested.X >= data->blockpos_min.X && - data->blockpos_requested.Y >= data->blockpos_min.Y && - data->blockpos_requested.Z >= data->blockpos_min.Z); - assert(data->blockpos_requested.X <= data->blockpos_max.X && - data->blockpos_requested.Y <= data->blockpos_max.Y && - data->blockpos_requested.Z <= data->blockpos_max.Z); this->generating = true; this->vm = data->vmanip; diff --git a/src/mapgen/mapgen_v5.cpp b/src/mapgen/mapgen_v5.cpp index 0f6a19fa1..87e54755f 100644 --- a/src/mapgen/mapgen_v5.cpp +++ b/src/mapgen/mapgen_v5.cpp @@ -201,12 +201,6 @@ void MapgenV5::makeChunk(BlockMakeData *data) // Pre-conditions assert(data->vmanip); assert(data->nodedef); - assert(data->blockpos_requested.X >= data->blockpos_min.X && - data->blockpos_requested.Y >= data->blockpos_min.Y && - data->blockpos_requested.Z >= data->blockpos_min.Z); - assert(data->blockpos_requested.X <= data->blockpos_max.X && - data->blockpos_requested.Y <= data->blockpos_max.Y && - data->blockpos_requested.Z <= data->blockpos_max.Z); this->generating = true; this->vm = data->vmanip; diff --git a/src/mapgen/mapgen_v6.cpp b/src/mapgen/mapgen_v6.cpp index 90a52e031..e04180f96 100644 --- a/src/mapgen/mapgen_v6.cpp +++ b/src/mapgen/mapgen_v6.cpp @@ -512,12 +512,6 @@ void MapgenV6::makeChunk(BlockMakeData *data) // Pre-conditions assert(data->vmanip); assert(data->nodedef); - assert(data->blockpos_requested.X >= data->blockpos_min.X && - data->blockpos_requested.Y >= data->blockpos_min.Y && - data->blockpos_requested.Z >= data->blockpos_min.Z); - assert(data->blockpos_requested.X <= data->blockpos_max.X && - data->blockpos_requested.Y <= data->blockpos_max.Y && - data->blockpos_requested.Z <= data->blockpos_max.Z); this->generating = true; this->vm = data->vmanip; diff --git a/src/mapgen/mapgen_v7.cpp b/src/mapgen/mapgen_v7.cpp index 6b0779e9f..cc5f5726d 100644 --- a/src/mapgen/mapgen_v7.cpp +++ b/src/mapgen/mapgen_v7.cpp @@ -317,12 +317,6 @@ void MapgenV7::makeChunk(BlockMakeData *data) // Pre-conditions assert(data->vmanip); assert(data->nodedef); - assert(data->blockpos_requested.X >= data->blockpos_min.X && - data->blockpos_requested.Y >= data->blockpos_min.Y && - data->blockpos_requested.Z >= data->blockpos_min.Z); - assert(data->blockpos_requested.X <= data->blockpos_max.X && - data->blockpos_requested.Y <= data->blockpos_max.Y && - data->blockpos_requested.Z <= data->blockpos_max.Z); //TimeTaker t("makeChunk"); diff --git a/src/mapgen/mapgen_valleys.cpp b/src/mapgen/mapgen_valleys.cpp index d7b6f738f..c4234857e 100644 --- a/src/mapgen/mapgen_valleys.cpp +++ b/src/mapgen/mapgen_valleys.cpp @@ -210,12 +210,6 @@ void MapgenValleys::makeChunk(BlockMakeData *data) // Pre-conditions assert(data->vmanip); assert(data->nodedef); - assert(data->blockpos_requested.X >= data->blockpos_min.X && - data->blockpos_requested.Y >= data->blockpos_min.Y && - data->blockpos_requested.Z >= data->blockpos_min.Z); - assert(data->blockpos_requested.X <= data->blockpos_max.X && - data->blockpos_requested.Y <= data->blockpos_max.Y && - data->blockpos_requested.Z <= data->blockpos_max.Z); //TimeTaker t("makeChunk"); From 8de51dae97aa2fe6ea02e4cf437bfe2b2a38eb06 Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein Date: Sat, 28 Nov 2020 12:08:53 +0100 Subject: [PATCH 251/266] Fixed crash when attempting to access nonexistant inventory from Lua API --- src/script/lua_api/l_client.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/script/lua_api/l_client.cpp b/src/script/lua_api/l_client.cpp index 6c4878873..5e69d55dd 100644 --- a/src/script/lua_api/l_client.cpp +++ b/src/script/lua_api/l_client.cpp @@ -471,6 +471,8 @@ int ModApiClient::l_get_inventory(lua_State *L) try { inventory_location.deSerialize(location); inventory = client->getInventory(inventory_location); + if (! inventory) + throw SerializationError(std::string("Attempt to access nonexistant inventory (") + location + ")"); push_inventory(L, inventory); } catch (SerializationError &) { lua_pushnil(L); From 549025f6a1216a8b0eb90bf07b493d44b8e8e3e2 Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein Date: Sat, 28 Nov 2020 14:23:29 +0100 Subject: [PATCH 252/266] EntityESP, EntityTracers, PlayerESP, PlayerTracers --- builtin/client/cheats/init.lua | 11 ++++++----- builtin/settingtypes.txt | 22 ++++++++++++---------- src/client/game.cpp | 2 +- src/client/render/core.cpp | 30 +++++++++++++++--------------- src/client/render/core.h | 9 +++++---- src/client/renderingengine.cpp | 4 ++-- src/client/renderingengine.h | 6 +++--- src/defaultsettings.cpp | 11 ++++++----- 8 files changed, 50 insertions(+), 45 deletions(-) diff --git a/builtin/client/cheats/init.lua b/builtin/client/cheats/init.lua index 4f4d33d86..f1cfedfef 100644 --- a/builtin/client/cheats/init.lua +++ b/builtin/client/cheats/init.lua @@ -8,7 +8,6 @@ core.cheats = { ["CrystalPvP"] = "crystal_pvp", ["AutoTotem"] = "autototem", ["ThroughWalls"] = "dont_point_nodes", - ["OnlyTracePlayers"] = "only_trace_players", ["AutoHit"] = "autohit", }, ["Movement"] = { @@ -33,11 +32,13 @@ core.cheats = { ["NoHurtCam"] = "no_hurt_cam", ["BrightNight"] = "no_night", ["Coords"] = "coords", - ["Tracers"] = "enable_tracers", - ["ESP"] = "enable_esp", - ["NodeTracers"] = "enable_node_tracers", - ["NodeESP"] = "enable_node_esp", ["CheatHUD"] = "cheat_hud", + ["EntityESP"] = "enable_entity_esp", + ["EntityTracers"] = "enable_entity_tracers", + ["PlayerESP"] = "enable_player_esp", + ["PlayerTracers"] = "enable_player_tracers", + ["NodeESP"] = "enable_node_esp", + ["NodeTracers"] = "enable_node_tracers", }, ["World"] = { ["FastDig"] = "fastdig", diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 49a15f4ff..33b9fa130 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -2279,10 +2279,6 @@ spamclick (FastHit) bool false no_force_rotate (NoForceRotate) bool false -enable_tracers (Tracers) bool false - -enable_esp (ESP) bool false - no_slow (NoSlow) bool false ignore_status_messages (IgnoreStatus) bool true @@ -2335,14 +2331,8 @@ friendlist (Killaura / Forcefield Friendlist) string cheat_hud (CheatHUD) bool true -enable_node_esp (NodeESP) bool false - -enable_node_tracers (NodeTracers) bool false - node_esp_nodes (NodeESP Nodes) string -only_trace_players (OnlyTracePlayers) bool false - autosprint (AutoSprint) bool false override_speed (SpeedOverride) bool false @@ -2362,3 +2352,15 @@ jetpack (JetPack) bool false autohit (AutoHit) bool false antislip (AntiSlip) bool false + +enable_entity_tracers (EntityTracers) bool false + +enable_entity_esp (EntityESP) bool false + +enable_player_tracers (PlayerTracers) bool false + +enable_player_esp (PlayerESP) bool false + +enable_node_esp (NodeESP) bool false + +enable_node_tracers (NodeTracers) bool false diff --git a/src/client/game.cpp b/src/client/game.cpp index cc2a1bc43..c3162b65d 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -3183,7 +3183,7 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, } #endif RenderingEngine::draw_scene(skycolor, m_game_ui->m_flags.show_hud, - m_game_ui->m_flags.show_minimap, draw_wield_tool, draw_crosshair, g_settings->getBool("enable_esp"), g_settings->getBool("enable_tracers"), g_settings->getBool("enable_node_esp"), g_settings->getBool("enable_node_tracers")); + m_game_ui->m_flags.show_minimap, draw_wield_tool, draw_crosshair); /* Profiler graph diff --git a/src/client/render/core.cpp b/src/client/render/core.cpp index 44e3ed744..6d1c68cd1 100644 --- a/src/client/render/core.cpp +++ b/src/client/render/core.cpp @@ -57,7 +57,7 @@ void RenderingCore::updateScreenSize() } void RenderingCore::draw(video::SColor _skycolor, bool _show_hud, bool _show_minimap, - bool _draw_wield_tool, bool _draw_crosshair, bool _draw_esp, bool _draw_tracers, bool _draw_node_esp, bool _draw_node_tracers) + bool _draw_wield_tool, bool _draw_crosshair) { v2u32 ss = driver->getScreenSize(); if (screensize != ss) { @@ -69,19 +69,19 @@ void RenderingCore::draw(video::SColor _skycolor, bool _show_hud, bool _show_min show_minimap = _show_minimap; draw_wield_tool = _draw_wield_tool; draw_crosshair = _draw_crosshair; - draw_esp = _draw_esp; - draw_tracers = _draw_tracers; - draw_node_esp = _draw_node_esp; - draw_node_tracers = _draw_node_tracers; - + draw_entity_esp = g_settings->getBool("enable_entity_esp"); + draw_entity_tracers = g_settings->getBool("enable_entity_tracers"); + draw_player_esp = g_settings->getBool("enable_player_esp"); + draw_player_tracers = g_settings->getBool("enable_player_tracers"); + draw_node_esp = g_settings->getBool("enable_node_esp"); + draw_node_tracers = g_settings->getBool("enable_node_tracers"); + beforeDraw(); drawAll(); } void RenderingCore::drawTracersAndESP() -{ - bool only_trace_players = g_settings->getBool("only_trace_players"); - +{ ClientEnvironment &env = client->getEnv(); Camera *camera = client->getCamera(); @@ -97,9 +97,8 @@ void RenderingCore::drawTracersAndESP() material.setFlag(video::EMF_ZWRITE_ENABLE, false); driver->setMaterial(material); - if (draw_esp || draw_tracers) { + if (draw_entity_esp || draw_entity_tracers || draw_player_esp || draw_player_tracers) { auto allObjects = env.getAllActiveObjects(); - for (auto &it : allObjects) { ClientActiveObject *cao = it.second; if (cao->isLocalPlayer() || cao->getParent()) @@ -107,7 +106,10 @@ void RenderingCore::drawTracersAndESP() GenericCAO *obj = dynamic_cast(cao); if (! obj) continue; - if (only_trace_players && ! obj->isPlayer()) + bool is_player = obj->isPlayer(); + bool draw_esp = is_player ? draw_player_esp : draw_entity_esp; + bool draw_tracers = is_player ? draw_player_tracers : draw_entity_tracers; + if (! (draw_esp || draw_tracers)) continue; aabb3f box; if (! obj->getSelectionBox(&box)) @@ -123,10 +125,8 @@ void RenderingCore::drawTracersAndESP() } if (draw_node_esp || draw_node_tracers) { Map &map = env.getMap(); - std::vector positions; map.listAllLoadedBlocks(positions); - for (v3s16 blockp : positions) { MapBlock *block = map.getBlockNoCreate(blockp); if (! block->mesh) @@ -159,7 +159,7 @@ void RenderingCore::draw3D() if (!show_hud) return; hud->drawSelectionMesh(); - if (draw_esp || draw_tracers || draw_node_esp || draw_node_tracers) + if (draw_entity_esp || draw_entity_tracers || draw_player_esp || draw_player_tracers || draw_node_esp || draw_node_tracers) drawTracersAndESP(); if (draw_wield_tool) camera->drawWieldedTool(); diff --git a/src/client/render/core.h b/src/client/render/core.h index 2040155a6..6137acd62 100644 --- a/src/client/render/core.h +++ b/src/client/render/core.h @@ -36,8 +36,10 @@ protected: bool show_minimap; bool draw_wield_tool; bool draw_crosshair; - bool draw_esp; - bool draw_tracers; + bool draw_entity_esp; + bool draw_entity_tracers; + bool draw_player_esp; + bool draw_player_tracers; bool draw_node_esp; bool draw_node_tracers; @@ -74,8 +76,7 @@ public: void initialize(); void draw(video::SColor _skycolor, bool _show_hud, bool _show_minimap, - bool _draw_wield_tool, bool _draw_crosshair, bool _draw_esp, - bool _draw_tracers, bool _draw_node_esp, bool _draw_node_tracers); + bool _draw_wield_tool, bool _draw_crosshair); inline v2u32 getVirtualSize() const { return virtual_size; } }; diff --git a/src/client/renderingengine.cpp b/src/client/renderingengine.cpp index e6d25d4ee..f5aca8f58 100644 --- a/src/client/renderingengine.cpp +++ b/src/client/renderingengine.cpp @@ -604,9 +604,9 @@ void RenderingEngine::_finalize() } void RenderingEngine::_draw_scene(video::SColor skycolor, bool show_hud, - bool show_minimap, bool draw_wield_tool, bool draw_crosshair, bool draw_esp, bool draw_tracers, bool draw_node_esp, bool draw_node_tracers) + bool show_minimap, bool draw_wield_tool, bool draw_crosshair) { - core->draw(skycolor, show_hud, show_minimap, draw_wield_tool, draw_crosshair, draw_esp, draw_tracers, draw_node_esp, draw_node_tracers); + core->draw(skycolor, show_hud, show_minimap, draw_wield_tool, draw_crosshair); } const char *RenderingEngine::getVideoDriverName(irr::video::E_DRIVER_TYPE type) diff --git a/src/client/renderingengine.h b/src/client/renderingengine.h index 99aa3c678..34cc60630 100644 --- a/src/client/renderingengine.h +++ b/src/client/renderingengine.h @@ -117,10 +117,10 @@ public: } inline static void draw_scene(video::SColor skycolor, bool show_hud, - bool show_minimap, bool draw_wield_tool, bool draw_crosshair, bool draw_esp, bool draw_tracers, bool draw_node_esp, bool draw_node_tracers) + bool show_minimap, bool draw_wield_tool, bool draw_crosshair) { s_singleton->_draw_scene(skycolor, show_hud, show_minimap, - draw_wield_tool, draw_crosshair, draw_esp, draw_tracers, draw_node_esp, draw_node_tracers); + draw_wield_tool, draw_crosshair); } inline static void initialize(Client *client, Hud *hud) @@ -148,7 +148,7 @@ private: bool clouds = true); void _draw_scene(video::SColor skycolor, bool show_hud, bool show_minimap, - bool draw_wield_tool, bool draw_crosshair, bool draw_esp, bool draw_tracers, bool draw_node_esp, bool draw_node_tracers); + bool draw_wield_tool, bool draw_crosshair); void _initialize(Client *client, Hud *hud); diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 8050b533c..34851cfa1 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -102,8 +102,6 @@ void set_default_settings(Settings *settings) settings->setDefault("log_particles", "false"); settings->setDefault("spamclick", "false"); settings->setDefault("no_force_rotate", "false"); - settings->setDefault("enable_tracers", "false"); - settings->setDefault("enable_esp", "false"); settings->setDefault("no_slow", "false"); settings->setDefault("float_above_parent", "false"); settings->setDefault("ignore_status_messages", "true"); @@ -131,10 +129,7 @@ void set_default_settings(Settings *settings) settings->setDefault("forcefield", "false"); settings->setDefault("friendlist", ""); settings->setDefault("cheat_hud", "true"); - settings->setDefault("enable_node_esp", "false"); - settings->setDefault("enable_node_tracers", "false"); settings->setDefault("node_esp_nodes", ""); - settings->setDefault("only_trace_players", "false"); settings->setDefault("autosprint", "false"); settings->setDefault("override_speed", "false"); settings->setDefault("override_jump", "false"); @@ -145,6 +140,12 @@ void set_default_settings(Settings *settings) settings->setDefault("jetpack", "false"); settings->setDefault("autohit", "false"); settings->setDefault("antislip", "false"); + settings->setDefault("enable_entity_esp", "false"); + settings->setDefault("enable_entity_tracers", "false"); + settings->setDefault("enable_player_esp", "false"); + settings->setDefault("enable_player_tracers", "false"); + settings->setDefault("enable_node_esp", "false"); + settings->setDefault("enable_node_tracers", "false"); // Keymap settings->setDefault("remote_port", "30000"); From b4e475726b0793af1338f0618fac29e4fef69098 Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein Date: Sat, 28 Nov 2020 14:37:26 +0100 Subject: [PATCH 253/266] Added configureable Colors for PlayerESP and EntityESP --- builtin/settingtypes.txt | 4 ++++ src/client/render/core.cpp | 9 +++++++-- src/client/render/core.h | 2 ++ src/defaultsettings.cpp | 2 ++ 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 33b9fa130..9c5e859b8 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -2364,3 +2364,7 @@ enable_player_esp (PlayerESP) bool false enable_node_esp (NodeESP) bool false enable_node_tracers (NodeTracers) bool false + +entity_esp_color (EntityESP Color) v3f 255, 255, 255 + +player_esp_color (PlayerESP Color) v3f 0, 255, 0 diff --git a/src/client/render/core.cpp b/src/client/render/core.cpp index 6d1c68cd1..794ec0186 100644 --- a/src/client/render/core.cpp +++ b/src/client/render/core.cpp @@ -75,6 +75,10 @@ void RenderingCore::draw(video::SColor _skycolor, bool _show_hud, bool _show_min draw_player_tracers = g_settings->getBool("enable_player_tracers"); draw_node_esp = g_settings->getBool("enable_node_esp"); draw_node_tracers = g_settings->getBool("enable_node_tracers"); + v3f entity_color = g_settings->getV3F("entity_esp_color"); + v3f player_color = g_settings->getV3F("player_esp_color"); + entity_esp_color = video::SColor(255, entity_color.X, entity_color.Y, entity_color.Z); + player_esp_color = video::SColor(255, player_color.X, player_color.Y, player_color.Z); beforeDraw(); drawAll(); @@ -109,6 +113,7 @@ void RenderingCore::drawTracersAndESP() bool is_player = obj->isPlayer(); bool draw_esp = is_player ? draw_player_esp : draw_entity_esp; bool draw_tracers = is_player ? draw_player_tracers : draw_entity_tracers; + video::SColor color = is_player ? player_esp_color : entity_esp_color; if (! (draw_esp || draw_tracers)) continue; aabb3f box; @@ -118,9 +123,9 @@ void RenderingCore::drawTracersAndESP() box.MinEdge += pos; box.MaxEdge += pos; if (draw_esp) - driver->draw3DBox(box, video::SColor(255, 255, 255, 255)); + driver->draw3DBox(box, color); if (draw_tracers) - driver->draw3DLine(eye_pos, box.getCenter(), video::SColor(255, 255, 255, 255)); + driver->draw3DLine(eye_pos, box.getCenter(), color); } } if (draw_node_esp || draw_node_tracers) { diff --git a/src/client/render/core.h b/src/client/render/core.h index 6137acd62..386c5a840 100644 --- a/src/client/render/core.h +++ b/src/client/render/core.h @@ -42,6 +42,8 @@ protected: bool draw_player_tracers; bool draw_node_esp; bool draw_node_tracers; + video::SColor entity_esp_color; + video::SColor player_esp_color; IrrlichtDevice *device; video::IVideoDriver *driver; diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 34851cfa1..3c4e5f82b 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -146,6 +146,8 @@ void set_default_settings(Settings *settings) settings->setDefault("enable_player_tracers", "false"); settings->setDefault("enable_node_esp", "false"); settings->setDefault("enable_node_tracers", "false"); + settings->setDefault("entity_esp_color", "(255, 255, 255)"); + settings->setDefault("player_esp_color", "(0, 255, 0)"); // Keymap settings->setDefault("remote_port", "30000"); From 9633ad122b4b0f90fa937a0f0df38e488344ddec Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein Date: Sat, 28 Nov 2020 15:58:19 +0100 Subject: [PATCH 254/266] Improved Freecam --- builtin/client/cheats/world.lua | 6 +++--- src/client/camera.cpp | 2 +- src/client/clientenvironment.cpp | 4 ++-- src/client/clientmap.cpp | 4 ++-- src/client/content_cao.cpp | 12 ++++++------ src/client/game.cpp | 5 +++-- src/client/localplayer.cpp | 16 ++++++++-------- src/defaultsettings.cpp | 1 - 8 files changed, 25 insertions(+), 25 deletions(-) diff --git a/builtin/client/cheats/world.lua b/builtin/client/cheats/world.lua index 1a86e0703..d537036a9 100644 --- a/builtin/client/cheats/world.lua +++ b/builtin/client/cheats/world.lua @@ -54,9 +54,9 @@ core.register_globalstep(function(dtime) end if core.settings:get_bool("nuke") then local i = 0 - for x = pos.x - 5, pos.x + 5 do - for y = pos.y - 5, pos.y + 5 do - for z = pos.z - 5, pos.z + 5 do + for x = pos.x - 4, pos.x + 4 do + for y = pos.y - 4, pos.y + 4 do + for z = pos.z - 4, pos.z + 4 do local p = vector.new(x, y, z) local node = core.get_node_or_nil(p) local def = node and core.get_node_def(node.name) diff --git a/src/client/camera.cpp b/src/client/camera.cpp index 398bc8377..53c0b351c 100644 --- a/src/client/camera.cpp +++ b/src/client/camera.cpp @@ -347,7 +347,7 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 busytime, f32 tool_r // 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"); + bool flying = (g_settings->getBool("free_move") && m_client->checkLocalPrivilege("fly")) || g_settings->getBool("freecam"); if (player_position.Y > old_player_position.Y && !player->is_climbing && !flying) { f32 oldy = old_player_position.Y; f32 newy = player_position.Y; diff --git a/src/client/clientenvironment.cpp b/src/client/clientenvironment.cpp index 2b50fbf64..d480c5056 100644 --- a/src/client/clientenvironment.cpp +++ b/src/client/clientenvironment.cpp @@ -147,8 +147,8 @@ void ClientEnvironment::step(float dtime) stepTimeOfDay(dtime); // Get some settings - bool fly_allowed = m_client->checkLocalPrivilege("fly"); - bool free_move = fly_allowed && g_settings->getBool("free_move"); + bool fly_allowed = m_client->checkLocalPrivilege("fly") || g_settings->getBool("freecam"); + bool free_move = (fly_allowed && g_settings->getBool("free_move")) || g_settings->getBool("freecam"); // Get local player LocalPlayer *lplayer = getLocalPlayer(); diff --git a/src/client/clientmap.cpp b/src/client/clientmap.cpp index 294687ff8..937c38bf1 100644 --- a/src/client/clientmap.cpp +++ b/src/client/clientmap.cpp @@ -144,7 +144,7 @@ void ClientMap::updateDrawList() // No occlusion culling when free_move is on and camera is // inside ground bool occlusion_culling_enabled = true; - if (g_settings->getBool("free_move") && g_settings->getBool("noclip")) { + if ((g_settings->getBool("free_move") && g_settings->getBool("noclip")) || g_settings->getBool("freecam")) { MapNode n = getNode(cam_pos_nodes); if (n.getContent() == CONTENT_IGNORE || m_nodedef->get(n).solidness == 2) @@ -588,7 +588,7 @@ void ClientMap::renderPostFx(CameraMode cam_mode) const ContentFeatures& features = m_nodedef->get(n); video::SColor post_effect_color = features.post_effect_color; if(features.solidness == 2 && !((g_settings->getBool("noclip") || g_settings->getBool("freecam")) && - m_client->checkLocalPrivilege("noclip")) && + (m_client->checkLocalPrivilege("noclip") || g_settings->getBool("freecam"))) && cam_mode == CAMERA_MODE_FIRST) { post_effect_color = video::SColor(255, 0, 0, 0); diff --git a/src/client/content_cao.cpp b/src/client/content_cao.cpp index 7208212d4..a5db49cd2 100644 --- a/src/client/content_cao.cpp +++ b/src/client/content_cao.cpp @@ -495,7 +495,7 @@ void GenericCAO::setAttachment(int parent_id, const std::string &bone, } else if (!m_is_local_player) { // Objects attached to the local player should be hidden in first person m_is_visible = !m_attached_to_local || - m_client->getCamera()->getCameraMode() != CAMERA_MODE_FIRST; + m_client->getCamera()->getCameraMode() != CAMERA_MODE_FIRST || g_settings->getBool("freecam"); m_force_visible = false; } else { // Local players need to have this set, @@ -937,8 +937,8 @@ void GenericCAO::updateMarker() void GenericCAO::updateNametag() { - if (m_is_local_player && ! g_settings->getBool("freecam")) // No nametag for local player - return; + //if (m_is_local_player && ! g_settings->getBool("freecam")) // No nametag for local player + //return; if (m_prop.nametag.empty()) { // Delete nametag @@ -1016,12 +1016,12 @@ void GenericCAO::step(float dtime, ClientEnvironment *env) bool allow_update = false; // increase speed if using fast or flying fast - if((g_settings->getBool("fast_move") && + if(((g_settings->getBool("fast_move") && m_client->checkLocalPrivilege("fast")) && (controls.aux1 || (!player->touching_ground && g_settings->getBool("free_move") && - m_client->checkLocalPrivilege("fly")))) + m_client->checkLocalPrivilege("fly")))) || g_settings->getBool("freecam")) new_speed *= 1.5; // slowdown speed if sneeking if (controls.sneak && walking && ! g_settings->getBool("no_slow")) @@ -1932,7 +1932,7 @@ void GenericCAO::updateMeshCulling() if (!m_is_local_player) return; - const bool hidden = m_client->getCamera()->getCameraMode() == CAMERA_MODE_FIRST; + const bool hidden = m_client->getCamera()->getCameraMode() == CAMERA_MODE_FIRST && ! g_settings->getBool("freecam"); if (m_meshnode && m_prop.visual == "upright_sprite") { u32 buffers = m_meshnode->getMesh()->getMeshBufferCount(); diff --git a/src/client/game.cpp b/src/client/game.cpp index c3162b65d..242e49e97 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -2235,7 +2235,8 @@ void Game::updatePlayerCAOVisibility() if (!playercao) return; playercao->updateMeshCulling(); - playercao->setChildrenVisible(camera->getCameraMode() > CAMERA_MODE_FIRST || g_settings->getBool("freecam")); + bool is_visible = camera->getCameraMode() > CAMERA_MODE_FIRST || g_settings->getBool("freecam"); + playercao->setChildrenVisible(is_visible); } void Game::updateSound(f32 dtime) @@ -2998,7 +2999,7 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, float direct_brightness; bool sunlight_seen; - if (m_cache_enable_noclip && m_cache_enable_free_move) { + if ((m_cache_enable_noclip && m_cache_enable_free_move) || g_settings->getBool("freecam")) { direct_brightness = time_brightness; sunlight_seen = true; } else { diff --git a/src/client/localplayer.cpp b/src/client/localplayer.cpp index 7d532c602..ddec04cb5 100644 --- a/src/client/localplayer.cpp +++ b/src/client/localplayer.cpp @@ -198,9 +198,9 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d, PlayerSettings &player_settings = getPlayerSettings(); // Skip collision detection if noclip mode is used - bool fly_allowed = m_client->checkLocalPrivilege("fly"); - bool noclip = m_client->checkLocalPrivilege("noclip") && player_settings.noclip; - bool free_move = player_settings.free_move && fly_allowed; + bool fly_allowed = m_client->checkLocalPrivilege("fly") || g_settings->getBool("freecam"); + bool noclip = (m_client->checkLocalPrivilege("noclip") && player_settings.noclip) || g_settings->getBool("freecam"); + bool free_move = (player_settings.free_move && fly_allowed) || g_settings->getBool("freecam"); if (noclip && free_move) { position += m_speed * dtime; @@ -500,8 +500,8 @@ void LocalPlayer::applyControl(float dtime, Environment *env) bool fly_allowed = m_client->checkLocalPrivilege("fly"); bool fast_allowed = m_client->checkLocalPrivilege("fast"); - bool free_move = fly_allowed && player_settings.free_move; - bool fast_move = fast_allowed && player_settings.fast_move; + bool free_move = (fly_allowed && player_settings.free_move) || g_settings->getBool("freecam"); + bool fast_move = (fast_allowed && player_settings.fast_move) || g_settings->getBool("freecam"); bool pitch_move = (free_move || in_liquid) && player_settings.pitch_move; // When aux1_descends is enabled the fast key is used to go down, so fast isn't possible bool fast_climb = fast_move && control.aux1 && !player_settings.aux1_descends; @@ -802,9 +802,9 @@ void LocalPlayer::old_move(f32 dtime, Environment *env, f32 pos_max_d, PlayerSettings &player_settings = getPlayerSettings(); // Skip collision detection if noclip mode is used - bool fly_allowed = m_client->checkLocalPrivilege("fly"); - bool noclip = m_client->checkLocalPrivilege("noclip") && player_settings.noclip; - bool free_move = noclip && fly_allowed && player_settings.free_move; + bool fly_allowed = m_client->checkLocalPrivilege("fly") || g_settings->getBool("freecam"); + bool noclip = (m_client->checkLocalPrivilege("noclip") && player_settings.noclip) || g_settings->getBool("freecam"); + bool free_move = (noclip && fly_allowed && player_settings.free_move) || g_settings->getBool("freecam"); if (free_move) { position += m_speed * dtime; setPosition(position); diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 3c4e5f82b..7c53cacc8 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -80,7 +80,6 @@ void set_default_settings(Settings *settings) settings->setDefault("xray_nodes", "default:stone,mcl_core:stone"); settings->setDefault("fullbright", "false"); settings->setDefault("priv_bypass", "true"); - settings->setDefault("fastdig", "false"); settings->setDefault("freecam", "false"); settings->setDefault("prevent_natural_damage", "true"); settings->setDefault("freecam", "false"); From eaa8a51323ded43c099ff0e1e09346faafe5baa9 Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein Date: Sat, 28 Nov 2020 16:22:30 +0100 Subject: [PATCH 255/266] Fixed FastPlace and AutoPlace --- src/client/game.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client/game.cpp b/src/client/game.cpp index 242e49e97..8537ba39a 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -2562,8 +2562,8 @@ void Game::handlePointingAtNode(const PointedThing &pointed, } } - if (((wasKeyPressed(KeyType::PLACE) || g_settings->getBool("autoplace")) || - (runData.repeat_place_timer >= (g_settings->getBool("fastplace") ? 0 : m_repeat_place_time))) && + if ((wasKeyPressed(KeyType::PLACE) || + (runData.repeat_place_timer >= (g_settings->getBool("fastplace") ? 0.001 : m_repeat_place_time))) && client->checkPrivilege("interact")) { runData.repeat_place_timer = 0; infostream << "Place button pressed while looking at ground" << std::endl; From f825cf0e35966869fe22ae0629a1c7256160add4 Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein Date: Sat, 28 Nov 2020 19:40:26 +0100 Subject: [PATCH 256/266] Fixed Minimap position --- src/client/minimap.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/client/minimap.cpp b/src/client/minimap.cpp index 60b095712..f809821c3 100644 --- a/src/client/minimap.cpp +++ b/src/client/minimap.cpp @@ -579,8 +579,8 @@ void Minimap::drawMinimap() const u32 size = 0.25 * screensize.Y; drawMinimap(core::rect( - screensize.X - size - 10, 10, - screensize.X - 10, size + 10)); + screensize.X - size * 2 - 10, 10, + screensize.X - size - 10, size + 10)); } void Minimap::drawMinimap(core::rect rect) { @@ -598,9 +598,6 @@ void Minimap::drawMinimap(core::rect rect) { core::matrix4 oldProjMat = driver->getTransform(video::ETS_PROJECTION); core::matrix4 oldViewMat = driver->getTransform(video::ETS_VIEW); -// driver->setViewPort(core::rect( -// screensize.X - size * 2 - 10, 10, -// screensize.X - size - 10, size + 10)); driver->setViewPort(rect); driver->setTransform(video::ETS_PROJECTION, core::matrix4()); driver->setTransform(video::ETS_VIEW, core::matrix4()); @@ -655,7 +652,6 @@ void Minimap::drawMinimap(core::rect rect) { driver->setViewPort(oldViewPort); // Draw player markers -// v2s32 s_pos(screensize.X - size * 2 - 10, 10); v2s32 s_pos(rect.UpperLeftCorner.X, rect.UpperLeftCorner.Y); core::dimension2di imgsize(data->object_marker_red->getOriginalSize()); core::rect img_rect(0, 0, imgsize.Width, imgsize.Height); From 5bead7daaf1782ecb142a57ac57bd8ba405ba150 Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein Date: Sat, 28 Nov 2020 20:13:20 +0100 Subject: [PATCH 257/266] Added minetest.close_formspec --- builtin/client/cheats/inventory.lua | 13 +++++++++---- builtin/client/util.lua | 6 +++++- doc/client_lua_api.txt | 8 ++++++++ src/client/game.cpp | 20 ++++++++++++++------ 4 files changed, 36 insertions(+), 11 deletions(-) diff --git a/builtin/client/cheats/inventory.lua b/builtin/client/cheats/inventory.lua index b9943f507..f40730844 100644 --- a/builtin/client/cheats/inventory.lua +++ b/builtin/client/cheats/inventory.lua @@ -79,22 +79,27 @@ local function check_tool(stack, node_groups, old_best_time) return best_time < old_best_time, best_time end -core.register_on_punchnode(function(pos, node) - if not minetest.settings:get_bool("autotool") then return end +function core.select_best_tool(nodename) local player = minetest.localplayer local inventory = minetest.get_inventory("current_player") - local node_groups = minetest.get_node_def(node.name).groups + local node_groups = minetest.get_node_def(nodename).groups local new_index = player:get_wield_index() local is_better, best_time = false, math.huge is_better, best_time = check_tool(player:get_wielded_item(), node_groups, best_time) is_better, best_time = check_tool(inventory.hand[1], node_groups, best_time) - for index, stack in pairs(inventory.main) do + for index, stack in ipairs(inventory.main) do is_better, best_time = check_tool(stack, node_groups, best_time) if is_better then new_index = index end end player:set_wield_index(new_index) +end + +core.register_on_punchnode(function(pos, node) + if not minetest.settings:get_bool("autotool") then + core.select_best_tool(node.name) + end end) -- Enderchest diff --git a/builtin/client/util.lua b/builtin/client/util.lua index 783d0ceb1..42e383c5c 100644 --- a/builtin/client/util.lua +++ b/builtin/client/util.lua @@ -49,4 +49,8 @@ function core.get_pointed_thing() local def = core.get_item_def(item:get_name()) local ray = core.raycast(pos, pos2, true, core.settings:get_bool("point_liquids") or def and def.liquids_pointable) return ray and ray:next() -end +end + +function core.close_formspec(formname) + return core.show_formspec(formname, "") +end diff --git a/doc/client_lua_api.txt b/doc/client_lua_api.txt index f14b8eceb..9ce553ca1 100644 --- a/doc/client_lua_api.txt +++ b/doc/client_lua_api.txt @@ -1107,6 +1107,14 @@ Passed to `HTTPApiTable.fetch` callback. Returned by * Reference to the camera object. See [`Camera`](#camera) class reference for methods. * `minetest.show_formspec(formname, formspec)` : returns true on success * Shows a formspec to the player +* `minetest.close_formspec(formname)` + * `formname`: has to exactly match the one given in `show_formspec`, or the + formspec will not close. + * calling `show_formspec(formname, "")` is equal to this + expression. + * to close a formspec regardless of the formname, call + `minetest.close_formspec("")`. + **USE THIS ONLY WHEN ABSOLUTELY NECESSARY!** * `minetest.display_chat_message(message)` returns true on success * Shows a chat message to the current player. diff --git a/src/client/game.cpp b/src/client/game.cpp index 8537ba39a..f79fdba8c 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -1889,13 +1889,21 @@ void Game::handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation } void Game::handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam) -{ - FormspecFormSource *fs_src = new FormspecFormSource(*event->show_formspec.formspec); - LocalFormspecHandler *txt_dst = - new LocalFormspecHandler(*event->show_formspec.formname, client); - GUIFormSpecMenu::create(m_game_ui->getFormspecGUI(), client, &input->joystick, +{ + if (event->show_formspec.formspec->empty()) { + auto formspec = m_game_ui->getFormspecGUI(); + if (formspec && (event->show_formspec.formname->empty() + || *(event->show_formspec.formname) == m_game_ui->getFormspecName())) { + formspec->quitMenu(); + } + } else { + FormspecFormSource *fs_src = new FormspecFormSource(*event->show_formspec.formspec); + LocalFormspecHandler *txt_dst = + new LocalFormspecHandler(*event->show_formspec.formname, client); + GUIFormSpecMenu::create(m_game_ui->getFormspecGUI(), client, &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound); - + } + delete event->show_formspec.formspec; delete event->show_formspec.formname; } From 4695222bc39578643e2ae64fb8725f45a340a008 Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein Date: Sun, 29 Nov 2020 19:08:39 +0100 Subject: [PATCH 258/266] Fix and Improve AutoTool --- builtin/client/cheats/combat.lua | 4 +-- builtin/client/cheats/inventory.lua | 38 +++++++++++++++++++++++------ 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/builtin/client/cheats/combat.lua b/builtin/client/cheats/combat.lua index 4904d8c52..4b753eabd 100644 --- a/builtin/client/cheats/combat.lua +++ b/builtin/client/cheats/combat.lua @@ -12,7 +12,7 @@ core.register_globalstep(function(dtime) local control = player:get_control() local pointed = core.get_pointed_thing() local item = player:get_wielded_item():get_name() - if core.settings:get_bool("killaura") or core.settings:get_bool("forcefield") and control.LMB then + if core.settings:get_bool("killaura") or core.settings:get_bool("forcefield") and control.dig then local friendlist = core.settings:get("friendlist"):split(",") for _, obj in ipairs(core.get_objects_inside_radius(player:get_pos(), 5)) do local do_attack = true @@ -45,7 +45,7 @@ core.register_globalstep(function(dtime) else switched_to_totem = switched_to_totem end - elseif control.RMB and item == "mcl_end:crystal" then + elseif control.place and item == "mcl_end:crystal" then placed_crystal = true elseif control.sneak then if pointed and pointed.type == "node" and not used_sneak then diff --git a/builtin/client/cheats/inventory.lua b/builtin/client/cheats/inventory.lua index f40730844..85dce3be5 100644 --- a/builtin/client/cheats/inventory.lua +++ b/builtin/client/cheats/inventory.lua @@ -29,7 +29,7 @@ core.register_globalstep(function(dtime) end -- Strip if core.settings:get_bool("strip") then - if itemdef and itemdef.groups.tree and player:get_control().RMB then + if itemdef and itemdef.groups.tree and player:get_control().place then strip_move_act:from("current_player", "main", wieldindex) strip_move_back_act:to("current_player", "main", wieldindex) strip_move_act:apply() @@ -79,10 +79,10 @@ local function check_tool(stack, node_groups, old_best_time) return best_time < old_best_time, best_time end -function core.select_best_tool(nodename) - local player = minetest.localplayer - local inventory = minetest.get_inventory("current_player") - local node_groups = minetest.get_node_def(nodename).groups +local function find_best_tool(nodename) + local player = core.localplayer + local inventory = core.get_inventory("current_player") + local node_groups = core.get_node_def(nodename).groups local new_index = player:get_wield_index() local is_better, best_time = false, math.huge is_better, best_time = check_tool(player:get_wielded_item(), node_groups, best_time) @@ -93,15 +93,37 @@ function core.select_best_tool(nodename) new_index = index end end - player:set_wield_index(new_index) + return new_index end +function core.select_best_tool(nodename) + player:set_wield_index(find_best_tool(nodename)) +end + +local new_index, old_index, pointed_pos + core.register_on_punchnode(function(pos, node) - if not minetest.settings:get_bool("autotool") then - core.select_best_tool(node.name) + if minetest.settings:get_bool("autotool") then + pointed_pos = pos + old_index = old_index or core.localplayer:get_wield_index() + new_index = find_best_tool(node.name) end end) +core.register_globalstep(function() + local player = core.localplayer + if not new_index then return end + if minetest.settings:get_bool("autotool") then + local pt = core.get_pointed_thing() + if pt and pt.type == "node" and vector.equals(core.get_pointed_thing_position(pt), pointed_pos) and player:get_control().dig then + player:set_wield_index(new_index) + return + end + end + player:set_wield_index(old_index) + new_index, old_index, pointed_pos = nil +end) + -- Enderchest function get_itemslot_bg(x, y, w, h) From 0a285dd338fb415744e3fb8d6a1cc3763d796c4a Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein Date: Sun, 29 Nov 2020 19:15:41 +0100 Subject: [PATCH 259/266] Remove NextItem --- builtin/client/cheats/init.lua | 1 - builtin/client/cheats/inventory.lua | 17 +++-------------- builtin/settingtypes.txt | 2 -- src/client/game.cpp | 18 +----------------- src/client/inputhandler.cpp | 1 - src/client/keys.h | 1 - src/defaultsettings.cpp | 2 -- src/gui/guiKeyChangeMenu.cpp | 2 -- 8 files changed, 4 insertions(+), 40 deletions(-) diff --git a/builtin/client/cheats/init.lua b/builtin/client/cheats/init.lua index f1cfedfef..31657e9ed 100644 --- a/builtin/client/cheats/init.lua +++ b/builtin/client/cheats/init.lua @@ -77,7 +77,6 @@ core.cheats = { ["AutoTool"] = "autotool", ["Enderchest"] = function() core.open_enderchest() end, ["HandSlot"] = function() core.open_handslot() end, - ["NextItem"] = "next_item", ["Strip"] = "strip", ["AutoRefill"] = "autorefill", } diff --git a/builtin/client/cheats/inventory.lua b/builtin/client/cheats/inventory.lua index 85dce3be5..bb32d7d8b 100644 --- a/builtin/client/cheats/inventory.lua +++ b/builtin/client/cheats/inventory.lua @@ -1,5 +1,3 @@ -local elapsed_time = 0 -local tick_time = 0.05 local drop_action = InventoryAction("drop") local strip_move_act = InventoryAction("move") @@ -48,15 +46,6 @@ core.register_globalstep(function(dtime) end end end - -- NextItem - if core.settings:get_bool("next_item") then - elapsed_time = elapsed_time + dtime - if elapsed_time < tick_time then return end - if item:get_count() == 0 then - player:set_wield_index(wieldindex + 1) - end - elapsed_time = 0 - end end) core.register_list_command("eject", "Configure AutoEject", "eject_items") @@ -103,7 +92,7 @@ end local new_index, old_index, pointed_pos core.register_on_punchnode(function(pos, node) - if minetest.settings:get_bool("autotool") then + if core.settings:get_bool("autotool") then pointed_pos = pos old_index = old_index or core.localplayer:get_wield_index() new_index = find_best_tool(node.name) @@ -113,7 +102,7 @@ end) core.register_globalstep(function() local player = core.localplayer if not new_index then return end - if minetest.settings:get_bool("autotool") then + if core.settings:get_bool("autotool") then local pt = core.get_pointed_thing() if pt and pt.type == "node" and vector.equals(core.get_pointed_thing_position(pt), pointed_pos) and player:get_control().dig then player:set_wield_index(new_index) @@ -167,7 +156,7 @@ local hand_formspec = "size[9,8.75]".. "listring[current_player;main]" function core.open_handslot() - minetest.show_formspec("__builtin__:hand", hand_formspec) + core.show_formspec("__builtin__:hand", hand_formspec) end diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 9c5e859b8..b66ac3538 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -2295,8 +2295,6 @@ autotool (AutoTool) bool false autorespawn (AutoRespawn) bool false -next_item (NextItem) bool false - scaffold (Scaffold) bool false scaffold_plus (ScaffoldPlus) bool false diff --git a/src/client/game.cpp b/src/client/game.cpp index f79fdba8c..69feeef97 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -1125,8 +1125,6 @@ void Game::processKeyInput() toggleFreecam(); } else if (wasKeyDown(KeyType::SCAFFOLD)) { toggleScaffold(); - } else if (wasKeyDown(KeyType::NEXT_ITEM)) { - toggleNextItem(); #if USE_SOUND } else if (wasKeyDown(KeyType::MUTE)) { if (g_settings->getBool("enable_sound")) { @@ -1448,18 +1446,6 @@ void Game::toggleScaffold() } } -void Game::toggleNextItem() -{ - bool next_item = ! g_settings->getBool("next_item"); - g_settings->set("next_item", bool_to_cstr(next_item)); - - if (next_item) { - m_game_ui->showTranslatedStatusText("NextItem enabled"); - } else { - m_game_ui->showTranslatedStatusText("NextItem disabled"); - } -} - void Game::toggleCinematic() { bool cinematic = !g_settings->getBool("cinematic"); @@ -3472,7 +3458,6 @@ void Game::showPauseMenu() "- %s: Killaura\n" "- %s: Freecam\n" "- %s: Scaffold\n" - "- %s: NextItem\n" ); char control_text_buf[600]; @@ -3492,8 +3477,7 @@ void Game::showPauseMenu() GET_KEY_NAME(keymap_chat), GET_KEY_NAME(keymap_toggle_killaura), GET_KEY_NAME(keymap_toggle_freecam), - GET_KEY_NAME(keymap_toggle_scaffold), - GET_KEY_NAME(keymap_toggle_next_item) + GET_KEY_NAME(keymap_toggle_scaffold) ); std::string control_text = std::string(control_text_buf); diff --git a/src/client/inputhandler.cpp b/src/client/inputhandler.cpp index 37ab838c0..544c0e344 100644 --- a/src/client/inputhandler.cpp +++ b/src/client/inputhandler.cpp @@ -78,7 +78,6 @@ void KeyCache::populate() key[KeyType::KILLAURA] = getKeySetting("keymap_toggle_killaura"); key[KeyType::FREECAM] = getKeySetting("keymap_toggle_freecam"); key[KeyType::SCAFFOLD] = getKeySetting("keymap_toggle_scaffold"); - key[KeyType::NEXT_ITEM] = getKeySetting("keymap_toggle_next_item"); key[KeyType::SELECT_UP] = getKeySetting("keymap_select_up"); key[KeyType::SELECT_DOWN] = getKeySetting("keymap_select_down"); key[KeyType::SELECT_LEFT] = getKeySetting("keymap_select_left"); diff --git a/src/client/keys.h b/src/client/keys.h index 828ea3ec4..d4f3dd4c1 100644 --- a/src/client/keys.h +++ b/src/client/keys.h @@ -75,7 +75,6 @@ public: KILLAURA, FREECAM, SCAFFOLD, - NEXT_ITEM, SELECT_UP, SELECT_DOWN, SELECT_LEFT, diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 7c53cacc8..141070003 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -110,7 +110,6 @@ void set_default_settings(Settings *settings) settings->setDefault("eject_items", ""); settings->setDefault("autotool", "false"); settings->setDefault("autorespawn", "false"); - settings->setDefault("next_item", "false"); settings->setDefault("scaffold", "false"); settings->setDefault("scaffold_plus", "false"); settings->setDefault("block_water", "false"); @@ -198,7 +197,6 @@ void set_default_settings(Settings *settings) settings->setDefault("keymap_toggle_killaura", "KEY_KEY_X"); settings->setDefault("keymap_toggle_freecam", "KEY_KEY_G"); settings->setDefault("keymap_toggle_scaffold", "KEY_KEY_Y"); - settings->setDefault("keymap_toggle_next_item", "KEY_KEY_U"); settings->setDefault("keymap_select_up", "KEY_UP"); settings->setDefault("keymap_select_down", "KEY_DOWN"); settings->setDefault("keymap_select_left", "KEY_LEFT"); diff --git a/src/gui/guiKeyChangeMenu.cpp b/src/gui/guiKeyChangeMenu.cpp index 94fe0074a..a7270da06 100644 --- a/src/gui/guiKeyChangeMenu.cpp +++ b/src/gui/guiKeyChangeMenu.cpp @@ -80,7 +80,6 @@ enum GUI_ID_KEY_KILLAURA_BUTTON, GUI_ID_KEY_FREECAM_BUTTON, GUI_ID_KEY_SCAFFOLD_BUTTON, - GUI_ID_KEY_NEXT_ITEM_BUTTON, GUI_ID_KEY_SELECT_UP_BUTTON, GUI_ID_KEY_SELECT_DOWN_BUTTON, GUI_ID_KEY_SELECT_LEFT_BUTTON, @@ -461,7 +460,6 @@ void GUIKeyChangeMenu::init_keys() this->add_key(GUI_ID_KEY_KILLAURA_BUTTON, wgettext("Killaura"), "keymap_toggle_killaura"); this->add_key(GUI_ID_KEY_FREECAM_BUTTON, wgettext("Freecam"), "keymap_toggle_freecam"); this->add_key(GUI_ID_KEY_SCAFFOLD_BUTTON, wgettext("Scaffold"), "keymap_toggle_scaffold"); - this->add_key(GUI_ID_KEY_NEXT_ITEM_BUTTON, wgettext("NextItem"), "keymap_toggle_next_item"); this->add_key(GUI_ID_KEY_SELECT_UP_BUTTON, wgettext("C. Menu Up"), "keymap_select_up"); this->add_key(GUI_ID_KEY_SELECT_DOWN_BUTTON,wgettext("C. Menu Down"), "keymap_select_down"); this->add_key(GUI_ID_KEY_SELECT_LEFT_BUTTON,wgettext("C. Menu Left"), "keymap_select_left"); From 8b3eaf5b05379f995bf8d55532c107b190848a69 Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein Date: Mon, 30 Nov 2020 11:20:07 +0100 Subject: [PATCH 260/266] Lua API: Particle callbacks; Add NoWeather --- builtin/client/cheats/init.lua | 1 + builtin/client/cheats/render.lua | 12 +++++++ builtin/client/register.lua | 1 + builtin/settingtypes.txt | 2 ++ doc/client_lua_api.txt | 6 +++- src/defaultsettings.cpp | 1 + src/network/clientpackethandler.cpp | 5 ++- src/script/common/c_content.cpp | 51 +++++++++++++++++++++-------- src/script/common/c_content.h | 1 + src/script/cpp_api/s_client.cpp | 37 +++++++++++++++++++++ src/script/cpp_api/s_client.h | 2 ++ 11 files changed, 101 insertions(+), 18 deletions(-) diff --git a/builtin/client/cheats/init.lua b/builtin/client/cheats/init.lua index 31657e9ed..30c3fe208 100644 --- a/builtin/client/cheats/init.lua +++ b/builtin/client/cheats/init.lua @@ -39,6 +39,7 @@ core.cheats = { ["PlayerTracers"] = "enable_player_tracers", ["NodeESP"] = "enable_node_esp", ["NodeTracers"] = "enable_node_tracers", + ["NoWeather"] = "noweather", }, ["World"] = { ["FastDig"] = "fastdig", diff --git a/builtin/client/cheats/render.lua b/builtin/client/cheats/render.lua index 6402246f3..dc3f71247 100644 --- a/builtin/client/cheats/render.lua +++ b/builtin/client/cheats/render.lua @@ -1,2 +1,14 @@ core.register_list_command("xray", "Configure X-Ray", "xray_nodes") core.register_list_command("search", "Configure NodeESP", "node_esp_nodes") + +core.register_on_spawn_particle(function(particle) + if core.settings:get_bool("noweather") and particle.texture:sub(1, 12) == "weather_pack" then + return true + end +end) + +core.register_on_play_sound(function(sound) + if core.settings:get_bool("noweather") and sound.name == "weather_rain" then + return true + end +end) diff --git a/builtin/client/register.lua b/builtin/client/register.lua index 03dc41f71..669ef134e 100644 --- a/builtin/client/register.lua +++ b/builtin/client/register.lua @@ -104,3 +104,4 @@ core.registered_on_modchannel_signal, core.register_on_modchannel_signal = make_ core.registered_on_inventory_open, core.register_on_inventory_open = make_registration() core.registered_on_recieve_physics_override, core.register_on_recieve_physics_override = make_registration() core.registered_on_play_sound, core.register_on_play_sound = make_registration() +core.registered_on_spawn_particle, core.register_on_spawn_particle = make_registration() diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index b66ac3538..45f20a99d 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -2366,3 +2366,5 @@ enable_node_tracers (NodeTracers) bool false entity_esp_color (EntityESP Color) v3f 255, 255, 255 player_esp_color (PlayerESP Color) v3f 0, 255, 0 + +noweather (NoWeather) bool false diff --git a/doc/client_lua_api.txt b/doc/client_lua_api.txt index 9ce553ca1..5618c7a6f 100644 --- a/doc/client_lua_api.txt +++ b/doc/client_lua_api.txt @@ -755,10 +755,14 @@ Call these functions only at load time! * Called when recieving physics_override from server * Newest functions are called first * If any function returns true, the physics override does not change -* `minetest.register_on_sound_play(function(SimpleSoundSpec))` +* `minetest.register_on_play_sound(function(SimpleSoundSpec))` * Called when recieving a play sound command from server * Newest functions are called first * If any function returns true, the sound does not play +* `minetest.register_on_spawn_partice(function(particle definition))` + * Called when recieving a spawn particle command from server + * Newest functions are called first + * If any function returns true, the particel does not spawn ### Setting-related * `minetest.settings`: Settings object containing all of the settings from the diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 141070003..bcf5012a9 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -146,6 +146,7 @@ void set_default_settings(Settings *settings) settings->setDefault("enable_node_tracers", "false"); settings->setDefault("entity_esp_color", "(255, 255, 255)"); settings->setDefault("player_esp_color", "(0, 255, 0)"); + settings->setDefault("noweather", "false"); // Keymap settings->setDefault("remote_port", "30000"); diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index c39586f2c..55f85571d 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -988,9 +988,8 @@ void Client::handleCommand_SpawnParticle(NetworkPacket* pkt) event->type = CE_SPAWN_PARTICLE; event->spawn_particle = new ParticleParameters(p); - if (g_settings->getBool("log_particles")) { - std::cout << p.pos.X << " " << p.pos.Y << " " << p.pos.Z << std::endl; - } + if (m_mods_loaded && m_script->on_spawn_particle(*event->spawn_particle)) + return; m_client_event_queue.push(event); } diff --git a/src/script/common/c_content.cpp b/src/script/common/c_content.cpp index 04f4c335c..72361bdb5 100644 --- a/src/script/common/c_content.cpp +++ b/src/script/common/c_content.cpp @@ -1324,20 +1324,6 @@ void push_inventory(lua_State *L, Inventory *inventory) } } -/******************************************************************************/ -void push_inventory_list(lua_State *L, Inventory *inv, const char *name) -{ - InventoryList *invlist = inv->getList(name); - if(invlist == NULL){ - lua_pushnil(L); - return; - } - std::vector items; - for(u32 i=0; igetSize(); i++) - items.push_back(invlist->getItem(i)); - push_items(L, items); -} - /******************************************************************************/ void read_inventory_list(lua_State *L, int tableindex, Inventory *inv, const char *name, Server* srv, int forcesize) @@ -1367,6 +1353,19 @@ void read_inventory_list(lua_State *L, int tableindex, } } +void push_inventory_list(lua_State *L, Inventory *inv, const char *name) +{ + InventoryList *invlist = inv->getList(name); + if(invlist == NULL){ + lua_pushnil(L); + return; + } + std::vector items; + for(u32 i=0; igetSize(); i++) + items.push_back(invlist->getItem(i)); + push_items(L, items); +} + /******************************************************************************/ struct TileAnimationParams read_animation_definition(lua_State *L, int index) { @@ -1402,6 +1401,29 @@ struct TileAnimationParams read_animation_definition(lua_State *L, int index) return anim; } +void push_animation_definition(lua_State *L, struct TileAnimationParams anim) +{ + switch (anim.type) { + case TAT_NONE: + lua_pushnil(L); + break; + case TAT_VERTICAL_FRAMES: + lua_newtable(L); + setstringfield(L, -1, "type", "vertical_frames"); + setfloatfield(L, -1, "aspect_w", anim.vertical_frames.aspect_w); + setfloatfield(L, -1, "aspect_h", anim.vertical_frames.aspect_h); + setfloatfield(L, -1, "length", anim.vertical_frames.length); + break; + case TAT_SHEET_2D: + lua_newtable(L); + setstringfield(L, -1, "type", "sheet_2d"); + setintfield(L, -1, "frames_w", anim.sheet_2d.frames_w); + setintfield(L, -1, "frames_h", anim.sheet_2d.frames_h); + setintfield(L, -1, "frame_length", anim.sheet_2d.frame_length); + break; + } +} + /******************************************************************************/ ToolCapabilities read_tool_capabilities( lua_State *L, int table) @@ -2103,6 +2125,7 @@ void push_collision_move_result(lua_State *L, const collisionMoveResult &res) /**/ } +/******************************************************************************/ void push_physics_override(lua_State *L, float speed, float jump, float gravity, bool sneak, bool sneak_glitch, bool new_move) { lua_createtable(L, 0, 6); diff --git a/src/script/common/c_content.h b/src/script/common/c_content.h index 08c74c292..10b77a116 100644 --- a/src/script/common/c_content.h +++ b/src/script/common/c_content.h @@ -98,6 +98,7 @@ void push_hit_params (lua_State *L, ItemStack read_item (lua_State *L, int index, IItemDefManager *idef); struct TileAnimationParams read_animation_definition(lua_State *L, int index); +void push_animation_definition(lua_State *L, struct TileAnimationParams anim); ToolCapabilities read_tool_capabilities (lua_State *L, int table); void push_tool_capabilities (lua_State *L, diff --git a/src/script/cpp_api/s_client.cpp b/src/script/cpp_api/s_client.cpp index cf7df5b5d..200a449ee 100644 --- a/src/script/cpp_api/s_client.cpp +++ b/src/script/cpp_api/s_client.cpp @@ -253,6 +253,43 @@ bool ScriptApiClient::on_play_sound(SimpleSoundSpec spec) return readParam(L, -1); } +bool ScriptApiClient::on_spawn_particle(struct ParticleParameters param) +{ + SCRIPTAPI_PRECHECKHEADER + + // Get core.registered_on_play_sound + + lua_getglobal(L, "core"); + lua_getfield(L, -1, "registered_on_spawn_particle"); + + // Push data + lua_newtable(L); + push_v3f(L, param.pos); + lua_setfield(L, -2, "pos"); + push_v3f(L, param.vel); + lua_setfield(L, -2, "velocity"); + push_v3f(L, param.acc); + lua_setfield(L, -2, "acceleration"); + setfloatfield(L, -1, "expirationtime", param.expirationtime); + setboolfield(L, -1, "collisiondetection", param.collisiondetection); + setboolfield(L, -1, "collision_removal", param.collision_removal); + setboolfield(L, -1, "object_collision", param.object_collision); + setboolfield(L, -1, "vertical", param.vertical); + push_animation_definition(L, param.animation); + lua_setfield(L, -2, "animation"); + setstringfield(L, -1, "texture", param.texture); + setintfield(L, -1, "glow", param.glow); + if (param.node.getContent() != CONTENT_IGNORE) { + pushnode(L, param.node, getGameDef()->ndef()); + lua_setfield(L, -2, "node"); + } + setintfield(L, -1, "node_tile", param.node_tile); + + // Call functions + runCallbacks(1, RUN_CALLBACKS_MODE_OR); + return readParam(L, -1); +} + bool ScriptApiClient::on_inventory_open(Inventory *inventory) { SCRIPTAPI_PRECHECKHEADER diff --git a/src/script/cpp_api/s_client.h b/src/script/cpp_api/s_client.h index 8db253d56..ff1c473ae 100644 --- a/src/script/cpp_api/s_client.h +++ b/src/script/cpp_api/s_client.h @@ -27,6 +27,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/string.h" #include "util/pointedthing.h" #include "lua_api/l_item.h" +#include "particles.h" #ifdef _CRT_MSVCP_CURRENT #include @@ -59,6 +60,7 @@ public: bool on_item_use(const ItemStack &item, const PointedThing &pointed); bool on_recieve_physics_override(float override_speed, float override_jump, float override_gravity, bool sneak, bool sneak_glitch, bool new_move); bool on_play_sound(SimpleSoundSpec spec); + bool on_spawn_particle(struct ParticleParameters param); bool on_inventory_open(Inventory *inventory); void open_enderchest(); From 3df23e23cd1ace3449660077665f5ebe3e13fdc2 Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein Date: Tue, 1 Dec 2020 10:59:02 +0100 Subject: [PATCH 261/266] Small AutoTool Fix --- builtin/client/cheats/inventory.lua | 2 +- builtin/client/util.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/builtin/client/cheats/inventory.lua b/builtin/client/cheats/inventory.lua index bb32d7d8b..024bc5e98 100644 --- a/builtin/client/cheats/inventory.lua +++ b/builtin/client/cheats/inventory.lua @@ -86,7 +86,7 @@ local function find_best_tool(nodename) end function core.select_best_tool(nodename) - player:set_wield_index(find_best_tool(nodename)) + core.localplayer:set_wield_index(find_best_tool(nodename)) end local new_index, old_index, pointed_pos diff --git a/builtin/client/util.lua b/builtin/client/util.lua index 42e383c5c..30f983af3 100644 --- a/builtin/client/util.lua +++ b/builtin/client/util.lua @@ -41,7 +41,7 @@ end function core.get_pointed_thing() local pos = core.camera:get_pos() - local pos2 = vector.add(pos, vector.multiply(core.camera:get_look_dir(), 5)) + local pos2 = vector.add(pos, vector.multiply(core.camera:get_look_dir(), 7)) local player = core.localplayer if not player then return end local item = player:get_wielded_item() From 89995efee487939d47e852d35a777e5dcfc28f35 Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein Date: Tue, 1 Dec 2020 14:32:54 +0100 Subject: [PATCH 262/266] CheatDB Support & Enable/Disable CSMs in Main Menu --- builtin/mainmenu/dlg_contentstore.lua | 8 +-- builtin/mainmenu/pkgmgr.lua | 56 ++++++++++++++++- builtin/mainmenu/tab_content.lua | 86 ++++++++++++++++++++++++++- src/script/lua_api/l_mainmenu.cpp | 3 + 4 files changed, 144 insertions(+), 9 deletions(-) diff --git a/builtin/mainmenu/dlg_contentstore.lua b/builtin/mainmenu/dlg_contentstore.lua index 6525f6013..b3616bfc8 100644 --- a/builtin/mainmenu/dlg_contentstore.lua +++ b/builtin/mainmenu/dlg_contentstore.lua @@ -40,8 +40,8 @@ local num_per_page = 5 local filter_type = 1 local filter_types_titles = { fgettext("All packages"), - fgettext("Games"), - fgettext("Mods"), +-- fgettext("Games"), + fgettext("Clientmods"), fgettext("Texture packs"), } @@ -50,7 +50,7 @@ local download_queue = {} local filter_types_type = { nil, - "game", +-- "game", "mod", "txp", } @@ -254,7 +254,7 @@ end function store.update_paths() local mod_hash = {} pkgmgr.refresh_globals() - for _, mod in pairs(pkgmgr.global_mods:get_list()) do + for _, mod in pairs(pkgmgr.clientmods:get_list()) do if mod.author then mod_hash[mod.author:lower() .. "/" .. mod.name] = mod end diff --git a/builtin/mainmenu/pkgmgr.lua b/builtin/mainmenu/pkgmgr.lua index 5b8807310..d4acb2b6a 100644 --- a/builtin/mainmenu/pkgmgr.lua +++ b/builtin/mainmenu/pkgmgr.lua @@ -592,7 +592,7 @@ function pkgmgr.install_dir(type, path, basename, targetpath) clean_path = get_last_folder(cleanup_path(basefolder.path)) end if clean_path then - targetpath = core.get_modpath() .. DIR_DELIM .. clean_path + targetpath = core.get_clientmodpath() .. DIR_DELIM .. clean_path else return nil, fgettext("Install Mod: Unable to find suitable folder name for modpack $1", @@ -619,7 +619,7 @@ function pkgmgr.install_dir(type, path, basename, targetpath) end if targetfolder ~= nil and pkgmgr.isValidModname(targetfolder) then - targetpath = core.get_modpath() .. DIR_DELIM .. targetfolder + targetpath = core.get_clientmodpath() .. DIR_DELIM .. targetfolder else return nil, fgettext("Install Mod: Unable to find real mod name for: $1", path) end @@ -671,6 +671,54 @@ function pkgmgr.install(type, modfilename, basename, dest) end -------------------------------------------------------------------------------- +function pkgmgr.prepareclientmodlist(data) + local retval = {} + + local clientmods = {} + + --read clientmods + local modpath = core.get_clientmodpath() + + if modpath ~= nil and + modpath ~= "" then + get_mods(modpath,clientmods) + end + + for i=1,#clientmods,1 do + clientmods[i].type = "mod" + clientmods[i].loc = "global" + clientmods[i].is_clientside = true + retval[#retval + 1] = clientmods[i] + end + + --read mods configuration + local filename = modpath .. + DIR_DELIM .. "mods.conf" + + local conffile = Settings(filename) + + for key,value in pairs(conffile:to_table()) do + if key:sub(1, 9) == "load_mod_" then + key = key:sub(10) + local element = nil + for i=1,#retval,1 do + if retval[i].name == key and + not retval[i].is_modpack then + element = retval[i] + break + end + end + if element ~= nil then + element.enabled = value ~= "false" and value ~= "nil" and value + else + core.log("info", "Clientmod: " .. key .. " " .. dump(value) .. " but not found") + end + end + end + + return retval +end + function pkgmgr.preparemodlist(data) local retval = {} @@ -813,6 +861,10 @@ function pkgmgr.refresh_globals() pkgmgr.comparemod, is_equal, nil, {}) pkgmgr.global_mods:add_sort_mechanism("alphabetic", sort_mod_list) pkgmgr.global_mods:set_sortmode("alphabetic") + pkgmgr.clientmods = filterlist.create(pkgmgr.prepareclientmodlist, + pkgmgr.comparemod, is_equal, nil, {}) + pkgmgr.clientmods:add_sort_mechanism("alphabetic", sort_mod_list) + pkgmgr.clientmods:set_sortmode("alphabetic") end -------------------------------------------------------------------------------- diff --git a/builtin/mainmenu/tab_content.lua b/builtin/mainmenu/tab_content.lua index 336730bf4..623210597 100644 --- a/builtin/mainmenu/tab_content.lua +++ b/builtin/mainmenu/tab_content.lua @@ -19,6 +19,10 @@ local packages_raw local packages +local function modname_valid(name) + return not name:find("[^a-z0-9_]") +end + -------------------------------------------------------------------------------- local function get_formspec(tabview, name, tabdata) if pkgmgr.global_mods == nil then @@ -33,6 +37,7 @@ local function get_formspec(tabview, name, tabdata) table.insert_all(packages_raw, pkgmgr.games) table.insert_all(packages_raw, pkgmgr.get_texture_packs()) table.insert_all(packages_raw, pkgmgr.global_mods:get_list()) + table.insert_all(packages_raw, pkgmgr.clientmods:get_list()) local function get_data() return packages_raw @@ -45,6 +50,38 @@ local function get_formspec(tabview, name, tabdata) packages = filterlist.create(get_data, pkgmgr.compare_package, is_equal, nil, {}) + + local filename = core.get_clientmodpath() .. DIR_DELIM .. "mods.conf" + + local conffile = Settings(filename) + local mods = conffile:to_table() + + for i = 1, #packages_raw do + local mod = packages_raw[i] + if mod.is_clientside and not mod.is_modpack then + if modname_valid(mod.name) then + conffile:set("load_mod_" .. mod.name, + mod.enabled and "true" or "false") + elseif mod.enabled then + gamedata.errormessage = fgettext_ne("Failed to enable clientmo" .. + "d \"$1\" as it contains disallowed characters. " .. + "Only characters [a-z0-9_] are allowed.", + mod.name) + end + mods["load_mod_" .. mod.name] = nil + end + end + + -- Remove mods that are not present anymore + for key in pairs(mods) do + if key:sub(1, 9) == "load_mod_" then + conffile:remove(key) + end + end + + if not conffile:write() then + core.log("error", "Failed to write clientmod config file") + end end if tabdata.selected_pkg == nil then @@ -94,9 +131,21 @@ local function get_formspec(tabview, name, tabdata) if selected_pkg.type == "mod" then if selected_pkg.is_modpack then - retval = retval .. - "button[8.65,4.65;3.25,1;btn_mod_mgr_rename_modpack;" .. - fgettext("Rename") .. "]" + if selected_pkg.is_clientside then + if pkgmgr.is_modpack_entirely_enabled({list = packages}, selected_pkg.name) then + retval = retval .. + "button[8.65,4.65;3.25,1;btn_mod_mgr_mp_disable;" .. + fgettext("Disable modpack") .. "]" + else + retval = retval .. + "button[8.65,4.65;3.25,1;btn_mod_mgr_mp_enable;" .. + fgettext("Enable modpack") .. "]" + end + else + retval = retval .. + "button[8.65,4.65;3.25,1;btn_mod_mgr_rename_modpack;" .. + fgettext("Rename") .. "]" + end else --show dependencies desc = desc .. "\n\n" @@ -117,6 +166,17 @@ local function get_formspec(tabview, name, tabdata) "\n" .. toadd_soft end end + if selected_pkg.is_clientside then + if selected_pkg.enabled then + retval = retval .. + "button[8.65,4.65;3.25,1;btn_mod_mgr_disable_mod;" .. + fgettext("Disable") .. "]" + else + retval = retval .. + "button[8.65,4.65;3.25,1;btn_mod_mgr_enable_mod;" .. + fgettext("Enable") .. "]" + end + end end else @@ -150,6 +210,26 @@ local function handle_buttons(tabview, fields, tabname, tabdata) if fields["pkglist"] ~= nil then local event = core.explode_table_event(fields["pkglist"]) tabdata.selected_pkg = event.row + local mod = packages:get_list()[tabdata.selected_pkg] + + if event.type == "DCL" and mod.is_clientside then + pkgmgr.enable_mod({data = {list = packages, selected_mod = tabdata.selected_pkg}}) + packages = nil + end + return true + end + + if fields.btn_mod_mgr_mp_enable ~= nil or + fields.btn_mod_mgr_mp_disable ~= nil then + pkgmgr.enable_mod({data = {list = packages, selected_mod = tabdata.selected_pkg}}, fields.btn_mod_mgr_mp_enable ~= nil) + packages = nil + return true + end + + if fields.btn_mod_mgr_enable_mod ~= nil or + fields.btn_mod_mgr_disable_mod ~= nil then + pkgmgr.enable_mod({data = {list = packages, selected_mod = tabdata.selected_pkg}}, fields.btn_mod_mgr_enable_mod ~= nil) + packages = nil return true end diff --git a/src/script/lua_api/l_mainmenu.cpp b/src/script/lua_api/l_mainmenu.cpp index 0aa2760e9..2cf4a979b 100644 --- a/src/script/lua_api/l_mainmenu.cpp +++ b/src/script/lua_api/l_mainmenu.cpp @@ -902,6 +902,9 @@ bool ModApiMainMenu::mayModifyPath(const std::string &path) if (fs::PathStartsWith(path, fs::RemoveRelativePathComponents(porting::path_user + DIR_DELIM "mods"))) return true; + if (fs::PathStartsWith(path, fs::RemoveRelativePathComponents(porting::path_user + DIR_DELIM "clientmods"))) + return true; + if (fs::PathStartsWith(path, fs::RemoveRelativePathComponents(porting::path_user + DIR_DELIM "textures"))) return true; From ce47003ccaeb53394e67883161c8a45b2e7e2ee8 Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein Date: Tue, 1 Dec 2020 17:10:48 +0100 Subject: [PATCH 263/266] Update defaults for ContentDB (->CheatDB) --- src/defaultsettings.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index bcf5012a9..1a9c4e5f6 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -413,13 +413,13 @@ void set_default_settings(Settings *settings) settings->setDefault("chat_font_size", "0"); // Default "font_size" // ContentDB - settings->setDefault("contentdb_url", "https://content.minetest.net"); + settings->setDefault("contentdb_url", "http://cheatdb.elidragon.com:5123"); settings->setDefault("contentdb_max_concurrent_downloads", "3"); #ifdef __ANDROID__ - settings->setDefault("contentdb_flag_blacklist", "nonfree, android_default"); + settings->setDefault("contentdb_flag_blacklist", "android_default"); #else - settings->setDefault("contentdb_flag_blacklist", "nonfree, desktop_default"); + settings->setDefault("contentdb_flag_blacklist", "desktop_default"); #endif From 56d536ea5e1f98c760d7c78a4584df043fb7f0f8 Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein Date: Tue, 1 Dec 2020 18:22:24 +0100 Subject: [PATCH 264/266] Update CheatDB URL again --- src/defaultsettings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 1a9c4e5f6..7ca30697b 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -413,7 +413,7 @@ void set_default_settings(Settings *settings) settings->setDefault("chat_font_size", "0"); // Default "font_size" // ContentDB - settings->setDefault("contentdb_url", "http://cheatdb.elidragon.com:5123"); + settings->setDefault("contentdb_url", "http://cheatdb.elidragon.com"); settings->setDefault("contentdb_max_concurrent_downloads", "3"); #ifdef __ANDROID__ From 8f3061cd89801430964d6187e59ff6298643f0cf Mon Sep 17 00:00:00 2001 From: cron Date: Wed, 9 Dec 2020 06:42:39 +0000 Subject: [PATCH 265/266] README: minor grammar change test change --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a06c3e257..b327f2f61 100644 --- a/README.md +++ b/README.md @@ -95,11 +95,11 @@ Where each location is on each platform: * `bin` = `bin` * `share` = `.` * `user` = `.` -* Windows installed: +* Windows installation: * `bin` = `C:\Program Files\Minetest\bin (Depends on the install location)` * `share` = `C:\Program Files\Minetest (Depends on the install location)` * `user` = `%APPDATA%\Minetest` -* Linux installed: +* Linux installation: * `bin` = `/usr/bin` * `share` = `/usr/share/minetest` * `user` = `~/.minetest` From 33a69f694aeb7bdbf1c15e7759ee0d01448ceb6c Mon Sep 17 00:00:00 2001 From: cron Date: Wed, 9 Dec 2020 07:06:09 +0000 Subject: [PATCH 266/266] Prepare for merge Move builtin cheats to Dragonfire modpack --- .../client/{cheats/init.lua => cheats.lua} | 40 +---- builtin/client/cheats/chat.lua | 43 ----- builtin/client/cheats/combat.lua | 77 --------- builtin/client/cheats/inventory.lua | 163 ------------------ builtin/client/cheats/movement.lua | 41 ----- builtin/client/cheats/player.lua | 39 ----- builtin/client/cheats/render.lua | 14 -- builtin/client/cheats/world.lua | 74 -------- builtin/client/init.lua | 2 +- src/defaultsettings.cpp | 2 +- 10 files changed, 4 insertions(+), 491 deletions(-) rename builtin/client/{cheats/init.lua => cheats.lua} (59%) delete mode 100644 builtin/client/cheats/chat.lua delete mode 100644 builtin/client/cheats/combat.lua delete mode 100644 builtin/client/cheats/inventory.lua delete mode 100644 builtin/client/cheats/movement.lua delete mode 100644 builtin/client/cheats/player.lua delete mode 100644 builtin/client/cheats/render.lua delete mode 100644 builtin/client/cheats/world.lua diff --git a/builtin/client/cheats/init.lua b/builtin/client/cheats.lua similarity index 59% rename from builtin/client/cheats/init.lua rename to builtin/client/cheats.lua index 30c3fe208..d920cf6de 100644 --- a/builtin/client/cheats/init.lua +++ b/builtin/client/cheats.lua @@ -1,12 +1,8 @@ core.cheats = { ["Combat"] = { - ["Killaura"] = "killaura", - ["Forcefield"] = "forcefield", ["AntiKnockback"] = "antiknockback", ["FastHit"] = "spamclick", ["AttachmentFloat"] = "float_above_parent", - ["CrystalPvP"] = "crystal_pvp", - ["AutoTotem"] = "autototem", ["ThroughWalls"] = "dont_point_nodes", ["AutoHit"] = "autohit", }, @@ -17,8 +13,6 @@ core.cheats = { ["AutoJump"] = "autojump", ["Jesus"] = "jesus", ["NoSlow"] = "no_slow", - ["AutoSneak"] = "autosneak", - ["AutoSprint"] = "autosprint", ["SpeedOverride"] = "override_speed", ["JumpOverride"] = "override_jump", ["GravityOverride"] = "override_gravity", @@ -39,7 +33,6 @@ core.cheats = { ["PlayerTracers"] = "enable_player_tracers", ["NodeESP"] = "enable_node_esp", ["NodeTracers"] = "enable_node_tracers", - ["NoWeather"] = "noweather", }, ["World"] = { ["FastDig"] = "fastdig", @@ -47,12 +40,6 @@ core.cheats = { ["AutoDig"] = "autodig", ["AutoPlace"] = "autoplace", ["InstantBreak"] = "instant_break", - ["Scaffold"] = "scaffold", - ["ScaffoldPlus"] = "scaffold_plus", - ["BlockWater"] = "block_water", - ["PlaceOnTop"] = "autotnt", - ["Replace"] = "replace", - ["Nuke"] = "nuke", }, ["Exploit"] = { ["EntitySpeed"] = "entity_speed", @@ -65,35 +52,12 @@ core.cheats = { ["UnlimitedRange"] = "increase_tool_range_plus", ["PointLiquids"] = "point_liquids", ["PrivBypass"] = "priv_bypass", - ["AutoRespawn"] = "autorespawn", }, - ["Chat"] = { - ["IgnoreStatus"] = "ignore_status_messages", - ["Deathmessages"] = "mark_deathmessages", - ["ColoredChat"] = "use_chat_color", - ["ReversedChat"] = "chat_reverse", - }, - ["Inventory"] = { - ["AutoEject"] = "autoeject", - ["AutoTool"] = "autotool", - ["Enderchest"] = function() core.open_enderchest() end, - ["HandSlot"] = function() core.open_handslot() end, - ["Strip"] = "strip", - ["AutoRefill"] = "autorefill", - } + ["Chat"] = {}, + ["Inventory"] = {} } function core.register_cheat(cheatname, category, func) core.cheats[category] = core.cheats[category] or {} core.cheats[category][cheatname] = func end - -local cheatpath = core.get_builtin_path() .. "client" .. DIR_DELIM .. "cheats" .. DIR_DELIM - -dofile(cheatpath .. "chat.lua") -dofile(cheatpath .. "combat.lua") -dofile(cheatpath .. "inventory.lua") -dofile(cheatpath .. "movement.lua") -dofile(cheatpath .. "player.lua") -dofile(cheatpath .. "render.lua") -dofile(cheatpath .. "world.lua") diff --git a/builtin/client/cheats/chat.lua b/builtin/client/cheats/chat.lua deleted file mode 100644 index 0763909df..000000000 --- a/builtin/client/cheats/chat.lua +++ /dev/null @@ -1,43 +0,0 @@ -core.register_on_receiving_chat_message(function(message) - if message:sub(1, 1) == "#" and core.settings:get_bool("ignore_status_messages") ~= false then - return true - elseif message:find('\1b@mcl_death_messages\1b') and core.settings:get_bool("mark_deathmessages") ~= false then - core.display_chat_message(core.colorize("#F25819", "[Deathmessage] ") .. message) - return true - end -end) - -function core.send_colorized(message) - local starts_with = message:sub(1, 1) - - if starts_with == "/" or starts_with == "." then return end - - local reverse = core.settings:get_bool("chat_reverse") - - if reverse then - local msg = "" - for i = 1, #message do - msg = message:sub(i, i) .. msg - end - message = msg - end - - local use_chat_color = core.settings:get_bool("use_chat_color") - local color = core.settings:get("chat_color") - - if use_chat_color and color then - local msg - if color == "rainbow" then - msg = core.rainbow(message) - else - msg = core.colorize(color, message) - end - message = msg - end - - core.send_chat_message(message) - return true -end - -core.register_on_sending_chat_message(core.send_colorized) - diff --git a/builtin/client/cheats/combat.lua b/builtin/client/cheats/combat.lua deleted file mode 100644 index 4b753eabd..000000000 --- a/builtin/client/cheats/combat.lua +++ /dev/null @@ -1,77 +0,0 @@ -local placed_crystal -local switched_to_totem = 0 -local used_sneak = true -local totem_move_action = InventoryAction("move") -totem_move_action:to("current_player", "main", 9) - -core.register_list_command("friend", "Configure Friend List (friends dont get attacked by Killaura or Forcefield)", "friendlist") - -core.register_globalstep(function(dtime) - local player = core.localplayer - if not player then return end - local control = player:get_control() - local pointed = core.get_pointed_thing() - local item = player:get_wielded_item():get_name() - if core.settings:get_bool("killaura") or core.settings:get_bool("forcefield") and control.dig then - local friendlist = core.settings:get("friendlist"):split(",") - for _, obj in ipairs(core.get_objects_inside_radius(player:get_pos(), 5)) do - local do_attack = true - if obj:is_local_player() then - do_attack = false - else - for _, friend in ipairs(friendlist) do - if obj:get_name() == friend or obj:get_nametag() == friend then - do_attack = false - break - end - end - end - if do_attack then - obj:punch() - end - end - elseif core.settings:get_bool("crystal_pvp") then - if placed_crystal then - if core.switch_to_item("mobs_mc:totem") then - switched_to_totem = 5 - end - placed_crystal = false - elseif switched_to_totem > 0 then - if item ~= "mobs_mc:totem" then - switched_to_totem = 0 - elseif pointed and pointed.type == "object" then - pointed.ref:punch() - switched_to_totem = 0 - else - switched_to_totem = switched_to_totem - end - elseif control.place and item == "mcl_end:crystal" then - placed_crystal = true - elseif control.sneak then - if pointed and pointed.type == "node" and not used_sneak then - local pos = core.get_pointed_thing_position(pointed) - local node = core.get_node_or_nil(pos) - if node and (node.name == "mcl_core:obsidian" or node.name == "mcl_core:bedrock") then - core.switch_to_item("mcl_end:crystal") - core.place_node(pos) - placed_crystal = true - end - end - used_sneak = true - else - used_sneak = false - end - end - - if core.settings:get_bool("autototem") then - local totem_stack = core.get_inventory("current_player").main[9] - if totem_stack and totem_stack:get_name() ~= "mobs_mc:totem" then - local totem_index = core.find_item("mobs_mc:totem") - if totem_index then - totem_move_action:from("current_player", "main", totem_index) - totem_move_action:apply() - player:set_wield_index(9) - end - end - end -end) diff --git a/builtin/client/cheats/inventory.lua b/builtin/client/cheats/inventory.lua deleted file mode 100644 index 024bc5e98..000000000 --- a/builtin/client/cheats/inventory.lua +++ /dev/null @@ -1,163 +0,0 @@ -local drop_action = InventoryAction("drop") - -local strip_move_act = InventoryAction("move") -strip_move_act:to("current_player", "craft", 1) -local strip_craft_act = InventoryAction("craft") -strip_craft_act:craft("current_player") -local strip_move_back_act = InventoryAction("move") -strip_move_back_act:from("current_player", "craftresult", 1) - -core.register_globalstep(function(dtime) - local player = core.localplayer - if not player then return end - local item = player:get_wielded_item() - local itemdef = core.get_item_def(item:get_name()) - local wieldindex = player:get_wield_index() - -- AutoRefill - if core.settings:get_bool("autorefill") and itemdef then - local space = item:get_free_space() - local i = core.find_item(item:get_name(), wieldindex + 1) - if i and space > 0 then - local move_act = InventoryAction("move") - move_act:to("current_player", "main", wieldindex) - move_act:from("current_player", "main", i) - move_act:set_count(space) - move_act:apply() - end - end - -- Strip - if core.settings:get_bool("strip") then - if itemdef and itemdef.groups.tree and player:get_control().place then - strip_move_act:from("current_player", "main", wieldindex) - strip_move_back_act:to("current_player", "main", wieldindex) - strip_move_act:apply() - strip_craft_act:apply() - strip_move_back_act:apply() - end - end - -- AutoEject - if core.settings:get_bool("autoeject") then - local list = (core.settings:get("eject_items") or ""):split(",") - local inventory = core.get_inventory("current_player") - for index, stack in pairs(inventory.main) do - if table.indexof(list, stack:get_name()) ~= -1 then - drop_action:from("current_player", "main", index) - drop_action:apply() - end - end - end -end) - -core.register_list_command("eject", "Configure AutoEject", "eject_items") - --- AutoTool - -local function check_tool(stack, node_groups, old_best_time) - local toolcaps = stack:get_tool_capabilities() - if not toolcaps then return end - local best_time = old_best_time - for group, groupdef in pairs(toolcaps.groupcaps) do - local level = node_groups[group] - if level then - local this_time = groupdef.times[level] - if this_time < best_time then - best_time = this_time - end - end - end - return best_time < old_best_time, best_time -end - -local function find_best_tool(nodename) - local player = core.localplayer - local inventory = core.get_inventory("current_player") - local node_groups = core.get_node_def(nodename).groups - local new_index = player:get_wield_index() - local is_better, best_time = false, math.huge - is_better, best_time = check_tool(player:get_wielded_item(), node_groups, best_time) - is_better, best_time = check_tool(inventory.hand[1], node_groups, best_time) - for index, stack in ipairs(inventory.main) do - is_better, best_time = check_tool(stack, node_groups, best_time) - if is_better then - new_index = index - end - end - return new_index -end - -function core.select_best_tool(nodename) - core.localplayer:set_wield_index(find_best_tool(nodename)) -end - -local new_index, old_index, pointed_pos - -core.register_on_punchnode(function(pos, node) - if core.settings:get_bool("autotool") then - pointed_pos = pos - old_index = old_index or core.localplayer:get_wield_index() - new_index = find_best_tool(node.name) - end -end) - -core.register_globalstep(function() - local player = core.localplayer - if not new_index then return end - if core.settings:get_bool("autotool") then - local pt = core.get_pointed_thing() - if pt and pt.type == "node" and vector.equals(core.get_pointed_thing_position(pt), pointed_pos) and player:get_control().dig then - player:set_wield_index(new_index) - return - end - end - player:set_wield_index(old_index) - new_index, old_index, pointed_pos = nil -end) - --- Enderchest - -function get_itemslot_bg(x, y, w, h) - local out = "" - for i = 0, w - 1, 1 do - for j = 0, h - 1, 1 do - out = out .."image["..x+i..","..y+j..";1,1;mcl_formspec_itemslot.png]" - end - end - return out -end - -local enderchest_formspec = "size[9,8.75]".. - "label[0,0;"..minetest.formspec_escape(minetest.colorize("#313131", "Ender Chest")).."]".. - "list[current_player;enderchest;0,0.5;9,3;]".. - get_itemslot_bg(0,0.5,9,3).. - "label[0,4.0;"..minetest.formspec_escape(minetest.colorize("#313131", "Inventory")).."]".. - "list[current_player;main;0,4.5;9,3;9]".. - get_itemslot_bg(0,4.5,9,3).. - "list[current_player;main;0,7.74;9,1;]".. - get_itemslot_bg(0,7.74,9,1).. - "listring[current_player;enderchest]".. - "listring[current_player;main]" - -function core.open_enderchest() - core.show_formspec("__builtin__:enderchest", enderchest_formspec) -end - --- HandSlot - -local hand_formspec = "size[9,8.75]".. - "label[0,0;"..minetest.formspec_escape(minetest.colorize("#313131", "Hand")).."]".. - "list[current_player;hand;0,0.5;1,1;]".. - get_itemslot_bg(0,0.5,1,1).. - "label[0,4.0;"..minetest.formspec_escape(minetest.colorize("#313131", "Inventory")).."]".. - "list[current_player;main;0,4.5;9,3;9]".. - get_itemslot_bg(0,4.5,9,3).. - "list[current_player;main;0,7.74;9,1;]".. - get_itemslot_bg(0,7.74,9,1).. - "listring[current_player;hand]".. - "listring[current_player;main]" - -function core.open_handslot() - core.show_formspec("__builtin__:hand", hand_formspec) -end - - - diff --git a/builtin/client/cheats/movement.lua b/builtin/client/cheats/movement.lua deleted file mode 100644 index 33a46fca0..000000000 --- a/builtin/client/cheats/movement.lua +++ /dev/null @@ -1,41 +0,0 @@ -local function register_keypress_cheat(cheat, keyname, condition) - local was_active = false - core.register_globalstep(function() - local is_active = core.settings:get_bool(cheat) and (not condition or condition()) - if is_active then - core.set_keypress(keyname, true) - elseif was_active then - core.set_keypress(keyname, false) - end - was_active = is_active - end) -end - -register_keypress_cheat("autosneak", "sneak", function() - return core.localplayer:is_touching_ground() -end) -register_keypress_cheat("autosprint", "special1") - -local legit_override - -local function get_override_factor(name) - if core.settings:get_bool("override_" .. name) then - return tonumber(core.settings:get("override_" .. name .. "_factor")) or 1 - else - return 1.0 - end -end - -core.register_globalstep(function() - if not legit_override then return end - local override = table.copy(legit_override) - override.speed = override.speed * get_override_factor("speed") - override.jump = override.jump * get_override_factor("jump") - override.gravity = override.gravity * get_override_factor("gravity") - core.localplayer:set_physics_override(override) -end) - -core.register_on_recieve_physics_override(function(override) - legit_override = override - return true -end) diff --git a/builtin/client/cheats/player.lua b/builtin/client/cheats/player.lua deleted file mode 100644 index 499ed47f3..000000000 --- a/builtin/client/cheats/player.lua +++ /dev/null @@ -1,39 +0,0 @@ -local death_formspec = "" - .. "size[11,5.5]" - .. "bgcolor[#320000b4;true]" - .. "label[4.85,1.35;" .. "You died" .. "]" - .. "button_exit[2,3;3,0.5;btn_respawn;" .. "Respawn" .. "]" - .. "button_exit[6,3;3,0.5;btn_ghost_mode;" .. "Ghost Mode" .. "]" - .. "set_focus[btn_respawn;true]" - -core.register_on_death(function() - core.display_chat_message("You died at " .. core.pos_to_string(vector.round(core.localplayer:get_pos())) .. ".") - if core.settings:get_bool("autorespawn") then - core.send_respawn() - else - core.show_formspec("__builtin__:death", death_formspec) - end -end) - -core.register_on_formspec_input(function(formname, fields) - if formname == "__builtin__:death" then - if fields.btn_ghost_mode then - core.display_chat_message("You are in ghost mode. Use .respawn to Respawn.") - else - core.send_respawn() - end - end -end) - -core.register_chatcommand("respawn", { - description = "Respawn when in ghost mode", - func = function() - if core.localplayer:get_hp() == 0 then - core.send_respawn() - core.display_chat_message("Respawned.") - else - core.display_chat_message("You are not in ghost mode.") - end - end -}) - diff --git a/builtin/client/cheats/render.lua b/builtin/client/cheats/render.lua deleted file mode 100644 index dc3f71247..000000000 --- a/builtin/client/cheats/render.lua +++ /dev/null @@ -1,14 +0,0 @@ -core.register_list_command("xray", "Configure X-Ray", "xray_nodes") -core.register_list_command("search", "Configure NodeESP", "node_esp_nodes") - -core.register_on_spawn_particle(function(particle) - if core.settings:get_bool("noweather") and particle.texture:sub(1, 12) == "weather_pack" then - return true - end -end) - -core.register_on_play_sound(function(sound) - if core.settings:get_bool("noweather") and sound.name == "weather_rain" then - return true - end -end) diff --git a/builtin/client/cheats/world.lua b/builtin/client/cheats/world.lua deleted file mode 100644 index d537036a9..000000000 --- a/builtin/client/cheats/world.lua +++ /dev/null @@ -1,74 +0,0 @@ -core.register_on_dignode(function(pos) - if core.settings:get_bool("replace") then - core.after(0, minetest.place_node, pos) - end -end) - -local etime = 0 - -core.register_globalstep(function(dtime) - etime = etime + dtime - if etime < 1 then return end - local player = core.localplayer - if not player then return end - local pos = player:get_pos() - local item = player:get_wielded_item() - local def = core.get_item_def(item:get_name()) - local nodes_per_tick = tonumber(minetest.settings:get("nodes_per_tick")) or 8 - if item and item:get_count() > 0 and def and def.node_placement_prediction ~= "" then - if core.settings:get_bool("scaffold") then - local p = vector.round(vector.add(pos, {x = 0, y = -0.6, z = 0})) - local node = minetest.get_node_or_nil(p) - if not node or minetest.get_node_def(node.name).buildable_to then - core.place_node(p) - end - elseif core.settings:get_bool("scaffold_plus") then - local z = pos.z - local positions = { - {x = 0, y = -0.6, z = 0}, - {x = 1, y = -0.6, z = 0}, - {x = -1, y = -0.6, z = 0}, - {x = -1, y = -0.6, z = -1}, - {x = 0, y = -0.6, z = -1}, - {x = 1, y = -0.6, z = -1}, - {x = -1, y = -0.6, z = 1}, - {x = 0, y = -0.6, z = 1}, - {x = 1, y = -0.6, z = 1} - } - for i, p in pairs(positions) do - core.place_node(vector.add(pos, p)) - end - elseif core.settings:get_bool("block_water") then - local positions = core.find_nodes_near(pos, 5, {"mcl_core:water_source", "mcl_core:water_floating"}, true) - for i, p in pairs(positions) do - if i > nodes_per_tick then return end - core.place_node(p) - end - elseif core.settings:get_bool("autotnt") then - local positions = core.find_nodes_near_under_air_except(pos, 5, item:get_name(), true) - for i, p in pairs(positions) do - if i > nodes_per_tick then return end - core.place_node(vector.add(p, {x = 0, y = 1, z = 0})) - end - end - end - if core.settings:get_bool("nuke") then - local i = 0 - for x = pos.x - 4, pos.x + 4 do - for y = pos.y - 4, pos.y + 4 do - for z = pos.z - 4, pos.z + 4 do - local p = vector.new(x, y, z) - local node = core.get_node_or_nil(p) - local def = node and core.get_node_def(node.name) - if def and def.diggable then - if i > nodes_per_tick then return end - core.dig_node(p) - i = i + 1 - end - end - end - end - end -end) - - diff --git a/builtin/client/init.lua b/builtin/client/init.lua index 40acf3b9b..44703a57c 100644 --- a/builtin/client/init.lua +++ b/builtin/client/init.lua @@ -9,5 +9,5 @@ dofile(commonpath .. "chatcommands.lua") dofile(commonpath .. "vector.lua") dofile(clientpath .. "util.lua") dofile(clientpath .. "chatcommands.lua") -dofile(clientpath .. "cheats"..DIR_DELIM.."init.lua") +dofile(clientpath .. "cheats.lua") diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 7ca30697b..21ef23fc3 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -413,7 +413,7 @@ void set_default_settings(Settings *settings) settings->setDefault("chat_font_size", "0"); // Default "font_size" // ContentDB - settings->setDefault("contentdb_url", "http://cheatdb.elidragon.com"); + settings->setDefault("contentdb_url", "127.0.0.1"); // http://cheatdb.elidragon.com in DF settings->setDefault("contentdb_max_concurrent_downloads", "3"); #ifdef __ANDROID__

        L$DSL_8dtlk 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

      Cen21N+!g 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 bKSI6q=s_NX;(>*;oYl1XM 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≧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%0Agya_5Lyzc9$BBbP8=EJd(6CC6*s@*C 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